ffmpeg新手成长之路
——关于flv转封装过程中编码器信息变化如何处理(困扰已久的avc sequence header更新问题)
一、背景介绍
私有协议传输H264裸流,进行录制,使用ffmpeg做flv封装。受限于网络情况或者实际录制需求,H264裸流数据源发生变化,编码信息肯定也要更新咯!
比如说,刚开始视频源是个1920*1080的,但是数据源作为数据发送方通过网络带宽统计,得知当前网络丢包严重,画面需要降质,分辨率调整为720*576,接收过程中识别到视频源编码信息发生了变化,需要把编码信息变化实时更新到flv容器中。
在flv封装格式中,主要通过一个avc sequence header的Tag来更新H264编码信息,那么问题来了,如何把识别到的编码信息变化写到这个结构中呢,下面来追本溯源。
二、AVCodecContext中的extradata结构
编解码上下文信息中的extradata结构记录了一些额外的编码信息,这部分信息是avcodec需要用到的。注意看这个变量名extradata,后续另外一个地方也出现了extradata。
通过查阅网上很多用例,不难看出,h264编码中很重的sps和pps信息就存储在extradata中。
/**
* some codecs need / can use extradata like Huffman tables.
* MJPEG: Huffman tables
* rv10: additional flags
* MPEG-4: global headers (they can be in the bitstream or here)
* The allocated memory should be AV_INPUT_BUFFER_PADDING_SIZE bytes larger
* than extradata_size to avoid problems if it is read with the bitstream reader.
* The bytewise contents of extradata must not depend on the architecture or CPU endianness.
* - encoding: Set/allocated/freed by libavcodec.
* - decoding: Set/allocated/freed by user.
*/
uint8_t *extradata;
int extradata_size;
三、AVPacket中的AVPacketSideData结构
笔者苦苦追寻更新avc sequence header的方法,一直以为这个信息是avcodec范畴修改才能才能更新的,曾经尝试更新AVCodecContext,无果……想来想去,ffmpeg不至于这么狠,不给用户开放修改的接口,但是苦于初学入门,不知道ffmpeg博大精深啊。很久以后,无意无意间发现AVPacket有这么side_data一个变量,很纳闷,想要了解一下。
/**
* Additional packet data that can be provided by the container.
* Packet can contain several types of side information.
*/
AVPacketSideData *side_data;
通过注释,还是可以看出,这个变量存储的信息,可能封装时容器会用到。(~正中下怀啊)
再细看,AVPacketSideDataType 枚举中还有个AV_PKT_DATA_NEW_EXTRADATA类型。(~是不是很眼熟,extradata, extradata)。灵光一闪,莫不是我要找的答案???
typedef struct AVPacketSideData {
uint8_t *data;
int size;
enum AVPacketSideDataType type;
} AVPacketSideData;
/**
* @defgroup lavc_packet AVPacket
*
* Types and functions for working with AVPacket.
* @{
*/
enum AVPacketSideDataType {
...
/**
* The AV_PKT_DATA_NEW_EXTRADATA is used to notify the codec or the format
* that the extradata buffer was changed and the receiving side should
* act upon it appropriately. The new extradata is embedded in the side
* data buffer and should be immediately used for processing the current
* frame or packet.
*/
AV_PKT_DATA_NEW_EXTRADATA,
...
};
写的多清楚,这个信息用于通知编解码器或者格式封装处理,有写编码信息发生变化了。试想,播放器解析到封装数据、编解码信息中的信息变化,是该做出重置解码器的动作了,Binggo~~~
四、如何更新AVPacketSideData
给AVPacket添加side_data信息,信息内容当然是sps和pps了
uint8_t* side_data = av_packet_new_side_data(&AVPacket_, AV_PKT_DATA_NEW_EXTRADATA, sps_ppslen);
if (side_data)
{
memcpy(side_data, sps_pps, sps_ppslen);
}
看下函数声明,这个函数自动给AVPacket分配了side_data的空间,而av_packet_free_side_data函数则可以释放分配的空间。
/**
* Allocate new information of a packet.
*
* @param pkt packet
* @param type side information type
* @param size side information size
* @return pointer to fresh allocated data or NULL otherwise
*/
uint8_t* av_packet_new_side_data(AVPacket *pkt, enum AVPacketSideDataType type,
int size);
/**
* Convenience function to free all the side data stored.
* All the other fields stay untouched.
*
* @param pkt packet
*/
void av_packet_free_side_data(AVPacket *pkt);