音视频开发—使用FFmpeg将YUV文件编码成H264裸流文件 C语言实现

文章目录

    • 1.准备工作
    • 2.压缩编码工作流程
    • 3.详细步骤
      • 1. 初始化日志和参数检查
      • 2. 输入/输出文件的打开
      • 3. 查找和初始化编码器
      • 4. 打开编码器
      • 5. 帧内存的分配和初始化
      • 6. 设置转换上下文(SWS)
      • 7. 读取和转换数据
      • 8. 编码过程
      • 9. 资源清理
    • 4.完整示例代码

1.准备工作

原始YUV文件,只包含图像的原始信息,无论是播放还是进行H264压缩编码,都需要知晓文件格式。

  • 像素格式:常见的格式有YUV420P、YUV422P、YUV420打包格式:NV12 NV21 等等

  • 分辨率:宽度和高度

FFmpeg播放YUV文件示例:

ffplay -f rawvideo -pixel_format yuv422p -video_size 1920x1080 input_1920x1080_yuv422p_1.yuv 

命令解析:

  • ffplay: 这是用于播放视频和音频文件的命令行媒体播放器。
  • -f rawvideo: 这个选项指定输入文件的格式。rawvideo 表示输入文件是一个未经压缩处理的视频数据流。
  • -pixel_format yuv422p: 指定了像素格式。yuv422p 是一种 YUV 格式,其中 Y、U 和 V 分量是平面分隔的(p 表示平面),并且色度(U 和 V)采样是每两个像素共享一次(即 4:2:2 采样)。这个格式常见于专业视频编辑和后期处理中。
  • -video_size 1920x1080: 设置视频的分辨率为 1920x1080 像素,这通常是全高清视频的标准分辨率。
  • input_1920x1080_yuv422p_1.yuv: 这是输入文件的路径和名称。文件扩展名 .yuv 通常用于存储原始的 YUV 格式视频数据。

效果如下:这里以一张高清图片为示例

在这里插入图片描述

2.压缩编码工作流程

总体流程:在这里插入图片描述

需要注意的是:编码H264一般使用X264的居多,要求输入的格式一般为YUV420格式,因此如果是YUV422需要转换为YUV420格式

3.详细步骤

1. 初始化日志和参数检查

 av_log_set_level(AV_LOG_DEBUG);av_log(NULL, AV_LOG_INFO, "FFMPEG DEBUG LOG BEGIN.....\n");if (argc < 5){printf("Usage: %s <input file> <width> <height> <output file>\n", argv[0]);return -1;}
  • 使用 av_log_set_level 设置日志级别,用于调试信息输出。
  • 检查传入的命令行参数数量,确保提供了足够的信息来进行后续处理。

2. 输入/输出文件的打开

 // 读取YUV 文件input_file = fopen(input_filename, "rb");if (!input_file){fprintf(stderr, "Could not open input file '%s'\n", input_filename);CLEANUP(failure);return -1;}// 打开要写入的文件output_file = fopen(output_filename, "wb");if (!output_file){fprintf(stderr, "Could not open output file '%s'\n", output_filename);CLEANUP(failure);return -1;}
  • 尝试打开输入文件(YUV数据源)和输出文件(存储编码后的视频)。如果文件打开失败,跳转到 failure 标签进行资源释放和退出。

3. 查找和初始化编码器

  • 查找H264编码器。如果找不到编码器,跳转到 failure
    // 查找编码器codec = avcodec_find_encoder(AV_CODEC_ID_H264);if (!codec){av_log(NULL, AV_LOG_ERROR, "H264 Codec not found.....\n");CLEANUP(failure);return -1;}// 分配编码器上下文codec_ctx = avcodec_alloc_context3(codec);if (!codec_ctx){av_log(NULL, AV_LOG_ERROR, "Could not allocate video codec context.....\n");CLEANUP(failure);return -1;}
  • 为编码器分配上下文,并设置编码参数,如比特率、分辨率、帧率等。这些参数对最终视频的质量和文件大小有直接影响。
// 设置编码器的参数codec_ctx->bit_rate = 4000000;  //与图像质量直接挂钩codec_ctx->height = height;codec_ctx->width = width;codec_ctx->time_base = (AVRational){1, 25};codec_ctx->framerate = (AVRational){25, 1};codec_ctx->gop_size = 10;codec_ctx->max_b_frames = 0;             // 设置成0,可以减少编码器负担codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P; // 使用YUV420P进行编码

4. 打开编码器

  • 尝试打开已配置的编码器。如果编码器无法打开,跳转到 failure
    // 打开编码器if (avcodec_open2(codec_ctx, codec, NULL) < 0){av_log(NULL, AV_LOG_ERROR, "Could not open codec.....\n");CLEANUP(failure);return -1;}

5. 帧内存的分配和初始化

  • 分配内存给原始帧 frame 和用于转换的帧 sws_frame。如果分配失败,进行资源清理并跳转到 failure
  • 对这两个帧进行格式设置和内存分配。
  // 分配原始帧和转换帧frame = av_frame_alloc();sws_frame = av_frame_alloc();if (!frame || !sws_frame){fprintf(stderr, "Could not allocate video frame\n");fclose(input_file);fclose(output_file);avcodec_free_context(&codec_ctx);if (frame)av_frame_free(&frame);if (sws_frame)av_frame_free(&sws_frame);return -1;}frame->format = codec_ctx->pix_fmt;frame->width = width;frame->height = height;ret = av_image_alloc(frame->data, frame->linesize, width, height, codec_ctx->pix_fmt, 32);if (ret < 0){CLEANUP(failure);return -1;}sws_frame->format = AV_PIX_FMT_YUV422P;sws_frame->width = width;sws_frame->height = height;ret = av_image_alloc(sws_frame->data, sws_frame->linesize, width, height, AV_PIX_FMT_YUV422P, 32);if (ret < 0){fprintf(stderr, "Could not allocate raw picture buffer for SWS frame\n");CLEANUP(failure);return -1;}

记得也要packet进行初始化,调了半个小时才发现,忘给AVPacket初始化了,一直报内存错误。。。。。。

    // 分配数据包av_init_packet(&packet); // 初始化packetpacket.data = NULL;packet.size = 0;

6. 设置转换上下文(SWS)

  • 初始化用于像素格式转换的 SwsContext。这个上下文负责将YUV422P格式转换成编码器需要的YUV420P格式。
    // 创建SWS上下文sws_ctx = sws_getContext(width, height, AV_PIX_FMT_YUV422P, width, height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);if (!sws_ctx){fprintf(stderr, "Could not initialize the conversion context\n");fclose(input_file);fclose(output_file);if (frame)av_freep(&frame->data[0]);if (sws_frame)av_freep(&sws_frame->data[0]);av_frame_free(&frame);av_frame_free(&sws_frame);avcodec_free_context(&codec_ctx);return -1;}

7. 读取和转换数据

  • 从输入文件中读取YUV422P数据到 sws_frame,然后使用 sws_scale 函数进行格式转换,并存储到 frame
  • 如果读取失败,输出错误信息并跳转到 failure
fread(sws_frame->data[0], 1, width * height * 2, input_file)
sws_scale(sws_ctx, (const uint8_t *const *)sws_frame->data, sws_frame->linesize, 0, height, frame->data, frame->linesize);

8. 编码过程

  • 将转换后的帧发送到编码器。
   // 数据送入编码器ret = avcodec_send_frame(codec_ctx, frame);if (ret < 0){fprintf(stderr, "Error sending frame for encoding\n");CLEANUP(failure);}
  • 循环调用 avcodec_receive_packet 来接收编码后的数据,并将其写入输出文件。
  • 在循环中,使用 av_packet_unref 来释放已经写入文件的数据包内存。
// 接收编码后的数据包while (ret >= 0){ret = avcodec_receive_packet(codec_ctx, &packet);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)break;else if (ret < 0){fprintf(stderr, "Error during encoding\n");CLEANUP(failure);break;}// 写入到文件fwrite(packet.data, 1, packet.size, output_file);av_packet_unref(&packet);}
  • 最后,发送一个空帧到编码器以刷新所有待处理的帧。
    // 刷新编码器ret = avcodec_send_frame(codec_ctx, NULL);if (ret < 0){fprintf(stderr, "Error sending flush packet to encoder\n");CLEANUP(failure);}while (ret >= 0){ret = avcodec_receive_packet(codec_ctx, &packet);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)break;else if (ret < 0){fprintf(stderr, "Error during encoding\n");CLEANUP(failure);break;}fwrite(packet.data, 1, packet.size, output_file);av_packet_unref(&packet);}

9. 资源清理

  • 在正常执行结束和错误处理 (failure 标签) 中,关闭文件、释放分配的内存和其他资源。
  fclose(input_file);fclose(output_file);if (frame)av_freep(&frame->data[0]);if (sws_frame)av_freep(&sws_frame->data[0]);av_frame_free(&frame);av_frame_free(&sws_frame);avcodec_free_context(&codec_ctx);

4.完整示例代码

#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#include <libavutil/log.h>
// 错误处理和资源释放
#define CLEANUP(label) \do                 \{                  \goto label;    \} while (0)int main(int argc, char *argv[])
{av_log_set_level(AV_LOG_DEBUG);av_log(NULL, AV_LOG_INFO, "FFMPEG DEBUG LOG BEGIN.....\n");if (argc < 5){printf("Usage: %s <input file> <width> <height> <output file>\n", argv[0]);return -1;}// 程序输入const char *input_filename = argv[1];int width = atoi(argv[2]);int height = atoi(argv[3]);const char *output_filename = argv[4];// 相关变量初始化AVCodecContext *codec_ctx = NULL;AVCodec *codec = NULL;AVFrame *frame = NULL;AVFrame *sws_frame = NULL;AVPacket packet;struct SwsContext *sws_ctx = NULL;FILE *input_file = NULL;FILE *output_file = NULL;int ret;// 读取YUV 文件input_file = fopen(input_filename, "rb");if (!input_file){fprintf(stderr, "Could not open input file '%s'\n", input_filename);CLEANUP(failure);return -1;}// 打开要写入的文件output_file = fopen(output_filename, "wb");if (!output_file){fprintf(stderr, "Could not open output file '%s'\n", output_filename);CLEANUP(failure);return -1;}// 查找编码器codec = avcodec_find_encoder(AV_CODEC_ID_H264);if (!codec){av_log(NULL, AV_LOG_ERROR, "H264 Codec not found.....\n");CLEANUP(failure);return -1;}// 分配编码器上下文codec_ctx = avcodec_alloc_context3(codec);if (!codec_ctx){av_log(NULL, AV_LOG_ERROR, "Could not allocate video codec context.....\n");CLEANUP(failure);return -1;}// 设置编码器的参数codec_ctx->bit_rate = 4000000;  //与图像质量codec_ctx->height = height;codec_ctx->width = width;codec_ctx->time_base = (AVRational){1, 25};codec_ctx->framerate = (AVRational){25, 1};codec_ctx->gop_size = 10;codec_ctx->max_b_frames = 0;             // 设置成0,可以减少编码器负担codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P; // 使用YUV420P进行编码// 打开编码器if (avcodec_open2(codec_ctx, codec, NULL) < 0){av_log(NULL, AV_LOG_ERROR, "Could not open codec.....\n");CLEANUP(failure);return -1;}// 分配原始帧和转换帧frame = av_frame_alloc();sws_frame = av_frame_alloc();if (!frame || !sws_frame){fprintf(stderr, "Could not allocate video frame\n");fclose(input_file);fclose(output_file);avcodec_free_context(&codec_ctx);if (frame)av_frame_free(&frame);if (sws_frame)av_frame_free(&sws_frame);return -1;}frame->format = codec_ctx->pix_fmt;frame->width = width;frame->height = height;ret = av_image_alloc(frame->data, frame->linesize, width, height, codec_ctx->pix_fmt, 32);if (ret < 0){CLEANUP(failure);return -1;}sws_frame->format = AV_PIX_FMT_YUV422P;sws_frame->width = width;sws_frame->height = height;ret = av_image_alloc(sws_frame->data, sws_frame->linesize, width, height, AV_PIX_FMT_YUV422P, 32);if (ret < 0){fprintf(stderr, "Could not allocate raw picture buffer for SWS frame\n");CLEANUP(failure);return -1;}// 创建SWS上下文sws_ctx = sws_getContext(width, height, AV_PIX_FMT_YUV422P, width, height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);if (!sws_ctx){fprintf(stderr, "Could not initialize the conversion context\n");fclose(input_file);fclose(output_file);if (frame)av_freep(&frame->data[0]);if (sws_frame)av_freep(&sws_frame->data[0]);av_frame_free(&frame);av_frame_free(&sws_frame);avcodec_free_context(&codec_ctx);return -1;}// 分配数据包av_init_packet(&packet); // 初始化packetpacket.data = NULL;packet.size = 0;// 读取YUV422P数据到sws_frame中if (fread(sws_frame->data[0], 1, width * height * 2, input_file) == width * height * 2){sws_scale(sws_ctx, (const uint8_t *const *)sws_frame->data, sws_frame->linesize, 0, height, frame->data, frame->linesize);frame->pts = 0; // 设置时间戳// 数据送入编码器ret = avcodec_send_frame(codec_ctx, frame);if (ret < 0){fprintf(stderr, "Error sending frame for encoding\n");CLEANUP(failure);}// 接收编码后的数据包while (ret >= 0){ret = avcodec_receive_packet(codec_ctx, &packet);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)break;else if (ret < 0){fprintf(stderr, "Error during encoding\n");CLEANUP(failure);break;}// 写入到文件fwrite(packet.data, 1, packet.size, output_file);av_packet_unref(&packet);}}else{fprintf(stderr, "Error reading input file\n");}// 刷新编码器ret = avcodec_send_frame(codec_ctx, NULL);if (ret < 0){fprintf(stderr, "Error sending flush packet to encoder\n");CLEANUP(failure);}while (ret >= 0){ret = avcodec_receive_packet(codec_ctx, &packet);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)break;else if (ret < 0){fprintf(stderr, "Error during encoding\n");CLEANUP(failure);break;}fwrite(packet.data, 1, packet.size, output_file);av_packet_unref(&packet);}// 释放资源fclose(input_file);fclose(output_file);if (frame)av_freep(&frame->data[0]);if (sws_frame)av_freep(&sws_frame->data[0]);av_frame_free(&frame);av_frame_free(&sws_frame);avcodec_free_context(&codec_ctx);return 0;failure:if (input_file)fclose(input_file);if (output_file)fclose(output_file);if (frame)av_freep(&frame->data[0]);if (sws_frame)av_freep(&sws_frame->data[0]);av_frame_free(&frame);av_frame_free(&sws_frame);avcodec_free_context(&codec_ctx);return -1;
}

该程序输出结果为.h264的码流文件,这里并没有添加文件容器(MP4/MKV/MOV)

可以直接调用ffplay进行播放,无需指定任何参数,因为已经封装到了NAL单元中,查看是否压编码成功。

可以看出有了显著的压缩效果,如果就降低码率,还可以进行缩小文件大小

在这里插入图片描述

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

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

相关文章

熊海CMS漏洞练习平台的一次xss、sql注入、越权黑盒思路分析

简介 熊海CMS是由熊海开发的一款功能丰富的网站综合管理系统&#xff0c;广泛应用于个人博客、个人网站以及企业网站&#xff0c;本文章用于黑盒测试&#xff0c;如果需要「源码审计」后台回复【「CMS」】获取即可&#xff0c;精心准备了40多个cms源码漏洞平台&#xff0c;供宝…

在浏览器控制台中输出js对象,为什么颜色不同,有深有浅

打开console&#xff0c;输入自定义的javascript对象的时候&#xff0c;打开看发现对象的属性是深紫色&#xff0c;后面有一些对象是浅紫色的&#xff0c;比如Array对象和一堆SVG,HTML,CSS开头的对象&#xff0c;常用的prototype和__proto__也是浅紫色的。 请问这里深紫和浅紫…

什么是Maven以及如何配置Maven

T04BF &#x1f44b;专栏: 算法|JAVA|MySQL|C语言 &#x1faf5; 今天你敲代码了吗 文章目录 1.Maven1.1什么是Maven1.2Maven的好处1.3使用idea创建一个Maven项目1.4Maven的核心功能1.4.1项目构建 1.5Maven仓库1.5.2 中央仓库1.5.3 私有服务器(私服) 1.6Maven设置国内源 1.Mave…

[pytorch]常用函数(自用)

一、公共部分 1、torch.linespace 返回一维张量&#xff0c;在start和end之间&#xff08;包括start也包括end&#xff09;的均匀间隔的steps个点&#xff0c;长度为steps。 print(torch.linspace(1,10,3)) #输出tensor([ 1.0000, 5.5000, 10.0000]) print(torch.linspace…

文本分类--NLP-AI(八)

文本分类任务 任务简介1.字符数值化方式1方式2 2.池化&#xff08;pooling&#xff09;3.全连接层4.归一化函数&#xff08;Sigmoid&#xff09;5.总结 从任务抽象新的技术点Embedding层池化层 任务简介 任务介绍&#xff1a; 字符串分类&#xff0c;根据一句话的含妈量&#…

伊利25届校招24年社招网申入职北森测评题库全攻略!一文通!

伊利校招社招网申测评全攻略&#x1f680; 亲爱的求职小伙伴们&#xff0c;今天我要分享一份伊利校招社招网申测评的全攻略&#xff0c;希望能助你们一臂之力&#xff01; 测评概览 伊利的网申测评分为六个部分&#xff0c;总共约60分钟的答题时间&#xff0c;涵盖了言语逻辑、…

【微信小程序开发】如何定义公共的js函数,其它页面可以调用

在微信小程序开发中&#xff0c;可以通过以下步骤定义和使用公共的 JS 函数&#xff0c;使得其它页面可以调用&#xff1a; 1. 创建一个公共的 JS 文件&#xff1a;在项目的 utils 目录下创建一个 JS 文件&#xff0c;例如 utils/util.js。 2. 定义公共函数&#xff1a;在 uti…

在word中删除endnote参考文献之间的空行

如图&#xff0c;在References中&#xff0c;每个文献之间都有空行。不建议手动删除。打开Endnote。 打开style manager 删除layout中的换行符。保存&#xff0c;在word中更新参考文献即可。

Python和C++全球导航卫星系统和机器人姿态触觉感知二分图算法

&#x1f3af;要点 &#x1f3af;马尔可夫随机场网格推理学习 | &#x1f3af;二维伊辛模型四连网格模型推理 | &#x1f3af;统计物理学模型扰动与最大乘积二值反卷积 | &#x1f3af;受限玻尔兹曼机扰动和最大乘积采样 | &#x1f3af;视觉概率生成模型测试图像 &#x1f3…

从课本上面开始学习的51单片机究竟有什么特点,在现在的市场上还有应用吗?

引言 51单片机&#xff0c;作为一种经典的微控制器&#xff0c;被广泛应用于各种嵌入式系统中。尽管如今ARM架构的高性能低成本单片机在市场上占据主导地位&#xff0c;但51单片机凭借其独特的优势依然在某些领域保持着应用价值。本文将深入探讨51单片机的特点、架构、应用以及…

数学建模·模糊评价法

模糊评价法 一种解决评价问题或者得出最佳方案的方法 主观性仍比较强 具体定义 三集&#xff1a;因素集&#xff0c;评语集和权重集&#xff0c;通过模拟矩阵的处理得到最合理的评语 具体步骤 因素集 因素集的确定不难&#xff0c;难在对分级评价时&#xff0c;对因素集的分级…

LeetCode --- 134双周赛

题目 3206. 交替组 I 3207. 与敌人战斗后的最大分数 3208. 交替组 II 3209. 子数组按位与值为 K 的数目 一、交替组 I & II 题目中问环形数组中交替组的长度为3的子数组个数&#xff0c;主要的问题在于它是环形的&#xff0c;我们要考虑首尾相接的情况&#xff0c;如何…

leetcode 404. 左叶子之和

给定二叉树的根节点 root &#xff0c;返回所有左叶子之和。 示例 1&#xff1a; 输入: root [3,9,20,null,null,15,7] 输出: 24 解释: 在这个二叉树中&#xff0c;有两个左叶子&#xff0c;分别是 9 和 15&#xff0c;所以返回 24示例 2: 输入: root [1] 输出: 0提示: 节点…

Linux 下使用Docker安装redis

redis&#xff1a; 是一个高性能的&#xff0c;键值对的&#xff0c;将数据存储到内存中的非关系型数据库&#xff08;nosql数据库 not only sql&#xff09; 高性能&#xff1a;数据存在内存中&#xff0c;直接访问内存 键值对&#xff1a;新闻id&#xff08;键&#xff09…

c++数据结构--构造无向图(算法6.1),深度和广度遍历

实验内容&#xff1a; 实现教材算法6.2利用邻接矩阵构造无向图的算法&#xff0c;提供从邻接矩阵获得邻接表的功能&#xff0c;在此基础上进行深度优先遍历和广度优先遍历。 实验步骤&#xff1a; &#xff08;1&#xff09;按照实验要求编写代码&#xff0c;构造无向图。 …

浅谈数学模型在UGC/AIGC游戏数值调参中的应用(AI智能体)

浅谈数学模型在UGC/AIGC游戏数值调参中的应用 ygluu 卢益贵 关键词&#xff1a;UGC、AIGC、AI智能体、大模型、数学模型、游戏数值调参、游戏策划 一、前言 在策划大大群提出《游戏工厂&#xff1a;AI&#xff08;AIGC/ChatGPT&#xff09;与流程式游戏开发》讨论之后就已完…

Hi3861 OpenHarmony嵌入式应用入门--HTTPD

httpd 是 Apache HTTP Server 的守护进程名称&#xff0c;Apache HTTP Server 是一种广泛使用的开源网页服务器软件。 本项目是从LwIP中抽取的HTTP服务器代码&#xff1b; Hi3861 SDK中已经包含了一份预编译的lwip&#xff0c;但没有开启HTTP服务器功能&#xff08;静态库无法…

NiFi1.25版本HTTPS模式下RestAPI使用入门

Apache NiFi 是一个强大的数据流处理工具&#xff0c;通过其 REST API&#xff0c;用户可以远程管理和控制数据流处理器。本文将介绍如何使用 NiFi 1.25 版本HTTPS 模式下Rest API&#xff0c;包括获取 token、获取组件信息、启动和停止组件、以及更改组件的调度频率等操作。 …

Linux vim文本编辑器

Vim&#xff08;Vi IMproved&#xff09;是一个高度可配置的文本编辑器&#xff0c;它是Vi编辑器的增强版本&#xff0c;广泛用于程序开发和系统管理。Vim不仅保留了Vi的所有功能&#xff0c;还增加了许多新特性&#xff0c;使其更加强大和灵活。 Vim操作模式 普通模式&#xf…

科普文:微服务之Apollo配置中心

1. 基本概念 由于Apollo 概念比较多&#xff0c;刚开始使用比较复杂&#xff0c;最好先过一遍概念再动手实践尝试使用。 1.1、背景 随着程序功能的日益复杂&#xff0c;程序的配置日益增多&#xff0c;各种功能的开关、参数的配置、服务器的地址……对程序配置的期望值也越来…