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;
}