ffmpeg视频编码

一、视频编码流程

使用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实现对内存中的视频帧数据编码

以下代码实现了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.rhkb.cn/news/472354.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【计算机毕设】无查重 基于python豆瓣电影评论舆情数据可视化系统(完整系统源码+数据库+开发笔记+详细部署教程)✅

目录 【计算机毕设】无查重 基于python豆瓣电影数据可视化系统&#xff08;完整系统源码数据库开发笔记详细部署教程&#xff09;✅ 一、项目背景 二、项目目标 三、项目功能 四、开发技术介绍 五、数据库设计 六、项目展示 七、开发笔记 八、启动步骤文档 九、权威教…

Python学习从0到1 day29 Python 高阶技巧 ⑦ 正则表达式

目录 一、正则表达式 二、正则表达式的三个基础方法 1.match 从头匹配 2.search&#xff08;匹配规则&#xff0c;被匹配字符串&#xff09; 3.findall&#xff08;匹配规则&#xff0c;被匹配字符串&#xff09; 三、元字符匹配 单字符匹配&#xff1a; 注&#xff1a; 示例&a…

[Python学习日记-67] 封装

[Python学习日记-67] 封装 简介 如何隐藏类中的属性 封装并不是单纯意义的隐藏 封装与扩展性 特性&#xff08;property&#xff09; 简介 从封装本身的意思去理解&#xff0c;封装就好像是拿来一个麻袋&#xff0c;把小猫、小狗、小王八和小猪一起装进麻袋&#xff0c;然…

@Autowired 和 @Resource思考(注入redisTemplate时发现一些奇怪的现象)

1. 前置知识 Configuration public class RedisConfig {Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {RedisTemplate<String, Object> template new RedisTemplate<>();template.setConnectionFactory(facto…

MongoDB分布式集群搭建----副本集----PSS/PSA

MongoDB分布式集群 Replication 复制、Replica Set 复制集/副本集 概念 一、 副本集的相关概念 1.概念 “ A replica set is a group of mongod instances that maintain the same data set. ” 一组MongoDB服务器&#xff08;多个mongod实例&#xff09;&#xff08;有不…

五、函数封装及调用、参数及返回值、作用域、匿名函数、立即执行函数

1. 函数基本使用 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title><style&…

数据分析-48-时间序列变点检测之在线实时数据的CPD

文章目录 1 时间序列结构1.1 变化点的定义1.2 结构变化的类型1.2.1 水平变化1.2.2 方差变化1.3 变点检测1.3.1 离线数据检测方法1.3.2 实时数据检测方法2 模拟数据2.1 模拟恒定方差数据2.2 模拟变化方差数据3 实时数据CPD3.1 SDAR学习算法3.2 Changefinder模块3.3 恒定方差CPD3…

第八节 如何结合AAA实现用户远程登录-路由基础

关于调试设备的登录方式&#xff0c;一共有三种&#xff1a; 第一个&#xff1a;console&#xff1a;需要工程师在现场&#xff0c;进行登录&#xff0c;设备开局的时候使用 第二个&#xff1a;telnet ssh&#xff1a;基于网络互通的前提下进行登录的&#xff0c;远程登录 第三…

【Conda】Windows下conda的安装并在终端运行

下载 在官网下载 https://www.anaconda.com/download/success 安装 双击 一直下一步安装 配置环境变量 为了在终端运行&#xff0c;需配置环境变量 进入到安装conda的目录并复制路径 设置高级环境变量 在终端运行 输入&#xff1a; conda list表明可以正常运行 参考…

LogViewer NLog, Log4Net, Log4j 文本日志可视化

LogViewer 下载 示例&#xff1a;NLog文本日志可视化软件&#xff0c;并且能够实时监听输出最新的日志 nlog.config 通过udp方式传输给LogViewer (udp://ip:port) <?xml version"1.0" encoding"utf-8" ?> <nlog xmlns"http://www.nlog-…

MuMu模拟器安卓12安装Xposed 框架

MuMu模拟器安卓12安装Xposed 框架 当开启代理后,客户端会对代理服务器证书与自身内置证书展开检测,只要检测出两者存在不一致的情况,客户端就会拒绝连接。正是这个原因,才致使我们既没有网络,又抓不到数据包。 解决方式: 通过xposed框架和trustmealready禁掉app里面校验…

Python Web 应用开发基础知识

Python Web 应用开发基础知识 引言 随着互联网的快速发展&#xff0c;Web 应用程序的需求日益增加。Python 作为一种简单易学且功能强大的编程语言&#xff0c;已经成为 Web 开发中广受欢迎的选择之一。本文将深入探讨 Python Web 开发的基础知识&#xff0c;包括常用框架、基…

CSS Module:告别类名冲突,拥抱模块化样式(5)

CSS Module 是一种解决 CSS 类名冲突的全新思路。它通过构建工具&#xff08;如 webpack&#xff09;将 CSS 样式切分为更加精细的模块&#xff0c;并在编译时将类名转换为唯一的标识符&#xff0c;从而避免类名冲突。本文将详细介绍 CSS Module 的实现原理和使用方法。 1. 思…

动力商城-03 Idea集成apifox Mybatis-Plus字段策略

1.Idea下载apifox插件 2.新建令牌放入Idea 3.右键上传到对应接口 4.设置前置url 插件能够自动识别swagger注解 Mybatis-Plus字段策略 1、FieldStrategy作用 Mybatis-Plus字段策略FieldStrategy的作用主要是在进行新增、更新时&#xff0c;根据配置的策略判断是否对实体对…

使用 npm 安装 Yarn

PS E:\WeChat Files\wxid_fipwhzebc1yh22\FileStorage\File\2024-11\spid-admin\spid-admin> yarn install yarn : 无法将“yarn”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写&#xff0c;如果包括路径&#xff0c;请确保路径正确&#xff0c;然后…

Springboot 使用EasyExcel导出含图片并设置样式的Excel文件

Springboot 使用EasyExcel导出含图片并设置样式的Excel文件 Excel导出系列目录&#xff1a;★★★★尤其注意&#xff1a;引入依赖创建导出模板类逻辑处理controllerservice 导出效果总结 Excel导出系列目录&#xff1a; 【Springboot 使用EasyExcel导出Excel文件】 【Springb…

深入理解 source 和 sh、bash 的区别

1 引言 在日常使用 Linux 的过程中&#xff0c;脚本的执行是不可避免的需求之一&#xff0c;而 source、sh、bash 等命令则是执行脚本的常用方式。尽管这些命令都能运行脚本&#xff0c;但它们之间的执行方式和效果却有着显著的区别。这些区别可能会影响到脚本的环境变量、工作…

CC6学习记录

&#x1f338; cc6 cc6和cc1的国外链其实后半条链子是一样的&#xff0c;但是cc6的不局限于jdk的版本和commons-collections的版本。 回忆一下cc1的后半条链子&#xff1a; LazyMap.get()->InvokerTransformer.transform() 这里我们就结合了URLDNS链的思路&#xff0c;在…

飞凌嵌入式RK3576核心板已适配Android 14系统

在今年3月举办的RKDC2024大会上&#xff0c;飞凌嵌入式FET3576-C核心板作为瑞芯微RK3576处理器的行业首秀方案重磅亮相&#xff0c;并于今年6月率先量产发货&#xff0c;为客户持续稳定地供应&#xff0c;得到了众多合作伙伴的认可。 FET3576-C核心板此前已提供了Linux 6.1.57…

路漫漫其修远兮,吾将上下而求索---第一次使用github的过程记录和个人感受

文章目录 1.仓库位置2.新建仓库3.配置仓库4.克隆和上传5.推荐文章和我的感受 1.仓库位置 这个仓库的位置就是在我们的这个个人主页的右上角&#xff1b;如果是第一次注册账号的话&#xff0c;这个主页里面肯定是不存在仓库的&#xff0c;需要我们自己手动的进行创建&#xff1…