1. 程式人生 > 其它 >SDL播放PCM

SDL播放PCM


//
// Created by simp on 2021/7/7.
//

#include <stdio.h>
#include <SDL.h>
//每次讀取2幀資料,以1024個取樣點一幀 2通道16bit取樣點為例
#define  PCM_BUFFER_SIZE (1024*2*2*2)
//音訊PCM資料快取
static Uint8 *s_audio_buf = NULL;
//目前讀取的位置
static Uint8 *s_audio_pos = NULL;
//快取結束位置
static Uint8 *s_audio_end = NULL;

//音訊裝置回撥函式
void fill_audio_pcm(void *userdata, Uint8 *stream, int len) {
    //SDL_AudioCallback
    //userdata:SDL_AudioSpec 結構中的使用者自定義資料,一般情況下可以不用。
    //stream:該指標指向需要填充的音訊緩衝區
    //len:音訊緩衝區的大小(以位元組為單位)1024*2*2
    SDL_memset(stream, 0, len);
    if (s_audio_pos >= s_audio_end)//資料讀取完畢
    {
        printf("read complete\n");
        return;
    }
    //資料夠了就讀取預設的長度,資料不夠就只讀部分(不夠的時候剩多少就讀多少)
    int remain_buffer_len = s_audio_end - s_audio_pos;
    len = (len < remain_buffer_len) ? len : remain_buffer_len;
    //拷貝資料到stream並調整音量
    SDL_MixAudio(stream, s_audio_pos, len, SDL_MIX_MAXVOLUME / 8);
    printf("len=%d\n", len);
    s_audio_pos += len;//移動快取指標
}
//提取PCM檔案
//ffmpeg -i input.mp4 -t20 -codec:a pcm_s16le -ar 44100 -ac 2  -f s16le 44100_16bit_2ch.pcm
//測試pcm
//ffplay -ar 44100 -ac2 -f s16le 44100_16bit_2ch.pcm
#undef main

int main(int argc, char *argv[]) {
    int ret = -1;
    FILE *audio_fd = NULL;
    SDL_AudioSpec spec;
//    C:\Users\simp\CLionProjects\SDLaction\cmake-build-debug\44100_16bit_2ch.pcm
    const char *path = "44100_16bit_2ch.pcm";
    size_t read_buffer_len = 0;
    //SDL_initialize
    if (SDL_Init(SDL_INIT_AUDIO)) {//確認裝置是否支援AUDIO
        fprintf(stderr, "Could not initalize SDL -%s\n", SDL_GetError());
        return ret;
    }
    //開啟PCM檔案
    audio_fd = fopen(path, "rb");
    if (!audio_fd) {
        fprintf(stderr, "Failed to open pcm file!\n");
        goto _FAIL;
    }
    s_audio_buf = (uint8_t *) malloc(PCM_BUFFER_SIZE);
    //音訊引數設定SDL_AudioSpec
    spec.freq = 44100;//取樣頻率
    spec.format = AUDIO_S16SYS;//取樣點格式 音訊資料格式
    spec.channels = 2;//聲道數:1單聲道 2立體聲
    spec.silence = 0;//設定靜音的值,因為聲音取樣是有符號的,所以0當然就是這個值
    spec.samples = 1024;//23.2ms -> 46.4ms每次讀取的取樣數量,多久產生一次回撥和samples  音訊緩衝區中的取樣個數,要求必須是2的n次方
    spec.callback = fill_audio_pcm;//回撥函式
    spec.userdata = NULL;

//    開啟音訊裝置
    if (SDL_OpenAudio(&spec, NULL)) {
        fprintf(stderr, "Failed to open audio device,%s\n", SDL_GetError());
        goto _FAIL;
    }
    //play audio
    SDL_PauseAudio(0);//當pause_on為0的時候即可開始播放音訊資料。設定為1的時候,將會播放靜音的值。
    int data_count = 0;
    while (1) {
        //從檔案讀取PCM資料
        read_buffer_len = fread(s_audio_buf, 1, PCM_BUFFER_SIZE, audio_fd);
        if (read_buffer_len == 0) {
            break;
        }
        data_count += read_buffer_len;//統計讀取資料總位元組數
        printf("now playing %10d bytes data.\n", data_count);
        s_audio_end = s_audio_buf + read_buffer_len;//更新buffer的結束位置
        s_audio_pos = s_audio_buf;//更新buffer的起始位置
//        the main thread wait for a moment
        while (s_audio_pos<s_audio_end) {
            SDL_Delay(10);//等待PCM資料消耗
        }
    }
    printf("play PCM finish\n");
    //關閉音訊裝置
    SDL_CloseAudio();
_FAIL:
    //release some resources
    if (s_audio_buf)free(s_audio_buf);
    if (audio_fd)fclose(audio_fd);
    SDL_Quit();
    return 0;


}