ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

ijkplayer 代码走读之 read_thread 线程中 av_read_frame() 数据流读取过程详解

2021-11-29 19:03:05  阅读:226  来源: 互联网

标签:ijkplayer stream read 走读 packet ffp pkt st


回顾

ijkplayer 开机过程:

  1. 用户在 Android 程序中,调用封装接口 IjkLibLoader 方法,装载 ijkffmpeg、ijksdl和ijkplayer三个库文件到安卓系统;
  2. 初始化播放器,调用的JNI接口程序 native_setup() 函数,此函数创建播放器消息队列和播放其相关参数;
  3. 用户在 Android 程序中,调用 createPlayer() 和 prepareAsync() 封装接口函数创建播放器,并让播放器进入待播放状态;
  4. 启动播放器。

前面分析过 prepareAsync() 函数相关内容,其中比较重要函数是 VideoState *is = stream_open(ffp, file_name, NULL);
在函数中:

  1. 建立 3 个队列,视频、音频和副标题队列,is中存放三个流 AVStream *audio_st、*subtitle_st、*video_st。
  2. 建立 2 个线程,read_thread() 和 video_refresh() 线程;
  3. 初始化解码器相关参数,函数退出。
    播放器就具备播放的能力,这个过程涵盖 ijkplayer 源码绝大部分内容; 进入播放过程中,程序逻辑架构已经建立起来,在运行期间能够
    处理一些用户交换功能。

再下来回顾 read_thread() 线程功能,总结如下:

  1. 调用 avformat_open_input() 函数,此函数根据数据源选择网络协议、解封装器类别,通过用户 URL地址关键字区分,
    如: “tcpext://192.168.1.31:1717/v-out.h264”, ijkplayer播放就解析出协议是tcpext、解封装器是h264方式。

  2. 调用 avformat_find_stream_info(ic, opts) 函数, 此函数通过数据流数据识别编码格式,得出该数据流应该配置
    什么样的解码器。在进入此函数时,该 AVFormatContext 中流的数量和种类就确定下来了,是在什么时候确认的呢?存疑.

  3. 调用 stream_component_open(ffp, st_index[AVMEDIA_TYPE_VIDEO])函数, 根据数据流信息为该数据流构建解码器,
    分别针对视频、音频和副标题流类型,配置解码器。

  4. 进入线程的循环体,av_read_frame(ic, pkt) -> packet_queue_put(&is->videoq, &copy),读取-> 入队过程反复循环。

笼统描述程序主体逻辑,大致就这么个逻辑。

本篇主要分析数据流读取过程,清晰目标,开启代码走读模式。

read_thread() 线程

我们先看看 read_thread() 线程中此部分相关简化逻辑代码,如下:

void read_thread(void *arg)
{
    FFPlayer *ffp = arg;                                            ///> 此参数是Android用户空间传递相关参数
    VideoState *is = ffp->is;
    AVFormatContext *ic = NULL;
    int err, i, ret __unused;
    int st_index[AVMEDIA_TYPE_NB];
    AVPacket pkt1, *pkt = &pkt1;

    ///> 数据流编码格式识别部分代码 1 部分
    if (ffp->find_stream_info) {
        AVDictionary **opts = setup_find_stream_info_opts(ic, ffp->codec_opts);  ///> 获取解码器参数字典指针
        int orig_nb_streams = ic->nb_streams;

        do {
            if (av_stristart(is->filename, "data:", NULL) && orig_nb_streams > 0) {
                for (i = 0; i < orig_nb_streams; i++) {
                    if (!ic->streams[i] || !ic->streams[i]->codecpar || ic->streams[i]->codecpar->profile == FF_PROFILE_UNKNOWN) {
                        break;
                    }
                }

                if (i == orig_nb_streams) {
                    break;
                }
            }
            err = avformat_find_stream_info(ic, opts);                          ///> 进入匹配查找过程,在解码器 options 字典中 flag 标识流的种类
        } while(0); 
        ffp_notify_msg1(ffp, FFP_MSG_FIND_STREAM_INFO);
    }

    is->realtime = is_realtime(ic);
    av_dump_format(ic, 0, is->filename, 0);

    ///> 数据流编码格式识别部分代码 2 部分
    int video_stream_count = 0;
    int h264_stream_count = 0;
    int first_h264_stream = -1;
    for (i = 0; i < ic->nb_streams; i++) {
        AVStream *st = ic->streams[i];
        enum AVMediaType type = st->codecpar->codec_type;
        st->discard = AVDISCARD_ALL;
        if (type >= 0 && ffp->wanted_stream_spec[type] && st_index[type] == -1)
            if (avformat_match_stream_specifier(ic, st, ffp->wanted_stream_spec[type]) > 0)
                st_index[type] = i;

        // choose first h264

        if (type == AVMEDIA_TYPE_VIDEO) {
            enum AVCodecID codec_id = st->codecpar->codec_id;
            video_stream_count++;
            if (codec_id == AV_CODEC_ID_H264) {
                h264_stream_count++;
                if (first_h264_stream < 0)
                    first_h264_stream = i;
            }
        }
        av_log(NULL, AV_LOG_INFO, "DEBUG %s, LINE:%d ,CODEC_ID:%d\n",__FILE__, __LINE__, (uint32_t)st->codecpar->codec_id);
    }

    ///> 如果是多流模式
    if (video_stream_count > 1 && st_index[AVMEDIA_TYPE_VIDEO] < 0) {
        st_index[AVMEDIA_TYPE_VIDEO] = first_h264_stream;
        av_log(NULL, AV_LOG_WARNING, "multiple video stream found, prefer first h264 stream: %d\n", first_h264_stream);
    }

    ///> 逐一匹配解码器
    if (!ffp->video_disable)
        st_index[AVMEDIA_TYPE_VIDEO] =
            av_find_best_stream(ic, AVMEDIA_TYPE_VIDEO,
                                st_index[AVMEDIA_TYPE_VIDEO], -1, NULL, 0);
    if (!ffp->audio_disable)
        st_index[AVMEDIA_TYPE_AUDIO] =
            av_find_best_stream(ic, AVMEDIA_TYPE_AUDIO,
                                st_index[AVMEDIA_TYPE_AUDIO],
                                st_index[AVMEDIA_TYPE_VIDEO],
                                NULL, 0);
    if (!ffp->video_disable && !ffp->subtitle_disable)
        st_index[AVMEDIA_TYPE_SUBTITLE] =
            av_find_best_stream(ic, AVMEDIA_TYPE_SUBTITLE,
                                st_index[AVMEDIA_TYPE_SUBTITLE],
                                (st_index[AVMEDIA_TYPE_AUDIO] >= 0 ?
                                 st_index[AVMEDIA_TYPE_AUDIO] :
                                 st_index[AVMEDIA_TYPE_VIDEO]),
                                NULL, 0);

    is->show_mode = ffp->show_mode;
    ///* open the streams,打开流格式 */
    if (st_index[AVMEDIA_TYPE_AUDIO] >= 0) {
        stream_component_open(ffp, st_index[AVMEDIA_TYPE_AUDIO]);
    } else {
        ffp->av_sync_type = AV_SYNC_VIDEO_MASTER;
        is->av_sync_type  = ffp->av_sync_type;
    }

    ret = -1;
    if (st_index[AVMEDIA_TYPE_VIDEO] >= 0) {
        ret = stream_component_open(ffp, st_index[AVMEDIA_TYPE_VIDEO]);
    }
    if (is->show_mode == SHOW_MODE_NONE)
        is->show_mode = ret >= 0 ? SHOW_MODE_VIDEO : SHOW_MODE_RDFT;

    if (st_index[AVMEDIA_TYPE_SUBTITLE] >= 0) {
        stream_component_open(ffp, st_index[AVMEDIA_TYPE_SUBTITLE]);
    }
    ffp_notify_msg1(ffp, FFP_MSG_COMPONENT_OPEN);

    ///> 通知 android 空间程序
    if (!ffp->ijkmeta_delay_init) {
        ijkmeta_set_avformat_context_l(ffp->meta, ic);
    }

    ///> 设置字典项的状态
    ffp->stat.bit_rate = ic->bit_rate;
    if (st_index[AVMEDIA_TYPE_VIDEO] >= 0)
        ijkmeta_set_int64_l(ffp->meta, IJKM_KEY_VIDEO_STREAM, st_index[AVMEDIA_TYPE_VIDEO]);
    if (st_index[AVMEDIA_TYPE_AUDIO] >= 0)
        ijkmeta_set_int64_l(ffp->meta, IJKM_KEY_AUDIO_STREAM, st_index[AVMEDIA_TYPE_AUDIO]);
    if (st_index[AVMEDIA_TYPE_SUBTITLE] >= 0)
        ijkmeta_set_int64_l(ffp->meta, IJKM_KEY_TIMEDTEXT_STREAM, st_index[AVMEDIA_TYPE_SUBTITLE]);

    ///> 播放器状态调节
    ffp->prepared = true;
    ffp_notify_msg1(ffp, FFP_MSG_PREPARED);
    if (ffp->auto_resume) {
        ffp_notify_msg1(ffp, FFP_REQ_START);
        ffp->auto_resume = 0;
    }
    /* offset should be seeked*/
    if (ffp->seek_at_start > 0) {
        ffp_seek_to_l(ffp, (long)(ffp->seek_at_start));
    }

    ///> 进入循环播放状态,线程循环体
    for (;;){

        ///>
        if (is->queue_attachments_req) {  ///> 在打开流的时候配置此标识 = 1
            if (is->video_st && (is->video_st->disposition & AV_DISPOSITION_ATTACHED_PIC)) {
                AVPacket copy = { 0 };
                if ((ret = av_packet_ref(&copy, &is->video_st->attached_pic)) < 0)
                    goto fail;
                packet_queue_put(&is->videoq, &copy);
                packet_queue_put_nullpacket(&is->videoq, is->video_stream);
            }
            is->queue_attachments_req = 0;
        }
        ///> 
        pkt->flags = 0;
        ret = av_read_frame(ic, pkt);
        ///>
        if (pkt->flags & AV_PKT_FLAG_DISCONTINUITY) {
            if (is->audio_stream >= 0) {
                packet_queue_put(&is->audioq, &flush_pkt);
            }
            if (is->subtitle_stream >= 0) {
                packet_queue_put(&is->subtitleq, &flush_pkt);
            }
            if (is->video_stream >= 0) {
                packet_queue_put(&is->videoq, &flush_pkt);
            }
        }
        ///>
        if (pkt->stream_index == is->audio_stream && pkt_in_play_range) {
            packet_queue_put(&is->audioq, pkt);
        } else if (pkt->stream_index == is->video_stream && pkt_in_play_range
                   && !(is->video_st && (is->video_st->disposition & AV_DISPOSITION_ATTACHED_PIC))) {
            packet_queue_put(&is->videoq, pkt);
        } else if (pkt->stream_index == is->subtitle_stream && pkt_in_play_range) {
            packet_queue_put(&is->subtitleq, pkt);
        } else {
            av_packet_unref(pkt);
        }
        ///>
        ffp_statistic_l(ffp);
        av_log(NULL, AV_LOG_INFO, " %s / %s , LINE:%d \n",__FILE__, __func__, __LINE__);
    }
}

此程序是简化版结构内容,各节点有标注信息。

读取数据流

在 read_thread() 线程执行 av_read_frame(ic, pkt) 函数循环读取数据流内容函数,进行跟踪梳理函数调用关系如下。

av_read_frame(ic, pkt);                             ///> 入口参数: AVFormatContext *ic
    -> read_frame_internal(s, pkt);
        -> ff_read_packet(s, &cur_pkt);             ///> 入口参数: AVPacket cur_pkt;
            -> av_init_packet(pkt);
            -> s->iformat->read_packet(s, pkt);     ///> 此处 read_packet 调用是 ff_raw_read_partial_packet(AVFormatContext *s, AVPacket *pkt) 函数,在 libavformat/rawdec.c 文件中
                -> av_new_packet(pkt, size)
                -> avio_read_partial(s->pb, pkt->data, size); ///> 入口参数: AVIOContext s->pb, 函数在 libavformat/aviobuf.c 文件中
                    -> s->read_packet(s->opaque, buf, size);  ///> 此 read_packet 调用是 io_read_packet() 函数,此函数最终调用 tcp_read() 函数,见下面分析。
                    -> memcpy(buf, s->buf_ptr, len);
                    -> s->buf_ptr += len;
                    -> return len;
                -> av_shrink_packet(pkt, ret);
        -> av_parser_init(st->codecpar->codec_id)
        -> avcodec_get_name(st->codecpar->codec_id)
        -> compute_pkt_fields(s, st, NULL, pkt, AV_NOPTS_VALUE, AV_NOPTS_VALUE)
        -> read_from_packet_buffer(&s->internal->parse_queue, &s->internal->parse_queue_end, pkt)
        -> update_stream_avctx(s);
    -> add_to_pktbuf(&s->internal->packet_buffer, pkt,&s->internal->packet_buffer_end, 1);

下面分析入口参数之间关系, 函数调用关系 io_read_packet() -> ffurl_read() -> tcp_read() 导图。
先从函数入口参数梳理,如下。
函数入口第一个参数 AVFormatContext -> AVIOContext -> opaque 入口参数来源关系导图,在 AVIOContext 结构体定义中为 void * opaque 类型。

///> 函数入口参数是 s->opaque 内容
static int io_read_packet(void *opaque, uint8_t *buf, int buf_size)
{
    AVIOInternal *internal = opaque;                    ///> 此处直接赋值转换成 AVIOInternal 指针,入口传递过来的 AVIOContext s->pb,
    return ffurl_read(internal->h, buf, buf_size);      ///> ffurl_read 调用是 tcp_read() 增加的私有协议中的函数。
}

//> 结构体 AVIOInternal 定义如下
typedef struct AVIOInternal {
    URLContext *h;
} AVIOInternal;

//> 结构体 URLContext 定义如下
typedef struct URLContext {
    const AVClass *av_class;    /**< information for av_log(). Set by url_open(). */
    const struct URLProtocol *prot;
    void *priv_data;
    char *filename;             /**< specified URL */
    int flags;
    int max_packet_size;        /**< if non zero, the stream is packetized with this max packet size */
    int is_streamed;            /**< true if streamed (no seek possible), default = false */
    int is_connected;
    AVIOInterruptCB interrupt_callback;
    int64_t rw_timeout;         /**< maximum time to wait for (network) read/write operation completion, in mcs */
    const char *protocol_whitelist;
    const char *protocol_blacklist;
    int min_packet_size;        /**< if non zero, the stream is packetized with this min packet size */
    int64_t pts;                ///< 增加 pts 变量
} URLContext;

///> 此函数的入口 URLContext *h 指针内容类型如上图。
static int tcp_read(URLContext *h, uint8_t *buf, int size)
{
    uint8_t header[HEADER_SIZE];
    TCPEXTContext *s = h->priv_data;
    int ret;

    if (!(h->flags & AVIO_FLAG_NONBLOCK)) {
        ret = ff_network_wait_fd_timeout(s->fd, 0, h->rw_timeout, &h->interrupt_callback);
        if (ret)
            return ret;
    }

    ret = recv(s->fd, header, HEADER_SIZE, MSG_WAITALL);
    if(ret < HEADER_SIZE){
        av_log(NULL, AV_LOG_INFO, "%s/%s(), LINE:%d ,READ_HEADER_AIL length:%d \n",__FILE__, __func__, __LINE__, ret);
        return 0;
    }
    uint32_t msb = header[0] << 24 | header[1] << 16 | header[2] << 8 | header[3];
    uint32_t lsb = header[4] << 24 | header[5] << 16 | header[6] << 8 | header[7];
    uint32_t len = header[8] << 24 | header[9] << 16 | header[10] << 8 | header[11];
    uint64_t pts = msb << 32 | lsb ;
    av_log(NULL, AV_LOG_INFO, "READ HEADER msb:%08x, lsb:%08x, len:%08x \n", msb, lsb, len);    
    assert( pts == NO_PTS || (pts & 0x8000000000000000) == 0);
    assert(len);

    ret = recv(s->fd, buf, len, MSG_WAITALL);
    if (ret > 0){
        av_application_did_io_tcp_read(s->app_ctx, (void*)h, ret);
        uint32_t hsb = buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3];
        msb = buf[4] << 24 | buf[5] << 16 | buf[6] << 8 | buf[7];
        lsb = buf[8] << 24 | buf[9] << 16 | buf[10] << 8 | buf[11];
        av_log(NULL, AV_LOG_INFO, "H264 HEADER hsb:%08x, msb:%08x, lsb:%08x \n", hsb, msb, lsb);
    }
    av_log(NULL, AV_LOG_INFO, "%s/%s(), LINE:%d ,recv length:%d \n",__FILE__, __func__, __LINE__, ret);
    return ret < 0 ? ff_neterrno() : ret;
}

总结:

  • 1>. 线程 read_thread() 定义 AVFormatContext ic,AVPacket pkt1,全局变量, 函数 av_read_frame(ic, pkt);入口参数
    都是全局变量,tcp_read函数入口 h = (URLContext
    )ic->pb->opaque, buf = pkt->data 参数。
  • 2>. 只能在 URLContext *h 中存放 pts 数据,在增加的私有解封装器 ff_raw_read_partial_packet() 函数中,把 pts 值转写
    到 pkt->pts 中。
  • 3>. 在 ijkplayer的sdk中,增加私有通讯协议和私有解封装器、程序处理思路基本类似,此模块可供参考。

此处获取的 packet_buffer 对象中有 pts 值,也就是说在读取数据时可以把当前的 pts 内容放进去,

标签:ijkplayer,stream,read,走读,packet,ffp,pkt,st
来源: https://blog.csdn.net/weixin_38387929/article/details/121616797

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有