基于 FFmpeg 的跨平台视频播放器简明教程(五):使用 SDL 播放视频

这篇具有很好参考价值的文章主要介绍了基于 FFmpeg 的跨平台视频播放器简明教程(五):使用 SDL 播放视频。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

系列文章目录

  1. 基于 FFmpeg 的跨平台视频播放器简明教程(一):FFMPEG + Conan 环境集成
  2. 基于 FFmpeg 的跨平台视频播放器简明教程(二):基础知识和解封装(demux)
  3. 基于 FFmpeg 的跨平台视频播放器简明教程(三):视频解码
  4. 基于 FFmpeg 的跨平台视频播放器简明教程(四):像素格式与格式转换


前言

经过前面四章的学习,现在我们已经掌握了如何使用 FFmpeg 进行视频解码,中间穿插了很多音视频相关的知识点,例如容器、编解码器、解封装、像素格式、格式转换等等。现在回看,音视频的入门门槛还是比较高的,一个最简单的任务就已经涉及到大量的知识点。但问题不大,本人希望通过一系列的文章来带你入门,通过完成一个播放器项目来不断地学习音视频内容。

在开始新的旅程前,重新审视下现有代码,发现有些模块可以被封装成更为内聚的类,具体的包括:

  1. FFmpegDemuxer,用于解封装相关的任务
  2. FFmpegCodec,用于解码相关的任务
  3. FFmpegImageConverter,用于 AVFrame 格式转换

这些类的使用方式,你可以在单元测试中找到示例,此处不再赘述。

抽象封装成一些类的好处主要有几点:

  1. 资源管理。FFmpeg 是 C 接口,很多资源需要手动的管理,这样的代码写多了难免会出现内存泄漏的问题。因此用 C++ 的 ”资源获取即初始化“ 理念来管理这些资源。
  2. 减少代码冗余。将某些任务封装成更为简便的接口,减少代码冗余。

好的,准备就绪,让我们进入今天的主题:播放视频。本文参考文章来自 An ffmpeg and SDL Tutorial - Tutorial 02: Outputting to the Screen。这个系列对新手较为友好,但 2015 后就不再更新了,以至于文章中的 ffmpeg api 已经被弃用了。幸运的是,有人对该教程的代码进行重写,使用了较新的 api,你可以在 rambodrahmani/ffmpeg-video-player 找到这些代码。

本文的代码在 ffmpeg_video_player_tutorial-my_tutorial02。

SDL2,跨平台多媒体开发库

SDL2 是 Simple DirectMedia Layer(简单直接媒体层)的缩写,它是一个跨平台的 C/C++ 库,专为游戏和多媒体应用程序提供硬件抽象层的支持。SDL2 提供了对音频、键盘、鼠标、操纵杆和图形硬件的底层访问能力,它在 Windows、macOS、Linux、iOS 和 Android 等平台上都有广泛的应用。

FFmpeg 提供的命令行工具 FFplay 中使用了 SDL2 作为渲染图像的依赖库。FFplay 是 FFmpeg 项目中的一个简单的媒体播放器,它依赖于 FFmpeg 库来解码、解复用和处理媒体数据。FFmpeg 本身专注于视频和音频的编解码,以及其他媒体处理功能,但不涉及与硬件交互的部分,例如音视频的渲染和播放。

FFplay 使用 SDL 主要是因为 SDL 提供了易于使用的跨平台 API,用于访问音频、视频、键盘、鼠标和操纵杆等硬件设备。通过使用 SDL,FFplay 能在各种操作环境下实现音视频的同步播放,以及用户交互(如键盘和鼠标操作)。

SDL 提供了以下特性,使其成为 FFplay 使用的理想选择:

  1. 跨平台支持:SDL 可在多个平台(如 Windows、macOS、Linux 等)上运行,这意味着基于 SDL 的 FFplay 可以很容易地移植到其他环境。
  2. 音频和视频渲染:SDL 提供了对音频和视频的播放支持,使得 FFplay 可以正确地渲染音频和视频数据。
  3. 事件处理:SDL 提供了对各类输入设备事件(如键盘、鼠标等)的处理,这使得 FFplay 可以对用户的操作做出响应,例如暂停、快进等。

关于 SDL 的使用,我之前写过一些文章,供大家参考,此处不再赘述:

  • SDL2 简明教程(一):使用 Cmake 和 Conan 构建 SDL2 编程环境
  • SDL2 简明教程(二):创建一个空的窗口
  • SDL2 简明教程(三):显示图片
  • SDL2 简明教程(四):用 SDL_IMAGE 库导入图片
  • SDL2 简明教程(五):OpenGL 绘制

SDL 显示图像

首先让我们快速过一下 SDL 显示图片的过程。使用SDL来显示一帧图像的步骤如下:

  1. 初始化SDL:调用SDL_Init函数来初始化SDL库。
  2. 创建一个窗口和渲染器:调用SDL_CreateWindow和SDL_CreateRenderer函数来创建一个窗口和渲染器。
  3. 加载图像:使用SDL_image库中的函数(如IMG_Load)加载需要显示的图像。
  4. 创建一个纹理:使用加载的图像创建一个纹理,使用SDL_CreateTextureFromSurface函数。
  5. 将纹理渲染到屏幕:使用SDL_RenderCopy函数将纹理渲染到屏幕上。
  6. 刷新屏幕:使用SDL_RenderPresent函数来刷新屏幕。
  7. 释放资源:使用SDL_DestroyTexture、SDL_DestroyRenderer、SDL_DestroyWindow等函数释放分配的资源。

下面是C++代码示例,显示一张图像:

#include <SDL.h>
#include <SDL_image.h>

int main(int argc, char const *argv[])
{
    SDL_Init(SDL_INIT_VIDEO);

    SDL_Window* window = SDL_CreateWindow("SDL Example", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 640, 480, 0);
    SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);

    SDL_Surface* image = IMG_Load("example.png");
    SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, image);

    SDL_RenderCopy(renderer, texture, NULL, NULL);
    SDL_RenderPresent(renderer);

    SDL_Delay(5000);    //等待5秒

    SDL_DestroyTexture(texture);
    SDL_FreeSurface(image);
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);

    IMG_Quit();
    SDL_Quit();
    
    return 0;
}

在SDL中,window、renderer、surface和texture是四个关键的图形和图像处理对象,它们之间有一定的联系。让我们一一分析它们的作用和关系。

  • SDL_Window: 窗口对象。用于在屏幕上创建、管理和显示一个简单的矩形窗口。窗口可以由操作系统进行管理,提供了标题栏、边框和其他窗口功能。SDL_Window代表了应用程序中使用的这个窗口实例。

  • SDL_Renderer: 渲染器对象。负责将多个图像(通常由SDL_Texture表示)绘制到屏幕上。渲染器可以使用不同的后端实现(如OpenGL,Direct3D等),并将其集成到SDL_Window中。与SDL_Window对象关联的渲染器用于将图像和图形绘制到窗口中。

  • SDL_Surface: 表面对象。表示原始的位图图像,每个像素由色彩值定义。表面可以包括一个或多个图层,用于绘制2D图像。然而,它们在处理上较慢,因为渲染操作通常在CPU端执行。表面通常用于加载、处理和创建图像资源,然后将它们转换成纹理用于高效渲染。

  • SDL_Texture: 纹理对象。代表了可以被硬件加速的图形。纹理被GPU管理,可以更高效地使用SDL_Renderer进行渲染。它们通常由SDL_Surface转换而来,在纹理上传到GPU之后,关联的表面对象可以被释放以节省内存。

这四个对象之间的关系如下:

  • 一个SDL_Window与一个SDL_Renderer关联,将图形和图像绘制到窗口上。
  • SDL_Renderer负责处理和绘制SDL_Texture对象。
  • SDL_Surface用于创建、处理和存储原始的位图图像,然后将它们转换成硬件加速的SDL_Texture以供渲染器绘制。

简而言之,SDL_Window负责显示,SDL_Renderer负责绘制,SDL_Surface负责处理原始位图,SDL_Texture则可以高效地被渲染器绘制到窗口。在实际应用中,通常需要处理并将多个表面转换成纹理,然后使用渲染器将它们绘制到窗口中。

SDL 显示 YUV 图像

视频多数情况下使用 YUV 格式来存放像素数据,主要原因是压缩效率更高,同时还能保持较好的图像质量。具体来说,有以下几点原因:

  1. 符合人眼特性: YUV格式将图像的亮度信息(Y分量)和色度信息(U和V分量)分开存储。而人眼对亮度信息(黑白图像)的敏感度要高于色度信息(彩色信息)。在进行压缩时,可以减少色度信息的分辨率,从而降低数据量,这符合人类视觉系统的特性,不会明显降低观感质量。这种削减色度分辨率的方式叫色度子采样(如4:2:0、4:2:2、4:4:4)。
  2. 节省存储空间和带宽: 相比于像RGB这种直接存储色彩值的格式,YUV格式可以利用色度子采样来有效地减少数据量,在同样的图像质量下,YUV格式需要的存储空间和传输带宽更小。这在视频传输、压缩、存储等场景中十分重要。
  3. 兼容性和广泛应用: YUV格式已经被广泛应用于许多视频压缩标准,如H.264(AVC)、H.265(HEVC)等,以及各种视频设备和传输系统。这意味着使用YUV格式可以显著提高视频系统的通用性和兼容性。
  4. 易于处理: YUV格式把亮度信息与色度信息分开存储,方便视频图像处理和编辑,如调整亮度、对比度、色彩平衡等。

在SDL中显示一张YUV图像,可以使用 SDL_Texture的SDL_PIXELFORMAT_YV12 或SDL_PIXELFORMAT_IYUV像素格式。首先,需要创建一个相应格式的纹理,然后通过SDL_UpdateYUVTexture()函数更新纹理数据。下面是一个简单的示例:

#include <SDL.h>

int main(int argc, char *argv[])
{
    // 初始化SDL
    if (SDL_Init(SDL_INIT_VIDEO) != 0)
    {
        SDL_Log("Unable to initialize SDL: %s", SDL_GetError());
        return -1;
    }

    // 创建窗口和渲染器
    SDL_Window* window = SDL_CreateWindow("YUV Example", SDL_WINDOWPOS_CENTERED,
        SDL_WINDOWPOS_CENTERED, 640, 480, 0);
    SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);

    // 创建纹理
    SDL_Texture* texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12,
        SDL_TEXTUREACCESS_STREAMING, 640, 480);

    // 加载YUV数据
    Uint8* yuvData = loadYUVData();


    // 假设 yPlane, uPlane 和 vPlane 分别是Y,U和V分量的指针
	Uint8 *yPlane;
	Uint8 *uPlane;
	Uint8 *vPlane;

	int dataSize = width * height;

	// 设置三个分量数据的跨度
	int yPitch = width;
	int uPitch = width / 2;
	int vPitch = width / 2;

	// 更新纹理数据
	SDL_UpdateYUVTexture(texture,
                     	nullptr, // 更新整个纹理
                     	yPlane, yPitch,
                     	uPlane, uPitch,
                     	vPlane, vPitch);

    // 渲染纹理
    SDL_RenderClear(renderer);
    SDL_RenderCopy(renderer, texture, NULL, NULL);
    SDL_RenderPresent(renderer);

    SDL_Delay(5000);

    // 释放资源
    SDL_DestroyTexture(texture);
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();

    return 0;
}

在文章 YUV 文件读取、显示、缩放、裁剪等操作教程
有更详细的说明,包括 SDL YUV 格式与 ffmpeg 像素格式之间的对应,Chroma subsampling 是什么,YUV Packed 与 Planar 的区别,如何正确读取一张 YUV 图片等等,强烈推荐先看这篇文章后再继续我们的教程。此外,你也可以直接看项目 simple_yuv_viewer 的 源码来学习 。

SDL 播放视频

现在,我们的目标是取代上一个教程中的 saveFrame 函数,而直接将视频帧输出到屏幕。为了让视频解码与视频播放的逻辑独立开,这里引入一个类叫 SDLApp,它负责 SDL 环境的创建、将 AVFrame 更新至纹理、处理事件等等。具体实现你可以在 ffmpeg_video_player_tutorial-my_tutorial02 中看到。

这里先简单介绍下 SDLApp 的主要方法:

  • SDLApp::onInit(int video_width, int video_height),负责 SDL 资源的初始化,包括窗口、纹理以及 Render。注意,在创建纹理时使用的格式 SDL_PIXELFORMAT_IYUV,对应 FFmpeg 中的 yuv420p 格式。这是最为常用的格式。
  • SDLApp::onLoop(AVFrame *pict),负责将 YUV 数据更新至纹理。
  • SDLApp::onRender(double sleep_time_s),负责渲染纹理,即将纹理显示在窗口中。其中 sleep_time_s 是一个等待时间,用于控制播放的速率。
  • void onEvent(const SDL_Event &event) 负责对 SDL 窗口事件进行响应。在这里,这个函数其实没有起任何作用。可以忽略。
  • SDLApp::onCleanup,负责释放 SDL 资源。

SDLApp 负责渲染图像,FFmpeg 负责解码视频,整体流程大致是这样的:

// 使用 demuxer 打开文件
FFmpegDmuxer demuxer;
demuxer.openFile(infile);

// 创建视频解码器
AVStream *video_stream = demuxer.getStream(video_stream_index);
FFmpegCodec video_codec;
auto codec_id = video_stream->codecpar->codec_id;
auto par = video_stream->codecpar;
video_codec.prepare(codec_id, par);

// 创建 ImageConverter,负责将解码后的图像转换为 yuv420p 格式。与 SDL 中的纹理格式匹配
auto dst_format = AVPixelFormat::AV_PIX_FMT_YUV420P;
auto codec_context = video_codec.getCodecContext();
FFMPEGImageConverter img_conv;
img_conv.prepare(codec_context->width, codec_context->height,
                 codec_context->pix_fmt, codec_context->width,
                 codec_context->height, dst_format, SWS_BILINEAR, nullptr,
                 nullptr, nullptr);

//  sdl 环境初始化
auto video_width = video_codec.getCodecContext()->width;
auto video_height = video_codec.getCodecContext()->height;
SDLApp app;
app.onInit(video_width, video_height);

// 获取视频的 fps
double fps = av_q2d(video_stream->r_frame_rate);
double sleep_time = 1.0 / fps;

// 一直解码,直到满足某种条件退出
for(;!finished;)
{
	AVFrame* frame = decodeNextFrame();
	AVFRame* yuv_frame = convertToYUV(frame);

	// 显示图片
	app.onLoop(pict);
	app.onRender(sleep_time);
}

app.onCleanup();

好的,以上就是使用 SDL 播放视频的所有逻辑。详细代码请参考 ffmpeg_video_player_tutorial-my_tutorial02。

总结

本文介绍了 SDL 框架,使用 SDL 框架显示图片的流程,以及如何结合 FFmpeg 的解码能力使用 SDL 来播放视频。在我们的实现中,只显示显示了画面,但没有声音,这是没有灵魂的。在下一章中,我们将介绍如何使用 SDL 同时播放视频与音频。文章来源地址https://www.toymoban.com/news/detail-532722.html

参考

  • SDL - github
  • SDL2 简明教程(一):使用 Cmake 和 Conan 构建 SDL2 编程环境
  • SDL2 简明教程(二):创建一个空的窗口
  • SDL2 简明教程(三):显示图片
  • SDL2 简明教程(四):用 SDL_IMAGE 库导入图片
  • SDL2 简明教程(五):OpenGL 绘制
  • YUV 文件读取、显示、缩放、裁剪等操作教程
  • simple_yuv_viewer
  • ffmpeg_video_player_tutorial-my_tutorial02

到了这里,关于基于 FFmpeg 的跨平台视频播放器简明教程(五):使用 SDL 播放视频的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 基于 FFMPEG 的跨平台视频播放器简明教程(二):基础知识和解封装(demux)

    基于 FFmpeg 的跨平台视频播放器简明教程(一):FFMPEG + Conan 环境集成 前面一章中我们介绍了如何使用 conan 和 cmake 搭建 ffmpeg 运行环境,你做的还顺利吗?如果遇到任何问题,请在进行评论,我看到都会回复的。 从本章开始,将正式开始我们的 ffmpeg 播放器学习之旅。接下去

    2024年02月08日
    浏览(188)
  • QtAV:基于Qt和FFmpeg的跨平台高性能音视频播放框架

    目录 一.简介 1.特性 2.支持的平台 3.简单易用的接口 二.编译 1.下载依赖包 2.开始编译 2.1克隆 2.2修改配置文件 2.3编译 三.试用 官网地址:http://www.qtav.org/ Github地址:https://github.com/wang-bin/QtAV ●支持大部分播放功能 ●播放、暂停、播放速度、快进快退、字幕、音量、声道、音

    2024年01月22日
    浏览(161)
  • 开源的跨平台的音视频处理工具FFmpeg

    FFmpeg是一个开源的跨平台的音视频处理工具,可以对音频、视频进行转码、裁剪、调节音量、添加水印等操作。 广泛的格式支持。 FFmpeg能够解码、编码、转码、复用、分离、流式传输、过滤和播放几乎人类和机器所创造的任何内容。它支持最古老且晦涩难懂的格式,也支持

    2024年02月15日
    浏览(68)
  • FFmpeg——开源的开源的跨平台音视频处理框架简介

    引言:         FFmpeg是一个开源的跨平台音视频处理框架,可以处理多种音视频格式。它由Fabrice Bellard于2000年创建,最初是一个只包括解码器的项目。后来,很多开发者参与其中,为FFmpeg增加了多种新的功能,例如编码器、过滤器、muxer、demuxer等等,使它成为了一个完整

    2024年03月23日
    浏览(71)
  • 基于FFmpeg的视频播放器之三:拉取rtsp流

    拉取网络流和打开本地文件流程差不多,详见:基于FFmpeg的视频播放器之二:解复用,下面是不同地方。 方法有很多,最方便的应该是用VLC串流了,具体步骤如下:https://blog.csdn.net/m0_61353061/article/details/120388230 当然也可以使用live555作为rtsp服务器,可参考:https://blog.csdn.net

    2023年04月25日
    浏览(50)
  • 音视频项目—基于FFmpeg和SDL的音视频播放器解析(三)

    介绍 在本系列,我打算花大篇幅讲解我的 gitee 项目音视频播放器,在这个项目,您可以学到音视频解封装,解码,SDL渲染相关的知识。您对源代码感兴趣的话,请查看基于FFmpeg和SDL的音视频播放器 如果您不理解本文,可参考我的前一篇文章音视频项目—基于FFmpeg和SDL的音视

    2024年02月05日
    浏览(71)
  • 音视频项目—基于FFmpeg和SDL的音视频播放器解析(二十一)

    介绍 在本系列,我打算花大篇幅讲解我的 gitee 项目音视频播放器,在这个项目,您可以学到音视频解封装,解码,SDL渲染相关的知识。您对源代码感兴趣的话,请查看基于FFmpeg和SDL的音视频播放器 如果您不理解本文,可参考我的前一篇文章音视频项目—基于FFmpeg和SDL的音视

    2024年02月02日
    浏览(74)
  • QT软件开发-基于FFMPEG设计视频播放器-软解图像(一)

    QT软件开发-基于FFMPEG设计视频播放器-CPU软解视频(一) https://xiaolong.blog.csdn.net/article/details/126832537 QT软件开发-基于FFMPEG设计视频播放器-GPU硬解视频(二) https://xiaolong.blog.csdn.net/article/details/126833434 QT软件开发-基于FFMPEG设计视频播放器-解码音频(三) https://xiaolong.blog.csdn.

    2023年04月08日
    浏览(51)
  • ffmpeg跨平台arm编译-ubuntu

    32位系统: 64位系统: 关键选项: –arch=arm:指定ARM平台 –target-os=linux:指定Linux系统 –enable-cross-compile :指定交叉编译 –cross-prefix=arm-linux-gnueabihf-:指定交叉编译链 如果是64位系统: –arch=aarch64:指定ARM平台 –cross-prefix=aarch64-linux-gnu-:指定交叉编译链

    2024年02月08日
    浏览(61)
  • 论文精讲 | 基于昇思MindSpore打造首个深度学习开源视频压缩算法库OpenDMC,支持跨平台和多种评价指标

    论文标题 OpenDMC: An Open-Source Library and Performance Evaluation for Deep-learning-based Multi-frame Compression 论文来源 ACM MultiMedia 论文链接 https://www.acmmm2023.org/open-source-program/ 代码链接 https://openi.pcl.ac.cn/OpenDMC/OpenDMC 昇思MindSpore作为开源的AI框架,为产学研和开发人员带来端边云全场景协同、

    2024年02月02日
    浏览(67)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包