音视频探索(7):FLV协议在RTMP中的使用

FLV是FLASH VIDEO的简称,FLV流媒体格式是随着Flash MX的推出发展而来的视频格式。由于它形成的文件极小、加载速度极快,使得网络观看视频文件成为可能,它的出现有效地解决了视频文件导入Flash后,使导出的SWF文件体积庞大,不能在网络上很好的使用等问题。RTMPHTTP_FLV内部使用FLV协议封装H.264AAC音视频包,FLV属于大端字节序

FLV格式由FLV Header+FLV Body构成:
 

音视频探索(7):FLV协议在RTMP中的使用_第1张图片

从上图可知,FLV封装主要由以下几个部分组成:‘

音视频探索(7):FLV协议在RTMP中的使用_第2张图片

FLV Body = PreviousTagSize0 + Tag1 + ..+ PreviousTagSize n + Tag n
PreviousTagSize + Tag = FLV Tag header + Data tag
Data tag = Video(video header + h264 data) / Audio (audio header + aac data)

为了进一步熟悉FLV协议,我们探讨下在RTMP中对FLV的封装。
RTMP的Video FLV Data (封装H.264)

通常,FLV的Video Tag应该由11字节FLV tag headerVideo tag header构成,即:
 

音视频探索(7):FLV协议在RTMP中的使用_第3张图片

FLV tag Header说明:

PreviousTagSize: 表面之前的 Tag 的长度(tag 头+tag 数据),第一个 tag值是 0
TagType:音频:8, 视频:9
DataSize:flv tag 的数据长度,其实如图里 audio tag 头及其数据长度
Timestamp:时间戳,貌似flv播放需要
TimestampExtended:当时间Timestamp不能表示的时候,启用本扩展字段
StreamID:都填 0.

但是,在RTMP协议中,Video Tag不包含FLV tag header,即只有Video tag header,并且封装的H.264数据需要区分PPS/SPS NALU普通NALU,具体结构如下:

  • PPS/SPS NALU

音视频探索(7):FLV协议在RTMP中的使用_第4张图片

MediaCodec采集的SPS NALUPPS NALU为例:

fun getConfigurationFlvVideoTag(ppsByteBuffer: ByteBuffer, spsByteBuffer: ByteBuffer): ByteArray {
    /**
     * 构造AVCDecoderConfigurationRecord
     */
    val configRecordData = generateAVCDecoderConfigurationRecord(ppsByteBuffer, spsByteBuffer)

    /**
     * ====================================================
     * FLV Video tag (5字节) + configRecord
     * ====================================================
     */
    val flvVideoTagLen = 5
    val videoTagLen = flvVideoTagLen + configRecordData.size
    val videoTag = ByteArray(videoTagLen)

    /**
     * FLV Video tag (5字节):
     * 
     * UB[4]FrameType = 指定帧类型,1表示IDR帧,2为非关键帧
     * UB[4]CodecID = 指定数据类型,7表示AVC数据
     * UB[8]AVCPacketType = 0x00表示AVC Sps/Pps;0x01表示普通AVC
     * UB[24]Composition Time = 0x00 0x00 0x00(忽略)
     */
    videoTag[0] = 0x27  // FrameType(2) + CodecId(7) = 0010 0111
    videoTag[1] = 0x00  // AVCPacketType = 0x00
    videoTag[2] = 0x00  // Composition Time = 0x00 0x00 0x00
    videoTag[3] = 0x00
    videoTag[4] = 0x00
    
    System.arraycopy(configRecordData, 0, videoTag, flvVideoTagLen, configRecordData.size)

    return videoTag
}


fun generateAVCDecoderConfigurationRecord(ppsByteBuffer: ByteBuffer, spsByteBuffer: ByteBuffer): ByteArray {
    /**
     * Rtmp flv不需要nalu起始地址
     * 去掉pps nalu和sps nalu的起始地址(4字节,0x00000001)
     */
    spsByteBuffer.position(4)
    ppsByteBuffer.position(4)

    /**
     * ====================================================
     * 拷贝sps、pps数据到缓存,预留11个字节配置
     * sps configuration(8字节) + sps data + pps configuration(3字节) + sps data
     * ====================================================
     */
    val spsLen = spsByteBuffer.remaining()
    val ppsLen = ppsByteBuffer.remaining()
    val totalLen = 11 + spsLen + ppsLen
    val recordData = ByteArray(totalLen)
    spsByteBuffer.get(recordData, 8, spsLen)
    ppsByteBuffer.get(recordData, 8 + spsLen + 3, ppsLen)

    /**
     * 配置sps
     * UB[8]configurationVersion = 0x01
     * UB[8]AVCProfileIndication = 0x4D
     * UB[8]profile_compatibility = 0x40
     * UB[8]AVCLevelIndication = 0x15
     * UB[8]lengthSizeMinusOne = FF
     * UB[8]numOfSequenceParameterSets = 0xE1
     * UB[16]sequenceParameterSetLength:sps长度,占2个字节
     */
    recordData[0] = 0x01.toByte()
    recordData[1] = 0x4D.toByte()
    recordData[2] = 0x40.toByte()
    recordData[3] = 0x15.toByte()
    recordData[4] = 0xFF.toByte()
    recordData[5] = 0xE1.toByte()
    recordData[6] = (spsLen shr 8 and 0xFF).toByte()
    recordData[7] = (spsLen and 0xFF).toByte()

    /**
     * 配置pps
     * UB[8]numOfPictureParameterSets = 0x01
     * UB[16]pictureParameterSetLength: pps长度,占2个字节
     */
    recordData[8 + spsLen] = 0x01.toByte()
    recordData[8 + spsLen + 1] = (spsLen shr 8 and 0xFF).toByte()
    recordData[8 + spsLen + 2] = (spsLen and 0xFF).toByte()
    return recordData
}

【免费分享】音视频学习资料包、大厂面试题、技术视频和学习路线图,资料包括(C/C++,Linux,FFmpeg webRTC rtmp hls rtsp ffplay srs 等等)有需要的可以点击788280672加群免费领取~

音视频探索(7):FLV协议在RTMP中的使用_第5张图片

普通NALU(IDR/I帧/non-I帧)

音视频探索(7):FLV协议在RTMP中的使用_第6张图片

MediaCodec采集的NALU为例:

fun geFlvVideoTag(buffer: ByteBuffer, bufferInfo: MediaCodec.BufferInfo): ByteArray {

    /**
     * Rtmp flv不需要nalu起始地址
     * 去掉nalu的起始地址(4字节,0x00000001)
     */
    buffer.position(bufferInfo.offset + 4)
    buffer.limit(bufferInfo.offset + bufferInfo.size)

    /**=====================================================
     * FLV Video tag (5字节) + nalu长度值(4字节) + nalu data
     * =====================================================
     */
    val flvVideoTagLen = 5
    val naluLen = 4
    val naluDataSize = buffer.remaining()
    val videoFlvDataLen = flvVideoTagLen + naluLen + naluDataSize
    val videoFlvData = ByteArray(videoFlvDataLen)

    /**
     * FLV Video tag (5字节):
     *
     * UB[4]FrameType = 1表示IDR帧,2为非关键帧
     * UB[4]CodecID = 7表示AVC数据
     * UB[8]AVCPacketType = 0x00表示AVC Sps/Pps;0x01表示普通AVC
     * UB[24]Composition Time = 0x00 0x00 0x00(忽略)
     */
    videoFlvData[0] = 0x17  // FrameType(1) + CodecId(7) = 0001 0111
    videoFlvData[1] = 0x00  // AVCPacketType = 0x00
    videoFlvData[2] = 0x00  // Composition Time = 0x00 0x00 0x00
    videoFlvData[3] = 0x00
    videoFlvData[4] = 0x00

    /**
     * NALU数据长度值(4字节)
     *
     * UB[32]naluLen = nalu长度
     */
    videoFlvData[5] = (naluDataSize shr 24 and 0xFF).toByte()
    videoFlvData[6] = (naluDataSize shr 16 and 0xFF).toByte()
    videoFlvData[7] = (naluDataSize shr 8 and 0xFF).toByte()
    videoFlvData[8] = (naluDataSize and 0xFF).toByte()

    /**
     * NALU数据
     */
    buffer.get(videoFlvData, flvVideoTagLen + naluLen, naluDataSize)

    return videoFlvData
}

RTMP的Audio FLV Data

通常,FLV的Audio Tag应该由11字节FLV tag headerAudio tag header构成,但是,在RTMP协议中,Audio Tag不包含FLV tag header,即只有Audio tag header,格式如下:
 

音视频探索(7):FLV协议在RTMP中的使用_第7张图片

Flv Audio tag说明(占2字节):

SoundFormat[4]:表示音频格式,10 = AAC
SoundRate[2]:表示音频采样率,3 = 44000Hz
soundSize[1]:表示采用精度,0=8bit 1=16bit
SoundType[1]:表示音频类型,1 = aac
AACPacketType[8]:表示AAC packet类型,0 = aac header; 1 = aac raw(不含ADTS头部)
 

音视频探索(7):FLV协议在RTMP中的使用_第8张图片

MediaCodec采集的AAC为例:

fun getFlvAudioTag(buffer: ByteBuffer, bufferInfo: MediaCodec.BufferInfo): ByteArray {
    buffer.position(bufferInfo.offset)
    buffer.limit(bufferInfo.offset + bufferInfo.size)

    /**
     * =====================================================
     * FLV Audio tag (2字节) + aac raw data(没有adts头)
     * =====================================================
     */
    val audioTagLen = 2
    val aacDataSize = buffer.remaining()
    val audioTagData = ByteArray(audioTagLen + aacDataSize)

    /**
     * FLV Audio tag (2字节):
     *
     * UB[4] 10=AAC
     * UB[2] 3=44kHz
     * UB[1] 1=16-bit
     * UB[1] 0=MonoSound
     * UB[8] 0=AAC header, 1=AAC raw 
     *
     * 1010 11 1 0 (0xAE)
     */
    audioTagData[0] = 0xAE.toByte()
    audioTagData[1] = 0x01.toByte() // 0x00=aac header

    /**
     * AAC raw data
     */
    buffer.get(audioTagData, audioTagLen, aacDataSize)
    
    return audioTagData
}

原文链接 音视频探索(7):FLV协议在RTMP中的使用 - 掘金

你可能感兴趣的:(音视频)