音视频开发—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.rhkb.cn/news/339916.html

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

相关文章

Python 图书馆管理系统 有GUI界面 【含Python源码 MX_031期】

使用python3&#xff0c;PyQt5&#xff0c;Sqlite3数据库搭建 数据库版本为MySQL&#xff1a;Python 图书馆管理系统&#xff08;MySQL数据库&#xff09; 有GUI界面 【含Python源码 MX_032期】-CSDN博客 主要功能&#xff1a; 用户注册、登录、修改密码、用户管理存储图书信…

详解Java反序列化漏洞

001&#xff1a;序列化基本概念 序列化&#xff1a;将对象写入IO流中反序列化&#xff1a;从IO流中恢复对象意义&#xff1a;序列化机制允许将实现序列化的Java对象转换位字节序列&#xff0c;这些字节序列可以保存在磁盘上&#xff0c;或通过网络传输&#xff0c;以达到以后恢…

C++多线程同步

C使用多线程必须包含头文件 #include <thread> 来实现 当多个线程同事访问一个对象的时候&#xff0c;会产生数据竞争现象。 这个时候&#xff0c;就可以加锁&#xff0c;同步资源&#xff0c;解决数据竞争。 最简单就是互斥锁mutex 上代码&#xff0c;计算一个数自增到1…

python常见数据分析函数

apply DataFrame.apply(func, axis0, broadcastFalse, rawFalse, reduceNone, args(), **kwds) 第一个参数是函数 可以在Series或DataFrame上执行一个函数 支持对行、列或单个值进行处理 import numpy as np import pandas as pdf lambda x: x.max()-x.min()df pd.DataFrame(…

韩顺平0基础学java——第15天

p303-326 重写override 和重载做个对比 注&#xff1a;但子类可以扩大范围&#xff0c;比如父类是protected&#xff0c;子类可以是public 多态 方法或对象具有多种形态&#xff0c;是面向对象的第三大特征&#xff0c;多态是建立在封装和继承基础之上的。 多态的具体体现…

【JavaEE】Servlet

文章目录 一、Servlet 是什么二、如何创建Servlet程序1、创建项目2、引入依赖3、创建目录4、编写代码5、打包程序6、部署程序7、验证程序 一、Servlet 是什么 二、如何创建Servlet程序 1、创建项目 2、引入依赖 Maven 项目创建完后&#xff0c;会自动生成一个 pom.xml 的文…

Spi Pwm Tim 对比分析

spi SPI时序图 (spi是主从机 所以主机需要从机数据 需要主极先喊从机 把从机喊答应了 才能开始读从机的数据&#xff09; cpol时钟极性 和cpha时钟相位分析 1.cpha为高&#xff0c;cpol为高&#xff0c;则偶数上升沿有效 2.cpha为高&#xff0c;cpol为低&#xff0c;则偶数…

山东军博会—2024年智能装备和通信技术展:见证类脑视觉芯片如何重塑未来

随着人工智能技术的飞速发展&#xff0c;类脑计算成为了科研领域的一个热点。最近&#xff0c;我国科学家成功研发出世界首款类脑互补视觉芯片&#xff0c;这一重大突破不仅标志着我国在人工智能硬件领域迈出了重要一步&#xff0c;也为未来的智能设备带来了无限可能。本文将从…

【轻触按键】终篇 -- 纯硬 VS 复合

1、选型 2、开关机电路–填坑1 3、开关机电路–填坑1.a 4、开关机电路–复合芯片解决方案 填坑2 总结 上述几篇&#xff0c;基本上都是比较靠谱的硬件方案&#xff1b; ①所有开关均关闭&#xff1b; X1灯亮&#xff1b;P-MOS 管Q1关断&#xff1b; 特别注意&#xff0c;…

代码界的奥斯卡:SpringBoot测试的艺术与科学

探索SpringBoot测试的神秘世界&#xff0c;揭秘如何成为代码质量的守护神&#xff01;从基础环境搭建到高级集成测试&#xff0c;本系列教程带你一步步构建坚不可摧的测试防线。深入JUnit 5的强大功能&#xff0c;学习如何用MockMvc和Testcontainers打造逼真的测试场景。准备好…

小型企业网络组网与配置仿真实验

实验要求如下: 我这里以学号46为例 一、IP 地址规划表 &#xff08;一&#xff09;主类网络 &#xff08;二&#xff09;子网划分 需要自己计算有效ip范围 在C类主网络192.168.46.0/24中&#xff0c;我们需要先了解这个网络的子网掩码为255.255.255.0&#xff0c;其二进制…

MOS管开关电路简单笔记

没错&#xff0c;这一篇还是备忘录&#xff0c;复杂的东西一律不讨论。主要讨论增强型的PMOS与NMOS。 PMOS 首先上场的是PMOS,它的导通条件&#xff1a;Vg-Vs<0且|Vg-Vs|>Vgsth&#xff0c;PMOS的电流流向是S->D,D端接负载&#xff0c;S端接受控电源。MOS管一般无法…

LeetCode:环形链表II

文章收录于LeetCode专栏 LeetCode地址 环形链表II 题目 给定一个链表&#xff0c;返回链表开始入环的第一个节点。如果链表无环&#xff0c;则返回null。   为了表示给定链表中的环&#xff0c;我们使用整数pos来表示链表尾连接到链表中的位置&#xff08;索引从0开始&#…

三十五、openlayers官网示例Dynamic Data——在地图上加载动态数据形成动画效果

官网demo地址&#xff1a; Dynamic Data 初始化地图 const tileLayer new TileLayer({source: new OSM(),});const map new Map({layers: [tileLayer],target: "map",view: new View({center: [0, 0],zoom: 2,}),}); 创建了三个样式 const imageStyle new Style(…

WIFI 万[néng]钥匙 v5.0.10/v4.9.80 SVIP版!

WiFi Master Key v5.0.10/v4.9.80 WIFI万[Nng]钥匙APP是一款专业的网络连接工具&#xff0c;设计宗旨在于为用户提供方便快捷的WiFi接入方案。本应用集成了覆盖全国的大量免费WiFi热点信息&#xff0c;确保用户能够在不同地区快速而稳定地连接到互联网。此外&#xff0c;该应用…

HackTheBox-Machines--Sense

Popcorn 测试过程 1 信息收集 服务器开启80、443端口 80端口 访问 80 跳转到 443 – https://10.129.196.51/ &#xff0c;该页面是 pfSense 登录界面&#xff0c;默认密码是&#xff1a; admin/pfSense&#xff0c;使用默认账号密码登录失败 目录扫描 ./gobuster dir -u htt…

【TB作品】MSP430F149单片机,广告牌,滚动显示

LCD1602滚动显示切换播放暂停字符串 显示Public Places 显示No Smoking 播放 暂停 部分代码 char zifu1[] "Public Places "; char zifu2[] "Class Now "; char zifu3[] "No admittance "; char *zifu[] { zifu1, zifu2, zifu3 }…

【Qt秘籍】[006]-Label实现Hello World程序-编程第一步

"Hello,World!" 中文意思是“你好&#xff0c;世界”。 因为 The C Programming Language 中使用它做为第一个演示程序&#xff0c;后来很多程序员在学习编程或进行设备调试时延续了这一习惯。 下面&#xff0c;我们也将演示利用Label显示Qt中的"Hello World!&q…

颠覆传统:探索Web3对传统计算机模式的冲击

随着Web3技术的崛起&#xff0c;传统计算机模式正面临着前所未有的冲击与挑战。Web3作为下一代互联网的代表&#xff0c;以其去中心化、安全可信的特性&#xff0c;正在颠覆着传统计算机模式的种种假设和局限性。本文将深入探讨Web3对传统计算机模式的冲击&#xff0c;并探索其…

imx6ull - 制作烧录SD卡

1、参考NXP官方的手册《i.MX_Linux_Users_Guide.pdf》的这一章节&#xff1a; 1、SD卡分区 提示&#xff1a;我们常用的SD卡一个扇区的大小是512字节。 先说一下i.MX6ULL使用SD卡启动时的分区情况&#xff0c;NXP官方给的镜像布局结构如下所示&#xff1a; 可以看到&#xff0c…