h264和aac格式介绍及mp4文件的封装

您所在的位置:网站首页 aac怎么改成mp4格式 h264和aac格式介绍及mp4文件的封装

h264和aac格式介绍及mp4文件的封装

2024-07-14 13:49| 来源: 网络整理| 查看: 265

mp4封装 目录h264视频流格式介绍aac音频流格式介绍h264视频文件读取通过帧索引解析h264文件通过解析h264结构读取文件 aac音频文件读取mp4封装初始化数据封装关闭mp4文件句柄 注意点

目录 h264视频流格式介绍

视频数据帧分为I帧,P帧,B帧,其中I帧为关键帧,所包含的图像信息最全,因而数据量最大,其他帧都有I帧通过计算而来

GOP 在这里插入图片描述

a) 所谓GOP就是1组图像Group of Picture,在这一组图像中有且只有1个I帧,多个P帧或B帧,两个I帧之间的帧数,就是一个GOP b) GOP一般设置为编码器每秒输出的帧数,即每秒帧率,一般为25或30,当然也可设置为其他值 c) 在一个GOP中,P、B帧是由I帧预测得到的,当I帧的图像质量比较差时,会影响到一个GOP中后续P、B帧的图像质量,直到下一个GOP 开始才有可能得以恢复,所以GOP值也不宜设置过大 d) 一般在播放时,为保证图像质量,第一帧应该播放I帧

H264结构中,一个视频图像编码后的数据叫做一帧,一帧由一个片(slice)或多个片组成,一个片由一个或多个宏块(MB)组成,一个宏块由16x16的yuv数据组成。宏块作为H264编码的基本单位

H264 Annexb byte-stream格式 在这里插入图片描述

a) SODB:String of Data Bits,数据 bit 流,最原始的编码数据 b) RBSP:Raw Byte Sequence Payload,原始字节序列载荷,在SODB的后面填加了结尾比特,RBSP trailing bits 一个bit“1”,若干比特“0”,以便字节对齐 c) EBSP:Encapsulated Byte Sequence Payload,扩展字节序列载荷,在RBSP基础上填加了仿校验字节(0x03)。 d) Start-code:在NALU加到Annexb即byte-stream格式时,需要在每组NALU之前添加开始码StartCode,如果该NALU对应的slice为1个GOP开始则用4位字节表示,0x00000001,否则用3位字节表示0x000001(也不一定)

H264网络传输的结构 在这里插入图片描述NALU头结构 长度:1byte forbidden_bit(1bit) + nal_reference_bit(2bit) + nal_unit_type(5bit) 在这里插入图片描述

a) 位信息

bitdescF禁止位,0表示正常,1表示错误,一般都是0NRI重要级别,11表示非常重要,一般取值为11、10、01Typenal_unit_type(表示该NALU的类型是什么,类型的具体取值可见下表)

b) nal_unit_type

nal_unit_type012345678910111213-2324-31NAL类型未使用非IDR的片片数据A分区片数据B分区片数据C分区一个序列的第一个图像叫做IDR图像(立即刷新图像),IDR图像都是I帧补充增强信息单元(SEI)序列参数集(SPS)图像参数集(PPS)分界符序列结束码流结束填充保留未使用

c) H264要求I帧为:帧分隔符+SPS+帧分隔符+PPS+帧分隔符+IDR

帧分隔符SPS帧分隔符PPS帧分隔符IDR0x00 00 010x67…0x00 00 010x68…0x00 00 010x65…

d) NALU start-code:0x00 00 00 01/0x00 00 01

H264 AVCC格式

a) Annex-B:没有NALU长度字节,使用start code分隔NALU,start code为三字节或四字节,0x000001或0x00000001,一般是四字节;SPS和PPS按流的方式写在一组GOP之前。 b) AVCC:使用NALU长度,固定字节,通常为4字节,分隔NALU;一般在每个NALU头部为4字节大端格式的长度字节,在一组GOP的头部包含extradata结构,用于存储sequence-header、SPS、PPS数据。 c) 虽然AVCC格式不使用起始码,防竞争字节仍然存在 d) SPS和PPS被存储在了非NALU包中(out of band带外),即独立于基本流数据 e) mp4文件即是采用这种存储方式

aac音频流格式介绍

aac有9中规格(LC/Main/SSR/LD…),LC为常用规格

aac文件格式有两种ADIF,ADTS

a) ADIF:头信息只在文件首部,解码必须在首部开始,获取头信息后,剩余全是音频数据 b) ADTS:每一帧都含有头信息,解码可以在任意位置 在这里插入图片描述

ADTS的头信息为两部分组成,其一为固定头信息,紧接着是可变头信息

a) adts固定头结构 在这里插入图片描述 b) adts可变头结构 在这里插入图片描述

adts头字段含义

a) syncword:同步字,一般为二进制"1111 1111 1111",用于找出帧头在比特流中的位置 b) ID:MPEG 标示符 c) layer:指示使用哪一层 d) protection_absent:表示是否误码校验 e) profile:表示使用哪个级别的AAC,如01:Low Complexity(LC)即为AACLC f) sampling_frequency_index:表示使用的采样率下标,如下图 在这里插入图片描述 g) channel_configuration:声道数

0x000x010x020x030x040x050x060x070x08-0x0f单声道双声道三声道四声道五声道5.1声道7.1声道保留

h) aac_frame_length:aac一帧数据大小,一个ADTS帧的长度包括ADTS头和raw data block i) adts_buffer_fullness:是否是码率可变码流(0x7FF:可变) j) number_of_raw_data_blocks_in_frame:表示ADTS帧中有number_of_raw_data_blocks_in_frame + 1个AAC原始帧, 所以说number_of_raw_data_blocks_in_frame == 0 表示说ADTS帧中有一个AAC数据块并不是说没有

h264视频文件读取

h264文件读取有两种方式:通过帧索引和通过解析h264结构

通过帧索引解析h264文件

所谓帧索引即是通过专门软件将h264文件中每一帧的信息做成索引存储在一个文件中,索引文件需要存储的信息有:

每一帧的地址(在源文件中的位置) 每次都从对应地址开始读取本帧数据每一帧的数据大小帧格式 需要表明是否为I帧时间戳

该方法比较简单,在此不做赘述

通过解析h264结构读取文件

h264文件每一帧都由分隔符隔开,分隔符或为0x00,0x00,0x00,0x01,或为0x00,0x00,0x01,解析时遇分隔符即为一帧起始,两个分隔符之间的即为一帧数据,紧跟分隔符的是该帧类型。 在这里插入图片描述

aac音频文件读取

adts头部信息包含一帧音频数据的大小,该大小包含7字节的头部,因此实际数据需要跳过头部读取,相应大小也应减去7。 aac帧大小信息计算

mp4封装

相关API用法网上很多,在此不做详解。

初始化 初始化包括创建生成的文件句柄,生成track信息,设置视频sps,pps信息,fps信息和图像width,height,以及设置音频specfice config信息,采样率等。 伪代码如下: int mp4_init(const char *mp4_name) { encoder->f_h = MP4Create(mp4_name, 0); if (encoder->f_h == MP4_INVALID_FILE_HANDLE) { printf("create mp4 file failed\n"); return -1; } MP4SetTimeScale(encoder->f_h, g_h264_timeScale); //g_h264_timeScale一般设为90000 /* 创建video track */ encoder->h264_t = MP4AddH264VideoTrack(encoder->f_h, g_h264_timeScale, g_h264_timeScale / g_h264_fps, g_width, g_height, encoder->sps[4], encoder->sps[5], encoder->sps[6], 3); //此处传入的sps信息要跳过分隔符和帧类型字段 if (encoder->h264_t == MP4_INVALID_TRACK_ID) { printf("add video track failed\n"); return -1; } MP4SetVideoProfileLevel(encoder->f_h, 0x7f); /* 设置sps,pps信息 */ MP4AddH264SequenceParameterSet(encoder->f_h, encoder->h264_t, encoder->sps + 3, encoder->sps_length - 3); //此处传入的sps信息要跳过分隔符 MP4AddH264PictureParameterSet(encoder->f_h, encoder->h264_t, encoder->pps + 3, encoder->pps_length); /* 创建audio track */ encoder->aac_t = MP4AddAudioTrack(encoder->f_h, g_aac_timeScale, 1024, MP4_MPEG4_AUDIO_TYPE); if (encoder->aac_t == MP4_INVALID_TRACK_ID) { printf("add audio track failed\n"); return -1; } MP4SetAudioProfileLevel(encoder->f_h, 0x2); /* 设置acc specfice config信息 */ MP4SetTrackESConfiguration(encoder->f_h, encoder->aac_t, g_spec_cfg, 2); return 0; } 数据封装 int mp4_packeted(unsigned char *h264_buffer,int h264_frame_len, unsigned char *aac_buffer,int aac_frame_len,bool i_frame) { if (video) { if (!MP4WriteSample(encoder->f_h, encoder->h264_t, h264_buffer, h264_frame_len, MP4_INVALID_DURATION, 0, i_frame)) { //最后一个参数代表是否为关键帧,是则为true printf("video MP4WriteSample failed\n"); } } if (audio)) { if (!MP4WriteSample(encoder->f_h, encoder->aac_t, aac_buffer, aac_frame_len, MP4_INVALID_DURATION, 0, 1)) { //音频写入时最后参数置1即可 printf("audio MP4WriteSample failed\n"); } } return 0; }

视频数据写入时帧前4字节替换为该帧大小,且为大端形式;音频数据写入时需要去掉adts头(某些不能去掉,具体原因暂未深入研究)。

替换视频帧头 int nal_size = h264_frame_len - 4; h264_frame[0] = (nal_size & 0xff000000) >> 24; h264_frame[1] = (nal_size & 0x00ff0000) >> 16; h264_frame[2] = (nal_size & 0x0000ff00) >> 8; h264_frame[3] = nal_size & 0x000000ff; 关闭mp4文件句柄 MP4Close(encoder->f_h, MP4_CLOSE_DO_NOT_COMPUTE_BITRATE);

在此需要注意分隔符为0x00,0x00,0x01的情况,此时读取视频帧时应将h264_frame[0]留出来,视频帧从h264_frame[1]开始存入,以便后续替换前4字节。

注意点 mp4v2支持h264+aac封装为mp4,不支持h265MP4AddH264VideoTrack()和MP4AddH264SequenceParameterSet(),MP4AddH264PictureParameterSet()需要传入sps,pps数据,该数据是去掉流分隔符的MP4SetTrackESConfiguration()需要传入音频流的AAC object typessps,pps信息不用写入数据区初始化时视频fps要设置正确否则会出现封装的mp4视频播放变慢问题。封装完毕需要调用MP4Close()关闭mp4句柄,否则封装的mp4文件无法播放,使用vlc播放报错解析moov等结构失败。视频帧前4字节必须为帧大小,若该处设置错误,使用vlc播放报错如下: [h264 @ 0x7f1dcccf7f60] AVC: nal size 20 [h264 @ 0x7f1dcccf7f60] AVC: nal size 20 [h264 @ 0x7f1dcccf7f60] no frame!


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3