FFmpeg之FLV Muxing & Demuxing

1、FLV简介

FLV是Adobe发布的一种可作为直播也可作为点播的封装格式,其封装格式非常简单,并且每个tag独立存在。常应用于网络的点播与直播场景中,比如常见的流媒体协议rtmp与flv格式兼容的非常好,比如rtmp与flv封装数据packet格式是一样的。查看资料发现rtmp与flv出自于一家,都是Adobe公司的产品。

2 、flv标准格式介绍

flv = flv header +body 构成,body则是有一个个 tag 组成。常见的tag类型有三种,script tag,audio/video tag。其中script tag是第一个tag,存到媒体信息,比如视频宽高,duration等等。

 

文章最底部,领取最新最全C++音视频学习提升资料,内容包括(C/C++Linux 服务器开发,FFmpeg webRTC rtmp hls rtsp ffplay srs

 

3 、flv muxing

在FFmpeg  中,muxing flv 的文件位于libavformat/flvenc.c , 其主要开出三支API 给上层调用,flv_write_header(),flv_write_packet(),flv_write_trailer()。

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      = (const AVCodecTag* const []) {
                          flv_video_codec_ids, flv_audio_codec_ids, 0
                      },
    .flags          = AVFMT_GLOBALHEADER | AVFMT_VARIABLE_FPS |
                      AVFMT_TS_NONSTRICT,
    .priv_class     = &flv_muxer_class,
};
## 

3.1 flv_write_header()
flv_write_header() 主要是对flv header 以及存放meta 的script tag 写入。其主要流程如下:

 

flv_write_header() ,可以看到调用avio_write(pb,“FLV”,3);写入FLV 表示头,然后还调其他几个avio_xxx() 函数写入FLV header。具体代码如下:

    avio_write(pb, "FLV", 3);//写入"FLV"标识
    avio_w8(pb, 1);//版本号1,占一个字节
    avio_w8(pb, FLV_HEADER_FLAG_HASAUDIO * !!flv->audio_par +
                FLV_HEADER_FLAG_HASVIDEO * !!flv->video_par);//音频,视频是否存在flag 位
    avio_wb32(pb, 9);//data offset ,表明flv header 占9个字节
    avio_wb32(pb, 0);//previos tag len 仍为0

我们在来看下一个具体的FLV header 对应的二进制:
在这里插入图片描述 

可以看出FFMPEG 是完全符合FLV header 协议规范的。

我们在来看看write_metadata()函数,它写入的是script tag ,是FLV 文件的第一个tag ,存放meta 信息。如下代码删减不重要的行数 并加入注释:

static void write_metadata(AVFormatContext *s, unsigned int ts)
{
    AVIOContext *pb = s->pb;//文件IO函数
    FLVContext *flv = s->priv_data;//AVFormatContext中的priv_data 实际存放是就是FLVContext
    /* write meta_tag */
    avio_w8(pb, FLV_TAG_TYPE_META); //Script tag标识 0x12
    flv->metadata_size_pos = avio_tell(pb);
    avio_wb24(pb, 0);           // size of data part (sum of all parts below)
    avio_wb24(pb, ts);          // timestamp
    avio_wb32(pb, 0);           // reserved
    /*
    	在了解script data 之前,先自行学习下AMF(Action Message Format),它是一种标准的的Flash 与服务端通信格式,meta 信息就是AMF格格式来存放的 。
    */
    avio_w8(pb, AMF_DATA_TYPE_STRING);//AMF
    put_amf_string(pb, "onMetaData"); // 12 bytes

    /* mixed array (hash) with size and string/type/data tuples */
    avio_w8(pb, AMF_DATA_TYPE_MIXEDARRAY);//相当于键值对,key,value 形式。
    avio_wb32(pb, metadata_count);//写入键值对个数
    put_amf_string(pb, "duration");
    flv->duration_offset = avio_tell(pb);
    put_amf_double(pb, s->duration / AV_TIME_BASE);
    put_amf_string(pb, "width");
	put_amf_double(pb, flv->video_par->width);
	put_amf_string(pb, "height");
	put_amf_double(pb, flv->video_par->height);
	..............................

接着是flv_write_codec_header()函数,主要针对特殊的编解码格式写入一些info , 比如针对AAC 格式写入ADTS头,因为每一帧AAC 数据都需要加入这个头才能播放.而对应的AAC Audio frame 可能无此信息。

3.2 flv_write_packet()
static int flv_write_packet(AVFormatContext *s, AVPacket *pkt), 其作用是加入tag 头 ,并将Audio/video data 写入FLV 文件中,其流程为:1 写入TAG 类型,2 写入datasize, 3 写入timestamp, 4 写入streameID ,5 写入TAG Data,6 写入previous tag size

static int flv_write_packet(AVFormatContext *s, AVPacket *pkt)
{
	avio_w8(pb, FLV_TAG_TYPE_VIDEO);//写入Video /Audio tag type
	avio_wb24(pb, size + flags_size);
	put_timestamp(pb, ts);//ts = pkt->dts, gen
	avio_wb24(pb, flv->reserved);//保留位
	avio_write(pb, data ? data : pkt->data, size);//写入video/audio data
	avio_wb32(pb, size + flags_size + 11); // previous tag size
}

3.2 flv_write_trailer()
static int flv_write_trailer(AVFormatContext *s) 也是写入一些meta 信息,与flv_write_header() 不同的是,Flv_write_trailer() 写入是一些只有Muxing才能知道的信息,比如Videodatasize,Audiodatasize,Filesize,duration 等等。
 

static int flv_write_trailer(AVFormatContext *s)
{
	    avio_seek(pb, flv->videosize_offset, SEEK_SET);//videosize_offset 在写header 时已经计算出来了,并留好空间,在此写入
        put_amf_double(pb, flv->videosize);//video size
        avio_seek(pb, flv->audiosize_offset, SEEK_SET);
        put_amf_double(pb, flv->audiosize);//audio size

        avio_seek(pb, flv->lasttimestamp_offset, SEEK_SET);
        put_amf_double(pb, flv->lasttimestamp);

        avio_seek(pb, flv->lastkeyframetimestamp_offset, SEEK_SET);
        put_amf_double(pb, flv->lastkeyframetimestamp);

        avio_seek(pb, flv->lastkeyframelocation_offset, SEEK_SET);
        put_amf_double(pb, flv->lastkeyframelocation + flv->keyframe_index_size);
        avio_seek(pb, cur_pos, SEEK_SET);
             avio_seek(pb, flv->keyframes_info_offset, SEEK_SET);
             /*下面写入关键帧的位置,以便于加快seek速度,存放位置在头部的Script tag 中 */
        put_amf_string(pb, "filepositions");
        put_amf_dword_array(pb, flv->filepositions_count);
        for (newflv_posinfo = flv->head_filepositions; newflv_posinfo; newflv_posinfo = newflv_posinfo->next) {
            put_amf_double(pb, newflv_posinfo->keyframe_position + flv->keyframe_index_size);
        }
        /*下面写入关键帧的timestamp,存放位置也在头部的Script tag 中 */
        put_amf_string(pb, "times");
        put_amf_dword_array(pb, flv->filepositions_count);
        for (newflv_posinfo = flv->head_filepositions; 	newflv_posinfo; newflv_posinfo = newflv_posinfo->next) {
            put_amf_double(pb, newflv_posinfo->keyframe_timestamp);
        }
		/* 给每个video stream 写入EOS tag, 疑问audio tag 不需要EOS tag 吗?假如audio 早于video 结束,是否会出现有画面无声音 */
        for (i = 0; i < s->nb_streams; i++) {
                if (par->codec_type == AVMEDIA_TYPE_VIDEO &&
                    (par->codec_id == AV_CODEC_ID_H264 || par->codec_id == AV_CODEC_ID_MPEG4))
                put_avc_eos_tag(pb, sc->last_ts);
        }
        file_size = avio_tell(pb);
       put_amf_double(pb, file_size);//最后写入file size ,FLV Muxing 到此结束
}

4 补充部分

1 看flv_write_packet() 函数,是有存放字幕功能的。

  if (par->codec_type == AVMEDIA_TYPE_DATA ||
        par->codec_type == AVMEDIA_TYPE_SUBTITLE ) {
        int data_size;
        int64_t metadata_size_pos = avio_tell(pb);
        if (par->codec_id == AV_CODEC_ID_TEXT) {
            // legacy FFmpeg magic?
            avio_w8(pb, AMF_DATA_TYPE_STRING);
            put_amf_string(pb, "onTextData");
            avio_w8(pb, AMF_DATA_TYPE_MIXEDARRAY);
            avio_wb32(pb, 2);
            put_amf_string(pb, "type");
            avio_w8(pb, AMF_DATA_TYPE_STRING);
            put_amf_string(pb, "Text");
            put_amf_string(pb, "text");
            avio_w8(pb, AMF_DATA_TYPE_STRING);
            put_amf_string(pb, pkt->data);
            put_amf_string(pb, "");
            avio_w8(pb, AMF_END_OF_OBJECT);
        } else {
            // just pass the metadata through
            avio_write(pb, data ? data : pkt->data, size);
        }

2 标准的FLV 文件是不带keyframe 索引信息的,但应其广泛使用,已经称为常用字段,因此FFMPEG 也支持这功能。

for (newflv_posinfo = flv->head_filepositions; newflv_posinfo; newflv_posinfo = newflv_posinfo->next) {
            put_amf_double(pb, newflv_posinfo->keyframe_position + flv->keyframe_index_size);
}
for (newflv_posinfo = flv->head_filepositions; newflv_posinfo; newflv_posinfo = newflv_posinfo->next) {
 	 put_amf_double(pb, newflv_posinfo->keyframe_timestamp);
}

 3 FLV script tag 的 metadata 信息是以AMF 格式存储的。它是它是一种标准的的Flash 与服务端通信格式,应用范围很广。

typedef enum {
    AMF_DATA_TYPE_NUMBER      = 0x00,//double 类型,8个字节
    AMF_DATA_TYPE_BOOL        = 0x01,//bool 类型,1 字节
    AMF_DATA_TYPE_STRING      = 0x02,//string 类型,len 需指定
    AMF_DATA_TYPE_OBJECT      = 0x03,//key / value形式,
    AMF_DATA_TYPE_NULL        = 0x05,
    AMF_DATA_TYPE_UNDEFINED   = 0x06,
    AMF_DATA_TYPE_REFERENCE   = 0x07,
    AMF_DATA_TYPE_MIXEDARRAY  = 0x08,//存放多个key/value,FLV meta info 就是这样存放的
    AMF_DATA_TYPE_OBJECT_END  = 0x09,
    AMF_DATA_TYPE_ARRAY       = 0x0a,
    AMF_DATA_TYPE_DATE        = 0x0b,
    AMF_DATA_TYPE_LONG_STRING = 0x0c,
    AMF_DATA_TYPE_UNSUPPORTED = 0x0d,
} AMFDataType;

猜你喜欢

转载自blog.csdn.net/m0_60259116/article/details/125207500
FLV
今日推荐