From: Hynek Hanke <hanke@brailcom.org>
To: Clemens Ladisch <clemens@ladisch.de>
Cc: alsa-devel@alsa-project.org
Subject: Re: Crack sounds in playback
Date: Fri, 5 Aug 2005 15:03:04 +0200 [thread overview]
Message-ID: <20050805130304.GD11890@brailcom.cz> (raw)
In-Reply-To: <Pine.HPX.4.33n.0508051447540.3253-100000@studcom.urz.uni-halle.de>
> So you're using the default sw_params for playback? In theory, this
> should work. How does your code look like?
See bellow.
With regards,
Hynek Hanke
/*
* alsa.c -- The Advanced Linux Sound System backend for Speech Dispatcher
*
* Copyright (C) 2005 Brailcom, o.p.s.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this package; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
* $Id: alsa.c,v 1.16 2005/08/04 14:12:37 hanke Exp $
*/
/* NOTE: This module uses the non-blocking write() / poll() approach to
alsa-lib functions.*/
#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
/* Put a message into the logfile (stderr) */
#define MSG(arg...) \
{ \
time_t t; \
struct timeval tv; \
char *tstr; \
t = time(NULL); \
tstr = strdup(ctime(&t)); \
tstr[strlen(tstr)-1] = 0; \
gettimeofday(&tv,NULL); \
fprintf(stderr," %s [%d]",tstr, (int) tv.tv_usec); \
fprintf(stderr," ALSA: "); \
fprintf(stderr,arg); \
fprintf(stderr,"\n"); \
fflush(stderr); \
}
#define ERR(arg...) \
{ \
time_t t; \
struct timeval tv; \
char *tstr; \
t = time(NULL); \
tstr = strdup(ctime(&t)); \
tstr[strlen(tstr)-1] = 0; \
gettimeofday(&tv,NULL); \
fprintf(stderr," %s [%d]",tstr, (int) tv.tv_usec); \
fprintf(stderr," ALSA ERROR: "); \
fprintf(stderr,arg); \
fprintf(stderr,"\n"); \
fflush(stderr); \
}
/* I/O error handler */
int
xrun(AudioID *id)
{
snd_pcm_status_t *status;
int res;
if (id == NULL) return -1;
MSG("WARNING: Entering XRUN handler");
snd_pcm_status_alloca(&status);
if ((res = snd_pcm_status(id->alsa_pcm, status))<0) {
ERR("status error: %s", snd_strerror(res));
return -1;
}
if (snd_pcm_status_get_state(status) == SND_PCM_STATE_XRUN) {
struct timeval now, diff, tstamp;
gettimeofday(&now, 0);
snd_pcm_status_get_trigger_tstamp(status, &tstamp);
timersub(&now, &tstamp, &diff);
MSG("underrun!!! (at least %.3f ms long)",
diff.tv_sec * 1000 + diff.tv_usec / 1000.0);
if ((res = snd_pcm_prepare(id->alsa_pcm)) < 0) {
ERR("xrun: prepare error: %s", snd_strerror(res));
return -1;
}
return 0; /* ok, data should be accepted again */
}
ERR("read/write error, state = %s",
snd_pcm_state_name(snd_pcm_status_get_state(status)));
return -1;
}
/* I/O suspend handler */
int
suspend(AudioID *id)
{
int res;
MSG("WARNING: Entering SUSPEND handler.");
if (id == NULL) return -1;
while ((res = snd_pcm_resume(id->alsa_pcm)) == -EAGAIN)
sleep(1); /* wait until suspend flag is released */
if (res < 0) {
if ((res = snd_pcm_prepare(id->alsa_pcm)) < 0) {
ERR("suspend: prepare error: %s", snd_strerror(res));
return -1;
}
}
return 0;
}
/* Open the device so that it's ready for playing on the default
device. Internal function used by the public alsa_open. */
int
_alsa_open(AudioID *id)
{
int err;
struct pollfd alsa_stop_pipe_pfd;
MSG("Opening ALSA device");
fflush(stderr);
/* Open the device */
if ((err = snd_pcm_open (&id->alsa_pcm, id->alsa_device_name,
SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) {
ERR("Cannot open audio device %s (%s)", id->alsa_device_name, snd_strerror (err));
return -1;
}
/* Allocate space for hw_params (description of the sound parameters) */
MSG("Allocating new hw_params structure");
if ((err = snd_pcm_hw_params_malloc (&id->alsa_hw_params)) < 0) {
ERR("Cannot allocate hardware parameter structure (%s)",
snd_strerror(err));
return -1;
}
/* Initialize hw_params on our pcm */
if ((err = snd_pcm_hw_params_any (id->alsa_pcm, id->alsa_hw_params)) < 0) {
ERR("Cannot initialize hardware parameter structure (%s)",
snd_strerror (err));
return -1;
}
/* Create the pipe for communication about stop requests */
if (pipe (id->alsa_stop_pipe))
{
ERR("Stop pipe creation failed (%s)", strerror(errno));
return -1;
}
/* Find how many descriptors we will get for poll() */
id->alsa_fd_count = snd_pcm_poll_descriptors_count(id->alsa_pcm);
if (id->alsa_fd_count <= 0){
ERR("Invalid poll descriptors count returned from ALSA.");
return -1;
}
/* Create and fill in struct pollfd *alsa_poll_fds with ALSA descriptors */
id->alsa_poll_fds = malloc ((id->alsa_fd_count + 1) * sizeof(struct pollfd));
assert(id->alsa_poll_fds);
if ((err = snd_pcm_poll_descriptors(id->alsa_pcm, id->alsa_poll_fds, id->alsa_fd_count)) < 0) {
ERR("Unable to obtain poll descriptors for playback: %s\n", snd_strerror(err));
return -1;
}
/* Create a new pollfd structure for requests by alsa_stop()*/
alsa_stop_pipe_pfd.fd = id->alsa_stop_pipe[0];
alsa_stop_pipe_pfd.events = POLLIN;
alsa_stop_pipe_pfd.revents = 0;
/* Join this our own pollfd to the ALSAs ones */
id->alsa_poll_fds[id->alsa_fd_count] = alsa_stop_pipe_pfd;
id->alsa_fd_count++;
id->alsa_opened = 1;
MSG("Opening ALSA device ... success");
return 0;
}
/*
Close the device. Internal function used by public alsa_close.
*/
int
_alsa_close(AudioID *id)
{
int err;
MSG("Closing ALSA device");
id->alsa_opened = 0;
if ((err = snd_pcm_close (id->alsa_pcm)) < 0) {
MSG("Cannot close ALSA device (%s)", snd_strerror (err));
return -1;
}
close(id->alsa_stop_pipe[0]);
close(id->alsa_stop_pipe[1]);
snd_pcm_hw_params_free (id->alsa_hw_params);
snd_pcm_sw_params_free (id->alsa_sw_params);
free(id->alsa_poll_fds);
MSG("Closing ALSA device ... success");
return 0;
}
/* Open ALSA for playback.
These parameters are passed in pars:
(char*) pars[0] ... null-terminated string containing the name
of the device to be used for sound output
on ALSA
(void*) pars[1] ... =NULL
*/
int
alsa_open(AudioID *id, void **pars)
{
int ret;
id->alsa_opened = 0;
if (id == NULL){
ERR("Can't open ALSA sound output, invalid AudioID structure.");
return 0;
}
if (pars[0] == NULL){
ERR("Can't open ALSA sound output, missing parameters in argument.");
return -1;
}
MSG("Opening ALSA sound output");
id->alsa_device_name = strdup(pars[0]);
ret = _alsa_open(id);
if (ret){
ERR("Cannot initialize Alsa device '%s': Can't open.", pars[0]);
return -1;
}
_alsa_close(id);
if (ret){
ERR("Cannot initialize Alsa device '%s': Can't close.", pars[0]);
return -1;
}
MSG("Device '%s' initialized succesfully.", pars[0]);
return 0;
}
/* Close ALSA */
int
alsa_close(AudioID *id)
{
int err;
/* Close device */
if ((err = _alsa_close(id)) < 0) {
ERR("Cannot close audio device (%s)");
return -1;
}
MSG("ALSA closed.");
id = NULL;
return 0;
}
/* Wait until ALSA is readdy for more samples or alsa_stop() was called.
Returns 0 if ALSA is ready for more input, +1 if a request to stop
the sound output was received and a negative value on error. */
int wait_for_poll(AudioID *id, struct pollfd *alsa_poll_fds,
unsigned int count, int draining)
{
unsigned short revents;
snd_pcm_state_t state;
int ret;
// MSG("Waiting for poll");
/* Wait for certain events */
while (1) {
ret = poll(id->alsa_poll_fds, count, -1);
// MSG("wait_for_poll: activity on %d descriptors", ret);
/* Check for stop request from alsa_stop on the last file
descriptors*/
if (revents = id->alsa_poll_fds[count-1].revents){
if (revents & POLLIN){
MSG("wait_for_poll: stop requested");
return 1;
}
}
/* Check the first count-1 descriptors for ALSA events */
snd_pcm_poll_descriptors_revents(id->alsa_pcm, id->alsa_poll_fds, count-1, &revents);
/* Ensure we are in the right state */
state = snd_pcm_state(id->alsa_pcm);
// MSG("State after poll returned is %s", snd_pcm_state_name(state));
if (SND_PCM_STATE_XRUN == state){
if (!draining){
MSG("WARNING: Buffer underrun detected!");
if (xrun(id) != 0) return -1;
return 0;
}else{
MSG("Poll: Playback terminated");
return 0;
}
}
if (SND_PCM_STATE_SUSPENDED == state){
MSG("WARNING: Suspend detected!");
if (suspend(id) != 0) return -1;
return 0;
}
/* Check for errors */
if (revents & POLLERR) {
MSG("wait_for_poll: poll revents says POLLERR");
return -EIO;
}
/* Is ALSA ready for more input? */
if ((revents & POLLOUT)){
// MSG("Poll: Ready for more input");
return 0;
}
}
}
#define ERROR_EXIT()\
free(track_volume.samples); \
ERR("alsa_play() abnormal exit"); \
_alsa_close(id); \
return -1;
/* Play the track _track_ (see spd_audio.h) using the id->alsa_pcm device and
id-hw_params parameters. This is a blocking function, however, it's possible
to interrupt playing from a different thread with alsa_stop(). alsa_play
returns after and immediatelly after the whole sound was played on the
speakers.
The idea is that we get the ALSA file descriptors and we will poll() to see
when alsa is ready for more input while sleeping in the meantime. We will
additionally poll() for one more descriptor used by alsa_stop() to notify the
thread with alsa_play() that the stop of the playback is requested. The
variable can_be_stopped is used for very simple synchronization between the
two threads. */
int
alsa_play(AudioID *id, AudioTrack track)
{
snd_pcm_format_t format;
int channels;
int bytes_per_sample;
int num_bytes;
int frames;
int bytes;
signed short* output_samples;
AudioTrack track_volume;
float real_volume;
int i;
int err;
int ret;
snd_pcm_uframes_t framecount;
snd_pcm_uframes_t period_size;
size_t samples_per_period;
size_t silent_samples;
size_t volume_size;
char buf[100];
snd_pcm_state_t state;
MSG("Start of playback on ALSA");
if (id == NULL){
ERR("Invalide device passed to alsa_play()");
return -1;
}
/* This is needed, otherwise we can't set different
parameters than in tha last call. */
err = _alsa_open(id);
if (err){
ERR("Cannot initialize Alsa device '%s': Can't open.", id->alsa_device_name);
return -1;
}
/* Is it not an empty track? */
/* Passing an empty track is not an error */
if (track.samples == NULL) return 0;
/* Report current state state */
state = snd_pcm_state(id->alsa_pcm);
MSG("PCM state before setting audio parameters: %s",
snd_pcm_state_name(state));
/* Choose the correct format */
if (track.bits == 16){
format = SND_PCM_FORMAT_S16_LE;
bytes_per_sample = 2;
}else if (track.bits == 8){
bytes_per_sample = 1;
format = SND_PCM_FORMAT_S8;
}else{
ERR("Unsupported sound data format, track.bits = %d", track.bits);
return -1;
}
/* Set access mode, bitrate, sample rate and channels */
MSG("Setting access type to INTERLEAVED");
if ((err = snd_pcm_hw_params_set_access (id->alsa_pcm,
id->alsa_hw_params,
SND_PCM_ACCESS_RW_INTERLEAVED)
)< 0) {
ERR("Cannot set access type (%s)",
snd_strerror (err));
return -1;
}
MSG("Setting sample format to %s", snd_pcm_format_name(format));
if ((err = snd_pcm_hw_params_set_format (id->alsa_pcm, id->alsa_hw_params, format)) < 0) {
ERR("Cannot set sample format (%s)",
snd_strerror (err));
return -1;
}
MSG("Setting sample rate to %i", track.sample_rate);
if ((err = snd_pcm_hw_params_set_rate_near (id->alsa_pcm, id->alsa_hw_params,
&(track.sample_rate), 0)) < 0) {
ERR("Cannot set sample rate (%s)",
snd_strerror (err));
return -1;
}
MSG("Setting channel count to %i", track.num_channels);
if ((err = snd_pcm_hw_params_set_channels (id->alsa_pcm, id->alsa_hw_params,
track.num_channels)) < 0) {
MSG("cannot set channel count (%s)",
snd_strerror (err));
return -1;
}
MSG("Setting hardware parameters on the ALSA device");
if ((err = snd_pcm_hw_params (id->alsa_pcm, id->alsa_hw_params)) < 0) {
MSG("cannot set parameters (%s) state=%s",
snd_strerror (err), snd_pcm_state_name(snd_pcm_state(id->alsa_pcm)));
return -1;
}
/* Allocate space for sw_params (description of the sound parameters) */
MSG("Allocating new sw_params structure");
if ((err = snd_pcm_sw_params_malloc (&id->alsa_sw_params)) < 0) {
ERR("Cannot allocate hardware parameter structure (%s)",
snd_strerror(err));
return -1;
}
/* Get the current swparams */
if ((err = snd_pcm_sw_params_current(id->alsa_pcm, id->alsa_sw_params)) < 0){
ERR("Unable to determine current swparams for playback: %s\n",
snd_strerror(err));
return -1;
}
// MSG("Checking buffer size");
if ((err = snd_pcm_hw_params_get_buffer_size(id->alsa_hw_params, &id->alsa_buffer_size)) < 0){
ERR("Unable to get buffer size for playback: %s\n", snd_strerror(err));
return -1;
}
MSG("Buffer size on ALSA device is %d bytes", id->alsa_buffer_size);
/* This is probably better left for the device driver to decide */
/* allow the transfer when at least period_size samples can be processed */
/* err = snd_pcm_sw_params_set_avail_min(id->alsa_pcm, id->alsa_sw_params, id->alsa_buffer_size/4);
if (err < 0) {
ERR("Unable to set avail min for playback: %s\n", snd_strerror(err));
return err;
}*/
MSG("Preparing device for playback");
if ((err = snd_pcm_prepare (id->alsa_pcm)) < 0) {
ERR("Cannot prepare audio interface for playback (%s)",
snd_strerror (err));
return -1;
}
/* Get period size. */
snd_pcm_hw_params_get_period_size(id->alsa_hw_params, &period_size, 0);
/* Calculate size of silence at end of buffer. */
samples_per_period = period_size * track.num_channels;
// MSG("samples per period = %i", samples_per_period);
// MSG("num_samples = %i", track.num_samples);
silent_samples = samples_per_period - (track.num_samples % samples_per_period);
// MSG("silent samples = %i", silent_samples);
/* Calculate space needed to round up to nearest period size. */
volume_size = bytes_per_sample*(track.num_samples + silent_samples);
MSG("volume size = %i", volume_size);
/* Create a copy of track with adjusted volume. */
MSG("Making copy of track and adjusting volume");
track_volume = track;
track_volume.samples = (short*) malloc(volume_size);
real_volume = ((float) id->volume + 100)/(float)200;
for (i=0; i<=track.num_samples-1; i++)
track_volume.samples[i] = track.samples[i] * real_volume;
if (silent_samples > 0) {
u_int16_t silent16;
u_int8_t silent8;
/* Fill remaining space with silence */
MSG("Filling with silence up to the period size, silent_samples=%d", silent_samples);
/* TODO: This hangs. Why?
snd_pcm_format_set_silence(format,
track_volume.samples + (track.num_samples * bytes_per_sample), silent_samples);
*/
switch (bytes_per_sample) {
case 2:
silent16 = snd_pcm_format_silence_16(format);
for (i = 0; i < silent_samples; i++)
track_volume.samples[track.num_samples + i] = silent16;
break;
case 1:
silent8 = snd_pcm_format_silence(format);
for (i = 0; i < silent_samples; i++)
track_volume.samples[track.num_samples + i] = silent8;
break;
}
}
/* Loop until all samples are played on the device. */
output_samples = track_volume.samples;
num_bytes = (track.num_samples + silent_samples)*bytes_per_sample;
// MSG("Still %d bytes left to be played", num_bytes);
while(num_bytes > 0) {
/* Write as much samples as possible */
framecount = num_bytes/bytes_per_sample/track.num_channels;
if (framecount < period_size) framecount = period_size;
/* Report current state state */
state = snd_pcm_state(id->alsa_pcm);
// MSG("PCM state before writei: %s",
// snd_pcm_state_name(state));
/* MSG("snd_pcm_writei() called") */
ret = snd_pcm_writei (id->alsa_pcm, output_samples, framecount);
// MSG("Sent %d of %d remaining bytes", ret*bytes_per_sample, num_bytes);
if (ret == -EAGAIN) {
MSG("Warning: Forced wait!");
snd_pcm_wait(id->alsa_pcm, 100);
} else if (ret == -EPIPE) {
if (xrun(id) != 0) ERROR_EXIT();
} else if (ret == -ESTRPIPE) {
if (suspend(id) != 0) ERROR_EXIT();
} else if (ret == -EBUSY){
MSG("WARNING: sleeping while PCM BUSY");
usleep(100);
continue;
} else if (ret < 0) {
ERR("Write to audio interface failed (%s)",
snd_strerror (ret));
ERROR_EXIT();
}
if (ret > 0) {
/* Update counter of bytes left and move the data pointer */
num_bytes -= ret*bytes_per_sample*track.num_channels;
output_samples += ret*bytes_per_sample*track.num_channels/2;
}
/* Report current state */
state = snd_pcm_state(id->alsa_pcm);
// MSG("PCM state before polling: %s",
// snd_pcm_state_name(state));
err = wait_for_poll(id, id->alsa_poll_fds, id->alsa_fd_count, 0);
if (err < 0) {
ERR("Wait for poll() failed\n");
ERROR_EXIT();
}
else if (err == 1){
MSG("Playback stopped");
/* Drop the playback on the sound device (probably
still in progress up till now) */
err = snd_pcm_drop(id->alsa_pcm);
if (err < 0) {
ERR("snd_pcm_drop() failed: ", snd_strerror (err));
return -1;
}
goto terminate;
}
if (num_bytes <= 0) break;
// MSG("ALSA ready for more samples");
/* Stop requests can be issued again */
}
MSG("Draining...");
/* We want to next "device ready" notification only after the buffer is
already empty */
err = snd_pcm_sw_params_set_avail_min(id->alsa_pcm, id->alsa_sw_params, id->alsa_buffer_size);
if (err < 0) {
ERR("Unable to set avail min for playback: %s\n", snd_strerror(err));
return err;
}
/* write the parameters to the playback device */
err = snd_pcm_sw_params(id->alsa_pcm, id->alsa_sw_params);
if (err < 0) {
ERR("Unable to set sw params for playback: %s\n", snd_strerror(err));
return -1;
}
err = wait_for_poll(id, id->alsa_poll_fds, id->alsa_fd_count, 1);
if (err < 0) {
ERR("Wait for poll() failed\n");
return -1;
} else if (err == 1){
MSG("Playback stopped while draining");
/* Drop the playback on the sound device (probably
still in progress up till now) */
err = snd_pcm_drop(id->alsa_pcm);
if (err < 0) {
ERR("snd_pcm_drop() failed: ", snd_strerror (err));
return -1;
}
}
MSG("Draining terminated");
terminate:
/* Terminating (successfully or after a stop) */
if (track_volume.samples != NULL)
free(track_volume.samples);
err = _alsa_close(id);
if (err){
ERR("Cannot close Alsa device!");
return -1;
}
MSG("End of playback on ALSA");
return 0;
}
#undef ERROR_EXIT
/*
Stop the playback on the device and interrupt alsa_play()
*/
int
alsa_stop(AudioID *id)
{
int ret;
char buf;
if (id->alsa_opened){
/* This constant is arbitrary */
buf = 42;
MSG("Request for stop, device state is %s",
snd_pcm_state_name(snd_pcm_state(id->alsa_pcm)));
if (id == NULL) return 0;
write(id->alsa_stop_pipe[1], &buf, 1);
}
return 0;
}
/*
Set volume
Comments: It's not possible to set individual track volume with Alsa, so we
handle volume in alsa_play() by multiplication of each sample.
*/
int
alsa_set_volume(AudioID*id, int volume)
{
return 0;
}
/* Provide the Alsa backend. */
AudioFunctions alsa_functions = {alsa_open, alsa_play, alsa_stop, alsa_close, alsa_set_volume};
#undef MSG
#undef ERR
-------------------------------------------------------
SF.Net email is Sponsored by the Better Software Conference & EXPO
September 19-22, 2005 * San Francisco, CA * Development Lifecycle Practices
Agile & Plan-Driven Development * Managing Projects & Teams * Testing & QA
Security * Process Improvement & Measurement * http://www.sqe.com/bsce5sf
prev parent reply other threads:[~2005-08-05 13:06 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2005-08-05 11:05 Crack sounds in playback Hynek Hanke
2005-08-05 12:20 ` Clemens Ladisch
2005-08-05 12:29 ` Hynek Hanke
2005-08-05 12:49 ` Clemens Ladisch
2005-08-05 13:03 ` Hynek Hanke [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20050805130304.GD11890@brailcom.cz \
--to=hanke@brailcom.org \
--cc=alsa-devel@alsa-project.org \
--cc=clemens@ladisch.de \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.