视频编码流程
之前了解了如何解码多媒体文件中的视频流,并将解码后的图像进行显示,接下来我们学习如何对视频流进行编码,从 图片 → h.264 和 图片 → MP4 两个案例中具体了解视频的编码流程
首先我们了解视频编码的流程,编码流程与解码流程类似,将解码器替换为了编码器,在细节上有点差异
图像 → h.264
本案例中我们将多张图像转成h.264文件,每张图像显示1s,最终实现代码如下:
为了方便,选择的图像均为 600x900 的 jpg 图像,同时可以使用之前编写的视频解码验证h264文件正确性
之后案例中导入的头文件都一致,后续的代码中就不多次写出了
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;
//刷新解码缓冲区,包数据的处理方式一致
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;
//需要编码的视频宽高、每幅图像所占帧数
int w = 600, h = 900, perFrameCnt = 25;
do {
//输出文件名
const char* outFile = "result.h264";
//----------------- 打开输出文件 -------------------
//创建输出结构上下文 AVFormatContext,会根据文件后缀创建相应的初始化参数
if (avformat_alloc_output_context2(&fmtCtx, NULL, NULL, outFile) < 0) {
cout << "Cannot alloc output file context" << endl;
break;
}
//打开文件
if (avio_open(&fmtCtx->pb, outFile, AVIO_FLAG_READ_WRITE) < 0) {
cout << "output file open failed" << endl;
break;
}
//----------------- 查找编码器 -------------------
//查找codec有三种方法
/*1.AVFormatContext的oformat中存放了对应的编码器类型
AVOutputFormat* outFmt = fmtCtx->oformat;
codec = avcodec_find_encoder(outFmt->video_codec);
*/
/*2.根据编码器名称去查找
codec = avcodec_find_encoder_by_name("libx264");
*/
//3.根据编码器ID查找
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 context" << endl;
break;
}
//----------------- 创建视频流,并设置参数 -------------------
vStream = avformat_new_stream(fmtCtx, codec);
if (vStream == NULL) {
cout << "failed create new video stream" << endl;
break;
}
//设置时间基,25为分母,1为分子,表示以1/25秒时间间隔播放一帧图像
vStream->time_base = AVRational{ 1,25 };
/*两种设置方法等价
vStream->time_base.den = 25;
vStream->time_base.num = 1;
*/
//设置编码所需的参数
AVCodecParameters* param = fmtCtx->streams[vStream->index]->codecpar;
param->codec_type = AVMEDIA_TYPE_VIDEO;
param->width = w;
param->height = h;
//----------------- 将参数传给解码器上下文 -------------------
avcodec_parameters_to_context(codecCtx, param);
//视频帧类型,使用YUV420P格式
codecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
codecCtx->time_base = AVRational{ 1,25 };
codecCtx->bit_rate = 400000;
//gop表示多少个帧中存在一个关键帧
codecCtx->gop_size = 12;
//H264-设置量化步长范围
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;
//----------------- 打开解码器 -------------------
if (avcodec_open2(codecCtx, codec, NULL) < 0) {
cout << "Open encoder failed" << endl;
break;
}
av_dump_format(fmtCtx, 0, outFile, 1);
//设置视频帧参数
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);
//设置BGR数据转换为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);
//写入文件头信息
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读取图像的数据,当然FFmpeg也可以读取图像数据,会略微麻烦一些之后会举例
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数据填充至图像帧 -------------------
memcpy(pictureBuf, img.data, size);
//进行图像格式转换
sws_scale(imgCtx,
rgbFrame->data,
rgbFrame->linesize,
0,
codecCtx->height,
yuvFrame->data,
yuvFrame->linesize);
for (int j = 0; j < perFrameCnt; j++) {
//设置 pts 值,用于度量解码后视频帧位置
yuvFrame->pts = i * perFrameCnt + j;
//解码时为 avcodec_send_packet ,编码时为 avcodec_send_frame
if (avcodec_send_frame(codecCtx, yuvFrame) >= 0) {
//解码时为 avcodec_receive_frame ,编码时为 avcodec_receive_packet
while (avcodec_receive_packet(codecCtx, pkt) >= 0) {
cout << "encoder success:" << pkt->size << endl;
pkt->stream_index = vStream->index;
//将解码上下文中的时间基同等转换为流中的时间基
av_packet_rescale_ts(pkt, codecCtx->time_base, vStream->time_base);
//pos为-1表示未知,编码器编码时进行设置
pkt->pos = -1;
//将包数据写入文件中
ret = av_interleaved_write_frame(fmtCtx, pkt);
if (ret < 0) {
cout << "error is:" << ret << endl;
}
}
}
}
}
//刷新解码缓冲区
ret = flush_encoder(fmtCtx, codecCtx, vStream->index);
if (ret < 0) {
printf("flushing encoder failed!\n");
break;
}
//向文件中写入文件尾部标识,并释放该文件
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);
}
}
这样我们就完成了视频的编码,将图像转换为了h.264文件,我们可以通过之前编写的视频解码+OpenCV播放该文件
产生的h264文件播放效果:
代码解析
avformat_alloc_output_context2
avformat_alloc_output_context2
与avformat_alloc_context
类似都是创建AVFormatContext
结构体,只不过avformat_alloc_output_context2
是专门创建用于输出的AVFormatContext
结构体
使用完后,释放资源需使用avformat_free_context
函数
int avformat_alloc_output_context2(AVFormatContext **ctx, AVOutputFormat *oformat,
const char *format_name, const char *filename);
参数:
- AVFormatContext **ctx:
AVFormatContext
结构体指针的指针,用于赋值。如果失败则设置为NULL - AVOutputFormat *oformat:输出格式,若为NULL,则会通过 format_name 去寻找对应格式
- const char *format_name:输出格式名称,若为NULL,则会通过 filename 文件后缀去寻找对应格式
- const char *filename:输出文件名
return:
返回数值 ≥ 0时代表成功,失败时会返回一个负值
avio_open
avio_open
函数会创建并初始化AVIOContext
,用于访问 url 指定的资源
使用完后,释放资源需使用avio_close
函数
int avio_open(AVIOContext **s, const char *url, int flags);
参数:
- AVIOContext **s:
AVIOContext
结构体指针的指针,用于赋值。如果失败则设置为NULL - const char *url:访问文件 url 路径
- int flags:对文件的访问标志,如:只读、只写、读写等
return:
返回数值 ≥ 0时代表成功,失败时会返回一个负值
avformat_new_stream
avformat_new_stream
函数会向媒体文件添加新的流,需要在avformat_write_header
之前调用
AVStream *avformat_new_stream(AVFormatContext *s, const AVCodec *c);
参数:
- AVFormatContext *s:媒体文件对应的结构上下文
- const AVCodec *c:如果不为NULL,则之后对应
AVStream
的AVCodecContext
默认初始化时使用该解码器
return:
成功返回AVStream
指针,失败返回NULL
avformat_write_header
avformat_write_header
函数向文件中输入对应多媒体格式的文件头信息,比如:流相关信息
av_warn_unused_result
int avformat_write_header(AVFormatContext *s, AVDictionary **options);
参数:
- AVFormatContext *s:媒体文件对应的结构上下文
- AVDictionary **options:指定各种参数,一般填NULL即可
return:
AVSTREAM_INIT_IN_WRITE_HEADER (0)表示成功,如果编码器还没有在 avformat_init中完全初始化,则AVSTREAM_INIT_IN_INIT_OUTPUT (1)表示成功,AVERROR为负值表示失败
avcodec_send_frame与avcodec_receive_packet
这两个函数与之前在解码流程中的avcodec_send_packet
、avcodec_receive_frame
功能一致,只是发送和接收的类型发生了变化,这里就不多加阐述
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
函数将解码上下文中的时间基同等转换为流中的时间基,会根据参数对AVPacket
的pts、dts、duration等属性进行赋值
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
}
参数:
- AVPacket *pkt:将在其上执行转换的包
- AVRational tb_src:源时间基
- AVRational tb_dst:目标时间基
av_interleaved_write_frame
av_interleaved_write_frame
函数将数据包写入输出媒体文件,确保交错正确。确保包按照dts递增的顺序正确交错
该函数无论成功还是失败,其内部都会调用av_packet_unref
解引用
int av_interleaved_write_frame(AVFormatContext *s, AVPacket *pkt);
参数:
- AVFormatContext *s:媒体文件对应的结构上下文
- AVPacket *pkt:包含要写入数据的数据包
return:
返回 0 表示成功,失败则会返回一个负数
av_write_trailer
av_write_trailer
函数写入文件结束符,并关闭文件
int av_write_trailer(AVFormatContext *s);
参数:
- AVFormatContext *s:媒体文件对应的结构上下文
return:
返回 0 表示成功,失败则会返回一个负数
FFmpeg读取图像数据
FFmpeg中将 jpg图像文件视为只有一帧数据的视频流,其编码方式为AV_CODEC_ID_MJPEG,我们同样可以使用解码的流程对其进行操作
FFmpeg对JPG文件的封装支持模式匹配,即如果想要将多张图片写入到多张jpg中只需要文件名包含百分号即可,例如 p%d.jpg,那么在每一次调用
av_read_frame
函数就会读取一个jpg文件数据
实际上代码和解码流程几乎一摸一样,甚至可以将视频解码流程中的
filePath
修改为图片路径就可以做到,只是考虑到 jpg图像格式差异,将SwsContext
的创建放在了avcodec_receive_frame
后为了简化代码,下面代码省略错误处理。完整版请参考视频解码流程中的具体代码
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();
//如果想要根据图片的宽高更改Frame大小,请将下列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进行显示
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) {
//打印结果 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);
//此时buffer就是BGR数据
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);
}
参考资料
https://www.jianshu.com/p/fe84413a140f
https://blog.csdn.net/qq_42139383/article/details/118334630
图像 → mp4
因为 FFmpeg 内部封装了各种格式的编解码方式,使得我们可以很方便使用,不需要管底层代码逻辑。也正是这种封装性,使得我们的代码可以有很强的复用性,我们只需要给函数添加一个const char* filePath
参数就可以完成 图像→ mp4/h264 的转换
如果你熟悉 图像→ h.264 的流程,你会发现几乎没有差异。为了代码看起来比较简洁,只会在和 图像→ h.264 有差异的地方标记注释,如果想看代码具体的功能可以参考 图像→ 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;
// 某些封装格式必须要设置该标志,否则会造成封装后文件中信息的缺失,如: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设置的参数传给param,用于写入头文件信息
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;
//将解码的流程抽离成一个方法
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
之间严格来说只有三处地方是不同的,而且如果在rgb2mp4
方法中将filePath
修改为"result2.h264"也是可以进行转换的
经过测试可以在只修改
filePath
的情况下生成.avi
、.flv
格式文件