視頻編碼流程
之前了解了如何解碼多媒體文件中的視頻流,并將解碼后的圖像進(jìn)行顯示,接下來(lái)我們學(xué)習(xí)如何對(duì)視頻流進(jìn)行編碼,從 圖片 → h.264 和 圖片 → MP4 兩個(gè)案例中具體了解視頻的編碼流程
首先我們了解視頻編碼的流程,編碼流程與解碼流程類(lèi)似,將解碼器替換為了編碼器,在細(xì)節(jié)上有點(diǎn)差異

圖像 → h.264
本案例中我們將多張圖像轉(zhuǎn)成h.264文件,每張圖像顯示1s,最終實(shí)現(xiàn)代碼如下:
為了方便,選擇的圖像均為 600x900 的 jpg 圖像,同時(shí)可以使用之前編寫(xiě)的視頻解碼驗(yàn)證h264文件正確性
之后案例中導(dǎo)入的頭文件都一致,后續(xù)的代碼中就不多次寫(xiě)出了
extern"C" {
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavutil/imgutils.h"
}
#include <iostream>
using namespace std;
#include <opencv2/opencv.hpp>
using namespace cv;
//刷新解碼緩沖區(qū),包數(shù)據(jù)的處理方式一致
int flush_encoder(AVFormatContext* fmtCtx, AVCodecContext* codecCtx, int vStreamIndex) {
int ret;
AVPacket* pkt = av_packet_alloc();
pkt->data = NULL;
pkt->size = 0;
if (!(codecCtx->codec->capabilities & AV_CODEC_CAP_DELAY)) {
av_packet_free(&pkt);
return 0;
}
cout << "Flushing stream " << vStreamIndex << " encoder" << endl;
if ((ret = avcodec_send_frame(codecCtx, 0)) >= 0) {
while (avcodec_receive_packet(codecCtx, pkt) >= 0) {
cout << "encoder success:" << pkt->size << endl;
pkt->stream_index = vStreamIndex;
av_packet_rescale_ts(pkt, codecCtx->time_base,
fmtCtx->streams[vStreamIndex]->time_base);
ret = av_interleaved_write_frame(fmtCtx, pkt);
if (ret < 0) {
break;
}
}
}
av_packet_free(&pkt);
return ret;
}
void rgb2h264() {
int ret = -1;
//聲明所需的變量名
AVFormatContext* fmtCtx = NULL;
AVCodecContext* codecCtx = NULL;
AVStream* vStream = NULL;
AVCodec* codec = NULL;
AVPacket* pkt = av_packet_alloc();
AVFrame* rgbFrame = NULL;
AVFrame* yuvFrame = NULL;
//需要編碼的視頻寬高、每幅圖像所占幀數(shù)
int w = 600, h = 900, perFrameCnt = 25;
do {
//輸出文件名
const char* outFile = "result.h264";
//----------------- 打開(kāi)輸出文件 -------------------
//創(chuàng)建輸出結(jié)構(gòu)上下文 AVFormatContext,會(huì)根據(jù)文件后綴創(chuàng)建相應(yīng)的初始化參數(shù)
if (avformat_alloc_output_context2(&fmtCtx, NULL, NULL, outFile) < 0) {
cout << "Cannot alloc output file context" << endl;
break;
}
//打開(kāi)文件
if (avio_open(&fmtCtx->pb, outFile, AVIO_FLAG_READ_WRITE) < 0) {
cout << "output file open failed" << endl;
break;
}
//----------------- 查找編碼器 -------------------
//查找codec有三種方法
/*1.AVFormatContext的oformat中存放了對(duì)應(yīng)的編碼器類(lèi)型
AVOutputFormat* outFmt = fmtCtx->oformat;
codec = avcodec_find_encoder(outFmt->video_codec);
*/
/*2.根據(jù)編碼器名稱(chēng)去查找
codec = avcodec_find_encoder_by_name("libx264");
*/
//3.根據(jù)編碼器ID查找
codec = avcodec_find_encoder(AV_CODEC_ID_H264);
if (codec == NULL) {
cout << "Cannot find any endcoder" << endl;
break;
}
//----------------- 申請(qǐng)編碼器上下文結(jié)構(gòu)體 -------------------
codecCtx = avcodec_alloc_context3(codec);
if (codecCtx == NULL) {
cout << "Cannot alloc context" << endl;
break;
}
//----------------- 創(chuàng)建視頻流,并設(shè)置參數(shù) -------------------
vStream = avformat_new_stream(fmtCtx, codec);
if (vStream == NULL) {
cout << "failed create new video stream" << endl;
break;
}
//設(shè)置時(shí)間基,25為分母,1為分子,表示以1/25秒時(shí)間間隔播放一幀圖像
vStream->time_base = AVRational{ 1,25 };
/*兩種設(shè)置方法等價(jià)
vStream->time_base.den = 25;
vStream->time_base.num = 1;
*/
//設(shè)置編碼所需的參數(shù)
AVCodecParameters* param = fmtCtx->streams[vStream->index]->codecpar;
param->codec_type = AVMEDIA_TYPE_VIDEO;
param->width = w;
param->height = h;
//----------------- 將參數(shù)傳給解碼器上下文 -------------------
avcodec_parameters_to_context(codecCtx, param);
//視頻幀類(lèi)型,使用YUV420P格式
codecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
codecCtx->time_base = AVRational{ 1,25 };
codecCtx->bit_rate = 400000;
//gop表示多少個(gè)幀中存在一個(gè)關(guān)鍵幀
codecCtx->gop_size = 12;
//H264-設(shè)置量化步長(zhǎng)范圍
if (codecCtx->codec_id == AV_CODEC_ID_H264) {
codecCtx->qmin = 10;
codecCtx->qmax = 51;
//(0~1.0),0=>CBR 1->恒定QP
codecCtx->qcompress = (float)0.6;
}
if (codecCtx->codec_id == AV_CODEC_ID_MPEG2VIDEO)
codecCtx->max_b_frames = 2;
if (codecCtx->codec_id == AV_CODEC_ID_MPEG1VIDEO)
codecCtx->mb_decision = 2;
//----------------- 打開(kāi)解碼器 -------------------
if (avcodec_open2(codecCtx, codec, NULL) < 0) {
cout << "Open encoder failed" << endl;
break;
}
av_dump_format(fmtCtx, 0, outFile, 1);
//設(shè)置視頻幀參數(shù)
rgbFrame = av_frame_alloc();
yuvFrame = av_frame_alloc();
rgbFrame->width = codecCtx->width;
yuvFrame->width = codecCtx->width;
rgbFrame->height = codecCtx->height;
yuvFrame->height = codecCtx->height;
rgbFrame->format = AV_PIX_FMT_BGR24;
yuvFrame->format = codecCtx->pix_fmt;
int size = av_image_get_buffer_size(AV_PIX_FMT_BGR24, codecCtx->width, codecCtx->height, 1);
int yuvSize = av_image_get_buffer_size(codecCtx->pix_fmt, codecCtx->width, codecCtx->height, 1);
uint8_t* pictureBuf = (uint8_t*)av_malloc(size);
uint8_t* yuvBuf = (uint8_t*)av_malloc(yuvSize);
av_image_fill_arrays(rgbFrame->data, rgbFrame->linesize,
pictureBuf, AV_PIX_FMT_BGR24,
codecCtx->width, codecCtx->height, 1);
av_image_fill_arrays(yuvFrame->data, yuvFrame->linesize,
yuvBuf, codecCtx->pix_fmt,
codecCtx->width, codecCtx->height, 1);
//設(shè)置BGR數(shù)據(jù)轉(zhuǎn)換為YUV的SwsContext
struct SwsContext* imgCtx = sws_getContext(
codecCtx->width, codecCtx->height, AV_PIX_FMT_BGR24,
codecCtx->width, codecCtx->height, codecCtx->pix_fmt,
SWS_BILINEAR, NULL, NULL, NULL);
//寫(xiě)入文件頭信息
ret = avformat_write_header(fmtCtx, NULL);
if (ret != AVSTREAM_INIT_IN_WRITE_HEADER) {
cout << "Write file header fail" << endl;
break;
}
av_new_packet(pkt, size);
//這里使用OpenCV讀取圖像的數(shù)據(jù),當(dāng)然FFmpeg也可以讀取圖像數(shù)據(jù),會(huì)略微麻煩一些之后會(huì)舉例
Mat img;
char imgPath[] = "img/p0.jpg";
for (int i = 0; i < 6; i++) {
imgPath[5] = '0' + i;
img = imread(imgPath);
//imshow("img", img);
//waitKey(0);
//----------------- BGR數(shù)據(jù)填充至圖像幀 -------------------
memcpy(pictureBuf, img.data, size);
//進(jìn)行圖像格式轉(zhuǎn)換
sws_scale(imgCtx,
rgbFrame->data,
rgbFrame->linesize,
0,
codecCtx->height,
yuvFrame->data,
yuvFrame->linesize);
for (int j = 0; j < perFrameCnt; j++) {
//設(shè)置 pts 值,用于度量解碼后視頻幀位置
yuvFrame->pts = i * perFrameCnt + j;
//解碼時(shí)為 avcodec_send_packet ,編碼時(shí)為 avcodec_send_frame
if (avcodec_send_frame(codecCtx, yuvFrame) >= 0) {
//解碼時(shí)為 avcodec_receive_frame ,編碼時(shí)為 avcodec_receive_packet
while (avcodec_receive_packet(codecCtx, pkt) >= 0) {
cout << "encoder success:" << pkt->size << endl;
pkt->stream_index = vStream->index;
//將解碼上下文中的時(shí)間基同等轉(zhuǎn)換為流中的時(shí)間基
av_packet_rescale_ts(pkt, codecCtx->time_base, vStream->time_base);
//pos為-1表示未知,編碼器編碼時(shí)進(jìn)行設(shè)置
pkt->pos = -1;
//將包數(shù)據(jù)寫(xiě)入文件中
ret = av_interleaved_write_frame(fmtCtx, pkt);
if (ret < 0) {
cout << "error is:" << ret << endl;
}
}
}
}
}
//刷新解碼緩沖區(qū)
ret = flush_encoder(fmtCtx, codecCtx, vStream->index);
if (ret < 0) {
printf("flushing encoder failed!\n");
break;
}
//向文件中寫(xiě)入文件尾部標(biāo)識(shí),并釋放該文件
av_write_trailer(fmtCtx);
av_free(pictureBuf);
av_free(yuvBuf);
sws_freeContext(imgCtx);
} while (0);
//釋放資源
av_packet_free(&pkt);
avcodec_close(codecCtx);
if (rgbFrame)
av_frame_free(&rgbFrame);
if (yuvFrame)
av_frame_free(&yuvFrame);
if (fmtCtx) {
avio_close(fmtCtx->pb);
avformat_free_context(fmtCtx);
}
}
這樣我們就完成了視頻的編碼,將圖像轉(zhuǎn)換為了h.264文件,我們可以通過(guò)之前編寫(xiě)的視頻解碼+OpenCV播放該文件
產(chǎn)生的h264文件播放效果:

代碼解析
avformat_alloc_output_context2
avformat_alloc_output_context2與avformat_alloc_context類(lèi)似都是創(chuàng)建AVFormatContext結(jié)構(gòu)體,只不過(guò)avformat_alloc_output_context2是專(zhuān)門(mén)創(chuàng)建用于輸出的AVFormatContext結(jié)構(gòu)體
使用完后,釋放資源需使用avformat_free_context函數(shù)
int avformat_alloc_output_context2(AVFormatContext **ctx, AVOutputFormat *oformat,
const char *format_name, const char *filename);
參數(shù):
- AVFormatContext **ctx:
AVFormatContext結(jié)構(gòu)體指針的指針,用于賦值。如果失敗則設(shè)置為NULL - AVOutputFormat *oformat:輸出格式,若為NULL,則會(huì)通過(guò) format_name 去尋找對(duì)應(yīng)格式
- const char *format_name:輸出格式名稱(chēng),若為NULL,則會(huì)通過(guò) filename 文件后綴去尋找對(duì)應(yīng)格式
- const char *filename:輸出文件名
return:
返回?cái)?shù)值 ≥ 0時(shí)代表成功,失敗時(shí)會(huì)返回一個(gè)負(fù)值
avio_open
avio_open函數(shù)會(huì)創(chuàng)建并初始化AVIOContext,用于訪(fǎng)問(wèn) url 指定的資源
使用完后,釋放資源需使用avio_close函數(shù)
int avio_open(AVIOContext **s, const char *url, int flags);
參數(shù):
- AVIOContext **s:
AVIOContext結(jié)構(gòu)體指針的指針,用于賦值。如果失敗則設(shè)置為NULL - const char *url:訪(fǎng)問(wèn)文件 url 路徑
- int flags:對(duì)文件的訪(fǎng)問(wèn)標(biāo)志,如:只讀、只寫(xiě)、讀寫(xiě)等
return:
返回?cái)?shù)值 ≥ 0時(shí)代表成功,失敗時(shí)會(huì)返回一個(gè)負(fù)值
avformat_new_stream
avformat_new_stream函數(shù)會(huì)向媒體文件添加新的流,需要在avformat_write_header之前調(diào)用
AVStream *avformat_new_stream(AVFormatContext *s, const AVCodec *c);
參數(shù):
- AVFormatContext *s:媒體文件對(duì)應(yīng)的結(jié)構(gòu)上下文
- const AVCodec *c:如果不為NULL,則之后對(duì)應(yīng)
AVStream的AVCodecContext默認(rèn)初始化時(shí)使用該解碼器
return:
成功返回AVStream指針,失敗返回NULL
avformat_write_header
avformat_write_header函數(shù)向文件中輸入對(duì)應(yīng)多媒體格式的文件頭信息,比如:流相關(guān)信息
av_warn_unused_result
int avformat_write_header(AVFormatContext *s, AVDictionary **options);
參數(shù):
- AVFormatContext *s:媒體文件對(duì)應(yīng)的結(jié)構(gòu)上下文
- AVDictionary **options:指定各種參數(shù),一般填NULL即可
return:
AVSTREAM_INIT_IN_WRITE_HEADER (0)表示成功,如果編碼器還沒(méi)有在 avformat_init中完全初始化,則AVSTREAM_INIT_IN_INIT_OUTPUT (1)表示成功,AVERROR為負(fù)值表示失敗
avcodec_send_frame與avcodec_receive_packet
這兩個(gè)函數(shù)與之前在解碼流程中的avcodec_send_packet、avcodec_receive_frame功能一致,只是發(fā)送和接收的類(lèi)型發(fā)生了變化,這里就不多加闡述
int avcodec_send_frame(AVCodecContext *avctx, const AVFrame *frame);
int avcodec_receive_packet(AVCodecContext *avctx, AVPacket *avpkt);
av_packet_rescale_ts
av_packet_rescale_ts函數(shù)將解碼上下文中的時(shí)間基同等轉(zhuǎn)換為流中的時(shí)間基,會(huì)根據(jù)參數(shù)對(duì)AVPacket的pts、dts、duration等屬性進(jìn)行賦值
void av_packet_rescale_ts(AVPacket *pkt, AVRational src_tb, AVRational dst_tb){
if (pkt->pts != AV_NOPTS_VALUE)
pkt->pts = av_rescale_q(pkt->pts, src_tb, dst_tb);
if (pkt->dts != AV_NOPTS_VALUE)
pkt->dts = av_rescale_q(pkt->dts, src_tb, dst_tb);
if (pkt->duration > 0)
pkt->duration = av_rescale_q(pkt->duration, src_tb, dst_tb);
#if FF_API_CONVERGENCE_DURATION
FF_DISABLE_DEPRECATION_WARNINGS
if (pkt->convergence_duration > 0)
pkt->convergence_duration = av_rescale_q(pkt->convergence_duration, src_tb, dst_tb);
FF_ENABLE_DEPRECATION_WARNINGS
#endif
}
參數(shù):
- AVPacket *pkt:將在其上執(zhí)行轉(zhuǎn)換的包
- AVRational tb_src:源時(shí)間基
- AVRational tb_dst:目標(biāo)時(shí)間基
av_interleaved_write_frame
av_interleaved_write_frame函數(shù)將數(shù)據(jù)包寫(xiě)入輸出媒體文件,確保交錯(cuò)正確。確保包按照dts遞增的順序正確交錯(cuò)
該函數(shù)無(wú)論成功還是失敗,其內(nèi)部都會(huì)調(diào)用av_packet_unref解引用
int av_interleaved_write_frame(AVFormatContext *s, AVPacket *pkt);
參數(shù):
- AVFormatContext *s:媒體文件對(duì)應(yīng)的結(jié)構(gòu)上下文
- AVPacket *pkt:包含要寫(xiě)入數(shù)據(jù)的數(shù)據(jù)包
return:
返回 0 表示成功,失敗則會(huì)返回一個(gè)負(fù)數(shù)
av_write_trailer
av_write_trailer函數(shù)寫(xiě)入文件結(jié)束符,并關(guān)閉文件
int av_write_trailer(AVFormatContext *s);
參數(shù):
- AVFormatContext *s:媒體文件對(duì)應(yīng)的結(jié)構(gòu)上下文
return:
返回 0 表示成功,失敗則會(huì)返回一個(gè)負(fù)數(shù)
FFmpeg讀取圖像數(shù)據(jù)
FFmpeg中將 jpg圖像文件視為只有一幀數(shù)據(jù)的視頻流,其編碼方式為AV_CODEC_ID_MJPEG,我們同樣可以使用解碼的流程對(duì)其進(jìn)行操作
FFmpeg對(duì)JPG文件的封裝支持模式匹配,即如果想要將多張圖片寫(xiě)入到多張jpg中只需要文件名包含百分號(hào)即可,例如 p%d.jpg,那么在每一次調(diào)用
av_read_frame函數(shù)就會(huì)讀取一個(gè)jpg文件數(shù)據(jù)
實(shí)際上代碼和解碼流程幾乎一摸一樣,甚至可以將視頻解碼流程中的
filePath修改為圖片路徑就可以做到,只是考慮到 jpg圖像格式差異,將SwsContext的創(chuàng)建放在了avcodec_receive_frame后為了簡(jiǎn)化代碼,下面代碼省略錯(cuò)誤處理。完整版請(qǐng)參考視頻解碼流程中的具體代碼
void readJpg() {
//聲明所需的變量名
AVFormatContext* fmtCtx = NULL;
AVCodecContext* codecCtx = NULL;
AVCodec* codec = NULL;
AVPacket* pkt = av_packet_alloc();
AVFrame* jpgFrame = NULL;
AVFrame* rgbFrame = NULL;
const char* filePath = "img/p%d.jpg";
do {
fmtCtx = avformat_alloc_context();
avformat_open_input(&fmtCtx, filePath, NULL, NULL);
avformat_find_stream_info(fmtCtx, NULL);
int videoIndex = -1;
for (int i = 0; i < fmtCtx->nb_streams; i++) {
if (fmtCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
videoIndex = i;
break;
}
}
AVCodecParameters* param = fmtCtx->streams[videoIndex]->codecpar;
codec = avcodec_find_decoder(param->codec_id);
codecCtx = avcodec_alloc_context3(codec);
avcodec_parameters_to_context(codecCtx, param);
avcodec_open2(codecCtx, codec, NULL);
//記錄第一張圖片的寬高,將其余圖片縮放至第一張圖片的大小
int w = codecCtx->width, h = codecCtx->height;
jpgFrame = av_frame_alloc();
rgbFrame = av_frame_alloc();
//如果想要根據(jù)圖片的寬高更改Frame大小,請(qǐng)將下列3行代碼移至while (avcodec_receive_frame(codecCtx, jpgFrame) >= 0)中,記得釋放buffer
int size = av_image_get_buffer_size(AV_PIX_FMT_BGR24, w, h, 1);
unsigned char* buffer = (unsigned char*)av_malloc(size * sizeof(unsigned char));
av_image_fill_arrays(rgbFrame->data, rgbFrame->linesize, buffer, AV_PIX_FMT_BGR24, w, h, 1);
SwsContext* imgCtx = NULL;
//老樣子,OpenCV進(jìn)行顯示
Mat img = Mat(Size(w, h), CV_8UC3);
while (av_read_frame(fmtCtx, pkt) >= 0) {
if (pkt->stream_index == videoIndex) {
if (avcodec_send_packet(codecCtx, pkt) >= 0) {
while (avcodec_receive_frame(codecCtx, jpgFrame) >= 0) {
//打印結(jié)果 12 -> AV_PIX_FMT_YUVJ420P 或 14 -> AV_PIX_FMT_YUV444P
cout << codecCtx->pix_fmt << endl;
imgCtx = sws_getCachedContext(imgCtx,
codecCtx->width, codecCtx->height, codecCtx->pix_fmt,
codecCtx->width, codecCtx->height, AV_PIX_FMT_BGR24,
SWS_BICUBIC, NULL, NULL, NULL);
sws_scale(imgCtx,
jpgFrame->data,
jpgFrame->linesize,
0,
codecCtx->height,
rgbFrame->data,
rgbFrame->linesize);
//此時(shí)buffer就是BGR數(shù)據(jù)
img.data = buffer;
imshow("img", img);
waitKey(0);
}
}
}
av_packet_unref(pkt);
}
sws_freeContext(imgCtx);
} while (0);
avformat_close_input(&fmtCtx);
avcodec_free_context(&codecCtx);
av_packet_free(&pkt);
if (jpgFrame)
av_frame_free(&jpgFrame);
if (rgbFrame)
av_frame_free(&rgbFrame);
}
參考資料
http://www.itdecent.cn/p/fe84413a140f
https://blog.csdn.net/qq_42139383/article/details/118334630
圖像 → mp4
因?yàn)?FFmpeg 內(nèi)部封裝了各種格式的編解碼方式,使得我們可以很方便使用,不需要管底層代碼邏輯。也正是這種封裝性,使得我們的代碼可以有很強(qiáng)的復(fù)用性,我們只需要給函數(shù)添加一個(gè)const char* filePath參數(shù)就可以完成 圖像→ mp4/h264 的轉(zhuǎn)換
如果你熟悉 圖像→ h.264 的流程,你會(huì)發(fā)現(xiàn)幾乎沒(méi)有差異。為了代碼看起來(lái)比較簡(jiǎn)潔,只會(huì)在和 圖像→ h.264 有差異的地方標(biāo)記注釋,如果想看代碼具體的功能可以參考 圖像→ h.264 處的代碼,那里有完整的注釋
int rgb2mp4Encode(AVCodecContext* codecCtx, AVFrame* yuvFrame, AVPacket* pkt, AVStream* vStream, AVFormatContext* fmtCtx){
int ret = 0;
if (avcodec_send_frame(codecCtx, yuvFrame) >= 0) {
while (avcodec_receive_packet(codecCtx, pkt) >= 0) {
pkt->stream_index = vStream->index;
pkt->pos = -1;
av_packet_rescale_ts(pkt, codecCtx->time_base, vStream->time_base);
cout << "encoder success:" << pkt->size << endl;
ret = av_interleaved_write_frame(fmtCtx, pkt);
if (ret < 0) {
char errStr[256];
av_strerror(ret, errStr, 256);
cout << "error is:" << errStr << endl;
}
}
}
return ret;
}
void rgb2mp4(){
int ret;
AVFormatContext* fmtCtx = NULL;
AVCodecContext* codecCtx = NULL;
AVCodec* codec = NULL;
AVStream* vStream = NULL;
AVPacket* pkt = av_packet_alloc();
AVFrame* rgbFrame = NULL, * yuvFrame = NULL;
int w = 600, h = 900, perFrameCnt = 25;
do {
//輸出文件名 從.h264后綴改為mp4
const char* filePath = "result2.mp4";
ret = avformat_alloc_output_context2(&fmtCtx, NULL, NULL, filePath);
if (ret < 0) {
cout << "Cannot alloc output file context" << endl;
break;
}
ret = avio_open(&fmtCtx->pb, filePath, AVIO_FLAG_READ_WRITE);
if (ret < 0) {
cout << "output file open failed" << endl;
break;
}
codec = avcodec_find_encoder(AV_CODEC_ID_H264);
if (codec == NULL) {
cout << "Cannot find any endcoder" << endl;
break;
}
codecCtx = avcodec_alloc_context3(codec);
if (codecCtx == NULL) {
cout << "Cannot alloc AVCodecContext" << endl;
break;
}
vStream = avformat_new_stream(fmtCtx, codec);
if (vStream == NULL) {
cout << "failed create new video stream" << endl;
break;
}
vStream->time_base = AVRational{ 1,25 };
AVCodecParameters* param = vStream->codecpar;
param->width = w;
param->height = h;
param->codec_type = AVMEDIA_TYPE_VIDEO;
ret = avcodec_parameters_to_context(codecCtx, param);
if (ret < 0) {
cout << "Cannot copy codec para" << endl;
break;
}
codecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
codecCtx->time_base = AVRational{ 1,25 };
codecCtx->bit_rate = 400000;
codecCtx->gop_size = 12;
// 某些封裝格式必須要設(shè)置該標(biāo)志,否則會(huì)造成封裝后文件中信息的缺失,如:mp4
if (fmtCtx->oformat->flags & AVFMT_GLOBALHEADER) {
codecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
}
if (codec->id == AV_CODEC_ID_H264) {
codecCtx->qmin = 10;
codecCtx->qmax = 51;
codecCtx->qcompress = (float)0.6;
}
ret = avcodec_open2(codecCtx, codec, NULL);
if (ret < 0) {
cout << "Open encoder failed" << endl;
break;
}
//再將codecCtx設(shè)置的參數(shù)傳給param,用于寫(xiě)入頭文件信息
avcodec_parameters_from_context(param, codecCtx);
rgbFrame = av_frame_alloc();
yuvFrame = av_frame_alloc();
yuvFrame->width = w;
yuvFrame->height = h;
yuvFrame->format = codecCtx->pix_fmt;
rgbFrame->width = w;
rgbFrame->height = h;
rgbFrame->format = AV_PIX_FMT_BGR24;
int size = av_image_get_buffer_size((AVPixelFormat)rgbFrame->format, w, h, 1);
uint8_t* buffer = (uint8_t*)av_malloc(size);
ret = av_image_fill_arrays(rgbFrame->data, rgbFrame->linesize, buffer, (AVPixelFormat)rgbFrame->format, w, h, 1);
if (ret < 0) {
cout << "Cannot filled rgbFrame" << endl;
break;
}
int yuvSize = av_image_get_buffer_size((AVPixelFormat)yuvFrame->format, w, h, 1);
uint8_t* yuvBuffer = (uint8_t*)av_malloc(size);
ret = av_image_fill_arrays(yuvFrame->data, yuvFrame->linesize, yuvBuffer, (AVPixelFormat)yuvFrame->format, w, h, 1);
if (ret < 0) {
cout << "Cannot filled yuvFrame" << endl;
break;
}
SwsContext* imgCtx = sws_getContext(w, h, AV_PIX_FMT_BGR24, w, h, codecCtx->pix_fmt, 0, NULL, NULL, NULL);
ret = avformat_write_header(fmtCtx, NULL);
if (ret != AVSTREAM_INIT_IN_WRITE_HEADER) {
cout << "Write file header fail" << endl;
break;
}
av_new_packet(pkt, size);
Mat img;
char imgPath[] = "img/p0.jpg";
int index = 0;
for (int i = 0; i < 7; i++) {
imgPath[5] = '0' + i;
img = imread(imgPath);
imshow("img", img);
waitKey(0);
memcpy(buffer, img.data, size);
sws_scale(imgCtx,
rgbFrame->data,
rgbFrame->linesize,
0,
codecCtx->height,
yuvFrame->data,
yuvFrame->linesize);
for (int j = 0; j < perFrameCnt; j++) {
yuvFrame->pts = i * perFrameCnt + j;
//將解碼的流程抽離成一個(gè)方法
rgb2mp4Encode(codecCtx, yuvFrame, pkt, vStream, fmtCtx);
}
}
rgb2mp4Encode(codecCtx, NULL, pkt, vStream, fmtCtx);
av_write_trailer(fmtCtx);
av_free(buffer);
av_free(yuvBuffer);
sws_freeContext(imgCtx);
} while (0);
av_packet_free(&pkt);
if (fmtCtx)
avformat_free_context(fmtCtx);
if (codecCtx)
avcodec_free_context(&codecCtx);
if (rgbFrame)
av_frame_free(&rgbFrame);
if (yuvFrame)
av_frame_free(&yuvFrame);
}
可以看到,rgb2mp4方法與rgb2h264之間嚴(yán)格來(lái)說(shuō)只有三處地方是不同的,而且如果在rgb2mp4方法中將filePath修改為"result2.h264"也是可以進(jìn)行轉(zhuǎn)換的
經(jīng)過(guò)測(cè)試可以在只修改
filePath的情況下生成.avi、.flv格式文件