FFMPEG推流器讲解
章节主要介绍FFMPEG的结构体,FFMPEG是音视频的瑞士军刀,它提供了一系列丰富的音视频处理接口。如:编码、解码、推流、滤镜等等。在我们这个项目里面,FFMPEG主要的作用是进行视频推流的功能,就是把RV1126编码的视频码流利用FFMPEG框架推送到流媒体服务器。
一.FFMPEG重要结构体的讲解:
FFMPEG中有六个比较重要的结构体,分别是AVFormatContext、AVOutputFormat、 AVStream、AVCodec、AVCodecContext、AVPacket、AVFrame、AVIOContext结构体,这几个结构体是贯穿着整个FFMPEG核心功能,并且也是我们这个项目中最重要的几个结构体。下面我们来重点介绍这几个结构体:
2.1. AVFormatContext结构体
这个结构体是统领全局的基本结构体,这个结构体最主要作用的是处理封装、解封装等核心功能。我们来看看AVFormatContext具体的成员变量:
2.1.1. AVInputFormat * iformat:输入数据的封装格式,仅作用于解封装用avformat_open_input
2.1.2. AVOutputFormat * ofomat:输出数据的封装格式,仅作用于解封装用avformat_write_header
2.1.3. AVIOContext * pb:I/O的上下文,在解封装中由用户在avformat_open_input之前来设置,若封装的时候用户在avformat_write_header之前设置
2.1.4. int nb_streams:流的个数,若只有视频流nb_streams = 1, 若同时有视频流和音频流则nb_streams = 2。
2.1.5. AVStream **stream:列出文件中所有流的列表
2.1.6. int64_t start_time:第一帧的位置
2.1.7. int64_t duration:流的持续时间
2.1.8. int64_t bit_rate:流的比特率
2.1.9. int64_t probsize:输入读取的用于确定容器格式的大小
2.1.10. AVDictionary *metadata:元数据
2.1.10. AVDictionary *metadata:元数据
2.1.11. AVCodec * video_codec:视频编解码器
2.1.12. AVCodec * audio_codec:音频编解码器
2.1.13. AVCodec *subtitle_codec:字幕编解码器
2.1.14. AVCodec *data_codec:数据编解码器
2.2. AVOutputFormat 结构体:
这个结构体的功能类似于COM接口,表示文件容器输出格式,这个结构体的特点是着重于函数的实现
具体的成员变量:
可以看到AVOutputFormat结构体有很多个成员变量
2.2.1 const char *name; //描述的名称
2.2.2. const char *long_name;//格式的描述性名称,易于阅读。
2.2.3. enum AVCodecID audio_codec; //默认的音频编解码器
2.2.4. enum AVCodecID video_codec; //默认的视频编解码器
2.2.5. enum AVCodecID subtitle_codec; //默认的字幕编解码器
2.2.6. struct AVOutputFormat *next; //链表NEXT
2.2.7. int (*write_header)(struct AVFormatContext *); //写入数据头部
2.2.8. int (*write_packet)(struct AVFormatContext *, AVPacket *pkt);//写一个数据包。 如果在标志中设置AVFMT_ALLOW_FLUSH,则pkt可以为NULL。
2.2.9. int (*write_trailer)(struct AVFormatContext *); //写入数据尾部
2.2.10. int (*interleave_packet)(struct AVFormatContext *, AVPacket *out, AVPacket *in, int flush); //刷新AVPacket,并写入
2.2.11. int (*control_message)(struct AVFormatContext *s, int type, void *data, size_t data_size);//允许从应用程序向设备发送消息。
2.2.12. int (*write_uncoded_frame)(struct AVFormatContext *, int stream_index, AVFrame **frame, unsigned flags);//写一个未编码的AVFrame。
2.2.13. int (*init)(struct AVFormatContext *);//初始化格式。 可以在此处分配数据,并设置在发送数据包之前需要设置的任何AVFormatContext或AVStream参数。
2.2.14.void (*deinit)(struct AVFormatContext *);//取消初始化格式。
2.2.15. int (*check_bitstream)(struct AVFormatContext *, const AVPacket *pkt);//设置任何必要的比特流过滤,并提取全局头部所需的任何额外数据
2.3.AVStream结构体:
AVStream包含了每一个视频/音频流信息的结构体,同样的这个结构体也有很多重要的成员变量,我们来看看这些成员变量具体的含义。
2.3.1. index/id:这个数字是自动生成的,根据index可以从streams表中找到该流
2.3.2.time_base:流的时间基,这是一个实数,该AVStream中流媒体数据的PTS和DTS都将会以这个时间基准。视频时间基准以帧率为基准,音频以采样率为时间基准
2.3.3.start_time:流的起始时间,以流的时间基准为单位,通常是该流的第一个PTS
2.3.4.duration:流的总时间,以流的时间基准为单位
2.3.5.need_parsing:对该流的parsing过程的控制
2.3.6.nb_frames:流内的帧数目
2.3.7.avg_frame_rate:平均帧率
2.3.8.codec:该流对应的AVCodecContext结构,调用avformat_open_input生成
2.3.9.parser:指向该流对应的AVCodecParserContext结构,调用av_find_stream_info生成
2.4.AVCodec结构体:
AVCodec是ffmpeg的音视频编解码,它提供了个钟音视频的编码库和解码库,FFMPEG通过AVCODEC可以将音视频数据编码成对应的数据压缩包。
2.4.1. AVCodecID:AVCODECID编码器的ID号,这里的编码器ID包含了视频的编码器ID,如:AV_CODEC_ID_H264、AV_CODEC_ID_H265等等。音频的编码器ID:AV_CODEC_ID_AAC、AV_CODEC_ID_MP3等等。
2.4.2. AVMediaType:指明当前编码器的类型,包括:视频(AVMEDIA_TYPE_VIDEO)、音频(AVMEDIA_TYPE_AUDIO)、字幕(AVMEDIA_TYPE_SUBTITILE)。
AVMediaType具体定义:
enum AVMediaType {AVMEDIA_TYPE_UNKNOWN = -1, ///< Usually treated as AVMEDIA_TYPE_DATAAVMEDIA_TYPE_VIDEO,AVMEDIA_TYPE_AUDIO,AVMEDIA_TYPE_DATA, ///< Opaque data information usually continuousAVMEDIA_TYPE_SUBTITLE,AVMEDIA_TYPE_ATTACHMENT, ///< Opaque data information usually sparseAVMEDIA_TYPE_NB
};
2.4.3. AVRotational supported_framerates:支持的帧率,这个参数仅支持视频设置
2.4.4. enum AVPixelFormat * pix_fmts:支持的像素格式,这个参数支持视频设置
2.4.5. int * supported_samplerates:支持的采样率,这个参数支持音频设置
2.4.6. enum AVSampleFormat * sample_fmts:支持的采样格式,这个参数仅支持音频设置
2.4.7. uint64_t * channel_layouts:支持的声道数,这个参数仅支持音频设置
2.4.8. int private_data_size:私有数据的大小
2.5.AVCodecContext结构体:
AVCodecContext是FFMPEG编解码上下文的结构体,它内部包含了AVCodec编解码参数结构体。除了AVCodec结构体外,还有AVCodecInternal、AVRotational结构体,这包含了AVCodecID、AVMediaType、AVPixelFormat、AVSampleFormat等类型,其中包含视频的分辨率width、height、帧率framerate、码率bitrate等。
2.5.1. AVMediaType codec_type:指明当前编码器的类型,包括:视频(AVMEDIA_TYPE_VIDEO)、音频(AVMEDIA_TYPE_AUDIO)、字幕(AVMEDIA_TYPE_SUBTITILE)。
AVMediaType具体定义:
enum AVMediaType {AVMEDIA_TYPE_UNKNOWN = -1, ///< Usually treated as AVMEDIA_TYPE_DATAAVMEDIA_TYPE_VIDEO,AVMEDIA_TYPE_AUDIO,AVMEDIA_TYPE_DATA, ///< Opaque data information usually continuousAVMEDIA_TYPE_SUBTITLE,AVMEDIA_TYPE_ATTACHMENT, ///< Opaque data information usually sparseAVMEDIA_TYPE_NB
};
2.5.2. AVMediaType codec_type:指明当前编码器的类型,包括:视频(AVMEDIA_TYPE_VIDEO)、音频(AVMEDIA_TYPE_AUDIO)、字幕(AVMEDIA_TYPE_SUBTITILE)。
2.5.3. AVCodec * codec:指明相应的编解码器,如H264/H265等等
2.5.4. AVCodecID * codec_id:编解码器的ID,这个在上面有详细的说明
2.5.5. void * priv_data:指向相对应的编解码器
2.5.6. int bit_rate:编码的码流,这里包含了音频码流和视频码流码率的设置
2.5.7.thread_count:编解码时候线程的数量,这个由用户自己设置和CPU数量有关。
2.5.8. AVRational time_base:根据该参数可以将pts转换为时间
2.5.9. int width, height:每一帧的宽和高
2.5.10. AVRational time_base:根据该参数可以将pts转换为时间
2.5.11. int gop_size:一组图片的数量,专门用于视频编码
2.5.12. enum AVPixelFormat pix_fmt:像素格式,编码的时候用户设置
2.5.13. int refs:参考帧的数量
2.5.14.enum AVColorSpace colorspace:YUV色彩空间类型
2.5.15.enum AVColorSpace color_range:MPEG JPEG YUV范围
2.5.16.int sample_rate:音频采样率
2.5.17.int channel:声道数(音频)
2.5.18. AVSampleFormat sample_fmt:采样格式
2.5.19.int frame_size:每个音频帧中每个声道的采样数量
2.5.20.int profile:配置类型
2.5.21.int level:级别
2.6.AVPacket
AVPacket是FFMPEG中一个非常重要的结构体,它保存了解复用之后,解码之前的数据,它存的是压缩数据的音视频数据。除了压缩数据后,它还包含了一些重要的参数信息,如显示时间戳(pts)、解码时间戳(dts)、数据时长、流媒体索引等。
AVPacket如果是存储视频数据的话,基本上是存储H264/H265这些码流,如果存储音频数据的话则会保存AAC/MP3码流
重要的结构体成员变量:
2.6.1. AVBufferRef * buf:对数据包所在的引用的计数缓冲区引用
2.6.2. int64_t pts:显示时间戳,它是来视频播放的时候用于控制视频显示顺序和控制速度。PTS是一个相对的时间戳,通常它以毫秒为单位,表示该帧在播放器的哪个时间节点进行显示
2.6.3. int64_t dts:解码时间戳,它是来指视频或者音频在解码时候的时间戳,用于控制解码器的解码顺序和速度。DTS也是一个相对的时间戳,通常它以毫秒为单位,表示该帧在解码器中哪个时间节点进行解码
2.6.4. uint8_t * data:具体的缓冲区数据,这里的数据可以是视频H264数据也可以是音频的AAC数据
2.6.5. int size:缓冲区长度,这里就是具体的码流数据的长度
2.6.6.int stream_index:流媒体索引值,假设同时有音频和视频。那stream_index = 0等于视频数据,stream_index = 1等于音频数据
2.6.7.flags:专门用在视频编码码流的标识符,默认都要添加AV_PKT_FLAG,这指的是每一帧都要添加一个关键帧,否则画面则无法正常解码出来。
2.6.8. AVPacketSideData * side_data:容器可以提供的额外数据包数据,包含多种类型的边信息。
2.6.9. int side_data_element:额外数据包的长度
2.6.10. int64_t duration:AVPacket的持续时间,它的时间以AVStream->time_base为单位作为计算,若未知则为0.
2.6.11.int64_t pos:字节的位置,这个很少用
2.7.AVFrame
AVFrame的结构体一般存储音视频的原始数据,若存储视频数据的话则存储YUV/RGB等数据;若存储音频数据的话,则会存储PCM数据,此外还包含了一些重要的相关信息。
2.7.1. uint8_t * data[AV_NUM_DATA_POINTERS]:原始数据,如果是视频数据则是(YUV,RGB这些数据;音频数据的话是PCM)
2.7.2.int linesize[AV_NUM_DATA_POINTERS]:data中一行数据的大小,要注意:这个值未必就完全等于图像的宽,一般都会大于图像的宽度
2.7.3. width、height:视频帧的宽度和高度
2.7.4. int nb_samples:音频的一个AVFrame种可能包含多个音频帧,这个参数就是包含了多少个音频帧
2.7.5. format:解码后的原始数据类型(如:YUV420、YUV422、RGB24等等)
2.7.6. int key_frame:是否是关键帧
2.7.7. eum AVPictureType pict_type:帧类型(I帧、P帧、B帧),下面是PictureType的枚举类型
enum AVPictureType {
AV_PICTURE_TYPE_NONE = 0, ///< Undefined
AV_PICTURE_TYPE_I, ///< Intra
AV_PICTURE_TYPE_P, ///< Predicted
AV_PICTURE_TYPE_B, ///< Bi-dir predicted
AV_PICTURE_TYPE_S, ///< S(GMC)-VOP MPEG-4
AV_PICTURE_TYPE_SI, ///< Switching Intra
AV_PICTURE_TYPE_SP, ///< Switching Predicted
AV_PICTURE_TYPE_BI, ///< BI type
};
2.7.8. AVRational sample_aspect_ration:宽高比(16:9、4:3)
2.7.9. int64_t pts:显示时间戳
2.7.10. coded_picture_number:编码帧序号
2.7.11. display_picture_number:显示帧序号
2.8. AVIOContext结构体
AVIOContext结构体是FFMPEG管理输入输出的结构体,下面我们来看看这里面比较重要的结构体成员变量
2.8.1. unsigned char * buffer:缓存开始位置
2.8.2. int buffer_size:缓存大小
2.8.3. unsigned char * buf_end:缓存结束的位置
2.8.4. void * opaque:URLContext结构体,下面我们来看看URLContext结构体的详细
URLContext结构体成员变量:
typedef struct URLContext {const AVClass *av_class; ///< information for av_log(). Set by url_open().struct URLProtocol *prot;int flags;int is_streamed; /**< true if streamed (no seek possible), default = false */int max_packet_size; /**< if non zero, the stream is packetized with this max packet size */void *priv_data;char *filename; /**< specified URL */int is_connected;AVIOInterruptCB interrupt_callback;
} URLContext;
2.8.4.1. AVClass:它是在FFMPEG中具体应用很多,它类似于C++中的虚基类的结构体,用于实现多继承的功能。它定义了一系列的函数指针,这些函数指针可以被子类所覆盖了,从而实现子类对父类的重载。
2.8.4.2. URLProtocol:每个协议对应一个URLProtocol,这个结构体也不在FFMPEG提供的头文件中。我们具体来看看这个结构体的定义
typedef struct URLProtocol {const char *name;int (*url_open)(URLContext *h, const char *url, int flags);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);struct URLProtocol *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 priv_data_size;const AVClass *priv_data_class;int flags;int (*url_check)(URLContext *h, int mask);
} URLProtocol;
2.8.4.2.1. url_open:打开对应的文件IO的url地址
2.8.4.2.2. url_read:读取对应的文件IO的url地址的数据内容
2.8.4.2.3. url_write:写入对应的文件IO的url地址的数据内容
2.8.4.2.4. url_seek:移动文件指针
2.8.4.2.5. url_close:关闭文件指针
3在这个结构体里面提供了一些列的回调函数,每个回调函数都会有对应的协议进行编写
二.FFMPEG输出模块初始化
讲解FFMPEG输出模块,输出模块的最大作用是对音视频推流模块进行初始化让其能够正常工作起来,RV1126的码流通过FFMPEG进行推流,输出模块一般由几个步骤。分别由avformat_alloc_output_context2分配AVFormatContext、avformat_new_stream初始化AVStream结构体、avcodec_find_encoder找出对应的codec编码器、利用avcodec_alloc_context3分配AVCodecCotext、设置AVCodecContext结构体参数、利用avcodec_parameters_from_context把codec参数传输到AVStream里面的参数、avio_open初始化FFMPEG的IO结构体、avformat_write_header初始化AVFormatContext。在RV1126+FFMPEG多路码流推流的项目中,FFMPEG输出模块的初始化在rkmedia_ffmpeg_config.cpp的init_rkmedia_ffmpeg_context里面。
FFMPEG输出配置的框图:
上图是整体的框图,我们具体来看看每个框图的代码实现。
2.1. 分配FFMPEG AVFormatContext输出的上下文结构体指针
int avformat_alloc_output_context2(AVFormatContext **ctx, AVOutputFormat *oformat, const char *format_name, const char *filename)
第一个传输参数:AVFormatContext结构体指针的指针,是存储音视频封装格式中包含的信息的结构体,所有对文件的封装、编码都是从这个结构体开始。
第二个传输参数:AVOutputFormat的结构体指针,它主要存储复合流信息的常规配置,默认为设置NULL。
第三个传输参数:format_name指的是复合流的格式,比方说:flv、ts、mp4等等
第四个传输参数:filename是输出地址,输出地址可以是本地文件(如:xxx.mp4、xxx.ts等等)。也可以是网络流地址(如:rtmp://xxx.xxx.xxx.xxx:1935/live/01)
上面这个API是根据我们流媒体类型去分配AVFormatContext结构体。我们传进来的类型会分为FLV_PROTOCOL和TS_PROTOCOL,具体如何配置如下面:
若TS_PROTOCOL类型:avformat_alloc_output_context2(&group->oc, NULL, "mpegts", group->url_addr);
若FLV_PROTOCOL类型:avformat_alloc_output_context2(&group->oc, NULL, "flv", group->url_addr);
注意:TS格式分别可以适配以下流媒体复合流,包括:SRT、UDP、TS本地文件等。flv格式包括:RTMP、FLV本地文件等等。
2.2. 配置推流器编码参数和AVStream结构体
AVStream主要是存储流信息结构体,这个流信息包含音频流和视频流。创建的API是avformat_new_stream,如下图:
AVStream * avformat_new_stream(AVFormatContext *s, AVDictionary **options);
第一个传输参数:AVFormatContext的结构体指针
第二个传输参数:AVDictionary结构体指针的指针
返回值:AVStream结构体指针
2.3. 设置对应的推流器编码器参数
AVCodec *avcodec_find_encoder(enum AVCodecID id); //
第一个传输参数:传递参数AVCodecID
2.4. 根据编码器ID分配AVCodecContext结构体
AVCodecContext *avcodec_alloc_context3(const AVCodec *codec);
第一个参数:传递AVCodec结构体指针
avcodec_find_encoder的主要作用是通过codec_id(编码器id )找到对应的AVCodec结构体。在RV1126推流项目中codec_id我们使用两种,分别是AV_CODEC_ID_H264、AV_CODEC_ID_H265。并利用avcodec_alloc_context3去创建AVCodecContext上下文。
初始化完AVStream和编码上下文结构体之后,我们就需要对这些参数进行配置。重点:推流编码器参数和RV1126编码器的参数要完全一样,否则可能会出问题,具体的如下图:
1920 * 1080编码器和FFMPEG推流器的配置
1280* 720编码器和FFMPEG推流器的配置
FFMPEG的视频编码参数如:分辨率(WIDTH、HEIGHT)、时间基(time_base)、 帧率(r_frame_rate)、GOP_SIZE等都需要和右边VENC的参数要一一对应起来。其中time_base的值要和视频帧率必须要一致。如RV1126高编码器分辨率是1920 * 1080,则FFMPEG推流器的WIDTH = 1920,HEIGHT = 1080;若RV1126编码器的分辨率是1280 * 720,则FFMPEG推流器的WIDTH = 1280,HEIGHT = 720;若RV1126的GOP的值是25,那右边FFMPEG的gop_size 也等于25;time_base的数值和帧率保持一致
AV_CODEC_FLAG_GLOBAL_HEADER:发送视频数据的时候都会在关键帧前面添加SPS/PPS,这个标识符在FFMPEG初始化的时候都需要添加。
2.5. 设置完上述参数之后,拷贝参数到AVStream编解码器,具体的操作如下:
拷贝参数到AVStream,我们封装到open_video自定义函数里面,要先调用avcodec_open2打开编码器,然后再调用avcodec_parameters_from_context把编码器参数传输到AVStream里面
2.5.1.int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);
这个函数的具体作用是,打开编解码器
第一个参数:AVCodecContext结构体指针
第二个参数:AVCodec结构体指针
第三个参数:AVDictionary二级指针
2.5.2. int avcodec_parameters_from_context(AVCodecParameters *par, const AVCodecContext *codec);
这个函数的具体作用是,把AVCodecContext的参数拷贝到AVCodecParameters里面。
第一个参数:AVCodecParameters结构体指针
第二个参数:AVCodecContext结构体指针
2.6. 打开IO文件操作
使用avio_open打开对应的文件,注意这里的文件不仅是指本地的文件也指的是网络流媒体文件,下面是avio_open的定义。
int avio_open(AVIOContext **s, const char *url, int flags);
第一个参数:AVIOContext的结构体指针,它主要是管理数据输入输出的结构体
第二个参数: url地址,这个URL地址既包括本地文件如(xxx.ts、xxx.mp4),也可以是网络流媒体地址,如(rtmp://192.168.22.22:1935/live/01)等
第三个参数:flags标识符
#define AVIO_FLAG_READ 1 /**< read-only */
#define AVIO_FLAG_WRITE 2 /**< write-only */
#define AVIO_FLAG_READ_WRITE (AVIO_FLAG_READ|AVIO_FLAG_WRITE) /**< read-write pseudo flag */
2.7. avformat_write_header对头部进行初始化,输出模块头部进行初始化
int avformat_write_header(AVFormatContext *s, AVDictionary **options);
第一个参数:传递AVFormatContext结构体指针
第二个参数:传递AVDictionary结构体指针的指针