#include "../compat.h" #if (defined(__linux__) || defined(__BSD__)) && !defined(TARGET_WEB) /* Simple sound playback using ALSA API and libasound. Dependencies: libasound, alsa Build-Dependencies: liasound-dev Compile: gcc -lasound -o play sound_playback.c Usage: ./play < Examples: ./play 44100 2 5 < /dev/urandom ./play 22050 1 8 < /path/to/file.wav Copyright (C) 2009 Alessandro Ghedini -------------------------------------------------------------- "THE BEER-WARE LICENSE" (Revision 42): Alessandro Ghedini wrote this file. As long as you retain this notice you can do whatever you want with this stuff. If we meet some day, and you think this stuff is worth it, you can buy me a beer in return. -------------------------------------------------------------- */ #include #include #include #include #include #include #include #include #include #include "audio_api.h" #define PCM_DEVICE "default" static snd_pcm_t *pcm_handle; static unsigned long int alsa_buffer_size; static unsigned long get_time(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (unsigned long)ts.tv_sec * 1000000 + ts.tv_nsec / 1000; } static bool audio_alsa_init(void) { int pcm; unsigned int tmp; unsigned int rate, channels; snd_pcm_hw_params_t *params; snd_pcm_uframes_t frames; rate = 32000; channels = 2; /* Open the PCM device in playback mode */ if ((pcm = snd_pcm_open(&pcm_handle, PCM_DEVICE, SND_PCM_STREAM_PLAYBACK, 0)) < 0) { printf("ERROR: Can't open \"%s\" PCM device. %s\n", PCM_DEVICE, snd_strerror(pcm)); return false; } /* Allocate parameters object and fill it with default values*/ snd_pcm_hw_params_alloca(¶ms); snd_pcm_hw_params_any(pcm_handle, params); /* Set parameters */ if ((pcm = snd_pcm_hw_params_set_access(pcm_handle, params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) printf("ERROR: Can't set interleaved mode. %s\n", snd_strerror(pcm)); if ((pcm = snd_pcm_hw_params_set_format(pcm_handle, params, SND_PCM_FORMAT_S16_LE)) < 0) printf("ERROR: Can't set format. %s\n", snd_strerror(pcm)); if ((pcm = snd_pcm_hw_params_set_channels(pcm_handle, params, channels)) < 0) printf("ERROR: Can't set channels number. %s\n", snd_strerror(pcm)); if ((pcm = snd_pcm_hw_params_set_rate_near(pcm_handle, params, &rate, 0)) < 0) printf("ERROR: Can't set rate. %s\n", snd_strerror(pcm)); alsa_buffer_size = 1600 + 528 + 544; // five audio buffers from the game if ((pcm = snd_pcm_hw_params_set_buffer_size_near(pcm_handle, params, &alsa_buffer_size)) < 0) printf("ERROR: Can't set buffer size. %s\n", snd_strerror(pcm)); /* Write parameters */ if ((pcm = snd_pcm_hw_params(pcm_handle, params)) < 0) printf("ERROR: Can't set harware parameters. %s\n", snd_strerror(pcm)); /* Resume information */ printf("PCM name: '%s'\n", snd_pcm_name(pcm_handle)); printf("PCM state: %s\n", snd_pcm_state_name(snd_pcm_state(pcm_handle))); snd_pcm_hw_params_get_channels(params, &tmp); printf("channels: %i ", tmp); if (tmp == 1) printf("(mono)\n"); else if (tmp == 2) printf("(stereo)\n"); snd_pcm_hw_params_get_rate(params, &tmp, 0); printf("rate: %d bps\n", tmp); snd_pcm_hw_params_get_buffer_size(params, &alsa_buffer_size); printf("buffer size: %lu\n", alsa_buffer_size); /* Allocate buffer to hold single period */ snd_pcm_hw_params_get_period_size(params, &frames, 0); printf("frames: %lu\n", frames); snd_pcm_hw_params_get_period_time(params, &tmp, NULL); printf("time: %d\n", tmp); return true; } static int audio_alsa_buffered(void) { if (!pcm_handle) { return 0; } snd_pcm_sframes_t ret = snd_pcm_avail(pcm_handle); if (ret < 0) { return 0; } ret = alsa_buffer_size - ret; return ret < 0 ? 0 : ret; } static int audio_alsa_get_desired_buffered(void) { return 1100; } static void audio_alsa_play(const uint8_t* buff, size_t len) { if (!pcm_handle) { audio_alsa_init(); } //unsigned long t1 = get_time(); int frames = len / 4; int pcm; if ((pcm = snd_pcm_writei(pcm_handle, buff, frames)) == -EPIPE) { printf("XRUN.\n"); snd_pcm_prepare(pcm_handle); // Add some silence to avoid another XRUN char buf[1100 * 4 + len]; memset(buf, 0, 1100 * 4); memcpy(buf + 1100 * 4, buff, len); if ((pcm = snd_pcm_writei(pcm_handle, buf, 1100 + frames)) < 0) { printf("Failed again %d\n", pcm); } } else if (pcm < 0) { printf("ERROR. Can't write to PCM device. %s\n", snd_strerror(pcm)); return; } //fprintf(stderr, "%u ", get_time() - t1); } struct AudioAPI audio_alsa = { audio_alsa_init, audio_alsa_buffered, audio_alsa_get_desired_buffered, audio_alsa_play }; #endif