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

  • Post author:
  • Post category:其他


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

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

字段 字节 描述
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;
}



版权声明:本文为u012794472原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。