最简单的视音频播放演示样例7:SDL2播放RGB/YUV
2017-05-15 12:46
513 查看
=====================================================
最简单的视音频播放演示样例系列文章列表:
最简单的视音频播放演示样例1:总述
最简单的视音频播放演示样例2:GDI播放YUV, RGB
最简单的视音频播放演示样例3:Direct3D播放YUV,RGB(通过Surface)
最简单的视音频播放演示样例4:Direct3D播放RGB(通过Texture)
最简单的视音频播放演示样例5:OpenGL播放RGB/YUV
最简单的视音频播放演示样例6:OpenGL播放YUV420P(通过Texture,使用Shader)
最简单的视音频播放演示样例7:SDL2播放RGB/YUV
最简单的视音频播放演示样例8:DirectSound播放PCM
最简单的视音频播放演示样例9:SDL2播放PCM
=====================================================
本文记录SDL播放视频的技术。在这里使用的版本号是SDL2。实际上SDL本身并不提供视音频播放的功能,它仅仅是封装了视音频播放的底层API。在Windows平台下。SDL封装了Direct3D这类的API用于播放视频;封装了DirectSound这类的API用于播放音频。由于SDL的编写目的就是简化视音频播放的开发难度,所以使用SDL播放视频(YUV/RGB)和音频(PCM)数据非常的easy。
下文记录一下使用SDL播放视频数据的技术。
SDL提供了数种控制图像、声音、输出入的函数。让开发人员仅仅要用同样或是类似的代码就能够开发出跨多个平台(Linux、Windows、Mac OS X等)的应用软件。眼下SDL多用于开发游戏、模拟器、媒体播放器等多媒体应用领域。用以下这张图能够非常明白地说明SDL的位置。
SDL实际上并不限于视音频的播放,它将功能分成下列数个子系统(subsystem):
在Windows下。SDL与DirectX的相应关系例如以下。
1. 初始化
以下具体分析一下上文的流程。
1. 初始化
1) 初始化SDL
使用SDL_Init()初始化SDL。该函数能够确定希望激活的子系统。
SDL_Init()函数原型例如以下:
当中。flags能够取下列值:
有关SDL_Init()有一点须要注意:初始化的时候尽量做到“够用就好”,而不要用SDL_INIT_EVERYTHING。由于有些情况下使用SDL_INIT_EVERYTHING会出现一些不可预知的问题。
比如,在MFC应用程序中播放纯音频。假设初始化SDL的时候使用SDL_INIT_EVERYTHING。那么就会出现听不到声音的情况。
后来发现,去掉了SDL_INIT_VIDEO之后,问题才得以解决。
2) 创建窗体(Window)
使用SDL_CreateWindow()创建一个用于视频播放的窗体。SDL_CreateWindow()的原型例如以下。
參数含义例如以下。
title :窗体标题
x :窗体位置x坐标。也能够设置为SDL_WINDOWPOS_CENTERED或SDL_WINDOWPOS_UNDEFINED。
y :窗体位置y坐标。同上。
w :窗体的宽
h :窗体的高
flags :支持下列标识。
包括了窗体的是否最大化、最小化,是否能调整边界等等属性。
::SDL_WINDOW_FULLSCREEN, ::SDL_WINDOW_OPENGL,
::SDL_WINDOW_HIDDEN, ::SDL_WINDOW_BORDERLESS,
::SDL_WINDOW_RESIZABLE, ::SDL_WINDOW_MAXIMIZED,
::SDL_WINDOW_MINIMIZED, ::SDL_WINDOW_INPUT_GRABBED,
::SDL_WINDOW_ALLOW_HIGHDPI.
返回创建完毕的窗体的ID。假设创建失败则返回0。
3) 基于窗体创建渲染器(Render)
使用SDL_CreateRenderer()基于窗体创建渲染器。
SDL_CreateRenderer()原型例如以下。
參数含义例如以下。
window : 渲染的目标窗体。
index :打算初始化的渲染设备的索引。
设置“-1”则初始化默认的渲染设备。
flags :支持以下值(位于SDL_RendererFlags定义中)
4) 创建纹理(Texture)
使用SDL_CreateTexture()基于渲染器创建一个纹理。SDL_CreateTexture()的原型例如以下。
參数的含义例如以下。
renderer:目标渲染器。
format :纹理的格式。后面会详述。
access :能够取以下值(定义位于SDL_TextureAccess中)
h :纹理的高
创建成功则返回纹理的ID,失败返回0。
在纹理的创建过程中。须要指定纹理的格式(即第二个參数)。
SDL的中的格式非常多,例如以下所列。
这一看确实给人一种“眼花缭乱”的感觉。
简单分析一下当中的定义吧。比如ARGB8888的定义例如以下。
当中用了一个宏SDL_DEFINE_PIXELFORMAT用于将几种属性合并到一个格式中。
以下我们看看一个格式都包括哪些属性:
SDL_PIXELTYPE_PACKED32:代表了像素分量的存储方式。
PACKED代表了像素的几个分量是一起存储的。内存中存储方式例如以下:R1|G1|B1,R2|G2|B2…;ARRAY则代表了像素的几个分量是分开存储的。内存中存储方式例如以下:R1|R2|R3…。G1|G2|G3…,B1|B2|B3…
SDL_PACKEDORDER_ARGB:代表了PACKED存储方式下像素分量的顺序。注意。这里所说的顺序涉及到了一个“大端”和“小端”的问题。
这个问题在《最简单的视音频播放演示样例2:GDI播放YUV, RGB》中已经叙述,不再反复记录。
对于Windows这种“小端”系统,“ARGB”格式在内存中的存储顺序是B|G|R|A。
SDL_PACKEDLAYOUT_8888:说明了每一个分量占领的比特数。比如ARGB格式每一个分量分别占领了8bit。
32:每一个像素占用的比特数。
比如ARGB格式占用了32bit(每一个分量占领8bit)。
4:每一个像素占用的字节数。
比如ARGB格式占用了4Byte(每一个分量占领1Byte)。
2. 循环显示画面
1) 设置纹理的数据
使用SDL_UpdateTexture()设置纹理的像素数据。SDL_UpdateTexture()的原型例如以下。
參数的含义例如以下。
texture:目标纹理。
rect:更新像素的矩形区域。设置为NULL的时候更新整个区域。
pixels:像素数据。
pitch:一行像素数据的字节数。
成功的话返回0。失败的话返回-1。
2) 纹理复制给渲染目标
使用SDL_RenderCopy()将纹理数据复制给渲染目标。在使用SDL_RenderCopy()之前,能够使用SDL_RenderClear()先使用清空渲染目标。实际上视频播放的时候不使用SDL_RenderClear()也是能够的,由于视频的后一帧会全然覆盖前一帧。
SDL_RenderClear()原型例如以下。
參数renderer用于指定渲染目标。
SDL_RenderCopy()原型例如以下。
參数的含义例如以下。
renderer:渲染目标。
texture:输入纹理。
srcrect:选择输入纹理的一块矩形区域作为输入。设置为NULL的时候整个纹理作为输入。
dstrect:选择渲染目标的一块矩形区域作为输出。
设置为NULL的时候整个渲染目标作为输出。
成功的话返回0,失败的话返回-1。
3) 显示
使用SDL_RenderPresent()显示画面。
SDL_RenderPresent()原型例如以下。
參数renderer用于指定渲染目标。
使用SDL播放视频的流程能够概括为下图。
SDL中几个关键的结构体之间的关系能够用下图概述。
简单解释一下各变量的作用:
SDL_Window就是使用SDL的时候弹出的那个窗体。
在SDL1.x版本号中,仅仅能够创建一个一个窗体。在SDL2.0版本号中,能够创建多个窗体。
SDL_Texture用于显示YUV数据。一个SDL_Texture相应一帧YUV数据。
SDL_Renderer用于渲染SDL_Texture至SDL_Window。
SDL_Rect用于确定SDL_Texture显示的位置。
SourceForge项目地址:https://sourceforge.net/projects/simplestmediaplay/
CSDN下载地址:http://download.csdn.net/detail/leixiaohua1020/8054395
注:
该项目会不定时的更新并修复一些小问题。最新的版本号请參考该系列文章的总述页面:
《最简单的视音频播放演示样例1:总述》
上述project包括了使用各种API(Direct3D。OpenGL,GDI。DirectSound,SDL2)播放多媒体样例。当中音频输入为PCM採样数据。输出至系统的声卡播放出来。
视频输入为YUV/RGB像素数据。输出至显示器上的一个窗体播放出来。
通过本project的代码刚開始学习的人能够高速学习使用这几个API播放视频和音频的技术。
一共包括了例如以下几个子project:
simplest_audio_play_directsound: 使用DirectSound播放PCM音频採样数据。
simplest_audio_play_sdl2: 使用SDL2播放PCM音频採样数据。
simplest_video_play_direct3d: 使用Direct3D的Surface播放RGB/YUV视频像素数据。
simplest_video_play_direct3d_texture:使用Direct3D的Texture播放RGB视频像素数据。
simplest_video_play_gdi: 使用GDI播放RGB/YUV视频像素数据。
simplest_video_play_opengl: 使用OpenGL播放RGB/YUV视频像素数据。
simplest_video_play_opengl_texture: 使用OpenGL的Texture播放YUV视频像素数据。
simplest_video_play_sdl2: 使用SDL2播放RGB/YUV视频像素数据。
最简单的视音频播放演示样例系列文章列表:
最简单的视音频播放演示样例1:总述
最简单的视音频播放演示样例2:GDI播放YUV, RGB
最简单的视音频播放演示样例3:Direct3D播放YUV,RGB(通过Surface)
最简单的视音频播放演示样例4:Direct3D播放RGB(通过Texture)
最简单的视音频播放演示样例5:OpenGL播放RGB/YUV
最简单的视音频播放演示样例6:OpenGL播放YUV420P(通过Texture,使用Shader)
最简单的视音频播放演示样例7:SDL2播放RGB/YUV
最简单的视音频播放演示样例8:DirectSound播放PCM
最简单的视音频播放演示样例9:SDL2播放PCM
=====================================================
本文记录SDL播放视频的技术。在这里使用的版本号是SDL2。实际上SDL本身并不提供视音频播放的功能,它仅仅是封装了视音频播放的底层API。在Windows平台下。SDL封装了Direct3D这类的API用于播放视频;封装了DirectSound这类的API用于播放音频。由于SDL的编写目的就是简化视音频播放的开发难度,所以使用SDL播放视频(YUV/RGB)和音频(PCM)数据非常的easy。
下文记录一下使用SDL播放视频数据的技术。
SDL简单介绍
SDL(Simple DirectMedia Layer)是一套开放源码的跨平台多媒体开发库,使用C语言写成。SDL提供了数种控制图像、声音、输出入的函数。让开发人员仅仅要用同样或是类似的代码就能够开发出跨多个平台(Linux、Windows、Mac OS X等)的应用软件。眼下SDL多用于开发游戏、模拟器、媒体播放器等多媒体应用领域。用以下这张图能够非常明白地说明SDL的位置。
SDL实际上并不限于视音频的播放,它将功能分成下列数个子系统(subsystem):
Video(图像):图像控制以及线程(thread)和事件管理(event)。
Audio(声音):声音控制
Joystick(摇杆):游戏摇杆控制
CD-ROM(光盘驱动器):光盘媒体控制
Window Management(视窗管理):与视窗程序设计集成
Event(事件驱动):处理事件驱动
Audio(声音):声音控制
Joystick(摇杆):游戏摇杆控制
CD-ROM(光盘驱动器):光盘媒体控制
Window Management(视窗管理):与视窗程序设计集成
Event(事件驱动):处理事件驱动
在Windows下。SDL与DirectX的相应关系例如以下。
SDL | DirectX |
SDL_Video、SDL_Image | DirectDraw、Direct3D |
SDL_Audio、SDL_Mixer | DirectSound |
SDL_Joystick、SDL_Base | DirectInput |
SDL_Net | DirectPlay |
SDL播放视频的流程
SDL播放视频的技术在此前做的FFmpeg的演示样例程序中已经多次用到。在这里又一次总结一下流程。1. 初始化
1) 初始化SDL
2) 创建窗体(Window)
3) 基于窗体创建渲染器(Render)
4) 创建纹理(Texture)
2. 循环显示画面2) 创建窗体(Window)
3) 基于窗体创建渲染器(Render)
4) 创建纹理(Texture)
1) 设置纹理的数据
2) 纹理复制给渲染目标
3) 显示
2) 纹理复制给渲染目标
3) 显示
以下具体分析一下上文的流程。
1. 初始化
1) 初始化SDL
使用SDL_Init()初始化SDL。该函数能够确定希望激活的子系统。
SDL_Init()函数原型例如以下:
int SDLCALL SDL_Init(Uint32 flags)
当中。flags能够取下列值:
SDL_INIT_TIMER:定时器
SDL_INIT_AUDIO:音频
SDL_INIT_VIDEO:视频
SDL_INIT_JOYSTICK:摇杆
SDL_INIT_HAPTIC:触摸屏
SDL_INIT_GAMECONTROLLER:游戏控制器
SDL_INIT_EVENTS:事件
SDL_INIT_NOPARACHUTE:不捕获关键信号(这个没研究过)
SDL_INIT_EVERYTHING:包括上述全部选项
SDL_INIT_AUDIO:音频
SDL_INIT_VIDEO:视频
SDL_INIT_JOYSTICK:摇杆
SDL_INIT_HAPTIC:触摸屏
SDL_INIT_GAMECONTROLLER:游戏控制器
SDL_INIT_EVENTS:事件
SDL_INIT_NOPARACHUTE:不捕获关键信号(这个没研究过)
SDL_INIT_EVERYTHING:包括上述全部选项
有关SDL_Init()有一点须要注意:初始化的时候尽量做到“够用就好”,而不要用SDL_INIT_EVERYTHING。由于有些情况下使用SDL_INIT_EVERYTHING会出现一些不可预知的问题。
比如,在MFC应用程序中播放纯音频。假设初始化SDL的时候使用SDL_INIT_EVERYTHING。那么就会出现听不到声音的情况。
后来发现,去掉了SDL_INIT_VIDEO之后,问题才得以解决。
2) 创建窗体(Window)
使用SDL_CreateWindow()创建一个用于视频播放的窗体。SDL_CreateWindow()的原型例如以下。
SDL_Window * SDLCALL SDL_CreateWindow(const char *title, int x, int y, int w, int h, Uint32 flags);
參数含义例如以下。
title :窗体标题
x :窗体位置x坐标。也能够设置为SDL_WINDOWPOS_CENTERED或SDL_WINDOWPOS_UNDEFINED。
y :窗体位置y坐标。同上。
w :窗体的宽
h :窗体的高
flags :支持下列标识。
包括了窗体的是否最大化、最小化,是否能调整边界等等属性。
::SDL_WINDOW_FULLSCREEN, ::SDL_WINDOW_OPENGL,
::SDL_WINDOW_HIDDEN, ::SDL_WINDOW_BORDERLESS,
::SDL_WINDOW_RESIZABLE, ::SDL_WINDOW_MAXIMIZED,
::SDL_WINDOW_MINIMIZED, ::SDL_WINDOW_INPUT_GRABBED,
::SDL_WINDOW_ALLOW_HIGHDPI.
返回创建完毕的窗体的ID。假设创建失败则返回0。
3) 基于窗体创建渲染器(Render)
使用SDL_CreateRenderer()基于窗体创建渲染器。
SDL_CreateRenderer()原型例如以下。
SDL_Renderer * SDLCALL SDL_CreateRenderer(SDL_Window * window, int index, Uint32 flags);
參数含义例如以下。
window : 渲染的目标窗体。
index :打算初始化的渲染设备的索引。
设置“-1”则初始化默认的渲染设备。
flags :支持以下值(位于SDL_RendererFlags定义中)
SDL_RENDERER_SOFTWARE :使用软件渲染
SDL_RENDERER_ACCELERATED :使用硬件加速
SDL_RENDERER_PRESENTVSYNC:和显示器的刷新率同步
SDL_RENDERER_TARGETTEXTURE :不太懂
返回创建完毕的渲染器的ID。假设创建失败则返回NULL。SDL_RENDERER_ACCELERATED :使用硬件加速
SDL_RENDERER_PRESENTVSYNC:和显示器的刷新率同步
SDL_RENDERER_TARGETTEXTURE :不太懂
4) 创建纹理(Texture)
使用SDL_CreateTexture()基于渲染器创建一个纹理。SDL_CreateTexture()的原型例如以下。
SDL_Texture * SDLCALL SDL_CreateTexture(SDL_Renderer * renderer, Uint32 format, int access, int w, int h);
參数的含义例如以下。
renderer:目标渲染器。
format :纹理的格式。后面会详述。
access :能够取以下值(定义位于SDL_TextureAccess中)
SDL_TEXTUREACCESS_STATIC :变化极少
SDL_TEXTUREACCESS_STREAMING :变化频繁
SDL_TEXTUREACCESS_TARGET :临时没有理解
w :纹理的宽SDL_TEXTUREACCESS_STREAMING :变化频繁
SDL_TEXTUREACCESS_TARGET :临时没有理解
h :纹理的高
创建成功则返回纹理的ID,失败返回0。
在纹理的创建过程中。须要指定纹理的格式(即第二个參数)。
SDL的中的格式非常多,例如以下所列。
SDL_PIXELFORMAT_UNKNOWN, SDL_PIXELFORMAT_INDEX1LSB = SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_INDEX1, SDL_BITMAPORDER_4321, 0, 1, 0), SDL_PIXELFORMAT_INDEX1MSB = SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_INDEX1, SDL_BITMAPORDER_1234, 0, 1, 0), SDL_PIXELFORMAT_INDEX4LSB = SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_INDEX4, SDL_BITMAPORDER_4321, 0, 4, 0), SDL_PIXELFORMAT_INDEX4MSB = SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_INDEX4, SDL_BITMAPORDER_1234, 0, 4, 0), SDL_PIXELFORMAT_INDEX8 = SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_INDEX8, 0, 0, 8, 1), SDL_PIXELFORMAT_RGB332 = SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED8, SDL_PACKEDORDER_XRGB, SDL_PACKEDLAYOUT_332, 8, 1), SDL_PIXELFORMAT_RGB444 = SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_XRGB, SDL_PACKEDLAYOUT_4444, 12, 2), SDL_PIXELFORMAT_RGB555 = SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_XRGB, SDL_PACKEDLAYOUT_1555, 15, 2), SDL_PIXELFORMAT_BGR555 = SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_XBGR, SDL_PACKEDLAYOUT_1555, 15, 2), SDL_PIXELFORMAT_ARGB4444 = SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_ARGB, SDL_PACKEDLAYOUT_4444, 16, 2), SDL_PIXELFORMAT_RGBA4444 = SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_RGBA, SDL_PACKEDLAYOUT_4444, 16, 2), SDL_PIXELFORMAT_ABGR4444 = SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_ABGR, SDL_PACKEDLAYOUT_4444, 16, 2), SDL_PIXELFORMAT_BGRA4444 = SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_BGRA, SDL_PACKEDLAYOUT_4444, 16, 2), SDL_PIXELFORMAT_ARGB1555 = SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_ARGB, SDL_PACKEDLAYOUT_1555, 16, 2), SDL_PIXELFORMAT_RGBA5551 = SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_RGBA, SDL_PACKEDLAYOUT_5551, 16, 2), SDL_PIXELFORMAT_ABGR1555 = SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_ABGR, SDL_PACKEDLAYOUT_1555, 16, 2), SDL_PIXELFORMAT_BGRA5551 = SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_BGRA, SDL_PACKEDLAYOUT_5551, 16, 2), SDL_PIXELFORMAT_RGB565 = SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_XRGB, SDL_PACKEDLAYOUT_565, 16, 2), SDL_PIXELFORMAT_BGR565 = SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_XBGR, SDL_PACKEDLAYOUT_565, 16, 2), SDL_PIXELFORMAT_RGB24 = SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_ARRAYU8, SDL_ARRAYORDER_RGB, 0, 24, 3), SDL_PIXELFORMAT_BGR24 = SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_ARRAYU8, SDL_ARRAYORDER_BGR, 0, 24, 3), SDL_PIXELFORMAT_RGB888 = SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_XRGB, SDL_PACKEDLAYOUT_8888, 24, 4), SDL_PIXELFORMAT_RGBX8888 = SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_RGBX, SDL_PACKEDLAYOUT_8888, 24, 4), SDL_PIXELFORMAT_BGR888 = SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_XBGR, SDL_PACKEDLAYOUT_8888, 24, 4), SDL_PIXELFORMAT_BGRX8888 = SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_BGRX, SDL_PACKEDLAYOUT_8888, 24, 4), SDL_PIXELFORMAT_ARGB8888 = SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_ARGB, SDL_PACKEDLAYOUT_8888, 32, 4), SDL_PIXELFORMAT_RGBA8888 = SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_RGBA, SDL_PACKEDLAYOUT_8888, 32, 4), SDL_PIXELFORMAT_ABGR8888 = SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_ABGR, SDL_PACKEDLAYOUT_8888, 32, 4), SDL_PIXELFORMAT_BGRA8888 = SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_BGRA, SDL_PACKEDLAYOUT_8888, 32, 4), SDL_PIXELFORMAT_ARGB2101010 = SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_ARGB, SDL_PACKEDLAYOUT_2101010, 32, 4), SDL_PIXELFORMAT_YV12 = /**< Planar mode: Y + V + U (3 planes) */ SDL_DEFINE_PIXELFOURCC('Y', 'V', '1', '2'), SDL_PIXELFORMAT_IYUV = /**< Planar mode: Y + U + V (3 planes) */ SDL_DEFINE_PIXELFOURCC('I', 'Y', 'U', 'V'), SDL_PIXELFORMAT_YUY2 = /**< Packed mode: Y0+U0+Y1+V0 (1 plane) */ SDL_DEFINE_PIXELFOURCC('Y', 'U', 'Y', '2'), SDL_PIXELFORMAT_UYVY = /**< Packed mode: U0+Y0+V0+Y1 (1 plane) */ SDL_DEFINE_PIXELFOURCC('U', 'Y', 'V', 'Y'), SDL_PIXELFORMAT_YVYU = /**< Packed mode: Y0+V0+Y1+U0 (1 plane) */ SDL_DEFINE_PIXELFOURCC('Y', 'V', 'Y', 'U')
这一看确实给人一种“眼花缭乱”的感觉。
简单分析一下当中的定义吧。比如ARGB8888的定义例如以下。
SDL_PIXELFORMAT_ARGB8888 = SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_ARGB, SDL_PACKEDLAYOUT_8888, 32, 4),
当中用了一个宏SDL_DEFINE_PIXELFORMAT用于将几种属性合并到一个格式中。
以下我们看看一个格式都包括哪些属性:
SDL_PIXELTYPE_PACKED32:代表了像素分量的存储方式。
PACKED代表了像素的几个分量是一起存储的。内存中存储方式例如以下:R1|G1|B1,R2|G2|B2…;ARRAY则代表了像素的几个分量是分开存储的。内存中存储方式例如以下:R1|R2|R3…。G1|G2|G3…,B1|B2|B3…
SDL_PACKEDORDER_ARGB:代表了PACKED存储方式下像素分量的顺序。注意。这里所说的顺序涉及到了一个“大端”和“小端”的问题。
这个问题在《最简单的视音频播放演示样例2:GDI播放YUV, RGB》中已经叙述,不再反复记录。
对于Windows这种“小端”系统,“ARGB”格式在内存中的存储顺序是B|G|R|A。
SDL_PACKEDLAYOUT_8888:说明了每一个分量占领的比特数。比如ARGB格式每一个分量分别占领了8bit。
32:每一个像素占用的比特数。
比如ARGB格式占用了32bit(每一个分量占领8bit)。
4:每一个像素占用的字节数。
比如ARGB格式占用了4Byte(每一个分量占领1Byte)。
2. 循环显示画面
1) 设置纹理的数据
使用SDL_UpdateTexture()设置纹理的像素数据。SDL_UpdateTexture()的原型例如以下。
int SDLCALL SDL_UpdateTexture(SDL_Texture * texture, const SDL_Rect * rect, const void *pixels, int pitch);
參数的含义例如以下。
texture:目标纹理。
rect:更新像素的矩形区域。设置为NULL的时候更新整个区域。
pixels:像素数据。
pitch:一行像素数据的字节数。
成功的话返回0。失败的话返回-1。
2) 纹理复制给渲染目标
使用SDL_RenderCopy()将纹理数据复制给渲染目标。在使用SDL_RenderCopy()之前,能够使用SDL_RenderClear()先使用清空渲染目标。实际上视频播放的时候不使用SDL_RenderClear()也是能够的,由于视频的后一帧会全然覆盖前一帧。
SDL_RenderClear()原型例如以下。
int SDLCALL SDL_RenderClear(SDL_Renderer * renderer);
參数renderer用于指定渲染目标。
SDL_RenderCopy()原型例如以下。
int SDLCALL SDL_RenderCopy(SDL_Renderer * renderer, SDL_Texture * texture, const SDL_Rect * srcrect, const SDL_Rect * dstrect);
參数的含义例如以下。
renderer:渲染目标。
texture:输入纹理。
srcrect:选择输入纹理的一块矩形区域作为输入。设置为NULL的时候整个纹理作为输入。
dstrect:选择渲染目标的一块矩形区域作为输出。
设置为NULL的时候整个渲染目标作为输出。
成功的话返回0,失败的话返回-1。
3) 显示
使用SDL_RenderPresent()显示画面。
SDL_RenderPresent()原型例如以下。
void SDLCALL SDL_RenderPresent(SDL_Renderer * renderer);
參数renderer用于指定渲染目标。
流程总结
在《最简单的基于FFMPEG+SDL的视频播放器 ver2(採用SDL2.0)》中总结过SDL2播放视频的流程。在这里简单复制过来。使用SDL播放视频的流程能够概括为下图。
SDL中几个关键的结构体之间的关系能够用下图概述。
简单解释一下各变量的作用:
SDL_Window就是使用SDL的时候弹出的那个窗体。
在SDL1.x版本号中,仅仅能够创建一个一个窗体。在SDL2.0版本号中,能够创建多个窗体。
SDL_Texture用于显示YUV数据。一个SDL_Texture相应一帧YUV数据。
SDL_Renderer用于渲染SDL_Texture至SDL_Window。
SDL_Rect用于确定SDL_Texture显示的位置。
代码
贴出源码。/** * 最简单的SDL2播放视频的样例(SDL2播放RGB/YUV) * Simplest Video Play SDL2 (SDL2 play RGB/YUV) * * 雷霄骅 Lei Xiaohua * leixiaohua1020@126.com * 中国传媒大学/数字电视技术 * Communication University of China / Digital TV Technology * http://blog.csdn.net/leixiaohua1020 * * 本程序使用SDL2播放RGB/YUV视频像素数据。 SDL实际上是对底层画图 * API(Direct3D,OpenGL)的封装,使用起来明显简单于直接调用底层 * API。 * * 函数调用过程例如以下: * * [初始化] * SDL_Init(): 初始化SDL。 * SDL_CreateWindow(): 创建窗体(Window)。 * SDL_CreateRenderer(): 基于窗体创建渲染器(Render)。 * SDL_CreateTexture(): 创建纹理(Texture)。 * * [循环渲染数据] * SDL_UpdateTexture(): 设置纹理的数据。 * SDL_RenderCopy(): 纹理复制给渲染器。 * SDL_RenderPresent(): 显示。 * * This software plays RGB/YUV raw video data using SDL2. * SDL is a wrapper of low-level API (Direct3D, OpenGL). * Use SDL is much easier than directly call these low-level API. * * The process is shown as follows: * * [Init] * SDL_Init(): Init SDL. * SDL_CreateWindow(): Create a Window. * SDL_CreateRenderer(): Create a Render. * SDL_CreateTexture(): Create a Texture. * * [Loop to Render data] * SDL_UpdateTexture(): Set Texture's data. * SDL_RenderCopy(): Copy Texture to Render. * SDL_RenderPresent(): Show. */ #include <stdio.h> extern "C" { #include "sdl/SDL.h" }; //set '1' to choose a type of file to play #define LOAD_BGRA 1 #define LOAD_RGB24 0 #define LOAD_BGR24 0 #define LOAD_YUV420P 0 //Bit per Pixel #if LOAD_BGRA const int bpp=32; #elif LOAD_RGB24|LOAD_BGR24 const int bpp=24; #elif LOAD_YUV420P const int bpp=12; #endif int screen_w=500,screen_h=500; const int pixel_w=320,pixel_h=180; unsigned char buffer[pixel_w*pixel_h*bpp/8]; //BPP=32 unsigned char buffer_convert[pixel_w*pixel_h*4]; //Convert RGB24/BGR24 to RGB32/BGR32 //And change Endian if needed void CONVERT_24to32(unsigned char *image_in,unsigned char *image_out,int w,int h){ for(int i =0;i<h;i++) for(int j=0;j<w;j++){ //Big Endian or Small Endian? //"ARGB" order:high bit -> low bit. //ARGB Format Big Endian (low address save high MSB, here is A) in memory : A|R|G|B //ARGB Format Little Endian (low address save low MSB, here is B) in memory : B|G|R|A if(SDL_BYTEORDER==SDL_LIL_ENDIAN){ //Little Endian (x86): R|G|B --> B|G|R|A image_out[(i*w+j)*4+0]=image_in[(i*w+j)*3+2]; image_out[(i*w+j)*4+1]=image_in[(i*w+j)*3+1]; image_out[(i*w+j)*4+2]=image_in[(i*w+j)*3]; image_out[(i*w+j)*4+3]='0'; }else{ //Big Endian: R|G|B --> A|R|G|B image_out[(i*w+j)*4]='0'; memcpy(image_out+(i*w+j)*4+1,image_in+(i*w+j)*3,3); } } } //Refresh Event #define REFRESH_EVENT (SDL_USEREVENT + 1) int thread_exit=0; int refresh_video(void *opaque){ while (thread_exit==0) { SDL_Event event; event.type = REFRESH_EVENT; SDL_PushEvent(&event); SDL_Delay(40); } return 0; } int main(int argc, char* argv[]) { if(SDL_Init(SDL_INIT_VIDEO)) { printf( "Could not initialize SDL - %s\n", SDL_GetError()); return -1; } SDL_Window *screen; //SDL 2.0 Support for multiple windows screen = SDL_CreateWindow("Simplest Video Play SDL2", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, screen_w, screen_h,SDL_WINDOW_OPENGL|SDL_WINDOW_RESIZABLE); if(!screen) { printf("SDL: could not create window - exiting:%s\n",SDL_GetError()); return -1; } SDL_Renderer* sdlRenderer = SDL_CreateRenderer(screen, -1, 0); Uint32 pixformat=0; #if LOAD_BGRA //Note: ARGB8888 in "Little Endian" system stores as B|G|R|A pixformat= SDL_PIXELFORMAT_ARGB8888; #elif LOAD_RGB24 pixformat= SDL_PIXELFORMAT_RGB888; #elif LOAD_BGR24 pixformat= SDL_PIXELFORMAT_BGR888; #elif LOAD_YUV420P //IYUV: Y + U + V (3 planes) //YV12: Y + V + U (3 planes) pixformat= SDL_PIXELFORMAT_IYUV; #endif SDL_Texture* sdlTexture = SDL_CreateTexture(sdlRenderer,pixformat, SDL_TEXTUREACCESS_STREAMING,pixel_w,pixel_h); FILE *fp=NULL; #if LOAD_BGRA fp=fopen("../test_bgra_320x180.rgb","rb+"); #elif LOAD_RGB24 fp=fopen("../test_rgb24_320x180.rgb","rb+"); #elif LOAD_BGR24 fp=fopen("../test_bgr24_320x180.rgb","rb+"); #elif LOAD_YUV420P fp=fopen("../test_yuv420p_320x180.yuv","rb+"); #endif if(fp==NULL){ printf("cannot open this file\n"); return -1; } SDL_Rect sdlRect; SDL_Thread *refresh_thread = SDL_CreateThread(refresh_video,NULL,NULL); SDL_Event event; while(1){ //Wait SDL_WaitEvent(&event); if(event.type==REFRESH_EVENT){ if (fread(buffer, 1, pixel_w*pixel_h*bpp/8, fp) != pixel_w*pixel_h*bpp/8){ // Loop fseek(fp, 0, SEEK_SET); fread(buffer, 1, pixel_w*pixel_h*bpp/8, fp); } #if LOAD_BGRA //We don't need to change Endian //Because input BGRA pixel data(B|G|R|A) is same as ARGB8888 in Little Endian (B|G|R|A) SDL_UpdateTexture( sdlTexture, NULL, buffer, pixel_w*4); #elif LOAD_RGB24|LOAD_BGR24 //change 24bit to 32 bit //and in Windows we need to change Endian CONVERT_24to32(buffer,buffer_convert,pixel_w,pixel_h); SDL_UpdateTexture( sdlTexture, NULL, buffer_convert, pixel_w*4); #elif LOAD_YUV420P SDL_UpdateTexture( sdlTexture, NULL, buffer, pixel_w); #endif //FIX: If window is resize sdlRect.x = 0; sdlRect.y = 0; sdlRect.w = screen_w; sdlRect.h = screen_h; SDL_RenderClear( sdlRenderer ); SDL_RenderCopy( sdlRenderer, sdlTexture, NULL, &sdlRect); SDL_RenderPresent( sdlRenderer ); //Delay 40ms SDL_Delay(40); }else if(event.type==SDL_WINDOWEVENT){ //If Resize SDL_GetWindowSize(screen,&screen_w,&screen_h); }else if(event.type==SDL_QUIT){ break; } } return 0; }
执行结果
程序的执行结果例如以下图所看到的。下载
代码位于“Simplest Media Play”中SourceForge项目地址:https://sourceforge.net/projects/simplestmediaplay/
CSDN下载地址:http://download.csdn.net/detail/leixiaohua1020/8054395
注:
该项目会不定时的更新并修复一些小问题。最新的版本号请參考该系列文章的总述页面:
《最简单的视音频播放演示样例1:总述》
上述project包括了使用各种API(Direct3D。OpenGL,GDI。DirectSound,SDL2)播放多媒体样例。当中音频输入为PCM採样数据。输出至系统的声卡播放出来。
视频输入为YUV/RGB像素数据。输出至显示器上的一个窗体播放出来。
通过本project的代码刚開始学习的人能够高速学习使用这几个API播放视频和音频的技术。
一共包括了例如以下几个子project:
simplest_audio_play_directsound: 使用DirectSound播放PCM音频採样数据。
simplest_audio_play_sdl2: 使用SDL2播放PCM音频採样数据。
simplest_video_play_direct3d: 使用Direct3D的Surface播放RGB/YUV视频像素数据。
simplest_video_play_direct3d_texture:使用Direct3D的Texture播放RGB视频像素数据。
simplest_video_play_gdi: 使用GDI播放RGB/YUV视频像素数据。
simplest_video_play_opengl: 使用OpenGL播放RGB/YUV视频像素数据。
simplest_video_play_opengl_texture: 使用OpenGL的Texture播放YUV视频像素数据。
simplest_video_play_sdl2: 使用SDL2播放RGB/YUV视频像素数据。
相关文章推荐
- 最简单的视音频播放演示样例5:OpenGL播放RGB/YUV
- 最简单的视音频播放演示样例4:Direct3D播放RGB(通过Texture)
- 最简单的视音频播放示例7:SDL2播放RGB/YUV
- 最简单的视音频播放示例7:SDL2播放RGB/YUV
- 最简单的视音频播放示例7:SDL2播放RGB/YUV
- 最简单的视音频播放示例7:SDL2播放RGB/YUV
- 最简单的视音频播放示例2:GDI播放YUV, RGB
- 最简单的视音频播放示例5:OpenGL播放RGB/YUV
- 最简单的视音频播放示例3:Direct3D播放YUV,RGB(通过Surface)
- 最简单的视音频播放示例2:GDI播放YUV, RGB
- 最简单的视音频播放示例3:Direct3D播放YUV,RGB(通过Surface)
- 最简单的视音频播放示例5:OpenGL播放RGB/YUV
- 视频和音频播放的演示最简单的例子6:OpenGL广播YUV420P(T经exture,采用Shader)
- 最简单的SDL2播放视频的例子(SDL2播放RGB/YUV)
- 最简单的视音频播放示例2:GDI播放YUV, RGB
- 最简单的视音频播放示例5:OpenGL播放RGB/YUV
- 最简单的视音频播放示例5:OpenGL播放RGB/YUV
- 最简单的SDL2播放视频的例子(SDL2播放RGB/YUV)--进阶
- 最简单的视音频播放演示样例8:DirectSound播放PCM
- 最简单的视音频播放示例2:GDI播放YUV, RGB