音视频开发—FFmpeg播放YUV文件,YUV转换为JPEG操作

文章目录

    • 1.使用命令行播放YUV数据
      • 1.1命令解析
      • 1.2参数说明
    • 2.使用C语言实现将YUV数据转为JPEG图片格式
      • 2.1需求分析
      • 2.2读取YUV源文件
      • 2.3将YUV数据封装为AVFrame
      • 2.4将NV12 转换为YUV420平面格式
      • 2.5初始化MJPEG编码器
      • 2.6将YUV420P编码为JPEG
      • 2.7将编码数据写入图片文件
      • 2.8完整代码

1.使用命令行播放YUV数据

使用 FFmpeg 的命令行工具 ffplay 可以很方便地播放 YUV 文件。但是不同于MP4或MKV格式文件,YUV 存储的原始的信息,需要指定分辨率参数和数据格式。

以下是一个典型的命令及其参数解析:

ffplay -pix_fmt nv12 -s 1920*1080 input_test.yuv 

1.1命令解析

  • ffplay:这是 FFmpeg 的播放工具,用于播放音视频文件和流。
  • -f rawvideo:指定输入文件格式为原始视频数据(未压缩)。
  • -pixel_format yuv420p:指定像素格式为 YUV 4:2:0 平面格式(planar)。YUV420p 是一种常见的 YUV 格式,其中 Y、U、V 分量分别存储在独立的平面上。
  • -video_size 1920x1080:指定视频的宽度和高度。这个参数对于原始视频数据非常重要,因为它没有头文件来存储这些信息。
  • input.yuv:这是输入文件的名称。

1.2参数说明

  1. -f rawvideo
    • 表示输入文件是原始视频数据,FFmpeg 不会自动检测文件格式,需要显式指定。
  2. -pixel_format yuv420p
    • yuv420p 表示 YUV 4:2:0 平面格式,Y、U、V 分量分别存储在独立的平面上。
    • 常见的像素格式还有 nv12(UV 分量交织存储)等。
  3. -video_size 1920x1080
    • 指定视频的分辨率(宽度 x 高度)。
    • 必须准确指定,否则播放时会出现图像错位或播放失败。

2.使用C语言实现将YUV数据转为JPEG图片格式

2.1需求分析

项目需求:从camera sensor传出来的数据为一帧帧的NV12格式数据,需要将每帧数据转为JPEG图片。

分析转换流程:

在这里插入图片描述

其中如果一开始的YUV数据本身已经为平面格式的话,无需进行转换,直接可以在初始化完之后,进行编码

MJPEG支持主流的平面格式,如果是打包格式,则需要进行转换为平面格式

AV_PIX_FMT_YUVJ420P,
AV_PIX_FMT_YUVJ422P,  
AV_PIX_FMT_YUVJ444P,  

2.2读取YUV源文件

int readYUVFile(const char *filename, int width, int height, unsigned char *&originalData)
{// 读取YUV 文件FILE *file = fopen(filename, "rb");if (!file){cerr << "Failed to open file." << endl;return -1;}size_t originalSize = width * height * 3 / 2; // YUV420 8-bit 1.5 bytes per pixeloriginalData = new unsigned char[originalSize];// 读取原始数据  这里是读取一帧, 如果要多帧数据,则使用循环if (fread(originalData, sizeof(unsigned char), originalSize, file) != originalSize){cerr << "Failed to read data." << endl;delete[] originalData;fclose(file);return -1;}fclose(file);return 0;
}

这里是读取了一帧,因为我的文件本身只有一帧数据,如果是多帧数据,那么需要考虑使用while循环

2.3将YUV数据封装为AVFrame

// 将一帧YUV数据 封装成AVFrame
int yuv_dataToAVFrame(unsigned char *originalData)
{original_frame = av_frame_alloc();if (!original_frame){cerr << "Failed to allocate AVFrame." << std::endl;return -1;}// 设置frame 参数original_frame->width = 1920;original_frame->height = 1080;original_frame->format = AV_PIX_FMT_NV12;if (av_frame_get_buffer(original_frame, 0) < 0){cerr << "Failed to alloc frame data" << endl;av_frame_free(&original_frame);return -1;}// copy data to frameint ret = av_image_fill_arrays(original_frame->data, original_frame->linesize, originalData, AV_PIX_FMT_NV12, 1920, 1080, 1);if (ret < 0){std::cerr << "Failed to fill frame data." << std::endl;av_frame_free(&original_frame);return -1;}
}

Frame不仅仅存放了原数据,还需要指定一些参数,宽度和高度,编码格式,

2.4将NV12 转换为YUV420平面格式

int nv12_to_yuv420p( AVFrame *&frame, AVFrame *&nv12_frame,int width,int height,const unsigned char *nv12_data){frame = av_frame_alloc();if (!frame) {std::cerr << "Failed to allocate AVFrame." << std::endl;return -1;}frame->format = AV_PIX_FMT_YUV420P;frame->width = width;frame->height = height;av_image_alloc(frame->data, frame->linesize, width, height, AV_PIX_FMT_YUV420P, 1);nv12_frame = av_frame_alloc();if (!nv12_frame) {std::cerr << "Failed to allocate AVFrame for NV12." << std::endl;av_frame_free(&frame);return -1;}nv12_frame->format = AV_PIX_FMT_NV12;nv12_frame->width = width;nv12_frame->height = height;av_image_fill_arrays(nv12_frame->data, nv12_frame->linesize, nv12_data, AV_PIX_FMT_NV12, width, height, 1);struct SwsContext *sws_ctx = sws_getContext(width, height, AV_PIX_FMT_NV12, width, height, AV_PIX_FMT_YUV420P, 0, nullptr, nullptr, nullptr);if (!sws_ctx) {std::cerr << "Could not create SwsContext." << std::endl;av_frame_free(&nv12_frame);av_frame_free(&frame);return -1;}cout<<"scale begin"<<endl;sws_scale(sws_ctx, nv12_frame->data, nv12_frame->linesize, 0, height, frame->data, frame->linesize);sws_freeContext(sws_ctx);cout<<"scale endl"<<endl;
}

如果数据本身已经是平面格式,无需这个步骤

2.5初始化MJPEG编码器

int init_mjpeg_encoder(AVFrame *frame)
{// 寻找编码器AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_MJPEG);if (!codec){std::cerr << "Codec not found" << std::endl;return -1;}// 分配编码器上下文codec_ctx = avcodec_alloc_context3(codec);if (!codec_ctx){std::cerr << "Could not allocate video codec context" << std::endl;return -1;}// 配置编码器参数codec_ctx->bit_rate = 400000;codec_ctx->width = frame->width;codec_ctx->height = frame->height;codec_ctx->time_base = {1, 25};codec_ctx->pix_fmt = AV_PIX_FMT_YUVJ420P;   // // 打开编码器if (avcodec_open2(codec_ctx, codec, nullptr) < 0){std::cerr << "Could not open codec" << std::endl;avcodec_free_context(&codec_ctx);return -1;}return 0;
}

这里需注意:设定编码器上下文支持的格式,而且要与Frame的格式对应,为平面格式,否则会编码失败

2.6将YUV420P编码为JPEG

int frame_encode(AVFrame *frame)
{//初始化数据包packet = av_packet_alloc();if (!packet){std::cerr << "Could not allocate AVPacket" << std::endl;avcodec_free_context(&codec_ctx);return -1;}//发送原始数据帧if(!codec_ctx){cout<<"codec_ctx is null"<<endl;return -1;}if(!frame){cout<<"frame is null"<<endl;return -1;}int ret = avcodec_send_frame(codec_ctx,frame);if(ret<0){std::cerr << "Error sending frame to codec context" << std::endl;av_packet_free(&packet);avcodec_free_context(&codec_ctx);return -1;}// 接收编码后的数据帧ret = avcodec_receive_packet(codec_ctx, packet);if (ret < 0) {std::cerr << "Error receiving packet from codec context" << std::endl;av_packet_free(&packet);avcodec_free_context(&codec_ctx);return -1;}return 0;
}

这里仅针对一帧完成了编码,多帧请考虑使用循环

2.7将编码数据写入图片文件

int save_frame_as_jpeg(const char *filename){std::ofstream outfile(filename, std::ios::out | std::ios::binary);if (!outfile.is_open()) {std::cerr << "Could not open output file" << std::endl;av_packet_free(&packet);avcodec_free_context(&codec_ctx);return -1;}outfile.write(reinterpret_cast<const char *>(packet->data), packet->size);outfile.close();return 0;
}

2.8完整代码

头文件 hpp

#ifndef __STDC_CONSTANT_MACROS
#define __STDC_CONSTANT_MACROS
#endifextern "C"
{
#include <stdio.h>
#include "libavutil/avutil.h"
#include "libavdevice/avdevice.h"
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"
#include <libswscale/swscale.h>
#include "libswresample/swresample.h"
#include "libavutil/imgutils.h"
}
#include <fstream>
#include <iostream>
using namespace std;
AVFormatContext *format_ctx;
AVPacket *packet;
AVFrame *original_frame,*new_frame;
AVCodec *codec;
AVCodecContext *codec_ctx;

c++实现

#include "readYUV.hpp"
int readYUVFile(const char *filename, int width, int height, unsigned char *&originalData)
{// 读取YUV 文件FILE *file = fopen(filename, "rb");if (!file){cerr << "Failed to open file." << endl;return -1;}size_t originalSize = width * height * 3 / 2; // YUV420 8-bit 1.5 bytes per pixeloriginalData = new unsigned char[originalSize];// 读取原始数据  这里是读取一帧, 如果要多帧数据,则使用循环if (fread(originalData, sizeof(unsigned char), originalSize, file) != originalSize){cerr << "Failed to read data." << endl;delete[] originalData;fclose(file);return -1;}fclose(file);return 0;
}
// 将一帧YUV数据 封装成AVFrame
int yuv_dataToAVFrame(unsigned char *originalData)
{original_frame = av_frame_alloc();if (!original_frame){cerr << "Failed to allocate AVFrame." << std::endl;return -1;}// 设置frame 参数original_frame->width = 1920;original_frame->height = 1080;original_frame->format = AV_PIX_FMT_NV12;if (av_frame_get_buffer(original_frame, 0) < 0){cerr << "Failed to alloc frame data" << endl;av_frame_free(&original_frame);return -1;}// copy data to frameint ret = av_image_fill_arrays(original_frame->data, original_frame->linesize, originalData, AV_PIX_FMT_NV12, 1920, 1080, 1);if (ret < 0){std::cerr << "Failed to fill frame data." << std::endl;av_frame_free(&original_frame);return -1;}
}
// 初始化 MJPEG编码器
int init_mjpeg_encoder(AVFrame *frame)
{// 寻找编码器AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_MJPEG);if (!codec){std::cerr << "Codec not found" << std::endl;return -1;}// 分配编码器上下文codec_ctx = avcodec_alloc_context3(codec);if (!codec_ctx){std::cerr << "Could not allocate video codec context" << std::endl;return -1;}// 配置编码器参数codec_ctx->bit_rate = 400000;codec_ctx->width = frame->width;codec_ctx->height = frame->height;codec_ctx->time_base = {1, 25};codec_ctx->pix_fmt = AV_PIX_FMT_YUVJ420P;   // // 打开编码器if (avcodec_open2(codec_ctx, codec, nullptr) < 0){std::cerr << "Could not open codec" << std::endl;avcodec_free_context(&codec_ctx);return -1;}return 0;
}
// 对一帧 frame 进行编码
int frame_encode(AVFrame *frame)
{//初始化数据包packet = av_packet_alloc();if (!packet){std::cerr << "Could not allocate AVPacket" << std::endl;avcodec_free_context(&codec_ctx);return -1;}//发送原始数据帧if(!codec_ctx){cout<<"codec_ctx is null"<<endl;return -1;}if(!frame){cout<<"frame is null"<<endl;return -1;}int ret = avcodec_send_frame(codec_ctx,frame);if(ret<0){std::cerr << "Error sending frame to codec context" << std::endl;av_packet_free(&packet);avcodec_free_context(&codec_ctx);return -1;}// 接收编码后的数据帧ret = avcodec_receive_packet(codec_ctx, packet);if (ret < 0) {std::cerr << "Error receiving packet from codec context" << std::endl;av_packet_free(&packet);avcodec_free_context(&codec_ctx);return -1;}return 0;
}
// 将 NV12 数据帧 转换为 YUV420P 数据帧  yuv420sp ---->yuv420p 从打包格式转换为平面格式
int nv12_to_yuv420p( AVFrame *&frame, AVFrame *&nv12_frame,int width,int height,const unsigned char *nv12_data){frame = av_frame_alloc();if (!frame) {std::cerr << "Failed to allocate AVFrame." << std::endl;return -1;}frame->format = AV_PIX_FMT_YUV420P;frame->width = width;frame->height = height;av_image_alloc(frame->data, frame->linesize, width, height, AV_PIX_FMT_YUV420P, 1);nv12_frame = av_frame_alloc();if (!nv12_frame) {std::cerr << "Failed to allocate AVFrame for NV12." << std::endl;av_frame_free(&frame);return -1;}nv12_frame->format = AV_PIX_FMT_NV12;nv12_frame->width = width;nv12_frame->height = height;av_image_fill_arrays(nv12_frame->data, nv12_frame->linesize, nv12_data, AV_PIX_FMT_NV12, width, height, 1);struct SwsContext *sws_ctx = sws_getContext(width, height, AV_PIX_FMT_NV12, width, height, AV_PIX_FMT_YUV420P, 0, nullptr, nullptr, nullptr);if (!sws_ctx) {std::cerr << "Could not create SwsContext." << std::endl;av_frame_free(&nv12_frame);av_frame_free(&frame);return -1;}cout<<"scale begin"<<endl;sws_scale(sws_ctx, nv12_frame->data, nv12_frame->linesize, 0, height, frame->data, frame->linesize);sws_freeContext(sws_ctx);cout<<"scale endl"<<endl;
}
int save_frame_as_jpeg(const char *filename){std::ofstream outfile(filename, std::ios::out | std::ios::binary);if (!outfile.is_open()) {std::cerr << "Could not open output file" << std::endl;av_packet_free(&packet);avcodec_free_context(&codec_ctx);return -1;}outfile.write(reinterpret_cast<const char *>(packet->data), packet->size);outfile.close();return 0;
}
int main()
{const char *filename = "input_test.yuv";unsigned char *originalData = nullptr;// 将读取的内存数据 赋值给originalDataint ret = readYUVFile(filename, 1920, 1080, originalData);if (ret < 0){cout << "open file fail " << endl;return -1;}// 将yuvdata 转化为 frameyuv_dataToAVFrame(originalData);//originalFrame 转化为 yuv420p framenv12_to_yuv420p(new_frame,original_frame,1920,1080,originalData);// 打开编码器ret = init_mjpeg_encoder(new_frame);if (ret < 0){cout << "open mjpeg encoder fail" << endl;return -1;}//编码ret =  frame_encode(new_frame);if(ret <0){cout <<"encode fail"<<endl;return -1;}//输出文件ret = save_frame_as_jpeg("output.jpeg");if(ret <0){cout <<"save jpeg fail"<<endl;return -1;}cout<<"yuv data susccess convert jpeg!"<<endl;//收尾工作av_packet_free(&packet);avcodec_free_context(&codec_ctx);delete[] originalData;av_frame_free(&original_frame);av_frame_free(&new_frame);return 0;
}

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

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

相关文章

App自动化测试_Python+Appium使用手册

一、Appium的介绍 Appium是一款开源的自动化测试工具&#xff0c;支持模拟器和真机上的原生应用、混合应用、Web应用&#xff1b;基于Selenium二次开发&#xff0c;Appium支持Selenium WebDriver支持的所有语言&#xff08;java、 Object-C 、 JavaScript 、p hp、 Python等&am…

安装flask:后端框架的学习之旅

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、引言 二、创建虚拟环境 1. 引入虚拟环境的概念 2. 创建Flask虚拟环境 三、安装Flask…

CSRF跨站请求伪造漏洞

CSRF跨站请求伪造漏洞 1.CSRF漏洞概述2.防御CSRF攻击3.CSRF防御绕过CSRF令牌未绑定到用户会话自定义标头令牌绕过绕过Referer检查关键词绕过 4.利用示例使用HTML标签进行GET表单 GET 请求表单POST请求通过 iframe 发送表单 POST 请求Ajax POST 请求 5.CSRF BP 验证方法6.CSRF测…

HTTP协议介绍与TCP协议的区别

1、HTTP介绍 HTTP&#xff08;超文本传输协议&#xff0c;Hypertext Transfer Protocol&#xff09;是一种用于从网络传输超文本到本地浏览器的传输协议。它定义了客户端与服务器之间请求和响应的格式。HTTP 是基于TCP/IP 进行数据的通信&#xff0c;通常使用端口 80/8080。HTT…

软件需求规格说明书(Word原件@配套软件全资料)

软件需求规格说明书编写规范编写规范 1.项目背景 2.项目目标 3.系统架构 4.总体流程 5.名称解释 6.功能模块 软件项目相关全套精华资料包获取方式①&#xff1a;点我获取 获取方式②&#xff1a;本文末个人名片直接获取。 软件资料清单列表部分文档清单&#xff1a;工作安排任…

Optional类

一、概述 泛型类、java8引进的、java.util包里 二、作用 解决空指针异常带来的不便 三、做法 将对象封装为一个Optional对象&#xff0c;如果封装的对象为空&#xff08;即该对象不存在&#xff09;&#xff0c;可以使用默认值和或者执行默认操作 四、方法 1、empty() 创…

【Qt知识】Qt Creator快捷键

以下是Qt Creator中的一些常用快捷键列表&#xff08;持续更新&#xff09;&#xff1a; 基本编辑 多行注释/取消多行注释: Ctrl /编译工程: Ctrl B运行工程: Ctrl R整行上移/下移: Ctrl Shift ↑/↓查找: Ctrl F函数声明和定义切换: F2向下查找: F3头文件和源文件切换:…

【postgresql初级使用】初识触发器,在数据行发生变化时自动执行用户行为,也可以SQL级别触发,特别是视图上可以有触发器了

初识触发器 ​专栏内容&#xff1a; postgresql使用入门基础手写数据库toadb并发编程 个人主页&#xff1a;我的主页 管理社区&#xff1a;开源数据库 座右铭&#xff1a;天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物. 文章目录 初识触发器概…

【成品设计】基于RT-thread星火开发板的智能监测系统设计

《基于RT-thread星火开发板的智能监测系统设计》 所需器件&#xff1a; 主控&#xff1a;STM32F407星火开发板。温湿度传感器&#xff1a;采集当前环境中的温湿度。环境传感器&#xff1a;采集当前环境中的光照强度。CO2传感器&#xff0c;采集当前环境中的C02浓度。粉尘传感…

excel怎么对非数字求和汇总?

如&#xff1a;学生小王的成绩为&#xff1a;A&#xff0c;A&#xff0c;A&#xff0c;A&#xff0c;B&#xff0c;B-……想得到的成绩汇总求和为&#xff1a;2A,2A,1B,1B- 如果在低版本里&#xff0c;用公式计算可能相当复杂&#xff0c;但是有了TEXTJOIN函数和UNIQUE函数&…

校园交友|基于SprinBoot+vue的校园交友网站(源码+数据库+文档)

校园交友网站 目录 基于SprinBootvue的校园交友网站 一、前言 二、系统设计 三、系统功能设计 1系统功能模块 2后台功能模块 5.2.1管理员功能模块 5.2.2用户功能模块 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#x…

CSS学习笔记:bootstrap的介绍、下载、使用及响应式布局的实现

介绍 Bootstrap 是由 Twitter 公司开发维护的前端 UI 框架&#xff0c;它提供了大量编写好的CSS 样式 bootstrap过时了吗&#xff1f; 其实在学bootstrap这一部分的网课时&#xff0c;在弹幕上看到一些同学说bootstrap已经过时了&#xff0c;这里谈谈我的看法 我的前端学习…

3天13部,端午档电影数何以是五一档2倍?

离端午还有12天&#xff0c;院线端午档再上热搜。 截止至5月29日&#xff0c;本次端午档将有13部电影在3天的节假日内集中上映&#xff0c;这一数量仅比2021年端午档的最高纪录少了2部&#xff0c;几乎是今年五一档期上映影片数量&#xff08;7部&#xff09;的两倍。 并且与…

UE5 Http Server

前言 最近要用UE 作为一个服务器去接收来自外部的请求&#xff0c;从而在UE中处理一些内容&#xff0c;但是之前只做过请求&#xff0c;哪整过这玩意&#xff0c;短期内还得出结果&#xff0c;那怎么搞嘞&#xff0c;本着省事的原则就找找呗&#xff0c;有没有现成的&#xff0…

结构体中内存的对齐

前言 学C的同学应该知道~ 想精通C语言就不得不面对—指针与内存 续上次指针进阶&#xff0c;这一章我来聊一聊C语言内存对齐的问题 学习结构体的你有没有注意过结构体向系统申请的内存为多少呢的&#x1f601; 思考 #include<stdio.h> typedef struct s1 {char a;char …

Qt6同时使用Qt3DCore与QtGUI时QTransform类冲突问题

在Qt GUI模块有一个QTransform类&#xff0c;在Qt3D Core模块也有一个QTransform类&#xff0c;如果不特殊指定一下会报错

全新/二手KEITHLEY 2400 数字万用表

吉时利Keithley 2400数字源表&#xff0c;200V&#xff0c;1A&#xff0c;20W Keithley 2400 源表是一款 20W 仪器&#xff0c;可输出和测量 5V&#xff08;输出&#xff09;和 1V&#xff08;测量&#xff09;至 200V DC 的电压以及 10pA 至 1A 的电流。该万用表功能包括高重复…

【linux深入剖析】进程间通信

&#x1f341;你好&#xff0c;我是 RO-BERRY &#x1f4d7; 致力于C、C、数据结构、TCP/IP、数据库等等一系列知识 &#x1f384;感谢你的陪伴与支持 &#xff0c;故事既有了开头&#xff0c;就要画上一个完美的句号&#xff0c;让我们一起加油 目录 1.进程间通信目的2. 什么…

react 怎样向ant table添加按钮,以及文本溢出隐藏?

这些都是一些组件自带的方法&#xff0c;只不过是不知道该怎么用&#xff0c;上面的内容可以直接拿去用&#xff0c;&#xff08;事件除外&#xff0c;要自己绑&#xff0c;还有引入的组件&#xff09;&#xff01;

数据结构:栈和队列的练习题1(括号匹配问题)

题目描述&#xff1a; 思路&#xff1a;我们首先可以把出现的情况大致分为以下几类&#xff1a; 因为涉及匹配问题&#xff0c;所以所有的左括号和右括号肯定要分开来整理。如果我们直接去匹配的话&#xff08;像第一行的从左到右独立匹配&#xff09;是行得通的&#xff0c;但…