SDL播放wav音频分析

主机环境:Win7

SDL版本:SDL2.0.3

开发环境:CodeBlocks13.12

关于SDL音频播放的分析可以查看SDL2源代码中test目录下的loopwave.c文件来帮助我们学习,源代码如下

/*
  Copyright (C) 1997-2014 Sam Lantinga <[email protected]>

  This software is provided 'as-is', without any express or implied
  warranty.  In no event will the authors be held liable for any damages
  arising from the use of this software.

  Permission is granted to anyone to use this software for any purpose,
  including commercial applications, and to alter it and redistribute it
  freely.
*/

/* Program to load a wave file and loop playing it using SDL sound */

/* loopwaves.c is much more robust in handling WAVE files --
    This is only for simple WAVEs
*/
#include "SDL_config.h"

#include <stdio.h>
#include <stdlib.h>

#if HAVE_SIGNAL_H
#include <signal.h>
#endif

#include "SDL.h"
#include "SDL_audio.h"

struct
{
    SDL_AudioSpec spec;
    Uint8 *sound;               /* Pointer to wave data */
    Uint32 soundlen;            /* Length of wave data */
    int soundpos;               /* Current play position */
} wave;


/* Call this instead of exit(), so we can clean up SDL: atexit() is evil. */
static void
quit(int rc)
{
    SDL_Quit();
    exit(rc);
}


void SDLCALL
fillerup(void *unused, Uint8 * stream, int len)
{
    Uint8 *waveptr;
    int waveleft;

    /* Set up the pointers */
    waveptr = wave.sound + wave.soundpos;
    waveleft = wave.soundlen - wave.soundpos;

    /* Go! */
    while (waveleft <= len) {
        SDL_memcpy(stream, waveptr, waveleft);
        stream += waveleft;
        len -= waveleft;
        waveptr = wave.sound;
        waveleft = wave.soundlen;
        wave.soundpos = 0;
    }
    SDL_memcpy(stream, waveptr, len);
    wave.soundpos += len;
}

static int done = 0;
void
poked(int sig)
{
    done = 1;
}

int
main(int argc, char *argv[])
{
    int i;
    char filename[4096];

	/* Enable standard application logging */
	SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO);

    /* Load the SDL library */
    if (SDL_Init(SDL_INIT_AUDIO) < 0) {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't initialize SDL: %s\n", SDL_GetError());
        return (1);
    }

    if (argc >= 1) {
        SDL_strlcpy(filename, argv[1], sizeof(filename));
    } else {
        SDL_strlcpy(filename, "sample.wav", sizeof(filename));
    }
    /* Load the wave file into memory */
    if (SDL_LoadWAV(filename, &wave.spec, &wave.sound, &wave.soundlen) == NULL) {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't load %s: %s\n", argv[1], SDL_GetError());
        quit(1);
    }

    wave.spec.callback = fillerup;
#if HAVE_SIGNAL_H
    /* Set the signals */
#ifdef SIGHUP
    signal(SIGHUP, poked);
#endif
    signal(SIGINT, poked);
#ifdef SIGQUIT
    signal(SIGQUIT, poked);
#endif
    signal(SIGTERM, poked);
#endif /* HAVE_SIGNAL_H */

    /* Show the list of available drivers */
    SDL_Log("Available audio drivers:");
    for (i = 0; i < SDL_GetNumAudioDrivers(); ++i) {
		SDL_Log("%i: %s", i, SDL_GetAudioDriver(i));
    }

    /* Initialize fillerup() variables */
    if (SDL_OpenAudio(&wave.spec, NULL) < 0) {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't open audio: %s\n", SDL_GetError());
        SDL_FreeWAV(wave.sound);
        quit(2);
    }

    SDL_Log("Using audio driver: %s\n", SDL_GetCurrentAudioDriver());

    /* Let the audio run */
    SDL_PauseAudio(0);
    while (!done && (SDL_GetAudioStatus() == SDL_AUDIO_PLAYING))
        SDL_Delay(1000);

    /* Clean up on signal */
    SDL_CloseAudio();
    SDL_FreeWAV(wave.sound);
    SDL_Quit();
    return (0);
}

/* vi: set ts=4 sw=4 expandtab: */

为了方便理解,可以把有关信号的代码去掉,修改后的代码如下

/*
  Copyright (C) 1997-2014 Sam Lantinga <[email protected]>

  This software is provided 'as-is', without any express or implied
  warranty.  In no event will the authors be held liable for any damages
  arising from the use of this software.

  Permission is granted to anyone to use this software for any purpose,
  including commercial applications, and to alter it and redistribute it
  freely.
*/

/* Program to load a wave file and loop playing it using SDL sound */

/* loopwaves.c is much more robust in handling WAVE files --
    This is only for simple WAVEs
*/
#include <SDL2/SDL.h>
#include <SDL2/SDL_audio.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

struct
{
    SDL_AudioSpec spec;
    Uint8 *sound;               /* Pointer to wave data */
    Uint32 soundlen;            /* Length of wave data */
    int soundpos;               /* Current play position */
} wave;
static int done = 0;
/* Call this instead of exit(), so we can clean up SDL: atexit() is evil. */
static void
quit(int rc)
{
    SDL_Quit();
    exit(rc);
}


void SDLCALL
fillerup(void *unused, Uint8 * stream, int len)
{
    Uint8 *waveptr;
    int waveleft;
	printf("callback len:%d\n",len);
    /* Set up the pointers */
    waveptr = wave.sound + wave.soundpos;
    waveleft = wave.soundlen - wave.soundpos;

    /* Go! */
    while (waveleft <= len) {
        SDL_memcpy(stream, waveptr, waveleft);
		SDL_PauseAudio(1);
		return;
    }
    SDL_memcpy(stream, waveptr, len);
    wave.soundpos += len;

}
int
main(int argc, char *argv[])
{
    int i;
    char filename[4096]="sample.wav";

	/* Enable standard application logging */
	SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO);

    /* Load the SDL library */
    if (SDL_Init(SDL_INIT_AUDIO) < 0) {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't initialize SDL: %s\n", SDL_GetError());
        return (1);
    }

    /* Load the wave file into memory */
    if (SDL_LoadWAV(filename, &wave.spec, &wave.sound, &wave.soundlen) == NULL) {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't load %s: %s\n", argv[1], SDL_GetError());
        quit(1);
    }

    wave.spec.callback = fillerup;//设置回调函数

    /* Show the list of available drivers */
    SDL_Log("Available audio drivers:");
    for (i = 0; i < SDL_GetNumAudioDrivers(); ++i) {
		SDL_Log("%i: %s", i, SDL_GetAudioDriver(i));
    }

    /* Initialize fillerup() variables */
    if (SDL_OpenAudio(&wave.spec, NULL) < 0) {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't open audio: %s\n", SDL_GetError());
        SDL_FreeWAV(wave.sound);
        quit(2);
    }

    SDL_Log("Using audio driver: %s\n", SDL_GetCurrentAudioDriver());
    printf("SDL_AudioSpec.samples:%d",wave.spec.samples);
    printf("SDL_AudioSpec sample size:%d",wave.spec.format);//AUDIO_S16LSB
    printf("SDL_AudioSpec.channels:%d",wave.spec.channels);
    printf("SDL_AudioSpec.size:%d",wave.spec.size);//跟回调函数的len相等应该是样品数*样品所占字节数
    /* Let the audio run */
    SDL_PauseAudio(0);
    while ((SDL_GetAudioStatus() == SDL_AUDIO_PLAYING))//获取音频状态
        SDL_Delay(1000);

    /* Clean up on signal */
    SDL_CloseAudio();//关掉音频进程以及音频设备
    SDL_FreeWAV(wave.sound);//释放数据由SDL_LoadWAV申请的
    SDL_Quit();
    printf("=========over==========\n");
    system("pause");
    return (0);
}

/* vi: set ts=4 sw=4 expandtab: */

在SDL2播放音频时是只支持WAV格式的音频文件的,因此播放其他格式的音频文件可以借助第三方的库如SDl2_mixer,我们只需要能播放WAV就够了,SDL2播放音频需借助一个结构体SDL_AudioSpec,其成员变量如下

int

freq

DSP frequency (samples per second); see Remarks for details

SDL_AudioFormat

format

audio data format; see Remarks for details

Uint8

channels

number of separate sound channels: see Remarks for details

Uint8

silence

audio buffer silence value (calculated)

Uint16

samples

audio buffer size in samples (power of 2); see Remarks for details

Uint32

size

audio buffer size in bytes (calculated)

SDL_AudioCallback

callback

the function to call when the audio device needs more data; see Remarks for details

void*

userdata

a pointer that is passed to callback (otherwise ignored by SDL)

有关SDL_AudioSpec结构体的详情可以去SDL官网查看: http://wiki.libsdl.org/SDL_AudioSpec

freq:音频采样率,如22025、44100等

format:播放的音频格式8位、16位、32位、浮点数等一般16位最常用

channels:音频通道数如 1 (mono), 2 (stereo), 4 (quad), and 6 (5.1)

samples:样品数,因为音频数据不可能一下子播放完,需分段播放,样品数定义了分段的大小

size:音频样品所占的字节数,该值可通过音频格式、通道数、样品数计算可得size=channels*format*samples,如格式为16位,通道数为2,样品数为1024,则size=2*2*1024=4096

callback:音频播放的回调函数,当样品播放完毕后,SDL2需要更多的音频数据,会调用该函数

userdata:用户数据,通过callback回调函数传递给用户,如果不需要该参数可忽略。

loopwav.c代码很简单,以修改过后的代码来查看,使用SDL_loadWAV函数来加载sample.wav文件,该函数会给SDL_AudioSpec结构体赋值,同时获取了音频数据指针以wave.sound及音频数据大小wave.soundlen,之后设置SDL_AudioSpec的回调函数为fillerup,接下来根据SDL_AudioSpec参数打开音频设备

/* Initialize fillerup() variables */
if (SDL_OpenAudio(&wave.spec, NULL) < 0) {
	SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't open audio: %s\n", SDL_GetError());
	SDL_FreeWAV(wave.sound);
	quit(2);
}

在此我们输出了一些参数,结果如下

SDL播放wav音频分析_第1张图片

由图可知样品数为4096,音频格式是32784=0x8010,根据SDl2中有关音频格式的说明如下所示:

/**
 *  \brief Audio format flags.
 *
 *  These are what the 16 bits in SDL_AudioFormat currently mean...
 *  (Unspecified bits are always zero).
 *
 *  \verbatim
    ++-----------------------sample is signed if set
    ||
    ||       ++-----------sample is bigendian if set
    ||       ||
    ||       ||          ++---sample is float if set
    ||       ||          ||
    ||       ||          || +---sample bit size---+
    ||       ||          || |                     |
    15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00
    \endverbatim
 *
 *  There are macros in SDL 2.0 and later to query these bits.
 */

可知,符号位为1,小端格式,不是浮点格式,样品大小为2,通道数为1,根据上面的公式可知size=2*1*4096=8192,与结果相符。调用SDL_PauseAudio(0)后就开始播放音频了SDL_PauseAudio(1)是暂停播放,当一个样品播放完毕之后就会自动调用回调函数fillerup来获取更多数据,回调函数有三个参数,形式如下

void SDL_AudioCallback(void*  userdata,
                       Uint8* stream,
                       int    len)

userdata是SDL_AudioSpec结构指定的,stream是要播放的音频数据指针,len是要播放的音频数据长度,回调函数主要功能就是赋值stream,之后SDL就会从音频数据指针stream处开始播放len长度的音频,

void SDLCALL
fillerup(void *unused, Uint8 * stream, int len)
{
    Uint8 *waveptr;
    int waveleft;
	printf("callback len:%d\n",len);
    /* Set up the pointers */
    waveptr = wave.sound + wave.soundpos;
    waveleft = wave.soundlen - wave.soundpos;

    /* Go! */
    while (waveleft <= len) {
        SDL_memcpy(stream, waveptr, waveleft);
		SDL_PauseAudio(1);
		return;
    }
    SDL_memcpy(stream, waveptr, len);
    wave.soundpos += len;

}

回调函数也是很简单,将sample.wav文件播放完毕之后就停止播放了,每次进入回调函数我们都输出了len的大小其值为8192,很容易联想到SDL_AudioSpec.size大小,是请求新的样本播放,进入回调函数后初始化音频指针的当前位置waveptr以及剩余音频数据的大小waveleft,如果剩余的音频数据大小小于len长度,就直接播放waveleft长度的音频就可以了,之后就可以关闭音频播放了,如果剩余音频数据大小超出了len长度,则只播放len长度的音频数据,并更新已播放的音频位置,整个流程简单易懂,很容易理解。

主程序中我们获取了音频播放的状态,如果不是播放状态就关闭音频设备,释放音频数据,终止程序运行,

typedef enum
{
    SDL_AUDIO_STOPPED = 0,
    SDL_AUDIO_PLAYING,
    SDL_AUDIO_PAUSED
} SDL_AudioStatus;
extern DECLSPEC SDL_AudioStatus SDLCALL SDL_GetAudioStatus(void);

音频状态就只有上述三种,至此,使用SDL来播放wav音频数据就已初步掌握了,之后就可以深入了解了。嘿嘿

有关SDL_mixer的使用可以查看该指导:http://lazyfoo.net/tutorials/SDL/21_sound_effects_and_music/index.php


你可能感兴趣的:(SDL播放wav音频分析)