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

 

posted @ 2019-03-04 20:37  灰色飘零  阅读(3800)  评论(1编辑  收藏  举报