1. 程式人生 > >5.基於SDL2播放PCM音訊

5.基於SDL2播放PCM音訊

接上一篇<基於FFMPEG將音訊解碼為PCM>,接下來就是需要將PCM音訊進行播放,查閱資料是通過SDL進行音視訊的播放,因此這裡記錄一下SDL相關的筆記。。。

一.簡介

摘抄自百度百科:

SDL(Simple DirectMedia Layer)是一套開放原始碼的跨平臺多媒體開發庫,使用C語言寫成。SDL提供了數種控制影象、聲音、輸出入的函式,讓開發者只要用相同或是相似的程式碼就可以開發出跨多個平臺(Linux、Windows、Mac OS X等)的應用軟體。目前SDL多用於開發遊戲、模擬器、媒體播放器等多媒體應用領域。

從上面介紹可以得知,SDL是一套開源的多媒體開發庫,對內封裝了與底層硬體互動的介面,對外提供統一的介面,我們使用者只需要呼叫介面,而不需要關注平臺、底層硬體等引數,即可正常進行的編碼工作。

SDL除了用於音視訊的播放外,還提供了其他的功能,例如:搖桿、光碟驅動器、視窗管理等等,而我們現在只需要關注的則是音視訊的播放,其他的暫且不提。

目前,使用的是SDL2。

二、流程及函式

1)音訊播放流程

只涉及到音訊的播放流程,初始化SDL—->根據提供引數開啟音訊裝置—>獲取音訊流—>迴圈播放—->結束

2)常用函式

1.SDL_Init()

函式原型: int SDLCALL SDL_Init(Uint32 flags);

初始化SDL系統,其中flag可以選擇的選項有:

/**
 *  \name SDL_INIT_*
 *
 *  These are the flags which may be passed to SDL_Init().  You should
 *  specify the subsystems which you will be using in your application.
 */
/* @{ */ #define SDL_INIT_TIMER 0x00000001 #define SDL_INIT_AUDIO 0x00000010 #define SDL_INIT_VIDEO 0x00000020 /**< SDL_INIT_VIDEO implies SDL_INIT_EVENTS */ #define SDL_INIT_JOYSTICK 0x00000200 /**< SDL_INIT_JOYSTICK implies SDL_INIT_EVENTS */ #define SDL_INIT_HAPTIC 0x00001000 #define SDL_INIT_GAMECONTROLLER 0x00002000
/**< SDL_INIT_GAMECONTROLLER implies SDL_INIT_JOYSTICK */ #define SDL_INIT_EVENTS 0x00004000 #define SDL_INIT_NOPARACHUTE 0x00100000 /**< Don't catch fatal signals */ #define SDL_INIT_EVERYTHING ( \ SDL_INIT_TIMER | SDL_INIT_AUDIO | SDL_INIT_VIDEO | SDL_INIT_EVENTS | \ SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC | SDL_INIT_GAMECONTROLLER \ ) /* @} */

可以根據自己程式碼裡面的需求進行選擇,例如只有音訊播放,則可以選擇SDL_INIT_AUDIO

2.SDL_INIT_AUDIO()

函式原型:int SDLCALL SDL_OpenAudio(SDL_AudioSpec * desired,
 SDL_AudioSpec * obtained);

根據提供的引數開啟音訊裝置,其中desired 為期望的音訊裝置引數, obtained為實際的音訊裝置引數,一般將其置為NULL即可。

結構體引數如下:

typedef struct SDL_AudioSpec
{
    int freq;                   /**< DSP frequency -- samples per second */
    SDL_AudioFormat format;     /**< Audio data format */
    Uint8 channels;             /**< Number of channels: 1 mono, 2 stereo */
    Uint8 silence;              /**< Audio buffer silence value (calculated) */
    Uint16 samples;             /**< Audio buffer size in samples (power of 2) */
    Uint16 padding;             /**< Necessary for some compile environments */
    Uint32 size;                /**< Audio buffer size in bytes (calculated) */
    SDL_AudioCallback callback;
    void *userdata;
} SDL_AudioSpec;

主要的引數含義如下:
freq:取樣率,例如44K、16K、48K等。
format:音訊資料格式,常見的有如下格式:

#define AUDIO_U16SYS    AUDIO_U16LSB
#define AUDIO_S16SYS    AUDIO_S16LSB
#define AUDIO_S32SYS    AUDIO_S32LSB
#define AUDIO_F32SYS    AUDIO_F32LSB

Channels:通道數 1 單聲道 2 雙聲道
silence:用於將緩衝區設定為靜默的值,由SDL_OpenAudio()計算
Samples:音訊緩衝區所需大小,必需是2的n次方
Size:音訊緩衝區的大小
Callback:音訊回撥函式
userdata:作為回撥函式的第一個引數傳遞。

其中,音訊回撥函式的原型為:

typedef void (SDLCALL * SDL_AudioCallback) (void *userdata, Uint8 * stream, int len);

當開始播放音訊的時候,如果音訊裝置需要音訊資料,就會呼叫此介面用來獲取音訊資料,
其中,stream是指向音訊資料緩衝區的指標,len則為資料的長度。
另外,在SDL2中必須首先使用SDL_memset()將stream中的資料設定為0。
在這個回撥函式中,既可以直接將音訊資料賦給stream,也可以呼叫其他函式進行一些音訊操作,例如呼叫SDL_MixAudio進行混音。

3.SDL_PauseAudio

函式原型: void SDLCALL SDL_PauseAudio(int pause_on);

這個函式用於暫停或者取消暫停音訊回撥處理,當我們開始播放音訊的時候,需要pause_on設定為0即可,當設定為1的時候,則不會呼叫回撥函式,即為靜音狀態。

三、測試程式碼

測試程式碼使用的是雷博的程式碼,在回撥函式中沒有采用混音,直接將音訊資料賦值給stream。

/**
 * 最簡單的SDL2播放音訊的例子(SDL2播放PCM)
 * Simplest Audio Play SDL2 (SDL2 play PCM) 
 *
 * 雷霄驊 Lei Xiaohua
 * [email protected]
 * 中國傳媒大學/數字電視技術
 * Communication University of China / Digital TV Technology
 * http://blog.csdn.net/leixiaohua1020
 *
 * 本程式使用SDL2播放PCM音訊取樣資料。SDL實際上是對底層繪圖
 * API(Direct3D,OpenGL)的封裝,使用起來明顯簡單于直接呼叫底層
 * API。
 *
 * 函式呼叫步驟如下: 
 *
 * [初始化]
 * SDL_Init(): 初始化SDL。
 * SDL_OpenAudio(): 根據引數(儲存於SDL_AudioSpec)開啟音訊裝置。
 * SDL_PauseAudio(): 播放音訊資料。
 *
 * [迴圈播放資料]
 * SDL_Delay(): 延時等待播放完成。
 *
 * This software plays PCM raw audio data using SDL2.
 * SDL is a wrapper of low-level API (DirectSound).
 * Use SDL is much easier than directly call these low-level API.
 *
 * The process is shown as follows:
 *
 * [Init]
 * SDL_Init(): Init SDL.
 * SDL_OpenAudio(): Opens the audio device with the desired 
 *                  parameters (In SDL_AudioSpec).
 * SDL_PauseAudio(): Play Audio.
 *
 * [Loop to play data]
 * SDL_Delay(): Wait for completetion of playback.
 */

#include <stdio.h>
//#include <tchar.h>

extern "C"
{
#include "sdl/SDL.h"
};

//Buffer:
//|-----------|-------------|
//chunk-------pos---len-----|
static  Uint8  *audio_chunk; 
static  Uint32  audio_len; 
static  Uint8  *audio_pos; 

/* Audio Callback
 * The audio function callback takes the following parameters: 
 * stream: A pointer to the audio buffer to be filled 
 * len: The length (in bytes) of the audio buffer 
 * 
*/ 
void  fill_audio(void *udata,Uint8 *stream,int len){ 
    //SDL 2.0
    SDL_memset(stream, 0, len);
    if(audio_len==0)        /*  Only  play  if  we  have  data  left  */ 
            return; 
    len=(len>audio_len?audio_len:len);  /*  Mix  as  much  data  as  possible  */ 

  //SDL_MixAudio(stream,audio_pos,len,SDL_MIX_MAXVOLUME);
    memcpy(stream, audio_pos, len);
    audio_pos += len; 
    audio_len -= len; 
} 

int main(int argc, char* argv[])
{
    //Init
    if(SDL_Init(SDL_INIT_AUDIO | SDL_INIT_TIMER)) {  
        printf( "Could not initialize SDL - %s\n", SDL_GetError()); 
        return -1;
    }

    //SDL_AudioSpec
    SDL_AudioSpec wanted_spec;
    wanted_spec.freq = 44100; 
    wanted_spec.format = AUDIO_S16SYS; 
    wanted_spec.channels = 2; 
    wanted_spec.silence = 0; 
    wanted_spec.samples = 1024; 
    wanted_spec.callback = fill_audio; 

    if (SDL_OpenAudio(&wanted_spec, NULL)<0){ 
        printf("can't open audio.\n"); 
        return -1; 
    } 

    FILE *fp=fopen("NocturneNo2inEflat_44.1k_s16le.pcm","rb+");
    if(fp==NULL){
        printf("cannot open this file\n");
        return -1;
    }

    int pcm_buffer_size=4096;
    char *pcm_buffer=(char *)malloc(pcm_buffer_size);
    int data_count=0;

    //Play
    SDL_PauseAudio(0);

    while(1){
        if (fread(pcm_buffer, 1, pcm_buffer_size, fp) != pcm_buffer_size){
            // Loop
            fseek(fp, 0, SEEK_SET);
            fread(pcm_buffer, 1, pcm_buffer_size, fp);
            data_count=0;
        }
        printf("Now Playing %10d Bytes data.\n",data_count);
        data_count+=pcm_buffer_size;
        //Set audio buffer (PCM data)
        audio_chunk = (Uint8 *) pcm_buffer; 
        //Audio buffer length
        audio_len =pcm_buffer_size;
        audio_pos = audio_chunk;

        while(audio_len>0)//Wait until finish
            SDL_Delay(1); 
    }
    free(pcm_buffer);
    SDL_Quit();
    return 0;
}