shopex更改数据密码后网站打不开了,自己做的网站能干站什么,监控网站模板下载,如何搭建网络教学平台文章目录 音频格式AACADIF音频数据交换格式ADTS音频数据传输流 音频解码音频编码 视频格式H264GOP图像组I帧#xff0c;P帧#xff0c;B帧H264压缩技术H264压缩级别H264视频级别H264码流结构SPSPPS 解码视频编码视频 音频格式
AAC
AAC全称 Advanced Audio Coding#xff0… 文章目录 音频格式AACADIF音频数据交换格式ADTS音频数据传输流 音频解码音频编码 视频格式H264GOP图像组I帧P帧B帧H264压缩技术H264压缩级别H264视频级别H264码流结构SPSPPS 解码视频编码视频 音频格式
AAC
AAC全称 Advanced Audio Coding是一种专为声音数据设计的文件压缩格式。出现于1997年基于MPEG-2的音频编码技术。由Fraunhofer IIS、杜比实验室、ATT、索尼等公司共同开发目的是取代MP3格式。与MP3不同它采用了全新的算法进行编码更加高效具有更高的性价比。利用AAC格式可使人感觉声音质量没有明显降低的前提下更加小巧。
优点相较于mp3AAC格式的音质更佳文件更小。 不足AAC属于有损压缩的格式与时下流行的APE、FLAC等无损格式相比音质存在本质上的差距。加之传输速度更快的USB3.0和16G以上大容量MP3正在加速普及也使得AAC头上小巧的光环不复存在。
AAC共有9种规格以适应不同场合的需要
MPEG-2 AAC LC 低复杂度规格low complexity比较简单没有增益控制但提高了编码效率在中等码率的编码效率及音质方面都能找到平衡点MPEG-2 AAC Main 主规格MPEG-2 AAC SSR 可变采样率规格scaleable sample rateMPEG-4 AAC LC 低复杂度规格现在的手机比较常见的MP4文件中的音频部分就包括了该规格音频文件MPEG-4 AAC Main 主规格。包含了除增益控制之外的全部功能其音质最好。MPEG-4 AAC SSR 可变采样率规格MPEG-4 AAC LTP 长时期预测规格Long Term PredicitionMPEG-4 AAC LD 低延迟规格 Low DelayMPEG-4 AAC HE 高效率规格High Efficiency这种规格适合用于低码率编码有Nero AAC编码器支持 HEHigh Efficiency高效性。 HE-AAC v1又称AACPlusV1,SBR用容器的方法实现了AAC(LC)SBR技术。SBR其实代表的是Spectral Band Replication频段复制。简要叙述一下音乐的主要频谱集中在低频段高频段幅度很小但很重要决定了音质。如果对整个频段编码若是为了保护高频就会造成低频段编码过细以致文件巨大若是保存了低频的主要成分而失去高频成分就会丧失音质。SBR把频谱切割开来低频单独编码保存主要成分高频单独放大编码保存音质“统筹兼顾”了在减少文件大小的情况下还保存了音质完美地化解了这一矛盾。 HEv2用容器的方法包含了HE-AAC v1和PS技术。PS指parametric stereo参数立体声。原来的立体声文件大小是一个声道的两倍。但是两个声道的声音存在某种相似性根据香农信息熵编码定理相关性应该被去掉才能减小文件大小。所以PS技术存储了一个声道的全部信息然后花很少的字节用参数描述另一个声道和它不同的地方
目前使用最多的是LC和HE适合低码率。流行的Nero AAC编码程序只支持LCHE、HEv2这三种规格编码后的AAC音频规格显示都是LCHE其实就是AAC(LC)SBR技术HEv2就是AAC(LC)SBRPS技术。
AAC音频格式分为ADIF和ADTS
ADIFaudio data interchange format音频数据交换格式。这种格式的特征是可以确定的找到这个音频数据的开始不需进行在音频数据流中间开始的解码即它的解码必须在明确定义的开始处进行。故这种格式常用在磁盘文件中。ADTS audio data transport stream音频数据传输流。这种格式的特征是它是一个有同步字的比特流解码可以在这个流中任何位置开始它的特征类似于mp3数据流格式。 简单的说ADTS可以在任意帧解码也就是说它每一帧都有头信息。ADIF只有一个统一的头所以必须得到所有的数据后解码。且这两种的header格式也是不同的目前一般编码后的和抽取出的都是ADTS格式的音频流两者具体的组织结构如下所示 空白处表示前后帧
有时候当你编码AAC裸流的时候会遇到写出来的AAC并不能在PC和手机上播放很大可能就是AAC文件的每一帧里缺少了ADTS头信息文件的包装拼接。秩序奥加入头文件ADTS即可。
ADIF音频数据交换格式
其中adif_header如下表 raw_data_stream如图所示 byte_alignment为了保持字节对齐用 可以看到ADIF只有一个header里面没有关于每帧的信息因为每帧不是固定大小故只能按照顺序进行解码也无法跳播或快进快退。除非自己解码遍历整个文件建立每帧位置表。
ADTS音频数据传输流
一个AAC原始数据块长度是可变的对原始帧加上ADTS头进行ADTS的封装就形成了ADTS帧AAC音频文件的每一帧由ADTS Header和AAC Audio Data组成。结构体如下 ADTS头部信息占据了整个文件中的前7或9个字节分为2部分
adts_fixed_header()固定头信息头信息中的每一帧都相同其中包括了一个固定的同步标记syncword该标记用于确定音频帧的边界位置。adts_variable_header()可变头信息头信息则在帧与帧之间可变 一个完整的头信息就是固定头可变头。 以下是对头信息中各字段的详细介绍
固定头
syncword同步字12bit同步字是ADTS文件的标志符它用于确定音频帧的开始位置和结束位置通常为0xFFFID9bitID指示使用的MPEG版本。0表示MPEG-41表示MPEG-2layer2bitLayer定义了音频流所属的层级对于AAC来说其值为0。protection_absent1bit指示是否启动CRC错误校验。0表明数据经过CRC校验否则未经过profile2bit指示编码所使用的AAC规范类型 在MPEG-2 中定义了3种 MPEG-4中profile的值MPEG-4 Audio Object Type - 1 sampling_frequency_index4bit表示采样率的索引它告诉解码器当前音频数据的采样率。值的范围是0-15每一个值表示一个特定的采样率。 private_bit1bit为私有比特通常被设置为0没有实际作用。channel_configuration3bit指示音频的通道数范围也是0-15 original_copy1bit指示编码数据是否被原始产生通常为0home1bit通常被设置为0没有实际作用
可变头
copyrighted_id_bit编码时设置为0解码时忽略copyrighted_id_start编码时设置为0解码时忽略aac_frame_lengthADTS帧长度它包括ADTS长度和AAC声音数据长度。即aac_frame_length ( protection_absent 0 ? 9 : 7 ) audio_data_lengthadts_buffer_fullness 固定位0x7FF。表示是码率可变的码流number_of_raw_data_blocks_in_frameADTS帧中有number_of_raw_data_blocks_in_frame 1个AAC原始帧。一个AAC原始帧包含一段时间内1024个采样及相关数据
AAC ES是AAC音频编码的一种基本数据格式也是AAC音频数据在流式传输和文件存储中的常见格式之一。 不同于其他容器格式它不包含额外的元数据或结构信息仅包含未经任何封装或压缩处理的原始音频数据。这些原始数据可以作为音频文件或流传输的基础同时也可以用于对AAC音频进行转码、编辑或者重组。 通常由一系列连续的AAC音频帧组成每个帧以一个特定的标志符开始该标志符表示这是一个AAC音频帧。在AAC ES中每个音频帧拥有相同的长度但并一定包含相同数量的采样点因为采样率和声道数量可能会发生变化。 另一个关键特征是其比特流顺序即数字音频数据的组织方式AAC ES采用大端字节顺序其中高位字节排在前面地位字节排在后面。此外在AAC ES中音频数据按照从左到右、自上而下的顺序排列与典型的文本文件不同。 总之AAC ES是AAC音频编码的一种基本数据格式它通常由一系列AAC音频帧组成并且不包含任何附加的元数据或结构信息。AAC ES可以作为音频文件或流传输的基础同时也可以用于对AAC音频进行转码、编辑或重组。由于其简单性和灵活性AAC ES受到了广泛的应用并且成为了数字音频编码领域的标准之一。
音频解码
void Widget::decode()
{char errbuf[1024]; // 错误信息缓冲区const char *inputFile ../source/audio.aac; // 输入文件const char *outputFile ../output/out.pcm; // 输出文件AVFormatContext *pInFormatCtx NULL; // 打开文件上下文// 打开输入文件if (avformat_open_input(pInFormatCtx, inputFile, NULL, NULL) ! 0){av_strerror(1, errbuf, sizeof(errbuf));printf(无法打开输入文件%s\n, errbuf);return;}// 获取输入文件信息if (avformat_find_stream_info(pInFormatCtx, NULL) 0){av_strerror(1, errbuf, sizeof(errbuf));printf(无法获取输入文件信息%s\n, errbuf);return;}// 查找音频流int audioStreamIndex av_find_best_stream(pInFormatCtx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);if (audioStreamIndex 0){printf(无法找到音频流\n);return;}// 查找解码器const AVCodec *pCodec avcodec_find_decoder(pInFormatCtx-streams[audioStreamIndex]-codecpar-codec_id);if (!pCodec){printf(无法找到解码器\n);return;}// 创建解码器上下文AVCodecContext *pCodecCtx avcodec_alloc_context3(pCodec);if (!pCodecCtx){printf(无法创建解码器上下文\n);return;}// 复制解码器参数if (avcodec_parameters_to_context(pCodecCtx, pInFormatCtx-streams[audioStreamIndex]-codecpar) 0){printf(无法复制解码器参数\n);return;}// 打开解码器if (avcodec_open2(pCodecCtx, pCodec, NULL) 0){printf(无法打开解码器\n);return;}// 创建输出文件FILE *pOutFile fopen(outputFile, wb);if (!pOutFile){printf(无法创建输出文件\n);return;}// 解码数据AVPacket packet; // 数据包AVFrame *pFrame av_frame_alloc(); // 解码后的数据帧while (av_read_frame(pInFormatCtx, packet) 0){// 解码数据包int ret avcodec_send_packet(pCodecCtx, packet);if (ret 0){return; // 解码失败}while (ret 0){ret avcodec_receive_frame(pCodecCtx, pFrame);if (ret AVERROR(EAGAIN) || ret AVERROR_EOF){break;}else if (ret 0){av_strerror(ret, errbuf, sizeof(errbuf));printf(解码数据帧失败%s\n, errbuf);break;}int fmtByteSize av_get_bytes_per_sample((AVSampleFormat)pFrame-format); // 每个样本的字节数for (int i 0; i pFrame-nb_samples; i) // 每个样本{for (int ch 0; ch pFrame-ch_layout.nb_channels; ch) // 每个声道{fwrite(pFrame-data[ch] i * fmtByteSize, 1, fmtByteSize, pOutFile); // 写入文件}}av_frame_unref(pFrame); // 释放数据帧}av_packet_unref(packet); // 释放数据包}// 编码器刷新将剩余的数据帧编码为数据包int ret avcodec_send_packet(pCodecCtx, NULL);while (ret 0){ret avcodec_receive_frame(pCodecCtx, pFrame);if (ret 0){break;}int fmtByteSize av_get_bytes_per_sample((AVSampleFormat)pFrame-format); // 每个样本的字节数for (int i 0; i pFrame-nb_samples; i) // 每个样本{for (int ch 0; ch pFrame-ch_layout.nb_channels; ch) // 每个声道{fwrite(pFrame-data[ch] i * fmtByteSize, 1, fmtByteSize, pOutFile); // 写入文件}}av_frame_unref(pFrame); // 释放数据帧}qDebug() 解码完成;// 释放资源fclose(pOutFile);av_frame_free(pFrame);avcodec_free_context(pCodecCtx);avformat_close_input(pInFormatCtx);
}音频编码
写入adts头的函数
void Widget::writeADTSHeader(std::ofstream file, const AVCodecContext *codecContext, const int packetSize)
{uint8_t adtsHeader[7]{0};// 写入ADTS头,注意位运算优先级比较低所以括号不能省略adtsHeader[0] 0xFF;adtsHeader[1] 0xF1; // 0xF1表示MPEG-4 AAAC 校验位为1adtsHeader[2] (codecContext-profile 6) (4 2) (codecContext-ch_layout.nb_channels 2);adtsHeader[3] ((codecContext-ch_layout.nb_channels 3) 6) ((packetSize 7) 11); // 固定头的后4位可变头的前4位adtsHeader[4] ((packetSize 7) 3) 0xFF;adtsHeader[5] (((packetSize 7) 0x07) 5) 0x1F;adtsHeader[6] 0xFC;file.write((char *)adtsHeader, 7);
}编码函数
void Widget::encode()
{char errbuf[1024]; // 错误信息缓冲区const char *inputFile ../../source/audio.pcm; // 输入文件const char *outputFile ../../output/out1.aac; // 输出文件// 查找AAC编码器const AVCodec *pCodec avcodec_find_encoder(AV_CODEC_ID_AAC);if (!pCodec){printf(无法找到编码器\n);return;}// 创建编码器上下文AVCodecContext *pCodecCtx avcodec_alloc_context3(pCodec);if (!pCodecCtx){printf(无法创建编码器上下文\n);return;}pCodecCtx-sample_fmt AV_SAMPLE_FMT_FLTP; // 设置采样格式pCodecCtx-sample_rate 44100; // 设置采样率AVChannelLayout ch_layout AV_CHANNEL_LAYOUT_STEREO; // 设置声道布局av_channel_layout_copy(pCodecCtx-ch_layout, ch_layout);pCodecCtx-bit_rate 128000; // 设置比特率pCodecCtx-flags AV_CODEC_FLAG_GLOBAL_HEADER; // 设置全局头标志pCodecCtx-profile AV_PROFILE_AAC_HE_V2; // 设置AAC编码器配置文件// 打开编码器if (avcodec_open2(pCodecCtx, pCodec, NULL) 0){printf(无法打开编码器\n);return;}std::ifstream pInFile(inputFile, std::ifstream::binary); // 打开输入文件std::ofstream pOutFile(outputFile, std::ofstream::binary); // 打开输出文件if (!pInFile.is_open() || !pOutFile.is_open()){return;}// 创建数据包AVPacket *packet av_packet_alloc();if (!packet){printf(无法创建数据包\n);return;}// 创建数据帧AVFrame *pFrame av_frame_alloc();if (!pFrame){printf(无法创建数据帧\n);return;}pFrame-sample_rate pCodecCtx-sample_rate; // 设置采样率pFrame-format pCodecCtx-sample_fmt; // 设置采样格式av_channel_layout_copy(pFrame-ch_layout, pCodecCtx-ch_layout); // 设置声道布局pFrame-nb_samples pCodecCtx-frame_size; // 设置每帧的样本数// 分配内存if (av_frame_get_buffer(pFrame, 0) 0){printf(无法分配内存\n);return;}// 分配缓冲区int fmtByteSize av_get_bytes_per_sample(pCodecCtx-sample_fmt); // 获取每个样本的字节数uint8_t *pBuffer (uint8_t *)av_malloc(fmtByteSize * pCodecCtx-frame_size * pCodecCtx-ch_layout.nb_channels); // 分配缓冲区if (!pBuffer){printf(无法分配缓冲区\n);return;}// 读取数据while (!pInFile.eof()){// 读取数据for (int i 0; i pFrame-nb_samples * pFrame-ch_layout.nb_channels / 2; i){pInFile.read((char *)pBuffer, fmtByteSize * 2);if (pInFile.eof()){break;}for (int ch 0; ch pFrame-ch_layout.nb_channels; ch) // 每个声道{memcpy(pFrame-data[ch] i * fmtByteSize, pBuffer ch * fmtByteSize, fmtByteSize); // 复制数据到数据帧}}// 编码int ret avcodec_send_frame(pCodecCtx, pFrame);if (ret 0)break;while (ret 0){ret avcodec_receive_packet(pCodecCtx, packet);if (ret 0)break;writeADTSHeader(pOutFile, pCodecCtx, packet-size); // 写入ADTS头pOutFile.write((char *)packet-data, packet-size); // 写入数据av_packet_unref(packet); // 释放数据包}// 清空缓冲区memset(pBuffer, 0, fmtByteSize * pFrame-nb_samples * pFrame-ch_layout.nb_channels);}// 清空编码器,防止缓冲区有数据int ret avcodec_send_frame(pCodecCtx, nullptr);while (ret 0){ret avcodec_receive_packet(pCodecCtx, packet);if (ret 0)break;writeADTSHeader(pOutFile, pCodecCtx, packet-size); // 写入ADTS头pOutFile.write((char *)packet-data, packet-size); // 写入数据av_packet_unref(packet); // 释放数据包}// 释放资源av_frame_free(pFrame);av_packet_free(packet);avcodec_free_context(pCodecCtx);pInFile.close();pOutFile.close();av_free(pBuffer);printf(转换完成\n);
}
视频格式
H264
H264从1999年开始到2003年形成草案最后在2007年定稿有待核实。H264是MPG-4标准所定义的最新编码格式同时也是技术含量最高、代表最新技术水平的视频编码格式之一在ITU的标准称为H.264,在MPEG的标准里是MPEG-4的一个组成部分MPEG-4 Part10又叫Advanced Video Codec因此常常被称为MPG-4 AVC或者直接叫AVC。H264视频格式是经过有损压缩的但在技术上尽可能做的降低存储体积下获得较好图像质量和低带宽图像快速传输。压缩比大约是1%
GOP图像组
GOPGroup of Pictures顾名思义就是一组图片在实际操作中就是一组完整的视频帧怎么叫做完整的视频帧就是说一个GOP拿出来必须能够完整地播放、显示。也就是两个I帧之间的间隔。
I帧P帧B帧
I帧intraframe frame也叫关键帧采用帧内压缩技术将其解压出来就是一张完整的图片。GOP中第一个I帧也被称为IDR帧。P帧forward Predicted frame向前参考帧也叫预测帧。压缩时只参考前面已经处理的帧采用帧间压缩技术它只占用I帧一半大小。B帧Bidirectionally predicted frame双向参考帧也叫双向预测帧。压缩时既参考前面已经处理的帧也参考后面的帧使用帧间压缩技术。它占I帧1/4大小
IDR帧和I帧的区别 一个序列的第一个图像叫做IDR帧立即刷新图像IDR帧都是I帧。I帧和IDR帧都是用帧内预测。不用参考任何帧但是之后的P帧和B帧是有可能参考这个I帧之前的帧的。IDR就不允许这样。
其核心作用是当解码器解码到IDR帧时立即将参考帧队列清空将已解码的数据全部输出或抛弃重新查找参数集开始一个新的序列。这样使错误不致传播从IDR帧开始算新的序列开始编码。
H264压缩技术
H264首先会把一张图片分成一个个的片片里面又会分成一个个宏块。 宏块是视频压缩操作的基本单元。无论是帧内压缩还是帧间压缩它们都以宏块为单位。 以下面的图片为例 划分片 划分宏块 H264默认是用16x16 大小的区域作为一个宏块也可以划分成8x8大小。划分好宏块后计算宏块的像素值 划分子块 H264对比较平坦的图像使用16x16大小的宏块。但是为了更高的压缩率还可以再这个宏块上划分出更小的子块子块的大小可以是8x1616x88x84x88x44x4非常的灵活 上图红框内的16x16宏块大部分是蓝色背景而三只鹰的部分图像被划在了该宏块内为了更好地处理三只鹰部分的图像 就在这个宏块内又划分出了多个子块。 这样再经过帧内压缩可以得到更高效的数据。
常见的宏块尺寸如下 帧内压缩也称为空间压缩当压缩一帧图像时仅考虑本帧的数据而不考虑相邻帧之间的冗余信息这实际上与静态图像压缩类似。帧内一般采用有损压缩算法由于帧内压缩是编码一个完整的图像所以可以独立地解码、显示。帧内压缩一般达不到很高的压缩跟编码JPEG差不多。
帧间压缩相邻几帧的数据有很大的相关性或者说前后两帧信息变化很小的特点。也即连续的视频其相邻帧之间具有冗余信息根据这一特性压缩相邻帧之间的冗余量就可以进一步提高压缩量减小压缩比。帧间压缩也称为时间压缩它通过比较时间轴上不同帧之间的数据进行压缩。帧间压缩一般是无损的帧差值算法是一种典型的时间压缩法它通过比较本帧与相邻帧之间的差异仅记录本帧与其相邻帧的差值这样可以大大减少数据量。
帧内预测 如图所示左侧显示的是H264对于帧内预测提供的9种模式 9种帧内预测模式
第一种是纵列的即垂直模式左边以及上边都是已经推测出来的宏块这个时候就可以将下面4x4的宏块预测出来对于垂直模式来说上边A对应下面的一整列都是A,第二个B对应一列下面也都是B以此类推。第二种是横向的水平模式还是左边以及上边都是都是预测出来的数据预测结果第一行是I第二行是j以此类推。第三种是求平均值在目标的4x4宏块内都是ABCD加上IJKL的求平均值例如宏块内的小a,它的值是ABCD加IJKL求平均值得出也就是下面每一个4x4内的像素值都是一样的。需要注意的是像素a对应的值是ABCDIJKL/2而不是AI)/2。
H264压缩级别
H264 Profile 是对视频压缩特性的描述Profile越高说明采用了越高级的压缩特性。 H264视频级别
H264 Level是对视频的描述Level越高视频的码率、分辨率、FPS越高。
H264码流结构
H264的两种码流格式它们分别为字节流格式和RTP包格式。
字节流格式Annexb这是在H264官方协议文档中规定的格式处于文档附录B(Annex-B Byte stream format)中所以它也成为了大多数编码器默认的输出格式。它的基本数据单位为NAL单元也即NALU。为了从字节流中提取出NALU协议规定在每个NALU的前面加上起始码0x000001或0x00000001RTP包格式这种格式并没有在H264中规定那为什么还要介绍它呢是因为在H264的官方参考软件JM里有这种封装格式的实现。在这种格式中NALU并不需要起始码Start_Code来进行识别而是在NALU开始的若干字节124字节代表NALU的长度。
码流分层图 接下来我们主要讲字节流格式H264码流分为2层NAL层和VCL层
NALNetwork Abstraction Layer层视频数据网络抽象层是H264码流的基本单元。VCLVideo Coding Layer层视频数据编码层VCL结构关系如下图所示在视频帧序列由图像组成图像中包含分片分片里面包含宏块宏块里面又包含子块 接下来我们来看NALUNALU由NALU头部和NALU载荷 (Raw Byte Sequence Payload ,RBSP) 组成其中NALU头部包括禁止位 (forbidden_zero_bit)、参考标识nal_ref_idc和NALU类型nal_uint_type等信息。NALU载荷则是包含实际视频数据的原始字节序列。
对于一个H264裸流它就是由一个起始码StartCode和一个NALU单元组成 由上图可以知道NALU单元 NALU Header RBSP 其中RBSP原始字节序列载荷即在SODB的后面添加了trailing bit即一个bit 1和若干个bit0以使字节对齐SODBString of Data Bits原始数据比特流原始数据比特流长度不一定是8的倍数故需要补齐他是由VCL层产生的
在每一个I帧之前都会存在一个SPS和PPS。
SPS(Sequence Parameter Set) 序列参数集作用于一连串连续的视频图像。如seq_parameter_set_id、帧数及POC(picture order count)的约束、参考帧数目、解码图像尺寸和熵编码模式选择标识等PPS(Picture Parameter Set)图像参数集作用于视频序列中的图像。如pic_parameter_set_id、熵编码模式选择标识、片组数目、初始量化参数和去方块滤波系数调整标识等
SPS
SPS结构如下
profile_idc标识当前H264码流的profile。我们知道H264中定义了三种常用的档次profile: 基准档次baseline profile主要档次main profile扩展档次extended profile 在H264的SPS中第一个字节表示profile_idc根据profile_idc的值可以确定码流符合哪一种档次。判断规律为profile_idc 66 → baseline profileprofile_idc 77 → main profileprofile_idc 88 → extended profile 在新版的标准中还包括了High、High 10、High 4:2:2、High 4:4:4、High 10 Intra、High4:2:2 Intra、High 4:4:4 Intra、CAVLC 4:4:4 Intra等每一种都由不同的profile_idc表示。另外constraint_set0_flag ~ constraint_set5_flag是在编码的档次方面对码流增加的其他一些额外限制性条件。
level_idc标识当前码流的Level。编码的Level定义了某种条件下的最大视频分辨率、最大视频帧率等参数码流所遵从的Level由level_idc指定。seq_parameter_set_id表示当前的序列参数集的id通过该id值图像参数集pps可以引用其代表的sps中的参数。log2_max_frame_num_minus4用于计算MaxFrameNum的值。计算公式为 M a x F r a m e N u m 2 l o g 2 m a x f r a m e n u m m i n u s 4 4 MaxFrameNum 2^{log2_max_frame_num_minus4 4} MaxFrameNum2log2maxframenumminus44。MaxFrameNum是frame_num的上限值frame_num是图像序号的一种表示方法在帧间编码中常用作一种参考帧标记的手段。pic_order_cnt_type表示解码picture order count(POC的方法。POC是另一种计量图像序号的方式与frame_num有着不同的计算方法。该语法元素的取值为0、1或2log2_max_pic_order_cnt_lsb_minus4用于计算MaxPicOrderCntLsb的值该值表示POC的上限。计算方法为 M a x P i c O r d e r C n t L s b 2 l o g 2 m a x p i c o r d e r c n t l s b m i n u s 4 4 MaxPicOrderCntLsb 2^{log2_max_pic_order_cnt_lsb_minus4 4} MaxPicOrderCntLsb2log2maxpicordercntlsbminus44。 max_num_ref_frames用于表示参考帧的最大数目。 gaps_in_frame_num_value_allowed_flag标识位说明frame_num中是否允许不连续的值。 pic_width_in_mbs_minus1用于计算图像的宽度。单位为宏块个数因此图像的实际宽度为: f r a m e w i d t h 16 × ( p i c w i d t h i n m b s m i n u s 1 1 ) frame_width 16 \times (pic_width_in_mbs_minus1 1) framewidth16×(picwidthinmbsminus11) pic_height_in_map_units_minus1使用PicHeightInMapUnits来度量视频中一帧图像的高度。PicHeightInMapUnits并非图像明确的以像素或宏块为单位的高度而需要考虑该宏块是帧编码或场编码。PicHeightInMapUnits的计算方式为 P i c H e i g h t I n M a p U n i t s p i c h e i g h t i n m a p u n i t s m i n u s 1 1 PicHeightInMapUnits pic_height_in_map_units_minus1 1 PicHeightInMapUnitspicheightinmapunitsminus11 frame_mbs_only_flag标识位说明宏块的编码方式。当该标识位为0时宏块可能为帧编码或场编码该标识位为1时所有宏块都采用帧编码。根据该标识位取值不同PicHeightInMapUnits的含义也不同为0时表示一场数据按宏块计算的高度为1时表示一帧数据按宏块计算的高度。 按照宏块计算的图像实际高度FrameHeightInMbs的计算方法为 F r a m e H e i g h t I n M b s ( 2 − f r a m e m b s o n l y f l a g ) × P i c H e i g h t I n M a p U n i t s FrameHeightInMbs ( 2 − frame_mbs_only_flag ) \times PicHeightInMapUnits FrameHeightInMbs(2−framembsonlyflag)×PicHeightInMapUnits mb_adaptive_frame_field_flag 标识位说明是否采用了宏块级的帧场自适应编码。当该标识位为0时不存在帧编码和场编码之间的切换当标识位为1时宏块可能在帧编码和场编码模式之间进行选择。 direct_8x8_inference_flag标识位用于B_Skip、B_Direct模式运动矢量的推导计算。 frame_cropping_flag标识位说明是否需要对输出的图像帧进行裁剪。 vui_parameters_present_flag标识位说明SPS中是否存在VUI信息。
PPS
PPS结构如下 其中的每一个语法元素及其含义如下 pic_parameter_set_id表示当前PPS的id。某个PPS在码流中会被相应的slice引用slice引用PPS的方式就是在Slice header中保存PPS的id值。该值的取值范围为[0,255]。 seq_parameter_set_id表示当前PPS所引用的激活的SPS的id。通过这种方式PPS中也可以取到对应SPS中的参数。该值的取值范围为[0,31]。 entropy_coding_mode_flag熵编码模式标识该标识位表示码流中熵编码/解码选择的算法。对于部分语法元素在不同的编码配置下选择的熵编码方式不同。例如在一个宏块语法元素中宏块类型mb_type的语法元素描述符为“ue(v)| ae(v)”在baseline profile等设置下采用指数哥伦布编码在main profile等设置下采用CABAC编码。 标识位entropy_coding_mode_flag的作用就是控制这种算法选择。当该值为0时选择左边的算法通常为指数哥伦布编码或者CAVLC当该值为1时选择右边的算法通常为CABAC。 bottom_field_pic_order_in_frame_present_flag标识位用于表示另外条带头中的两个语法元素delta_pic_order_cnt_bottom和delta_pic_order_cn是否存在的标识。这两个语法元素表示了某一帧的底场的POC的计算方法。 num_slice_groups_minus1表示某一帧中slice group的个数。当该值为0时一帧中所有的slice都属于一个slice group。slice group是一帧中宏块的组合方式定义在协议文档的3.141部分。 num_ref_idx_l0_default_active_minus1、num_ref_idx_l0_default_active_minus1表示当Slice Header中的num_ref_idx_active_override_flag标识位为0时P/SP/Bslice的语法元素num_ref_idx_l0_active_minus1和num_ref_idx_l1_active_minus1的默认值。 weighted_pred_flag标识位表示在P/SP slice中是否开启加权预测。 weighted_bipred_idc表示在B Slice中加权预测的方法取值范围为[0,2]。0表示默认加权预测1表示显式加权预测2表示隐式加权预测。 pic_init_qp_minus26和pic_init_qs_minus26表示初始的量化参数。实际的量化参数由该参数、slice header中的 s l i c e q p d e l t a ÷ s l i c e q s d e l t a slice_qp_delta \div slice_qs_delta sliceqpdelta÷sliceqsdelta计算得到。 chroma_qp_index_offset用于计算色度分量的量化参数取值范围为[-12,12]。 deblocking_filter_control_present_flag标识位用于表示Slice header中是否存在用于去块滤波器控制的信息。当该标志位为1时slice header中包含去块滤波相应的信息当该标识位为0时slice header中没有相应的信息。 constrained_intra_pred_flag若该标识为1表示I宏块在进行帧内预测时只能使用来自I和SI类型宏块的信息若该标识位0表示I宏块可以使用来自Inter类型宏块的信息。 redundant_pic_cnt_present_flag标识位用于表示Slice header中是否存在redundant_pic_cnt语法元素。当该标志位为1时slice header中包含redundant_pic_cnt当该标识位为0时slice header中没有相应的信息。
解码视频
void Widget::H264Decode()
{char errbuf[1024];const char *infile ../../source/video.h264;const char *outfile ../../output/out.yuv;AVFormatContext *fmt_ctx nullptr; // 输入文件上下文// 打开输入文件if (avformat_open_input(fmt_ctx, infile, nullptr, nullptr) 0){av_log(NULL, AV_LOG_ERROR, 无法打开资源文件 %s\n, infile);return;}// 获取资源信息if (avformat_find_stream_info(fmt_ctx, nullptr) 0){av_log(NULL, AV_LOG_ERROR, 无法获取资源信息\n);return;}// 查找视频流int video_stream_index av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);if (video_stream_index 0){av_log(NULL, AV_LOG_ERROR, 无法找到视频流\n);return;}// 查找解码器const AVCodec *codec avcodec_find_decoder(fmt_ctx-streams[video_stream_index]-codecpar-codec_id);if (!codec){av_log(NULL, AV_LOG_ERROR, 无法找到解码器\n);return;}// 创建解码器上下文AVCodecContext *codec_ctx avcodec_alloc_context3(codec);if (!codec_ctx){av_log(NULL, AV_LOG_ERROR, 无法创建解码器上下文\n);return;}// 填充解码器上下文if (avcodec_parameters_to_context(codec_ctx, fmt_ctx-streams[video_stream_index]-codecpar) 0){av_log(NULL, AV_LOG_ERROR, 无法填充解码器上下文\n);return;}// 打开解码器if (avcodec_open2(codec_ctx, codec, nullptr) 0){av_log(NULL, AV_LOG_ERROR, 无法打开解码器\n);return;}// 打开输出文件std::ofstream outfileSream(outfile, std::ios::binary);if (!outfileSream.is_open()){av_log(NULL, AV_LOG_ERROR, 无法打开输出文件\n);return;}// 读取数据包AVPacket *pkt av_packet_alloc(); // 分配数据包AVFrame *frame av_frame_alloc(); // 分配帧int ret 0;while (av_read_frame(fmt_ctx, pkt) 0){// 解码ret avcodec_send_packet(codec_ctx, pkt); // 发送数据包到解码器if (pkt-stream_index ! video_stream_index)continue; // 不是视频流就跳过if (ret 0){av_log(NULL, AV_LOG_ERROR, 解码失败\n);break;}while (ret 0){ret avcodec_receive_frame(codec_ctx, frame); // 从解码器接收解码后的帧if (ret AVERROR(EAGAIN) || ret AVERROR_EOF)break;else if (ret 0){av_log(NULL, AV_LOG_ERROR, 解码失败\n);break;}// 写入文件for (int i 0; i frame-height; i){outfileSream.write((char *)frame-data[0] i * frame-linesize[0], frame-width); // 写入Y分量}for (int i 0; i frame-height / 2; i){outfileSream.write((char *)frame-data[1] i * frame-linesize[1], frame-width / 2); // 写入U分量}for (int i 0; i frame-height / 2; i){outfileSream.write((char *)frame-data[2] i * frame-linesize[2], frame-width / 2); // 写入V分量}av_frame_unref(frame); // 清空帧}av_packet_unref(pkt); // 清空数据包}// 清空编码器ret avcodec_send_packet(codec_ctx, nullptr);while (ret 0){ret avcodec_receive_frame(codec_ctx, frame);if (ret 0)break;// 写入文件for (int i 0; i frame-height; i){outfileSream.write((char *)frame-data[0] i * frame-linesize[0], frame-width); // 写入Y分量}for (int i 0; i frame-height / 2; i){outfileSream.write((char *)frame-data[1] i * frame-linesize[1], frame-width / 2); // 写入U分量}for (int i 0; i frame-height / 2; i){outfileSream.write((char *)frame-data[2] i * frame-linesize[2], frame-width / 2); // 写入V分量}}qDebug() 解码完成;// 释放资源av_frame_free(frame);av_packet_free(pkt);outfileSream.close();avcodec_free_context(codec_ctx);avformat_close_input(fmt_ctx);
}编码视频
void Widget::H264Encode()
{char errorBuffer[1024];const char *inputFile ../../source/video.yuv;const char *outputFile ../../output/out.h264;// 查找H264编码器const AVCodec *codec avcodec_find_encoder(AV_CODEC_ID_H264);if (!codec){qDebug() 找不到编码器;return;}// 创建编码器上下文AVCodecContext *codecContext avcodec_alloc_context3(codec);if (!codecContext){qDebug() 创建编码器上下文失败;return;}// 设置编码器参数codecContext-profile AV_PROFILE_H264_HIGH_444; // 设置编码器压缩级别codecContext-level 50; // 设置编码器视频质量级别50表示5.0codecContext-width 1280; // 设置编码器视频宽度codecContext-height 720; // 设置编码器视频高度codecContext-pix_fmt AV_PIX_FMT_YUV420P; // 设置编码器视频像素格式codecContext-time_base AVRational{1, 30}; // 设置编码器时间基准codecContext-framerate AVRational{30, 1}; // 设置编码器帧率codecContext-gop_size 30; // 设置编码器I帧间隔codecContext-max_b_frames 3; // 设置编码器B帧最大数量codecContext-keyint_min 3; // 设置编码器最小I帧间隔codecContext-has_b_frames 1; // 设置编码器是否支持B帧codecContext-refs 3; // 设置编码器参考帧数量codecContext-bit_rate 2000000; // 设置编码器比特率// 打开编码器CoUninitialize(); // 初始化COM库,头文件objbase.h这里防止报错COM must not be in STA modeint ret avcodec_open2(codecContext, codec, nullptr);if (ret 0){av_strerror(ret, errorBuffer, sizeof(errorBuffer));qDebug() 打开编码器失败: errorBuffer;return;}// 打开输入文件FILE *inputFilePtr fopen(inputFile, rb);if (!inputFilePtr){qDebug() Could not open input file;return;}// 打开输出文件FILE *outputFilePtr fopen(outputFile, wb);if (!outputFilePtr){qDebug() Could not open output file;return;}// 创建AVFrame对象AVFrame *frame av_frame_alloc();if (!frame){qDebug() Could not allocate video frame;return;}frame-format codecContext-pix_fmt;frame-width codecContext-width;frame-height codecContext-height;// 分配AVFrame对象缓冲区ret av_frame_get_buffer(frame, 0);if (ret 0){av_strerror(ret, errorBuffer, sizeof(errorBuffer));qDebug() 不能为帧分配缓冲区: errorBuffer;return;}// 创建AVPacket对象AVPacket *packet av_packet_alloc();if (!packet){qDebug() 不能分配数据包;return;}// 读取输入文件数据int ySize codecContext-width * codecContext-height;int frameCount 0;while (!feof(inputFilePtr)){fread(frame-data[0], ySize, 1, inputFilePtr); // Y分量fread(frame-data[1], ySize / 4, 1, inputFilePtr); // U分量fread(frame-data[2], ySize / 4, 1, inputFilePtr); // V分量frame-pts frameCount; // 设置帧时间戳// 编码一帧数据ret avcodec_send_frame(codecContext, frame);if (ret 0){av_strerror(ret, errorBuffer, sizeof(errorBuffer));qDebug() 编码失败: errorBuffer;return;}while (ret 0){ret avcodec_receive_packet(codecContext, packet);if (ret AVERROR(EAGAIN) || ret AVERROR_EOF)break;else if (ret 0){av_strerror(ret, errorBuffer, sizeof(errorBuffer));qDebug() 编码失败: errorBuffer;return;}// 写入输出文件fwrite(packet-data, packet-size, 1, outputFilePtr);av_packet_unref(packet);}frameCount; // 帧计数器加1}// 清空编码器ret avcodec_send_frame(codecContext, nullptr);while (ret 0){ret avcodec_receive_packet(codecContext, packet);if (ret 0)break;fwrite(packet-data, packet-size, 1, outputFilePtr); // 写入输出文件}// 释放资源av_packet_free(packet);av_frame_free(frame);avcodec_free_context(codecContext);fclose(inputFilePtr);fclose(outputFilePtr);qDebug() 编码完成;
}