在上文,我們做了YUV播放器,這樣我們就入門了SDL播放視頻。下面我們來做一個PCM播放,即使用SDL播放PCM數據。
下面說明一下使用SDL播放PCM音頻的基本流程,主要分為兩大部分:初始化SDL、循環播放數據。
1. 初始化SDL
1). 初始化SDL
執行的方法為SDL_Init(SDL_INIT_AUDIO)
2). 打開音頻設備
使用SDL_OpenAudio()打開音頻設備。該函數需要傳入一個SDL_AudioSpec的結構體。
這里SDL_OpenAudio() 函數的原型為:
int SDL_OpenAudio(SDL_AudioSpec * desired, SDL_AudioSpec * obtained);
它的參數是兩個SDL_AudioSpec結構體,它們的含義:
desired:期望的參數。
obtained:實際音頻設備的參數,一般情況下設置為NULL即可。
其中SDL_AudioSpec結構體如下:
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:音頻數據的采樣率。常用的有48000,44100等。
- format:音頻數據的格式。舉例幾種格式:
- AUDIO_U16SYS:Unsigned 16-bit samples
- AUDIO_S16SYS:Signed 16-bit samples
- AUDIO_S32SYS:32-bit integer samples
- AUDIO_F32SYS:32-bit floating point samples
- channels:聲道數。例如單聲道取值為1,立體聲取值為2。
- silence:設置靜音的值。
- samples:音頻緩沖區中的采樣個數,要求必須是2的n次方。
- padding:考慮到兼容性的一個參數。
- size:音頻緩沖區的大小,以字節為單位。
- callback:填充音頻緩沖區的回調函數。
- userdata:用戶自定義的數據。
在這里說明一下填充音頻緩沖區的回調函數的作用。當音頻設備需要更多數據的時候會調用該回調函數。
回調函數的格式要求如下:
void (SDLCALL * SDL_AudioCallback) (void *userdata, Uint8 * stream, int len);
回調函數的參數含義如下:
- userdata:SDL_AudioSpec結構中的用戶自定義數據,一般情況下可以不用。
- stream:該指針指向需要填充的音頻緩沖區。
- len:音頻緩沖區的大小(以字節為單位)。
在回調函數中可以使用SDL_MixAudio()完成混音等工作。注意:SDL2中必須首先使用SDL_memset()將stream中的數據設置為0。
2. 循環播放數據
1) 播放音頻數據。
使用SDL_PauseAudio()可以播放音頻數據。SDL_PauseAudio() 函數的原型如下:
void SDLCALL SDL_PauseAudio(int pause_on)
當pause_on設置為0的時候即可開始播放音頻數據。設置為1的時候,將會播放靜音的值。
2) 延時等待播放完成。
使用像SDL_Delay()這樣的延時函數即可。
實戰
// SDL.cpp : 此文件包含 "main" 函數。程序執行將在此處開始並結束。 // #include "pch.h" #include <iostream> extern "C" { #include "SDL.h" } /** * * 使用SDL2播放PCM音頻采樣數據。SDL實際上是對底層繪圖API(Direct3D,OpenGL)的封裝,使用起來明顯簡單於直接調用底層API。 * * 函數調用步驟如下: * * [初始化] * SDL_Init(): 初始化SDL。 * SDL_OpenAudio(): 根據參數(存儲於SDL_AudioSpec)打開音頻設備。 * SDL_PauseAudio(): 播放音頻數據。 * * [循環播放數據] * SDL_Delay(): 延時等待播放完成。 * * [播放音頻的基本原則] * 聲卡向你要數據而不是你主動推給聲卡 * 數據的多少是由音頻參數決定的 */ //Buffer: //|-----------|-------------| //chunk-------pos---len-----| static Uint8 *audio_chunk; static Uint32 audio_len; static Uint8 *audio_pos; void fill_audio(void *udata, Uint8 *stream, int len) { //SDL 2.0 SDL_memset(stream, 0, len); if (audio_len == 0) return; len = (len > audio_len ? audio_len : len); SDL_MixAudio(stream, audio_pos, len, SDL_MIX_MAXVOLUME); 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 = 48000; 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("test.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; }