FFmpeg編程(三)SDL開發


一:SDL介紹與安裝

(一)SDL介紹

(二)SDL安裝

 

1.源碼下載:http://www.libsdl.org/download-2.0.php

2.生成Makefile文件

./configure --prefix=/usr/local

3.安裝

sudo make -j 8 && sudo make install

二:SDL的簡單使用

SDL播放視頻的代碼流程如下所示:

初始化: 

SDL_Init(): 初始化SDL。 

SDL_CreateWindow(): 創建窗口(Window)。 

SDL_CreateRenderer(): 基於窗口創建渲染器(Render)。 

SDL_CreateTexture(): 創建紋理(Texture)。

循環渲染數據: 

SDL_UpdateTexture(): 設置紋理的數據。 

SDL_RenderCopy(): 紋理復制給渲染器。
SDL_RenderPresent(): 顯示。

(一)基本使用步驟

(二)SDL渲染窗口

1.SDL_Init/SDL_Quit 初始化和退出操作

https://blog.csdn.net/leixiaohua1020/article/details/40680907

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:包含上述所有選項

2.SDL_CreateWindow()/SDL_DestroyWindow() 創建窗口(比如將圖片渲染到窗口)

https://blog.csdn.net/leixiaohua1020/article/details/40701203

SDL_Window * SDLCALL SDL_CreateWindow(const char *title, int x, int y, int w, int h, Uint32 flags);

參數含義如下:https://blog.csdn.net/qq_25333681/article/details/89787867

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.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。

(三)簡單實例(未渲染)

#include <stdio.h>
#include <SDL.h>

int main(int argc,char* argv[]){
    SDL_Window* wind = NULL;

    SDL_Init(SDL_INIT_VIDEO);    //進行視頻初始化

    wind = SDL_CreateWindow("SDL2 Window",
        200,200,
        640,480,
        SDL_WINDOW_SHOWN);    //顯示、有邊界

    if(!wind){
        printf("Failed to Create window!\n");
        goto __EXIT;
    }

    while(1){
        sleep(5000);
    }

    SDL_DestroyWindow(wind);

__EXIT:
    SDL_Quit();
    return 0;
}
View Code
gcc 01_sdl_sample.c -o 01ss -I /usr/local/include/SDL2/ -L /usr/local/lib/ -lSDL2

可以看到出現窗口,但是沒有渲染,導致出現透明狀態,顯示屏幕原本信息 

(四)簡單實例(使用渲染器)

SDL_Renderer表示渲染上下文。這意味着它包含與渲染相關的所有當前設置,以及有關如何渲染當前幀的說明。

要創建渲染上下文,可以使用函數SDL_CreateWindowAndRenderer()或SDL_CreateRenderer()。前者同時創建窗口和渲染器。后者要求我們先創建一個窗口。

在程序中,您將使用SDL_SetRenderDrawColor()等函數更改上下文中的設置,並使用SDL_RenderDrawPoint()等函數執行渲染操作。

int SDL_RenderClear(SDL_Renderer* renderer) ,該函數的作用是用指定的顏色清空緩沖區。renderer是上面創建的渲染器上下文。

void SDL_RenderPresent(SDL_Renderer* renderer),將緩沖區中的內容輸出到目標上,也就是 windows 窗口上。注意:渲染的數據也可以是從GPU渲染到屏幕!!

#include <stdio.h>
#include <SDL.h>

int main(int argc,char* argv[]){
    SDL_Window* wind = NULL;
    SDL_Renderer* rend = NULL;

    SDL_Init(SDL_INIT_VIDEO);    //進行視頻初始化

    wind = SDL_CreateWindow("SDL2 Window",
        200,200,
        640,480,
        SDL_WINDOW_SHOWN);    //顯示、有邊界

    if(!wind){
        printf("Failed to Create window!\n");
        goto __EXIT;
    }

    rend = SDL_CreateRenderer(wind,-1,0);    //創建Render渲染器
    if(!rend){
        printf("Failed to create render\n");
        goto __DWIND;
    }
    SDL_SetRenderDrawColor(rend,0,0,0,255);    //設置顏色和透明度(可選操作)
    SDL_RenderClear(rend);            //清屏處理(清空render之前的數據)
    SDL_RenderPresent(rend);            //將渲染器結果放入窗口中顯示出來

    SDL_Delay(5000);
__DWIND:
    SDL_DestroyWindow(wind);

__EXIT:
    SDL_Quit();
    return 0;
}
View Code

(五)SDL_Surface與SDL_Texture

SDL 渲染的工作原理:

在SDL_Render對象中有一個視頻緩沖區,該緩沖區我們稱之為SDL_Surface,它是按照像素存放圖像的。
我們一般把真彩色的像素稱為RGB24數據。
也就是說,每一個像素由24位組成,每8位代表一種顏色,像素的最終顏色是由RGB三種顏色混合而成的。

SDL_Texture 與SDL_Surface相似,也是一種緩沖區

只不過它存放的不是真正的像素數據,而是存放的圖像的描述信息。這些描述信息通過OpenGL、D3D 或 Metal等技術操作GPU,從而繪制出與SDL_Surface一樣的圖形,且效率更高(因為它是GPU硬件計算的)。

(六)SDL_Window與SDL_Render

SDL_Window代表的是窗口的邏輯概念,它是存放在主內存中的一個對象。

SDL_Render 是渲染器,它也是主存中的一個對象。對Render操作時實際上分為兩個階段

一、渲染階段。在該階段,用戶可以畫各種圖形渲染到SDL_Surface或SDL_Texture 中;

二、顯示階段。參SDL_Texture為數據,通過OpenGL操作GPU,最終將 SDL_Surfce 或SDL_Texture中的數據輸出到顯示器上。

三:SDL事件 

(一)SDL事件基本原理

(二)SDL事件種類

(三)SDL事件處理

1.poll 論詢機制,處理不及時,占CPU
2.wait 事件觸發(類似epoll池),處理及時,不占用過多CPU。

對於wait,可能出現線程阻塞,導致無法處理到來的其他事件,應該為每一個時間處理設置Timeout,所以出現了WaitEventTimeOut

(四)事件機制簡單使用 

#include <stdio.h>
#include <SDL.h>

int main(int argc,char* argv[]){
    int exitFlag=1;
    SDL_Window* wind = NULL;
    SDL_Renderer* rend = NULL;
    SDL_Event event;

    SDL_Init(SDL_INIT_VIDEO);    //進行視頻初始化

    wind = SDL_CreateWindow("SDL2 Window",
        200,200,
        640,480,
        SDL_WINDOW_SHOWN);    //顯示、有邊界

    if(!wind){
        printf("Failed to Create window!\n");
        goto __EXIT;
    }

    rend = SDL_CreateRenderer(wind,-1,0);    //創建Render渲染器
    if(!rend){
        printf("Failed to create render\n");
        goto __DWIND;
    }
    SDL_SetRenderDrawColor(rend,0,0,0,255);    //設置顏色和透明度(可選操作)
    SDL_RenderClear(rend);            //清屏處理(清空render之前的數據)
    SDL_RenderPresent(rend);            //將渲染器結果放入窗口中顯示出來

    while(exitFlag){
        SDL_WaitEvent(&event);    //如果沒有事件的話,會阻塞在這里的,不會一直while輪詢
        switch(event.type){
            case SDL_QUIT:
                exitFlag = 0;
                break;
            default:
                SDL_Log("event type is %d",event.type);
                break;
        }
    }


__DWIND:
    SDL_DestroyWindow(wind);

__EXIT:
    SDL_Quit();
    return 0;
}
View Code
gcc 02_sdl_event.c -o 02se -I /usr/local/include/SDL2/ -L /usr/local/lib/ -lSDL2

四:紋理渲染

依據SDL編程方式,這里又分為兩種情況:

1. 不使用紋理

由cpu直接繪制一幅畫(cpu需要將最原始的rgb/YUV數據,刷到屏幕上), 相當於學生小A直接在牆上畫畫

2. 使用紋理

相當於小A同學(cpu)指揮畫家(gpu)在紙上畫, 然后把紙貼在牆上。 
這個過程中畫是由畫家(gpu)畫的, 小A同學負責發號施令(即告訴畫家畫什么), 紙代表紋理, 畫家代表gpu, 所有繪制的操作都是在紋理上進行。
事實上,紋理的概念並不僅僅是一張紙, 還包括小A同學中對這幅畫的構思,可以理解成畫畫的算法, 而紙相當於是一個載體(內存空間,用於保存這些構思)。
gpu根據紋理就可以計算出這幅圖每個像素點的顏色( 相當於畫家根據小A同學的描述,畫出一幅畫一樣)

可以看出,使用紋理,可以減輕cpu的負擔, cpu處於一個發號施令的角色,圖片的計算過程交給效率更好的gpu來做,可以提高渲染的效率

(一)紋理渲染基本原理

圖像結果渲染器,變為紋理(即對這張圖像的描述數據),將紋理交給顯卡GPU,GPU經過計算,將圖像顯示在窗口中! 

(二)SDL紋理相關API

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。

(三)渲染相關API

SDL_CreateTexture用於創建紋理, 若要使用SDL_SetRenderTarget()設置渲染到紋理時, 必須使用SDL_TEXTUREACCESS_TARGET方式來創建紋理。

函數SDL_SetRenderTarget()用於在渲染到紋理或屏幕之間進行選擇。切換方式如下:

        SDL_SetRenderTarget(renderer, texture) // 渲染到紋理
 SDL_SetRenderTarget(renderer, NULL) // 渲染到屏幕(默認渲染到窗口/屏幕

int SDL_RenderCopy(SDL_Renderer*   renderer, SDL_Texture*  texture, const SDL_Rect* srcrect, const SDL_Rect* dstrect)將紋理拷貝到顯卡GPU中去,通過SDL_RenderPresent進行渲染到屏幕

renderer:渲染目標。
texture:輸入紋理。
srcrect:選擇輸入紋理的一塊矩形區域作為輸入。設置為NULL的時候整個紋理作為輸入。
dstrect:選擇渲染目標的一塊矩形區域作為輸出。設置為NULL的時候整個渲染目標作為輸出。

(四)紋理使用

#include <stdio.h>
#include <SDL.h>

int main(int argc,char* argv[]){
    int exitFlag=1;
    SDL_Window* wind = NULL;
    SDL_Renderer* rend = NULL;
    SDL_Texture* text = NULL;
    SDL_Event event;
    SDL_Rect rect;    //創建矩形

    SDL_Init(SDL_INIT_VIDEO);    //進行視頻初始化

    wind = SDL_CreateWindow("SDL2 Window",
        200,200,
        640,480,
        SDL_WINDOW_SHOWN);    //顯示、有邊界

    if(!wind){
        printf("Failed to Create window!\n");
        goto __EXIT;
    }

    rend = SDL_CreateRenderer(wind,-1,0);    //創建Render渲染器
    if(!rend){
        printf("Failed to create render\n");
        goto __DWIND;
    }
    SDL_SetRenderDrawColor(rend,0,0,0,255);    //設置顏色和透明度(可選操作)
    SDL_RenderClear(rend);            //清屏處理(清空render之前的數據)
    SDL_RenderPresent(rend);            //將渲染器結果放入窗口中顯示出來

    text = SDL_CreateTexture(rend,SDL_PIXELFORMAT_RGBA8888,SDL_TEXTUREACCESS_TARGET,
                640,480);    //創建紋理
    if(!text){
        printf("Failed to create texture\n");
        goto __DREND;
    }

    rect.w = 30;
    rect.h = 30;

    while(exitFlag){
        SDL_PollEvent(&event);    //如果沒有事件的話,會一直while輪詢
        switch(event.type){
            case SDL_QUIT:
                exitFlag = 0;
                break;
            default:
                SDL_Log("event type is %d",event.type);
                break;
        }

        rect.x = rand() % 610;
        rect.y = rand() % 450;

        SDL_SetRenderTarget(rend,text);    //渲染到紋理,下面的操作都會轉為紋理
        SDL_SetRenderDrawColor(rend,0,0,0,0);
        SDL_RenderClear(rend);

        //繪制矩形
        SDL_RenderDrawRect(rend,&rect);
        SDL_SetRenderDrawColor(rend,255,0,0,0);    //為矩形繪制顏色
        SDL_RenderFillRect(rend,&rect);    //將渲染器設置到矩形中去,使得clear只在矩形中生效

        SDL_SetRenderTarget(rend,NULL);    //使用默認渲染,到窗口
        
        SDL_RenderCopy(rend,text,NULL,NULL);    //拷貝紋理到顯卡
        SDL_RenderPresent(rend);                //將結果顯示到窗口
    }

    SDL_DestroyTexture(text);
__DREND:
    SDL_DestroyRenderer(rend);
__DWIND:
    SDL_DestroyWindow(wind);
__EXIT:
    SDL_Quit();
    return 0;
}
View Code
gcc 03_sdl_texture.c -o 03st -I /usr/local/include/SDL2/ -L /usr/local/lib/ -lSDL2

        SDL_SetRenderTarget(rend,text);       //渲染到紋理,下面的操作都會轉為紋理!!!

SDL_SetRenderDrawColor(rend,0,0,0,0);   SDL_RenderClear(rend);            //繪制矩形 SDL_RenderDrawRect(rend,&rect); SDL_SetRenderDrawColor(rend,255,0,0,0); //為矩形繪制顏色 SDL_RenderFillRect(rend,&rect);    //將渲染器設置到矩形中去,使得內部clear只在矩形中生效 SDL_SetRenderTarget(rend,NULL);    //使用默認渲染,到窗口;下面的操作會顯示在窗口,不經過紋理轉換 SDL_RenderCopy(rend,text,NULL,NULL); //拷貝紋理到顯卡---在窗口層面處理,而不是紋理 SDL_RenderPresent(rend); //GPU顯卡將計算的結果顯示到窗口

五:實戰YUV播放器

(一)創建線程(提高效率)

創建線程:SDL_Thread* SDL_CreateThread(SDL_ThreadFunction fn, const char*  name, void*  data)

fn: 線程要運行的函數。
name: 線程名。
data: 函數參數。

等待線程:void SDL_WaitThread(SDL_Thread* thread, int*   status) 等待線程結束

創建互斥量:SDL_mutex* SDL_CreateMutex(void) 也就是創建一個稀有資源,這樣大家就去搶這個資源。從而達到為真正資源加鎖的目的。

銷毀互斥量:void SDL_DestroyMutex(SDL_mutex* mutex)

加鎖: int SDL_LockMutex(SDL_mutex* mutex)

解鎖: int SDL_UnlockMutex(SDL_mutex* mutex)

(二)紋理更新

int SDLCALL SDL_UpdateTexture(SDL_Texture * texture, const SDL_Rect * rect, const void *pixels, int pitch); 

參數的含義如下:

texture:目標紋理。
rect:更新像素的矩形區域。設置為NULL的時候更新整個區域。
pixels:像素數據。 pitch:一行像素數據的字節數。

成功的話返回0,失敗的話返回-1

SDL_UpdateTexture()的大致流程如下:

1. 檢查輸入參數的合理性。例如像素格式是否支持,寬和高是否小於等於0等等。
2. 如果是一些特殊的格式,進行一定的處理:
a) 如果輸入的像素數據是YUV格式的,則會調用SDL_UpdateTextureYUV()進行處理。
b) 如果輸入的像素數據的像素格式不是渲染器支持的格式,則會調用SDL_UpdateTextureNative()進行處理3. 調用SDL_Render的UpdateTexture()方法更新紋理。這一步是整個函數的核心。

通過SDL_CreateTexture中指定紋理格式format為SDL_PIXELFORMAT_IYUV即可處理YUV數據!!

(三)編程實現

ffmpeg -i gfxm.mp4 -an -c:v rawvideo -pix_fmt yuv420p out.yuv
#include <stdio.h>
#include <string.h>
#include <SDL.h>

//下面定義自定義事件,SDL_USEREVENT 0x8000之后,到0xFFFF都可以
#define REFRASH_EVENT (SDL_USEREVENT + 1)
#define QUIT_EVENT (SDL_USEREVENT + 2)

int thread_exit = 0;    //控制線程退出

void refresh_video_timer(void* udata){
    thread_exit = 0;

    while(!thread_exit){
        SDL_Event event;
        event.type = REFRASH_EVENT;
        SDL_PushEvent(&event);
        SDL_Delay(40);    //25幀/S
    }

    thread_exit = 0;
    //開始退出
    SDL_Event event;
    event.type = QUIT_EVENT;
    SDL_PushEvent(&event);
    return 0;
}

int main(int argc,char* argv[]){
    int exitFlag=1;    //控制循環

    char* filename = NULL;
    FILE* fp = NULL;
    int len;

    uint8_t* src_data = NULL,*src_start=NULL;    //獲取視頻幀數據
    int v_w,v_h,w_w=1280,w_h=720;    //視頻寬高、窗口寬高
    unsigned int frame_len,frame_temp_len;

    SDL_Window* wind = NULL;
    SDL_Renderer* rend = NULL;
    SDL_Texture* text = NULL;
    SDL_Event event;
    SDL_Rect rect;    //創建矩形
    SDL_Thread* timer_thread = NULL;    //線程

    if(argc<4){
        printf("The number of parameter must be than 3\n");
        return 0;
    }

    filename = argv[1];    //獲取YUV文件----YUV為YUV420數據
    v_w = atoi(argv[2]);    //獲取文件的寬
    v_h = atoi(argv[3]);    //獲取視頻的高

    frame_temp_len = v_h*v_w*1.5;    //YUV420數據大小,需要對數據進行對齊操作
    frame_len = frame_temp_len;
    if(frame_temp_len&0xF){    //后4位存在數據,則表示不是16對齊
        frame_len = (frame_temp_len&0xFFF0) + 0x10;    //進16
    }

    if(SDL_Init(SDL_INIT_VIDEO)){    //進行視頻初始化
        printf("Can`t initialize SDL\n");
        return -1;
    }

    wind = SDL_CreateWindow("YUV Player",
        SDL_WINDOWPOS_UNDEFINED,SDL_WINDOWPOS_UNDEFINED,    //不指定左上位置
        w_w,w_h,            //設置窗口寬高,與視頻一般或者大些都可以
        SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);    //窗口可用於OpenGL上下文 窗口可以調整大小

    if(!wind){
        printf("Failed to Create window!\n");
        goto __EXIT;
    }

    rend = SDL_CreateRenderer(wind,-1,0);    //創建Render渲染器
    if(!rend){
        printf("Failed to create render\n");
        goto __DWIND;
    }

    //創建紋理
    text = SDL_CreateTexture(rend,
                            SDL_PIXELFORMAT_IYUV,    //< Planar mode: Y + U + V  (3 planes)
                            SDL_TEXTUREACCESS_TARGET,
                            v_w,v_h);    //創建紋理
    if(!text){
        printf("Failed to create texture\n");
        goto __DREND;
    }

    //開始讀取YUV數據
    fp = fopen(filename,"rb");
    if(!fp){
        printf("Failed to open file:%s\n",filename);
        goto __TEXT;
    }

    src_data = (uint8_t*)malloc(frame_len);    //分配空間
    if(!src_data){
        printf("Failed to alloc memory for src_data!\n");
        goto __FILE;
    }
    src_start = src_data;    //開始位置

    //開啟線程,處理其他控制事件
    timer_thread = SDL_CreateThread(refresh_video_timer,NULL,NULL);

    //要顯示紋理數據的矩形
    rect.x = 0;
    rect.y = 0;

    while(exitFlag){
        SDL_PollEvent(&event);    //如果沒有事件的話,會一直while輪詢
        switch(event.type){
            case SDL_QUIT:
                exitFlag = 0;
                break;
            case REFRASH_EVENT:    //更新紋理
                //先讀取一幀數據
                if((len=fread(src_data,1,frame_len,fp))<=0){    //數據讀取完成,退出
                    thread_exit = 1;
                    continue;
                }

                SDL_UpdateTexture(text,NULL,src_start,v_w);    //內部設置
                rect.w = w_w;    //因為窗口可以被重置
                rect.h = w_h;    

                SDL_RenderClear(rend);

                SDL_RenderCopy(rend,text,NULL,&rect);    //拷貝紋理到顯卡,選擇渲染目標的一塊矩形區域作為輸出
                SDL_RenderPresent(rend);                //將結果顯示到窗口
                break;
            case QUIT_EVENT:    //退出播放
                exitFlag = 0;
                break;
            default:
                SDL_Log("event type is %d",event.type);
                break;
        }
    }

    if(src_data)
        free(src_data);
__FILE:
    if(fp)
        fclose(fp);
__TEXT:
    SDL_DestroyTexture(text);
__DREND:
    SDL_DestroyRenderer(rend);
__DWIND:
    SDL_DestroyWindow(wind);
__EXIT:
    SDL_Quit();
    return 0;
}
View Code
gcc 04_sdl_yuv.c -o 04sy -I /usr/local/include/SDL2/ -L /usr/local/lib/ -lSDL2 
./04sy out.yuv 864 486

六:實戰PCM播放器

使用 SDL 的音頻 API 來播放聲音。其基本流程是,從 pcm 文件一塊一塊的讀數據。然后通過 read_audio_data 這個回調函數給聲卡喂數據。

如果一次沒用完,SDL會再次調用回調函數讀數據。如果audio_buf中的數據用完了,則再次從文件中讀一塊數據,直到讀到文件尾。

(一)播放音頻基本流程

(二)播放音頻原則

(三)相關API

1.打開音頻設備    int SDL_OpenAudio(SDL_AudioSpec* desired,SDL_AudioSpec* obtained) 

desired: 設置音頻參數。

參數 說明
freq 每秒采頻率
SDL_AudioFormat 音頻數據存儲格式
channels 通道數
silence 靜音值
samples 采樣個數
size 音頻緩沖區大小
SDL_AudioCallback 回調函數
userdata 回調函數參數指針

obtained: 返回參數。

2.關閉音頻設備    void SDL_CloseAudio(void)

3.播放與暫停  void SDL_PauseAudio(int pause_on)

pause_on: 0, 暫停播放;1, 播放;

4.喂數據  void SDL_MixAudio(Uint8* dst,const Uint8* src,Uint32 len,int volume)

dst: 目的緩沖區
src: 源緩沖區
len: 音頻數據長度
volume: 音量大小,0-128 之間的數。SDL_MIX_MAXVOLUME代表最大音量。

(四)編程實現

ffmpeg -i out.aac -ar 44100 -ac 2 -f s16le out.pcm
#include <stdio.h>
#include <string.h>
#include <SDL.h>

#define BLOCK_SIZE 4096000 //4096k,設置一個較大的數值空間

//將數據存放在全局
static uint8_t* audio_buf = NULL;
static uint8_t* audio_pos = NULL;
static int buffer_len = 0;

//回調函數
void read_audio_data(void* udata,uint8_t* stream,int len){    //回調數據,目的緩沖區,和緩沖區長度
    if(buffer_len == 0)
        return;

    SDL_memset(stream,0,len);    //初始化緩沖區

    len = (len<buffer_len)?len:buffer_len;    //獲取空間長度
    SDL_MixAudio(stream,audio_pos,len,SDL_MIX_MAXVOLUME);

    audio_pos += len;
    buffer_len -= len;
}

int main(int argc,char* argv[]){
    int exitFlag=1;    //控制循環

    char* filename = NULL;
    FILE* fp = NULL;

    SDL_AudioSpec spec;    //用來設置音頻參數

    if(argc<2){
        printf("The number of parameter must be than 1\n");
        return 0;
    }

    filename = argv[1];    //獲取pcm文件

    if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)){    //進行視頻初始化
        printf("Can`t initialize SDL\n");
        return -1;
    }

    fp = fopen(filename,"rb");
    if(!fp){
        printf("Failed to open file:%s",filename);
        goto __EXIT;
    }

    audio_buf = (uint8_t*)malloc(BLOCK_SIZE);    //分配空間
    if(!audio_buf){
        printf("Failed to alloc memory for audio buffer\n");
        goto __FILE;
    }

    //開始開啟音頻設備
    spec.freq = 44100;
    spec.format = AUDIO_S16SYS;
    spec.channels = 2;
    spec.silence = 0;
    spec.samples = 1024;    //每幀數據,單通道的采樣數量
    spec.callback = read_audio_data;
    spec.userdata = NULL;

    if(SDL_OpenAudio(&spec,NULL)){
        printf("Failed to open audio device:%s\n",SDL_GetError());
        goto __BUFFER;
    }

    //開始播放
    SDL_PauseAudio(0);

    while(1){
        buffer_len = fread(audio_buf,1,BLOCK_SIZE,fp);
        if(buffer_len<=0)
            break;
        printf("read block size:%d \n",buffer_len);

        audio_pos = audio_buf;

        while(audio_pos<(audio_buf+buffer_len)){    //數據沒有讀取完成
            SDL_Delay(1);    //延遲1毫秒,等等設備繼續讀取
        }
    }

    SDL_CloseAudio();    //關閉音頻設備
__BUFFER:
    if(audio_buf)
        free(audio_buf);
__FILE:
    if(fp)
        fclose(fp);
__EXIT:
    SDL_Quit();
    return 0;
}
View Code

  gcc 05_sdl_pcm.c -o 05sp -I /usr/local/include/SDL2/ -L /usr/local/lib/ -lSDL2

./05sp out.pcm

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM