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 头。字段说明如下:
- PreviousTagSize,是 4 字节长度,表面之前的 Tag 的长度,包含了 tag 头和 tag 数据。第一个 tag ,此值是 0。
- TagType,是 1 字节长度。音频:8, 视频:9
- DataSize , 是 3 字节长。flv tag 的数据长度,其实如图里 audio tag 头及其数据长度。
- Timestamp,是 3 字节长。时间戳,貌似 flv 播放需要。
- TimestampExtended, 是对 Timestamp 长度的扩展,当时间长度 3 字节不能表示的时候,启用扩展字段。
- StreamID ,3 字节长,都填 0
字段 | 字节数 | 描述 |
---|---|---|
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,否则为线性数组。 |
码流分析器:
5.视频 tag 格式
视频 tag 格式
视频 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;
}