前言
本篇文章的需求为:
1、输入视频文件的路径。
2、输入要输出的视频的分辨率。
3、将视频文件转为指定分辨率的视频。
当前进展:目前仅将数据解码出,且使用sws_scale 更改为其他分辨率的AVFrame,但将该帧编码成视频的方式,目前还未成功。后续完善。
正文
一、将视频的每一帧重编辑为指定的格式及宽高
code
void CFFmpegDemoTest::on_btnChangePix_clicked()
{
qDebug() << "--> CFFmpegDemoTest::on_btnChangePix_clicked Start";
int ret = 0; int err;
int read_end = 0;
int frame_num = 0;
AVPacket *pkt = av_packet_alloc();
AVFrame *frame = av_frame_alloc();
//打开输入文件
char filename[] = "juren-30s.mp4";
struct SwsContext* img_convert_ctx = NULL;
AVFormatContext *fmt_ctx = avformat_alloc_context();
if (!fmt_ctx) {
qDebug() << "fmt_ctx error code %d " << AVERROR(ENOMEM);
return;
}
if((err = avformat_open_input(&fmt_ctx, filename,NULL,NULL)) < 0){
qDebug() << "can not open file:"<<err;
return;
}
//打开解码器
AVCodecContext *avctx = avcodec_alloc_context3(NULL);
ret = avcodec_parameters_to_context(avctx, fmt_ctx->streams[0]->codecpar);
if (ret < 0)
{
qDebug() << " avcodec_parameters_to_contexterror code:" << ret;
return;
}
AVCodec *codec = avcodec_find_decoder(avctx->codec_id);
if ((ret = avcodec_open2(avctx, codec, NULL)) < 0) {
qDebug() << "open codec faile :"<<ret;
return;
}
int sws_flags = SWS_BICUBIC;
int result_format = AV_PIX_FMT_BGRA;
int result_width = 300;
int result_height = 300;
//确定内存的大小
int buf_size = av_image_get_buffer_size(static_cast<AVPixelFormat>(result_format), result_width, result_height, 1);
for(;;)
{
if( 1 == read_end ){
break;
}
ret = av_read_frame(fmt_ctx, pkt);
//跳过不处理音频包
if( 1 == pkt->stream_index)
{
av_packet_unref(pkt);
continue;
}
if ( AVERROR_EOF == ret)
{
//读取完文件,这时候 pkt 的 data 跟 size 应该是 null
avcodec_send_packet(avctx, NULL);
}
else
{
if( 0 != ret)
{
qDebug() << "read error code:" << ret;
return;
}else{
retry:
if (avcodec_send_packet(avctx, pkt) == AVERROR(EAGAIN)) {
qDebug() << "Receive_frame and send_packet both returned EAGAIN, which is an API violation";
//这里可以考虑休眠 0.1 秒,返回 EAGAIN 通常是 ffmpeg 的内部 api 有bug
goto retry;
}
//释放 pkt 里面的编码数据
av_packet_unref(pkt);
}
}
//循环不断从解码器读数据,直到没有数据可读。
for(;;){
//读取 AVFrame
ret = avcodec_receive_frame(avctx, frame);
/* 释放 frame 里面的YUV数据,
* 由于 avcodec_receive_frame 函数里面会调用 av_frame_unref,所以下面的代码可以注释。
* 所以我们不需要 手动 unref 这个 AVFrame
* */
//av_frame_unref(frame);
if( AVERROR(EAGAIN) == ret )
{
//提示 EAGAIN 代表 解码器 需要 更多的 AVPacket
//跳出 第一层 for,让 解码器拿到更多的 AVPacket
break;
}
else if( AVERROR_EOF == ret )
{
/* 提示 AVERROR_EOF 代表之前已经往 解码器发送了一个 data 跟 size 都是 NULL 的 AVPacket
* 发送 NULL 的 AVPacket 是提示解码器把所有的缓存帧全都刷出来。
* 通常只有在 读完输入文件才会发送 NULL 的 AVPacket,或者需要用现有的解码器解码另一个的视频流才会这么干。
* */
//跳出 第二层 for,文件已经解码完毕。
read_end = 1;
break;
}
else if( ret >= 0 )
{
if( NULL == img_convert_ctx )
{
img_convert_ctx = sws_getCachedContext(img_convert_ctx,
frame->width, frame->height, static_cast<AVPixelFormat>(frame->format),
result_width, result_height, static_cast<AVPixelFormat>(result_format),
sws_flags, NULL, NULL, NULL);
if (NULL == img_convert_ctx) {
av_log(NULL, AV_LOG_FATAL, "no memory 1\n");
return;
}
}
//申请内存
uint8_t* buffer = (uint8_t *)av_malloc(buf_size);
if( !buffer ){
av_log(NULL, AV_LOG_FATAL, "no memory 2\n");
return;
}
uint8_t *pixels[4];
int pitch[4];
//把内存映射到数组里面
av_image_fill_arrays(pixels, pitch, buffer, static_cast<AVPixelFormat>(result_format), result_width, result_height, 1);
sws_scale(img_convert_ctx,
(const uint8_t * const *)frame->data, frame->linesize, 0, frame->height,
pixels,pitch);
//处理%20 ==0 的帧
if (frame_num % 20 == 0)
{
Save_rgb_to_file(pixels, pitch, result_height, frame_num);
}
//减少引用,释放内存。
av_frame_unref(frame);
av_freep(&buffer);
frame_num++;
}
else
{
av_log(NULL, AV_LOG_FATAL, "other fail \n");
qDebug() << "other fail";
return;
}
}
}
av_frame_free(&frame);
av_packet_free(&pkt);
sws_freeContext(img_convert_ctx);
img_convert_ctx = NULL;
//关闭编码器,解码器。
avcodec_close(avctx);
//释放容器内存。
avformat_free_context(fmt_ctx);
qDebug() <<"done \n";
return;
}
END、总结的知识与问题
1、avcodec_send_frame和avcodec_receive_packet的关系
1、编码器和解码器都维护了一个缓冲区,在刚开始输入数据时,需要多输入几帧,等缓冲区被填充满后,才会在receive端接收到编码或解码后的数据。
2、存在AVPacket中的数据不一定是一帧(比如音频的数据可能1个AVPacket包含1s的数据,帧率为25的话,就包含25帧),但存在AVFrame中的是一帧数据,所以avcodec_send_packet和avcodec_receive_frame不一定一一对应,调用一次avcodec_send_packet后,可能需要多次调用avcodec_receive_frame。
3、frame只需要分配对象本身空间就好,frame->data的空间并不需要分配。函数会判断是否已经给frame->data分配空间,如果没有分配会在函数内部为frame->data分配合适的空间。
4、avcodec_receive_frame:send端只把数据放入缓冲区,recive端才是解码并获得数据的函数,如果receive发现已有解码后的数据则直接获取,如果没有则开始解码。
5、数据遗留:在最后应该向avcodec_send_frame(enc_ctx, NULL)传入NULL数据,这样编码器知道后面不会再有数据,就把放在缓冲区中的数据,全部编码并通过avcodec_receive_packet输出出来。
6、返回值:EAGAIN,意思是需要输入更多的数据,才会有新的数据编码后的数据返回。AVERROR_EOF在从文件中读取数据推流时,出现这个错误是因为文件内的数据读完了。
7、参数内存:newpkt->data并不需要分配数据空间,只需要给newpkt本身结构体分配空间就好。
参考:avcodec_send_frame和avcodec_receive_packet
2、Input picture width (640) is greater than stride (200)
问题: 在调用sws_scale 时,一定传入的目的图像的宽高小于输入图像的宽高时,则出现该问题。文章来源:https://www.toymoban.com/news/detail-812539.html
参考
1、avcodec_send_frame和avcodec_receive_packet
2、FFmpeg Basics 中文文档-命令行教程文章来源地址https://www.toymoban.com/news/detail-812539.html
到了这里,关于FFmpeg零基础学习(三)——视频分辨率更改的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!