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;