1. 程式人生 > >最簡單的視音訊播放示例7:SDL2播放RGB/YUV

最簡單的視音訊播放示例7:SDL2播放RGB/YUV

=====================================================

最簡單的視音訊播放示例系列文章列表:

=====================================================


本文記錄SDL播放視訊的技術。在這裡使用的版本是SDL2。實際上SDL本身並不提供視音訊播放的功能,它只是封裝了視音訊播放的底層API。在Windows平臺下,SDL封裝了Direct3D這類的API用於播放視訊;封裝了DirectSound這類的API用於播放音訊。因為SDL的編寫目的就是簡化視音訊播放的開發難度,所以使用SDL播放視訊(YUV/RGB)和音訊(PCM)資料非常的容易。下文記錄一下使用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(事件驅動):處理事件驅動

在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.       迴圈顯示畫面
1)       設定紋理的資料
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()有一點需要注意:初始化的時候儘量做到“夠用就好”,而不要用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。
 
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               :紋理的寬
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
 * [email protected]
 * 中國傳媒大學/數字電視技術
 * 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”中
 
 
 
 

注:

該專案會不定時的更新並修復一些小問題,最新的版本請參考該系列文章的總述頁面:


上述工程包含了使用各種API(Direct3D,OpenGL,GDI,DirectSound,SDL2)播放多媒體例子。其中音訊輸入為PCM取樣資料。輸出至系統的音效卡播放出來。視訊輸入為YUV/RGB畫素資料。輸出至顯示器上的一個視窗播放出來。
通過本工程的程式碼初學者可以快速學習使用這幾個API播放視訊和音訊的技術。
一共包括瞭如下幾個子工程:
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視訊畫素資料。
 
 

相關推薦

簡單音訊播放示例7SDL2播放RGB/YUV

=====================================================最簡單的視音訊播放示例系列文章列表:=====================================================本文記錄SDL播放視訊的技術

簡單音訊播放示例9SDL2播放PCM

最簡單的視音訊播放示例系列文章列表: ===================================================== 本文記錄SDL播放音訊的技術。在這裡使用的版本是SDL2。實際上SDL本身並不提供視音訊播放的功能,它只

簡單音訊播放示例6OpenGL播放YUV420P(通過Texture,使用Shader)

=====================================================最簡單的視音訊播放示例系列文章列表:=====================================================本文記錄OpenGL播放視訊

簡單音訊播放示例5OpenGL播放RGB/YUV

=====================================================最簡單的視音訊播放示例系列文章列表:=====================================================本文記錄OpenGL播放視訊

簡單音頻播放演示樣例7SDL2播放RGB/YUV

pro big 更新 沒有 opaque support 解決 控制 mem =====================================================最簡單的視音頻播放演示樣例系列文章列表:最簡單的視音頻播放演示樣例1:總述最簡單的視音

簡單的基於libVLC的例子簡單的基於libVLC的視訊播放器(圖形介面版)

=====================================================最簡單的基於libVLC的例子文章列表:=====================================================本文記錄使用libVLC

HTML5用audio標籤做一個簡單音訊播放

在做系統的時候,要求做一個音訊播放器,就在網上查找了一些資料,發現這樣的資料還是很千篇一律的,EasyUI框架並沒有給我們一個音訊播放器的功能,在bootstrap上有,但是也是結合html5來寫的,因此,我們在這裡就用純的html5血一個音訊播放器,如何播放本地的音訊。

簡單的基於libVLC的例子簡單的基於libVLC的視訊播放

=====================================================最簡單的基於libVLC的例子文章列表:=====================================================本文記錄使用libVLC

MongoDB簡單的入門教程之四使用Spring Boot操作MongoDB

Spring Boot 是一個輕量級框架,可以完成基於 Spring 的應用程式的大部分配置工作。Spring Boot的目的是提供一組工具,以便快速構建容易配置的Spring應用程式,省去大量傳統Spring專案的繁瑣配置。 MongoDB是一個基於分散式檔

音訊資料處理入門PCM音訊取樣資料處理

                =====================================================視音訊資料處理入門系列文章:=====================================================上一篇文章記錄了RGB/YUV視訊畫素

史上簡單的 SpringCloud 教程 | 第一篇 服務的註冊與發現(Eureka)

一、spring cloud簡介 spring cloud 為開發人員提供了快速構建分散式系統的一些工具,包括配置管理、服務發現、斷路器、路由、微代理

史上簡單的 SpringCloud 教程 | 第一篇 服務的註冊與發現Eureka(Finchley版本)

一、spring cloud簡介 鑑於《史上最簡單的Spring Cloud教程》很受讀者歡迎,再次我特意升級了一下版本,目前支援的版本為Spring Boot版本2.0.3.RELEASE,Spring Cloud版本為Finchley.RELEASE。

簡單的spring入門示例

應群裡一位朋友的要求,寫一個最簡單的spring示例,使用spring的MVC,並應用了spring的依賴注入 ,實現簡單應用,索性放在這裡供還沒入門的spring愛好者參考,初步感受一下spring應用(spring高手就不必看了,這裡並沒有涉及高階特性,比如與ORM框架的

簡單的基於libVLC的例子簡單的基於libVLC的推流器

=====================================================最簡單的基於libVLC的例子文章列表:=====================================================本文記錄基於libVLC

shareding-jdbc實現讀寫分離簡單的容易理解示例

資料庫建立create database demo_ds_master; //建立主庫create database demo_ds_slave_0; //建立從庫1create database

windows下簡單音訊採集示例

最近需要在window下進行音訊採集,網上找了很久都沒找到win7下如何採集pcm資料的完整示例,經過一翻折騰後寫了一個很簡單的demo程式以供同行進行參考,如有不正確的地方請指正 本例是採用audio core進行音訊採集 程式碼塊 #include "

簡單的基於FFMPEG+SDL的音視訊播放

一、概述         在《最簡單的基於FFMPEG+SDL的音訊播放器》記錄一中,我們實現了音訊的播放。更早前,我們在《最簡單的基於FFMPEG+SDL的視訊播放器》記錄一和二中,實現了視訊的播放。在實現視訊播放的時候,我們設定了一個延遲40ms,否則視訊就會以解碼的速

音訊資料處理入門UDP-RTP協議解析

=====================================================視音訊資料處理入門系列文章:=====================================================本文介紹網路協議資料的處理程式。網路

音訊資料處理入門AAC音訊碼流解析

=====================================================視音訊資料處理入門系列文章:=====================================================本文繼續上一篇文章的內容,介紹一個音

音訊資料處理入門FLV封裝格式解析

=====================================================視音訊資料處理入門系列文章:=====================================================前兩篇文章介紹了音訊碼流處理程式和視