H264編碼原理和音視頻-AAC編碼原理幾乎一樣露懒, 不同的是就buffer緩沖區(qū)的處理, 編碼的事情都是通過H264編碼器去實(shí)現(xiàn)
AAC編碼的簡略邏輯 :
源文件 ==》 AVFrame ==》編碼器 ==》AVPacket ==> 輸出文件
H264編碼的簡略邏輯
源文件 ==》 AVFrame ==》編碼器 ==》AVPacket ==> 輸出文件
是的, 兩個(gè)是一樣的。不同的是褂始, YUV是以一幀一幀的數(shù)據(jù)讀取。
#include "h264encodethread.h"
#include <QDebug>
#include <QFile>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavutil/channel_layout.h>
#include <libavutil/common.h>
#include <libavutil/frame.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_YUV_FILEPATH "G:/BigBuckBunny_CIF_24fps.yuv"
#define OUT_H264_FILEPATH "G:/BigBuckBunny_CIF_24fps_h264.h264"
#define YUV_VIDEO_SIZE_WIDTH 352
#define YUV_VIDEO_SIZE_HEIGH 288
#else
#define IN_YUV_FILEPATH "/Users/liliguang/Desktop/in.yuv"
#define OUT_H264_FILEPATH "/Users/liliguang/Desktop/out.h264"
#define YUV_VIDEO_SIZE_WIDTH 352
#define YUV_VIDEO_SIZE_HEIGH 288
#endif
H264EncodeThread::H264EncodeThread(QObject *parent) : QThread(parent) {
// 當(dāng)監(jiān)聽到線程結(jié)束時(shí)(finished)描函,就調(diào)用deleteLater回收內(nèi)存
connect(this, &H264EncodeThread::finished,
this, &H264EncodeThread::deleteLater);
}
H264EncodeThread::~H264EncodeThread() {
// 斷開所有的連接
disconnect();
// 內(nèi)存回收之前崎苗,正常結(jié)束線程
requestInterruption();
// 安全退出
quit();
wait();
qDebug() << this << "析構(gòu)(內(nèi)存被回收)";
}
/* check that a given pix_format is supported by the encoder */
static int check_pixel_fmt(const AVCodec *codec,
enum AVPixelFormat pix_fmt) {
const enum AVPixelFormat *p = codec->pix_fmts;
while (*p != AV_PIX_FMT_NONE) {
if (*p == pix_fmt) {
return 1;
}
p++;
}
return 0;
}
// H264編碼
// 返回負(fù)數(shù):中途出現(xiàn)了錯(cuò)誤
// 返回0:編碼操作正常完成
static int encode(AVCodecContext *ctx,
AVFrame *frame,
AVPacket *pkt,
QFile &outFile) {
// 發(fā)送數(shù)據(jù)到編碼器
int ret = avcodec_send_frame(ctx, frame);
if (ret < 0) {
ERROR_BUF(ret);
qDebug() << "avcodec_send_frame error" << errbuf;
return ret;
}
// 不斷從編碼器中取出編碼后的數(shù)據(jù)
// while (ret >= 0)
while (true) {
ret = avcodec_receive_packet(ctx, pkt);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
// output is not available in the current state - user must try to send input
// 繼續(xù)讀取數(shù)據(jù)到frame,然后送到編碼器
return 0;
} else if (ret < 0) { // 其他錯(cuò)誤
return ret;
}
// 成功從編碼器拿到編碼后的數(shù)據(jù)
// 將編碼后的數(shù)據(jù)寫入文件
outFile.write((char *) pkt->data, pkt->size);
// 釋放pkt內(nèi)部的資源
av_packet_unref(pkt);
}
}
void H264EncodeThread::run() {
// H264編碼的思路和Acc編碼的思路差不多
// 讀取文件內(nèi)容 -> buffer緩沖區(qū) -> 一幀數(shù)據(jù) -> 核心函數(shù)編碼 -> 輸出緩沖區(qū) -> 輸出文件
qDebug() << "H264EncodeThread run ";
// 輸入輸出文件
const char *infilename;
const char *outfilename;
// 編碼器
const AVCodec *codec;
// 編碼器上下文
AVCodecContext *codecCtx = nullptr;
// 源文件數(shù)據(jù)源存儲(chǔ)結(jié)構(gòu)指針
AVFrame *frame = nullptr;
// 編碼文件數(shù)據(jù)源存儲(chǔ)結(jié)構(gòu)指針
AVPacket *pkt = nullptr;
int check_pixel_fmt_Ret;
int avcodec_open2_Ret;
int av_image_alloc_ret;
int infileOpen_Ret;
int outfileOpen_Ret;
int readFile_Ret;
int encode_ret;
int ptsIndex = 0;
int imageSize = YUV_VIDEO_SIZE_WIDTH * YUV_VIDEO_SIZE_HEIGH * 1.5;
infilename = IN_YUV_FILEPATH;
outfilename = OUT_H264_FILEPATH;
QFile inFile(infilename);
QFile outFile(outfilename);
// 編碼器
codec = avcodec_find_encoder_by_name("libx264"); // 用查找編碼器的名稱的方式舀寓。 默認(rèn)的可能找到的不一樣
CHECK_IF_ERROR_BUF_END(!codec, "avcodec_find_encoder");
// 創(chuàng)建編碼器上下文
codecCtx = avcodec_alloc_context3(codec);
CHECK_IF_ERROR_BUF_END(!codecCtx, "avcodec_alloc_context3");
// 設(shè)置編碼器上下文對(duì)應(yīng)信息
codecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
codecCtx->width = YUV_VIDEO_SIZE_WIDTH;
codecCtx->height = YUV_VIDEO_SIZE_HEIGH;
// 這里要計(jì)算24 pfs
// fps = codecCtx->time_base.den / codecCtx->time_base.num
// 24 = 24 / 1
codecCtx->time_base.num = 1; //分子
codecCtx->time_base.den = 24; //分母
// 檢查編碼器支持的樣本格式
check_pixel_fmt_Ret = check_pixel_fmt(codec, codecCtx->pix_fmt);
CHECK_IF_ERROR_BUF_END(!check_pixel_fmt_Ret, "check_sample_fmt");
// 打開編碼器
avcodec_open2_Ret = avcodec_open2(codecCtx, codec, nullptr);
CHECK_IF_ERROR_BUF_END(avcodec_open2_Ret, "avcodec_open2");
// 打開源文件
infileOpen_Ret = !inFile.open(QFile::ReadOnly);
CHECK_IF_ERROR_BUF_END(infileOpen_Ret, "sourceFile.open");
// 打開源文件
outfileOpen_Ret = !outFile.open(QFile::WriteOnly);
CHECK_IF_ERROR_BUF_END(outfileOpen_Ret, "sourceFile.outFile");
// 創(chuàng)建輸出Packet
pkt = av_packet_alloc();
CHECK_IF_ERROR_BUF_END(!pkt, "av_packet_alloc");
// 創(chuàng)建AVFrame結(jié)構(gòu)體本身
frame = av_frame_alloc();
CHECK_IF_ERROR_BUF_END(!frame, "av_frame_alloc");
// 為音頻或視頻數(shù)據(jù)分配新的緩沖區(qū)胆数。
// 在調(diào)用此函數(shù)之前,必須在框架上設(shè)置以下字段:
// - 格式(視頻的像素格式互墓,音頻的樣本格式)
// - 視頻的寬度和高度
// 設(shè)置frame必要信息
frame->format = codecCtx->pix_fmt;//像素格式
frame->width = codecCtx->width;//分辨率寬
frame->height = codecCtx->height; //分辨率高
// 創(chuàng)建一幀的buffer緩沖區(qū)
av_image_alloc_ret = av_image_alloc(frame->data, frame->linesize, frame->width, frame->height, (AVPixelFormat)frame->format, 16);
CHECK_IF_ERROR_BUF_END(av_image_alloc_ret < 0, "av_image_alloc");
// 編碼
// 源文件 ==> (AVFrame)輸入緩沖區(qū) ==> 編碼器 ==> (AVPacket)輸出緩沖區(qū) ==> 輸出文件
// 這里應(yīng)該讀取一幀的大小
while( (readFile_Ret = inFile.read((char *)frame->data[0], imageSize )) > 0 ) {
frame->pts = ptsIndex++;
// 編碼
encode_ret = encode(codecCtx, frame, pkt, outFile);
CHECK_IF_ERROR_BUF_END(encode_ret < 0,
}
// 在讀取最后一次必尼, 沖刷緩沖區(qū)
encode(codecCtx, nullptr, pkt, outFile);
end:
// 關(guān)閉文件
inFile.close();
outFile.close();
// 釋放資源
av_frame_free(&frame);
av_packet_free(&pkt);
avcodec_free_context(&codecCtx);
qDebug() << "AACEncodeThread end ";
}
注意點(diǎn), 創(chuàng)建buffer緩沖區(qū)篡撵, 和音頻的有點(diǎn)不一樣胰伍,使用av_image_alloc
av_image_alloc_ret = av_image_alloc(frame->data, frame->linesize, frame->width, frame->height, (AVPixelFormat)frame->format, 16);
分別用命令行和程序運(yùn)行進(jìn)行H264編碼
ffmpeg -video_size 352x288 -pixel_format yuv420p -framerate 24 -i .\BigBuckBunny_CIF_24fps.yuv -c:v libx264 .\ffmpeg_h264_out.h264
# -c:v libx264是指定使用libx264作為編碼器
大小一致齿诞, 證明代碼思路是沒有問題, 現(xiàn)在再接著用ffplay播放一下