flv 是 flash video 的缩写,是 Adobe Flash payler 支持的一种流媒体播放格式。flv 是一种层级格式,除了一个 flv header 外,剩下全是由 一个个 tag 组成。tag 是由 tag 头和 tag 数据组成。tag 类型分为音频、视频、脚本,一共三种类型。每一种数据类型又有自己的 tag 头。
1. flv file header

引用网络图片:

2. AMF
该类型Tag又被称为MetaData Tag,存放一些关于FLV视频和音频的元信息,比如:duration、width、height等。通常该类型Tag会作为FLV文件的第一个tag,并且只有一个,跟在File Header后。该类型Tag DaTa的结构如下所示:
第一个AMF包:

第1个字节表示AMF包类型,一般总是0x02,表示字符串。第2-3个字节为UI16类型值,标识字符串的长度,一般总是0x000A(“onMetaData”长度)。后面字节为具体的字符串,一般总为“onMetaData”(6F,6E,4D,65,74,61,44,61,74,61)。
第二个AMF包:
第1个字节表示AMF包类型,一般总是0x08,表示数组。第2-5个字节为UI32类型值,表示数组元素的个数。后面即为各数组元素的封装,数组元素为元素名称和值组成的对。常见的数组元素如下表所示。

码流分析
tagheader :


3. avc sequece header tag


AAC special config

4 .音频tag:

如上图,除了 9 字节的公共文件头外,body 部分就是一个个 tag 组成的。每一个 tag 都有 15 字节的 tag 头。字段说明如下:
| 字段 | 字节数 | 描述 |
|---|---|---|
| TagType | 1 | 0x12 : Scipt 0x09 :视频 0x08:音频 |
| DataSize | 3 | 数据长度 |
| TimeStamp | 3 | 相对于第一帧的时间戳,单位为毫秒,第一帧总为0 |
| TimeStampExtender | 1 | 时间戳的补充字段 |
| StreamID | 3 | 0 |
flv tag 的 body 部分其实就是音频的 tag 部分了,图中每一个字段都有简单说明。具体每一个参数都有很多取值,取值的详细说明参考 [1] 中标明的 flv 规范音频 tag 部分。
对于 AACPacketType = 0 的情况,音频数据是 AudioSpecificConfig 格式,此格式在 ISO/IEC 14496-3 2009 中第一,可惜下载不到此文档。
如果是 AACPacketType = 1 的情况,那么后续数据都是 AAC 格式了(data里面的数据不包含Acc 的头7个字节)。
音频数据
| Field | type | Comment |
| 音频格式 | UB4 | 0 = Linear PCM, platform endian 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 7, 8, 14, and 15:内部保留使用。 flv是不支持g711a的,如果要用,可能要用线性音频。 |
| 采样率 | UB2 | For AAC: always 3 (AAC总是3) 0 = 5.5-kHz 1 = 11-kHz 2 = 22-kHz 3 = 44-kHz |
| 采样大小 | UB1 | 0 = snd8Bit 1 = snd16Bit |
| 声道 | UB1 | 0=单声道 1=立体声,双声道。AAC永远是1 |
| 声音数据 | UI8[N] | 如果是PCM线性数据,存储的时候每个16bit小端存储,有符号。 如果音频格式是AAC,则存储的数据是AAC AUDIO DATA,否则为线性数组。 |
码流分析器:


视频 tag 格式和音频格式 flv 文件头、flv tag 头都相同。
这里需要说明一下的是,当 flv 包含的是 h264 的时候,CodecID 值是 7。在 H264 视频流开始的第一个 NALU 数据,需要发送 SPS、PPS 类型的数据。此时,AVCPacketType 会填 0,SPS/PPS 是包含在 AVCDecoderConfigurationRecord 结构中。
视频Tag Data 是由 FrameType 和CodecID 以及VideoData 或者AVCVIDEOPACKET构成,AVCVIDEOPACKET只存在第一个video tag 中。
1、AVCVIDEOPACKET 解析
| 字段 | 字节 | 描述 |
|---|---|---|
| AVCPACKETType | 1 | 0:AVC sequence header 1:AVC NALU 2:AVC end of sequence |
| Composition Offset | 3 | 合成时间。 AVCPacketType==1,表示 合成时间(单位毫秒); 否则为0 |
| VideoData | 2 | 如果AVCPacketType=0数据部分为AVCDecoderConfigurationRecord; 如果AVCPacketType=1,数据部分为1个或多个NALU; 如果AVCPacketType==2,数据部分为空 |
关键的AVCDecoderConfigurationRecord 数据构成
| 字段 | 字节 | 描述 |
|---|---|---|
| 版本 | 1 | 0x01版本号为1 |
| 编码规格 | 3 | sps[1]+sps[2]+sps[3] |
| NALU 的长度 | 1 | 0xff |
| SPS个数 | 1 | 0xE1 |
| SPS长度 | 2 | 整个sps长度 |
| SPS的内容 | n | 整个sps |
| PPS个数 | 1 | 0x01 |
| PPS长度 | 2 | 整个pps长度 |
| PPS内容 | n | 整个pps内容 |
普通Video Data 解析
讲完第一个特殊的VideoData, 其他所有的VideoData 都是由AVPACKETTYPE + CompsitionTime offset + Data(去除startCode 的有效图像数据)构成。

ffmpeg
在上一章整体rtmp 协议分析:整体分ffmpeg 实现rtmp 协议流程中flv
- 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,
- .init = flv_init,
- .write_header = flv_write_header,
- .write_packet = flv_write_packet,
- .write_trailer = flv_write_trailer,
- .check_bitstream= flv_check_bitstream,
- .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,
- };
flv_write_header
- static int flv_write_header(AVFormatContext *s)
- {
- int i;
- AVIOContext *pb = s->pb;
- FLVContext *flv = s->priv_data;
-
- avio_write(pb, "FLV", 3);
- avio_w8(pb, 1);
- avio_w8(pb, FLV_HEADER_FLAG_HASAUDIO * !!flv->audio_par +
- FLV_HEADER_FLAG_HASVIDEO * !!flv->video_par);
- avio_wb32(pb, 9);
- avio_wb32(pb, 0);
- //上面代码是对flv file header 封装
- for (i = 0; i < s->nb_streams; i++)
- if (s->streams[i]->codecpar->codec_tag == 5) {
- avio_w8(pb, 8); // message type
- avio_wb24(pb, 0); // include flags
- avio_wb24(pb, 0); // time stamp
- avio_wb32(pb, 0); // reserved
- avio_wb32(pb, 11); // size
- flv->reserved = 5;
- }
- if (flv->flags & FLV_NO_METADATA) {
- pb->seekable = 0;
- } else {
- write_metadata(s, 0);
- // 对script tag 封装
- }
-
- for (i = 0; i < s->nb_streams; i++) {
- // avc sequece header tag
- // aac spcial config 封装
- flv_write_codec_header(s, s->streams[i]->codecpar, 0);
- }
- flv->datastart_offset = avio_tell(pb);
- return 0;
- }
flv_write_packet 主要对音视频tag 封装:
- static int flv_write_packet(AVFormatContext *s, AVPacket *pkt)
- {
- AVIOContext *pb = s->pb;
- AVCodecParameters *par = s->streams[pkt->stream_index]->codecpar;
- FLVContext *flv = s->priv_data;
- FLVStreamContext *sc = s->streams[pkt->stream_index]->priv_data;
- unsigned ts;
- int size = pkt->size;
- uint8_t *data = NULL;
- int flags = -1, flags_size, ret = 0;
- int64_t cur_offset = avio_tell(pb);
-
- if (par->codec_type == AVMEDIA_TYPE_AUDIO && !pkt->size) {
- av_log(s, AV_LOG_WARNING, "Empty audio Packet\n");
- return AVERROR(EINVAL);
- }
-
- if (par->codec_id == AV_CODEC_ID_VP6F || par->codec_id == AV_CODEC_ID_VP6A ||
- par->codec_id == AV_CODEC_ID_VP6 || par->codec_id == AV_CODEC_ID_AAC)
- flags_size = 2;
- else if (par->codec_id == AV_CODEC_ID_H264 || par->codec_id == AV_CODEC_ID_MPEG4)
- flags_size = 5;
- else
- flags_size = 1;
-
- if (par->codec_id == AV_CODEC_ID_AAC || par->codec_id == AV_CODEC_ID_H264
- || par->codec_id == AV_CODEC_ID_MPEG4) {
- int side_size = 0;
- uint8_t *side = av_packet_get_side_data(pkt, AV_PKT_DATA_NEW_EXTRADATA, &side_size);
- if (side && side_size > 0 && (side_size != par->extradata_size || memcmp(side, par->extradata, side_size))) {
- ret = ff_alloc_extradata(par, side_size);
- if (ret < 0)
- return ret;
- memcpy(par->extradata, side, side_size);
- flv_write_codec_header(s, par, pkt->dts);
- }
- }
-
- if (flv->delay == AV_NOPTS_VALUE)
- flv->delay = -pkt->dts;
-
- if (pkt->dts < -flv->delay) {
- av_log(s, AV_LOG_WARNING,
- "Packets are not in the proper order with respect to DTS\n");
- return AVERROR(EINVAL);
- }
- if (par->codec_id == AV_CODEC_ID_H264 || par->codec_id == AV_CODEC_ID_MPEG4) {
- if (pkt->pts == AV_NOPTS_VALUE) {
- av_log(s, AV_LOG_ERROR, "Packet is missing PTS\n");
- return AVERROR(EINVAL);
- }
- }
-
- ts = pkt->dts;
-
- if (s->event_flags & AVSTREAM_EVENT_FLAG_METADATA_UPDATED) {
- write_metadata(s, ts);
- s->event_flags &= ~AVSTREAM_EVENT_FLAG_METADATA_UPDATED;
- }
-
- avio_write_marker(pb, av_rescale(ts, AV_TIME_BASE, 1000),
- pkt->flags & AV_PKT_FLAG_KEY && (flv->video_par ? par->codec_type == AVMEDIA_TYPE_VIDEO : 1) ? AVIO_DATA_MARKER_SYNC_POINT : AVIO_DATA_MARKER_BOUNDARY_POINT);
-
- switch (par->codec_type) {
- case AVMEDIA_TYPE_VIDEO:
- avio_w8(pb, FLV_TAG_TYPE_VIDEO);
-
- flags = ff_codec_get_tag(flv_video_codec_ids, par->codec_id);
-
- flags |= pkt->flags & AV_PKT_FLAG_KEY ? FLV_FRAME_KEY : FLV_FRAME_INTER;
- break;
- case AVMEDIA_TYPE_AUDIO:
- flags = get_audio_flags(s, par);
-
- av_assert0(size);
-
- avio_w8(pb, FLV_TAG_TYPE_AUDIO);
- break;
- case AVMEDIA_TYPE_SUBTITLE:
- case AVMEDIA_TYPE_DATA:
- avio_w8(pb, FLV_TAG_TYPE_META);
- break;
- default:
- return AVERROR(EINVAL);
- }
-
- if (par->codec_id == AV_CODEC_ID_H264 || par->codec_id == AV_CODEC_ID_MPEG4) {
- /* check if extradata looks like mp4 formatted */
- if (par->extradata_size > 0 && *(uint8_t*)par->extradata != 1)
- if ((ret = ff_avc_parse_nal_units_buf(pkt->data, &data, &size)) < 0)
- return ret;
- } else if (par->codec_id == AV_CODEC_ID_AAC && pkt->size > 2 &&
- (AV_RB16(pkt->data) & 0xfff0) == 0xfff0) {
- if (!s->streams[pkt->stream_index]->nb_frames) {
- av_log(s, AV_LOG_ERROR, "Malformed AAC bitstream detected: "
- "use the audio bitstream filter 'aac_adtstoasc' to fix it "
- "('-bsf:a aac_adtstoasc' option with ffmpeg)\n");
- return AVERROR_INVALIDDATA;
- }
- av_log(s, AV_LOG_WARNING, "aac bitstream error\n");
- }
-
- /* check Speex packet duration */
- if (par->codec_id == AV_CODEC_ID_SPEEX && ts - sc->last_ts > 160)
- av_log(s, AV_LOG_WARNING, "Warning: Speex stream has more than "
- "8 frames per packet. Adobe Flash "
- "Player cannot handle this!\n");
-
- if (sc->last_ts < ts)
- sc->last_ts = ts;
-
- if (size + flags_size >= 1<<24) {
- av_log(s, AV_LOG_ERROR, "Too large packet with size %u >= %u\n",
- size + flags_size, 1<<24);
- ret = AVERROR(EINVAL);
- goto fail;
- }
-
- avio_wb24(pb, size + flags_size);
- put_timestamp(pb, ts);
- avio_wb24(pb, flv->reserved);
-
- 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);
- }
- /* write total size of tag */
- data_size = avio_tell(pb) - metadata_size_pos;
- avio_seek(pb, metadata_size_pos - 10, SEEK_SET);
- avio_wb24(pb, data_size);
- avio_seek(pb, data_size + 10 - 3, SEEK_CUR);
- avio_wb32(pb, data_size + 11);
- } else {
- av_assert1(flags>=0);
- avio_w8(pb,flags);
- if (par->codec_id == AV_CODEC_ID_VP6)
- avio_w8(pb,0);
- if (par->codec_id == AV_CODEC_ID_VP6F || par->codec_id == AV_CODEC_ID_VP6A) {
- if (par->extradata_size)
- avio_w8(pb, par->extradata[0]);
- else
- avio_w8(pb, ((FFALIGN(par->width, 16) - par->width) << 4) |
- (FFALIGN(par->height, 16) - par->height));
- } else if (par->codec_id == AV_CODEC_ID_AAC)
- avio_w8(pb, 1); // AAC raw
- else if (par->codec_id == AV_CODEC_ID_H264 || par->codec_id == AV_CODEC_ID_MPEG4) {
- avio_w8(pb, 1); // AVC NALU
- avio_wb24(pb, pkt->pts - pkt->dts);
- }
-
- avio_write(pb, data ? data : pkt->data, size);
-
- avio_wb32(pb, size + flags_size + 11); // previous tag size
- flv->duration = FFMAX(flv->duration,
- pkt->pts + flv->delay + pkt->duration);
- }
-
- if (flv->flags & FLV_ADD_KEYFRAME_INDEX) {
- switch (par->codec_type) {
- case AVMEDIA_TYPE_VIDEO:
- flv->videosize += (avio_tell(pb) - cur_offset);
- flv->lasttimestamp = flv->acurframeindex / flv->framerate;
- flv->acurframeindex++;
- if (pkt->flags & AV_PKT_FLAG_KEY) {
- double ts = flv->lasttimestamp;
- int64_t pos = cur_offset;
-
- flv->lastkeyframetimestamp = ts;
- flv->lastkeyframelocation = pos;
- ret = flv_append_keyframe_info(s, flv, ts, pos);
- if (ret < 0)
- goto fail;
- }
- break;
-
- case AVMEDIA_TYPE_AUDIO:
- flv->audiosize += (avio_tell(pb) - cur_offset);
- break;
-
- default:
- av_log(s, AV_LOG_WARNING, "par->codec_type is type = [%d]\n", par->codec_type);
- break;
- }
- }
- fail:
- av_free(data);
-
- return ret;
- }