Jaroslav Kysela wrote:
>On Wed, 12 Dec 2001, Tim Goetze wrote:
>
>> Abramo Bagnara wrote:
>>
>> >Tim Goetze wrote:
>> >>
>> >> >>but sometimes i also get inexplicable corruption within ordinary
>> >> >>dynamically allocated memory of the process before it exits.
>> >> >>
>> >> >>i have spent considerable time on verifying that these are indeed
>> >> >>caused by changing the period_size, and not by my own code.
>> >> >
>> >> >i would be too certain of this conclusion. i spent many a long night
>> >> >and day and night tracking down places in the audioengine code that
>> >> >had overrun the mmapped areas. i was only ever to track things down by
>> >> >generating huge log files that detailed every byte i wrote, and then
>> >> >use perl and awk to digest them.
>> >> >
>> >> >i'm not saying its not a problem in ALSA, only that its unlikely at
>> >> >this point. what have you done to ensure that its not your code? do
>> >> >you completely avoid writing data to the mapped areas?
>> >> >
>> >>
>> >> yes, exactly that. i just did
>> >> n * (open, configure, m * (poll, mmap_begin, mmap_commit), close)
>> >> and nothing more.
>> >
>> >You're missing avail_update: it's *mandatory*.
>> >
>> >> i ran this twice, one time with the usual configure, and one time
>> >> with f/c hardcoded to 64 in the hw setup so my code in all places believes
>> >> it's going 2048, 1024, 512, ... -- not that it matters because i don't
>> >> process any data. the one that's doing the 'real configure' crashes.
>>
>> yes, i did avail_update. sorry for not quoting all the 12111 lines of code
>> involved.
>>
>> and before i receive another message like this: i also did snd_pcm_link,
>> _unlink, _start and _drop.
>
>I think that it would be best for us to see some C code invoking the bad
>behaviour to debug the alsa-lib's code.
so thinks me too. i've isolated the audio stream code and attached it.
compile with 'gcc -o a.out main.cc -lasound -L/usr/lib', and run with
for example
$ ./a.out hw:0,0 2048
it will try to configure both streams on hw:0,0 for period_size of
2048, 1024 ... 64. on the awe it crashes when reaching 512.
this code does not even link, start, drop or unlink - just configure
the streams. it segfaults here before exit() even if i only configured
them one time.
tim
#include <unistd.h>
#include <stdio.h>
#include <alsa/asoundlib.h>
#include <exception>
#define D_SAMPLE_TO_INT(in,out) \
__asm__ __volatile__ ("fistpl %0" : "=m" (out) : "t" (in) : "st");
typedef float d_sample;
typedef char int8;
typedef short int16;
typedef int int32;
typedef long long int64;
class blewit : public std::exception
{
public:
char excuse [200];
blewit (const char * s = "");
blewit & operator << (const char * s);
blewit & operator << (int i);
blewit & operator << (double d);
blewit & operator << (void * v);
virtual const char * what() const {
return excuse;
}
};
#define msg() _msg (__FILE__, __LINE__)
class _msg
{
public:
static FILE * file;
FILE * f;
static void init (const char * path = 0);
_msg (FILE * _f);
_msg (char * __file__ = 0, int __line__ = -1);
~_msg() {
if (f == file) fputc ('\n', f); fflush (f);
}
operator FILE * () {
return f;
}
_msg & operator << (const char * s)
{
if (!s) s = "(char *) 0";
fputs (s, f); return *this;
}
_msg & operator << (unsigned long long ll)
{
fprintf (f, "%llu ", ll); return *this;
}
_msg & operator << (long l)
{
fprintf (f, "%ld ", l); return *this;
}
_msg & operator << (unsigned long l)
{
fprintf (f, "%lu ", l); return *this;
}
_msg & operator << (int i)
{
fprintf (f, "%d ", i); return *this;
}
_msg & operator << (unsigned int i)
{
fprintf (f, "%u ", i); return *this;
}
_msg & operator << (const void * p)
{
fprintf (f, "<%p> ", p); return *this;
}
_msg & operator << (double d)
{
fprintf (f, "%.5f ", d);
return *this;
}
};
FILE * _msg::file = stderr;
_msg::_msg (FILE * _f)
{
f = _f;
}
_msg::_msg (char * __file__ = 0, int __line__ = -1)
{
f = file;
if (__file__ && __line__ >= 0)
fprintf (f, "%s:%d:", __file__, __line__);
}
void
_msg::init (const char * path)
{
file = path ? fopen (path, "w") : 0;
if (!file)
file = stderr;
else if (0)
fcntl (
fileno (file), F_SETFL,
fcntl (fileno (file), F_GETFL) | O_NONBLOCK
);
}
blewit::blewit (const char * s)
{
strcpy (excuse, s);
}
blewit &
blewit::operator << (const char * s)
{
strcpy (excuse + strlen (excuse), s);
return *this;
}
blewit &
blewit::operator << (int i)
{
sprintf (excuse + strlen (excuse), "%d", i);
return *this;
}
blewit &
blewit::operator << (double d)
{
sprintf (excuse + strlen (excuse), "%.4f", d);
return *this;
}
blewit &
blewit::operator << (void * v)
{
sprintf (excuse + strlen (excuse), "<%p>", v);
return *this;
}
enum {
Input = 0,
Output = 1
};
class AudioStream
{
public:
snd_pcm_t * handle;
snd_pcm_hw_params_t * hw_params;
snd_pcm_sw_params_t * sw_params;
const snd_pcm_channel_area_t *
areas;
public:
snd_pcm_sframes_t available,
delay;
snd_pcm_uframes_t offset,
frames;
int frames_per_cycle, sample_rate;
int type; /* Port::Input || Port::Output */
bool interleaved,
configured;
uint bytes_per_sample;
int channels;
AudioStream();
~AudioStream();
void open (char * _name, int _type);
/* Plugin::* */
virtual void configure (uint sample_rate, uint frames_per_cycle);
virtual void process (uint _samples);
private:
void get_pollfd (struct pollfd * pfd) {
snd_pcm_poll_descriptors (handle, pfd, 1);
}
bool link (AudioStream *);
bool unlink();
bool prepare();
bool start();
bool stop();
void complete_silence (uint frames);
/* returns negative on xrun. */
int frames_available();
/* returns number of frames accessible */
uint begin_cycle (uint _frames);
/* commits */
bool end_cycle (uint _frames);
protected:
void silence (int channel);
void stream_in (int channel, d_sample *);
void stream_out (int channel, d_sample *);
/* called by configure to initialize the ports. */
void setup_ports();
};
AudioStream::AudioStream()
{
type = -1;
frames = 0;
handle = 0;
int error =
snd_pcm_hw_params_malloc (&hw_params) |
snd_pcm_sw_params_malloc (&sw_params);
if (error)
throw blewit ("cannot create a pcm_device_stream since we're out of
memory.");
}
AudioStream::~AudioStream()
{
if (handle)
snd_pcm_close (handle);
snd_pcm_hw_params_free (hw_params);
snd_pcm_sw_params_free (sw_params);
}
void
AudioStream::open (char * _name, int _type)
{
type = _type;
int error;
if (type == Input)
error = snd_pcm_open (&handle, _name, SND_PCM_STREAM_CAPTURE, 0);
else
error = snd_pcm_open (&handle, _name, SND_PCM_STREAM_PLAYBACK, 0);
if (error)
{
msg() << "cannot open " << _name
<< " " << -error << ":" << snd_strerror
(error) << ".";
throw blewit() << "snd_pcm_open failed.";
}
}
bool
AudioStream::link (AudioStream * other)
{
int error = snd_pcm_link (handle, other->handle);
if (error == 0)
return true;
msg() << " cannot link because "
<< -error << ":" << snd_strerror (error) << ".";
/* already means that we haven't failed in a way. */
return error == -EALREADY || false;
}
bool
AudioStream::unlink()
{
int error = snd_pcm_unlink (handle);
if (error == 0)
return true;
msg() << " cannot unlink because "
<< -error << ":" << snd_strerror (error) << ".";
/* already means that we haven't failed in a way. */
return error == -EALREADY || false;
}
bool
AudioStream::prepare()
{
if (!configured)
{
msg() << " is not configured yet.";
return false;
}
int error = snd_pcm_prepare (handle);
if (error == 0)
return true;
msg() << " cannot prepare because "
<< -error << ":" << snd_strerror (error) << ".";
return false;
}
bool
AudioStream::start()
{
int error = snd_pcm_start (handle);
if (error == 0)
return true;
msg() << " could not be started because "
<< -error << ":" << snd_strerror (error) << ".";
return false;
}
bool
AudioStream::stop()
{
int error = snd_pcm_drop (handle);
if (error == 0)
return true;
msg() << "could not stop "
<< "because "
<< -error << ":" << snd_strerror (error) << ".";
return false;
}
int
AudioStream::frames_available()
{
snd_pcm_sframes_t a = snd_pcm_avail_update (handle);
snd_pcm_delay (handle, (snd_pcm_sframes_t *) &delay);
if (a >= 0)
return (int) (available = a);
int xrun = abs (delay);
msg() << xrun << "samples "
<< (char *) (type == Input ? "over" : "under")
<< "run.";
return -xrun;
}
uint
AudioStream::begin_cycle (uint _frames)
{
frames = _frames;
areas = 0;
int error =
snd_pcm_mmap_begin (
handle, &areas,
(snd_pcm_uframes_t *) &offset,
(snd_pcm_uframes_t *) &frames
);
// msg() << "::begin_cycle = " << frames;
if (error == 0)
return frames;
msg() << "mmap_begin failed"
<< " because " << snd_strerror (error);
return 0;
}
bool
AudioStream::end_cycle (uint _frames)
{
int error = snd_pcm_mmap_commit (handle, offset, _frames);
// msg() << "::end_cycle = " << _frames;
/* remember that the cycle has ended. */
frames = 0;
areas = 0;
/* the ALSA doc says it returns 0 on success, but >= 0 is more like it. */
if (error >= 0)
return true;
msg() << "cannot mmap_commit"
<< " because " << snd_strerror (error);
return false;
}
/* Plugin::process for an AudioStream. Note that we inherently assume
* that uint samples is the same as our available frames count. */
void
AudioStream::process (uint samples)
{
if (samples != frames)
throw blewit() << "number of frames to process mismatch.";
end_cycle (frames);
}
/* silences *all* channels. could be optimized by checking for adjacency
* of channel buffers. later. */
void
AudioStream::complete_silence (uint frames)
{
for (int i = 0; i < channels; i++)
silence (i);
}
/* silences this channel. */
void
AudioStream::silence (int channel)
{
if (!frames)
throw blewit() << "ringbuffer not open.";
const snd_pcm_channel_area_t & area = areas[channel];
int8 * ptr = (int8 *) area.addr + ((area.first + area.step * offset) >> 3);
uint to_process = frames;
int step = area.step >> 3;
if (bytes_per_sample == 4)
{
while (to_process--)
{
*((int32 *) ptr) = 0;
ptr += step;
}
}
else if (bytes_per_sample == 2)
{
while (to_process--)
{
*((int16 *) ptr) = 0;
ptr += step;
}
}
else if (bytes_per_sample == 1)
{
while (to_process--)
{
*ptr = 0;
ptr += step;
}
}
}
/* streams audio data into the mmap buffer */
void
AudioStream::stream_in (int channel, d_sample * buffer)
{
if (!frames)
throw blewit() << "ringbuffer not open.";
const snd_pcm_channel_area_t & area = areas[channel];
int8 * ptr = (int8 *) area.addr + ((area.first + area.step * offset) >> 3);
int step = area.step >> 3;
uint to_process = frames;
if (bytes_per_sample == 4)
{
while (to_process--)
{
D_SAMPLE_TO_INT (0x7FFFFFFF * (*buffer), *((int32 *) ptr));
buffer++;
ptr += step;
}
}
else if (bytes_per_sample == 2)
{
while (to_process--)
{
*((int16 *) ptr) = (int16) (0x7FFF * (*buffer));
buffer++;
ptr += step;
}
}
else if (bytes_per_sample == 1)
{
while (to_process--)
{
*ptr = (int8) (0x7F * (*buffer));
buffer++;
ptr += step;
}
}
}
/* streams audio data from the mmap buffer */
void
AudioStream::stream_out (int channel, d_sample * buffer)
{
if (!frames)
throw blewit() << "ringbuffer not open.";
const snd_pcm_channel_area_t & area = areas[channel];
int8 * ptr = (int8 *) area.addr + ((area.first + area.step * offset) >> 3);
uint step = area.step >> 3;
uint to_process = frames;
if (bytes_per_sample == 4)
{
while (to_process--)
{
*buffer =
(d_sample) *((int32 *) ptr) / (d_sample) 0x7FFFFFFF;
buffer++;
ptr += step;
}
}
else if (bytes_per_sample == 2)
{
while (to_process--)
{
*buffer =
(d_sample) *((int16 *) ptr) / (d_sample) 32767.;
buffer++;
ptr += step;
}
}
else if (bytes_per_sample == 1)
{
while (to_process--)
{
*buffer =
(d_sample) *ptr / 127.;
buffer++;
ptr += step;
}
}
}
/* after configuring the stream, this creates the ports that the stream
* presents as a Plugin. */
void
AudioStream::setup_ports()
{
}
/* the _big_ configure. */
#define CONFIGURE(what,ifnot) \
{ \
int error = what; \
if (error) \
{ \
msg() << ifnot << " in AudioStream" \
<< "::configure (line " << __LINE__ <<
"): " \
<< snd_strerror (error); \
throw blewit() << ifnot; \
} \
}
void
AudioStream::configure (uint _sample_rate, uint _frames_per_cycle)
{
if (configured)
if (sample_rate == _sample_rate)
if (frames_per_cycle == _frames_per_cycle)
return;
configured = false;
sample_rate = _sample_rate;
frames_per_cycle = _frames_per_cycle;
CONFIGURE (snd_pcm_hw_params_any (handle, hw_params),
"no 'any' configuration to start with.");
CONFIGURE (snd_pcm_hw_params_set_periods_integer (handle, hw_params),
"no integer period size.");
int error = snd_pcm_hw_params_set_access
(handle, hw_params, SND_PCM_ACCESS_MMAP_NONINTERLEAVED);
if (error == 0)
interleaved = false;
else
{
CONFIGURE (snd_pcm_hw_params_set_access
(handle, hw_params, SND_PCM_ACCESS_MMAP_INTERLEAVED),
"memory-mapped access method refused.");
interleaved = true;
}
/* determine sample format */
snd_pcm_format_t formats [3] = {
SND_PCM_FORMAT_S32,
SND_PCM_FORMAT_S16,
SND_PCM_FORMAT_S8
};
bytes_per_sample = ~0;
for (int i = 0; i < 3; i++)
{
error = snd_pcm_hw_params_set_format (handle, hw_params, formats[i]);
if (error == 0)
{
bytes_per_sample = 4 >> i;
break;
}
}
if (bytes_per_sample > 4)
throw blewit() << "none of (32, 16, 8) bit sample sizes available.";
CONFIGURE (snd_pcm_hw_params_set_rate (handle, hw_params, sample_rate, 0),
"unsupported sample rate.");
channels = snd_pcm_hw_params_get_channels_max (hw_params);
error = snd_pcm_hw_params_set_channels (handle, hw_params, channels);
if (error)
{
msg() << " does not want to give us all its "
<< channels << " channels.";
throw blewit() << "no channels available.";
}
CONFIGURE (snd_pcm_hw_params_set_period_size
(handle, hw_params, (uint) frames_per_cycle, 0), /* XXX */
"hardware refuses this frames_per_cycle setting.");
CONFIGURE (snd_pcm_hw_params_set_periods
(handle, hw_params, 2, 0),
"hardware refuses 2 cycles per buffer.");
CONFIGURE (snd_pcm_hw_params_set_buffer_size
(handle, hw_params, 2 * frames_per_cycle), /* XXX */
"hardare refuses a buffer size of 2 * frames_per_cycle.");
CONFIGURE (snd_pcm_hw_params (handle, hw_params),
"hardware setup failed.");
/* sw params */
snd_pcm_sw_params_current (handle, sw_params);
CONFIGURE (snd_pcm_sw_params_set_start_threshold (handle, sw_params, ~0u),
"cannot turn off start threshold.");
CONFIGURE (snd_pcm_sw_params_set_stop_threshold (handle, sw_params, ~0u),
"cannot turn off stop threshold.");
CONFIGURE (snd_pcm_sw_params_set_silence_threshold (handle, sw_params, 0),
"cannot set 0 silence threshold.");
CONFIGURE (snd_pcm_sw_params_set_silence_size (handle, sw_params, 0),
"cannot set 0 silence size.");
uint avail_min = frames_per_cycle;
CONFIGURE (snd_pcm_sw_params_set_avail_min (handle, sw_params, avail_min),
"software setup for minimum available frames failed.");
CONFIGURE (snd_pcm_sw_params (handle, sw_params),
"software setup failed.");
setup_ports();
configured = true;
msg()
<< " configured for "
<< sample_rate
<< frames_per_cycle
<< (8 * bytes_per_sample) << "bit "
<< (char *) (interleaved ? "interleaved" :
"non-interleaved");
}
int main (int argc, char ** argv)
{
if (argc != 3) {
_msg() << argv[0] << " <device> <initial period_size>";
return 1;
}
int fpc;
if (sscanf (argv[2], "%d", &fpc) != 1) {
_msg() << argv[2] << " is not a valid period size.";
return 2;
}
try {
while (fpc >= 64)
{
AudioStream playback, capture;
capture.open (argv[1], Input);
playback.open (argv[1], Output);
capture.configure (44100, fpc);
playback.configure (44100, fpc);
sleep (1);
fpc >>= 1;
}
} catch (exception & e) {
_msg() << e.what();
return 3;
}
return 0;
}