H.264的编码原理参考文章H.264的编码原理
解码原理
解码器负责将符合H.264码流规范的压缩视频流解码,并进行图像重建。
根据如下图所示的解码器流图,我们可以看出基本的解码流程如下:解码器从网络提取层中接收压缩的比特流,经过对码流进行熵解码和重排序获得量化系数X;这些系数经过反量化和反变换得到残差数据D;解码器使用从码流中解码得到的头信息创建一个预测数据PRED,PRED与残差数据D求和得到图像块数据uF;最后每个uF通过去方块滤波得到重建图像的解码块F。预测数据PRED是由参考图像经过运动估计,或者未经滤波的重建图像经过帧内预测得到。
NAL单元结构
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位主要用于适应不同种类的网络环境。
NRI
NAL单元的优先级。这两个比特位指示了NAL单元的优先级,用于指定NAL单元在传输时的处理顺序。0表示最高优先级,3表示最低优先级。
NAL_Type
取值范围是0~31,标识本单元内的RBSP数据结构的类型
NAL单元解码过程
进行NAL单元解码之前,首先进行RTP解析(采用RTP封装)或者通过起始码检测(采用比特流方式),从传输码流中查找获取NAL单元数据。
NAL单元解码的总体流程是:首先从NAL单元中提取出RBSP语法结构,然后按照流程处理RBSP语法结构。对于NAL单元的解码过程,输入是NAL单元,输出结果是解码后的当前图像(CurrPic)的样点值。
使用ffmpeg进行H.264解码并播放视频
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <SDL2/SDL.h>
}int main() {// 初始化FFmpegav_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;
}