• 流媒体分析之rtmp 协议flv 封装


    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
    字段字节数描述
    TagType10x12 : Scipt 0x09 :视频 0x08:音频
    DataSize3数据长度
    TimeStamp3相对于第一帧的时间戳,单位为毫秒,第一帧总为0
    TimeStampExtender1时间戳的补充字段
    StreamID30

    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
    1 = ADPCM
    2 = MP3
    3 = Linear PCM, little endian
    4 = Nellymoser 16-kHz mono
    5 = Nellymoser 8-kHz mono
    6 = Nellymoser

    7 = G.711 A-law logarithmic PCM

    8 = G.711 mu-law logarithmic PCM 9 = reserved

    10 = AAC
    11 = Speex

    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 解析

    字段字节描述
    AVCPACKETType10:AVC sequence header 1:AVC NALU 2:AVC end of sequence
    Composition Offset3合成时间。 AVCPacketType==1,表示 合成时间(单位毫秒); 否则为0
    VideoData2如果AVCPacketType=0数据部分为AVCDecoderConfigurationRecord; 如果AVCPacketType=1,数据部分为1个或多个NALU; 如果AVCPacketType==2,数据部分为空

    关键的AVCDecoderConfigurationRecord 数据构成

    字段字节描述
    版本10x01版本号为1
    编码规格3sps[1]+sps[2]+sps[3]
    NALU 的长度10xff
    SPS个数10xE1
    SPS长度2整个sps长度
    SPS的内容n整个sps
    PPS个数10x01
    PPS长度2整个pps长度
    PPS内容n整个pps内容

    普通Video Data 解析

    讲完第一个特殊的VideoData, 其他所有的VideoData 都是由AVPACKETTYPE + CompsitionTime offset + Data(去除startCode 的有效图像数据)构成。

    ffmpeg

    在上一章整体rtmp 协议分析:整体分ffmpeg  实现rtmp 协议流程中flv

    1. AVOutputFormat ff_flv_muxer = {
    2. .name = "flv",
    3. .long_name = NULL_IF_CONFIG_SMALL("FLV (Flash Video)"),
    4. .mime_type = "video/x-flv",
    5. .extensions = "flv",
    6. .priv_data_size = sizeof(FLVContext),
    7. .audio_codec = CONFIG_LIBMP3LAME ? AV_CODEC_ID_MP3 : AV_CODEC_ID_ADPCM_SWF,
    8. .video_codec = AV_CODEC_ID_FLV1,
    9. .init = flv_init,
    10. .write_header = flv_write_header,
    11. .write_packet = flv_write_packet,
    12. .write_trailer = flv_write_trailer,
    13. .check_bitstream= flv_check_bitstream,
    14. .codec_tag = (const AVCodecTag* const []) {
    15. flv_video_codec_ids, flv_audio_codec_ids, 0
    16. },
    17. .flags = AVFMT_GLOBALHEADER | AVFMT_VARIABLE_FPS |
    18. AVFMT_TS_NONSTRICT,
    19. .priv_class = &flv_muxer_class,
    20. };

     flv_write_header 

    1. static int flv_write_header(AVFormatContext *s)
    2. {
    3. int i;
    4. AVIOContext *pb = s->pb;
    5. FLVContext *flv = s->priv_data;
    6. avio_write(pb, "FLV", 3);
    7. avio_w8(pb, 1);
    8. avio_w8(pb, FLV_HEADER_FLAG_HASAUDIO * !!flv->audio_par +
    9. FLV_HEADER_FLAG_HASVIDEO * !!flv->video_par);
    10. avio_wb32(pb, 9);
    11. avio_wb32(pb, 0);
    12. //上面代码是对flv file header 封装
    13. for (i = 0; i < s->nb_streams; i++)
    14. if (s->streams[i]->codecpar->codec_tag == 5) {
    15. avio_w8(pb, 8); // message type
    16. avio_wb24(pb, 0); // include flags
    17. avio_wb24(pb, 0); // time stamp
    18. avio_wb32(pb, 0); // reserved
    19. avio_wb32(pb, 11); // size
    20. flv->reserved = 5;
    21. }
    22. if (flv->flags & FLV_NO_METADATA) {
    23. pb->seekable = 0;
    24. } else {
    25. write_metadata(s, 0);
    26. // 对script tag 封装
    27. }
    28. for (i = 0; i < s->nb_streams; i++) {
    29. // avc sequece header  tag
    30. // aac spcial config 封装
    31. flv_write_codec_header(s, s->streams[i]->codecpar, 0);
    32. }
    33. flv->datastart_offset = avio_tell(pb);
    34. return 0;
    35. }

    flv_write_packet 主要对音视频tag 封装:

    1. static int flv_write_packet(AVFormatContext *s, AVPacket *pkt)
    2. {
    3. AVIOContext *pb = s->pb;
    4. AVCodecParameters *par = s->streams[pkt->stream_index]->codecpar;
    5. FLVContext *flv = s->priv_data;
    6. FLVStreamContext *sc = s->streams[pkt->stream_index]->priv_data;
    7. unsigned ts;
    8. int size = pkt->size;
    9. uint8_t *data = NULL;
    10. int flags = -1, flags_size, ret = 0;
    11. int64_t cur_offset = avio_tell(pb);
    12. if (par->codec_type == AVMEDIA_TYPE_AUDIO && !pkt->size) {
    13. av_log(s, AV_LOG_WARNING, "Empty audio Packet\n");
    14. return AVERROR(EINVAL);
    15. }
    16. if (par->codec_id == AV_CODEC_ID_VP6F || par->codec_id == AV_CODEC_ID_VP6A ||
    17. par->codec_id == AV_CODEC_ID_VP6 || par->codec_id == AV_CODEC_ID_AAC)
    18. flags_size = 2;
    19. else if (par->codec_id == AV_CODEC_ID_H264 || par->codec_id == AV_CODEC_ID_MPEG4)
    20. flags_size = 5;
    21. else
    22. flags_size = 1;
    23. if (par->codec_id == AV_CODEC_ID_AAC || par->codec_id == AV_CODEC_ID_H264
    24. || par->codec_id == AV_CODEC_ID_MPEG4) {
    25. int side_size = 0;
    26. uint8_t *side = av_packet_get_side_data(pkt, AV_PKT_DATA_NEW_EXTRADATA, &side_size);
    27. if (side && side_size > 0 && (side_size != par->extradata_size || memcmp(side, par->extradata, side_size))) {
    28. ret = ff_alloc_extradata(par, side_size);
    29. if (ret < 0)
    30. return ret;
    31. memcpy(par->extradata, side, side_size);
    32. flv_write_codec_header(s, par, pkt->dts);
    33. }
    34. }
    35. if (flv->delay == AV_NOPTS_VALUE)
    36. flv->delay = -pkt->dts;
    37. if (pkt->dts < -flv->delay) {
    38. av_log(s, AV_LOG_WARNING,
    39. "Packets are not in the proper order with respect to DTS\n");
    40. return AVERROR(EINVAL);
    41. }
    42. if (par->codec_id == AV_CODEC_ID_H264 || par->codec_id == AV_CODEC_ID_MPEG4) {
    43. if (pkt->pts == AV_NOPTS_VALUE) {
    44. av_log(s, AV_LOG_ERROR, "Packet is missing PTS\n");
    45. return AVERROR(EINVAL);
    46. }
    47. }
    48. ts = pkt->dts;
    49. if (s->event_flags & AVSTREAM_EVENT_FLAG_METADATA_UPDATED) {
    50. write_metadata(s, ts);
    51. s->event_flags &= ~AVSTREAM_EVENT_FLAG_METADATA_UPDATED;
    52. }
    53. avio_write_marker(pb, av_rescale(ts, AV_TIME_BASE, 1000),
    54. 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);
    55. switch (par->codec_type) {
    56. case AVMEDIA_TYPE_VIDEO:
    57. avio_w8(pb, FLV_TAG_TYPE_VIDEO);
    58. flags = ff_codec_get_tag(flv_video_codec_ids, par->codec_id);
    59. flags |= pkt->flags & AV_PKT_FLAG_KEY ? FLV_FRAME_KEY : FLV_FRAME_INTER;
    60. break;
    61. case AVMEDIA_TYPE_AUDIO:
    62. flags = get_audio_flags(s, par);
    63. av_assert0(size);
    64. avio_w8(pb, FLV_TAG_TYPE_AUDIO);
    65. break;
    66. case AVMEDIA_TYPE_SUBTITLE:
    67. case AVMEDIA_TYPE_DATA:
    68. avio_w8(pb, FLV_TAG_TYPE_META);
    69. break;
    70. default:
    71. return AVERROR(EINVAL);
    72. }
    73. if (par->codec_id == AV_CODEC_ID_H264 || par->codec_id == AV_CODEC_ID_MPEG4) {
    74. /* check if extradata looks like mp4 formatted */
    75. if (par->extradata_size > 0 && *(uint8_t*)par->extradata != 1)
    76. if ((ret = ff_avc_parse_nal_units_buf(pkt->data, &data, &size)) < 0)
    77. return ret;
    78. } else if (par->codec_id == AV_CODEC_ID_AAC && pkt->size > 2 &&
    79. (AV_RB16(pkt->data) & 0xfff0) == 0xfff0) {
    80. if (!s->streams[pkt->stream_index]->nb_frames) {
    81. av_log(s, AV_LOG_ERROR, "Malformed AAC bitstream detected: "
    82. "use the audio bitstream filter 'aac_adtstoasc' to fix it "
    83. "('-bsf:a aac_adtstoasc' option with ffmpeg)\n");
    84. return AVERROR_INVALIDDATA;
    85. }
    86. av_log(s, AV_LOG_WARNING, "aac bitstream error\n");
    87. }
    88. /* check Speex packet duration */
    89. if (par->codec_id == AV_CODEC_ID_SPEEX && ts - sc->last_ts > 160)
    90. av_log(s, AV_LOG_WARNING, "Warning: Speex stream has more than "
    91. "8 frames per packet. Adobe Flash "
    92. "Player cannot handle this!\n");
    93. if (sc->last_ts < ts)
    94. sc->last_ts = ts;
    95. if (size + flags_size >= 1<<24) {
    96. av_log(s, AV_LOG_ERROR, "Too large packet with size %u >= %u\n",
    97. size + flags_size, 1<<24);
    98. ret = AVERROR(EINVAL);
    99. goto fail;
    100. }
    101. avio_wb24(pb, size + flags_size);
    102. put_timestamp(pb, ts);
    103. avio_wb24(pb, flv->reserved);
    104. if (par->codec_type == AVMEDIA_TYPE_DATA ||
    105. par->codec_type == AVMEDIA_TYPE_SUBTITLE ) {
    106. int data_size;
    107. int64_t metadata_size_pos = avio_tell(pb);
    108. if (par->codec_id == AV_CODEC_ID_TEXT) {
    109. // legacy FFmpeg magic?
    110. avio_w8(pb, AMF_DATA_TYPE_STRING);
    111. put_amf_string(pb, "onTextData");
    112. avio_w8(pb, AMF_DATA_TYPE_MIXEDARRAY);
    113. avio_wb32(pb, 2);
    114. put_amf_string(pb, "type");
    115. avio_w8(pb, AMF_DATA_TYPE_STRING);
    116. put_amf_string(pb, "Text");
    117. put_amf_string(pb, "text");
    118. avio_w8(pb, AMF_DATA_TYPE_STRING);
    119. put_amf_string(pb, pkt->data);
    120. put_amf_string(pb, "");
    121. avio_w8(pb, AMF_END_OF_OBJECT);
    122. } else {
    123. // just pass the metadata through
    124. avio_write(pb, data ? data : pkt->data, size);
    125. }
    126. /* write total size of tag */
    127. data_size = avio_tell(pb) - metadata_size_pos;
    128. avio_seek(pb, metadata_size_pos - 10, SEEK_SET);
    129. avio_wb24(pb, data_size);
    130. avio_seek(pb, data_size + 10 - 3, SEEK_CUR);
    131. avio_wb32(pb, data_size + 11);
    132. } else {
    133. av_assert1(flags>=0);
    134. avio_w8(pb,flags);
    135. if (par->codec_id == AV_CODEC_ID_VP6)
    136. avio_w8(pb,0);
    137. if (par->codec_id == AV_CODEC_ID_VP6F || par->codec_id == AV_CODEC_ID_VP6A) {
    138. if (par->extradata_size)
    139. avio_w8(pb, par->extradata[0]);
    140. else
    141. avio_w8(pb, ((FFALIGN(par->width, 16) - par->width) << 4) |
    142. (FFALIGN(par->height, 16) - par->height));
    143. } else if (par->codec_id == AV_CODEC_ID_AAC)
    144. avio_w8(pb, 1); // AAC raw
    145. else if (par->codec_id == AV_CODEC_ID_H264 || par->codec_id == AV_CODEC_ID_MPEG4) {
    146. avio_w8(pb, 1); // AVC NALU
    147. avio_wb24(pb, pkt->pts - pkt->dts);
    148. }
    149. avio_write(pb, data ? data : pkt->data, size);
    150. avio_wb32(pb, size + flags_size + 11); // previous tag size
    151. flv->duration = FFMAX(flv->duration,
    152. pkt->pts + flv->delay + pkt->duration);
    153. }
    154. if (flv->flags & FLV_ADD_KEYFRAME_INDEX) {
    155. switch (par->codec_type) {
    156. case AVMEDIA_TYPE_VIDEO:
    157. flv->videosize += (avio_tell(pb) - cur_offset);
    158. flv->lasttimestamp = flv->acurframeindex / flv->framerate;
    159. flv->acurframeindex++;
    160. if (pkt->flags & AV_PKT_FLAG_KEY) {
    161. double ts = flv->lasttimestamp;
    162. int64_t pos = cur_offset;
    163. flv->lastkeyframetimestamp = ts;
    164. flv->lastkeyframelocation = pos;
    165. ret = flv_append_keyframe_info(s, flv, ts, pos);
    166. if (ret < 0)
    167. goto fail;
    168. }
    169. break;
    170. case AVMEDIA_TYPE_AUDIO:
    171. flv->audiosize += (avio_tell(pb) - cur_offset);
    172. break;
    173. default:
    174. av_log(s, AV_LOG_WARNING, "par->codec_type is type = [%d]\n", par->codec_type);
    175. break;
    176. }
    177. }
    178. fail:
    179. av_free(data);
    180. return ret;
    181. }

  • 相关阅读:
    “云”上交通,“翼”路畅行
    C++实战学习笔记
    爬虫爬取mp3文件例子
    软考考完了,如何评职称?
    Netty——FileChannel中方法说明与代码示例
    【蓝桥每日一题]-贪心(保姆级教程 篇1)#拼数 #合并果子 #凌乱yyy
    Spring篇---第七篇
    【算法题】2903. 找出满足差值条件的下标 I
    使用vagrant安装CentOS7虚拟机
    面试美团被问到了Redis,搞懂这几个问题,让你轻松吊打面试官
  • 原文地址:https://blog.csdn.net/u012794472/article/details/126755755