一、显示YUV图片

显示 YUV 图片和显示 BMP 图片的大致流程是一样的。显示 BMP 图片我们可以直接获取到 BMP 图片的 surface,然后直接从 surface 创建纹理。显示 YUV 格式的图片,我们需要先创建一个对应像素格式的空白纹理,然后读取 YUV 数据,再把 YUV 数据更新到纹理上面。

宏定义

#include <SDL2/SDL.h>
#include <QDebug> #define END(judge, func) \
if (judge) { \
qDebug() << #func << "error" << SDL_GetError(); \
goto end; \
} #define FILENAME "F:/res/in.yuv"
#define PIXEL_FORMAT SDL_PIXELFORMAT_IYUV
#define IMG_W 512
#define IMG_H 512

变量定义

// 窗口
SDL_Window *window = nullptr; // 渲染上下文
SDL_Renderer *renderer = nullptr; // 纹理(直接跟特定驱动程序相关的像素数据)
SDL_Texture *texture = nullptr; // 文件
QFile file(FILENAME);

初始化子系统

// 初始化Video子系统
END(SDL_Init(SDL_INIT_VIDEO), SDL_Init);

创建窗口

// 创建窗口
window = SDL_CreateWindow(
// 窗口标题
"SDL显示YUV图片",
// 窗口X(未定义)
SDL_WINDOWPOS_UNDEFINED,
// 窗口Y(未定义)
SDL_WINDOWPOS_UNDEFINED,
// 窗口宽度(跟图片宽度一样)
surface->w,
// 窗口高度(跟图片高度一样)
surface->h,
// 显示窗口
SDL_WINDOW_SHOWN
);
END(!window, SDL_CreateWindow);

创建渲染上下文

// 创建渲染上下文(默认的渲染目标是window)
renderer = SDL_CreateRenderer(window,
-1,// 要初始化的渲染设备的索引,设置 -1 则初始化第一个支持flags的设备
SDL_RENDERER_ACCELERATED |
SDL_RENDERER_PRESENTVSYNC);
if (!renderer) { // 说明开启硬件加速失败
renderer = SDL_CreateRenderer(window, -1, 0);
}
END(!renderer, SDL_CreateRenderer);

创建纹理

// 创建纹理
texture = SDL_CreateTexture(renderer,
//显示的像素数据格式,我们显示的YUV图片像素格式是yuv420p,
//其实SDL_PIXELFORMAT_IYUV就是yuv420p像素格式
PIXEL_FORMAT,
//之前我们把同一个texture在窗口绘制多次时,我们设置的是SDL_TEXTUREACCESS_TARGET,
//这里我们设置SDL_TEXTUREACCESS_STATIC,当然设置成SDL_TEXTUREACCESS_STREAMING也可以
SDL_TEXTUREACCESS_STREAMING,
IMG_W, IMG_H);
END(!texture, SDL_CreateTexture);

这里我们仅仅是创建了一个yuv420p像素格式的空白纹理,其上面并没有像素格式的数据。所以后面需要加载YUV数据,把YUV格式像素数据加载到纹理上面。PS:和加载 BMP 图片比较,加载YUV数据构建纹理的过程发生了变化,加载BMP图片我们使用的是SDL_CreateTextureFromSurface,加载YUV我们先创建了一个空的纹理,重要的是一定要设置好像素格式,以便后面能够正确解析我们的 YUV 数据。

/**
* \brief The access pattern allowed for a texture.
*/
typedef enum
{
SDL_TEXTUREACCESS_STATIC, /**< 静态(图片) */
SDL_TEXTUREACCESS_STREAMING, /**< 数据流(视频) */
SDL_TEXTUREACCESS_TARGET /**< 纹理可以作为渲染目标使用,比如我们需要把同一个图形在 window 中绘制多次。我们可以创建一个纹理并设置成 Target,把图形绘制到此纹理上,然后设置 Target 为 window,再把纹理拷贝到 window(可多次拷贝) */
} SDL_TextureAccess;

打开文件

// 打开文件
if (!file.open(QFile::ReadOnly)) {
qDebug() << "file open error" << FILENAME;
goto end;
}

渲染

// 将YUV的像素数据填充到texture
END(SDL_UpdateTexture(texture,
nullptr,// SDL_Rect:更新像素的矩形区域,传nullptr表示更新整个纹理区域
file.readAll().data(),// 原始像素数据
IMG_W),// 一行像素数据的字节数,这里传图片宽度即可
SDL_UpdateTexture); // 设置绘制颜色(画笔颜色)
END(SDL_SetRenderDrawColor(renderer,
0, 0, 0, SDL_ALPHA_OPAQUE),
SDL_SetRenderDrawColor); // 用绘制颜色(画笔颜色)清除渲染目标
END(SDL_RenderClear(renderer),
SDL_RenderClear); // 复制纹理到渲染目标上(默认是window)可以使用SDL_SetRenderTarget()修改渲染目标
// srcrect源矩形框,dstrect目标矩形框,两者都传nullptr表示整个纹理渲染到整个目标上去
END(SDL_RenderCopy(renderer, texture, nullptr, nullptr),
SDL_RenderCopy); // 更新所有的渲染操作到屏幕上
SDL_RenderPresent(renderer);

延迟退出

// 延迟3秒退出
SDL_Delay(3000);

释放资源

end:
file.close();
SDL_DestroyTexture(texture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();

代码链接

二、显示YUV视频

不管我们的视频是 mp4、mkv还是avi,播放时最终都要解码成原始数据,一般就是YUV格式数据。显示YUV视频和显示YUV图片的大致流程也是一样的。不同之处就是我们要循环的显示视频的每一帧像素数据。

在按钮的事件响应中开启一个定时器, startTimerQObject中的方法,继承自QObject的对象中都可以调用这个方法。 调用方法 startTimer就会开启一个定时器,并且开启成功会返回一个定时器Id,定时器调用间隔是1000ms / 帧率

void MainWindow::on_playButton_clicked(){
// 开启定时器
_timerId = startTimer(1000 /30.0);
}

定时器会不断的调用下面的方法timerEvent,每次从YUV文件中读取一帧像素数据,这就需要我们计算出一帧像素数据的大小,yuv420p像素格式每个像素占 1.5字节{(4Y+1U+1V)/4 = 1.5},通过视频宽度 * 视频高度 * 1.5就可算出一帧像素数据大小,或者使用FFmpeg提供的函数av_image_get_buffer_size(在libavutil/imgutils.h中),然后将读取的一帧图素数据更新到纹理,并复制纹理到渲染目标,最后更新所有的渲染操作到屏幕上,这一帧像素就显示出来了。重复相同的操作,就达到了视频播放的效果。YUV文件数据读取完毕,要记得调用killTimer杀死定时器。

// 每隔一段时间就会调用
void MainWindow::timerEvent(QTimerEvent *event){
// yuv420p 像素格式每个像素占 1.5 字节
int imgSize = IMG_W * IMG_H * 1.5;
char data[imgSize];
// 每次读取一帧图像
if(_file.read(data,imgSize) > 0){
// 将YUV的像素数据填充到texture
RET(SDL_UpdateTexture(_texture,
nullptr,// SDL_Rect:更新像素的矩形区域,传nullptr表示更新整个纹理区域
data,// 原始像素数据
IMG_W),// 一行像素数据的字节数,这里传图片宽度即可
SDL_UpdateTexture);
// 渲染
// 设置绘制颜色(这里随便设置了一个颜色:黄色)
RET(SDL_SetRenderDrawColor(_renderer,255,255,0,SDL_ALPHA_OPAQUE),SDL_SetRenderDrawColor); // 用DrawColor清除渲染目标
RET(SDL_RenderClear(_renderer),SDL_RenderClear); // 复制纹理到渲染目标上(默认是window)可以使用SDL_SetRenderTarget()修改渲染目标
// srcrect源矩形框,dstrect目标矩形框,两者都传nullptr表示整个纹理渲染到整个目标上去
RET(SDL_RenderCopy(_renderer,_texture,nullptr,nullptr),SDL_RenderCopy); // 将此前的所有需要渲染的内容更新到屏幕上
SDL_RenderPresent(_renderer);
}else{
// 文件数据已经读取完毕
killTimer(_timerId);
}
}

具体代码

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H #include <QMainWindow>
#include <SDL2/SDL.h>
#include <QFile> QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE class MainWindow : public QMainWindow
{
Q_OBJECT public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
void timerEvent(QTimerEvent *event);
private slots:
void on_playButton_clicked(); private:
Ui::MainWindow *ui;
QWidget *_widget;
// 窗口
SDL_Window *_window = nullptr;
// 渲染上下文
SDL_Renderer *_renderer = nullptr;
// 纹理(直接跟特定驱动程序相关的像素数据)
SDL_Texture *_texture = nullptr;
QFile _file;
int _timerId; };
#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug> #ifdef Q_OS_WIN
#define FILENAME "../test/out.yuv"
#else
#define FILENAME "/Users/zuojie/QtProjects/audio-video-dev/test/in.yuv"
#endif // 出错了就执行goto end
#define RET(judge, func) \
if (judge) { \
qDebug() << #func << "Error" << SDL_GetError(); \
return; \
} #define PIXEL_FORMAT SDL_PIXELFORMAT_IYUV
#define IMG_W 848
#define IMG_H 480 MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
_widget = new QWidget(this);
_widget->setGeometry(0,50,IMG_W,IMG_H); // 初始化Video子系统
RET(SDL_Init(SDL_INIT_VIDEO),SDL_Init); // 创建窗口
_window = SDL_CreateWindowFrom((void *)_widget->winId());
RET(!_window, SDL_CreateWindow); // 创建渲染上下文(默认的渲染目标是window)
_renderer = SDL_CreateRenderer(_window,
// 要初始化的渲染设备的索引,设置 -1 则初始化第一个支持 flags 的设备
-1,
SDL_RENDERER_ACCELERATED |
SDL_RENDERER_PRESENTVSYNC);
if (!_renderer) { // 说明开启硬件加速失败
_renderer = SDL_CreateRenderer(_window, -1, 0);
}
RET(!_renderer, SDL_CreateRenderer); // 创建纹理
_texture = SDL_CreateTexture(_renderer,
//显示的像素数据格式,我们显示的YUV图片像素格式是yuv420p,
//其实SDL_PIXELFORMAT_IYUV就是yuv420p像素格式
PIXEL_FORMAT,
//之前我们把同一个texture在窗口绘制多次时,我们设置的是SDL_TEXTUREACCESS_TARGET,
//这里我们设置SDL_TEXTUREACCESS_STATIC,当然设置成SDL_TEXTUREACCESS_STREAMING也可以
SDL_TEXTUREACCESS_STATIC,
IMG_W,IMG_H);
RET(!_texture, SDL_CreateTexture); // 打开文件
_file.setFileName(FILENAME);
if(!_file.open(QFile::ReadOnly)){
qDebug() << "file open error" << FILENAME;
}
} MainWindow::~MainWindow()
{
delete ui;
_file.close();
SDL_DestroyTexture(_texture);
SDL_DestroyRenderer(_renderer);
SDL_DestroyWindow(_window);
SDL_Quit();
} void MainWindow::on_playButton_clicked(){
// 开启定时器
_timerId = startTimer(1000 /30.0);
} // 每隔一段时间就会调用
void MainWindow::timerEvent(QTimerEvent *event){
// yuv420p 像素格式每个像素占 1.5 字节
int imgSize = IMG_W * IMG_H * 1.5;
char data[imgSize];
// 每次读取一帧图像
if(_file.read(data,imgSize) > 0){
// 将YUV的像素数据填充到texture
RET(SDL_UpdateTexture(_texture,
nullptr,// SDL_Rect:更新像素的矩形区域,传nullptr表示更新整个纹理区域
data,// 原始像素数据
IMG_W),// 一行像素数据的字节数,这里传图片宽度即可
SDL_UpdateTexture);
// 渲染
// 设置绘制颜色(这里随便设置了一个颜色:黄色)
RET(SDL_SetRenderDrawColor(_renderer,255,255,0,SDL_ALPHA_OPAQUE),SDL_SetRenderDrawColor); // 用DrawColor清除渲染目标
RET(SDL_RenderClear(_renderer),SDL_RenderClear); // 复制纹理到渲染目标上(默认是window)可以使用SDL_SetRenderTarget()修改渲染目标
// srcrect源矩形框,dstrect目标矩形框,两者都传nullptr表示整个纹理渲染到整个目标上去
RET(SDL_RenderCopy(_renderer,_texture,nullptr,nullptr),SDL_RenderCopy); // 将此前的所有需要渲染的内容更新到屏幕上
SDL_RenderPresent(_renderer);
}else{
// 文件数据已经读取完毕
killTimer(_timerId);
}
}

代码链接

21_显示YUV图片&视频的更多相关文章

  1. 【秒懂音视频开发】21_显示BMP图片

    文本的主要内容是:使用SDL显示一张BMP图片,算是为后面的<播放YUV>做准备. 为什么是显示BMP图片?而不是显示JPG或PNG图片? 因为SDL内置了加载BMP的API,使用起来会更 ...

  2. 使用DirectDraw直接显示YUV视频数据

    最近在编写一个进行视频播放的ActiveX控件,工作已经接近尾声,现将其中显示YUV数据的使用DirectDraw的一些经验总结如下:(解码部分不是我编写的,我负责从网络接收数据,将数据传给解码器,并 ...

  3. Android上使用OpenGLES2.0显示YUV数据

    在Android上用OpenGLES来显示YUV图像,之所以这样做,是因为: 1.Android本身也不能直接显示YUV图像,YUV转成RGB还是必要的: 2.YUV手动转RGB会占用大量的CPU资源 ...

  4. Android用surface直接显示yuv数据(三)

    本文用Java创建UI并联合JNI层操作surface来直接显示yuv数据(yv12),开发环境为Android 4.4,全志A23平台. package com.example.myyuvviewe ...

  5. 实战OpenGLES--iOS平台使用OpenGLES渲染YUV图片

    上一篇文章 实战FFmpeg--iOS平台使用FFmpeg将视频文件转换为YUV文件 演示了如何将视频文件转换为yuv文件保存,现在要做的是如何将yuv文件利用OpenGLES渲染展示出图像画面.要将 ...

  6. 使用图片视频展示插件blueimp Gallery改造网站的视频图片展示

    在很多情况下,我们网站可能会展示我们的产品图片.以及教程视频等内容,结合一个比较好的图片.视频展示插件,能够使得我们的站点更加方便使用,也更加酷炫,在Github上有很多相关的处理插件可以找来使用,有 ...

  7. 嵌入式linux------SDL移植(am335x下显示bmp图片)

    #include<stdio.h> #include "/usr/local/ffmpeg_arm/include/SDL/SDL.h" char *bmp_name[ ...

  8. luvcview,使用mplayer查看摄像头和luvcview保存YUV图像视频的播放(转)

    luvcview,使用mplayer查看摄像头和luvcview保存YUV图像视频的播放 在mplayer中查看摄像头,可使用如下命令:mplayer tv:// -tv driver=v4l2:in ...

  9. android多图选择器 图片/视频 单选or多选,以及视频录制。

    PictureSelector 最近项目中用到多图选择上传的需求,考虑到android机型众多问题就自己花时间写了一个,测试了大概60款机型,出现过一些问题也都一一修复了,基本上稳定了特分享出来,界面 ...

  10. IM聊天教程:发送图片/视频/语音/表情

    经常有朋友问起,如何在IM即时通讯中实现发送图片.视频.语音和表情? 为此,小编特意写了一个vue版本的Demo,实现了图片视频文件和表情的的发送,参考这个Demo源代码,相信你就可以轻松的用Unia ...

随机推荐

  1. Azure Data Factory(十二)传参调用 Azure Function

    一,引言 在实际的项目中,Azure Data Factroy 中的 Data Flow 并不能彻底帮我们完成一系列复制逻辑计算, 比如我们需要针对数据集的每一行数据进行判断计算,Data Flow ...

  2. Spring Cloud Config核心功能和原理解析

    配置管理的前世今生 随着技术的发展,配置项管理变得越来越简单,尽管如今它只限于管理业务属性或者配置初始化参数等等,但是当年它可肩负着 Spring IOC 的光荣使命,风光无限. 想当年刚入行的时候还 ...

  3. 任何Bean通过实现ProxyableBeanAccessor接口即可获得动态灵活的获取代理对象或原生对象的能力

    如果一个BEAN类上加了@Transactional,则默认的该类及其子类的公开方法均会开启事务,但有时某些业务场景下某些公开的方法可能并不需要事务,那这种情况该如何做呢? 常规的做法: 针对不同的场 ...

  4. Argocd学习

    argocd官网文档链接 ArgoCD官网文档 在K8S集群使用argocd命令将集群添加到argcd的cluster列表中 argocd cluster add kubernetes-admin@i ...

  5. 将PL/SQL代码封装在灵巧的包中

    将代码封装在灵巧的包中 http://www.oracle.com/technetwork/issue-archive/2013/13-jan/o13plsql-1872456.html 绝大多数基于 ...

  6. 面试官不按套路出牌,上来就让聊一聊Java中的迭代器(Iterator ),夺命连环问,怎么办?

    写在开头 某大厂的面试现场,一位目光深邃,头顶稀疏的中年面试官坐在椅子上,这时候的我走了进来. 面试官:"小伙子,学过Java中容器和数据结构了吧?" 我:"嗯,学了&q ...

  7. 简单了解HTTP、Websocket和Netty

    前言 伴随着网络的快速发展,网络通讯越来越重要,通讯的快捷.安全.方便影响着用户的体验.本文将探讨这些技术的原理.特点以及在实际应用中的应用场景. 1.HTTTP(超文本传输协议) HTTP是一种传输 ...

  8. Java 中拼接 String 的 N 种方式

    文章持续更新,可以关注公众号程序猿阿朗或访问未读代码博客. 本文 Github.com/niumoo/JavaNotes 已经收录,欢迎Star. 1. 前言 Java 提供了拼接 String 字符 ...

  9. .Net 6 WebAPI 使用JWT进行 授权认证配置

    .Net 6 WebAPI 使用JWT进行 授权认证 1.安装组件(Nuget) Microsoft.AspNetCore.Authentication.JwtBearer 2.Program.cs ...

  10. 项目实战:Qt终端命令模拟工具 v1.0.0(实时获取命令行输出,执行指令,模拟ctrl+c中止操作)

    需求   在Qt软件中实现部分终端控制命令行功能,使软件内可以又好的模拟终端控制,提升软件整体契合度.   Demo演示          运行包下载地址:   CSDNf粉丝0积分下载:https: ...