171 lines
4.9 KiB
C
171 lines
4.9 KiB
C
#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 <sample_rate> <channels> < <file>
|
|
Examples:
|
|
./play 44100 2 5 < /dev/urandom
|
|
./play 22050 1 8 < /path/to/file.wav
|
|
|
|
Copyright (C) 2009 Alessandro Ghedini <al3xbio@gmail.com>
|
|
--------------------------------------------------------------
|
|
"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 <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <poll.h>
|
|
#include <sys/socket.h>
|
|
#include <arpa/inet.h>
|
|
#include <netinet/tcp.h>
|
|
|
|
#include <time.h>
|
|
|
|
#include <alsa/asoundlib.h>
|
|
#include <stdio.h>
|
|
|
|
#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
|