SDL 開發實戰(七): 使用 SDL 實現 PCM播放器


在上文,我們做了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;
}

 


免責聲明!

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



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