FFMPEG RTMP推流分析

简介

RTMP推流器(Streamer)的在流媒体系统中的作用可以用下图表示。首先将视频数据以RTMP的形式发送到流媒体服务器端(Server,比如FMS,Red5,Wowza等),然后客户端(一般为Flash Player)通过访问流媒体服务器就可以收看实时流了。

运行本程序之前需要先运行RTMP流媒体服务器,并在流媒体服务器上建立相应的Application。有关流媒体服务器的操作不在本文的论述范围内,在此不再详述。本程序运行后,即可通过RTMP客户端(例如 Flash Player, FFplay等等)收看推送的直播流。

需要要注意的地方

 

封装格式

RTMP采用的封装格式是FLV。因此在指定输出流媒体的时候需要指定其封装格式为“flv”。同理,其他流媒体协议也需要指定其封装格式。例如采用UDP推送流媒体的时候,可以指定其封装格式为“mpegts”。

扫描二维码关注公众号,回复: 3870533 查看本文章

延时

发送流媒体的数据的时候需要延时。不然的话,FFmpeg处理数据速度很快,瞬间就能把所有的数据发送出去,流媒体服务器是接受不了的。因此需要按照视频实际的帧率发送数据。本文记录的推流器在视频帧与帧之间采用了av_usleep()函数休眠的方式来延迟发送。这样就可以按照视频的帧率发送数据了,参考代码如下。

//…
int64_t start_time=av_gettime();
while (1) {
//…
         //Important:Delay
         if(pkt.stream_index==videoindex){
                  AVRationaltime_base=ifmt_ctx->streams[videoindex]->time_base;
                  AVRationaltime_base_q={1,AV_TIME_BASE};
                  int64_tpts_time = av_rescale_q(pkt.dts, time_base, time_base_q);
                  int64_tnow_time = av_gettime() - start_time;
                  if(pts_time > now_time)
                          av_usleep(pts_time- now_time);
         }
//…
}
//…


PTS/DTS问题

没有封装格式的裸流(例如H.264裸流)是不包含PTS、DTS这些参数的。在发送这种数据的时候,需要自己计算并写入AVPacket的PTS,DTS,duration等参数。如下所示。


//FIX:No PTS (Example: Raw H.264)
//Simple Write PTS
if(pkt.pts==AV_NOPTS_VALUE){
         //WritePTS
         AVRationaltime_base1=ifmt_ctx->streams[videoindex]->time_base;
         //Durationbetween 2 frames (us)
         int64_tcalc_duration=(double)AV_TIME_BASE/av_q2d(ifmt_ctx->streams[videoindex]->r_frame_rate);
         //Parameters
         pkt.pts=(double)(frame_index*calc_duration)/(double)(av_q2d(time_base1)*AV_TIME_BASE);
         pkt.dts=pkt.pts;
         pkt.duration=(double)calc_duration/(double)(av_q2d(time_base1)*AV_TIME_BASE);
}


程序流程图

 

代码

#include <stdio.h>
 
#define __STDC_CONSTANT_MACROS
 
#ifdef _WIN32
//Windows
extern "C"
{
#include "libavformat/avformat.h"
#include "libavutil/mathematics.h"
#include "libavutil/time.h"
};
#else
//Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavformat/avformat.h>
#include <libavutil/mathematics.h>
#include <libavutil/time.h>
#ifdef __cplusplus
};
#endif
#endif
 
int push(char* input_str, char* output_str)
{
   AVOutputFormat *ofmt = NULL;
   AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;
   AVPacket pkt;
 
    intret, i;
 
   //FFmpeg av_log() callback
   av_log_set_callback(custom_log);
 
   av_register_all();
   //Network
   avformat_network_init();
    //Input
    if((ret = avformat_open_input(&ifmt_ctx, input_str, 0, 0)) < 0)
    {
       printf( "Could not open input file.");
       goto end;
    }
    LOGI("avformat_find_stream_info");
    if((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0)
    {
       printf( "error to retrieve input stream information");
       goto end;
    }
 
   //Output
   avformat_alloc_output_context2(&ofmt_ctx, NULL,"flv",output_str); //RTMP
   //avformat_alloc_output_context2(&ofmt_ctx, NULL,"mpegts", output_str);//UDP
    if(!ofmt_ctx)
    {
        printf( "Could not create outputcontext\n");
        ret= AVERROR_UNKNOWN;
       goto end;
    }
    ofmt =ofmt_ctx->oformat;
    intvideoindex = -1;
    for(i =0; i<ifmt_ctx->nb_streams; i++)
       if(ifmt_ctx->streams[i]->codec->codec_type ==AVMEDIA_TYPE_VIDEO)
        {
           videoindex = i;
           break;
        }
   //Create output AVStream according to input AVStream
   AVStream *in_stream = ifmt_ctx->streams[videoindex];
   AVStream *out_stream = avformat_new_stream(ofmt_ctx,in_stream->codec->codec);
    if(!out_stream)
    {
       printf( "Error occurred when allocating output stream\n");
        ret= AVERROR_UNKNOWN;
       goto end;
    }
    //Copythe settings of AVCodecContext
    ret =avcodec_copy_context(out_stream->codec, in_stream->codec);
    if (ret< 0)
    {
       printf( "error to copy context from input to output stream codeccontext\n");
       goto end;
    }
   out_stream->codec->codec_tag = 0;
    if(ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
       out_stream->codec->flags | = CODEC_FLAG_GLOBAL_HEADER;
 
    //Openoutput URL
    if(!(ofmt->flags & AVFMT_NOFILE))
    {
        ret= avio_open(&ofmt_ctx->pb, output_str, AVIO_FLAG_WRITE);
        if(ret < 0)
        {
           printf( "Could not open output URL '%s'", output_str);
           goto end;
        }
    }
    //Writefile header
    ret =avformat_write_header(ofmt_ctx, NULL);
    if (ret< 0)
    {
       printf( "Error occurred when opening output URL : %d\n", ret);
        goto end;
    }
 
    intframe_index = 0;
 
    int64_tstart_time = av_gettime();
    while(1)
    {
       AVStream *in_stream, *out_stream;
       //Get an AVPacket
        ret= av_read_frame(ifmt_ctx, &pkt);
        if(ret < 0)
           break;
       if(pkt.stream_index != videoindex)
        {
           av_free_packet(&pkt);
           continue;
        }
       //FIX:NoPTS (Example: Raw H.264)
       //Simple Write PTS
       if(pkt.pts == AV_NOPTS_VALUE)
        {
           //Write PTS
           AVRational time_base1 = ifmt_ctx->streams[videoindex]->time_base;
           //Duration between 2 frames (us)
           int64_t calc_duration =(double)AV_TIME_BASE/av_q2d(ifmt_ctx->streams[videoindex]->r_frame_rate);
           //Parameters
           pkt.pts =(double)(frame_index*calc_duration)/(double)(av_q2d(time_base1)*AV_TIME_BASE);
           pkt.dts = pkt.pts;
           pkt.duration =(double)calc_duration/(double)(av_q2d(time_base1)*AV_TIME_BASE);
        }
       //Important:Delay
       if(pkt.stream_index == videoindex)
        {
           AVRational time_base = ifmt_ctx->streams[videoindex]->time_base;
           AVRational time_base_q = {1,AV_TIME_BASE};
           int64_t pts_time = av_rescale_q(pkt.dts, time_base, time_base_q);
           int64_t now_time = av_gettime() - start_time;
           if (pts_time > now_time)
                av_usleep(pts_time - now_time);
        }
 
       in_stream  =ifmt_ctx->streams[pkt.stream_index];
       out_stream = ofmt_ctx->streams[pkt.stream_index];
        /*copy packet */
       //Convert PTS/DTS
       pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base,out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
       pkt.dts = pkt.pts;//av_rescale_q_rnd(pkt.dts, in_stream->time_base,out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
       pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base,out_stream->time_base);
       pkt.pos = -1;
       //Print to Screen
       if(pkt.stream_index == videoindex)
        {
           printf("Send %8d video frames to output URL\n",frame_index);
            frame_index++;
        }
        ret= av_interleaved_write_frame(ofmt_ctx, &pkt);
 
        if(ret < 0)
        {
           printf( "Error muxing packet\n");
           break;
        }
       av_free_packet(&pkt);
 
    }
    //Writefile trailer
   av_write_trailer(ofmt_ctx);
end:
   avformat_close_input(&ifmt_ctx);
    /*close output */
    if(ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE))
       avio_close(ofmt_ctx->pb);
   avformat_free_context(ofmt_ctx);
    if (ret< 0 && ret != AVERROR_EOF)
    {
       printf( "Error occurred.\n");
       return -1;
    }
    return0;
}

avformat_open_input ()


         该函数用于打开多媒体数据并且获得一些相关的信息。它的声明位于libavformat\avformat.h,如下所示。


/**
 * Open aninput stream and read the header. The codecs are not opened.
 * Thestream must be closed with avformat_close_input().
 *
 * @paramps Pointer to user-supplied AVFormatContext (allocated byavformat_alloc_context).
 *           May be a pointer to NULL, in whichcase an AVFormatContext is allocated by this
 *           function and written into ps.
 *           Note that a user-suppliedAVFormatContext will be freed on failure.
 * @paramfilename Name of the stream to open.
 * @paramfmt If non-NULL, this parameter forces a specific input format.
 *            Otherwise the format isautodetected.
 * @paramoptions  A dictionary filled withAVFormatContext and demuxer-private options.
 *                 On return this parameter willbe destroyed and replaced with a dict containing
 *                 options that were not found.May be NULL.
 *
 * @return0 on success, a negative AVERROR on failure.
 *
 * @note Ifyou want to use custom IO, preallocate the format context and set its pb field.
 */
int avformat_open_input(AVFormatContext **ps,const char *filename, AVInputFormat *fmt, AVDictionary **options);

         代码中的英文注释写的已经比较详细了,在这里拿中文简单叙述一下。

ps:函数调用成功之后处理过的AVFormatContext结构体。

file:打开的视音频流的URL。

fmt:强制指定AVFormatContext中AVInputFormat的。这个参数一般情况下可以设置为NULL,这样FFmpeg可以自动检测AVInputFormat。

dictionay:附加的一些选项,一般情况下可以设置为NULL。


         函数执行成功的话,其返回值大于等于0。

函数调用关系图

         函数调用结构图如下所示。



avformat_open_input()源码位于libavformat\utils.c中,这里只选择它关键的两个函数进行分析:

init_input():绝大部分初始化工作都是在这里做的。

s->iformat->read_header():读取多媒体数据文件头,根据视音频流创建相应的AVStream。

         下面我们逐一看看上述函数。

init_input()

         init_input()主要工作就是打开输入的视频数据并且探测视频的格式。该函数的定义位于libavformat\utils.c,

        

av_probe_input_format2()

         av_probe_input_format2()是一个API函数,声明位于libavformat\avformat.h。

av_probe_input_format3()

 

         av_probe_input_format3()是一个API函数,声明位于libavformat\avformat.h。

         av_probe_input_format3()根据输入数据查找合适的AVInputFormat。输入的数据位于AVProbeData中。

         上述过程中涉及到以下几个知识点:

AVInputFormat->read_probe()

         AVInputFormat中包含read_probe()是用于获得匹配函数的函数指针,不同的封装格式包含不同的实现函数。例如,FLV封装格式的AVInputFormat模块定义(位于libavformat\flvdec.c)如下所示。


AVInputFormat ff_flv_demuxer = {
   .name           = "flv",
   .long_name      =NULL_IF_CONFIG_SMALL("FLV (Flash Video)"),
   .priv_data_size = sizeof(FLVContext),
   .read_probe     = flv_probe,
   .read_header    = flv_read_header,
   .read_packet    = flv_read_packet,
    .read_seek      = flv_read_seek,
   .read_close     = flv_read_close,
   .extensions     = "flv",
   .priv_class     = &flv_class,
};

         其中,read_probe()函数对应的是flv_probe()函数。我们可以看一下flv_probe()函数的定义:


static int flv_probe(AVProbeData *p)
{
    returnprobe(p, 0);
}

         可见flv_probe()调用了一个probe()函数。

         该函数做了如下工作:

(1)获得第6至第9字节的数据(对应Headersize字段)并且做大小端转换,然后存入offset变量。之所以要进行大小端转换是因为FLV是以“大端”方式存储数据,而操作系统是以“小端”方式存储数据,这一转换主要通过AV_RB32()函数实现。AV_RB32()是一个宏定义,其对应的函数是av_bswap32()。

(2)检查开头3个字符(Signature)是否为“FLV”。

(3)第4个字节(Version)小于5。

(4)第6个字节(Headersize的第1个字节?)为0。

(5)offset取值大于8。

         参照FLV文件头的格式可以对上述判断有一个更清晰的认识:

av_match_name()

         av_match_name()是一个API函数,声明位于libavutil\avstring.h,如下所示。


         上述函数还有一点需要注意,其中使用了一个while()循环,用于搜索“,”。这是因为FFmpeg中有些格式是对应多种格式名称的,例如MKV格式的解复用器(Demuxer)的定义如下。


AVInputFormat ff_matroska_demuxer = {
   .name           ="matroska,webm",
   .long_name      =NULL_IF_CONFIG_SMALL("Matroska / WebM"),
   .extensions     ="mkv,mk3d,mka,mks",
   .priv_data_size = sizeof(MatroskaDemuxContext),
   .read_probe     = matroska_probe,
   .read_header    =matroska_read_header,
   .read_packet    =matroska_read_packet,
   .read_close     = matroska_read_close,
   .read_seek      =matroska_read_seek,
   .mime_type      ="audio/webm,audio/x-matroska,video/webm,video/x-matroska"
};

         从代码可以看出,ff_matroska_demuxer中的name字段对应“matroska,webm”,mime_type字段对应“audio/webm,audio/x-matroska,video/webm,video/x-matroska”。av_match_name()函数对于这样的字符串,会把它按照“,”截断成一个个的名称,然后一一进行比较。

av_match_ext()

         av_match_ext()是一个API函数,声明位于libavformat\avformat.h(注意位置和av_match_name()不一样),如下所示。


/**
 * Return apositive value if the given filename has one of the given
 *extensions, 0 otherwise.
 *
 * @paramfilename   file name to check against thegiven extensions
 * @paramextensions a comma-separated list of filename extensions
 */
int av_match_ext(const char *filename, const char*extensions);

         av_match_ext()用于比较文件的后缀。该函数首先通过反向查找的方式找到输入文件名中的“.”,就可以通过获取“.”后面的字符串来得到该文件的后缀。然后调用av_match_name(),采用和比较格式名称的方法比较两个后缀。


AVInputFormat-> read_header()


         在调用完init_input()完成基本的初始化并且推测得到相应的AVInputFormat之后,avformat_open_input()会调用AVInputFormat的read_header()方法读取媒体文件的文件头并且完成相关的初始化工作。read_header()是一个位于AVInputFormat结构体中的一个函数指针,对于不同的封装格式,会调用不同的read_header()的实现函数。举个例子,当输入视频的封装格式为FLV的时候,会调用FLV的AVInputFormat中的read_header()。FLV的AVInputFormat定义位于libavformat\flvdec.c文件中,如下所示。

AVInputFormat ff_flv_demuxer = {
   .name           = "flv",
   .long_name      =NULL_IF_CONFIG_SMALL("FLV (Flash Video)"),
   .priv_data_size = sizeof(FLVContext),
   .read_probe     = flv_probe,
   .read_header    = flv_read_header,
   .read_packet    = flv_read_packet,
   .read_seek      = flv_read_seek,
   .read_close     = flv_read_close,
   .extensions     = "flv",
   .priv_class     = &flv_class,
};

         可以看出read_header()指向了flv_read_header()函数。flv_read_header()的实现同样位于libavformat\flvdec.c文件中,函数读取了FLV的文件头并且判断其中是否包含视频流和音频流。如果包含视频流或者音频流,就会调用create_stream()函数。

         create_stream()函数定义也位于libavformat\flvdec.c中,create_stream()调用了API函数avformat_new_stream()创建相应的视频流和音频流。

         经过上面的步骤AVInputFormat的read_header()完成了视音频流对应的AVStream的创建。至此,avformat_open_input()中的主要代码分析完毕。

avformat_find_stream_info()

 

本文简单分析FFmpeg中一个常用的函数:avformat_find_stream_info()。该函数可以读取一部分视音频数据并且获得一些相关的信息。avformat_find_stream_info()的声明位于libavformat\avformat.h,如下所示。


/**
 * Readpackets of a media file to get stream information. This
 * isuseful for file formats with no headers such as MPEG. This
 * functionalso computes the real framerate in case of MPEG-2 repeat
 * framemode.
 * Thelogical file position is not changed by this function;
 * examinedpackets may be buffered for later processing.
 *
 * @paramic media file handle
 * @paramoptions  If non-NULL, an ic.nb_streamslong array of pointers to
 *                 dictionaries, where i-thmember contains options for
 *                 codec corresponding to i-thstream.
 *                 On return each dictionary willbe filled with options that were not found.
 * @return>=0 if OK, AVERROR_xxx on error
 *
 * @notethis function isn't guaranteed to open all the codecs, so
 *       options being non-empty at return is aperfectly normal behavior.
 *
 * @todoLet the user decide somehow what information is needed so that
 *       we do not waste time getting stuff theuser does not need.
 */
int avformat_find_stream_info(AVFormatContext*ic, AVDictionary **options);

参数的含义:

ic:输入的AVFormatContext。

options:额外的选项。

函数正常执行后返回值大于等于0。

函数调用关系图

函数的调用关系如下图所示。



avformat_find_stream_info()的定义位于libavformat\utils.c。

该函数主要用于给每个媒体流(音频/视频)的AVStream结构体赋值。它实现了解码器的查找,解码器的打开,视音频帧的读取,视音频帧的解码等工作。换句话说,该函数实际上已经“走通”的解码的整个流程。下面看一下除了成员变量赋值之外,该函数的几个关键流程。

1.查找解码器:find_decoder()

2.打开解码器:avcodec_open2()

3.读取完整的一帧压缩编码的数据:read_frame_internal()

注:av_read_frame()内部实际上就是调用的read_frame_internal()。

4.解码一些压缩编码数据:try_decode_frame()

5. 估算AVFormatContext以及AVStream的时长duration:estimate_timings()

avformat_alloc_output_context2()

在基于FFmpeg的视音频编码器程序中,该函数通常是第一个调用的函数(除了组件注册函数av_register_all())。avformat_alloc_output_context2()函数可以初始化一个用于输出的AVFormatContext结构体。它的声明位于libavformat\avformat.h,如下所示。


/**
 * Allocatean AVFormatContext for an output format.
 *avformat_free_context() can be used to free the context and
 *everything allocated by the framework within it.
 *
 * @param*ctx is set to the created format context, or to NULL in
*@param oformat format to use for allocating the context, if NULL
 *format_name and filename are used instead
 * @paramformat_name the name of output format to use for allocating the
 * context,if NULL filename is used instead
 * @paramfilename the name of the filename to use for allocating the
 * context,may be NULL
 * @return>= 0 in case of success, a negative AVERROR code in case of
*/
intavformat_alloc_output_context2(AVFormatContext **ctx, AVOutputFormat *oformat,
                                   const char*format_name, const char *filename);

代码中的英文注释写的已经比较详细了,在这里拿中文简单叙述一下。


ctx:函数调用成功之后创建的AVFormatContext结构体。

oformat:指定AVFormatContext中的AVOutputFormat,用于确定输出格式。如果指定为NULL,可以设定后两个参数(format_name或者filename)由FFmpeg猜测输出格式。

PS:使用该参数需要自己手动获取AVOutputFormat,相对于使用后两个参数来说要麻烦一些。

format_name:指定输出格式的名称。根据格式名称,FFmpeg会推测输出格式。输出格式可以是“flv”,“mkv”等等。

filename:指定输出文件的名称。根据文件名称,FFmpeg会推测输出格式。文件名称可以是“xx.flv”,“yy.mkv”等等。

函数执行成功的话,其返回值大于等于0。

函数调用结构图

 

avformat_alloc_output_context2()的流程如要包含以下2步:

1)     调用avformat_alloc_context()初始化一个默认的AVFormatContext。

2)     如果指定了输入的AVOutputFormat,则直接将输入的AVOutputFormat赋值给AVOutputFormat的oformat。如果没有指定输入的AVOutputFormat,就需要根据文件格式名称或者文件名推测输出的AVOutputFormat。无论是通过文件格式名称还是文件名推测输出格式,都会调用一个函数av_guess_format()。

avformat_alloc_context()首先调用av_malloc()为AVFormatContext分配一块内存。然后调用了一个函数avformat_get_context_defaults()用于给AVFormatContext设置默认值。

avformat_get_context_defaults()首先调用memset()将AVFormatContext的内存置零;然后指定它的AVClass(指定了AVClass之后,该结构体就支持和AVOption相关的功能);最后调用av_opt_set_defaults()给AVFormatContext的成员变量设置默认值(av_opt_set_defaults()就是和AVOption有关的一个函数,专门用于给指定的结构体设定默认值,此处暂不分析)。

av_guess_format()中使用一个整型变量score记录每种输出格式的匹配程度。函数中包含了一个while()循环,该循环利用函数av_oformat_next()遍历FFmpeg中所有的AVOutputFormat,并逐一计算每个输出格式的score。具体的计算过程分成如下几步:

1)     如果封装格式名称匹配,score增加100。匹配中使用了函数av_match_name()。

2)     如果mime类型匹配,score增加10。匹配直接使用字符串比较函数strcmp()。

3)     如果文件名称的后缀匹配,score增加5。匹配中使用了函数av_match_ext()。

while()循环结束后,得到得分最高的格式,就是最匹配的格式。

下面看一下一个AVOutputFormat的实例,就可以理解“封装格式名称”,“mine类型”,“文件名称后缀”这些概念了。下面是flv格式的视音频复用器(Muxer)对应的AVOutputFormat格式的变量ff_flv_muxer。


AVOutputFormat ff_flv_muxer = {
   .name           = "flv",
   .long_name      =NULL_IF_CONFIG_SMALL("FLV (Flash Video)"),
   .mime_type      ="video/x-flv",
   .extensions     = "flv",
   .priv_data_size = sizeof(FLVContext),
   .audio_codec    =CONFIG_LIBMP3LAME ? AV_CODEC_ID_MP3 : AV_CODEC_ID_ADPCM_SWF,
   .video_codec    =AV_CODEC_ID_FLV1,
   .write_header   =flv_write_header,
   .write_packet   =flv_write_packet,
   .write_trailer  =flv_write_trailer,
   .codec_tag      = (constAVCodecTag* const []) {
                          flv_video_codec_ids,flv_audio_codec_ids, 0
                      },
   .flags          =AVFMT_GLOBALHEADER | AVFMT_VARIABLE_FPS |
                      AVFMT_TS_NONSTRICT,
};

av_match_name()用于比较两个格式的名称。简单地说就是比较字符串。注意该函数的字符串是不区分大小写的:字符都转换为小写进行比较。

av_match_ext()用于比较文件的后缀。该函数首先通过反向查找的方式找到输入文件名中的“.”,就可以通过获取“.”后面的字符串来得到该文件的后缀。然后调用av_match_name(),采用和比较格式名称的方法比较两个后缀。

经过以上几步之后,av_guess_format()最终可以得到最合适的AVOutputFormat并且返回给avformat_alloc_output_context2()。avformat_alloc_output_context2()接下来将获得的AVOutputFormat赋值给刚刚新建的AVFormatContext,即可完成初始化工作。

avio_open2()

该函数用于打开FFmpeg的输入输出文件。avio_open2()的声明位于libavformat\avio.h文件中,如下所示。


/**
 * Createand initialize a AVIOContext for accessing the
 * resourceindicated by url.
 * @noteWhen the resource indicated by url has been opened in
 *read+write mode, the AVIOContext can be used only for writing.
 *
 * @param sUsed to return the pointer to the created AVIOContext.
 * In caseof failure the pointed to value is set to NULL.
 * @paramurl resource to access
 * @paramflags flags which control how the resource indicated by url
 * is to beopened
 * @paramint_cb an interrupt callback to be used at the protocols level
 * @paramoptions  A dictionary filled withprotocol-private options. On return
 * thisparameter will be destroyed and replaced with a dict containing options
 * thatwere not found. May be NULL.
 * @return>= 0 in case of success, a negative value corresponding to an
 * AVERRORcode
 */
int avio_open2(AVIOContext **s, const char *url,int flags,
              const AVIOInterruptCB *int_cb, AVDictionary **options);

avio_open2()函数参数的含义如下:

s:函数调用成功之后创建的AVIOContext结构体。

url:输入输出协议的地址(文件也是一种“广义”的协议,对于文件来说就是文件的路径)。

flags:打开地址的方式。可以选择只读,只写,或者读写。取值如下。

         AVIO_FLAG_READ:只读。

         AVIO_FLAG_WRITE:只写。

         AVIO_FLAG_READ_WRITE:读写。

int_cb:目前还没有用过。

options:目前还没有用过。

函数调用结构图

 

 

avio_open()

avio_open()比avio_open2()少了最后2个参数。而它前面几个参数的含义和avio_open2()是一样的。从源代码中可以看出,avio_open()内部调用了avio_open2(),并且把avio_open2()的后2个参数设置成了NULL,因此它的功能实际上和avio_open2()是一样的。avio_open()源代码如下所示。

int avio_open(AVIOContext **s, const char *filename,int flags)

{

    returnavio_open2(s, filename, flags, NULL, NULL);

}

avio_open2()主要调用了2个函数:ffurl_open()和ffio_fdopen()。其中ffurl_open()用于初始化URLContext,ffio_fdopen()用于根据URLContext初始化AVIOContext。URLContext中包含的URLProtocol完成了具体的协议读写等工作。AVIOContext则是在URLContext的读写函数外面加上了一层“包装”(通过retry_transfer_wrapper()函数)。

URLProtocol和URLContext

首先查看一下URLContext和URLProtocol的定义。这两个结构体在FFmpeg的早期版本的SDK中是定义在头文件中可以直接使用的。但是近期的FFmpeg的SDK中已经找不到这两个结构体的定义了。FFmpeg把这两个结构体移动到了源代码的内部,变成了内部结构体。

URLProtocol的定义位于libavformat\url.h,如下所示。

typedef struct URLProtocol {

    constchar *name;

   int     (*url_open)( URLContext*h, const char *url, int flags);

   int     (*url_open2)(URLContext*h, const char *url, int flags, AVDictionary **options);

   int     (*url_read)( URLContext*h, unsigned char *buf, int size);

   int     (*url_write)(URLContext*h, const unsigned char *buf, int size);

    int64_t(*url_seek)( URLContext *h, int64_t pos, int whence);

   int     (*url_close)(URLContext*h);

    structURLProtocol *next;

    int (*url_read_pause)(URLContext*h, int pause);

    int64_t(*url_read_seek)(URLContext *h, int stream_index,

                             int64_t timestamp,int flags);

    int(*url_get_file_handle)(URLContext *h);

    int(*url_get_multi_file_handle)(URLContext *h, int **handles,

                                     int*numhandles);

    int(*url_shutdown)(URLContext *h, int flags);

    intpriv_data_size;

    constAVClass *priv_data_class;

    intflags;

    int(*url_check)(URLContext *h, int mask);

} URLProtocol;

从URLProtocol的定义可以看出,其中包含了用于协议读写的函数指针。例如:

url_open():打开协议。

url_read():读数据。

url_write():写数据。

url_close():关闭协议。

每种具体的协议都包含了一个URLProtocol结构体,例如:

FILE协议(“文件”在FFmpeg中也被当做一种协议)的结构体ff_file_protocol的定义如下所示(位于libavformat\file.c)。

URLProtocol ff_file_protocol = {

    .name                = "file",

   .url_open            = file_open,

   .url_read            = file_read,

   .url_write           = file_write,

   .url_seek            = file_seek,

   .url_close           = file_close,

   .url_get_file_handle = file_get_handle,

   .url_check           = file_check,

   .priv_data_size      =sizeof(FileContext),

   .priv_data_class     =&file_class,

};

在使用FILE协议进行读写的时候,调用url_open()实际上就是调用了file_open()函数,这里限于篇幅不再对file_open()的源代码进行分析。file_open()函数实际上调用了系统的打开文件函数open()。同理,调用url_read()实际上就是调用了file_read()函数;file_read()函数实际上调用了系统的读取文件函数read()。url_write(),url_seek()等函数的道理都是一样的。

LibRTMP协议的结构体ff_librtmp_protocol的定义如下所示(位于libavformat\librtmp.c)。

URLProtocol ff_librtmp_protocol = {

   .name                ="rtmp",

   .url_open            = rtmp_open,

   .url_read            = rtmp_read,

   .url_write           = rtmp_write,

   .url_close           = rtmp_close,

   .url_read_pause      =rtmp_read_pause,

   .url_read_seek       =rtmp_read_seek,

   .url_get_file_handle = rtmp_get_file_handle,

   .priv_data_size      =sizeof(LibRTMPContext),

   .priv_data_class     =&librtmp_class,

   .flags               = URL_PROTOCOL_FLAG_NETWORK,

};

UDP协议的结构体ff_udp_protocol的定义如下所示(位于libavformat\udp.c)。

URLProtocol ff_udp_protocol = {

   .name                ="udp",

   .url_open            = udp_open,

   .url_read            = udp_read,

   .url_write           = udp_write,

   .url_close           = udp_close,

   .url_get_file_handle = udp_get_file_handle,

   .priv_data_size      =sizeof(UDPContext),

   .priv_data_class     =&udp_context_class,

   .flags               =URL_PROTOCOL_FLAG_NETWORK,

};

上文中简单介绍了URLProtocol结构体。下面看一下URLContext结构体。URLContext的定义也位于libavformat\url.h,如下所示。

typedef struct URLContext {

    constAVClass *av_class;    /**< informationfor av_log(). Set by url_open(). */

    structURLProtocol *prot;

    void*priv_data;

    char*filename;             /**< specifiedURL */

    intflags;

    intmax_packet_size;        /**< if nonzero, the stream is packetized with this max packet size */

    intis_streamed;            /**< true ifstreamed (no seek possible), default = false */

    intis_connected;

   AVIOInterruptCB interrupt_callback;

    int64_trw_timeout;         /**< maximum timeto wait for (network) read/write operation completion, in mcs */

} URLContext;

从代码中可以看出,URLProtocol结构体是URLContext结构体的一个成员。由于还没有对URLContext结构体进行详细研究,有关该结构体的代码不再做过多分析。

avformat_write_header()

 

FFmpeg的写文件用到的3个函数:avformat_write_header(),av_write_frame()以及av_write_trailer()。其中av_write_frame()用于写视频数据,avformat_write_header()用于写视频文件头,而av_write_trailer()用于写视频文件尾。

需要注意的是,尽管这3个函数功能是配套的,但是它们的前缀却不一样,写文件头Header的函数前缀是“avformat_”,其他两个函数前缀是“av_”(不太明白其中的原因)。

avformat_write_header()的声明位于libavformat\avformat.h,如下所示。

/**

 * Allocatethe stream private data and write the stream header to

 * anoutput media file.

 *

 * @param sMedia file handle, must be allocated with avformat_alloc_context().

 *          Its oformat field must be set to thedesired output format;

 *          Its pb field must be set to analready opened AVIOContext.

 * @paramoptions  An AVDictionary filled withAVFormatContext and muxer-private options.

 *                 On return this parameter willbe destroyed and replaced with a dict containing

 *                 options that were not found.May be NULL.

 *

 * @return0 on success, negative AVERROR on failure.

 *

 * @seeav_opt_find, av_dict_set, avio_open, av_oformat_next.

 */

int avformat_write_header(AVFormatContext *s,AVDictionary **options);简单解释一下它的参数的含义:

s:用于输出的AVFormatContext。

options:额外的选项,一般为NULL。

函数正常执行后返回值等于0。

函数调用关系图

avformat_write_header()的调用关系如下图所示。

avformat_write_header()主要完成了以下工作:

(1)调用init_muxer()初始化复用器

(2)调用AVOutputFormat的write_header()

init_muxer()

init_muxer()用于初始化复用器,它所做的工作比较简单,可以概括成两个字:检查。函数的流程可以概括成以下几步:

(1)将传入的AVDictionary形式的选项设置到AVFormatContext

(2)遍历AVFormatContext中的每个AVStream,并作如下检查:

a)AVStream的time_base是否正确设置。如果发现AVStream的time_base没有设置,则会调用avpriv_set_pts_info()进行设置。

b)对于音频,检查采样率设置是否正确;对于视频,检查宽、高、宽高比。

c)其他一些检查,不再详述。

AVOutputFormat->write_header()

avformat_write_header()中最关键的地方就是调用了AVOutputFormat的write_header()。write_header()是AVOutputFormat中的一个函数指针,指向写文件头的函数。不同的AVOutputFormat有不同的write_header()的实现方法。在这里我们举例子看一下FLV封装格式对应的AVOutputFormat,它的定义位于libavformat\flvenc.c,如下所示。

AVOutputFormat ff_flv_muxer = {

   .name           = "flv",

   .long_name      =NULL_IF_CONFIG_SMALL("FLV (Flash Video)"),

   .mime_type      ="video/x-flv",

   .extensions     = "flv",

   .priv_data_size = sizeof(FLVContext),

   .audio_codec    =CONFIG_LIBMP3LAME ? AV_CODEC_ID_MP3 : AV_CODEC_ID_ADPCM_SWF,

   .video_codec    =AV_CODEC_ID_FLV1,

   .write_header   =flv_write_header,

   .write_packet   =flv_write_packet,

   .write_trailer  =flv_write_trailer,

   .codec_tag      = (constAVCodecTag* const []) {

                         flv_video_codec_ids,flv_audio_codec_ids, 0

                      },

   .flags          =AVFMT_GLOBALHEADER | AVFMT_VARIABLE_FPS |

                      AVFMT_TS_NONSTRICT,

};

从ff_flv_muxer的定义中可以看出,write_header()指向的函数为flv_write_header()。我们继续看一下flv_write_header()函数。flv_write_header()的定义同样位于libavformat\flvenc.c。

flv_write_header()完成了FLV文件头的写入工作。该函数的工作可以大体分为以下两部分:

(1)给FLVContext设置参数

(2)写文件头,以及相关的Tag

写文件头的代码很短,如下所示。

    //开始写入

   //Signature

   avio_write(pb, "FLV", 3);

   //Version

    avio_w8(pb,1);

    //“!!”意思是把非0转换成1

    //Flags

   avio_w8(pb, FLV_HEADER_FLAG_HASAUDIO * !!flv->audio_enc +

               FLV_HEADER_FLAG_HASVIDEO * !!flv->video_enc);

   //Header size

   avio_wb32(pb, 9);

   //Header结束

   //Previous Tag Size

    avio_wb32(pb,0);

可以参考下图中FLV文件头的定义比对一下上面的代码。

av_read_frame()

 

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

av_read_frame()的声明位于libavformat\avformat.h,如下所示。

/**

 * Returnthe next frame of a stream.

 * Thisfunction returns what is stored in the file, and does not validate

 * thatwhat is there are valid frames for the decoder. It will split what is

 * storedin the file into frames and return one for each call. It will not

 * omitinvalid data between valid frames so as to give the decoder the maximum

 *information possible for decoding.

 *

 * Ifpkt->buf is NULL, then the packet is valid until the next

 *av_read_frame() or until avformat_close_input(). Otherwise the packet

 * is validindefinitely. In both cases the packet must be freed with

 *av_free_packet when it is no longer needed. For video, the packet contains

 * exactlyone frame. For audio, it contains an integer number of frames if each

 * framehas a known fixed size (e.g. PCM or ADPCM data). If the audio frames

 * have avariable size (e.g. MPEG audio), then it contains one frame.

 *

 *pkt->pts, pkt->dts and pkt->duration are always set to correct

 * valuesin AVStream.time_base units (and guessed if the format cannot

 * providethem). pkt->pts can be AV_NOPTS_VALUE if the video format

 * hasB-frames, so it is better to rely on pkt->dts if you do not

 *decompress the payload.

 *

 * @return0 if OK, < 0 on error or end of file

 */

int av_read_frame(AVFormatContext *s, AVPacket*pkt);av_read_frame()使用方法在注释中写得很详细,用中文简单描述一下它的两个参数:

s:输入的AVFormatContext

pkt:输出的AVPacket

如果返回0则说明读取正常。

函数调用结构图

函数调用结构图如下所示。

read_frame_internal()

 

av_read_frame()调用了read_frame_internal()。有2步是十分关键的:

(1)调用了ff_read_packet()从相应的AVInputFormat读取数据。

(2)如果媒体频流需要使用AVCodecParser,则调用parse_packet()解析相应的AVPacket。

下面我们分成分别看一下ff_read_packet()和parse_packet()的源代码。

ff_read_packet()

ff_read_packet()中最关键的地方就是调用了AVInputFormat的read_packet()方法。AVInputFormat的read_packet()是一个函数指针,指向当前的AVInputFormat的读取数据的函数。在这里我们以FLV封装格式对应的AVInputFormat为例,看看read_packet()的实现函数是什么样子的。

FLV封装格式对应的AVInputFormat的定义位于libavformat\flvdec.c,如下所示。

AVInputFormat ff_flv_demuxer = {

   .name           = "flv",

   .long_name      =NULL_IF_CONFIG_SMALL("FLV (Flash Video)"),

   .priv_data_size = sizeof(FLVContext),

   .read_probe     = flv_probe,

   .read_header    = flv_read_header,

   .read_packet    = flv_read_packet,

   .read_seek      = flv_read_seek,

   .read_close     = flv_read_close,

    .extensions    = "flv",

   .priv_class     = &flv_class,

};

从ff_flv_demuxer的定义可以看出,read_packet()对应的是flv_read_packet()函数。在看flv_read_packet()函数之前,我们先回顾一下FLV封装格式的结构,如下图所示。

从图中可以看出,FLV文件体部分是由一个一个的Tag连接起来的(中间间隔着PreviousTag Size)。每个Tag包含了Tag Header和Tag Data两个部分。Tag Data根据Tag的Type不同而不同:可以分为音频TagData,视频TagData以及ScriptTag Data。下面简述一下音频TagData和视频TagData。

Audio Tag Data

Audio Tag在官方标准中定义如下。

Audio Tag开始的第1个字节包含了音频数据的参数信息,从第2个字节开始为音频流数据。

第1个字节的前4位的数值表示了音频数据格式:

0 = Linear PCM, platform endian

1 = ADPCM

2 = MP3

3 = Linear PCM, little endian

4 = Nellymoser 16-kHz mono

5 = Nellymoser 8-kHz mono

6 = Nellymoser

7 = G.711 A-law logarithmic PCM

8 = G.711 mu-law logarithmic PCM

9 = reserved

10 = AAC

14 = MP3 8-Khz

15 = Device-specific sound

第1个字节的第5-6位的数值表示采样率:0 = 5.5kHz,1 = 11KHz,2 = 22 kHz,3 = 44 kHz。

第1个字节的第7位表示采样精度:0 = 8bits,1 = 16bits。

第1个字节的第8位表示音频类型:0 = sndMono,1 = sndStereo。

其中,当音频编码为AAC的时候,第一个字节后面存储的是AACAUDIODATA,格式如下所示。

Video Tag Data

Video Tag在官方标准中的定义如下。

Video Tag也用开始的第1个字节包含视频数据的参数信息,从第2个字节为视频流数据。

第1个字节的前4位的数值表示帧类型(FrameType):

1: keyframe (for AVC, a seekableframe)(关键帧)

2: inter frame (for AVC, a nonseekableframe)

3: disposable inter frame (H.263only)

4: generated keyframe (reservedfor server useonly)

5: video info/command frame

第1个字节的后4位的数值表示视频编码ID(CodecID):

1: JPEG (currently unused)

2: Sorenson H.263

3: Screen video

4: On2 VP6

5: On2 VP6 with alpha channel

6: Screen video version 2

7: AVC

其中,当音频编码为AVC(H.264)的时候,第一个字节后面存储的是AVCVIDEOPACKET,格式如下所示。

关键代码:

/* pkt size is repeated at end. skip it */

    for (;;avio_skip(s->pb, 4)) {

       pos  = avio_tell(s->pb);

        //解析Tag Header==========

       //Tag类型

       type = (avio_r8(s->pb) & 0x1F);

       //Datasize数据大小

       size = avio_rb24(s->pb);

       //Timstamp时间戳

       dts  = avio_rb24(s->pb);

        dts|= avio_r8(s->pb) << 24;

avio_skip(s->pb, 3); /* stream id, always 0 */

if (type == FLV_TAG_TYPE_AUDIO) {

          //Type是音频

           stream_type = FLV_STREAM_TYPE_AUDIO;

           //Tag Data的第一个字节

           flags    = avio_r8(s->pb);

           size--;

        }else if (type == FLV_TAG_TYPE_VIDEO) {

          //Type是音频

           stream_type = FLV_STREAM_TYPE_VIDEO;

           //Tag Data的第一个字节

           flags    = avio_r8(s->pb);

           size--;

           if ((flags & FLV_VIDEO_FRAMETYPE_MASK) == FLV_FRAME_VIDEO_INFO_CMD)

               goto skip;

        }

    //几种特殊的格式

    if(st->codec->codec_id == AV_CODEC_ID_AAC ||

       st->codec->codec_id == AV_CODEC_ID_H264 ||

       st->codec->codec_id == AV_CODEC_ID_MPEG4) {

    //对应AACPacketType或者AVCPacketType

        inttype = avio_r8(s->pb);

       size--;

       //H.264

        if(st->codec->codec_id == AV_CODEC_ID_H264 || st->codec->codec_id ==AV_CODEC_ID_MPEG4) {

           // sign extension

          //对应CompositionTime

           int32_t cts = (avio_rb24(s->pb) + 0xff800000) ^ 0xff800000;

           //计算PTS

           pts = dts + cts;

           if (cts < 0) { // dts might be wrong

           …

               flv->wrong_dts = 1;

           } else if (FFABS(dts - pts) > 1000*60*15) {

               …

               dts = pts = AV_NOPTS_VALUE;

           }

        }

        //如果编码器是AAC或者H.264

        if(type == 0 && (!st->codec->extradata || st->codec->codec_id== AV_CODEC_ID_AAC ||

            st->codec->codec_id ==AV_CODEC_ID_H264)) {

           AVDictionaryEntry *t;

           if (st->codec->extradata) {

               if ((ret = flv_queue_extradata(flv, s->pb, stream_type, size)) <0)

                    return ret;

               ret = AVERROR(EAGAIN);

               goto leave;

           }

           if ((ret = flv_get_extradata(s, st, size)) < 0)

               return ret;

           /* Workaround for buggy Omnia A/XE encoder */

           t = av_dict_get(s->metadata, "Encoder", NULL, 0);

           if (st->codec->codec_id == AV_CODEC_ID_AAC && t &&!strcmp(t->value, "Omnia A/XE"))

               st->codec->extradata_size = 2;

           //AAC

           if (st->codec->codec_id == AV_CODEC_ID_AAC && 0) {

               MPEG4AudioConfig cfg;

               if (avpriv_mpeg4audio_get_config(&cfg, st->codec->extradata,

                                            st->codec->extradata_size * 8, 1) >= 0) {

               st->codec->channels       =cfg.channels;

                st->codec->channel_layout = 0;

               if (cfg.ext_sample_rate)

                   st->codec->sample_rate = cfg.ext_sample_rate;

               else

                   st->codec->sample_rate = cfg.sample_rate;

               av_dlog(s, "mp4a config channels %d sample rate %d\n",

                       st->codec->channels, st->codec->sample_rate);

               }

           }

           ret = AVERROR(EAGAIN);

           goto leave;

        }

}

ret =av_get_packet(s->pb, pkt, size);

av_write_frame()

 

av_write_frame()用于输出一帧视音频数据,它的声明位于libavformat\avformat.h,如下所示。

/**

 * Write apacket to an output media file.

 *

 * Thisfunction passes the packet directly to the muxer, without any buffering

 * orreordering. The caller is responsible for correctly interleaving the

 * packetsif the format requires it. Callers that want libavformat to handle

 * theinterleaving should call av_interleaved_write_frame() instead of this

 *function.

 *

 * @param smedia file handle

 * @parampkt The packet containing the data to be written. Note that unlike

 *            av_interleaved_write_frame(), thisfunction does not take

 *            ownership of the packet passed toit (though some muxers may make

 *            an internal reference to the inputpacket).

 *            <br>

 *            This parameter can be NULL (at anytime, not just at the end), in

 *            order to immediately flush databuffered within the muxer, for

 *            muxers that buffer up datainternally before writing it to the

 *            output.

 *            <br>

 *            Packet's @ref AVPacket.stream_index"stream_index" field must be

 *            set to the index of thecorresponding stream in @ref

 *            AVFormatContext.streams"s->streams". It is very strongly

 *            recommended that timing information(@ref AVPacket.pts "pts", @ref

 *            AVPacket.dts "dts", @refAVPacket.duration "duration") is set to

 *            correct values.

 * @return< 0 on error, = 0 if OK, 1 if flushed and there is no more data to flush

 *

 * @seeav_interleaved_write_frame()

 */

int av_write_frame(AVFormatContext *s, AVPacket*pkt);

简单解释一下它的参数的含义:

s:用于输出的AVFormatContext。

pkt:等待输出的AVPacket。

函数正常执行后返回值等于0。

函数调用关系图

av_write_frame()的调用关系如下图所示。

av_write_frame()主要完成了以下几步工作:

(1)调用prepare_input_packet()-> check_packet()做一些简单的检测

(2)调用compute_pkt_fields2()设置AVPacket的一些属性值

(3)调用write_packet()写入数据

下面分别看一下flv_write_packet()。需要先回顾一下前文的FLV封装格式。

flv_write_packet()

下面我们看一下FLV格式中write_packet()对应的实现函数flv_write_packet(),位于libavformat\flvenc.c。

flv_write_packet()在写入H.264/AAC时候的流程:

(1)写入Tag Header的Type,如果是视频,代码如下:

avio_w8(pb, FLV_TAG_TYPE_VIDEO);

如果是音频,代码如下:

avio_w8(pb, FLV_TAG_TYPE_AUDIO);

(2)写入Tag Header的Datasize,Timestamp和StreamID(至此完成Tag Header):

    //TagHeader - Datasize

   avio_wb24(pb, size + flags_size);

    //TagHeader - Timestamp

   avio_wb24(pb, ts & 0xFFFFFF);

   avio_w8(pb, (ts >> 24) & 0x7F); // timestamps are 32 bits_signed_

   //StreamID

avio_wb24(pb,flv->reserved);

(3)写入Tag Data的第一字节(其中flag已经在前面的代码中设置完毕):

    //FirstByte of Tag Data

avio_w8(pb,flags);

(4)如果编码格式VP6作相应的处理(不研究);编码格式为AAC,写入AACAUDIODATA;编码格式为H.264,写入AVCVIDEOPACKET:

        if(enc->codec_id == AV_CODEC_ID_VP6F || enc->codec_id == AV_CODEC_ID_VP6A){

            if (enc->extradata_size)

               avio_w8(pb, enc->extradata[0]);

           else

               avio_w8(pb, ((FFALIGN(enc->width, 16) - enc->width) << 4) |

                            (FFALIGN(enc->height, 16) - enc->height));

        }else if (enc->codec_id == AV_CODEC_ID_AAC)

           avio_w8(pb, 1); // AAC raw

       else if (enc->codec_id == AV_CODEC_ID_H264 || enc->codec_id ==AV_CODEC_ID_MPEG4) {

           //AVCVIDEOPACKET-AVCPacketType

          avio_w8(pb, 1); // AVC NALU

          //AVCVIDEOPACKET-CompositionTime

           avio_wb24(pb, pkt->pts - pkt->dts);

        }

(5)写入数据:

       //Data

       avio_write(pb, data ? data : pkt->data, size);

(6)       写入previous tag size:

avio_wb32(pb, size + flags_size + 11); //previous tag size

至此,flv_write_packet()就完成了一个Tag的写入。

av_write_trailer()

av_write_trailer()用于输出文件尾,它的声明位于libavformat\avformat.h,如下所示。

/**

 * Writethe stream trailer to an output media file and free the

 * fileprivate data.

 *

 * May onlybe called after a successful call to avformat_write_header.

 *

 * @param smedia file handle

 * @return0 if OK, AVERROR_xxx on error

 */

int av_write_trailer(AVFormatContext *s);

它只需要指定一个参数,即用于输出的AVFormatContext。

函数正常执行后返回值等于0。

函数调用关系图

av_write_trailer()的调用关系如下图所示。

av_write_trailer()

av_write_trailer()的定义位于libavformat\mux.c,主要完成了以下两步工作:

(1)循环调用interleave_packet()以及write_packet(),将还未输出的AVPacket输出出来。

(2)调用AVOutputFormat的write_trailer(),输出文件尾。

其中第一步和av_write_frame()中的步骤大致是一样的(interleave_packet()这一部分在并不包含在av_write_frame()中,而是包含在av_interleaved_write_frame()中,这一部分源代码还没有分析)。

flv_write_trailer()

flv_write_trailer()函数的定义位于libavformat\flvenc.c。该函数做了以下两步工作:

(1)如果视频流是H.264,则添加包含EOS(End Of Stream) NALU的Tag。

(2)更新FLV的时长信息,以及文件大小信息。

其中,put_avc_eos_tag()函数用于添加包含EOS NALU的Tag(包含结尾的一个PreviousTagSize),),如下所示。

static void put_avc_eos_tag(AVIOContext *pb,unsigned ts)

{

   avio_w8(pb, FLV_TAG_TYPE_VIDEO);

   avio_wb24(pb, 5);               /*Tag Data Size */

   avio_wb24(pb, ts);              /*lower 24 bits of timestamp in ms */

   avio_w8(pb, (ts >> 24) & 0x7F); /* MSB of ts in ms */

   avio_wb24(pb, 0);               /*StreamId = 0 */

   avio_w8(pb, 23);                /*ub[4] FrameType = 1, ub[4] CodecId = 7 */

   avio_w8(pb, 2);                 /*AVC end of sequence */

   avio_wb24(pb, 0);               /*Always 0 for AVC EOS. */

   avio_wb32(pb, 16);              /*Size of FLV tag */

}

可以参考FLV封装格式理解上述函数。由于前面的文章中已经描述过FLV封装格式,在这里不再重复叙述,在这里仅在此记录一下AVCVIDEOPACKET的格式,如下所示。

可以看出包含EOSNALU的AVCVIDEOPACKET的AVCPacketType为2。在这种情况下,AVCVIDEOPACKET的CompositionTime字段取0,并且无需包含Data字段。

猜你喜欢

转载自blog.csdn.net/xuqiqiang1993/article/details/68489124