diff options
Diffstat (limited to 'examples/alsa_timed_audio/alsa.c')
-rw-r--r-- | examples/alsa_timed_audio/alsa.c | 498 |
1 files changed, 498 insertions, 0 deletions
diff --git a/examples/alsa_timed_audio/alsa.c b/examples/alsa_timed_audio/alsa.c new file mode 100644 index 00000000..6b8a7fd0 --- /dev/null +++ b/examples/alsa_timed_audio/alsa.c @@ -0,0 +1,498 @@ +/****************************************************************************** + + Copyright (c) 2018, Intel Corporation + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the Intel Corporation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ + +#include <audio_output.h> +#include <audio_input.h> +#include <stack.h> +#include <init.h> + +#include <asoundlib.h> + +#include <limits.h> + +void alsa_init( void ) _isaudk_io_init; + +#define DEFAULT_ALSA_DEVICE "hw:0,0" + +#define ALSA_PERIOD_TIME ( 20000 /* microsec */) +#define ALSA_PERIODS_PER_BUFFER ( 15 ) /* ALSA always allocates 1 extra */ + +typedef enum +{ + ISAUDK_ALSA_INTERNAL_ERROR, + ISAUDK_ALSA_FORMAT_ERROR, + ISAUDK_ALSA_OK, +} isaudk_alsa_error_t; + +struct alsa_context +{ + snd_pcm_t *alsa_handle; + snd_pcm_status_t *alsa_status; + char *devname; + isaudk_alsa_error_t last_error; + const char *last_error_func; + unsigned short last_error_line; +}; + +struct isaudk_input_context +{ + struct alsa_context ctx; +}; + +struct isaudk_output_context +{ + struct alsa_context ctx; +}; + +const struct isaudk_output *default_alsa_output; + +const struct isaudk_output *isaudk_get_default_alsa_output() +{ + return default_alsa_output; +} + +const struct isaudk_input *default_alsa_input; + +const struct isaudk_input *isaudk_get_default_alsa_input() +{ + return default_alsa_input; +} + +static inline void +alsa_no_error( struct alsa_context *ctx ) +{ + ctx->last_error = ISAUDK_ALSA_OK; + ctx->last_error_func = ""; + ctx->last_error_line = 0; +} + +static inline void +_alsa_set_error( struct alsa_context *ctx, isaudk_alsa_error_t error, + const char *func, unsigned short line ) +{ + ctx->last_error = error; + ctx->last_error_func = func; + ctx->last_error_line = line; +} + +#define alsa_set_error( ctx, error ) \ + _alsa_set_error( ctx, error, __FUNCTION__, __LINE__ ); + +#define CHECK_ALSA_RESULT( err_code, ctx ) \ + if( err < 0 ) \ + { \ + alsa_set_error( ctx, err_code ); \ + return false; \ + } + +static bool +_alsa_set_audio_param( struct alsa_context *ctx, + struct isaudk_format *format, + unsigned rate ) +{ + snd_pcm_hw_params_t *hwparams; + snd_pcm_sw_params_t *swparams; + snd_pcm_audio_tstamp_config_t tstamp_config; + snd_pcm_format_t alsa_format; + + unsigned int period_time; + unsigned int periods_per_buffer; + int dir; + int err; + + if( format == NULL ) + { + alsa_set_error( ctx, ISAUDK_ALSA_FORMAT_ERROR ); + return false; + } + switch( format->encoding ) + { + default: + // Unsupported format request + alsa_set_error( ctx, ISAUDK_ALSA_FORMAT_ERROR ); + return false; + case ISAUDK_ENC_PS16: + alsa_format = SND_PCM_FORMAT_S16; + break; + } + + snd_pcm_hw_params_alloca( &hwparams ); + snd_pcm_sw_params_alloca( &swparams ); + + err = snd_pcm_hw_params_any( ctx->alsa_handle, hwparams ); + CHECK_ALSA_RESULT( ISAUDK_ALSA_INTERNAL_ERROR, ctx ); + + err = snd_pcm_hw_params_set_rate_resample( ctx->alsa_handle, hwparams, + 0 ); + CHECK_ALSA_RESULT( ISAUDK_ALSA_INTERNAL_ERROR, ctx ); + + err = snd_pcm_hw_params_set_access( ctx->alsa_handle, hwparams, + SND_PCM_ACCESS_MMAP_INTERLEAVED ); + CHECK_ALSA_RESULT( ISAUDK_ALSA_INTERNAL_ERROR, ctx ); + + err = snd_pcm_hw_params_set_format( ctx->alsa_handle, hwparams, + alsa_format ); + CHECK_ALSA_RESULT( ISAUDK_ALSA_FORMAT_ERROR, ctx ); + + err = snd_pcm_hw_params_set_channels + ( ctx->alsa_handle, hwparams, format->channels ); + CHECK_ALSA_RESULT( ISAUDK_ALSA_FORMAT_ERROR, ctx ); + + err = snd_pcm_hw_params_set_rate + ( ctx->alsa_handle, hwparams, rate*ISAUDK_RATE_MULTIPLIER, 0 ); + CHECK_ALSA_RESULT( ISAUDK_ALSA_FORMAT_ERROR, ctx ); + + dir = 1; + period_time = ALSA_PERIOD_TIME; + err = snd_pcm_hw_params_set_period_time_near + ( ctx->alsa_handle, hwparams, &period_time, &dir ); + CHECK_ALSA_RESULT( ISAUDK_ALSA_INTERNAL_ERROR, ctx ); + + dir = 1; + periods_per_buffer = ALSA_PERIODS_PER_BUFFER; + err = snd_pcm_hw_params_set_periods_near + ( ctx->alsa_handle, hwparams, &periods_per_buffer, &dir ); + CHECK_ALSA_RESULT( ISAUDK_ALSA_INTERNAL_ERROR, ctx ); + + err = snd_pcm_hw_params( ctx->alsa_handle, hwparams ); + + err = snd_pcm_sw_params_current( ctx->alsa_handle, swparams ); + CHECK_ALSA_RESULT( ISAUDK_ALSA_INTERNAL_ERROR, ctx ); + + err = snd_pcm_sw_params_set_tstamp_mode + ( ctx->alsa_handle, swparams, SND_PCM_TSTAMP_ENABLE ); + CHECK_ALSA_RESULT( ISAUDK_ALSA_INTERNAL_ERROR, ctx ); + + err = snd_pcm_sw_params_set_tstamp_type + ( ctx->alsa_handle, swparams, + // SND_PCM_TSTAMP_TYPE_GETTIMEOFDAY ); + SND_PCM_TSTAMP_TYPE_MONOTONIC_RAW ); + CHECK_ALSA_RESULT( ISAUDK_ALSA_INTERNAL_ERROR, ctx ); + + tstamp_config.type_requested = 5; + tstamp_config.report_delay = 0; + snd_pcm_status_set_audio_htstamp_config + ( ctx->alsa_status, &tstamp_config ); + + err = snd_pcm_sw_params_set_start_threshold + ( ctx->alsa_handle, swparams, INT_MAX ); + CHECK_ALSA_RESULT( ISAUDK_ALSA_INTERNAL_ERROR, ctx ); + + err = snd_pcm_sw_params( ctx->alsa_handle, swparams ); + CHECK_ALSA_RESULT( ISAUDK_ALSA_INTERNAL_ERROR, ctx ); + + alsa_no_error( ctx ); + return true; +} + +static bool +alsa_set_audio_output_param( struct isaudk_output_context *ctx, + struct isaudk_format *format, + unsigned rate ) +{ + int err; + + err = snd_pcm_open( &ctx->ctx.alsa_handle, ctx->ctx.devname, + SND_PCM_STREAM_PLAYBACK, 0 ); + CHECK_ALSA_RESULT( ISAUDK_ALSA_INTERNAL_ERROR, &ctx->ctx ); + + return _alsa_set_audio_param( &ctx->ctx, format, rate ); +} + +static bool +alsa_set_audio_input_param( struct isaudk_input_context *ctx, + struct isaudk_format *format, + unsigned rate ) +{ + int err; + + err = snd_pcm_open( &ctx->ctx.alsa_handle, ctx->ctx.devname, + SND_PCM_STREAM_CAPTURE, 0 ); + CHECK_ALSA_RESULT( ISAUDK_ALSA_INTERNAL_ERROR, &ctx->ctx ); + + return _alsa_set_audio_param( &ctx->ctx, format, rate ); +} + +#define NSEC_PER_SEC (1000000000) + +#define snd_htstamp_to_systime( ts ) \ + ((struct isaudk_system_time) \ + { .time = (ts)->tv_sec*NSEC_PER_SEC*1ULL + (ts)->tv_nsec }) + +#define snd_htstamp_to_audiotime( ts ) \ + ((struct isaudk_audio_time) \ + { .time = (ts)->tv_sec*NSEC_PER_SEC*1ULL + (ts)->tv_nsec }) + +bool _alsa_get_cross_tstamp( struct alsa_context *ctx, + struct isaudk_cross_time *time ) +{ + int err; + snd_htimestamp_t sys_ts, audio_ts; + snd_pcm_state_t state; + + err = snd_pcm_status( ctx->alsa_handle, ctx->alsa_status ); + CHECK_ALSA_RESULT( ISAUDK_ALSA_INTERNAL_ERROR, ctx ); + + state = snd_pcm_status_get_state( ctx->alsa_status ); + switch( state ) + { + // These states will give valid results + case SND_PCM_STATE_RUNNING: + case SND_PCM_STATE_DRAINING: + break; + // Other states will not + default: + return false; + } + + snd_pcm_status_get_htstamp( ctx->alsa_status, &sys_ts ); + snd_pcm_status_get_audio_htstamp( ctx->alsa_status, &audio_ts ); + time->sys = snd_htstamp_to_systime( &sys_ts ); + time->dev = snd_htstamp_to_audiotime( &audio_ts ); + + alsa_no_error( ctx ); + return true; +} + +bool _alsa_start( struct alsa_context *ctx ) +{ + int err; + + alsa_no_error( ctx ); + err = snd_pcm_start( ctx->alsa_handle ); + CHECK_ALSA_RESULT( ISAUDK_ALSA_INTERNAL_ERROR, ctx ); + + alsa_no_error( ctx ); + return true; +} + +bool _alsa_stop( struct alsa_context *ctx ) +{ + int err; + + err = snd_pcm_drain( ctx->alsa_handle ); + CHECK_ALSA_RESULT( ISAUDK_ALSA_INTERNAL_ERROR, ctx ); + + err = snd_pcm_close( ctx->alsa_handle ); + CHECK_ALSA_RESULT( ISAUDK_ALSA_INTERNAL_ERROR, ctx ); + + alsa_no_error( ctx ); + return true; +} + +bool alsa_get_output_cross_tstamp( struct isaudk_output_context *ctx, + struct isaudk_cross_time *time ) +{ + return _alsa_get_cross_tstamp( &ctx->ctx, time ); +} + +bool alsa_start_output( isaudk_output_context_t ctx ) +{ + return _alsa_start( &ctx->ctx ); +} + +bool alsa_stop_output( isaudk_output_context_t ctx ) +{ + return _alsa_stop( &ctx->ctx ); +} + +bool alsa_queue_output_buffer( isaudk_output_context_t ctx, + void *buffer, unsigned *count ) +{ + snd_pcm_sframes_t err; + + err = snd_pcm_mmap_writei( ctx->ctx.alsa_handle, buffer, *count ); + CHECK_ALSA_RESULT( ISAUDK_ALSA_INTERNAL_ERROR, &ctx->ctx ); + + *count = err; + alsa_no_error( &ctx->ctx ); + return true; +} + +static struct isaudk_output_fn default_alsa_output_fn = +{ + .set_audio_param = alsa_set_audio_output_param, + .get_cross_tstamp = alsa_get_output_cross_tstamp, + .start = alsa_start_output, + .stop = alsa_stop_output, + .queue_output_buffer = alsa_queue_output_buffer, +}; + +void alsa_delete_output( struct isaudk_output *output ) +{ + if( output == NULL ) + return; + else + { + if( output->ctx != NULL ) + { + if( output->ctx->ctx.devname != NULL ) + free(( void * ) output->ctx->ctx.devname ); + snd_pcm_status_free( output->ctx->ctx.alsa_status ); + free(( void * ) output->ctx ); + } + free(( void * ) output ); + } +} + +struct isaudk_output * +alsa_open_output( char *devname ) +{ + struct isaudk_output *output; + int err; + + output = (__typeof__( output )) malloc(( size_t ) sizeof( *output )); + if( output == NULL ) + goto error; + + output->ctx = (__typeof__( output->ctx )) + malloc(( size_t ) sizeof( *output->ctx )); + if( output->ctx == NULL ) + goto error; + + output->ctx->ctx.devname = (__typeof__( output->ctx->ctx.devname )) + malloc(( size_t ) sizeof( *output->ctx->ctx.devname )* + strlen( devname ) + 1 ); + if( output->ctx->ctx.devname == NULL ) + goto error; + strncpy( output->ctx->ctx.devname, DEFAULT_ALSA_DEVICE, 7 ); + + err = snd_pcm_status_malloc( &output->ctx->ctx.alsa_status ); + if( err == 0 ) + { + alsa_no_error( &output->ctx->ctx ); + output->fn = &default_alsa_output_fn; + return output; + } + +error: + alsa_delete_output( output ); + return NULL; +} + +bool alsa_get_input_cross_tstamp( struct isaudk_input_context *ctx, + struct isaudk_cross_time *time ) +{ + return _alsa_get_cross_tstamp( &ctx->ctx, time ); +} + +bool alsa_start_input( isaudk_input_context_t ctx ) +{ + return _alsa_start( &ctx->ctx ); +} + +bool alsa_stop_input( isaudk_input_context_t ctx ) +{ + return _alsa_stop( &ctx->ctx ); +} + +bool alsa_read_input_buffer( isaudk_input_context_t ctx, + void *buffer, unsigned *count ) +{ + snd_pcm_sframes_t err; + + err = snd_pcm_mmap_readi( ctx->ctx.alsa_handle, buffer, *count ); + CHECK_ALSA_RESULT( ISAUDK_ALSA_INTERNAL_ERROR, &ctx->ctx ); + + *count = err; + alsa_no_error( &ctx->ctx ); + return true; +} + +static struct isaudk_input_fn default_alsa_input_fn = +{ + .set_audio_param = alsa_set_audio_input_param, + .get_cross_tstamp = alsa_get_input_cross_tstamp, + .start = alsa_start_input, + .stop = alsa_stop_input, + .read_input_buffer = alsa_read_input_buffer, +}; + +void alsa_delete_input( struct isaudk_input *input ) +{ + if( input == NULL ) + return; + else + { + if( input->ctx != NULL ) + { + if( input->ctx->ctx.devname != NULL ) + free(( void * ) input->ctx->ctx.devname ); + snd_pcm_status_free( input->ctx->ctx.alsa_status ); + free(( void * ) input->ctx ); + } + free(( void * ) input ); + } +} + +struct isaudk_input * +alsa_open_input( char *devname ) +{ + struct isaudk_input *input; + int err; + + input = (__typeof__( input )) malloc(( size_t ) sizeof( *input )); + if( input == NULL ) + goto error; + + input->ctx = (__typeof__( input->ctx )) + malloc(( size_t ) sizeof( *input->ctx )); + if( input->ctx == NULL ) + goto error; + + input->ctx->ctx.devname = (__typeof__( input->ctx->ctx.devname )) + malloc(( size_t ) sizeof( *input->ctx->ctx.devname )* + strlen( devname ) + 1 ); + if( input->ctx->ctx.devname == NULL ) + goto error; + strncpy( input->ctx->ctx.devname, DEFAULT_ALSA_DEVICE, 7 ); + + err = snd_pcm_status_malloc( &input->ctx->ctx.alsa_status ); + if( err == 0 ) + { + alsa_no_error( &input->ctx->ctx ); + input->fn = &default_alsa_input_fn; + return input; + } + +error: + alsa_delete_input( input ); + return NULL; +} + +void +alsa_init( void ) +{ + default_alsa_output = alsa_open_output( DEFAULT_ALSA_DEVICE ); + default_alsa_input = alsa_open_input( DEFAULT_ALSA_DEVICE ); +} |