/*
* Safe read (for pipes)
*/
static ssize_t safe_read(int fd, void *buf, size_t count)
{
ssize_t result = 0, res;
while (count > 0) {
if ((res = read(fd, buf, count)) == 0)
break;
if (res < 0)
return result > 0 ? result : res;
count -= res;
result += res;
buf = (char *)buf + res;
}
return result;
}
/*
* helper for test_wavefile
*/
static size_t test_wavefile_read(int fd, u_char *buffer, size_t *size, size_t reqsize, int line)
{
if (*size >= reqsize)
return *size;
if ((size_t)safe_read(fd, buffer + *size, reqsize - *size) != reqsize - *size) {
error(_("read error (called from line %i)"), line);
prg_exit(EXIT_FAILURE);
}
return *size = reqsize;
}
#define check_wavefile_space(buffer, len, blimit) \
if (len > blimit) { \
blimit = len; \
if ((buffer = realloc(buffer, blimit)) == NULL) { \
error(_("not enough memory")); \
prg_exit(EXIT_FAILURE); \
} \
}
/*
* test, if it's a .WAV file, > 0 if ok (and set the speed, stereo etc.)
* == 0 if not
* Value returned is bytes to be discarded.
*/
static ssize_t test_wavefile(int fd, u_char *_buffer, size_t size)
{
WaveHeader *h = (WaveHeader *)_buffer;
u_char *buffer = NULL;
size_t blimit = 0;
WaveFmtBody *f;
WaveChunkHeader *c;
u_int type, len;
if (size < sizeof(WaveHeader))
return -1;
if (h->magic != WAV_RIFF || h->type != WAV_WAVE)
return -1;
if (size > sizeof(WaveHeader)) {
check_wavefile_space(buffer, size - sizeof(WaveHeader), blimit);
memcpy(buffer, _buffer + sizeof(WaveHeader), size - sizeof(WaveHeader));
}
size -= sizeof(WaveHeader);
while (1) {
check_wavefile_space(buffer, sizeof(WaveChunkHeader), blimit);
test_wavefile_read(fd, buffer, &size, sizeof(WaveChunkHeader), __LINE__);
c = (WaveChunkHeader*)buffer;
type = c->type;
len = LE_INT(c->length);
len += len % 2;
if (size > sizeof(WaveChunkHeader))
memmove(buffer, buffer + sizeof(WaveChunkHeader), size - sizeof(WaveChunkHeader));
size -= sizeof(WaveChunkHeader);
if (type == WAV_FMT)
break;
check_wavefile_space(buffer, len, blimit);
test_wavefile_read(fd, buffer, &size, len, __LINE__);
if (size > len)
memmove(buffer, buffer + len, size - len);
size -= len;
}
if (len < sizeof(WaveFmtBody)) {
error(_("unknown length of 'fmt ' chunk (read %u, should be %u at least)"),
len, (u_int)sizeof(WaveFmtBody));
prg_exit(EXIT_FAILURE);
}
check_wavefile_space(buffer, len, blimit);
test_wavefile_read(fd, buffer, &size, len, __LINE__);
f = (WaveFmtBody*) buffer;
if (LE_SHORT(f->format) == WAV_FMT_EXTENSIBLE) {
WaveFmtExtensibleBody *fe = (WaveFmtExtensibleBody*)buffer;
if (len < sizeof(WaveFmtExtensibleBody)) {
error(_("unknown length of extensible 'fmt ' chunk (read %u, should be %u at least)"),
len, (u_int)sizeof(WaveFmtExtensibleBody));
prg_exit(EXIT_FAILURE);
}
if (memcmp(fe->guid_tag, WAV_GUID_TAG, 14) != 0) {
error(_("wrong format tag in extensible 'fmt ' chunk"));
prg_exit(EXIT_FAILURE);
}
f->format = fe->guid_format;
}
if (LE_SHORT(f->format) != WAV_FMT_PCM &&
LE_SHORT(f->format) != WAV_FMT_IEEE_FLOAT) {
error(_("can't play WAVE-file format 0x%04x which is not PCM or FLOAT encoded"), LE_SHORT(f->format));
prg_exit(EXIT_FAILURE);
}
if (LE_SHORT(f->channels) < 1) {
error(_("can't play WAVE-files with %d tracks"), LE_SHORT(f->channels));
prg_exit(EXIT_FAILURE);
}
hwparams.channels = LE_SHORT(f->channels);
switch (LE_SHORT(f->bit_p_spl)) {
case 8:
if (hwparams.format != DEFAULT_FORMAT &&
hwparams.format != SND_PCM_FORMAT_U8)
fprintf(stderr, _("Warning: format is changed to U8\n"));
hwparams.format = SND_PCM_FORMAT_U8;
break;
case 16:
if (hwparams.format != DEFAULT_FORMAT &&
hwparams.format != SND_PCM_FORMAT_S16_LE)
fprintf(stderr, _("Warning: format is changed to S16_LE\n"));
hwparams.format = SND_PCM_FORMAT_S16_LE;
break;
case 24:
switch (LE_SHORT(f->byte_p_spl) / hwparams.channels) {
case 3:
if (hwparams.format != DEFAULT_FORMAT &&
hwparams.format != SND_PCM_FORMAT_S24_3LE)
fprintf(stderr, _("Warning: format is changed to S24_3LE\n"));
hwparams.format = SND_PCM_FORMAT_S24_3LE;
break;
case 4:
if (hwparams.format != DEFAULT_FORMAT &&
hwparams.format != SND_PCM_FORMAT_S24_LE)
fprintf(stderr, _("Warning: format is changed to S24_LE\n"));
hwparams.format = SND_PCM_FORMAT_S24_LE;
break;
default:
error(_(" can't play WAVE-files with sample %d bits in %d bytes wide (%d channels)"),
LE_SHORT(f->bit_p_spl), LE_SHORT(f->byte_p_spl), hwparams.channels);
prg_exit(EXIT_FAILURE);
}
break;
case 32:
if (LE_SHORT(f->format) == WAV_FMT_PCM)
hwparams.format = SND_PCM_FORMAT_S32_LE;
else if (LE_SHORT(f->format) == WAV_FMT_IEEE_FLOAT)
hwparams.format = SND_PCM_FORMAT_FLOAT_LE;
break;
default:
error(_(" can't play WAVE-files with sample %d bits wide"),
LE_SHORT(f->bit_p_spl));
prg_exit(EXIT_FAILURE);
}
hwparams.rate = LE_INT(f->sample_fq);
if (size > len)
memmove(buffer, buffer + len, size - len);
size -= len;
while (1) {
u_int type, len;
check_wavefile_space(buffer, sizeof(WaveChunkHeader), blimit);
test_wavefile_read(fd, buffer, &size, sizeof(WaveChunkHeader), __LINE__);
c = (WaveChunkHeader*)buffer;
type = c->type;
len = LE_INT(c->length);
if (size > sizeof(WaveChunkHeader))
memmove(buffer, buffer + sizeof(WaveChunkHeader), size - sizeof(WaveChunkHeader));
size -= sizeof(WaveChunkHeader);
if (type == WAV_DATA) {
if (len < pbrec_count && len < 0x7ffffffe)
pbrec_count = len;
if (size > 0)
memcpy(_buffer, buffer, size);
free(buffer);
return size;
}
len += len % 2;
check_wavefile_space(buffer, len, blimit);
test_wavefile_read(fd, buffer, &size, len, __LINE__);
if (size > len)
memmove(buffer, buffer + len, size - len);
size -= len;
}
/* shouldn't be reached */
return -1;
}
static void show_available_sample_formats(snd_pcm_hw_params_t* params)
{
snd_pcm_format_t format;
fprintf(stderr, "Available formats:\n");
for (format = 0; format < SND_PCM_FORMAT_LAST; format++) {
if (snd_pcm_hw_params_test_format(handle, params, format) == 0)
fprintf(stderr, "- %s\n", snd_pcm_format_name(format));
}
}
static void set_params(void)
{
snd_pcm_hw_params_t *params;
snd_pcm_sw_params_t *swparams;
snd_pcm_uframes_t buffer_size;
int err;
size_t n;
unsigned int rate;
snd_pcm_uframes_t start_threshold, stop_threshold;
snd_pcm_hw_params_alloca(¶ms);
snd_pcm_sw_params_alloca(&swparams);
err = snd_pcm_hw_params_any(handle, params);
if (err < 0) {
error(_("Broken configuration for this PCM: no configurations available"));
prg_exit(EXIT_FAILURE);
}
if (mmap_flag) {
snd_pcm_access_mask_t *mask = alloca(snd_pcm_access_mask_sizeof());
snd_pcm_access_mask_none(mask);
snd_pcm_access_mask_set(mask, SND_PCM_ACCESS_MMAP_INTERLEAVED);
snd_pcm_access_mask_set(mask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED);
snd_pcm_access_mask_set(mask, SND_PCM_ACCESS_MMAP_COMPLEX);
err = snd_pcm_hw_params_set_access_mask(handle, params, mask);
} else if (interleaved)
err = snd_pcm_hw_params_set_access(handle, params,
SND_PCM_ACCESS_RW_INTERLEAVED);
else
err = snd_pcm_hw_params_set_access(handle, params,
SND_PCM_ACCESS_RW_NONINTERLEAVED);
if (err < 0) {
error(_("Access type not available"));
prg_exit(EXIT_FAILURE);
}
err = snd_pcm_hw_params_set_format(handle, params, hwparams.format);
if (err < 0) {
error(_("Sample format non available"));
show_available_sample_formats(params);
prg_exit(EXIT_FAILURE);
}
err = snd_pcm_hw_params_set_channels(handle, params, hwparams.channels);
if (err < 0) {
error(_("Channels count non available"));
prg_exit(EXIT_FAILURE);
}
#if 0
err = snd_pcm_hw_params_set_periods_min(handle, params, 2);
assert(err >= 0);
#endif
rate = hwparams.rate;
err = snd_pcm_hw_params_set_rate_near(handle, params, &hwparams.rate, 0);
assert(err >= 0);
if ((float)rate * 1.05 < hwparams.rate || (float)rate * 0.95 > hwparams.rate) {
if (!quiet_mode) {
char plugex[64];
const char *pcmname = snd_pcm_name(handle);
fprintf(stderr, _("Warning: rate is not accurate (requested = %iHz, got = %iHz)\n"), rate, hwparams.rate);
if (! pcmname || strchr(snd_pcm_name(handle), ':'))
*plugex = 0;
else
snprintf(plugex, sizeof(plugex), "(-Dplug:%s)",
snd_pcm_name(handle));
fprintf(stderr, _(" please, try the plug plugin %s\n"),
plugex);
}
}
rate = hwparams.rate;
if (buffer_time == 0 && buffer_frames == 0) {
err = snd_pcm_hw_params_get_buffer_time_max(params,
&buffer_time, 0);
assert(err >= 0);
if (buffer_time > 500000)
buffer_time = 500000;
}
if (period_time == 0 && period_frames == 0) {
if (buffer_time > 0)
period_time = buffer_time / 4;
else
period_frames = buffer_frames / 4;
}
if (period_time > 0)
err = snd_pcm_hw_params_set_period_time_near(handle, params,
&period_time, 0);
else
err = snd_pcm_hw_params_set_period_size_near(handle, params,
&period_frames, 0);
assert(err >= 0);
if (buffer_time > 0) {
err = snd_pcm_hw_params_set_buffer_time_near(handle, params,
&buffer_time, 0);
} else {
err = snd_pcm_hw_params_set_buffer_size_near(handle, params,
&buffer_frames);
}
assert(err >= 0);
monotonic = snd_pcm_hw_params_is_monotonic(params);
err = snd_pcm_hw_params(handle, params);
if (err < 0) {
error(_("Unable to install hw params:"));
snd_pcm_hw_params_dump(params, log);
prg_exit(EXIT_FAILURE);
}
snd_pcm_hw_params_get_period_size(params, &chunk_size, 0);
snd_pcm_hw_params_get_buffer_size(params, &buffer_size);
if (chunk_size == buffer_size) {
error(_("Can't use period equal to buffer size (%lu == %lu)"),
chunk_size, buffer_size);
prg_exit(EXIT_FAILURE);
}
snd_pcm_sw_params_current(handle, swparams);
if (avail_min < 0)
n = chunk_size;
else
n = (double) rate * avail_min / 1000000;
err = snd_pcm_sw_params_set_avail_min(handle, swparams, n);
/* round up to closest transfer boundary */
n = buffer_size;
if (start_delay <= 0) {
start_threshold = n + (double) rate * start_delay / 1000000;
} else
start_threshold = (double) rate * start_delay / 1000000;
if (start_threshold < 1)
start_threshold = 1;
if (start_threshold > n)
start_threshold = n;
err = snd_pcm_sw_params_set_start_threshold(handle, swparams, start_threshold);
assert(err >= 0);
if (stop_delay <= 0)
stop_threshold = buffer_size + (double) rate * stop_delay / 1000000;
else
stop_threshold = (double) rate * stop_delay / 1000000;
err = snd_pcm_sw_params_set_stop_threshold(handle, swparams, stop_threshold);
assert(err >= 0);
if (snd_pcm_sw_params(handle, swparams) < 0) {
error(_("unable to install sw params:"));
snd_pcm_sw_params_dump(swparams, log);
prg_exit(EXIT_FAILURE);
}
if (verbose)
snd_pcm_dump(handle, log);
bits_per_sample = snd_pcm_format_physical_width(hwparams.format);
bits_per_frame = bits_per_sample * hwparams.channels;
chunk_bytes = chunk_size * bits_per_frame / 8;
audiobuf = realloc(audiobuf, chunk_bytes);
if (audiobuf == NULL) {
error(_("not enough memory"));
prg_exit(EXIT_FAILURE);
}
// fprintf(stderr, "real chunk_size = %i, frags = %i, total = %i\n", chunk_size, setup.buf.block.frags, setup.buf.block.frags * chunk_size);
/* stereo VU-meter isn't always available... */
if (vumeter == VUMETER_STEREO) {
if (hwparams.channels != 2 || !interleaved || verbose > 2)
vumeter = VUMETER_MONO;
}
/* show mmap buffer arragment */
if (mmap_flag && verbose) {
const snd_pcm_channel_area_t *areas;
snd_pcm_uframes_t offset, size = chunk_size;
int i;
err = snd_pcm_mmap_begin(handle, &areas, &offset, &size);
if (err < 0) {
error("snd_pcm_mmap_begin problem: %s", snd_strerror(err));
prg_exit(EXIT_FAILURE);
}
for (i = 0; i < hwparams.channels; i++)
fprintf(stderr, "mmap_area[%i] = %p,%u,%u (%u)\n", i, areas[i].addr, areas[i].first, areas[i].step, snd_pcm_format_physical_width(hwparams.format));
/* not required, but for sure */
snd_pcm_mmap_commit(handle, offset, 0);
}
buffer_frames = buffer_size; /* for position test */
}
#ifndef timersub
#define timersub(a, b, result) \
do { \
(result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
(result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \
if ((result)->tv_usec < 0) { \
--(result)->tv_sec; \
(result)->tv_usec += 1000000; \
} \
} while (0)
#endif
#ifndef timermsub
#define timermsub(a, b, result) \
do { \
(result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
(result)->tv_nsec = (a)->tv_nsec - (b)->tv_nsec; \
if ((result)->tv_nsec < 0) { \
--(result)->tv_sec; \
(result)->tv_nsec += 1000000000L; \
} \
} while (0)
#endif
/* I/O error handler */
static void xrun(void)
{
snd_pcm_status_t *status;
int res;
snd_pcm_status_alloca(&status);
if ((res = snd_pcm_status(handle, status))<0) {
error(_("status error: %s"), snd_strerror(res));
prg_exit(EXIT_FAILURE);
}
if (snd_pcm_status_get_state(status) == SND_PCM_STATE_XRUN) {
if (monotonic) {
#ifdef HAVE_CLOCK_GETTIME
struct timespec now, diff, tstamp;
clock_gettime(CLOCK_MONOTONIC, &now);
snd_pcm_status_get_trigger_htstamp(status, &tstamp);
timermsub(&now, &tstamp, &diff);
fprintf(stderr, _("%s!!! (at least %.3f ms long)\n"),
stream == SND_PCM_STREAM_PLAYBACK ? _("underrun") : _("overrun"),
diff.tv_sec * 1000 + diff.tv_nsec / 10000000.0);
#else
fprintf(stderr, "%s !!!\n", _("underrun"));
#endif
} else {
struct timeval now, diff, tstamp;
gettimeofday(&now, 0);
snd_pcm_status_get_trigger_tstamp(status, &tstamp);
timersub(&now, &tstamp, &diff);
fprintf(stderr, _("%s!!! (at least %.3f ms long)\n"),
stream == SND_PCM_STREAM_PLAYBACK ? _("underrun") : _("overrun"),
diff.tv_sec * 1000 + diff.tv_usec / 1000.0);
}
if (verbose) {
fprintf(stderr, _("Status:\n"));
snd_pcm_status_dump(status, log);
}
if ((res = snd_pcm_prepare(handle))<0) {
error(_("xrun: prepare error: %s"), snd_strerror(res));
prg_exit(EXIT_FAILURE);
}
return; /* ok, data should be accepted again */
} if (snd_pcm_status_get_state(status) == SND_PCM_STATE_DRAINING) {
if (verbose) {
fprintf(stderr, _("Status(DRAINING):\n"));
snd_pcm_status_dump(status, log);
}
if (stream == SND_PCM_STREAM_CAPTURE) {
fprintf(stderr, _("capture stream format change? attempting recover...\n"));
if ((res = snd_pcm_prepare(handle))<0) {
error(_("xrun(DRAINING): prepare error: %s"), snd_strerror(res));
prg_exit(EXIT_FAILURE);
}
return;
}
}
if (verbose) {
fprintf(stderr, _("Status(R/W):\n"));
snd_pcm_status_dump(status, log);
}
error(_("read/write error, state = %s"), snd_pcm_state_name(snd_pcm_status_get_state(status)));
prg_exit(EXIT_FAILURE);
}
/* I/O suspend handler */
static void suspend(void)
{
int res;
if (!quiet_mode)
fprintf(stderr, _("Suspended. Trying resume. ")); fflush(stderr);
while ((res = snd_pcm_resume(handle)) == -EAGAIN)
sleep(1); /* wait until suspend flag is released */
if (res < 0) {
if (!quiet_mode)
fprintf(stderr, _("Failed. Restarting stream. ")); fflush(stderr);
if ((res = snd_pcm_prepare(handle)) < 0) {
error(_("suspend: prepare error: %s"), snd_strerror(res));
prg_exit(EXIT_FAILURE);
}
}
if (!quiet_mode)
fprintf(stderr, _("Done.\n"));
}
static void print_vu_meter_mono(int perc, int maxperc)
{
const int bar_length = 50;
char line[80];
int val;
for (val = 0; val <= perc * bar_length / 100 && val < bar_length; val++)
line[val] = '#';
for (; val <= maxperc * bar_length / 100 && val < bar_length; val++)
line[val] = ' ';
line[val] = '+';
for (++val; val <= bar_length; val++)
line[val] = ' ';
if (maxperc > 99)
sprintf(line + val, "| MAX");
else
sprintf(line + val, "| %02i%%", maxperc);
fputs(line, stdout);
if (perc > 100)
printf(_(" !clip "));
}
|