其他分享
首页 > 其他分享> > ijkplayer 代码走读之 read_thread 线程中 av_read_frame() 数据流读取过程详解

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

作者:互联网

回顾

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;
}

总结:

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

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