`

ffmpeg 源代码简单分析 : av_read_frame()

 
阅读更多

 

此前写了好几篇ffmpeg源代码分析文章,列表如下:

图解FFMPEG打开媒体的函数avformat_open_input
ffmpeg 源代码简单分析 : av_register_all()
ffmpeg 源代码简单分析 : avcodec_register_all()
ffmpeg 源代码简单分析 : av_read_frame()
ffmpeg 源代码简单分析 : avcodec_decode_video2()

============================

 

ffmpeg中的av_read_frame()的作用是读取码流中的音频若干帧或者视频一帧。例如,解码视频的时候,每解码一个视频帧,需要先调用av_read_frame()获得一帧视频的压缩数据,然后才能对该数据进行解码(例如H.264中一帧压缩数据通常对应一个NAL)。

对该函数源代码的分析是很久之前做的了,现在翻出来,用博客记录一下。

 

上代码之前,先参考了其他人对av_read_frame()的解释,在此做一个参考:

通过av_read_packet(***),读取一个包,需要说明的是此函数必须是包含整数帧的,不存在半帧的情况,以ts流为例,是读取一个完整的PES包(一个完整pes包包含若干视频或音频es包),读取完毕后,通过av_parser_parse2(***)分析出视频一帧(或音频若干帧),返回,下次进入循环的时候,如果上次的数据没有完全取完,则st = s->cur_st;不会是NULL,即再此进入av_parser_parse2(***)流程,而不是下面的av_read_packet(**)流程,这样就保证了,如果读取一次包含了N帧视频数据(以视频为例),则调用av_read_frame(***)N次都不会去读数据,而是返回第一次读取的数据,直到全部解析完毕。

av_read_frame()的源代码如下:

 

//获取一个AVPacket
/*
 * av_read_frame - 新版本的ffmpeg用的是av_read_frame,而老版本的是av_read_packet
 * 。区别是av_read_packet读出的是包,它可能是半帧或多帧,不保证帧的完整性。av_read_frame对
 * av_read_packet进行了封装,使读出的数据总是完整的帧
 */
int av_read_frame(AVFormatContext *s, AVPacket *pkt)
{
    const int genpts = s->flags & AVFMT_FLAG_GENPTS;
    int          eof = 0;

    if (!genpts)
    	/**
    	 * This buffer is only needed when packets were already buffered but
    	 * not decoded, for example to get the codec parameters in MPEG
    	 * streams.
    	 * 一般情况下会调用read_frame_internal(s, pkt)
    	 * 直接返回
    	 */
        return s->packet_buffer ? read_from_packet_buffer(s, pkt) :
                                  read_frame_internal(s, pkt);

    for (;;) {
        int ret;
        AVPacketList *pktl = s->packet_buffer;

        if (pktl) {
            AVPacket *next_pkt = &pktl->pkt;

            if (next_pkt->dts != AV_NOPTS_VALUE) {
                int wrap_bits = s->streams[next_pkt->stream_index]->pts_wrap_bits;
                while (pktl && next_pkt->pts == AV_NOPTS_VALUE) {
                    if (pktl->pkt.stream_index == next_pkt->stream_index &&
                        (av_compare_mod(next_pkt->dts, pktl->pkt.dts, 2LL << (wrap_bits - 1)) < 0) &&
                         av_compare_mod(pktl->pkt.pts, pktl->pkt.dts, 2LL << (wrap_bits - 1))) { //not b frame
                        next_pkt->pts = pktl->pkt.dts;
                    }
                    pktl = pktl->next;
                }
                pktl = s->packet_buffer;
            }

            /* read packet from packet buffer, if there is data */
            if (!(next_pkt->pts == AV_NOPTS_VALUE &&
                  next_pkt->dts != AV_NOPTS_VALUE && !eof))
                return read_from_packet_buffer(s, pkt);
        }

        ret = read_frame_internal(s, pkt);
        if (ret < 0) {
            if (pktl && ret != AVERROR(EAGAIN)) {
                eof = 1;
                continue;
            } else
                return ret;
        }

        if (av_dup_packet(add_to_pktbuf(&s->packet_buffer, pkt,
                          &s->packet_buffer_end)) < 0)
            return AVERROR(ENOMEM);
    }
}


一般情况下,av_read_frame()会调用read_frame_internal(),其代码如下所示:

 

 

//av_read_frame对他进行了封装
static int read_frame_internal(AVFormatContext *s, AVPacket *pkt)
{
    AVStream *st;
    int len, ret, i;
    //初始化
    av_init_packet(pkt);

    for(;;) {
        /* 选择当前的 input stream */
        st = s->cur_st;
        if (st) {
        	//不需要解析。不清楚哪些数据属于这类
            if (!st->need_parsing || !st->parser) {
                /* no parsing needed: we just output the packet as is */
                /* raw data support */
                *pkt = st->cur_pkt;
                st->cur_pkt.data= NULL;
                st->cur_pkt.side_data_elems = 0;
                st->cur_pkt.side_data = NULL;
                compute_pkt_fields(s, st, NULL, pkt);
                s->cur_st = NULL;
                if ((s->iformat->flags & AVFMT_GENERIC_INDEX) &&
                    (pkt->flags & AV_PKT_FLAG_KEY) && pkt->dts != AV_NOPTS_VALUE) {
                    ff_reduce_index(s, st->index);
                    av_add_index_entry(st, pkt->pos, pkt->dts, 0, 0, AVINDEX_KEYFRAME);
                }
                break;
            } //需要解析
            else if (st->cur_len > 0 && st->discard < AVDISCARD_ALL) {
            	//解析
                len = av_parser_parse2(st->parser, st->codec, &pkt->data, &pkt->size,
                                       st->cur_ptr, st->cur_len,
                                       st->cur_pkt.pts, st->cur_pkt.dts,
                                       st->cur_pkt.pos);
                st->cur_pkt.pts = AV_NOPTS_VALUE;
                st->cur_pkt.dts = AV_NOPTS_VALUE;
                /* increment read pointer */
                st->cur_ptr += len;
                st->cur_len -= len;

                /* return packet if any */
                if (pkt->size) {
                got_packet:
                    pkt->duration = 0;
                    pkt->stream_index = st->index;
                    pkt->pts = st->parser->pts;
                    pkt->dts = st->parser->dts;
                    pkt->pos = st->parser->pos;
                    if(pkt->data == st->cur_pkt.data && pkt->size == st->cur_pkt.size){
                        s->cur_st = NULL;
                        pkt->destruct= st->cur_pkt.destruct;
                        st->cur_pkt.destruct= NULL;
                        st->cur_pkt.data    = NULL;
                        assert(st->cur_len == 0);
                    }else{
                        pkt->destruct = NULL;
                    }
                    compute_pkt_fields(s, st, st->parser, pkt);

                    if((s->iformat->flags & AVFMT_GENERIC_INDEX) && pkt->flags & AV_PKT_FLAG_KEY){
                        int64_t pos= (st->parser->flags & PARSER_FLAG_COMPLETE_FRAMES) ? pkt->pos : st->parser->frame_offset;
                        ff_reduce_index(s, st->index);
                        av_add_index_entry(st, pos, pkt->dts,
                                           0, 0, AVINDEX_KEYFRAME);
                    }

                    break;
                }
            } else {
                /* free packet */
                av_free_packet(&st->cur_pkt);
                s->cur_st = NULL;
            }
        } else {
            AVPacket cur_pkt;
            /* read next packet */
            //读取AVPacket,老版本里只有av_read_packet,现在被封装了
            ret = av_read_packet(s, &cur_pkt);
            if (ret < 0) {
                if (ret == AVERROR(EAGAIN))
                    return ret;
                /* return the last frames, if any */
                for(i = 0; i < s->nb_streams; i++) {
                    st = s->streams[i];
                    if (st->parser && st->need_parsing) {
                        av_parser_parse2(st->parser, st->codec,
                                        &pkt->data, &pkt->size,
                                        NULL, 0,
                                        AV_NOPTS_VALUE, AV_NOPTS_VALUE,
                                        AV_NOPTS_VALUE);
                        if (pkt->size)
                            goto got_packet;
                    }
                }
                /* no more packets: really terminate parsing */
                return ret;
            }
            st = s->streams[cur_pkt.stream_index];
            st->cur_pkt= cur_pkt;

            if(st->cur_pkt.pts != AV_NOPTS_VALUE &&
               st->cur_pkt.dts != AV_NOPTS_VALUE &&
               st->cur_pkt.pts < st->cur_pkt.dts){
                av_log(s, AV_LOG_WARNING, "Invalid timestamps stream=%d, pts=%"PRId64", dts=%"PRId64", size=%d\n",
                    st->cur_pkt.stream_index,
                    st->cur_pkt.pts,
                    st->cur_pkt.dts,
                    st->cur_pkt.size);
//                av_free_packet(&st->cur_pkt);
//                return -1;
            }

            if(s->debug & FF_FDEBUG_TS)
                av_log(s, AV_LOG_DEBUG, "av_read_packet stream=%d, pts=%"PRId64", dts=%"PRId64", size=%d, duration=%d, flags=%d\n",
                    st->cur_pkt.stream_index,
                    st->cur_pkt.pts,
                    st->cur_pkt.dts,
                    st->cur_pkt.size,
                    st->cur_pkt.duration,
                    st->cur_pkt.flags);

            s->cur_st = st;
            st->cur_ptr = st->cur_pkt.data;
            st->cur_len = st->cur_pkt.size;
            if (st->need_parsing && !st->parser && !(s->flags & AVFMT_FLAG_NOPARSE)) {
                st->parser = av_parser_init(st->codec->codec_id);
                if (!st->parser) {
                    av_log(s, AV_LOG_VERBOSE, "parser not found for codec "
                           "%s, packets or times may be invalid.\n",
                           avcodec_get_name(st->codec->codec_id));
                    /* no parser available: just output the raw packets */
                    st->need_parsing = AVSTREAM_PARSE_NONE;
                }else if(st->need_parsing == AVSTREAM_PARSE_HEADERS){
                    st->parser->flags |= PARSER_FLAG_COMPLETE_FRAMES;
                }else if(st->need_parsing == AVSTREAM_PARSE_FULL_ONCE){
                    st->parser->flags |= PARSER_FLAG_ONCE;
                }
            }
        }
    }
    if(s->debug & FF_FDEBUG_TS)
        av_log(s, AV_LOG_DEBUG, "read_frame_internal stream=%d, pts=%"PRId64", dts=%"PRId64", size=%d, duration=%d, flags=%d\n",
            pkt->stream_index,
            pkt->pts,
            pkt->dts,
            pkt->size,
            pkt->duration,
            pkt->flags);

    return 0;
}


一般的码流都需要解析,这是需要调用av_paser_parse2(),它的代码如下所示:

 

 

//解析。例如解析264里的NAL等等
int av_parser_parse2(AVCodecParserContext *s,
                     AVCodecContext *avctx,
                     uint8_t **poutbuf, int *poutbuf_size,
                     const uint8_t *buf, int buf_size,
                     int64_t pts, int64_t dts,
                     int64_t pos)
{
    int index, i;
    uint8_t dummy_buf[FF_INPUT_BUFFER_PADDING_SIZE];

    if(!(s->flags & PARSER_FLAG_FETCHED_OFFSET)) {
        s->next_frame_offset =
        s->cur_offset        = pos;
        s->flags |= PARSER_FLAG_FETCHED_OFFSET;
    }

    if (buf_size == 0) {
        /* padding is always necessary even if EOF, so we add it here */
        memset(dummy_buf, 0, sizeof(dummy_buf));
        buf = dummy_buf;
    } else if (s->cur_offset + buf_size !=
               s->cur_frame_end[s->cur_frame_start_index]) { /* skip remainder packets */
        /* add a new packet descriptor */
            i = (s->cur_frame_start_index + 1) & (AV_PARSER_PTS_NB - 1);
            s->cur_frame_start_index = i;
            s->cur_frame_offset[i] = s->cur_offset;
            s->cur_frame_end[i] = s->cur_offset + buf_size;
            s->cur_frame_pts[i] = pts;
            s->cur_frame_dts[i] = dts;
            s->cur_frame_pos[i] = pos;
    }

    if (s->fetch_timestamp){
        s->fetch_timestamp=0;
        s->last_pts = s->pts;
        s->last_dts = s->dts;
        s->last_pos = s->pos;
        ff_fetch_timestamp(s, 0, 0);
    }

    /* WARNING: the returned index can be negative */
    //H264里对应的就是parser_parse=h264_parse,
    index = s->parser->parser_parse(s, avctx, (const uint8_t **)poutbuf, poutbuf_size, buf, buf_size);
//av_log(NULL, AV_LOG_DEBUG, "parser: in:%"PRId64", %"PRId64", out:%"PRId64", %"PRId64", in:%d out:%d id:%d\n", pts, dts, s->last_pts, s->last_dts, buf_size, *poutbuf_size, avctx->codec_id);
    /* update the file pointer */
    if (*poutbuf_size) {
        /* fill the data for the current frame */
        s->frame_offset = s->next_frame_offset;

        /* offset of the next frame */
        s->next_frame_offset = s->cur_offset + index;
        s->fetch_timestamp=1;
    }
    if (index < 0)
        index = 0;
    s->cur_offset += index;
    return index;
}


从index = s->parser->parser_parse(s, avctx, (const uint8_t **)poutbuf, poutbuf_size, buf, buf_size);这句代码可以看出,最终调用了相应解码器的parser_parse()函数。

 

有点累了,先不做详细分析,以后有机会再补上。

 

 

 

 

 

分享到:
评论

相关推荐

    output_example.rar_FFmpeg解码_ffmpeg_ffmpeg 编译_output exmple_outpu

    5. **解码数据**:循环读取输入流的packet(`av_read_frame()`),然后使用`avcodec_decode_video2()`或`avcodec_decode_audio4()`进行解码。 6. **处理解码后的数据**:解码成功后,会返回解码后的AVFrame,从中...

    FFmpeg源代码结构图

    FFmpeg 源代码结构图 FFmpeg 是一个功能强大且广泛使用的开源多媒体处理框架,它提供了丰富的 API 和强大的多媒体处理能力。了解 FFmpeg 的源代码结构图可以帮助开发者更好地理解 FFmpeg 的内部机制和工作原理,...

    ffmpeg_read_ffmpeg_

    - 打包和编译FFmpeg:初学者可能需要了解如何配置和编译FFmpeg源代码,以便在项目中使用。 以上就是从给定的标题和描述中可以推测出的主要知识点。具体的实现细节,例如如何解析和显示每一帧,以及如何处理解码...

    FFmpeg-master.zip_FFmpeg-master_ffmpeg_ffmpeg 播放

    "FFmpeg-master.zip" 是 FFmpeg 源代码的压缩包,"FFmpeg-master" 是源代码仓库的主目录,而 "ffmpeg_ffmpeg 播放" 指的是使用 FFmpeg 进行视频播放的相关示例。 在 FFmpeg 中,播放视频主要涉及到以下几个关键步骤...

    C#+FFmpeg实现RTSP流媒体播放器

    例如,`av_seek_frame()`用于跳转到指定位置,`av_read_pause()`和`av_read_play()`控制流的暂停和恢复。 8. **资源释放**: 当播放结束或需要关闭时,务必正确地释放所有资源,包括解码器上下文、格式上下文、...

    ffmpeg 音频数据采集

    项目中的 `VideoRecorder5` 文件可能是包含此音频采集功能的源代码文件,你可以通过查看和分析这个文件来深入理解 FFmpeg 音频采集的具体实现。同时,要注意的是,实时音频采集可能涉及到多线程和缓冲管理,确保数据...

    Linux下使用ffmpeg录屏代码

    源代码会展示如何整合这些步骤,将`ffmpeg`的功能封装到C++类中,提供更方便的接口进行调用。通过阅读和学习这些代码,你可以了解到如何在实际项目中应用`ffmpeg`进行录屏操作。 此外,`ffmpeg`库提供了丰富的选项...

    QT + ffmpeg 播放 rtsp,rtmp,udp视频流

    7. **读取和解码数据包**:通过`av_read_frame()`读取原始数据包,然后使用`avcodec_decode_video2()`或`avcodec_decode_audio4()`解码数据。 8. **图像/音频转换**:如果解码后的数据格式不匹配QT的显示或播放需求...

    FFmpeg实现mp4转ts视频格式的源代码方案资料

    在实际开发中,`FFmpeg.sdf`和`FFmpeg.sln`文件可能是Visual Studio的项目文件,它们帮助开发者管理和编译FFmpeg源代码。`ipch`目录可能包含了预编译头文件,而`Debug`目录则包含了编译后的调试版本库和可执行文件。...

    利用ffmpeg从USB摄像头获取视频并保存为H264的TS流的C语言源代码

    通过`av_read_frame()`从输入流读取原始视频帧,然后调用`avcodec_encode_video2()`将这些帧编码为H264 NAL单元。编码后的NAL单元将被写入TS包。 9. **封装TS包** 将编码的NAL单元封装到TS包中。TS包头包括同步...

    MFC 播放FFMPEG SDL视频所需库

    在源代码中,引入以下头文件: ```cpp #include &lt;ffmpeg/avformat.h&gt; #include &lt;ffmpeg/avcodec.h&gt; #include &lt;ffmpeg/avutil.h&gt; #include &lt;ffmpeg/swscale.h&gt; #include #include &lt;SDL_video.h&gt; ``` **3. 初始化...

    最简单的基于FFMPEG的转码器(源代码)

    通过分析和实践这个简单的FFMPEG转码器源代码,开发者能够深入理解FFmpeg的工作原理,并在此基础上开发更复杂的多媒体处理应用,如视频编辑软件、直播推流服务等。同时,学习FFmpeg还能帮助开发者应对不同的多媒体...

    FFmpeg基础库编程开发

    8.3 av_read_frame() 283 8.4 avcodec_decode_video2() 283 8.5 transcode_init() 283 8.6 transcode() 294 第九章 ffmpeg相关工程 301 9.1 ffdshow 301 ffdshow 源代码分析1 : 整体结构 302 ffdshow 源代码分析 2...

    ffmpeg基础开发资料自总结

    8.3 av_read_frame() 269 8.4 avcodec_decode_video2() 270 8.5 transcode_init() 270 8.6 transcode() 280 第九章 ffmpeg 相关工程 288 9.1 ffdshow 288 ffdshow 源代码分析 1 : 整体结构 288 ffdshow 源代码分析 ...

    FFmpegAPI使用手册

    1. 下载源代码:从FFmpeg官方网站获取最新版本的源代码。 2. 配置选项:使用`./configure`命令设置编译选项,例如指定解码器、编码器、滤镜等。 3. 编译源码:运行`make`命令,编译源代码。 4. 安装:使用`make ...

    FFMpeg录屏h264 aac 合并MP4源代码方案资料

    总之,FFMpeg录屏h264 aac 合并MP4的源代码方案涉及到FFmpeg库的使用,包括配置、编码器选择、参数设置、读取输入、编码帧和写入输出等步骤。通过学习和理解这些内容,开发者可以构建自定义的录屏工具,满足特定需求...

    C#FFmpeg拉取RTMP流

    10. **代码示例**:压缩包中的`FFmpegDemo_RTMP_Pull`可能是C#项目的源代码示例,你可以参考其中的实现细节,如如何创建并初始化FFmpeg上下文,如何读取和解码RTMP流。 通过以上步骤,你就能构建一个基本的C#程序,...

Global site tag (gtag.js) - Google Analytics