H.264的编码原理参考文章H.264的编码原理
解码器负责将符合H.264码流规范的压缩视频流解码,并进行图像重建。
根据如下图所示的解码器流图,我们可以看出基本的解码流程如下:解码器从网络提取层中接收压缩的比特流,经过对码流进行熵解码和重排序获得量化系数X;这些系数经过反量化和反变换得到残差数据D;解码器使用从码流中解码得到的头信息创建一个预测数据PRED,PRED与残差数据D求和得到图像块数据uF;最后每个uF通过去方块滤波得到重建图像的解码块F。预测数据PRED是由参考图像经过运动估计,或者未经滤波的重建图像经过帧内预测得到。
H.264/AVC标准中,NAL(Network Abstract Layer)是以NALU(NAL Unit)为单元来支持编码数据在基于包交换技术网络中传输的。它定义了符合传输层或存储介质要求的数据格式,同时给出各自的头信息,进一步提供了视频编码的接口。
每一个NAL单元都包含两个部分:一个字节的NAL Header和一个原始字节序列载荷RBSP(Raw Byte Sequence Payload)。RBSP可以是一个编码片、A/B/C型数据分片、图像参数集、序列参数集等。NALHeader为一个字节,由定长的三部分组成:隐藏比特位(F)和NAL-REFERENCE-IDC(NRI)、NAL类型(NAL_Type)。
在H.264编码中默认置为0,当网络识别到单元中存在比特错误时,可将其置为1。F位主要用于适应不同种类的网络环境。
NAL单元的优先级。这两个比特位指示了NAL单元的优先级,用于指定NAL单元在传输时的处理顺序。0表示最高优先级,3表示最低优先级。
取值范围是0~31,标识本单元内的RBSP数据结构的类型
进行NAL单元解码之前,首先进行RTP解析(采用RTP封装)或者通过起始码检测(采用比特流方式),从传输码流中查找获取NAL单元数据。
NAL单元解码的总体流程是:首先从NAL单元中提取出RBSP语法结构,然后按照流程处理RBSP语法结构。对于NAL单元的解码过程,输入是NAL单元,输出结果是解码后的当前图像(CurrPic)的样点值。
extern "C" {
#include
#include
#include
#include
}
int main() {
// 初始化FFmpeg
av_register_all();
// 打开输入文件
AVFormatContext *formatContext = avformat_alloc_context();
avformat_open_input(&formatContext, "output.h264", nullptr, nullptr);
avformat_find_stream_info(formatContext, nullptr);
// 找到视频流
int videoStreamIndex = -1;
for (int i = 0; i < formatContext->nb_streams; ++i) {
if (formatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
videoStreamIndex = i;
break;
}
}
if (videoStreamIndex == -1) {
fprintf(stderr, "No video stream found.\n");
return -1;
}
// 打开解码器
AVCodec *codec = avcodec_find_decoder(formatContext->streams[videoStreamIndex]->codecpar->codec_id);
AVCodecContext *codecContext = avcodec_alloc_context3(codec);
avcodec_parameters_to_context(codecContext, formatContext->streams[videoStreamIndex]->codecpar);
avcodec_open2(codecContext, codec, nullptr);
// 创建图像显示窗口
SDL_Init(SDL_INIT_VIDEO);
SDL_Window *window = SDL_CreateWindow("H.264 Decoding", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
codecContext->width, codecContext->height, SDL_WINDOW_OPENGL);
SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, 0);
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YUV420P, SDL_TEXTUREACCESS_STREAMING,
codecContext->width, codecContext->height);
// 解码并显示
AVPacket packet;
av_init_packet(&packet);
SDL_Event event;
int frameFinished;
AVFrame *frame = av_frame_alloc();
while (av_read_frame(formatContext, &packet) >= 0) {
if (packet.stream_index == videoStreamIndex) {
avcodec_decode_video2(codecContext, frame, &frameFinished, &packet);
if (frameFinished) {
SDL_UpdateYUVTexture(texture, nullptr, frame->data[0], frame->linesize[0],
frame->data[1], frame->linesize[1],
frame->data[2], frame->linesize[2]);
SDL_RenderClear(renderer);
SDL_RenderCopy(renderer, texture, nullptr, nullptr);
SDL_RenderPresent(renderer);
}
}
SDL_PollEvent(&event);
if (event.type == SDL_QUIT)
break;
av_packet_unref(&packet);
}
// 释放资源
avcodec_free_context(&codecContext);
avformat_close_input(&formatContext);
av_frame_free(&frame);
SDL_DestroyTexture(texture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}