H264解碼原理和音視頻-AAC解碼原理幾乎一樣, 不同的是就decode 里面數(shù)據(jù)的處理, 解碼的事情都是通過H264解碼器去實(shí)現(xiàn)
AAC解碼的簡略邏輯 :
AAC源文件 ==> (AVPacket)輸入緩沖區(qū) ==> (AVCodec)解碼器 ==> (AVFrame)輸出緩沖區(qū) ==> 輸出文件
H264解碼的簡略邏輯
H264源文件 ==> (AVPacket)輸入緩沖區(qū) ==> (AVCodec)解碼器 ==> (AVFrame)輸出緩沖區(qū) ==> 輸出文件
核心代碼
#include "h264DecodeThread.h"
#include <QDebug>
#include <QFile>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavutil/imgutils.h>
}
#define ERROR_BUF(ret) \
char errbuf[1024]; \
av_strerror(ret, errbuf, sizeof (errbuf));
#define CHECK_IF_ERROR_BUF_END(ret, funcStr) \
if (ret) { \
ERROR_BUF(ret); \
qDebug() << #funcStr << " error :" << errbuf; \
goto end; \
}
#ifdef Q_OS_WIN
#define IN_H264_FILEPATH "G:/BigBuckBunny_CIF_24fps_h264.h264"
#define OUT_H264_FILEPATH "G:/BigBuckBunny_CIF_24fps_h264_out.yuv"
#define IMGW 352
#define IMGH 288
#else
#define IN_H264_FILEPATH "/Users/liliguang/Desktop/dstYuv.h264"
#define OUT_H264_FILEPATH "/Users/liliguang/Desktop/h264_out.yuv"
#define IMGW 352
#define IMGH 288
#endif
#define VIDEO_INBUF_SIZE 4096
H264DecodeThread::H264DecodeThread(QObject *parent) : QThread(parent) {
// 當(dāng)監(jiān)聽到線程結(jié)束時(shí)(finished),就調(diào)用deleteLater回收內(nèi)存
connect(this, &H264DecodeThread::finished,
this, &H264DecodeThread::deleteLater);
}
H264DecodeThread::~H264DecodeThread() {
// 斷開所有的連接
disconnect();
// 內(nèi)存回收之前,正常結(jié)束線程
requestInterruption();
// 安全退出
quit();
wait();
qDebug() << this << "析構(gòu)(內(nèi)存被回收)";
}
static int frameIdx = 0;
// 音頻解碼
// 返回負(fù)數(shù):中途出現(xiàn)了錯誤
// 返回0:解碼操作正常完成
static int decode(AVCodecContext *ctx,
AVFrame *frame,
AVPacket *pkt,
QFile &outFile) {
// 發(fā)送數(shù)據(jù)到解碼 , sent_ret = 0 為sucesss
int ret = avcodec_send_packet(ctx, pkt);
if (ret < 0) {
ERROR_BUF(ret);
qDebug() << "avcodec_send_packet error" << errbuf;
return ret;
}
while (1) {
// 從解碼器中獲取到數(shù)據(jù)到frame
ret = avcodec_receive_frame(ctx, frame);
qDebug() << "avcodec_receive_frame : " << ret ;
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF ) {
return ret;
} else if (ret < 0) {
qDebug() << "ret < 0" << ret ;
return ret;
}
qDebug() << "解碼出第" << ++frameIdx << "幀";
// 將解碼后的數(shù)據(jù)寫入文件
qDebug() << "frame->linesize[0]" << frame->linesize[0] ;
qDebug() << "frame->linesize[1]" << frame->linesize[1] ;
qDebug() << "frame->linesize[2]" << frame->linesize[2] ;
qDebug() << "frame->linesize[3]" << frame->linesize[3] ;
qDebug() << "ctx->width" << ctx->width ;
qDebug() << "ctx->height" << ctx->height ;
qDebug() << "ctx->pix_fmt" << ctx->pix_fmt ;
qDebug() << "frame->format" << frame->format ;
qDebug() << "av_image_get_buffer_size " << av_image_get_buffer_size(ctx->pix_fmt, ctx->width, ctx->height, 0) ;
//yuv420p yyyy yyyy uu vv
//一幀yuv420p 352 * 288 * 1.5 = 152064
// y分量 :152064 * (8/12) = 152064 * 0.6666 = 101376
// u分量 :152064 * (2/12) = 152064 * 0.1666 = 25344
// v分量 :152064 * (2/12) = 152064 * 0.1666 = 25344
// 字節(jié)流中存儲樣式 :
// y1y2y3.....y101376 u1u2u3......u25344 v1v2v3......v25344
// qDebug() << "frame->data[0]" << frame->data[0] ;
// qDebug() << "frame->data[1]" << frame->data[1] ;
// qDebug() << "frame->data[2]" << frame->data[2] ;
// qDebug() << "frame->data[3]" << frame->data[3] ;
// 寫入Y平面
outFile.write((char *) frame->data[0], frame->linesize[0] * ctx->height);
// 寫入U(xiǎn)平面
outFile.write((char *) frame->data[1], frame->linesize[1] * ctx->height >> 1);
// 寫入V平面
outFile.write((char *) frame->data[2], frame->linesize[2] * ctx->height >> 1);
}
}
void H264DecodeThread::run() {
qDebug() << "H264DecodeThread run ";
// 解碼器
const AVCodec *codec = nullptr;
// 解碼器上下文
AVCodecContext *codecCtx = nullptr;
// Parser上下文
AVCodecParserContext *codecParserCtx = nullptr;
// 源文件數(shù)據(jù)源存儲結(jié)構(gòu)指針
AVFrame *frame = nullptr;
// 編碼文件數(shù)據(jù)源存儲結(jié)構(gòu)指針
AVPacket *pkt = nullptr;
int avcodec_open2_Ret;
// 輸入輸出文件
const char *infilename;
const char *outfilename;
infilename = IN_H264_FILEPATH;
outfilename = OUT_H264_FILEPATH;
QFile inFile(infilename);
QFile outFile(outfilename);
int infileOpen_Ret;
int outfileOpen_Ret;
int av_image_alloc_ret;
// 加上AV_INPUT_BUFFER_PADDING_SIZE是為了防止某些優(yōu)化過的reader一次性讀取過多導(dǎo)致越界.
char inDataArray[VIDEO_INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE]; // 輸入緩沖區(qū)
char *inData = inDataArray; // 指向輸入緩沖區(qū)指針
int inLen; // 讀取到文件的數(shù)據(jù)大小
bool inEnd = false;
int inParserRet;
int decode_ret;
// ============================================================
// 解碼邏輯 源文件 ==> 解析器 ==> (AVPacket)輸入緩沖區(qū) ==> 解碼器 ==> (AVFrame)輸出緩沖區(qū) ==> 輸出文件
// 輸入文件
infileOpen_Ret = inFile.open(QFile::ReadOnly);
CHECK_IF_ERROR_BUF_END(!infileOpen_Ret, "inFile.open");
// 輸出文件
outfileOpen_Ret = outFile.open(QFile::WriteOnly);
CHECK_IF_ERROR_BUF_END(!outfileOpen_Ret, "outFile.open");
// 創(chuàng)建輸入Packet
pkt = av_packet_alloc();
CHECK_IF_ERROR_BUF_END(!pkt, "av_packet_alloc");
// 創(chuàng)建輸出rame
frame = av_frame_alloc();
CHECK_IF_ERROR_BUF_END(!frame, "av_frame_alloc");
// 解碼器
codec = avcodec_find_decoder_by_name("h264");
CHECK_IF_ERROR_BUF_END(!codec, "avcodec_find_decoder");
// Parser解析器上下文
codecParserCtx = av_parser_init(codec->id);
CHECK_IF_ERROR_BUF_END(!codecParserCtx, "av_parser_init");
// 解碼器上下文
codecCtx = avcodec_alloc_context3(codec);
CHECK_IF_ERROR_BUF_END(!codecCtx, "avcodec_alloc_context3");
// 打開解碼器
avcodec_open2_Ret = avcodec_open2(codecCtx, codec, nullptr);
CHECK_IF_ERROR_BUF_END(avcodec_open2_Ret, "avcodec_open2");
do {
// 只要還沒有到文件結(jié)尾, 每次都讀取一次文件
inLen = inFile.read(inDataArray, VIDEO_INBUF_SIZE);
inEnd = !inLen;
// 每次將inData的位置重置為buffer緩沖區(qū)的首位置
inData = inDataArray;
// 如果不是文件結(jié)尾
while (inLen > 0 || inEnd) {
// 傳給parser
inParserRet = av_parser_parse2(codecParserCtx,
codecCtx,
&pkt->data,
&pkt->size,
(uint8_t *)inData,
inLen,
AV_NOPTS_VALUE,
AV_NOPTS_VALUE,
0);
// 如果經(jīng)過parser 處理返回的內(nèi)容大于0, 那么就是解碼成功
CHECK_IF_ERROR_BUF_END(inParserRet < 0, "av_parser_parse2");
inData += inParserRet;
inLen -= inParserRet;
qDebug() << "inLen : " << inLen << "inEnd : " << inEnd << " pkt->size : " << pkt->size << "inParserRet : " << inParserRet;
if (pkt->size) {
decode_ret = decode(codecCtx, frame, pkt, outFile);
CHECK_IF_ERROR_BUF_END( (decode_ret != AVERROR(EAGAIN) && decode_ret != AVERROR_EOF && decode_ret < 0), "decode");
}
// 如果到了文件尾部
if (inEnd) {
break;
}
}
qDebug() << " " ;
qDebug() << "下一次讀取" ;
} while (!inEnd);
// 沖刷最后一次緩沖區(qū)
decode_ret = decode(codecCtx, frame, nullptr, outFile);
qDebug() << "H264DecodeThread Last Decode " << decode_ret;
CHECK_IF_ERROR_BUF_END(decode_ret < 0, "decode");
end:
// 關(guān)閉文件
inFile.close();
outFile.close();
// 釋放資源
av_frame_free(&frame);
av_packet_free(&pkt);
avcodec_free_context(&codecCtx);
av_parser_close(codecParserCtx);
qDebug() << "H264DecodeThread end ";
}
關(guān)于 Win h264編碼
下載地址 :http://trace.eas.asu.edu/yuv/index.html
視頻內(nèi)容 : Big Buck Bunny
像素格式 :yuv420p
分辨率 :352X288
幀率 :24
文件大小 : 2.02 GB (2,176,796,160 字節(jié))
命令行播放 :ffplay -video_size 352X288 -pixel_format yuv420p -framerate 24 .\BigBuckBunny_CIF_24fps_h264_out.yuv
h264編碼后
BigBuckBunny_CIF_24fps_h264.h264
文件大小 :18.4 MB (19,313,821 字節(jié))
h264解碼后 :
BigBuckBunny_CIF_24fps_h264_out.yuv
文件大小 :2.21 GB (2,374,686,720 字節(jié))

命令行播放效果 = 花屏
ffplay -video_size 352X288 -pixel_format yuv420p -framerate 24 .\BigBuckBunny_CIF_24fps_h264_out.yuv

這里遇到一個(gè)問題是, 通過win的h264解碼后, 得到的linesize居然是
frame->linesize[0] 384
frame->linesize[1] 192
frame->linesize[2] 192
frame->linesize[3] 0

00->15 總共22行有數(shù)據(jù) , 一行16個(gè)字節(jié)
22 * 16 = 352 ,
下面多出了兩行全為0的空白數(shù)據(jù)
24 * 16 = 384,
這里就很神奇了,分辨率 :352X288 YUV420p對應(yīng)的應(yīng)該是
//yuv420p yyyy yyyy uu vv
//一幀yuv420p 352 * 288 * 1.5 = 152064
// y分量 :152064 * (8/12) = 152064 * 0.6666 = 101376
// u分量 :152064 * (2/12) = 152064 * 0.1666 = 25344
// v分量 :152064 * (2/12) = 152064 * 0.1666 = 25344
// 字節(jié)流中存儲樣式 :
// y1y2y3.....y101376 u1u2u3......u25344 v1v2v3......v25344
一行有 352個(gè) y, 總共有288行 352 * 288 = 101,376
一行有 176個(gè) u, 總共有144行 176 * 144 = 25,344
一行有 176個(gè) v, 總共有144行 176 * 144 = 25,344
按理應(yīng)該是
// 寫入Y平面
outFile.write((char *) frame->data[0], 101376);
// 寫入U(xiǎn)平面
outFile.write((char *) frame->data[1], 25344);
// 寫入V平面
outFile.write((char *) frame->data[2], 25344);
找了半天的代碼邏輯也沒發(fā)現(xiàn)異常, 從源碼上找

ret = av_image_fill_linesizes(linesize, avctx->pix_fmt, w);
if (ret < 0)
goto fail;
w += w & ~(w - 1);
這里對w做了一次運(yùn)算, 不知道為什么,可能是因?yàn)閮?nèi)存對齊的關(guān)系?或者是其他的關(guān)系? 也有可能是因?yàn)橐曨l的編解碼 跟 錄制視頻時(shí)候, 需要固定的分辨率搭配像素格式一樣。 但是這里確實(shí)是一個(gè)坑, 不能隨便拿一個(gè)視頻就直接進(jìn)行h264編碼
H264編解碼 , 可能對分辨率的規(guī)格有做了什么限制,于是乎做了一個(gè)大膽的猜想
把352X288 YUV420p做一次音視頻-像素格式轉(zhuǎn)換, 轉(zhuǎn)換為1280*720 YUV420p
- 原始YUV :
BigBuckBunny_CIF_24fps.yuv - 像素格式轉(zhuǎn)換 :
BigBuckBunny_CIF_24fps2.yuv - h264編碼 :
BigBuckBunny_CIF_24fps2.h264 - h264解碼 :
BigBuckBunny_CIF_24fps2_h264_out.yuv
得到的linesize數(shù)據(jù)
frame->linesize[0] 1280
frame->linesize[1] 640
frame->linesize[2] 640
frame->linesize[3] 0
ctx->width 1280
ctx->height 720

命令行播放 :
ffplay -video_size 1280X720 -pixel_format yuv420p -framerate 24 .\BigBuckBunny_CIF_24fps2_h264_out.yuv

image.png