1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
|
/*
* ALSA audio driver, since OSS is pretty much dead at this stage.
*/
#include "audio/linux_alsa.h"
#if DEMOLIB_USE_ALSA
#include "exception.h"
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <sys/time.h>
#include <alsa/pcm.h>
#include <alsa/asoundlib.h>
#include <algorithm>
#define SAMPLE_RATE 44100
#define NUM_CHANNELS 2
#define BYTES_PER_FRAME (sizeof(short) * NUM_CHANNELS)
ALSAAudioDriver::ALSAAudioDriver(const char *device, AudioProvider *prv, float jump, MainLoop *lp)
{
this->prov = prv;
snd_pcm_hw_params_t *hw_params;
snd_pcm_hw_params_alloca(&hw_params);
die_on_error("snd_pcm_open()", snd_pcm_open(&this->pcm_handle, device, SND_PCM_STREAM_PLAYBACK, 0));
/* Set format. */
die_on_error("snd_pcm_hw_params_any()", snd_pcm_hw_params_any(this->pcm_handle, hw_params));
die_on_error("snd_pcm_hw_params_set_access()", snd_pcm_hw_params_set_access(this->pcm_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED));
die_on_error("snd_pcm_hw_params_set_format()", snd_pcm_hw_params_set_format(this->pcm_handle, hw_params, SND_PCM_FORMAT_S16));
die_on_error("snd_pcm_hw_params_set_rate()", snd_pcm_hw_params_set_rate(this->pcm_handle, hw_params, SAMPLE_RATE, 0));
die_on_error("snd_pcm_hw_params_set_channels", snd_pcm_hw_params_set_channels(this->pcm_handle, hw_params, NUM_CHANNELS));
/* Match the OSS driver: Fragment size of 4 kB = 1024 samples.
* Ask for 64 periods (a bit over a second).
*/
unsigned int num_periods = 64;
int dir = 0;
die_on_error("snd_pcm_hw_params_set_periods_near", snd_pcm_hw_params_set_periods_near(this->pcm_handle, hw_params, &num_periods, &dir));
this->period_size = 65536 / num_periods;
dir = 0;
die_on_error("snd_pcm_hw_params_set_period_size_near", snd_pcm_hw_params_set_period_size_near(this->pcm_handle, hw_params, &this->period_size, &dir));
die_on_error("snd_pcm_hw_params", snd_pcm_hw_params(this->pcm_handle, hw_params));
//snd_pcm_hw_params_free(hw_params);
this->bytes_written = (int)(jump * SAMPLE_RATE) * BYTES_PER_FRAME;
this->eof = false;
this->in_outbuf = 0;
}
ALSAAudioDriver::~ALSAAudioDriver()
{
snd_pcm_close(this->pcm_handle);
}
bool ALSAAudioDriver::run()
{
if (this->eof) {
struct timeval now;
gettimeofday(&now, NULL);
float secs =
(float)(now.tv_sec - eof_time.tv_sec) +
(float)(now.tv_usec - eof_time.tv_usec) * 0.000001f;
this->bytes_played += (int)(secs * SAMPLE_RATE * BYTES_PER_FRAME);
memcpy(&this->eof_time, &now, sizeof(struct timeval));
return true;
}
snd_pcm_sframes_t avail = 0, delay = 0;
int err = snd_pcm_avail_delay(this->pcm_handle, &avail, &delay);
if (err < 0) {
/* Don't die on this, as it is nearly always just an underrun. */
fprintf(stderr, "warning: snd_pcm_avail_delay() returned '%s'\n", snd_strerror(err));
} else {
this->bytes_played = this->bytes_written - delay * BYTES_PER_FRAME;
}
while (avail >= int(this->period_size)) {
/* whopee, we can output :-) */
if (in_outbuf < 65536) {
int ret = this->prov->fill_buf(this->outbuf + in_outbuf, 65536 - in_outbuf);
if (ret == 0) {
/* switch to software timer */
this->eof = true;
gettimeofday(&this->eof_time, NULL);
return true;
}
in_outbuf += ret;
}
/* write only whole periods */
int periods_to_write = std::min<int>(avail, in_outbuf / BYTES_PER_FRAME) / this->period_size;
if (periods_to_write > 0) {
int ret = snd_pcm_writei(this->pcm_handle, this->outbuf, periods_to_write * this->period_size);
if (ret < 0) {
/* Don't die on this, as it is nearly always just an underrun. */
fprintf(stderr, "warning: snd_pcm_writei() returned '%s'\n",
snd_strerror(ret));
} else {
in_outbuf -= ret * BYTES_PER_FRAME;
this->bytes_written += ret * BYTES_PER_FRAME;
memmove(outbuf, outbuf + ret * BYTES_PER_FRAME, in_outbuf);
}
}
avail = snd_pcm_avail_update(this->pcm_handle);
}
return false;
}
float ALSAAudioDriver::get_time()
{
return (float)(this->bytes_played) / SAMPLE_RATE / BYTES_PER_FRAME;
}
void ALSAAudioDriver::die_on_error(const char *func_name, int err)
{
if (err < 0) {
throw new FatalException("snd_pcm_open()", snd_strerror(err));
}
}
std::vector<ALSAAudioDriver::ALSASoundCard> ALSAAudioDriver::enumerate_sound_cards()
{
std::vector<ALSASoundCard> ret;
char **hints;
int err = snd_device_name_hint(-1, "pcm", (void ***)&hints);
if (err != 0) {
return ret;
}
for (char **n = hints; *n != NULL; ++n) {
ALSASoundCard sc;
char *name = snd_device_name_get_hint(*n, "NAME");
char *desc = snd_device_name_get_hint(*n, "DESC");
if (name != NULL && strcmp(name, "null") != 0 && strncmp(name, "dsnoop:", 7) != 0) {
char buf[256];
if (desc == NULL) {
snprintf(buf, sizeof(buf), "ALSA: %s", name);
} else {
/*
* Remove everything after the first newline;
* the descriptions tend to be a bit verbose.
*/
char *nl = strchr(desc, '\n');
if (nl != NULL) {
*nl = '\0';
}
snprintf(buf, sizeof(buf), "ALSA: %s (%s)", desc, name);
}
sc.device = name;
sc.description = buf;
ret.push_back(sc);
}
free(name);
free(desc);
}
snd_device_name_free_hint((void **)hints);
return ret;
}
#endif /* DEMOLIB_USE_ALSA */
|