( ͡° ͜ʖ ͡°){ʇlnɐɟ ƃǝs}Týr
Posts: 6,586
Karma: 6299991
Join Date: Jun 2012
Location: uti gratia usura (Yao ying da ying; Mo ying da yieng)
Device: PW-WIFI|K5-3G+WIFI| K4|K3-3G|DXG|K2| Rooted Nook Touch
|
Audio (video) - Kindle. Getting it up and running, Code level.
Latest Update:
"Audio - Kindle - Linux. Getting it up and running, Code level."
This thread is about the quest to get some audio action on the Kindle - direct via code.
If this is not something you are interested in. Stop reading now. Thanks
OUTPUT OF THE PROJECT
There are now a wealth of links on these threads to pretty much everything I could find about alsa as it stands today. It's not an impressive list of resources all in out there and hopefully this guide will serve to AT LEAST get sound in your applications in a singular direction without too much grind.
There is a useful overview of the PCM interface that is a bit scary but not a terrible read here http://www.alsa-project.org/alsa-doc/alsa-lib/pcm.html
STEP ONE: INIT MY ALSA
There are a few simple demos freely available from the Alsa project. We have used these as part of the basis of the explanations here. Hopefully in a clear enough manner to get you going.
BARE MINIMUM:
Spoiler:
Code:
#include <asoundlib.h>
static char *device = "hw:0"; /* playback device */
int main(void)
{
// setup the references.
snd_pcm_t *handle;
snd_pcm_sframes_t frames;
// open it.
snd_pcm_open(&handle, device, SND_PCM_STREAM_PLAYBACK, 0);
// init it.
snd_pcm_set_params(handle,
SND_PCM_FORMAT_S16_LE,
SND_PCM_ACCESS_RW_INTERLEAVED,
2, // channels
44100, // rate
0, // soft resample
500000); // latency
// write to it.
frames = snd_pcm_writei(handle, buffer, sizeof(buffer));
// close it.
snd_pcm_close(handle);
That's an absolute bare minimum example. The further examples demonstrate better error handling and alternate setup methods. All are succinct.
One of the more succinct demonstrations for initialising a sound device is here but the actual how and why of what's happening is not clear, plus much of the code can be elided for use on the kindle, so to this end here are two reworked versions of that demo. It's enough to get you going in about 5 minutes flat.
Here is the (Astlye compliant) discursive version of the code with additional notes and highlighting of important areas: (In the preferred style of Knc1)
Spoiler:
Code:
#include <alsa/asoundlib.h>
main (int argc, char *argv[])
{
// The err value being setup here would ordinarily
// be populated by error handling code that is "wrapped around"
// each of the calls to the alsa methods (in bold).
// I have left in one example of this construct on the snd_pcm_writei
// method to show how this works in practice. (At the end.)
// This repetitive construct is elided here for brevity, further examples
// can be found on previous posts on this thread
// i = index for looping over buffer at the end - It's a superfluous
// variable used only to illustrate buffer iteration and can be ignored
// err = alsa method return codes / frame counts etc...
int i, err;
// buf = An example data store to splat into buffer at the end
// You would fill a similar construct with something
// useful in the real world.
short buf[128];
// Declare an instance (or occurence if you prefer)
// of the special "Type" defined by Alsa
// This tells Alsa the card is an available sound device
// We use this name to reference the device in other calls
snd_pcm_t *playback_handle;
// Similarly we create a new "parameters" construct
// Simply creating the reference here is enough to create it
// You can use this to setup the card to your liking
// We do the bare minimum here to get us going
snd_pcm_hw_params_t *hw_params
// This is the 1st Alsa method we have called (directly anyway)
// note we reference the card via it's handle we just created
// Then we grab the first argument passed to the command line
// argv[1] gives us this value - an e.g is below.
// (like ./TestProgram hw:0 would return hw:0)
// At this stage we link the handle to the device hw:0 in this case
// (Further details of how devices are configed via conf files in earlier posts)
// We would get back an error code (into the err construct) at this
// stage if something was amiss in accessing the card (bad conf for e.g.)
// The full details of the enum SND_PCM_STREAM_ETC... can be
// viewed here the complete PCM command set here
snd_pcm_open (&playback_handle, argv[1], SND_PCM_STREAM_PLAYBACK, 0);
// This defines some space for our params constuct
// c is fussy about memory handling
// Make some space - remember to tidy it up after
// Malloc = Think "Memory Allocate"
snd_pcm_hw_params_malloc (&hw_params);
// This is defined in the ALSA Hardware section here
// Fill our params space with a full configuration construct for a PCM.
// Making use of the space we just made and created a skeleton for us
// Note we still reference the card via the handle from now on in
snd_pcm_hw_params_any (playback_handle, hw_params);
// Restrict a configuration space to contain only one access type.
// The full reference for the method is here
// I seemed to need to set this to just one type to get the card going.
// Perhaps setting one val is absolute minimum for configuration
// I am unsure but this got it working for me without going on to set
// the sample rate, access format etc. the full gory details are in the
// original demo, this is about getting the kindle going with minimum code
// the full tech ref for the enum SND_PCM_ACCESS_BLAH is here
// In essence it is pcm_read/write interleaved access
// Pcm means it's not done in memory it's (kinda) direct to card
// read/write means duplexing possible, Interleaving means that we
// alternate samples for the left and right channel (LR,LR,LR...).
// It's a very generic choice. You could "refine" this decision to your needs
snd_pcm_hw_params_set_access (playback_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
// The next utility method puts our params decisions in motion
// "Install one PCM hardware configuration chosen from a configuration
// space and snd_pcm_prepare it." say the docs here
// so as you can see this runs another method internally for us. Happy days.
snd_pcm_hw_params (playback_handle, hw_params);
// This simply tidies up the memory space from the param construct
// as we are done with it,
// it's always good practice in c to destruct your disused objects.
snd_pcm_hw_params_free (hw_params);
// Showtime, as shown here this "Prepare[s] PCM for use."
// All of these methods could have been wrapped in error handlers,
// only some of them have real value though, this is maybe one of them
snd_pcm_prepare (playback_handle);
// We are now ready to so some stuff so let's write some interleaved data.
// Everything after this point is "Program", we have done our job really.
// buf is just some 0 data 128 long,
// 128 is length of buf, effectively buf.length
// 10 is just a value to show looping - has no instrinsic meaning
// write 10 * 128 shorties via the handle using interleaved writing
// This says - in English-Psuedo-Code
// Try to write 128 shorts to the PCM
// If it fails a) return an error value for the process and Then
// b) exit the application with a code indicating Failure
// Do this ten times
for (i = 0; i < 10; ++i)
{ if ((err = snd_pcm_writei (playback_handle, buf, 128)) != 128) {
fprintf (stderr, "write to audio interface failed (%s)\n",snd_strerror (err));
exit (1);
}
}
//Tidy up. Ref here
snd_pcm_close (playback_handle);
// Exit the application with a code indicating Success.
exit (0);
}
INIT ALSA - CHOP FU
And here is that demo again but this time squished: (In GM's preferred compressed style)
Spoiler:
Code:
#include <alsa/asoundlib.h>
main (int argc, char *argv[]) {
//INIT BLOCK
int err; short buf[128]; snd_pcm_t *playback_handle;
snd_pcm_hw_params_t *hw_params;
snd_pcm_open (&playback_handle, argv[1], SND_PCM_STREAM_PLAYBACK, 0);
snd_pcm_hw_params_malloc (&hw_params);
snd_pcm_hw_params_any (playback_handle, hw_params);
snd_pcm_hw_params_set_access (playback_handle, hw_params,
SND_PCM_ACCESS_RW_INTERLEAVED);
snd_pcm_hw_params (playback_handle, hw_params);
snd_pcm_hw_params_free (hw_params);
snd_pcm_prepare (playback_handle);
// Write something
snd_pcm_writei (playback_handle, buf, 128) ;
//Tidy up.
snd_pcm_close (playback_handle); exit (0);}
//Done.
ACKNOWLEDGMENTS
Many thanks to: The excellent assistance of GeekMaster, Knc1, NiLuJe and others
and notably the KeK project, without which this project would still be stuck in no-x-compile-land.
All of the work regarding video is completely down to the work of Geekmaster and you can find the thread dedicated to that here geekmaster kindle video player , currently it is without audio. Perfect for the thrust of this thread: "Providing support and documentation to enable sound for the kindle." Testing continues but I can report decent beta results on a 3.
WORK DONE SO FAR
Spoiler:
Identified the audio card, drivers and some ancillary support mechanisms in place on the device. These are documented in the further posts.
We have working card init code in cpp. c,
A Tones demo cross-compiled for all audio enabled kindles.
TODO:
The application outlined below is a simple tone generator demonstrating access the kindles alsa layer.
It's a more fun demo than the previous top-post and people like fun.
A more general purpose initialisation codeset - possibly called "Kradle" - is in the wings, somewhere, one day.
DEMO APPLICATION: tones
Frankly I'm amazed it works at all. so if it blows up your kindle? Don't blame me.
Source:
Spoiler:
Code:
/*
* This small demo sends a simple sinusoidal wave to your speakers.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sched.h>
#include <errno.h>
#include <getopt.h>
#include <asoundlib.h>
#include <sys/time.h>
#include <math.h>
static char *device = "hw:0,0"; /* playback device */
static snd_pcm_format_t format = SND_PCM_FORMAT_S16; /* sample format */
static unsigned int rate = 44100; /* stream rate */
static unsigned int channels = 2; /* count of channels */
static unsigned int buffer_time = 500000; /* ring buffer length in us */
static unsigned int period_time = 100000; /* period time in us */
static double freq = 440; /* sinusoidal wave frequency in Hz */
static int verbose = 0; /* verbose flag */
static int resample = 1; /* enable alsa-lib resampling */
static int period_event = 0; /* produce poll event after each period */
static snd_pcm_sframes_t buffer_size;
static snd_pcm_sframes_t period_size;
static snd_output_t *output = NULL;
static void generate_sine(const snd_pcm_channel_area_t *areas,
snd_pcm_uframes_t offset,
int count, double *_phase)
{
// this is to store the value of direction
static int direction = 0; // default direction;
static int pace_count = 30; // default speedup;
static int multi = 1; // for reference really
static int d_count = 0; // to reduce screen clutter;
static int inc_dec = 1; // to inc or dec the multi;
static double max_phase = 2. * M_PI;
double phase = *_phase;
double step = max_phase*freq/(double)rate;
unsigned char *samples[channels];
int steps[channels];
unsigned int chn;
int format_bits = snd_pcm_format_width(format);
unsigned int maxval = (1 << (format_bits - 1)) - 1;
int bps = format_bits / 8; /* bytes per sample */
int phys_bps = snd_pcm_format_physical_width(format) / 8;
int big_endian = snd_pcm_format_big_endian(format) == 1;
int to_unsigned = snd_pcm_format_unsigned(format) == 1;
int is_float = (format == SND_PCM_FORMAT_FLOAT_LE ||
format == SND_PCM_FORMAT_FLOAT_BE);
/* verify and prepare the contents of areas */
for (chn = 0; chn < channels; chn++) {
if ((areas[chn].first % 8) != 0) {
printf("areas[%i].first == %i, aborting...\n", chn, areas[chn].first);
exit(EXIT_FAILURE);
}
samples[chn] = /*(signed short *)*/(((unsigned char *)areas[chn].addr) + (areas[chn].first / 8));
if ((areas[chn].step % 16) != 0) {
printf("areas[%i].step == %i, aborting...\n", chn, areas[chn].step);
exit(EXIT_FAILURE);
}
steps[chn] = areas[chn].step / 8;
samples[chn] += offset * steps[chn];
}
/* fill the channel areas */
while (count-- > 0) {
union {
float f;
int i;
} fval;
//result and index
int res, i;
if (is_float) {
// Do the maths
fval.f = sin(phase) * maxval;
res = fval.i;
} else
//Do the maths
res = sin(phase) * maxval;
// remove sign
if (to_unsigned)
res ^= 1U << (format_bits - 1);
for (chn = 0; chn < channels; chn++) {
/* Generate data in native endian format */
if (big_endian) {
for (i = 0; i < bps; i++)
*(samples[chn] + phys_bps - 1 - i) = (res >> i * 8) & 0xff;
} else {
for (i = 0; i < bps; i++)
*(samples[chn] + i) = (res >> i * 8) & 0xff;
}
samples[chn] += steps[chn];
}
//increment phase
phase += (step);
// >= 2 PI
if (phase >= max_phase)
// loop back to beginning
phase -= max_phase;
}
*_phase = phase;
// We need to stop the number range going to far out
// The number range should not be more than half of the sample rate - like 8k for 16000
if (freq <= -3000)
{
direction = 1;
}
else if (freq >= 3000)
{
direction = 0;
}
// make it circle round some helpful edges
if (multi > 1000)
{ inc_dec = -20;}
else if (multi < 20)
{ inc_dec = 2;}
pace_count+= inc_dec;
multi= pace_count * M_PI;
if (direction)
{
//* M_PI added just for a laugh
freq +=max_phase*multi; //added just for a laugh;
}
else
{//* M_PI added just for a laugh
freq -=max_phase*multi;
}
if (d_count == 10)
{
printf("freak: %f multiplier %i\n",freq, multi);
d_count = 0;
}
d_count++;
}
static int set_hwparams(snd_pcm_t *handle,
snd_pcm_hw_params_t *params,
snd_pcm_access_t access)
{
unsigned int rrate;
snd_pcm_uframes_t size;
int err, dir;
/* choose all parameters */
err = snd_pcm_hw_params_any(handle, params);
if (err < 0) {
printf("Broken configuration for playback: no configurations available: %s\n", snd_strerror(err));
return err;
}
/* set hardware resampling */
err = snd_pcm_hw_params_set_rate_resample(handle, params, resample);
if (err < 0) {
printf("Resampling setup failed for playback: %s\n", snd_strerror(err));
return err;
}
/* set the interleaved read/write format */
err = snd_pcm_hw_params_set_access(handle, params, access);
if (err < 0) {
printf("Access type not available for playback: %s\n", snd_strerror(err));
return err;
}
/* set the sample format */
err = snd_pcm_hw_params_set_format(handle, params, format);
if (err < 0) {
printf("Sample format not available for playback: %s\n", snd_strerror(err));
return err;
}
/* set the count of channels */
err = snd_pcm_hw_params_set_channels(handle, params, channels);
if (err < 0) {
printf("Channels count (%i) not available for playbacks: %s\n", channels, snd_strerror(err));
return err;
}
/* set the stream rate */
rrate = rate;
err = snd_pcm_hw_params_set_rate_near(handle, params, &rrate, 0);
if (err < 0) {
printf("Rate %iHz not available for playback: %s\n", rate, snd_strerror(err));
return err;
}
if (rrate != rate) {
printf("Rate doesn't match (requested %iHz, get %iHz)\n", rate, err);
return -EINVAL;
}
/* set the buffer time */
err = snd_pcm_hw_params_set_buffer_time_near(handle, params, &buffer_time, &dir);
if (err < 0) {
printf("Unable to set buffer time %i for playback: %s\n", buffer_time, snd_strerror(err));
return err;
}
err = snd_pcm_hw_params_get_buffer_size(params, &size);
if (err < 0) {
printf("Unable to get buffer size for playback: %s\n", snd_strerror(err));
return err;
}
buffer_size = size;
/* set the period time */
err = snd_pcm_hw_params_set_period_time_near(handle, params, &period_time, &dir);
if (err < 0) {
printf("Unable to set period time %i for playback: %s\n", period_time, snd_strerror(err));
return err;
}
err = snd_pcm_hw_params_get_period_size(params, &size, &dir);
if (err < 0) {
printf("Unable to get period size for playback: %s\n", snd_strerror(err));
return err;
}
period_size = size;
/* write the parameters to device */
err = snd_pcm_hw_params(handle, params);
if (err < 0) {
printf("Unable to set hw params for playback: %s\n", snd_strerror(err));
return err;
}
return 0;
}
static int set_swparams(snd_pcm_t *handle, snd_pcm_sw_params_t *swparams)
{
int err;
/* get the current swparams */
err = snd_pcm_sw_params_current(handle, swparams);
if (err < 0) {
printf("Unable to determine current swparams for playback: %s\n", snd_strerror(err));
return err;
}
/* start the transfer when the buffer is almost full: */
/* (buffer_size / avail_min) * avail_min */
err = snd_pcm_sw_params_set_start_threshold(handle, swparams, (buffer_size / period_size) * period_size);
if (err < 0) {
printf("Unable to set start threshold mode for playback: %s\n", snd_strerror(err));
return err;
}
/* allow the transfer when at least period_size samples can be processed */
/* or disable this mechanism when period event is enabled (aka interrupt like style processing) */
err = snd_pcm_sw_params_set_avail_min(handle, swparams, period_event ? buffer_size : period_size);
if (err < 0) {
printf("Unable to set avail min for playback: %s\n", snd_strerror(err));
return err;
}
/* enable period events when requested */
if (period_event) {
// err = snd_pcm_sw_params_set_period_event(handle, swparams, 1);
// if (err < 0) {
// printf("Unable to set period event: %s\n", snd_strerror(err));
// return err;
// }
}
/* write the parameters to the playback device */
err = snd_pcm_sw_params(handle, swparams);
if (err < 0) {
printf("Unable to set sw params for playback: %s\n", snd_strerror(err));
return err;
}
return 0;
}
/*
* Underrun and suspend recovery
*/
static int xrun_recovery(snd_pcm_t *handle, int err)
{
if (verbose)
printf("stream recovery\n");
if (err == -EPIPE) { /* under-run */
err = snd_pcm_prepare(handle);
if (err < 0)
printf("Can't recovery from underrun, prepare failed: %s\n", snd_strerror(err));
return 0;
} else if (err == -ESTRPIPE) {
while ((err = snd_pcm_resume(handle)) == -EAGAIN)
sleep(1); /* wait until the suspend flag is released */
if (err < 0) {
err = snd_pcm_prepare(handle);
if (err < 0)
printf("Can't recovery from suspend, prepare failed: %s\n", snd_strerror(err));
}
return 0;
}
return err;
}
/*
* Transfer method - write only
*/
static int write_loop(snd_pcm_t *handle,
signed short *samples,
snd_pcm_channel_area_t *areas)
{
double phase = 0;
signed short *ptr;
int err, cptr;
while (1) {
generate_sine(areas, 0, period_size, &phase);
ptr = samples;
cptr = period_size;
while (cptr > 0) {
err = snd_pcm_writei(handle, ptr, cptr);
if (err == -EAGAIN)
continue;
if (err < 0) {
if (xrun_recovery(handle, err) < 0) {
printf("Write error: %s\n", snd_strerror(err));
exit(EXIT_FAILURE);
}
break; /* skip one period */
}
ptr += err * channels;
cptr -= err;
}
}
}
/*
* Transfer method - write and wait for room in buffer using poll
*/
static int wait_for_poll(snd_pcm_t *handle, struct pollfd *ufds, unsigned int count)
{
unsigned short revents;
while (1) {
poll(ufds, count, -1);
snd_pcm_poll_descriptors_revents(handle, ufds, count, &revents);
if (revents & POLLERR)
return -EIO;
if (revents & POLLOUT)
return 0;
}
}
static int write_and_poll_loop(snd_pcm_t *handle,
signed short *samples,
snd_pcm_channel_area_t *areas)
{
struct pollfd *ufds;
double phase = 0;
signed short *ptr;
int err, count, cptr, init;
count = snd_pcm_poll_descriptors_count (handle);
if (count <= 0) {
printf("Invalid poll descriptors count\n");
return count;
}
ufds = malloc(sizeof(struct pollfd) * count);
if (ufds == NULL) {
printf("No enough memory\n");
return -ENOMEM;
}
if ((err = snd_pcm_poll_descriptors(handle, ufds, count)) < 0) {
printf("Unable to obtain poll descriptors for playback: %s\n", snd_strerror(err));
return err;
}
init = 1;
while (1) {
if (!init) {
err = wait_for_poll(handle, ufds, count);
if (err < 0) {
if (snd_pcm_state(handle) == SND_PCM_STATE_XRUN ||
snd_pcm_state(handle) == SND_PCM_STATE_SUSPENDED) {
err = snd_pcm_state(handle) == SND_PCM_STATE_XRUN ? -EPIPE : -ESTRPIPE;
if (xrun_recovery(handle, err) < 0) {
printf("Write error: %s\n", snd_strerror(err));
exit(EXIT_FAILURE);
}
init = 1;
} else {
printf("Wait for poll failed\n");
return err;
}
}
}
generate_sine(areas, 0, period_size, &phase);
ptr = samples;
cptr = period_size;
while (cptr > 0) {
err = snd_pcm_writei(handle, ptr, cptr);
if (err < 0) {
if (xrun_recovery(handle, err) < 0) {
printf("Write error: %s\n", snd_strerror(err));
exit(EXIT_FAILURE);
}
init = 1;
break; /* skip one period */
}
if (snd_pcm_state(handle) == SND_PCM_STATE_RUNNING)
init = 0;
ptr += err * channels;
cptr -= err;
if (cptr == 0)
break;
/* it is possible, that the initial buffer cannot store */
/* all data from the last period, so wait awhile */
err = wait_for_poll(handle, ufds, count);
if (err < 0) {
if (snd_pcm_state(handle) == SND_PCM_STATE_XRUN ||
snd_pcm_state(handle) == SND_PCM_STATE_SUSPENDED) {
err = snd_pcm_state(handle) == SND_PCM_STATE_XRUN ? -EPIPE : -ESTRPIPE;
if (xrun_recovery(handle, err) < 0) {
printf("Write error: %s\n", snd_strerror(err));
exit(EXIT_FAILURE);
}
init = 1;
} else {
printf("Wait for poll failed\n");
return err;
}
}
}
}
}
/*
* Transfer method - asynchronous notification
*/
struct async_private_data {
signed short *samples;
snd_pcm_channel_area_t *areas;
double phase;
};
static void async_callback(snd_async_handler_t *ahandler)
{
snd_pcm_t *handle = snd_async_handler_get_pcm(ahandler);
struct async_private_data *data = snd_async_handler_get_callback_private(ahandler);
signed short *samples = data->samples;
snd_pcm_channel_area_t *areas = data->areas;
snd_pcm_sframes_t avail;
int err;
avail = snd_pcm_avail_update(handle);
while (avail >= period_size) {
generate_sine(areas, 0, period_size, &data->phase);
err = snd_pcm_writei(handle, samples, period_size);
if (err < 0) {
printf("Write error: %s\n", snd_strerror(err));
exit(EXIT_FAILURE);
}
if (err != period_size) {
printf("Write error: written %i expected %li\n", err, period_size);
exit(EXIT_FAILURE);
}
avail = snd_pcm_avail_update(handle);
}
}
static int async_loop(snd_pcm_t *handle,
signed short *samples,
snd_pcm_channel_area_t *areas)
{
struct async_private_data data;
snd_async_handler_t *ahandler;
int err, count;
data.samples = samples;
data.areas = areas;
data.phase = 0;
err = snd_async_add_pcm_handler(&ahandler, handle, async_callback, &data);
if (err < 0) {
printf("Unable to register async handler\n");
exit(EXIT_FAILURE);
}
for (count = 0; count < 2; count++) {
generate_sine(areas, 0, period_size, &data.phase);
err = snd_pcm_writei(handle, samples, period_size);
if (err < 0) {
printf("Initial write error: %s\n", snd_strerror(err));
exit(EXIT_FAILURE);
}
if (err != period_size) {
printf("Initial write error: written %i expected %li\n", err, period_size);
exit(EXIT_FAILURE);
}
}
if (snd_pcm_state(handle) == SND_PCM_STATE_PREPARED) {
err = snd_pcm_start(handle);
if (err < 0) {
printf("Start error: %s\n", snd_strerror(err));
exit(EXIT_FAILURE);
}
}
/* because all other work is done in the signal handler,
suspend the process */
while (1) {
sleep(1);
}
}
/*
* Transfer method - asynchronous notification + direct write
*/
static void async_direct_callback(snd_async_handler_t *ahandler)
{
snd_pcm_t *handle = snd_async_handler_get_pcm(ahandler);
struct async_private_data *data = snd_async_handler_get_callback_private(ahandler);
const snd_pcm_channel_area_t *my_areas;
snd_pcm_uframes_t offset, frames, size;
snd_pcm_sframes_t avail, commitres;
snd_pcm_state_t state;
int first = 0, err;
while (1) {
state = snd_pcm_state(handle);
if (state == SND_PCM_STATE_XRUN) {
err = xrun_recovery(handle, -EPIPE);
if (err < 0) {
printf("XRUN recovery failed: %s\n", snd_strerror(err));
exit(EXIT_FAILURE);
}
first = 1;
} else if (state == SND_PCM_STATE_SUSPENDED) {
err = xrun_recovery(handle, -ESTRPIPE);
if (err < 0) {
printf("SUSPEND recovery failed: %s\n", snd_strerror(err));
exit(EXIT_FAILURE);
}
}
avail = snd_pcm_avail_update(handle);
if (avail < 0) {
err = xrun_recovery(handle, avail);
if (err < 0) {
printf("avail update failed: %s\n", snd_strerror(err));
exit(EXIT_FAILURE);
}
first = 1;
continue;
}
if (avail < period_size) {
if (first) {
first = 0;
err = snd_pcm_start(handle);
if (err < 0) {
printf("Start error: %s\n", snd_strerror(err));
exit(EXIT_FAILURE);
}
} else {
break;
}
continue;
}
size = period_size;
while (size > 0) {
frames = size;
err = snd_pcm_mmap_begin(handle, &my_areas, &offset, &frames);
if (err < 0) {
if ((err = xrun_recovery(handle, err)) < 0) {
printf("MMAP begin avail error: %s\n", snd_strerror(err));
exit(EXIT_FAILURE);
}
first = 1;
}
generate_sine(my_areas, offset, frames, &data->phase);
commitres = snd_pcm_mmap_commit(handle, offset, frames);
if (commitres < 0 || (snd_pcm_uframes_t)commitres != frames) {
if ((err = xrun_recovery(handle, commitres >= 0 ? -EPIPE : commitres)) < 0) {
printf("MMAP commit error: %s\n", snd_strerror(err));
exit(EXIT_FAILURE);
}
first = 1;
}
size -= frames;
}
}
}
static int async_direct_loop(snd_pcm_t *handle,
signed short *samples ATTRIBUTE_UNUSED,
snd_pcm_channel_area_t *areas ATTRIBUTE_UNUSED)
{
struct async_private_data data;
snd_async_handler_t *ahandler;
const snd_pcm_channel_area_t *my_areas;
snd_pcm_uframes_t offset, frames, size;
snd_pcm_sframes_t commitres;
int err, count;
data.samples = NULL; /* we do not require the global sample area for direct write */
data.areas = NULL; /* we do not require the global areas for direct write */
data.phase = 0;
err = snd_async_add_pcm_handler(&ahandler, handle, async_direct_callback, &data);
if (err < 0) {
printf("Unable to register async handler\n");
exit(EXIT_FAILURE);
}
for (count = 0; count < 2; count++) {
size = period_size;
while (size > 0) {
frames = size;
err = snd_pcm_mmap_begin(handle, &my_areas, &offset, &frames);
if (err < 0) {
if ((err = xrun_recovery(handle, err)) < 0) {
printf("MMAP begin avail error: %s\n", snd_strerror(err));
exit(EXIT_FAILURE);
}
}
generate_sine(my_areas, offset, frames, &data.phase);
commitres = snd_pcm_mmap_commit(handle, offset, frames);
if (commitres < 0 || (snd_pcm_uframes_t)commitres != frames) {
if ((err = xrun_recovery(handle, commitres >= 0 ? -EPIPE : commitres)) < 0) {
printf("MMAP commit error: %s\n", snd_strerror(err));
exit(EXIT_FAILURE);
}
}
size -= frames;
}
}
err = snd_pcm_start(handle);
if (err < 0) {
printf("Start error: %s\n", snd_strerror(err));
exit(EXIT_FAILURE);
}
/* because all other work is done in the signal handler,
suspend the process */
while (1) {
sleep(1);
}
}
/*
* Transfer method - direct write only
*/
static int direct_loop(snd_pcm_t *handle,
signed short *samples ATTRIBUTE_UNUSED,
snd_pcm_channel_area_t *areas ATTRIBUTE_UNUSED)
{
double phase = 0;
const snd_pcm_channel_area_t *my_areas;
snd_pcm_uframes_t offset, frames, size;
snd_pcm_sframes_t avail, commitres;
snd_pcm_state_t state;
int err, first = 1;
while (1) {
state = snd_pcm_state(handle);
if (state == SND_PCM_STATE_XRUN) {
err = xrun_recovery(handle, -EPIPE);
if (err < 0) {
printf("XRUN recovery failed: %s\n", snd_strerror(err));
return err;
}
first = 1;
} else if (state == SND_PCM_STATE_SUSPENDED) {
err = xrun_recovery(handle, -ESTRPIPE);
if (err < 0) {
printf("SUSPEND recovery failed: %s\n", snd_strerror(err));
return err;
}
}
avail = snd_pcm_avail_update(handle);
if (avail < 0) {
err = xrun_recovery(handle, avail);
if (err < 0) {
printf("avail update failed: %s\n", snd_strerror(err));
return err;
}
first = 1;
continue;
}
if (avail < period_size) {
if (first) {
first = 0;
err = snd_pcm_start(handle);
if (err < 0) {
printf("Start error: %s\n", snd_strerror(err));
exit(EXIT_FAILURE);
}
} else {
err = snd_pcm_wait(handle, -1);
if (err < 0) {
if ((err = xrun_recovery(handle, err)) < 0) {
printf("snd_pcm_wait error: %s\n", snd_strerror(err));
exit(EXIT_FAILURE);
}
first = 1;
}
}
continue;
}
size = period_size;
while (size > 0) {
frames = size;
err = snd_pcm_mmap_begin(handle, &my_areas, &offset, &frames);
if (err < 0) {
if ((err = xrun_recovery(handle, err)) < 0) {
printf("MMAP begin avail error: %s\n", snd_strerror(err));
exit(EXIT_FAILURE);
}
first = 1;
}
generate_sine(my_areas, offset, frames, &phase);
commitres = snd_pcm_mmap_commit(handle, offset, frames);
if (commitres < 0 || (snd_pcm_uframes_t)commitres != frames) {
if ((err = xrun_recovery(handle, commitres >= 0 ? -EPIPE : commitres)) < 0) {
printf("MMAP commit error: %s\n", snd_strerror(err));
exit(EXIT_FAILURE);
}
first = 1;
}
size -= frames;
}
}
}
/*
* Transfer method - direct write only using mmap_write functions
*/
static int direct_write_loop(snd_pcm_t *handle,
signed short *samples,
snd_pcm_channel_area_t *areas)
{
double phase = 0;
signed short *ptr;
int err, cptr;
while (1) {
generate_sine(areas, 0, period_size, &phase);
ptr = samples;
cptr = period_size;
while (cptr > 0) {
err = snd_pcm_mmap_writei(handle, ptr, cptr);
if (err == -EAGAIN)
continue;
if (err < 0) {
if (xrun_recovery(handle, err) < 0) {
printf("Write error: %s\n", snd_strerror(err));
exit(EXIT_FAILURE);
}
break; /* skip one period */
}
ptr += err * channels;
cptr -= err;
}
}
}
/*
*
*/
struct transfer_method {
const char *name;
snd_pcm_access_t access;
int (*transfer_loop)(snd_pcm_t *handle,
signed short *samples,
snd_pcm_channel_area_t *areas);
};
static struct transfer_method transfer_methods[] = {
{ "write", SND_PCM_ACCESS_RW_INTERLEAVED, write_loop },
{ "write_and_poll", SND_PCM_ACCESS_RW_INTERLEAVED, write_and_poll_loop },
{ "async", SND_PCM_ACCESS_RW_INTERLEAVED, async_loop },
{ "async_direct", SND_PCM_ACCESS_MMAP_INTERLEAVED, async_direct_loop },
{ "direct_interleaved", SND_PCM_ACCESS_MMAP_INTERLEAVED, direct_loop },
{ "direct_noninterleaved", SND_PCM_ACCESS_MMAP_NONINTERLEAVED, direct_loop },
{ "direct_write", SND_PCM_ACCESS_MMAP_INTERLEAVED, direct_write_loop },
{ NULL, SND_PCM_ACCESS_RW_INTERLEAVED, NULL }
};
static void help(void)
{
int k;
printf(
"Usage: pcm [OPTION]... [FILE]...\n"
"-h,--help help\n"
"-D,--device playback device\n"
"-r,--rate stream rate in Hz\n"
"-c,--channels count of channels in stream\n"
"-f,--frequency sine wave frequency in Hz\n"
"-b,--buffer ring buffer size in us\n"
"-p,--period period size in us\n"
"-m,--method transfer method\n"
"-o,--format sample format\n"
"-v,--verbose show the PCM setup parameters\n"
"-n,--noresample do not resample\n"
"-e,--pevent enable poll event after each period\n"
"\n");
printf("Recognized sample formats are:");
for (k = 0; k < SND_PCM_FORMAT_LAST; ++k) {
const char *s = snd_pcm_format_name(k);
if (s)
printf(" %s", s);
}
printf("\n");
printf("Recognized transfer methods are:");
for (k = 0; transfer_methods[k].name; k++)
printf(" %s", transfer_methods[k].name);
printf("\n");
}
int main(int argc, char *argv[])
{
struct option long_option[] =
{
{"help", 0, NULL, 'h'},
{"device", 1, NULL, 'D'},
{"rate", 1, NULL, 'r'},
{"channels", 1, NULL, 'c'},
{"frequency", 1, NULL, 'f'},
{"buffer", 1, NULL, 'b'},
{"period", 1, NULL, 'p'},
{"method", 1, NULL, 'm'},
{"format", 1, NULL, 'o'},
{"verbose", 1, NULL, 'v'},
{"noresample", 1, NULL, 'n'},
{"pevent", 1, NULL, 'e'},
{NULL, 0, NULL, 0},
};
snd_pcm_t *handle;
int err, morehelp;
snd_pcm_hw_params_t *hwparams;
snd_pcm_sw_params_t *swparams;
int method = 0;
signed short *samples;
unsigned int chn;
snd_pcm_channel_area_t *areas;
snd_pcm_hw_params_alloca(&hwparams);
snd_pcm_sw_params_alloca(&swparams);
morehelp = 0;
while (1) {
int c;
if ((c = getopt_long(argc, argv, "hD:r:c:f:b:p:m:o:vne", long_option, NULL)) < 0)
break;
switch (c) {
case 'h':
morehelp++;
break;
case 'D':
device = strdup(optarg);
break;
case 'r':
rate = atoi(optarg);
rate = rate < 4000 ? 4000 : rate;
rate = rate > 196000 ? 196000 : rate;
break;
case 'c':
channels = atoi(optarg);
channels = channels < 1 ? 1 : channels;
channels = channels > 1024 ? 1024 : channels;
break;
case 'f':
freq = atoi(optarg);
freq = freq < 50 ? 50 : freq;
freq = freq > 5000 ? 5000 : freq;
break;
case 'b':
buffer_time = atoi(optarg);
buffer_time = buffer_time < 1000 ? 1000 : buffer_time;
buffer_time = buffer_time > 1000000 ? 1000000 : buffer_time;
break;
case 'p':
period_time = atoi(optarg);
period_time = period_time < 1000 ? 1000 : period_time;
period_time = period_time > 1000000 ? 1000000 : period_time;
break;
case 'm':
for (method = 0; transfer_methods[method].name; method++)
if (!strcasecmp(transfer_methods[method].name, optarg))
break;
if (transfer_methods[method].name == NULL)
method = 0;
break;
case 'o':
for (format = 0; format < SND_PCM_FORMAT_LAST; format++) {
const char *format_name = snd_pcm_format_name(format);
if (format_name)
if (!strcasecmp(format_name, optarg))
break;
}
if (format == SND_PCM_FORMAT_LAST)
format = SND_PCM_FORMAT_S16;
if (!snd_pcm_format_linear(format) &&
!(format == SND_PCM_FORMAT_FLOAT_LE ||
format == SND_PCM_FORMAT_FLOAT_BE)) {
printf("Invalid (non-linear/float) format %s\n",
optarg);
return 1;
}
break;
case 'v':
verbose = 1;
break;
case 'n':
resample = 0;
break;
case 'e':
period_event = 1;
break;
}
}
if (morehelp) {
help();
return 0;
}
err = snd_output_stdio_attach(&output, stdout, 0);
if (err < 0) {
printf("Output failed: %s\n", snd_strerror(err));
return 0;
}
printf("Playback device is %s\n", device);
printf("Stream parameters are %iHz, %s, %i channels\n", rate, snd_pcm_format_name(format), channels);
printf("Sine wave rate is %.4fHz\n", freq);
printf("Using transfer method: %s\n", transfer_methods[method].name);
if ((err = snd_pcm_open(&handle, device, SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
printf("Playback open error: %s\n", snd_strerror(err));
return 0;
}
if ((err = set_hwparams(handle, hwparams, transfer_methods[method].access)) < 0) {
printf("Setting of hwparams failed: %s\n", snd_strerror(err));
exit(EXIT_FAILURE);
}
if ((err = set_swparams(handle, swparams)) < 0) {
printf("Setting of swparams failed: %s\n", snd_strerror(err));
exit(EXIT_FAILURE);
}
if (verbose > 0)
snd_pcm_dump(handle, output);
samples = malloc((period_size * channels * snd_pcm_format_physical_width(format)) / 8);
if (samples == NULL) {
printf("No enough memory\n");
exit(EXIT_FAILURE);
}
areas = calloc(channels, sizeof(snd_pcm_channel_area_t));
if (areas == NULL) {
printf("No enough memory\n");
exit(EXIT_FAILURE);
}
for (chn = 0; chn < channels; chn++) {
areas[chn].addr = samples;
areas[chn].first = chn * snd_pcm_format_physical_width(format);
areas[chn].step = channels * snd_pcm_format_physical_width(format);
}
err = transfer_methods[method].transfer_loop(handle, samples, areas);
if (err < 0)
printf("Transfer failed: %s\n", snd_strerror(err));
free(areas);
free(samples);
snd_pcm_close(handle);
return 0;
}
This is in essence the demo from Alsa with a few bits of musicality chucked in.
Notably the use of numeric constraints to limit to pleasant range, multi-step offsets linked an overall counter to give us motion and loops, etc.
It's basic stuff, chuck in a Pi here and a > there and you're off. You want to be looking at the second half of the function generate_sine, Feel free to hack, I added notes.
FEEDBACK
I'm interested in any random output it may have on your kindles that I can fix up.
INSTALL
Copy it to your device somewhere.
RUNNING THE APPLICATION
Launch it via SSH, Launchpad or whatever.
EXAMPLE simple usage: ./tones
EXAMPLE advanced usage: ./tones -f 4000 -D hw -c 2 -v -r16000 -mdirect_write
Usage: tones [OPTION]...
-h,--help help
-D,--device playback device
-r,--rate stream rate in Hz
-c,--channels count of channels in stream
-f,--frequency sine wave frequency in Hz
-b,--buffer ring buffer size in us
-p,--period period size in us
-m,--method transfer method
-o,--format sample format
-v,--verbose show the PCM setup parameters
-n,--noresample do not resample
-e,--pevent enable poll event after each period
UNINSTALL
Just delete the file.
OTHER THOUGHTS
This tones application is just a simple demo, fiddling about with sines via the alsa interface to show we now can do direct access, and how to have a play, don't expect "Kuitar Hero", "Kubase" or "Kuake" just yet. It's a silly demo.
The final overall aim of the "Kradle" project is simple:
Give us enough information to implement sound support in other applications,
Thanks.
MANY THANKS TO GM FOR BUILDING A VERSION THAT WORKS ON ALL SOUND ENABLED KINDLES: (ATTACHED)
Last edited by twobob; 01-30-2013 at 08:12 AM.
Reason: Added some content information and more up to date info on the thread.
|