Encoder编码器

Encoder编码器

#include <libavutil/log.h>
#include <libavutil/opt.h>
#include <libavcodec/avcodec.h>static int encode(AVCodecContext *ctx, AVFrame *frame, AVPacket *pkt, FILE *out){int ret = -1;ret = avcodec_send_frame(ctx, frame);if(ret < 0) {av_log(NULL, AV_LOG_ERROR, "Failed to send frame to encoder!\n");goto _END;}while( ret >= 0){ret = avcodec_receive_packet(ctx, pkt);if(ret == AVERROR(EAGAIN) || ret == AVERROR_EOF){return 0;} else if( ret < 0) {return -1; //退出tkyc}fwrite(pkt->data, 1, pkt->size, out);av_packet_unref(pkt);}
_END:return 0;
}int main(int argc, char* argv[]){int ret = -1;FILE *f = NULL;char *dst = NULL;char *codecName = NULL;const AVCodec *codec = NULL;AVCodecContext *ctx = NULL;AVFrame *frame = NULL;AVPacket *pkt = NULL;av_log_set_level(AV_LOG_DEBUG);//1. 输入参数if(argc < 3){av_log(NULL, AV_LOG_ERROR, "arguments must be more than 3\n");goto _ERROR;}dst = argv[1];codecName = argv[2];//2. 查找编码器codec = avcodec_find_encoder_by_name(codecName);if(!codec){av_log(NULL, AV_LOG_ERROR, "don't find Codec: %s", codecName);goto _ERROR;}//3. 创建编码器上下文ctx = avcodec_alloc_context3(codec);if(!ctx){av_log(NULL, AV_LOG_ERROR, "NO MEMRORY\n");goto _ERROR;}//4. 设置编码器参数ctx->width = 640;ctx->height = 480;ctx->bit_rate = 500000;ctx->time_base = (AVRational){1, 25};ctx->framerate = (AVRational){25, 1};ctx->gop_size = 10;ctx->max_b_frames = 1;ctx->pix_fmt = AV_PIX_FMT_YUV420P;if(codec->id == AV_CODEC_ID_H264){av_opt_set(ctx->priv_data, "preset", "slow", 0);}//5. 编码器与编码器上下文绑定到一起ret = avcodec_open2(ctx, codec , NULL);if(ret < 0) {av_log(ctx, AV_LOG_ERROR, "Don't open codec: %s \n", av_err2str(ret));goto _ERROR;}//6. 创建输出文件f = fopen(dst, "wb");if(!f){av_log(NULL, AV_LOG_ERROR, "Don't open file:%s", dst);goto _ERROR;}//7. 创建AVFrameframe = av_frame_alloc();if(!frame){av_log(NULL, AV_LOG_ERROR, "NO MEMORY!\n");goto _ERROR;}frame->width = ctx->width;frame->height = ctx->height;frame->format = ctx->pix_fmt; ret = av_frame_get_buffer(frame, 0);if(ret < 0) {av_log(NULL, AV_LOG_ERROR, "Could not allocate the video frame \n");goto _ERROR;}//8. 创建AVPacketpkt = av_packet_alloc();if(!pkt){av_log(NULL, AV_LOG_ERROR, "NO MEMORY!\n");goto _ERROR;}//9. 生成视频内容for(int i=0; i<25; i++){ret = av_frame_make_writable(frame);if(ret < 0) {break;}//Y分量for(int y = 0; y < ctx->height; y++){for(int x=0; x < ctx->width; x++){frame->data[0][y*frame->linesize[0]+x] = x + y + i * 3;}}//UV分量for(int y=0; y< ctx->height/2; y++){for(int x=0; x < ctx->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;//10. 编码ret = encode(ctx, frame, pkt, f);if(ret == -1){goto _ERROR;}}//10. 编码encode(ctx, NULL, pkt, f);
_ERROR://ctxif(ctx){avcodec_free_context(&ctx);}//avframeif(frame){av_frame_free(&frame);}//avpacketif(pkt){av_packet_free(&pkt);}//dstif(f){fclose(f);}return 0;
}

我们来逐行分析这段新的 C 代码。这段代码的功能是创建一个视频编码器生成一些简单的 YUV 视频帧,将这些帧编码成指定的格式(例如 H.264),并将编码后的原始码流写入一个文件。


encode 函数分析

这个辅助函数负责将一帧 AVFrame 发送给编码器,并接收所有可能产生的 AVPacket,然后将这些包写入文件。

static int encode(AVCodecContext *ctx, AVFrame *frame, AVPacket *pkt, FILE *out){// 定义一个静态函数 encode。// - AVCodecContext *ctx: 编码器上下文。// - AVFrame *frame: 要编码的帧。如果为 NULL,表示要刷新编码器。// - AVPacket *pkt: 用于接收编码后的数据包。// - FILE *out: 输出文件指针。// - 返回值: 成功时返回 0,失败时返回 -1。int ret = -1;                          // 声明整型变量 ret 并初始化为 -1,用于存储函数返回值。ret = avcodec_send_frame(ctx, frame);  // 将 AVFrame 发送给编码器。if(ret < 0) {                          // 检查发送是否成功。av_log(NULL, AV_LOG_ERROR, "Failed to send frame to encoder!\n"); // 失败则记录错误。goto _END;                          // 跳转到 _END 标签。}while( ret >= 0){                       // 进入循环,尝试从编码器接收编码后的 AVPacket。ret = avcodec_receive_packet(ctx, pkt); // 尝试接收一个包。if(ret == AVERROR(EAGAIN) || ret == AVERROR_EOF){// 如果返回 EAGAIN (表示编码器需要更多输入帧才能输出一个包) // 或者 AVERROR_EOF (表示编码器已刷新完毕,没有更多包了)。return 0;                          // 这两种情况都算正常,直接返回 0 (成功)。} else if( ret < 0) {                 // 如果发生其他错误。// **** 注意:这里的中文注释 "退出tkyc" 可能是拼写错误或内部术语 ****return -1;                       // 返回 -1 表示发生错误。}fwrite(pkt->data, 1, pkt->size, out); // 将接收到的包的数据 (pkt->data) 写入输出文件。av_packet_unref(pkt);                // 释放对该包的引用,以便下次可以重用 pkt。}
_END:return 0;                              // 正常或出错(send_frame 失败)时返回 0 (这里的设计可能有点不一致,但_END 意味着返回 0)。
}

main 函数分析

这是程序的主体,负责设置编码器、生成视频帧并调用 encode 函数。

int main(int argc, char* argv[]){int ret = -1;                          // 声明整型变量 ret 并初始化为 -1。FILE *f = NULL;                        // 输出文件指针。char *dst = NULL;                      // 输出文件名字符串指针。char *codecName = NULL;                // 编码器名称字符串指针。const AVCodec *codec = NULL;           // 指向找到的编码器。AVCodecContext *ctx = NULL;            // 编码器上下文。AVFrame *frame = NULL;                 // 用于存储待编码的原始视频帧。AVPacket *pkt = NULL;                  // 用于存储编码后的数据包。av_log_set_level(AV_LOG_DEBUG);        // 设置 FFmpeg 的日志级别为 DEBUG,以便看到更多信息。// 1. 输入参数if(argc < 3){                          // 检查命令行参数数量是否足够。av_log(NULL, AV_LOG_ERROR, "arguments must be more than 3\n"); // 不够则打印错误。goto _ERROR;                         // 跳转到 _ERROR 进行清理。}dst = argv[1];                         // 获取输出文件名。codecName = argv[2];                   // 获取编码器名称 (例如 "libx264")。// 2. 查找编码器codec = avcodec_find_encoder_by_name(codecName); // 根据名称查找编码器。if(!codec){                              // 检查是否找到。av_log(NULL, AV_LOG_ERROR, "don't find Codec: %s", codecName); // 未找到则打印错误。goto _ERROR;}// 3. 创建编码器上下文ctx = avcodec_alloc_context3(codec);     // 为找到的编码器分配上下文。if(!ctx){                                // 检查分配是否成功。av_log(NULL, AV_LOG_ERROR, "NO MEMRORY\n");goto _ERROR;}// 4. 设置编码器参数ctx->width = 640;                      // 设置视频宽度为 640。ctx->height = 480;                     // 设置视频高度为 480。ctx->bit_rate = 500000;                // 设置目标比特率 (码率) 为 500 kbps。ctx->time_base = (AVRational){1, 25};  // 设置时间基准为 1/25 (表示 PTS 的单位是 1/25 秒)。ctx->framerate = (AVRational){25, 1};  // 设置帧率为 25/1 (25 fps)。ctx->gop_size = 10;                    // 设置 GOP (Group of Pictures) 大小为 10,即每 10 帧一个 I 帧。ctx->max_b_frames = 1;                 // 设置最大 B 帧数量为 1。ctx->pix_fmt = AV_PIX_FMT_YUV420P;     // 设置输入的像素格式为 YUV420P。if(codec->id == AV_CODEC_ID_H264){     // 如果选择的是 H.264 编码器。av_opt_set(ctx->priv_data, "preset", "slow", 0); // 设置 H.264 的私有选项 "preset" 为 "slow",以获得更好的压缩率。}// 5. 编码器与编码器上下文绑定到一起 (即打开编码器)ret = avcodec_open2(ctx, codec , NULL);  // 打开编码器,初始化它。if(ret < 0) {                            // 检查是否成功。av_log(ctx, AV_LOG_ERROR, "Don't open codec: %s \n", av_err2str(ret)); // 失败则打印错误。goto _ERROR;}// 6. 创建输出文件f = fopen(dst, "wb");                  // 以二进制写入模式打开输出文件。if(!f){                                  // 检查是否成功。av_log(NULL, AV_LOG_ERROR, "Don't open file:%s", dst);goto _ERROR;}// 7. 创建AVFrameframe = av_frame_alloc();                // 分配 AVFrame 结构体。if(!frame){                              // 检查分配是否成功。av_log(NULL, AV_LOG_ERROR, "NO MEMORY!\n");goto _ERROR;}frame->width = ctx->width;               // 设置 AVFrame 的宽度。frame->height = ctx->height;             // 设置 AVFrame 的高度。frame->format = ctx->pix_fmt;            // 设置 AVFrame 的像素格式。ret = av_frame_get_buffer(frame, 0);   // 为 AVFrame 分配实际存储像素数据的缓冲区。if(ret < 0) {                            // 检查分配是否成功。av_log(NULL, AV_LOG_ERROR, "Could not allocate the video frame \n");goto _ERROR;}// 8. 创建AVPacketpkt = av_packet_alloc();                 // 分配 AVPacket 结构体。if(!pkt){                                // 检查分配是否成功。av_log(NULL, AV_LOG_ERROR, "NO MEMORY!\n");goto _ERROR;}// 9. 生成视频内容并编码for(int i=0; i<25; i++){               // 循环 25 次,生成并编码 25 帧视频。ret = av_frame_make_writable(frame); // 确保 AVFrame 的数据区是可写的。if(ret < 0) {                      // 检查是否成功。break;                           // 如果失败,跳出循环。}// Y分量 (亮度)for(int y = 0; y < ctx->height; y++){ // 遍历 Y 分量的每一行。for(int x=0; x < ctx->width; x++){ // 遍历 Y 分量的每一列。frame->data[0][y*frame->linesize[0]+x] = x + y + i * 3; // 填充一个简单的渐变图案。}}// UV分量 (色度) - 注意尺寸是 Y 的一半for(int y=0; y< ctx->height/2; y++){ // 遍历 U/V 分量的每一行。for(int x=0; x < ctx->width/2; x++){ // 遍历 U/V 分量的每一列。frame->data[1][y * frame->linesize[1] + x ] = 128 + y + i * 2; // 填充 U 分量。frame->data[2][y * frame->linesize[2] + x ] = 64 + x + i * 5;  // 填充 V 分量。}}frame->pts = i;                      // 设置当前帧的 PTS (显示时间戳)。这里简单地使用帧序号。// 10. 编码ret = encode(ctx, frame, pkt, f);    // 调用 encode 函数处理这一帧。if(ret == -1){                       // 检查编码是否出错。goto _ERROR;                       // 出错则跳转到 _ERROR。}}// 10. 编码 (刷新编码器)encode(ctx, NULL, pkt, f);             // 发送一个 NULL 帧给 encode 函数,以刷新编码器中可能存在的缓存数据包。
_ERROR:                                  // 错误处理和资源释放标签。// ctx (释放编码器上下文)if(ctx){avcodec_free_context(&ctx);}// avframe (释放 AVFrame)if(frame){av_frame_free(&frame);}// avpacket (释放 AVPacket)if(pkt){av_packet_free(&pkt);}// dst (关闭文件)if(f){fclose(f);}return 0;                              // 程序结束,返回 0。
}

总结:

这个程序演示了 FFmpeg 视频编码的基本流程:

  1. 设置: 查找编码器、创建上下文、设置参数、打开编码器。
  2. 准备: 创建 AVFrame 用于存放原始数据,创建 AVPacket 用于接收编码后数据,打开输出文件。
  3. 循环:
    • 生成数据: 创建 YUV 图像数据并放入 AVFrame
    • 设置 PTS: 为 AVFrame 设置时间戳。
    • 编码: 调用 avcodec_send_frameavcodec_receive_packet (通过 encode 函数) 进行编码。
    • 写入: 将编码后的 AVPacket 写入文件。
  4. 刷新: 发送 NULL 帧以获取所有剩余的包。
  5. 清理: 释放所有分配的资源。

它生成的是裸码流 (Raw Stream),不包含任何容器格式(如 MP4 或 MKV)的头信息或元数据。要播放这种文件,通常需要播放器知道其编码格式,或者需要使用 FFmpeg 等工具将其封装到容器中。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/95219.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/95219.shtml

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

相关文章

微服务-ruoyi-cloud部署

微服务 阿里 阿里nacos 注册中心&#xff0c;配置中心 spring cloud gateway网关 公共服务 阿里sentinel 面向分布式、多语言异构化服务架构的流量治理组件 阿里seata 是一款开源的分布式事务解决方案 nginx 静态资源服器 反向代理 ruoyi-cloud部署架构 VM配置 网…

Win7能看到Win10打印机但连接不上

解决方案&#xff1a;方法1.修改注册表&#xff08;适用于错误代码0x0000011b&#xff09;修改注册表&#xff08;关键步骤&#xff09;&#xff1a;许多情况下&#xff0c;这是由于Win10的一项隐私设置导致的1。在Win10电脑上&#xff0c;按 Win R&#xff0c;输入 regedit 并…

如何构建灵活、可控、可扩展的多云网络底座

在 AI 和数字化的浪潮中&#xff0c;越来越多的企业不再满足于单一云环境&#xff0c;而是同时使用 多家公有云、自建IDC、甚至边缘节点。这种多云模式虽然灵活&#xff0c;但也带来了一个新的挑战&#xff1a;如何让跨云、跨地的数据流动变得高效、安全、可控&#xff1f;答案…

幂等性设计艺术:在分布式重试风暴中构筑坚不可摧的防线

幂等性设计艺术&#xff1a;在分布式重试风暴中构筑坚不可摧的防线​​2023年某支付平台凌晨故障​​&#xff1a;由于网络抖动导致支付指令重复发送&#xff0c;系统在2分钟内处理了​​17万笔重复交易​​&#xff0c;引发​​4.2亿资金风险​​。事故根本原因&#xff1a;​…

从零开始理解NDT算法的原理及应用

1. 概述 NDT&#xff0c;全称 Normal Distributions Transform&#xff08;正态分布变换&#xff09;&#xff0c;是一种广泛使用的点云配准算法&#xff0c;它的核心思想与ICP截然不同&#xff1a;NDT不直接计算点与点之间的对应关系&#xff0c;而是通过概率模型来描述和匹配…

艾体宝案例 | 数据驱动破局:DOMO 如何重塑宠物零售门店的生存法则

某连锁宠物店店长紧盯着电脑屏幕上的Excel表格&#xff0c;手指机械地在键盘上敲击出“CtrlC/V”的组合键——这是她第17次尝试将三个不同系统的数据拼凑到一起。门店POS机导出的销售记录、会员系统的消费偏好、库存管理的临期预警&#xff0c;这些本应串联起门店运营全貌的关键…

极飞科技AI智慧农业实践:3000亩棉田2人管理+产量提15%,精准灌溉与老农操作门槛引讨论

在新疆尉犁县的棉田里&#xff0c;两架农业无人机正沿着设定航线低空飞行&#xff0c;它们掠过之处&#xff0c;传感器实时传回土壤湿度和作物长势数据——这片3000亩的棉田&#xff0c;如今只需要两名90后新农人通过手机管理&#xff0c;产量却比传统种植模式提高了15%。这不是…

企业级-搭建CICD(持续集成持续交付)实验手册

搭建CI/CD(持续集成/持续交付)企业示例 为了让容器构建镜像并可以持续集成&#xff0c;可以自动上传到Harbor仓库&#xff1b;并且业务主机可以通过CD自动从仓库中下载镜像latest版本并实现业务更新。1.环境部署 1.1 环境搭建业务IP域名GitLab172.25.254.50gitlab.dhj.orgJenki…

吃透《数据结构》C 语言版:线性表的类型定义详解

作为数据结构的入门章节&#xff0c;线性表就像 “地基” 一样重要&#xff0c;而第二章 2.3 节的 “线性表的类型定义”&#xff0c;更是理解后续操作&#xff08;插入、删除、查找等&#xff09;的核心前提。今天就结合自己的学习笔记&#xff0c;用通俗的语言拆解这个知识点…

文件系统中的核心数据结构

宏观上文件系统在kernel的形态文件系统运作流程按照:vfs->磁盘缓存->实际磁盘文件系统->通用块设备层->io调度层->块设备驱动层->磁盘。具体流程的详细展现如下如如何理解文件系统中的数据结构&#xff1f;linux中文件系统还有几种核心数据结构分别是super_b…

TDengine与StarRocks在技术架构和适用场景上有哪些主要区别?

TDengine 与 StarRocks 作为国产数据库领域的代表性产品&#xff0c;分别专注于时序数据处理和高性能分析场景&#xff0c;在技术架构和适用场景上存在显著差异。以下从核心架构、数据模型、性能特点及典型应用场景等方面进行对比分析&#xff1a;&#x1f3d7;️ ​​一、技术…

Qt事件_xiaozuo

Qt事件Qt 的事件机制是其实现用户交互和系统响应的核心框架&#xff0c;基于事件驱动模型构建。以下从五个关键方面详细解释其工作原理和用法&#xff1a;1. 事件&#xff08;QEvent&#xff09;的定义与分类事件本质&#xff1a;事件是 QEvent 类或其子类的实例&#xff0c;用…

运动控制技术:自动化与智能驱动的核心

一、运动控制概述运动控制技术是自动化技术和电气拖动技术的融合&#xff0c;以工控机、PLC、DSP等为控制器的运动控制技术融合了微电子技术、计算机技术、检测技术、自动化技术以及伺服控制技术等学科的新成果&#xff0c;在工业生产中起着极为重要的作用。早期的运动控制技术…

链表实战指南:手动实现单链表与双链表的接口及OJ挑战(含完整源码)

文章目录一、链表的概念二、链表的分类三、手动实现单链表1.链表的初始化2.链表的打印3.申请新的节点大小空间4.链表的尾插5.链表的头插6.链表的尾删7.链表的头删8.链表的查找9.在指定位置之前插入数据10.在指定位置之后插入数据11.删除指定节点12.删除指定节点之后的数据13.销…

Spring 事件驱动编程初探:用 @EventListener 轻松处理业务通知

一、核心概念与模型Spring 的事件机制是观察者模式&#xff08;也叫发布-订阅模型&#xff09;的一种典型实现。它主要由三个核心部分组成&#xff1a;事件 (Event)&#xff1a; 承载信息的对象&#xff0c;通常是某种状态变化的通知。可以是继承 ApplicationEvent 的类&#x…

无人机也能称重?电力巡检称重传感器安装与使用指南

在无人机电力巡检中&#xff0c;工程师们常常面临一个棘手难题&#xff1a;如何精确知道新架设或老旧缆线的实际负重&#xff1f; 传统依靠老师傅“肉眼估算”的方法不仅风险极高&#xff0c;而且数据极不准确&#xff0c;给电网安全埋下巨大隐患。难道没有更科学的方法吗&…

第二阶段WinForm-8:特性和反射,加密和解密,单例模式

1_预处理指令 &#xff08;1&#xff09;源代码指定了程序的定义&#xff0c;预处理指令&#xff08;preprocessor directive&#xff09;指示编译器如何处理源代码。例如&#xff0c;在某些情况下&#xff0c;我们希望编译器能够忽略一部分代码&#xff0c;而在其他情况下&am…

基于mac的智能语音处理与应用开发-环境部署

上一次写文章还是上一次&#xff0c;时隔一年再次开启学习之路。新机mac没有开发环境&#xff0c;在gpt老师的指导下开始学习之路。 mac开发环境的部署参考了b站程序员云谦和Clover-You的视频教程&#xff0c;然后结合自身及gpt老师的帮助现在开始部署。 g老师的&#x1f34e…

Java中使用正则表达式的正确打开方式

正则表达式基础语法Java正则表达式基于java.util.regex包&#xff0c;核心类是Pattern和Matcher。基本语法遵循标准正则规范&#xff1a;. 匹配任意单个字符&#xff08;除换行符&#xff09;\d 匹配数字&#xff0c;等价于 [0-9]\w 匹配单词字符&#xff0c;等价于 [a-zA-Z0-9…

Docker中Mysql容器忽略大小写

场景说明 在数据迁移场景中&#xff0c;从一个数据库中将数据迁移到另一个数据&#xff0c;经常会遇到&#xff0c;两个不同数据库之间&#xff0c;一个默认忽略大小写&#xff0c;一个默认不忽略大小写&#xff0c;导致实际业务层服务进行数据库访问时&#xff0c;切换数据库之…