当前位置: 首页 > news >正文

ffmpeg视频编码原理和实战-(5)对编码过程进行封装并解决丢帧问题

头文件:

xencode.h

#pragma once
#include <mutex>
#include<vector>
struct AVCodecContext;
struct AVPacket;
struct AVFrame;
class XEncode
{
public:///// 创建编码上下文/// @para codec_id 编码器ID号,对应ffmpeg/// @return 编码上下文 ,失败返回nullptrstatic AVCodecContext* Create(int codec_id);///// 设置对象的编码器上下文 上下文传递到对象中,资源由XEncode维护/// 加锁 线程安全/// @para c 编码器上下文 如果c_不为nullptr,则先清理资源void set_c(AVCodecContext* c);//// 设置编码参数,线程安全bool SetOpt(const char* key, const char* val);bool SetOpt(const char* key, int val);///// 打开编码器 线程安全bool Open();///// 编码数据 线程安全 每次新创建AVPacket/// @para frame 空间由用户维护/// @return 失败范围nullptr 返回的AVPacket用户需要通过av_packet_free 清理AVPacket* Encode(const AVFrame* frame);/////根据AVCodecContext 创建一个AVFrame,需要调用者释放av_frame_freeAVFrame* CreateFrame();//返回所有编码缓存中的AVPacket,解决丢帧问题std::vector<AVPacket*> End();private:AVCodecContext* c_ = nullptr;  //编码器上下文std::mutex mux_;               //编码器上下文锁
};

源文件:

xencode.cpp


#include "xencode.h"
#include <iostream>
using namespace std;
extern "C"
{
#include <libavcodec/avcodec.h>
#include <libavutil/opt.h>
}
//预处理指令导入库
#pragma comment(lib,"avcodec.lib")
#pragma comment(lib,"avutil.lib")static void PrintErr(int err)
{char buf[1024] = { 0 };av_strerror(err, buf, sizeof(buf) - 1);cerr << buf << endl;
}//
/// 创建编码上下文
/// @para codec_id 编码器ID号,对应ffmpeg
/// @return 编码上下文 ,失败返回nullptr
AVCodecContext* XEncode::Create(int codec_id)
{//1 找到编码器auto codec = avcodec_find_encoder((AVCodecID)codec_id);if (!codec){cerr << "avcodec_find_encoder failed!" << codec_id << endl;return nullptr;}//创建上下文auto c = avcodec_alloc_context3(codec);if (!c){cerr << "avcodec_alloc_context3 failed!" << codec_id << endl;return nullptr;}//设置参数默认值c->time_base = { 1,25 };c->pix_fmt = AV_PIX_FMT_YUV420P;c->thread_count = 16;return c;
}//
/// 设置对象的编码器上下文 上下文传递到对象中,资源由XEncode维护
/// 加锁 线程安全
/// @para c 编码器上下文 如果c_不为nullptr,则先清理资源
void XEncode::set_c(AVCodecContext* c)
{unique_lock<mutex>lock(mux_);if (c_){avcodec_free_context(&c_);}this->c_ = c;
}bool XEncode::SetOpt(const char* key, const char* val)
{unique_lock<mutex>lock(mux_);if (!c_)return false;auto re = av_opt_set(c_->priv_data, key, val, 0);if (re != 0){cerr << "av_opt_set failed!";PrintErr(re);}return true;
}bool XEncode::SetOpt(const char* key, int val)
{unique_lock<mutex>lock(mux_);if (!c_)return false;auto re = av_opt_set_int(c_->priv_data, key, val, 0);if (re != 0){cerr << "av_opt_set failed!";PrintErr(re);}return true;
}//
/// 打开编码器 线程安全
bool XEncode::Open()
{unique_lock<mutex>lock(mux_);if (!c_)return false;auto re = avcodec_open2(c_, NULL, NULL);if (re != 0){PrintErr(re);return false;}return true;
}
//
/// 编码数据 线程安全 每次新创建AVPacket
/// @para frame 空间由用户维护
/// @return 失败范围nullptr 返回的AVPacket用户需要通过av_packet_free 清理
AVPacket* XEncode::Encode(const AVFrame* frame)
{if (!frame)return nullptr;unique_lock<mutex>lock(mux_);if (!c_)return nullptr;//发送到编码线程auto re = avcodec_send_frame(c_, frame);if (re != 0)return nullptr;auto pkt = av_packet_alloc();//接收编码线程数据re = avcodec_receive_packet(c_, pkt);if (re == 0){return pkt;}av_packet_free(&pkt);if (re == AVERROR(EAGAIN) || re == AVERROR_EOF){return nullptr;}if (re < 0){PrintErr(re);}return nullptr;
}//返回所有编码缓存中的AVPacket,解决丢帧问题
std::vector<AVPacket*> XEncode::End()
{std::vector<AVPacket*> res;//类似一个数组,存的是AVPacket 对象指针unique_lock<mutex>lock(mux_);if (!c_)return res;auto re = avcodec_send_frame(c_, NULL);//发送NULL到编码器中,获取编码器中的缓存区if (re != 0)return res;while (re >= 0){auto pkt = av_packet_alloc();re = avcodec_receive_packet(c_,pkt);//avpacket从编码器c_中读取编码数据。if (re != 0){av_packet_free(&pkt);break;}res.push_back(pkt);}return res;}///
//根据AVCodecContext 创建一个AVFrame,需要调用者释放av_frame_free
AVFrame* XEncode::CreateFrame()
{unique_lock<mutex>lock(mux_);if (!c_)return nullptr;auto frame = av_frame_alloc();frame->width = c_->width;frame->height = c_->height;frame->format = c_->pix_fmt;auto re = av_frame_get_buffer(frame, 0);if (re != 0){av_frame_free(&frame);PrintErr(re);return nullptr;}return frame;
}

main.cpp


#include <iostream>
#include <fstream>
#include "xencode.h"
using namespace std;
extern "C" { //指定函数是c语言函数,函数名不包含重载标注
//引用ffmpeg头文件
#include <libavcodec/avcodec.h>
#include <libavutil/opt.h>
}int main(int argc, char* argv[])
{string filename = "400_300_25_preset";AVCodecID codec_id = AV_CODEC_ID_H264;if (argc > 1){string codec = argv[1];if (codec == "h265" || codec == "hevc"){codec_id = AV_CODEC_ID_HEVC;}}if (codec_id == AV_CODEC_ID_H264){filename += ".h264";}else if (codec_id == AV_CODEC_ID_HEVC){filename += ".h265";}//输出文件ofstream ofs;ofs.open(filename, ios::binary);XEncode en;auto c = en.Create(codec_id);c->width = 400;c->height = 300;en.set_c(c);en.SetOpt("crf", 18); //恒定速率因子(CRF)en.Open();auto frame = en.CreateFrame();int count = 0;//写入文件的帧数,看是否有500,SPS PPS IDR放在一帧中for (int i = 0; i < 500; i++){//生成AVFrame 数据 每帧数据不同//Yfor (int y = 0; y < c->height; y++){for (int x = 0; x < c->width; x++){frame->data[0][y * frame->linesize[0] + x] = x + y + i * 3;}}//UVfor (int y = 0; y < c->height / 2; y++){for (int x = 0; x < c->width / 2; x++){frame->data[1][y * frame->linesize[1] + x] = 128 + y + i * 2;frame->data[2][y * frame->linesize[2] + x] = 64 + x + i * 5;}}frame->pts = i;//显示的时间auto pkt = en.Encode(frame);if (pkt){count++;ofs.write((char*)pkt->data, pkt->size);av_packet_free(&pkt);}}auto missing_pkt =  en.End();for (auto pkt : missing_pkt){count++;ofs.write((char*)pkt->data, pkt->size);av_packet_free(&pkt);}ofs.close();en.set_c(nullptr);cout << "encode" << count << endl;getchar();return 0;
}

讨论:

1.上一篇文章提到的丢帧问题得到了解决,原因是编码器缓存不够

2.封装使得代码更加简洁

相关文章:

ffmpeg视频编码原理和实战-(5)对编码过程进行封装并解决丢帧问题

头文件&#xff1a; xencode.h #pragma once #include <mutex> #include<vector> struct AVCodecContext; struct AVPacket; struct AVFrame; class XEncode { public:///// 创建编码上下文/// para codec_id 编码器ID号&#xff0c;对应ffmpeg/// return 编码上…...

halo进阶-主题插件使用

开始捣鼓捣鼓halo&#xff0c;换换主题&#xff0c;加个页面 可参考&#xff1a;Halo 文档 安装/更新主题 主题如同壁纸&#xff0c;萝卜青菜各有所爱&#xff0c;大家按需更换即可&#xff1b; Halo好在一键更换主题&#xff0c;炒鸡方便。 安装/更新插件 此插件还扩展了插件…...

资深开发推荐的IDEA 插件

开发如虎添翼 工欲善其事&#xff0c;必先利其器。想要提升编程开发效率&#xff0c;必须选择一款顺手的开发工具&#xff0c;插件不在多&#xff0c;而在精&#xff0c;作为从业10年的程序员&#xff0c;我目前用到这十几个插件&#xff0c;在平时开发&#xff0c;代码review…...

数学题目系列(一)|丑数|各位和|埃氏筛|欧拉筛

一.丑数 链接&#xff1a;丑数 分析&#xff1a; 丑数只有2&#xff0c;3&#xff0c;5这三个质因数&#xff0c;num 2a 3b 5c也就是一个丑数是由若干个2&#xff0c;3&#xff0c;5组成&#xff0c;那么丑数除以这若干个数字最后一定变为1 代码 class Solution {publi…...

k8s学习--Secret详细解释与应用

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 Secret什么是Secret?Secret四种类型及其特点Secret应用案例&#xff08;1&#xff09;将明文密码进行base64编码&#xff08;2&#xff09;编写创建secret的YAML文…...

功能问题:如何防止接口重复请求?

大家好&#xff0c;我是大澈&#xff01; 本文约 1400 字&#xff0c;整篇阅读约需 3 分钟。 防止接口重复请求在软件开发中非常重要&#xff0c;重复请求必然会导致服务器资源的浪费。 因为每次请求都需要服务器进行处理&#xff0c;如果请求是重复的&#xff0c;那么服务…...

系统架构设计师【第5章】: 软件工程基础知识 (核心总结)

文章目录 5.1 软件工程5.1.1 软件工程定义5.1.2 软件过程模型5.1.3 敏捷模型5.1.4 统一过程模型&#xff08;RUP&#xff09;5.1.5 软件能力成熟度模型 5.2 需求工程5.2.1 需求获取5.2.2 需求变更5.2.3 需求追踪 5.3 系统分析与设计5.3.1 结构化方法5.3.2 面向对象…...

嵌入式Linux系统编程 — 2.2 标准I/O库:检查或复位状态

目录 1 检查或复位状态简介 2 feof()函数 2.1 feof()函数简介 2.2 示例程序 3 ferror()函数 4 clearerr()函数 4.1 clearerr()函数简介 4.2 示例程序 1 检查或复位状态简介 调用 fread() 函数读取数据时&#xff0c;如果返回值小于参数 nmemb 所指定的值&#xff0c;这…...

pESC-HIS是什么,怎么看?-实验操作系列-2

01 典型的pESC-HIS质粒遗传图谱 02 介绍 质粒类型&#xff1a;酿酒酵母蛋白表达载体 表达水平&#xff1a;高拷贝 诱导方法&#xff1a;半乳糖 启动子&#xff1a;GAL1和GAL10 克隆方法&#xff1a;多克隆位点&#xff0c;限制性内切酶 载体大小&#xff1a;6706bp 5 测…...

树形表/树形数据接口的开发

数据表格式 需要返回的json格式 点击查看json数据 [{"childrenTreeNodes" : [{"childrenTreeNodes" : null,"id" : "1-1-1","isLeaf" : null,"isShow" : null,"label" : "HTML/CSS","na…...

二叉树的镜像--c++【做题记录】

【问题描述】 给定扩展二叉树的前序序列&#xff0c;构建二叉树。 求这课二叉树的镜像&#xff0c;并输出其前序遍历序列。 【输入形式】 输入扩展二叉树的前序序列。 【输出形式】 输出镜像二叉树的前序遍历序列。 【样例输入】 ab##cd##e## 【样例输出】 镜像后二叉树的前序遍…...

redis安裝启动

1、下载redis解压 https://github.com/tporadowski/redis/releases 2、打开cmd&#xff0c;切换到解压的文件夹 3、redis-server.exe redis.windows.conf 启动redis redis可通过命令行输入config set requirepass password和直接修改redis.config文件中修改 requirepass 来设…...

为什么Java中的main方法必须是public static void的?

当我们创建main方法时&#xff0c;首先都是public、都是static&#xff0c;返回值都是void&#xff0c;方法名都是main&#xff0c;入参都是一个字符串数组。 在以上的方法声明中&#xff0c;唯一可以改变的部分就是方法的参数名&#xff0c;我们可以吧args改成任意我们想要使…...

shell的编程方式

文章目录 变量俩种方式第一种方式第二种方式 取消变量数组创建数组获取数组元素的方式 read输出的方式限制输入的方式 流程控制方式for循环输出的方式第一种方式第二种方式while循环输出的方式select选择输出的方式 判断方式判断的四种方式第一种方式第二种方式第三种方式 算术…...

前端面试项目细节重难点(已工作|做分享)想(八)

面试官&#xff1a;请你讲讲你在该项目中遇到的印象深刻的问题是什么&#xff1f; 答&#xff1a;我的回答&#xff1a;该项目的实现过程中我确实遇到了问题&#xff1a;【我会给大家整理回答思路和角度&#xff0c;那那么遇到这样的问题也可借鉴这种思路进行阐述】 第一层面…...

Loguru,一个 Python 日志神器

大家好!我是爱摸鱼的小鸿,关注我,收看每期的编程干货。 一个简单的库,也许能够开启我们的智慧之门, 一个普通的方法,也许能在危急时刻挽救我们于水深火热, 一个新颖的思维方式,也许能激发我们无尽的创造力, 一个独特的技巧,也许能成为我们的隐形盾牌…… 神奇的 Pyth…...

C++ 反转单词

在C中&#xff0c;反转一个字符串中的单词&#xff08;单词之间通过空格分隔&#xff0c;但单词内部保持原有顺序&#xff09;可以通过以下步骤实现&#xff1a; 找到字符串中的所有单词&#xff0c;这可以通过查找空格来实现。将单词存储在一个容器中&#xff08;例如 std::v…...

Apache Doris 基础 -- 数据表设计(表索引)

1、索引概述 索引用于帮助快速过滤或搜索数据。目前&#xff0c;Doris支持两种类型的索引:内置智能索引和用户创建的二级索引。 内置智能索引 排序键和前缀索引:Apache Doris基于排序键以有序的方式存储数据。它为每1024行数据创建一个前缀索引。索引中的键是当前1024行组的…...

资源描述框架的用途及实际应用解析

什么是RDF&#xff1f; RDF代表 资源描述框架 RDF是用于描述网络资源的框架 RDF旨在被计算机阅读和理解 RDF并非设计用于供人阅读 RDF以 XML 编写 示例 描述购物商品的属性&#xff0c;如价格和可用性描述网络活动的时间表描述网页的信息&#xff08;内容&#xff0c;作者&a…...

工业级物联网边缘网关解决方案-天拓四方

随着工业4.0时代的到来&#xff0c;越来越多的企业开始寻求智能化升级&#xff0c;以提高生产效率、降低运营成本并增强市场竞争力。然而&#xff0c;在实际的转型升级过程中&#xff0c;许多企业面临着数据孤岛、设备兼容性差、网络安全风险高等问题&#xff0c;这些问题严重制…...

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…...

装饰模式(Decorator Pattern)重构java邮件发奖系统实战

前言 现在我们有个如下的需求&#xff0c;设计一个邮件发奖的小系统&#xff0c; 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式&#xff08;Decorator Pattern&#xff09;允许向一个现有的对象添加新的功能&#xff0c;同时又不改变其…...

golang循环变量捕获问题​​

在 Go 语言中&#xff0c;当在循环中启动协程&#xff08;goroutine&#xff09;时&#xff0c;如果在协程闭包中直接引用循环变量&#xff0c;可能会遇到一个常见的陷阱 - ​​循环变量捕获问题​​。让我详细解释一下&#xff1a; 问题背景 看这个代码片段&#xff1a; fo…...

【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力

引言&#xff1a; 在人工智能快速发展的浪潮中&#xff0c;快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型&#xff08;LLM&#xff09;。该模型代表着该领域的重大突破&#xff0c;通过独特方式融合思考与非思考…...

Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器

第一章 引言&#xff1a;语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域&#xff0c;文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量&#xff0c;支撑着搜索引擎、推荐系统、…...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)

设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile&#xff0c;新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...

【单片机期末】单片机系统设计

主要内容&#xff1a;系统状态机&#xff0c;系统时基&#xff0c;系统需求分析&#xff0c;系统构建&#xff0c;系统状态流图 一、题目要求 二、绘制系统状态流图 题目&#xff1a;根据上述描述绘制系统状态流图&#xff0c;注明状态转移条件及方向。 三、利用定时器产生时…...

MySQL 8.0 OCP 英文题库解析(十三)

Oracle 为庆祝 MySQL 30 周年&#xff0c;截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始&#xff0c;将英文题库免费公布出来&#xff0c;并进行解析&#xff0c;帮助大家在一个月之内轻松通过OCP认证。 本期公布试题111~120 试题1…...

【JavaSE】绘图与事件入门学习笔记

-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角&#xff0c;以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向&#xff0c;距离坐标原点x个像素;第二个是y坐标&#xff0c;表示当前位置为垂直方向&#xff0c;距离坐标原点y个像素。 坐标体系-像素 …...

Android Bitmap治理全解析:从加载优化到泄漏防控的全生命周期管理

引言 Bitmap&#xff08;位图&#xff09;是Android应用内存占用的“头号杀手”。一张1080P&#xff08;1920x1080&#xff09;的图片以ARGB_8888格式加载时&#xff0c;内存占用高达8MB&#xff08;192010804字节&#xff09;。据统计&#xff0c;超过60%的应用OOM崩溃与Bitm…...