//Copyright 2015-2020 <>< Charles Lohr under the MIT/x11, NewBSD or ColorChord License. You choose. #include "CNFA.h" #include "os_generic.h" #include #include struct CNFADriverAlsa { void (*CloseFn)( void * object ); int (*StateFn)( void * object ); CNFACBType callback; short channelsPlay; short channelsRec; int spsPlay; int spsRec; void * opaque; char * devRec; char * devPlay; snd_pcm_uframes_t bufsize; og_thread_t threadPlay; og_thread_t threadRec; snd_pcm_t *playback_handle; snd_pcm_t *record_handle; char playing; char recording; }; int CNFAStateAlsa( void * v ) { struct CNFADriverAlsa * r = (struct CNFADriverAlsa *)v; return ((r->playing)?2:0) | ((r->recording)?1:0); } void CloseCNFAAlsa( void * v ) { struct CNFADriverAlsa * r = (struct CNFADriverAlsa *)v; if( r ) { if( r->playback_handle ) snd_pcm_close (r->playback_handle); if( r->record_handle ) snd_pcm_close (r->record_handle); if( r->threadPlay ) OGJoinThread( r->threadPlay ); if( r->threadRec ) OGJoinThread( r->threadRec ); OGUSleep(2000); if( r->devRec ) free( r->devRec ); if( r->devPlay ) free( r->devPlay ); free( r ); } } static int SetHWParams( snd_pcm_t * handle, int * samplerate, short * channels, snd_pcm_uframes_t * bufsize, struct CNFADriverAlsa * a ) { int err; int bufs; int dir; snd_pcm_hw_params_t *hw_params; if ((err = snd_pcm_hw_params_malloc (&hw_params)) < 0) { fprintf (stderr, "cannot allocate hardware parameter structure (%s)\n", snd_strerror (err)); return -1; } if ((err = snd_pcm_hw_params_any (handle, hw_params)) < 0) { fprintf (stderr, "cannot initialize hardware parameter structure (%s)\n", snd_strerror (err)); goto fail; } if ((err = snd_pcm_hw_params_set_access (handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { fprintf (stderr, "cannot set access type (%s)\n", snd_strerror (err)); goto fail; } if ((err = snd_pcm_hw_params_set_format (handle, hw_params, SND_PCM_FORMAT_S16_LE )) < 0) { fprintf (stderr, "cannot set sample format (%s)\n", snd_strerror (err)); goto fail; } if ((err = snd_pcm_hw_params_set_rate_near (handle, hw_params, (unsigned int*)samplerate, 0)) < 0) { fprintf (stderr, "cannot set sample rate (%s)\n", snd_strerror (err)); goto fail; } if ((err = snd_pcm_hw_params_set_channels (handle, hw_params, *channels)) < 0) { fprintf (stderr, "cannot set channel count (%s)\n", snd_strerror (err)); goto fail; } dir = 0; if( (err = snd_pcm_hw_params_set_period_size_near(handle, hw_params, bufsize, &dir)) < 0 ) { fprintf( stderr, "cannot set period size. (%s)\n", snd_strerror(err) ); goto fail; } //NOTE: This step is critical for low-latency sound. bufs = *bufsize*3; if( (err = snd_pcm_hw_params_set_buffer_size(handle, hw_params, bufs)) < 0 ) { fprintf( stderr, "cannot set snd_pcm_hw_params_set_buffer_size size. (%s)\n", snd_strerror(err) ); goto fail; } if ((err = snd_pcm_hw_params (handle, hw_params)) < 0) { fprintf (stderr, "cannot set parameters (%s)\n", snd_strerror (err)); goto fail; } snd_pcm_hw_params_free (hw_params); return 0; fail: snd_pcm_hw_params_free (hw_params); return -2; } static int SetSWParams( struct CNFADriverAlsa * d, snd_pcm_t * handle, int isrec ) { snd_pcm_sw_params_t *sw_params; int err; //Time for software parameters: if( !isrec ) { if ((err = snd_pcm_sw_params_malloc (&sw_params)) < 0) { fprintf (stderr, "cannot allocate software parameters structure (%s)\n", snd_strerror (err)); goto failhard; } if ((err = snd_pcm_sw_params_current (handle, sw_params)) < 0) { fprintf (stderr, "cannot initialize software parameters structure (%s) (%p)\n", snd_strerror (err), handle); goto fail; } int buffer_size = d->bufsize*3; int period_size = d->bufsize; printf( "PERIOD: %d BUFFER: %d\n", period_size, buffer_size ); if ((err = snd_pcm_sw_params_set_avail_min (handle, sw_params, period_size )) < 0) { fprintf (stderr, "cannot set minimum available count (%s)\n", snd_strerror (err)); goto fail; } //if ((err = snd_pcm_sw_params_set_stop_threshold(handle, sw_params, 512 )) < 0) { // fprintf (stderr, "cannot set minimum available count (%s)\n", // snd_strerror (err)); // goto fail; //} if ((err = snd_pcm_sw_params_set_start_threshold(handle, sw_params, buffer_size - period_size )) < 0) { fprintf (stderr, "cannot set minimum available count (%s)\n", snd_strerror (err)); goto fail; } if ((err = snd_pcm_sw_params (handle, sw_params)) < 0) { fprintf (stderr, "cannot set software parameters (%s)\n", snd_strerror (err)); goto fail; } } if ((err = snd_pcm_prepare (handle)) < 0) { fprintf (stderr, "cannot prepare audio interface for use (%s)\n", snd_strerror (err)); goto fail; } return 0; fail: if( !isrec ) { snd_pcm_sw_params_free (sw_params); } failhard: return -1; } void * RecThread( void * v ) { struct CNFADriverAlsa * r = (struct CNFADriverAlsa *)v; short samples[r->bufsize * r->channelsRec]; snd_pcm_start(r->record_handle); do { int err = snd_pcm_readi( r->record_handle, samples, r->bufsize ); if( err < 0 ) { fprintf( stderr, "Warning: ALSA Recording Failed\n" ); break; } if( err != r->bufsize ) { fprintf( stderr, "Warning: ALSA Recording Underflow\n" ); } r->recording = 1; r->callback( (struct CNFADriver *)r, 0, samples, 0, err ); } while( 1 ); r->recording = 0; fprintf( stderr, "ALSA Recording Stopped\n" ); return 0; } void * PlayThread( void * v ) { struct CNFADriverAlsa * r = (struct CNFADriverAlsa *)v; short samples[r->bufsize * r->channelsPlay]; int err; //int total_avail = snd_pcm_avail(r->playback_handle); snd_pcm_start(r->playback_handle); r->callback( (struct CNFADriver *)r, samples, 0, r->bufsize, 0 ); err = snd_pcm_writei(r->playback_handle, samples, r->bufsize); while( err >= 0 ) { // int avail = snd_pcm_avail(r->playback_handle); // printf( "avail: %d\n", avail ); r->callback( (struct CNFADriver *)r, samples, 0, r->bufsize, 0 ); err = snd_pcm_writei(r->playback_handle, samples, r->bufsize); if( err != r->bufsize ) { fprintf( stderr, "Warning: ALSA Playback Overflow\n" ); } r->playing = 1; } r->playing = 0; fprintf( stderr, "ALSA Playback Stopped\n" ); return 0; } static struct CNFADriverAlsa * InitALSA( struct CNFADriverAlsa * r ) { printf( "CNFA Alsa Init %p %p (%d %d) %d %d\n", r->playback_handle, r->record_handle, r->spsPlay, r->spsRec, r->channelsPlay, r->channelsRec ); int err; if( r->channelsPlay ) { if ((err = snd_pcm_open (&r->playback_handle, r->devPlay?r->devPlay:"default", SND_PCM_STREAM_PLAYBACK, 0)) < 0) { fprintf (stderr, "cannot open output audio device (%s)\n", snd_strerror (err)); goto fail; } } if( r->channelsRec ) { if ((err = snd_pcm_open (&r->record_handle, r->devRec?r->devRec:"default", SND_PCM_STREAM_CAPTURE, 0)) < 0) { fprintf (stderr, "cannot open input audio device (%s)\n", snd_strerror (err)); goto fail; } } printf( "%p %p\n", r->playback_handle, r->record_handle ); if( r->playback_handle ) { if( SetHWParams( r->playback_handle, &r->spsPlay, &r->channelsPlay, &r->bufsize, r ) < 0 ) goto fail; if( SetSWParams( r, r->playback_handle, 0 ) < 0 ) goto fail; } if( r->record_handle ) { if( SetHWParams( r->record_handle, &r->spsRec, &r->channelsRec, &r->bufsize, r ) < 0 ) goto fail; if( SetSWParams( r, r->record_handle, 1 ) < 0 ) goto fail; } #if 0 if( r->playback_handle ) { snd_async_handler_t *pcm_callback; //Handle automatically cleaned up when stream closed. err = snd_async_add_pcm_handler(&pcm_callback, r->playback_handle, playback_callback, r); if(err < 0) { printf("Playback callback handler error: %s\n", snd_strerror(err)); } } if( r->record_handle ) { snd_async_handler_t *pcm_callback; //Handle automatically cleaned up when stream closed. err = snd_async_add_pcm_handler(&pcm_callback, r->record_handle, record_callback, r); if(err < 0) { printf("Record callback handler error: %s\n", snd_strerror(err)); } } #endif if( r->playback_handle && r->record_handle ) { err = snd_pcm_link ( r->playback_handle, r->record_handle ); if(err < 0) { printf("snd_pcm_link error: %s\n", snd_strerror(err)); } } if( r->playback_handle ) { r->threadPlay = OGCreateThread( PlayThread, r ); } if( r->record_handle ) { r->threadRec = OGCreateThread( RecThread, r ); } printf( "CNFA Alsa Init Out -> %p %p (%d %d) %d %d\n", r->playback_handle, r->record_handle, r->spsPlay, r->spsRec, r->channelsPlay, r->channelsRec ); return r; fail: if( r ) { if( r->playback_handle ) snd_pcm_close (r->playback_handle); if( r->record_handle ) snd_pcm_close (r->record_handle); free( r ); } fprintf( stderr, "Error: ALSA failed to start.\n" ); return 0; } void * InitALSADriver( CNFACBType cb, const char * your_name, int reqSPSPlay, int reqSPSRec, int reqChannelsPlay, int reqChannelsRec, int sugBufferSize, const char * outputSelect, const char * inputSelect, void * opaque ) { struct CNFADriverAlsa * r = (struct CNFADriverAlsa *)malloc( sizeof( struct CNFADriverAlsa ) ); r->CloseFn = CloseCNFAAlsa; r->StateFn = CNFAStateAlsa; r->callback = cb; r->opaque = opaque; r->spsPlay = reqSPSPlay; r->spsRec = reqSPSRec; r->channelsPlay = reqChannelsPlay; r->channelsRec = reqChannelsRec; r->devRec = (inputSelect)?strdup(inputSelect):0; r->devPlay = (outputSelect)?strdup(outputSelect):0; r->playback_handle = 0; r->record_handle = 0; r->bufsize = sugBufferSize; return InitALSA(r); } REGISTER_CNFA( ALSA, 10, "ALSA", InitALSADriver );