ffmpeg视频编码

ops/2024/11/22 15:54:47/

一、视频编码流程

使用ffmpeg解码视频帧主要可分为两大步骤:初始化编码器编码视频帧,以下代码以h264为例

1. 初始化编码器

初始化编码器包含以下步骤:

(1)查找编码器

videoCodec = avcodec_find_encoder_by_name(videoCodecName);
if (!videoCodec) {release();return false;
}

(2)设置编码器上下文参数

pCodecCtx = avcodec_alloc_context3(videoCodec);
pCodecCtx->time_base = { 1, m_fps }; // 设置编码器上下文的时间基准
pCodecCtx->framerate = { m_fps, 1 };
pCodecCtx->bit_rate = videoBitrate;
pCodecCtx->gop_size = 25;//影响视频的压缩效率、随机访问性能以及编码复杂度,对视频质量、文件大小和编码/解码性能也有影响
pCodecCtx->width = in_w;
pCodecCtx->height = in_h;
pCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;// AV_PIX_FMT_NV12,AV_PIX_FMT_YUV420P
pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
av_opt_set(pCodecCtx->priv_data, "preset", "superfast", 0); // 设置编码速度
// 以下为根据对应的编码格式设置相关的参数
if (pCodecCtx->codec_id == AV_CODEC_ID_H264)
{pCodecCtx->keyint_min = m_fps;av_opt_set(pCodecCtx->priv_data, "profile", "main", 0);av_opt_set(pCodecCtx->priv_data, "b-pyramid", "none", 0);av_opt_set(pCodecCtx->priv_data, "tune", "zerolatency", 0);
}
else if (pCodecCtx->codec_id == AV_CODEC_ID_MPEG2VIDEO)pCodecCtx->max_b_frames = 2;
else if (pCodecCtx->codec_id == AV_CODEC_ID_MPEG1VIDEO)pCodecCtx->mb_decision = 2;

(3)打开编码器

if (avcodec_open2(pCodecCtx, videoCodec, NULL) < 0) // 将编码器上下文和编码器进行关联
{release();return false;
}

(4)设置图像帧参数

// 初始化编码帧
in_frame = av_frame_alloc();
if (!in_frame) {return false;
}
// 设置 AVFrame 的其他属性
in_frame->width = pCodecCtx->width;
in_frame->height = pCodecCtx->height;
in_frame->format = pCodecCtx->pix_fmt;// 计算所需缓冲区大小
int size = av_image_get_buffer_size(pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, 1);
in_frame_buf = (uint8_t*)av_malloc(size); // 分配图像帧内存空间
// 填充 AVFrame
av_image_fill_arrays(in_frame->data, in_frame->linesize, in_frame_buf, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, 1);

(5)创建格式转换上下文
ffmpeg的libx264编码器只支持输入格式为P,如果输入格式不是YUV420P,则需要转换

// 创建缩放上下文,图像缩放和格式转换上下文
if (videoSrcFormat != AV_PIX_FMT_YUV420P) {sws_ctx = sws_getContext(in_w, in_h, videoSrcFormat,in_w, in_h, AV_PIX_FMT_YUV420P,SWS_BILINEAR, NULL, NULL, NULL);if (!sws_ctx) {release();return false;}
}

(6)封装设置和打开文件
如果要将编码后数据进行封装(如封装成mp4文件),则需要使用AVFormatContext,并设置视频流

// 打开格式上下文if (avformat_alloc_output_context2(&pFormatCtx, NULL, NULL, mp4_file) < 0) {release();return false;}// 初始化视频码流
video_st = avformat_new_stream(pFormatCtx, pCodecCtx->codec);
if (!video_st) {std::cout << "avformat_new_stream error" << endl;return false;
}
fmt = pFormatCtx->oformat;
video_st->id = pFormatCtx->nb_streams - 1;
avcodec_parameters_from_context(video_st->codecpar, pCodecCtx);
pFormatCtx->video_codec_id = pFormatCtx->oformat->video_codec;// 打开文件
if (avio_open(&pFormatCtx->pb, mp4_file, AVIO_FLAG_READ_WRITE) < 0) {//std::cout << "output file open fail!" << endl;swprintf(buf, 1024, L"avio_open error");logger.logg(buf);return false;
}
// 输出格式信息
av_dump_format(pFormatCtx, 0, mp4_file, 1);

2. 编码视频帧

(1)将编码数据送往解码器

    // data为需要编码的数据地址,为uint8_t类型的指针,size为输入帧的大小// 如果输入数据格式不是YUV420P则需要转化if (videoSrcFormat != AV_PIX_FMT_YUV420P) {if (sws_scale(sws_ctx, (const uint8_t* const*)&data, mVideoSrcStride, 0, in_h, in_frame->data, in_frame->linesize) < 0) {return false;}}else {memcpy(in_frame->data[0],  data, size);}in_frame->pts = videoPktCount; // videoPktCount为输入帧的序号// 编码int ret = avcodec_send_frame(pCodecCtx, in_frame);//avcodec_send_frame() 函数用于将解码器处理的视频帧发送给编码器if (ret < 0) {return false;}

(2)接收编码数据

AVPacket* vicdeo_pkt = av_packet_alloc();
while (ret >= 0) {ret = avcodec_receive_packet(pCodecCtx, &vicdeo_pkt);//用于从编码器接收编码后的数据包if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)break;else if (ret < 0) {//cout << "Error during encoding" << endl;return false;}// Prepare packet for muxingpkt.stream_index = video_st->index;av_packet_rescale_ts(&pkt, pCodecCtx->time_base, video_st->time_base);//用于将 AVPacket 中的时间戳(PTS和DTS)从一个时间基转换到另一个时间基pkt.pos = -1;//在某些情况下,如果数据包的原始位置是未知的,或者该数据包不是从文件中读取的,而是由其他方式生成的,那么可能会将 pos 设置为 -1。ret = av_interleaved_write_frame(pFormatCtx, &pkt); //用于将一个 AVPacket 写入到一个输出媒体文件中if (ret < 0) {return false;}// Free the packetav_packet_unref(&pkt);
}
++videoPktCount; // 每编码完成一帧,videoPktCount加一
av_packet_free(&vicdeo_pkt);
videoPkt = nullptr;

ffmpeg_176">二、使用ffmpeg实现对内存中的视频帧数据编码

以下代码实现了ffmpeg对视频流数据进行编码的主要过程,可分为初始化编码器(InitEncoder)和编码视频帧(DecodeVideoFrame)


extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
#include <libavutil/opt.h>
#include <libavutil/mathematics.h>
#include <libavutil/timestamp.h>
#include <libswresample/swresample.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#include <libavutil/error.h>
}bool mHasVideo = false;
char* mp4_file = "test.mp4";
int in_w = 1920;
int in_h = 1080;
int m_fps = 30;
int64_t videoBitrate = 6000000;
int      mVideoSrcChannel = 0;
int      mVideoSrcStride[1] = { 0 };
int64_t   videoPktCount = 0;AVPixelFormat videoSrcFormat = AV_PIX_FMT_YUV420P; // 输入的像素格式AVStream* video_st = nullptr;
const AVCodec* videoCodec = nullptr; // 视频编码器
AVCodecContext* pCodecCtx = nullptr; // 视频编码器上下文uint8_t* in_frame_buf = nullptr;
AVFrame* in_frame = nullptr;
SwsContext* sws_ctx = nullptr;// 初始化编码器
bool InitEncoder() {/****   编码器设置      ****/// 查找编码器videoCodec = avcodec_find_encoder_by_name(videoCodecName);if (!videoCodec) {release();return false;}pCodecCtx = avcodec_alloc_context3(videoCodec);pCodecCtx->time_base = { 1, m_fps }; // 设置编码器上下文的时间基准pCodecCtx->framerate = { m_fps, 1 };pCodecCtx->bit_rate = videoBitrate;pCodecCtx->gop_size = 25;//影响视频的压缩效率、随机访问性能以及编码复杂度,对视频质量、文件大小和编码/解码性能也有影响pCodecCtx->width = in_w;pCodecCtx->height = in_h;pCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;// AV_PIX_FMT_NV12,AV_PIX_FMT_YUV420PpCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;av_opt_set(pCodecCtx->priv_data, "preset", "superfast", 0);// 以下为根据对应的编码格式设置相关的参数if (pCodecCtx->codec_id == AV_CODEC_ID_H264){pCodecCtx->keyint_min = m_fps;av_opt_set(pCodecCtx->priv_data, "profile", "main", 0);av_opt_set(pCodecCtx->priv_data, "b-pyramid", "none", 0);av_opt_set(pCodecCtx->priv_data, "tune", "zerolatency", 0);}else if (pCodecCtx->codec_id == AV_CODEC_ID_MPEG2VIDEO)pCodecCtx->max_b_frames = 2;else if (pCodecCtx->codec_id == AV_CODEC_ID_MPEG1VIDEO)pCodecCtx->mb_decision = 2;if (avcodec_open2(pCodecCtx, videoCodec, NULL) < 0) // 将编码器上下文和编码器进行关联{release();return false;}/****   封装设置      ****/if (avformat_alloc_output_context2(&pFormatCtx, NULL, NULL, mp4_file) < 0) {swprintf(buf, 1024, L"avformat_alloc_output_context2 error");logger.logg(buf);return false;}// 初始化视频码流video_st = avformat_new_stream(pFormatCtx, pCodecCtx->codec);if (!video_st) {std::cout << "avformat_new_stream error" << endl;return false;}video_st->id = pFormatCtx->nb_streams - 1;avcodec_parameters_from_context(video_st->codecpar, pCodecCtx);pFormatCtx->video_codec_id = pFormatCtx->oformat->video_codec;// 打开文件if (avio_open(&pFormatCtx->pb, mp4_file, AVIO_FLAG_READ_WRITE) < 0) {release();return false;}// 输出格式信息av_dump_format(pFormatCtx, 0, mp4_file, 1);/****** 输入参数 *********/// 初始化编码帧in_frame = av_frame_alloc();if (!in_frame) {return false;}// 设置 AVFrame 的其他属性in_frame->width = pCodecCtx->width;in_frame->height = pCodecCtx->height;in_frame->format = pCodecCtx->pix_fmt;// 计算所需缓冲区大小int size = av_image_get_buffer_size(pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, 1);in_frame_buf = (uint8_t*)av_malloc(size);// 填充 AVFrameav_image_fill_arrays(in_frame->data, in_frame->linesize, in_frame_buf, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, 1);// 创建缩放上下文,图像缩放和格式转换上下文if (videoSrcFormat != AV_PIX_FMT_YUV420P) {sws_ctx = sws_getContext(in_w, in_h, videoSrcFormat,in_w, in_h, AV_PIX_FMT_YUV420P,SWS_BILINEAR, NULL, NULL, NULL);if (!sws_ctx) {release();return false;}}av_image_fill_linesizes(mVideoSrcStride, videoSrcFormat, in_w); // 计算图像的行大小,格式转换时会用上videoPktCount = 0;}bool DecodeVideoFrame(uint8_t* data, int size) {// data为需要编码的数据地址,为uint8_t类型的指针,size为输入帧大小// 如果输入数据格式不是YUV420P则需要转化if (videoSrcFormat != AV_PIX_FMT_YUV420P) {if (sws_scale(sws_ctx, (const uint8_t* const*)&data, mVideoSrcStride, 0, in_h, in_frame->data, in_frame->linesize) < 0) {return false;}}else {memcpy(in_frame->data[0],  data, size);}in_frame->pts = videoPktCount; // videoPktCount为输入帧的序号// 编码int ret = avcodec_send_frame(pCodecCtx, in_frame);//avcodec_send_frame() 函数用于将解码器处理的视频帧发送给编码器if (ret < 0) {return false;}AVPacket* vicdeo_pkt = av_packet_alloc();while (ret >= 0) {ret = avcodec_receive_packet(pCodecCtx, &vicdeo_pkt);//用于从编码器接收编码后的数据包if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)break;else if (ret < 0) {return false;}// Prepare packet for muxingvicdeo_pkt.stream_index = video_st->index;av_packet_rescale_ts(&pkt, pCodecCtx->time_base, video_st->time_base);//用于将 AVPacket 中的时间戳(PTS和DTS)从一个时间基转换到另一个时间基vicdeo_pkt.pos = -1;//在某些情况下,如果数据包的原始位置是未知的,或者该数据包不是从文件中读取的,而是由其他方式生成的,那么可能会将 pos 设置为 -1。ret = av_interleaved_write_frame(pFormatCtx, vicdeo_pkt); //用于将一个 AVPacket 写入到一个输出媒体文件中if (ret < 0) {return false;}// Free the packetav_packet_unref(vicdeo_pkt);}av_packet_free(&vicdeo_pkt);videoPkt = nullptr;++videoPktCount; // 每编码完成一帧,videoPktCount加一
}void release() {if (in_frame_buf) {av_free(in_frame_buf);in_frame_buf = nullptr;}if (in_frame) {av_frame_free(&in_frame);in_frame = nullptr;}if (pCodecCtx) {avcodec_close(pCodecCtx);avcodec_free_context(&pCodecCtx);pCodecCtx = nullptr;}if (sws_ctx) {sws_freeContext(sws_ctx);sws_ctx = nullptr;}if (pFormatCtx) {avio_close(pFormatCtx->pb);avformat_free_context(pFormatCtx);pFormatCtx = nullptr;}
}

http://www.ppmy.cn/ops/135816.html

相关文章

神经网络(系统性学习四):深度学习——卷积神经网络(CNN)

相关文章&#xff1a; 神经网络中常用的激活函数神经网络&#xff08;系统性学习一&#xff09;&#xff1a;入门篇神经网络&#xff08;系统性学习二&#xff09;&#xff1a;单层神经网络&#xff08;感知机&#xff09;神经网络&#xff08;系统性学习三&#xff09;&#…

【团购核销】抖音生活服务商家应用快速接入①——基础工作

文章目录 一、前言二、抖音开放平台&#xff08;服务商平台&#xff09;三、认证服务能力四、第三方生活服务商家应用五、APPID和AppSecret六、申请接口权限七、开发配置八、参考 一、前言 目的&#xff1a;将抖音团购核销的功能集成到我们自己开发的App和小程序中 【团购核销】…

算法定制LiteAIServer检测算法入侵检测算法平台部署:危险区域人员闯入治理

在现代安全管理的广阔领域中&#xff0c;入侵检测系统&#xff08;IDS&#xff09;作为重要的安全防线&#xff0c;正日益受到各行各业的重视。特别是在那些需要高度安全防范的危险区域&#xff0c;如化工厂、核电站、监狱以及军事基地等&#xff0c;有效的人员闯入检测机制是确…

【Redis】服务器异常重启,导致redis启动失败

redis启动失败日志提示信息&#xff1a;Bad file format reading the append only file: make a backup of your AOF file, then use ./redis-check-aof --fix <filename> 错误日志示例图&#xff08;看最后一句&#xff09; 错误原因解析 这个错误通常是由于Redis的…

阿里巴巴官方「SpringCloudAlibaba全彩学习手册」限时开源!

最近我在知乎上看过的一个热门回答&#xff1a; 初级 Java 开发面临的最大瓶颈在于&#xff0c;脱离不出自身业务带来的局限。日常工作中大部分时间在增删改查、写写接口、改改 bug&#xff0c;久而久之就会发现&#xff0c;自己的技术水平跟刚工作时相比没什么进步。 所以我们…

创客匠人老蒋:个人IP如何获取有效流量?

大家好&#xff0c;我是老蒋。 为什么我反复强调说&#xff0c;如果你想把个人IP、创始人IP做起来&#xff0c;想把自己直播间的流量变大变活&#xff0c;一定要去参加这场将在2024年底举办的《全球创始人IP领袖高峰论坛》&#xff1f;一定要走出去看看更高的世界&#xff1f;…

EtherNet/IP从站转ModbusTCP主网关是一款 ETHERNET/IP 从站功能的通讯网关

EtherNet/IP从站转ModbusTCP主网关是一款 ETHERNET/IP 从站功能的通讯网关。该产品主要功能是将各种 MODBUS-TCP 设备接入到 ETHERNET/IP 网络 中。本网关连接到 ETHERNET/IP 总线中做为从站使用&#xff0c;连接到 MODBUS-TCP总线中做为主站或从站使用。 来百度APP畅享高清图…

贪心算法(1)

目录 柠檬水找零 题解&#xff1a; 代码&#xff1a; 将数组和减半的最少操作次数&#xff08;大根堆&#xff09; 题解&#xff1a; 代码&#xff1a; 最大数&#xff08;注意 sort 中 cmp 的写法&#xff09; 题解&#xff1a; 代码&#xff1a; 摆动序列&#xff0…