我在 Linux 下使用 alsa lib。我想加载几个 wav 文件并根据一些测试条件播放它们。我正在使用以下代码,但需要进行改进:

// A simple C example to play a mono or stereo, 16-bit 44KHz
// WAVE file using ALSA. This goes directly to the first
// audio card (ie, its first set of audio out jacks). It
// uses the snd_pcm_writei() mode of outputting waveform data,
// blocking.
//
// Compile as so to create "alsawave":
// gcc -o alsawave alsawave.c -lasound
//
// Run it from a terminal, specifying the name of a WAVE file to play:
// ./alsawave MyWaveFile.wav

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

// Include the ALSA .H file that defines ALSA functions/data
#include <alsa/asoundlib.h>

#pragma pack (1)
/////////////////////// WAVE File Stuff /////////////////////
// An IFF file header looks like this
typedef struct _FILE_head
{
    unsigned char   ID[4];  // could be {'R', 'I', 'F', 'F'} or {'F', 'O', 'R', 'M'}
    unsigned int    Length; // Length of subsequent file (including remainder of header). This is in
                                    // Intel reverse byte order if RIFF, Motorola format if FORM.
    unsigned char   Type[4];    // {'W', 'A', 'V', 'E'} or {'A', 'I', 'F', 'F'}
} FILE_head;

// An IFF chunk header looks like this
typedef struct _CHUNK_head
{
    unsigned char ID[4];    // 4 ascii chars that is the chunk ID
    unsigned int    Length; // Length of subsequent data within this chunk. This is in Intel reverse byte
                            // order if RIFF, Motorola format if FORM. Note: this doesn't include any
                            // extra byte needed to pad the chunk out to an even size.
} CHUNK_head;

// WAVE fmt chunk
typedef struct _FORMAT {
    short               wFormatTag;
    unsigned short  wChannels;
    unsigned int    dwSamplesPerSec;
    unsigned int    dwAvgBytesPerSec;
    unsigned short  wBlockAlign;
    unsigned short  wBitsPerSample;
  // Note: there may be additional fields here, depending upon wFormatTag
} FORMAT;
#pragma pack()

// Size of the audio card hardware buffer. Here we want it
// set to 1024 16-bit sample points. This is relatively
// small in order to minimize latency. If you have trouble
// with underruns, you may need to increase this, and PERIODSIZE
// (trading off lower latency for more stability)
#define BUFFERSIZE  (2*1024)

// How many sample points the ALSA card plays before it calls
// our callback to fill some more of the audio card's hardware
// buffer. Here we want ALSA to call our callback after every
// 64 sample points have been played
#define PERIODSIZE  (2*64)

// Handle to ALSA (audio card's) playback port
snd_pcm_t               *PlaybackHandle;

// Handle to our callback thread
snd_async_handler_t *CallbackHandle;

// Points to loaded WAVE file's data
unsigned char           *WavePtr;

// Size (in frames) of loaded WAVE file's data
snd_pcm_uframes_t       WaveSize;

// Sample rate
unsigned short          WaveRate;

// Bit resolution
unsigned char           WaveBits;

// Number of channels in the wave file
unsigned char           WaveChannels;

// The name of the ALSA port we output to. In this case, we're
// directly writing to hardware card 0,0 (ie, first set of audio
// outputs on the first audio card)
static const char       SoundCardPortName[] = "default";

// For WAVE file loading
static const unsigned char Riff[4]  = { 'R', 'I', 'F', 'F' };
static const unsigned char Wave[4] = { 'W', 'A', 'V', 'E' };
static const unsigned char Fmt[4] = { 'f', 'm', 't', ' ' };
static const unsigned char Data[4] = { 'd', 'a', 't', 'a' };

/********************** compareID() *********************
 * Compares the passed ID str (ie, a ptr to 4 Ascii
 * bytes) with the ID at the passed ptr. Returns TRUE if
 * a match, FALSE if not.
 */

static unsigned char compareID(const unsigned char * id, unsigned char * ptr)
{
    register unsigned char i = 4;

    while (i--)
    {
        if ( *(id)++ != *(ptr)++ ) return(0);
    }
    return(1);
}

/********************** waveLoad() *********************
 * Loads a WAVE file.
 *
 * fn =         Filename to load.
 *
 * RETURNS: 0 if success, non-zero if not.
 *
 * NOTE: Sets the global "WavePtr" to an allocated buffer
 * containing the wave data, and "WaveSize" to the size
 * in sample points.
 */

static unsigned char waveLoad(const char *fn)
{
    const char              *message;
    FILE_head               head;
    register int            inHandle;

    if ((inHandle = open(fn, O_RDONLY)) == -1)
        message = "didn't open";

    // Read in IFF File header
    else
    {
        if (read(inHandle, &head, sizeof(FILE_head)) == sizeof(FILE_head))
        {
            // Is it a RIFF and WAVE?
            if (!compareID(&Riff[0], &head.ID[0]) || !compareID(&Wave[0], &head.Type[0]))
            {
                message = "is not a WAVE file";
                goto bad;
            }

            // Read in next chunk header
            while (read(inHandle, &head, sizeof(CHUNK_head)) == sizeof(CHUNK_head))
            {
                // ============================ Is it a fmt chunk? ===============================
                if (compareID(&Fmt[0], &head.ID[0]))
                {
                    FORMAT  format;

                    // Read in the remainder of chunk
                    if (read(inHandle, &format.wFormatTag, sizeof(FORMAT)) != sizeof(FORMAT)) break;

                    // Can't handle compressed WAVE files
                    if (format.wFormatTag != 1)
                    {
                        message = "compressed WAVE not supported";
                        goto bad;
                    }

                    WaveBits = (unsigned char)format.wBitsPerSample;
                    WaveRate = (unsigned short)format.dwSamplesPerSec;
                    WaveChannels = format.wChannels;
                }

                // ============================ Is it a data chunk? ===============================
                else if (compareID(&Data[0], &head.ID[0]))
                {
                    // Size of wave data is head.Length. Allocate a buffer and read in the wave data
                    if (!(WavePtr = (unsigned char *)malloc(head.Length)))
                    {
                        message = "won't fit in RAM";
                        goto bad;
                    }

                    if (read(inHandle, WavePtr, head.Length) != head.Length)
                    {
                        free(WavePtr);
                        break;
                    }

                    // Store size (in frames)
                    WaveSize = (head.Length * 8) / ((unsigned int)WaveBits * (unsigned int)WaveChannels);

                    close(inHandle);
                    return(0);
                }

                // ============================ Skip this chunk ===============================
                else
                {
                    if (head.Length & 1) ++head.Length;  // If odd, round it up to account for pad byte
                    lseek(inHandle, head.Length, SEEK_CUR);
                }
            }
        }

        message = "is a bad WAVE file";
bad:    close(inHandle);
    }

    printf("%s %s\n", fn, message);
    return(1);
}

/********************** play_audio() **********************
 * Plays the loaded waveform.
 *
 * NOTE: ALSA sound card's handle must be in the global
 * "PlaybackHandle". A pointer to the wave data must be in
 * the global "WavePtr", and its size of "WaveSize".
 */

static void play_audio(void)
{
    register snd_pcm_uframes_t      count, frames;

    // Output the wave data
    count = 0;
    do
    {
        frames = snd_pcm_writei(PlaybackHandle, WavePtr + count, WaveSize - count);

        // If an error, try to recover from it
        if (frames < 0)
            frames = snd_pcm_recover(PlaybackHandle, frames, 0);
        if (frames < 0)
        {
            printf("Error playing wave: %s\n", snd_strerror(frames));
            break;
        }

        // Update our pointer
        count += frames;

    } while (count < WaveSize);

    // Wait for playback to completely finish
    //if (count == WaveSize)
        //snd_pcm_drain(PlaybackHandle);
}

/*********************** free_wave_data() *********************
 * Frees any wave data we loaded.
 *
 * NOTE: A pointer to the wave data be in the global
 * "WavePtr".
 */

static void free_wave_data(void)
{
    if (WavePtr) free(WavePtr);
    WavePtr = 0;
}

int main(int argc, char **argv)
{
    // No wave data loaded yet
    WavePtr = 0;

    if (argc < 2)
        printf("You must supply the name of a 16-bit mono WAVE file to play\n");

    // Load the wave file
    else if (!waveLoad(argv[1]))
    {
        register int        err;

        // Open audio card we wish to use for playback
        if ((err = snd_pcm_open(&PlaybackHandle, &SoundCardPortName[0], SND_PCM_STREAM_PLAYBACK, 0)) < 0)
            printf("Can't open audio %s: %s\n", &SoundCardPortName[0], snd_strerror(err));
        else
        {
            switch (WaveBits)
            {
                case 8:
                    err = SND_PCM_FORMAT_U8;
                    break;

                case 16:
                    err = SND_PCM_FORMAT_S16;
                    break;

                case 24:
                    err = SND_PCM_FORMAT_S24;
                    break;

                case 32:
                    err = SND_PCM_FORMAT_S32;
                    break;
            }

            // Set the audio card's hardware parameters (sample rate, bit resolution, etc)
            if ((err = snd_pcm_set_params(PlaybackHandle, err, SND_PCM_ACCESS_RW_INTERLEAVED, WaveChannels, WaveRate, 1, 100000)) < 0)
                printf("Can't set sound parameters: %s\n", snd_strerror(err));

            // Play the waveform
            else
                play_audio();
                int i;
                usleep(10000);

                play_audio();
                play_audio();

            // Close sound card
            snd_pcm_close(PlaybackHandle);
        }
    }

    // Free the WAVE data
    free_wave_data();

    return(0);
}

由于我想同时播放多个声音,我开始尝试不止一次播放相同的声音,所以我评论了以下几行:

if (count == WaveSize)
    snd_pcm_drain(PlaybackHandle);

在 play_audio 函数中。不幸的是,这并没有真正起作用,因为如果我尝试不止一次播放相同的声音,它会起作用,但是,如果我在播放声音之前插入一个很长的延迟,则不播放任何内容。

例如,在主函数 play_audio()中;

usleep(10000);

            play_audio();
            play_audio();

工作,我可以听到三次相同的声音。但是,如果我使用 usleep(100000),我只会听到一次声音。

另一个问题是它必须等待第一个声音在开始播放下一个声音之前结束。所以,我希望能够发送多个声音,并同时播放几个声音。我想手动混合它们(这并不困难)。 main 函数将包含一个 while 循环,其中包含一些测试以确定需要播放哪个 sound(s。

我想过将 play_audio 放在一个线程中并在一个无限循环中运行它,然后让主线程修改(mix,etc.) WavePtr。我只是不知道这是否是正确的方法,或者是否有效率更高方法。有什么建议吗?谢谢。