Skip to content

Latest commit

 

History

History
780 lines (645 loc) · 36.2 KB

音视频媒体格式.md

File metadata and controls

780 lines (645 loc) · 36.2 KB

音视频媒体格式

RGB

RGB 颜色空间

  • Full Range 的 R、G、B 取值范围都是 0 ~ 255
  • Limited Range 的 R、G、B 取值范围是 16 ~ 235

YUV

YUV类型 YUV444 YUV422 YUV420

  • 主要用于视频信号的压缩、传输和存储,和向后相容老式黑白电视。
  • 其中 Y 表示明亮度(Luminance 或 Luma),也称灰阶值。
  • UV 表示的则是色度(Chrominance 或 Chroma),作用是描述影像色彩及饱和度,用于指定像素的颜色。
  • 对于 planar 的 YUV 格式,先连续存储所有像素点的 Y,紧接着存储所有像素点的 U,随后是所有像素点的 V。
  • 对于 packed 的 YUV 格式,每个像素点的 Y,U,V 是可以连续交错存储的。
  • YUV4:4:4 采样,每 1 个 Y 对应 1 组 UV 分量。
  • YUV4:2:2 采样,每 2 个 Y 共用 1 组 UV 分量。
  • YUV4:2:0 采样,每 4 个 Y 共用 1 组 UV 分量。

h264码流结构总结

  • 图片帧的像素块之间存在相似性,因此视频帧图像可以进行图像压缩;H264 采用了 16*16 的分块大⼩对视频帧图像(Y 分量)进行相似比较和压缩编码。

  • H264 使用 视频帧预测 的方式提高编码压缩率。

    h264视频帧 h264码流结构

  • H264 除了实现了对视频的压缩处理之外,为了方便网络传输,提供了对应的视频编码和 分片 策略。在 H264 中将其称为 组(GOP, group of pictures)、片(slice)、宏块(Macroblock)

  • H264 的码流分层结构组织成为 序列(GOP)、图片(pictrue)、片(Slice)、宏块(Macroblock)、子块(subblock) 五个层次。

GOP

  • GOP 的大小是由 IDR 帧之间的间隔来确定的,而这个间隔叫做关键帧间隔。
  • 通过提高 GOP 值来提高图像质量是有限度的,在遇到场景切换的情况时,H264 编码器会自动 强制 插⼊一个 I 帧 ,此时实际的 GOP 值被缩短了。另一方面,在一个 GOP 中,P、B 帧是由 I 帧预测得到的,当 I 帧的图像质量比较差时,会影响到一个 GOP 中后续 P、B 帧的图像 质量,直到下一个 GOP 开始才有可能得以恢复,所以 GOP 值也不宜设置过大。
  • 由于 P、B 帧的复杂度大于 I 帧,所以过多的 P、B 帧会影响编码效率,使编码效率降低。另外,过长的 GOP 还会影响 seek 操作的响应速度,由于 P、B 帧是由前面的 I 或 P 帧预测得到的,所以 seek 操作需要直接定位,解码某一个 P 或 B 帧时,需要先解码得到本 GOP 内的 I 帧及之前的 N 个预测帧才可以。GOP 值越长,需要解码的预测帧就越多,seek 响应的时间也越长。

Slice

  • Slice 其实是为了并行编码设计的。将一帧图像划分成几个 Slice,并且 Slice 之间相互独立、互不依赖、独立编码。在机器性能比较高的情况下,我们就可以多线程并行对多个 Slice 进行编码,从而提升速度。但也因为一帧内的几个 Slice 是相互独立的,所以如果帧内预测的话,就不能跨 Slice 进行,因此编码性能会差一些。

  • H264 中编码的基本单元是宏块,所以一个 Slice 又包含整数个宏块。宏块 MB 大小是 16x16。在做帧内和帧间预测的时候,我们又可以将宏块继续划分成不同大小的子块,用来给复杂区域做精细化编码。

  • 帧都是以 Slice 的方式在码流中呈现,图像内的层次结构就是一帧图像可以划分成一个或多个 Slice,而一个 Slice 包含多个宏块,且一个宏块又可以划分成多个不同尺寸的子块。

    slice_header

  • Slice NALUNALU HeaderNALU Data 组成,其中 NALU Data 里面就是 Slice 数据,而 Slice 数据又是由 Slice Header 和 Slice Data 组成。在 Slice Header 开始的地方有一个 first_mb_in_slice 的字段,表示当前 Slice 的第一个宏块 MB 在当前编码图像中的序号。

  • 如果 first_mb_in_slice 的值等于 0,就代表了当前 Slice 的第一个宏块是一帧的第一个宏块,也就是说当前 Slice 就是一帧的第一个 Slice。如果 first_mb_in_slice 的值不等于 0,就代表了当前 Slice 不是一帧的第一个 Slice。

NALU

  • H264 原始码流(裸流)是由一个接一个 NALU 组成,它的功能分为两层,VCL(视频编码层)和 NAL(网络提取层)。
    • VCL 层,视频数据编码层(Video Coding Layer),包括核⼼压缩引擎和块,宏块和片的语法级别定义,设计⽬标是尽可能地独立于网络进行高效的编码。
    • NAL 层,视频数据网络抽象层(Network Abstraction Layer),负责将 VCL 产生的比特字符串适配到各种各样的网络和多元环境中,覆盖了所有片级以上的语法级别。

annexb格式 mp4格式

  • H264 有两种封装

    • annexb 模式,传统模式,有 startcode,SPS 和 PPS 是在 ES 中。
    • mp4 模式,一般 mp4、mkv 都是 mp4 模式,没有 startcode,SPS 和 PPS 以及其它信息被封装在 container 中,每一个 frame 前面 4 个字节是这个 frame 的长度。很多解码器只支持 annexb 这种模式,因此需要将 mp4 做转换。在 ffmpeg 中用 h264_mp4toannexb_filter 可以做转换。

    h264帧类型值

  • 一个原始的 H264 NALU 单元通常由 [StartCode] [NALU Header] [NALU Payload] 三部分组成,其中 StartCode 用于标示这是一个 NALU 单元的开始,必须是 "00 00 00 01"或"00 00 01" ,除此之外基本相当于一个 NAL header + RBSP 。3 字节的 0x000001 只有一种场合下使用,就是一个完整的帧被编为多个 slice 的时候,包含这些 slice 的 NALU 使用 3 字节起始码。其余场合都是 4 字节 0x00000001 的。

视频帧

h264视频帧类型

SPS

SPS

  • 序列参数集 ,保存了一组 编码视频序列(Coded video sequence) 的全局参数。
  • SPS 主要包含的是图像的宽、高、YUV 格式和位深等基本信息。

PPS

  • 图像参数集,对应的是一个序列中某一幅图像或者某几幅图像的参数。
  • PPS 主要包含 熵编码 类型、基础 QP最大参考帧数量 等基本编码信息。

I 帧

  • I 帧经过适度地压缩,作为 随机访问 的参考点,可以当成 图象 。I 帧可以看成是一个图像经过压缩后的产物。自身可以通过视频解压算法解压成一张单独的完整的图片。
  • 一个序列的第一个图像叫做 IDR 图像(立即刷新图像),IDR 图像都是 I 帧图像。I 帧和 IDR 帧都使用 帧内预测
  • I 帧不用参考任何帧 ,但是之后的 P 帧和 B 帧是有可能参考这个 I 帧之前的帧的。IDR 就不允许这样,其核⼼作用是为了 解码的重同步 ,当解码器解码到 IDR 图像时,立即将参考帧队列清空 ,将已解码的数据全部输出或抛弃,重新查找参数集 ,开始一个新的序列。这样,如果前一个序列出现重大错误,在这里可以获得重新同步的机会。
  • IDR 图像之后的图像永远不会使用 IDR 之前的图像的数据来解码。

P 帧

  • P 帧通过利用与序列前面的编码帧的 冗余信息 来压缩传输数据量的编码图像,也叫 预测帧 。需要参考其前面的一个 I 帧或者 P 帧来生成一张完整的图片。
  • P 帧采用 运动补偿 的方法传送它与前面的 I 或 P 帧的差值及运动矢量。
  • 解码时必须将 I 帧中的预测值与预测误差求和后才能重构完整的 P 帧图像。
  • P 帧属于 前向预测帧间编码 。它只参考前面最靠近它的 I 帧或 P 帧。
  • P 帧可以是其后面 P 帧的参考帧,也可以是其前后的 B 帧的参考帧。
  • 由于 P 帧是参考帧,它可能造成 解码错误的扩散
  • 由于是差值传送,P 帧的压缩比较高。

B 帧

  • B 帧既考虑与源图像序列前面已编码帧,也顾及源图像序列后面已编码帧之间的 冗余信息 来压缩传输数据量的编码图像, 也叫 双向预测帧 。则要参考其前一个 I 或者 P 帧及其后面的一个 P 帧来生成一张完整的图片。
  • B 帧是由前面的 I 或 P 帧和后面的 P 帧来进行预测的。
  • B 帧传送的是它与前面的 I 或 P 帧和后面的 P 帧之间的 预测误差运动矢量
  • B 帧是 双向预测 编码帧。
  • B 帧压缩比最高,因为它只反映两参考帧间运动主体的变化情况,预测比较准确。
  • B 帧不是参考帧,不会造成解码错误的扩散。

WAVE

WAVE

  • WAVE 文件由 WAVE 文件头部分和 WAVE 文件数据体部分组成,其中 0 ~ 43 字节存放采样率、通道数、数据部分的标识符等头信息,44 字节以后的就是数据部分。

AAC

ADIF,音频数据交换格式(Audio Data Interchange Format)。这种格式的特征是可以确定的找到这个音频数据的开始,不需进行在音频数据流中间开始的解码,即它的解码必须在明确定义的开始处进行。故这种格式常用在磁盘文件中。

ADTS,音频数据传输流(Audio Data Transport Stream)。这种格式的特征是它是一个有同步字的比特流,解码可以在这个流中任何位置开始。它的特征类似于mp3数据流格式。

ADTS帧

每一帧的ADTS头都包含了音频的采样率,声道,帧长度等信息,这样解码器才能解析读取。一般情况下ADTS的头信息都是7个字节,分为 2 部分:

  • adts_fixed_header
  • adts_variable_header

其一为固定头信息,紧接着是可变头信息。固定头信息中的数据每一帧都相同,而可变头信息则在帧与帧之间可变。

  • syncword:同步头,总是0xFFFall bits must be 1,代表着一个ADTS帧的开始。

  • IDMPEG标识符,0标识MPEG-41标识MPEG-2

  • Layer:always: '00'

  • protection_absent:表示是否误码校验。Warning, set to 1 if there is no CRC and 0 if there is CRC

  • profile:表示使用哪个级别的AAC,如01 Low Complexity(LC)--- AAC LC。有些芯片只支持AAC LCprofile的值等于Audio Object Type 的值减 1profile = MPEG-4 Audio Object Type - 1

  • sampling_frequency_index:表示使用的采样率下标,通过这个下标在Sampling Frequencies[]数组中查找得知采样率的值。

  • channel_configuration:表示声道数,比如 2 表示立体声双声道。

  • frame_length:一个ADTS帧的长度包括ADTS 头和 AAC 原始流

    • frame length, this value must include 7 or 9 bytes of header length;
    • aac_frame_length = (protection_absent == 1 ? 7 : 9) + size(AACFrame);
    • protection_absent=0 时,header length=9bytes;
    • protection_absent=1 时,header length=7bytes;
  • adts_buffer_fullness0x7FF说明是码率可变的码流。

  • number_of_raw_data_blocks_in_frame:表示ADTS帧中有number_of_raw_data_blocks_in_frame + 1AAC原始帧。所以说number_of_raw_data_blocks_in_frame == 0表示说ADTS帧中只有一个AAC数据块。

  • ADTS可以在任意帧解码,也就是说它每一帧都有头信息。ADIF只有一个统一的头,所以必须得到所有的数据后解码。

  • AAC 分析代码

    AAC分析代码
    /*
     * @Author: gongluck
     * @Date: 2020-11-02 23:11:22
     * @Last Modified by: gongluck
     * @Last Modified time: 2021-08-02 22:13:50
     */
    
    #pragma once
    
    // Advanced Audio Coding(⾼级⾳频编码)
    
    #include <stdint.h>
    #include <iostream>
    
    // ADTS ID
    #define ADTS_ID_MPEG4 0 //标识MPEG-4
    #define ADTS_ID_MPEG2 1 //标识MPEG-2
    
    // ADTS profile
    #define ADTS_PROFILE_MAIN 0				 // Main profile
    #define ADTS_PROFILE_LC 1					 // Low Complexity Profile(LC)
    #define ADTS_PROFILE_SSR 2				 // Scalable Sampling Rate profile(SSR)
    #define ADTS_PROFILE_LTP 3				 // AAC LTP
    #define ADTS_PROFILE_SBR 4				 // SBR
    #define ADTS_PROFILE_AACSCALABLE 5 // AAC scalable
    #define ADTS_PROFILE_TWINVQ 6			 // TwinVQ
    #define ADTS_PROFILE_CELP 7				 // CELP
    #define ADTS_PROFILE_HVXC 8				 // HVXC
    // 9..10	//(reserved)
    #define ADTS_PROFILE_TTSI 11					// TTSI
    #define ADTS_PROFILE_MAINSYNTHETIC 12 // Main synthetic
    #define ADTS_PROFILE_WAVESYNTHETIC 13 // Wavetable synthesis
    #define ADTS_PROFILE_GENERALMIDI 14		// General MIDI
    #define ADTS_PROFILE_ALGORITHMICFX 15 // Algorithmic Synthesis and Audio FX
    #define ADTS_PROFILE_FRAACLC 16				// FR AAC LC
    
    // ADTS sampling frequencies
    const int SamplingFrequencies[16] = {
    		96000,
    		88200,
    		64000,
    		48000,
    		44100,
    		32000,
    		24000,
    		22050,
    		16000,
    		12000,
    		11025,
    		8000,
    		7350,
    		-1 /*reserved*/,
    		-1 /*reserved*/,
    		-1 /*escape value*/,
    };
    
    // ADTS channel configuration
    #define ADTS_CHANNEL_CONFIGURATION_AOT 0									 // Defined in AOT Specifc Config
    #define ADTS_CHANNEL_CONFIGURATION_SINGLE 1								 // 1 channel : front - center
    #define ADTS_CHANNEL_CONFIGURATION_PAIR 2									 // 2 channels : front - left, front - right
    #define ADTS_CHANNEL_CONFIGURATION_SINGLEPAIR 3						 // channels : front - center, front - left, front - right
    #define ADTS_CHANNEL_CONFIGURATION_SINGLEPAIRSINGLE 4			 // channels : front - center, front - left, front - right, back - center
    #define ADTS_CHANNEL_CONFIGURATION_SINGLEPAIRPAIR 5				 // channels : front - center, front - left, front - right, back - left, backright
    #define ADTS_CHANNEL_CONFIGURATION_SINGLEPAIRPAIRLEF 6		 // channels : front - center, front - left, front - right, back - left, backright, LFE - channel
    #define ADTS_CHANNEL_CONFIGURATION_SINGLEPAIRPAIRPAIRLEF 8 // channels : front - center, front - left, front - right, side - left, side - right, back - left, back - right, LFE - channel
    // 8..15	//Reserved
    
    typedef struct __ADTS
    {
    	// 16bit
    	uint8_t syncwordL : 8;				 //同步头低8位 总是0xFF, all bits must be 1,代表着⼀个ADTS帧的开始
    	uint8_t protection_absent : 1; //表示是否误码校验。Warning, set to 1 if there is no CRC and 0 if there is CRC
    	uint8_t layer : 2;						 // always: '00'
    	uint8_t ID : 1;								 // MPEG标识符,ADTS_ID_XXX
    	uint8_t syncwordH : 4;				 //同步头高4位 总是0xF, all bits must be 1,代表着⼀个ADTS帧的开始
    
    	// 8bit
    	uint8_t channel_configurationH : 1; //表示声道数高1位
    	uint8_t private_bit : 1;
    	uint8_t sampling_frequency_index : 4; //表示使⽤的采样率下标,通过这个下标在SamplingFrequencies[]数组中查找得知采样率的值
    	uint8_t profile : 2;									//表示使⽤哪个级别的AAC,ADTS_PROFILE_XXX。有些芯⽚只支持AAC LC。
    
    	// 8bit
    	uint8_t aac_frame_lengthH : 2;
    	uint8_t copyright_identification_start : 1;
    	uint8_t copyright_identification_bit : 1;
    	uint8_t home : 1;
    	uint8_t original_copy : 1;
    	uint8_t channel_configurationL : 2; //表示声道数低2位
    
    	// 24bit
    	uint8_t aac_frame_lengthM : 8;
    	uint8_t adts_buffer_fullnessH : 5;
    	uint8_t aac_frame_lengthL : 3; //⼀个ADTS帧的⻓度包括ADTS头和AAC原始流.
    																 // frame length, this value must include 7 or 9 bytes of header length :
    																 // aac_frame_length = (protection_absent == 1 ? 7 : 9) + size(AACFrame)
    																 // protection_absent = 0时, header length = 9bytes
    																 // protection_absent = 1时, header length = 7bytes
    	uint8_t number_of_raw_data_blocks : 2; //表示ADTS帧中有number_of_raw_data_blocks_in_frame + 1个AAC原始帧。
    																				 //所以说number_of_raw_data_blocks_in_frame == 0 表示说ADTS帧中有⼀个AAC数据块。
    	uint8_t adts_buffer_fullnessL : 6;		 // 0x7FF 说明是码率可变的码流。
    } ADTS;
    
    uint16_t get_syncword(const ADTS &adts);
    void set_syncword(ADTS &adts, uint16_t syncword);
    uint8_t get_channel_configuration(const ADTS &adts);
    void set_channel_configuration(ADTS &adts, uint8_t channel_configuration);
    uint16_t get_aac_frame_length(const ADTS &adts);
    void set_aac_frame_length(ADTS &adts, uint16_t aac_frame_length);
    uint16_t get_adts_buffer_fullness(const ADTS &adts);
    void set_adts_buffer_fullness(ADTS &adts, uint16_t adts_buffer_fullness);
    
    const char *adts_parse_id(uint8_t id);
    const char *adts_parse_profile(uint8_t profile);
    const char *adts_parse_channelconfiguration(uint8_t channelconfiguration);
    
    std::ostream &operator<<(std::ostream &os, const ADTS &adts);
    

FLV

FLV

  • FLV 封装是比较简单的封装格式,由一个个 Tag 组成的。

  • Tag 又分为视频 Tag、音频 Tag 和 Script Tag,分别用来存放视频数据、音频数据和 MetaData 数据。

  • 时间戳占用 3 ~ 4 字节,如果 3 字节不够的话,则需要使用 1 字节的扩展时间戳作为时间戳的高 8 位。时间戳的单位是 ms。

    AudioTag

  • 音频 Tag Data 的第一个字节表示音频的编码方式、采样率和位宽等信息,之后就是音频数据了。

    VideoTagHead

  • 视频 Tag 的第 1 个字节包含了这个 Tag 的视频帧类型和视频编码方式。

    AVCPacketType

  • 对于 H264 数据,紧接着会有 4 字节的 AVC Packet Type 格式。

  • 前面 Tag Header 里的时间戳就是 DTS,PTS = DTS + CTS

  • AVC 包类型是 0 数据格式

    AVCType0

  • AVC 包类型为 1 数据格式

    AVCType1

FLV分析代码
// FLV tag type
#define FLV_TAG_TYPE_AUDIO 0x08
#define FLV_TAG_TYPE_VIDEO 0x09
#define FLV_TAG_TYPE_SCRIPT 0x12

// flv video tag frame type
#define FLV_VIDEO_FRAME_KEYFRAME 1 // keyframe(for AVC, a seekable frame) —— 即 H.264 的 IDR 帧;
#define FLV_VIDEO_FRAME_IFRAME 2 // inter frame(for AVC, a non - seekable frame) —— H.264 的普通 I 帧;
#define FLV_VIDEO_FRAME_DISPOSABLE 3 // disposable inter frame(H.263 only)
#define FLV_VIDEO_FRAME_GENERATED 4 // generated keyframe(reserved for server use only)
#define FLV_VIDEO_FRAME_COMMAND 5 // video info / command frame

// flv video tag frame codecid
#define FLV_VIDEO_CODECID_JPEG 1 // JPEG (currently unused)
#define FLV_VIDEO_CODECID_H263 2 // Sorenson H.263
#define FLV_VIDEO_CODECID_SCREEN 3 // Screen video
#define FLV_VIDEO_CODECID_ON2VP6 4 // On2 VP6
#define FLV_VIDEO_CODECID_ON2VP6A 5 // On2 VP6 with alpha channel
#define FLV_VIDEO_CODECID_SCREEN2 6 // Screen video version 2
#define FLV_VIDEO_CODECID_AVC 7 // AVC

// AVC packet type
#define AVC_PACKET_HEADER 0 // AVC sequence header
#define AVC_PACKET_NALU 1 // AVC NALU
#define AVC_PACKET_END 2 // AVC end of sequence

// flv sound format
#define FLV_SOUND_FORMAT_PCM 0 // Linear PCM, platform endian
#define FLV_SOUND_FORMAT_ADPCM 1 // ADPCM
#define FLV_SOUND_FORMAT_MP3 2 // MP3
#define FLV_SOUND_FORMAT_PCMLE 3 // inear PCM, little endian
#define FLV_SOUND_FORMAT_NELLYMOSER16MONO 4 // Nellymoser 16-kHz mono
#define FLV_SOUND_FORMAT_NELLYMOSER8MONO 5 // Nellymoser 8-kHz mono
#define FLV_SOUND_FORMAT_NELLYMOSER 6 // Nellymoser
#define FLV_SOUND_FORMAT_G711LA 7 // G.711A-law logarithmic PCM
#define FLV_SOUND_FORMAT_G711MU 8 // G.711mu-law logarithmic PCM
#define FLV_SOUND_FORMAT_RESERVED 9 // reserved
#define FLV_SOUND_FORMAT_AAC 10 // AAC
#define FLV_SOUND_FORMAT_SPEEX 11 // Speex
#define FLV_SOUND_FORMAT_MP3_8 14 // MP3 8-Khz
#define FLV_SOUND_FORMAT_DEVICE 15 // Device-specific sound

// flv sound rate
#define FLV_SOUND_RATE_55 0 // 5.5-kHz
#define FLV_SOUND_RATE_11 1 // 11-kHz
#define FLV_SOUND_RATE_22 2 // 22-kHz
#define FLV_SOUND_RATE_44 3 // 44-kHz

// flv sound size
#define FLV_SOUND_SIZE_8 0 // 8Bit
#define FLV_SOUND_SIZE_16 1 // 16Bit

// flv audio tag sound type
#define FLV_AUDIO_SOUND_MONO 0 //单声道
#define FLV_AUDIO_SOUND_STEREO 1 //双声道

// aac packet type
#define AAC_PACKET_TYPE_HEAD 0 // AAC sequence header
#define AAC_PACKET_TYPE_RAW 1 // raw

#pragma pack(1)
typedef GINT8 FLVINT8;
typedef GINT16 FLVINT16;
typedef GINT24 FLVINT24;
typedef GINT32 FLVINT32;
typedef struct \_\_FLVTIMESTAMP
{
uint8_t data2;
uint8_t data3;
uint8_t data4;
uint8_t data1;
} FLVTIMESTAMP;

/_FLV 文件头_/
typedef struct **FLVHEADER
{
FLVINT8 F; // 0x46
FLVINT8 L; // 0x4C
FLVINT8 V; // 0x56
FLVINT8 flvtype; //版本
FLVINT8 hasvideo : 1; //是否有视频
FLVINT8 : 1; //全为 0
FLVINT8 hasaudio : 1; //是否有音频
FLVINT8 : 5; //全为 0
FLVINT32 headlen; //文件头长度 9
} FLVHEADER;
/**************\***************/
/_前一个 tag 的长度(4 字节)_/
/**************\***************/
/_tag 头_/
typedef struct **FLVTAGHEADER
{
FLVINT8 flvtagtype; // FLV_TAG_TYPE_XXX
FLVINT24 datalen; //数据区的长度
FLVTIMESTAMP timestamp; //时间戳
FLVINT24 streamsid; //流信息
} FLVTAGHEADER;
/**************\***************/
/_tag 数据区_/
typedef struct \_\_FLVVIDEOTAG
{
FLVINT8 codecid : 4; //编解码器,FLV_VIDEO_CODECID_XXX
FLVINT8 type : 4; //视频帧类型,FLV_VIDEO_FRAME_XXX
union
{
// codecid == FLV_VIDEO_CODECID_AVC
struct AVCVIDEOPACKE
{
FLVINT8 avcpacketype; // AVC_PACKET_XXX

      //如果avcpacketype=1,则为时间cts偏移量;否则,为0。当B帧的存在时,视频解码呈现过程中,dts、pts可能不同,cts的计算公式为 pts - dts/90,单位为毫秒;如果B帧不存在,则cts固定为0。
      FLVINT24 compositiontime;

      // avcpacketype=0,则为AVCDecoderConfigurationRecord,H.264 视频解码所需要的参数集(SPS、PPS)
      // avcpacketype=1,则为NALU(一个或多个),data[0-3]是数据长度!
      //如果avcpacketype=2,则为空
      FLVINT8 avcpacketdata[0];
    } avcvideopacket;

} videopacket;
} FLVVIDEOTAG;
// AVCDecoderConfigurationRecord = AVCDecoderConfigurationRecordHeader + SequenceParameterSet + PictureParameterSet
typedef struct **AVCDecoderConfigurationRecordHeader
{
FLVINT8 configurationVersion;
FLVINT8 AVCProfileIndication;
FLVINT8 profile_compatibility;
FLVINT8 AVCLevelIndication;
FLVINT8 lengthSizeMinusOne : 2;
FLVINT8 : 6;
FLVINT8 data[0]; //后续数据
} AVCDecoderConfigurationRecordHeader;
typedef struct **SequenceParameterSet
{
FLVINT8 numOfSequenceParameterSets : 5;
FLVINT8 : 3;
FLVINT16 sequenceParameterSetLength;
FLVINT8 sequenceParameterSetNALUnit[0];
} SequenceParameterSet;
typedef struct **PictureParameterSet
{
FLVINT8 numOfPictureParameterSets;
FLVINT16 pictureParameterSetLength;
FLVINT8 pictureParameterSetNALUnit[0];
} PictureParameterSet;
/**************\***************/
/_..........................._/
typedef struct **FLVAUDIOTAG
{
FLVINT8 soundtype : 1; // FLV_AUDIO_SOUND_XXX
FLVINT8 soundSize : 1; // FLV_SOUND_SIZE_XXX
FLVINT8 soundRate : 2; // FLV_SOUND_RATE_XXX
FLVINT8 soundFormat : 4; // FLV_SOUND_FORMAT_XXX
union
{
// soundFormat == FLV_SOUND_FORMAT_AAC
struct AACAUDIOPACKET
{
FLVINT8 aacpackettype; // AAC_PACKET_TYPE_XXX
FLVINT8 data[0];
} aacaudiopacket;
} audiopacket;
} FLVAUDIOTAG;
typedef struct \_\_AudioSpecificConfig
{
FLVINT8 SamplingFrequencyIndexH : 3;
FLVINT8 AudioObjectType : 5;
FLVINT8 : 3;
FLVINT8 ChannelConfiguration : 4;
FLVINT8 SamplingFrequencyIndexL : 1;
FLVINT8 AOTSpecificConfig[0];
} AudioSpecificConfig;
/_..........................._/
/_前一个 tag 的长度(4 字节)_/
/_EOF_/

#pragma pack()

MP4

MP4

  • MP4由一个个box组成,每一个box存放了不同的数据,而且box里面还可以嵌套着box。

  • MP4最外层的box主要有三个,分别是File Type box(ftyp box)、Movie box(moov box)和Media Data box(mdat box)。其中最重要、最复杂的就是moov box了,它里面存放了音视频的基本信息和每一个音视频数据的具体位置。

  • MP4文件中,视频的一帧和音频的一段编码数据称为一个sample。连续的几个sample称之为chunk,而视频的所有sample称为一个视频track,同样音频的所有sample也称为一个音频track。

  • MP4文件是由音频track和视频track组成,而track由sample组成,其中若干个sample组成一个chunk。

    BOXHeader

  • 每一个box都是由Box Header和Box Data组成。

MP4分析代码
// mp4的box名称
const MP4INT32 MP4BOXTYPE_FTYP[4] = {'f', 't', 'y', 'p'};
const MP4INT32 MP4BOXTYPE_MOOV[4] = {'m', 'o', 'o', 'v'};

const MP4INT32 MP4BOXTYPE_MVHD[4] = {'m', 'v', 'h', 'd'};
const MP4INT32 MP4BOXTYPE_TRAK[4] = {'t', 'r', 'a', 'k'};

const MP4INT32 MP4BOXTYPE_TKHD[4] = {'t', 'k', 'h', 'd'};
const MP4INT32 MP4BOXTYPE_MDIA[4] = {'m', 'd', 'i', 'a'};

const MP4INT32 MP4BOXTYPE_MDHD[4] = {'m', 'd', 'h', 'd'};
const MP4INT32 MP4BOXTYPE_HDLR[4] = {'h', 'd', 'l', 'r'};
const MP4INT32 MP4BOXTYPE_MINF[4] = {'m', 'i', 'n', 'f'};

const MP4INT32 MP4BOXTYPE_DINF[4] = {'d', 'i', 'n', 'f'};
const MP4INT32 MP4BOXTYPE_STBL[4] = {'s', 't', 'b', 'l'};

const MP4INT32 MP4BOXTYPE_DREF[4] = {'d', 'r', 'e', 'f'};

const MP4INT32 MP4BOXTYPE_STSD[4] = {'s', 't', 's', 'd'};
const MP4INT32 MP4BOXTYPE_STTS[4] = {'s', 't', 't', 's'};
const MP4INT32 MP4BOXTYPE_STSC[4] = {'s', 't', 's', 'c'};
const MP4INT32 MP4BOXTYPE_STCO[4] = {'s', 't', 'c', 'o'};

#pragma pack(1)

// mp4的box头
typedef struct __BASEBOXHEADER
{
  MP4INT32 size; // box大小
  MP4INT32 name; // box名称
} baseboxheader;
typedef struct __FULLBOXHEADER
{
  baseboxheader baseheader;
  MP4INT8 version; // box版本,0或1,一般为0
  MP4INT24 flags;
} fullboxheader;

//基础重复结构
typedef struct __BASEBOX
{
  baseboxheader header; // box头
  MP4INT8 data[0];      //子box数据
} basebox;
typedef struct __FULLBOX
{
  fullboxheader header; // box头
  MP4INT8 data[0];      //子box数据
} fullbox;
typedef struct __FULLTABLEBOX
{
  fullboxheader header; // box头
  MP4INT32 count;       //表节点数
  MP4INT8 data[0];      //
} fulltablebox;
typedef struct __TIMEGROUP0
{
  MP4INT32 creationtime;     //创建时间(相对于UTC时间1904-01-01零点的秒数)
  MP4INT32 modificationtime; //修改时间
  MP4INT32 timescale;        //文件媒体在1秒时间内的刻度值,可以理解为1秒长度的时间单元数
  MP4INT32 duration;         //该track的时间长度,用duration和timescale值可以计算track时长
} timegroup0;
typedef struct __TIMEGROUP1
{
  MP4INT64 creationtime;     //创建时间(相对于UTC时间1904-01-01零点的秒数)
  MP4INT64 modificationtime; //修改时间
  MP4INT32 timescale;        //文件媒体在1秒时间内的刻度值,可以理解为1秒长度的时间单元数
  MP4INT64 duration;         //该track的时间长度,用duration和timescale值可以计算track时长
} timegroup1;

/************************************************************************************************/

// root/ftype
// ftyp相当于就是该mp4的纲领性说明。即,告诉demuxer它的基本解码版本,兼容格式。简而言之,就是用来告诉客户端,该MP4的使用的解码标准。通常,ftyp都是放在MP4的开头。
typedef struct __FTYPBOX
{
  baseboxheader header;          // box头
  MP4INT32 majorbrand;           //主要标识
  MP4INT32 minorversion;         //次要版本
  MP4INT32 compatible_brands[0]; //兼容标识
} ftypbox;

// root/moov
// moovbox主要是作为一个很重要的容器盒子存在的,它本身的实际内容并不重要。moov主要是存放相关的trak。
typedef basebox moovbox;

// root/moov/mvhd
// mvhd是moov下的第一个box,用来描述media的相关信息。
typedef fullbox mvhdboxheader; // data->MVHDBOXBODY_X数据
// mvhdboxheader.version == 0
typedef struct __MVHDBOXBODY_0
{
  timegroup0 time;
  MP4INT32 rate;        //推荐播放速率,高16位和低16位分别为小数点整数部分和小数部分,即[16.16]格式,该值为1.0(0x00010000)表示正常前向播放
  MP4INT16 volume;      //与rate类似,[8.8]格式,1.0(0x0100)表示最大音量
  MP4INT8 reserved[10]; //保留位
  MP4INT8 matrix[36];   //视频变换矩阵
  MP4INT8 predefined[24];
  MP4INT32 nexttrackid; //下一个track使用的id号
} mvhdboxbody_0;
// mvhdboxheader.version == 1
typedef struct __MVHDBOXBODY_1
{
  timegroup1 time;
  MP4INT32 rate;        //推荐播放速率,高16位和低16位分别为小数点整数部分和小数部分,即[16.16]格式,该值为1.0(0x00010000)表示正常前向播放
  MP4INT16 volume;      //与rate类似,[8.8]格式,1.0(0x0100)表示最大音量
  MP4INT8 reserved[10]; //保留位
  MP4INT8 matrix[36];   //视频变换矩阵
  MP4INT8 predefined[24];
  MP4INT32 nexttrackid; //下一个track使用的id号
} mvhdboxbody_1;

// root/moov/trak
// trakbox就是主要存放相关mediastream的内容。
typedef basebox trakbox;

// root/moov/trak/tkhd
// tkhd是trakbox的子一级box的内容。主要是用来描述该特定trak的相关内容信息。
typedef fullbox tkhdboxheader; // tkhdboxheader.flags:
                               // 0x000001 track_enabled,否则该track不被播放;
                               // 0x000002 track_in_movie,表示该track在播放中被引用;
                               // 0x000004 track_in_preview,表示该track在预览时被引用。
                               //一般该值为7,如果一个媒体所有track均未设置track_in_movie和track_in_preview,将被理解为所有track均设置了这两项;对于hint track,该值为0
// tkhdboxheader.version == 0
typedef struct __TKHDBOXBODY_0
{
  MP4INT32 creationtime;     //创建时间(相对于UTC时间1904-01-01零点的秒数)
  MP4INT32 modificationtime; //修改时间
  MP4INT32 trackid;          // id号,不能重复且不能为0
  MP4INT32 reserved1;        //保留位
  MP4INT32 duration;         // track的时间长度
  MP4INT64 reserved2;        //保留位
  MP4INT16 layer;            //视频层,默认为0,值小的在上层
  MP4INT16 alternategroup;   // track分组信息,默认为0表示该track未与其他track有群组关系
  MP4INT16 volume;           //[8.8] 格式,如果为音频track,1.0(0x0100)表示最大音量;否则为0
  MP4INT16 reserved3;        //保留位
  MP4INT8 matrix[36];        //视频变换矩阵
  MP4INT32 width;            //宽 为[16.16] 格式值,与sample描述中的实际画面大小比值,用于播放时的展示宽
  MP4INT32 height;           //高 为[16.16] 格式值,与sample描述中的实际画面大小比值,用于播放时的展示高
} tkhdboxbody_0;
// tkhdboxheader.verflags.version == 1
typedef struct __TKHDBOXBODY_1
{
  MP4INT64 creationtime;     //创建时间(相对于UTC时间1904-01-01零点的秒数)
  MP4INT64 modificationtime; //修改时间
  MP4INT32 trackid;          // id号,不能重复且不能为0
  MP4INT32 reserved1;        //保留位
  MP4INT64 duration;         // track的时间长度
  MP4INT64 reserved2;        //保留位
  MP4INT16 layer;            //视频层,默认为0,值小的在上层
  MP4INT16 alternategroup;   // track分组信息,默认为0表示该track未与其他track有群组关系
  MP4INT16 volume;           //[8.8] 格式,如果为音频track,1.0(0x0100)表示最大音量;否则为0
  MP4INT16 reserved3;        //保留位
  MP4INT8 matrix[36];        //视频变换矩阵
  MP4INT32 width;            //宽 为[16.16] 格式值,与sample描述中的实际画面大小比值,用于播放时的展示宽
  MP4INT32 height;           //高 为[16.16] 格式值,与sample描述中的实际画面大小比值,用于播放时的展示高
} tkhdboxbody_1;

// root/moov/trak/mdia
// mdia主要用来包裹相关的media信息。
typedef basebox bdiabox;

// root/moov/trak/mdia/mdhd
// mdhd和tkhd来说,内容大致都是一样的。不过,tkhd通常是对指定的track设定相关属性和内容。而mdhd是针对于独立的media来设置的。不过事实上,两者一般都是一样的。
typedef fullbox mdhdboxheader;
// mdhdboxheader.version == 0
typedef struct __MDHDBOXBODY_0
{
  timegroup0 time;
  MP4INT16 language; //媒体语言码。最高位为0,后面15位为3个字符(见ISO 639-2/T标准中定义)
  MP4INT16 predefined;
} mdhdboxbody_0;
// tkhdboxheader.version == 1
typedef struct __MDHDBOXBODY_1
{
  timegroup1 time;
  MP4INT16 language; //媒体语言码。最高位为0,后面15位为3个字符(见ISO 639-2/T标准中定义)
  MP4INT16 predefined;
} mdhdboxbody_1;

// root/moov/trak/mdia/hdlr
// hdlr是用来设置不同trak的处理方式的。
typedef struct __HDLRBOX
{
  fullboxheader header;
  MP4INT32 predefined;
  MP4INT32 handlertype; //在media box中,该值为4个字符:
                        // vide — video track
                        // soun — audio track
                        // hint — hint track
  MP4INT8 reserved[12];
  MP4INT8 name[0]; // track type name,以'\0'结尾的字符串
} hdlrbox;

// root/moov/trak/mdia/minf
// minf是子属内容中,重要的容器box,用来存放当前track的基本描述信息。
typedef basebox minfbox;

// root/moov/trak/mdia/minf/dinf
// dinf是用来说明在trak中,media描述信息的位置。
typedef basebox dinfbox;

// root/moov/trak/mdia/minf/stbl
// MP4box根据trak中的stbl下的stts、stsc等基本box来完成在mdatbox中的索引。
typedef basebox stblbox;

// root/moov/trak/mdia/minf/dinf/dref
// dref是用来设置当前Box描述信息的data_entry。
typedef fulltablebox drefbox;

// root/moov/trak/mdia/minf/stbl/stsd
typedef fulltablebox stsdbox;

// root/moov/trak/mdia/minf/stbl/stts
// stts主要是用来存储refSampleDelta。即,相邻两帧间隔的时间。
typedef fulltablebox sttsboxheader;
typedef struct __STTSENTRY
{
  MP4INT32 count;
  MP4INT32 duration;
} sttsentry;

// root/moov/trak/mdia/minf/stbl/stsc
typedef fulltablebox stscboxheader;
typedef struct __STSCENTRY
{
  MP4INT32 firstchunk;       //每一个entry开始的chunk位置
  MP4INT32 samplesperchunk;  //每一个chunk里面包含多少的 sample
  MP4INT32 descriptionindex; //每一个sample的描述。一般可以默认设置为1
} stscentry;

// root/moov/trak/mdia/minf/stbl/stco
// stco是stbl包里面一个非常关键的Box。它用来定义每一个sample在mdat具体的位置。
typedef fulltablebox stcoboxheader;
typedef struct __STCOENTRY
{
  MP4INT32 offset;
} stcoentry;

#pragma pack()