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

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

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 header和Video tag header构成,即:

FLV tag Header说明:
PreviousTagSize: 表面之前的 Tag 的长度(tag 头+tag 数据),第一个 tag值是 0TagType:音频:8, 视频:9DataSize: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

以MediaCodec采集的SPS NALU和PPS NALU为例:
fun getConfigurationFlvVideoTag(ppsByteBuffer: ByteBuffer, spsByteBuffer: ByteBuffer): ByteArray {/*** 构造AVCDecoderConfigurationRecord*/val configRecordData = generateAVCDecoderConfigurationRecord(ppsByteBuffer, spsByteBuffer)/*** ====================================================* FLV Video tag (5字节) + configRecord* ====================================================*/val flvVideoTagLen = 5val videoTagLen = flvVideoTagLen + configRecordData.sizeval 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 0111videoTag[1] = 0x00 // AVCPacketType = 0x00videoTag[2] = 0x00 // Composition Time = 0x00 0x00 0x00videoTag[3] = 0x00videoTag[4] = 0x00System.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 + ppsLenval 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加群免费领取~

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

以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 = 5val naluLen = 4val naluDataSize = buffer.remaining()val videoFlvDataLen = flvVideoTagLen + naluLen + naluDataSizeval 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 0111videoFlvData[1] = 0x00 // AVCPacketType = 0x00videoFlvData[2] = 0x00 // Composition Time = 0x00 0x00 0x00videoFlvData[3] = 0x00videoFlvData[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 header和Audio tag header构成,但是,在RTMP协议中,Audio Tag不包含FLV tag header,即只有Audio tag header,格式如下:

Flv Audio tag说明(占2字节):
SoundFormat[4]:表示音频格式,10 = AACSoundRate[2]:表示音频采样率,3 = 44000HzsoundSize[1]:表示采用精度,0=8bit 1=16bitSoundType[1]:表示音频类型,1 = aacAACPacketType[8]:表示AAC packet类型,0 = aac header; 1 = aac raw(不含ADTS头部)

以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 = 2val 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中的使用 - 掘金