374 lines
9.5 KiB
C
374 lines
9.5 KiB
C
//Copyright 2015-2020 <>< Charles Lohr under the MIT/x11, NewBSD or ColorChord License. You choose.
|
|
|
|
#include "CNFA.h"
|
|
#include "os_generic.h"
|
|
#include <alsa/asoundlib.h>
|
|
#include <string.h>
|
|
|
|
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 );
|
|
|