diff --git a/.github/workflows/build-cnfa.yml b/.github/workflows/build-cnfa.yml index 2888cfa..8f74ef9 100644 --- a/.github/workflows/build-cnfa.yml +++ b/.github/workflows/build-cnfa.yml @@ -20,7 +20,7 @@ jobs: vim gettext-base libasound2-dev - libpulse2-dev + libpulse-dev - name: Check out respository code uses: actions/checkout@v3 with: @@ -33,7 +33,7 @@ jobs: # run: | # git_hash=$(git rev-parse --short "$GITHUB_SHA") # git commit -m "Updating CNFA_sf.h to ${git_hash}" CNFA_sf.h - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: cnfa_sf path: CNFA_sf.h @@ -51,7 +51,7 @@ jobs: rm CNFA_sf.h make CNFA_sf.h make - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: cnfa_sf path: CNFA_sf.h diff --git a/CNFA.h b/CNFA.h index 92d9898..28503ab 100644 --- a/CNFA.h +++ b/CNFA.h @@ -99,6 +99,7 @@ void RegCNFADriver( int priority, const char * name, CNFAInitFn * fn ); #define CNFA_SUN 1 #elif defined(__linux) || defined(__linux__) || defined(linux) || defined(__LINUX__) #define CNFA_LINUX 1 +#define CNFA_PULSE 1 #endif #if defined(PULSEAUDIO) @@ -115,11 +116,11 @@ void RegCNFADriver( int priority, const char * name, CNFAInitFn * fn ); #include "CNFA.c" #include "CNFA_null.c" #if CNFA_WINDOWS - #include "CNFA_winmm.c" - #include // This probably won't work on pre-NT systems - #if VER_PRODUCTBUILD >= 7601 - #include "CNFA_wasapi.c" - #endif +#include "CNFA_winmm.c" +#include // This probably won't work on pre-NT systems +#if VER_PRODUCTBUILD >= 7601 +#include "CNFA_wasapi.c" +#endif #elif CNFA_ANDROID #include "CNFA_android.c" #elif CNFA_SUN diff --git a/CNFA_alsa.c b/CNFA_alsa.c index 8326da3..b56e4e4 100644 --- a/CNFA_alsa.c +++ b/CNFA_alsa.c @@ -5,6 +5,8 @@ #include #include +#define ALSA_PRINT_PREFIX "[CNFA][ALSA]: " + struct CNFADriverAlsa { void (*CloseFn)( void * object ); @@ -64,37 +66,37 @@ static int SetHWParams( snd_pcm_t * handle, int * samplerate, short * channels, 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", + fprintf (stderr, ALSA_PRINT_PREFIX"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", + fprintf (stderr, ALSA_PRINT_PREFIX"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", + fprintf (stderr, ALSA_PRINT_PREFIX"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", + fprintf (stderr, ALSA_PRINT_PREFIX"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", + fprintf (stderr, ALSA_PRINT_PREFIX"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", + fprintf (stderr, ALSA_PRINT_PREFIX"cannot set channel count (%s)\n", snd_strerror (err)); goto fail; } @@ -102,7 +104,7 @@ static int SetHWParams( snd_pcm_t * handle, int * samplerate, short * channels, 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", + fprintf( stderr, ALSA_PRINT_PREFIX"cannot set period size. (%s)\n", snd_strerror(err) ); goto fail; } @@ -111,14 +113,14 @@ static int SetHWParams( snd_pcm_t * handle, int * samplerate, short * channels, 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", + fprintf( stderr, ALSA_PRINT_PREFIX"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", + fprintf (stderr, ALSA_PRINT_PREFIX"cannot set parameters (%s)\n", snd_strerror (err)); goto fail; } @@ -140,37 +142,37 @@ static int SetSWParams( struct CNFADriverAlsa * d, snd_pcm_t * handle, int isrec if( !isrec ) { if ((err = snd_pcm_sw_params_malloc (&sw_params)) < 0) { - fprintf (stderr, "cannot allocate software parameters structure (%s)\n", + fprintf (stderr, ALSA_PRINT_PREFIX"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", + fprintf (stderr, ALSA_PRINT_PREFIX"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 ); + printf( ALSA_PRINT_PREFIX"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", + fprintf (stderr, ALSA_PRINT_PREFIX"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", + // fprintf (stderr, ALSA_PRINT_PREFIX"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", + fprintf (stderr, ALSA_PRINT_PREFIX"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", + fprintf (stderr, ALSA_PRINT_PREFIX"cannot set software parameters (%s)\n", snd_strerror (err)); goto fail; } @@ -180,7 +182,7 @@ static int SetSWParams( struct CNFADriverAlsa * d, snd_pcm_t * handle, int isrec } if ((err = snd_pcm_prepare (handle)) < 0) { - fprintf (stderr, "cannot prepare audio interface for use (%s)\n", + fprintf (stderr, ALSA_PRINT_PREFIX"cannot prepare audio interface for use (%s)\n", snd_strerror (err)); goto fail; } @@ -205,18 +207,18 @@ void * RecThread( void * v ) int err = snd_pcm_readi( r->record_handle, samples, r->bufsize ); if( err < 0 ) { - fprintf( stderr, "Warning: ALSA Recording Failed\n" ); + fprintf( stderr, ALSA_PRINT_PREFIX"Warning: ALSA Recording Failed\n" ); break; } if( err != r->bufsize ) { - fprintf( stderr, "Warning: ALSA Recording Underflow\n" ); + fprintf( stderr, ALSA_PRINT_PREFIX"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" ); + fprintf( stderr, ALSA_PRINT_PREFIX"ALSA Recording Stopped\n" ); return 0; } @@ -234,29 +236,29 @@ void * PlayThread( void * v ) while( err >= 0 ) { // int avail = snd_pcm_avail(r->playback_handle); - // printf( "avail: %d\n", avail ); + // printf( ALSA_PRINT_PREFIX"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" ); + fprintf( stderr, ALSA_PRINT_PREFIX"Warning: ALSA Playback Overflow\n" ); } r->playing = 1; } r->playing = 0; - fprintf( stderr, "ALSA Playback Stopped\n" ); + fprintf( stderr, ALSA_PRINT_PREFIX"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 ); + printf( ALSA_PRINT_PREFIX"initialized %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", + if ((err = snd_pcm_open (&r->playback_handle, r->devPlay?r->devPlay:"hw:0,0", SND_PCM_STREAM_PLAYBACK, 0)) < 0) { + fprintf (stderr, ALSA_PRINT_PREFIX"cannot open output audio device (%s)\n", snd_strerror (err)); goto fail; } @@ -264,15 +266,13 @@ static struct CNFADriverAlsa * InitALSA( struct CNFADriverAlsa * r ) 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", + if ((err = snd_pcm_open (&r->record_handle, r->devRec?r->devRec:"hw:0,0", SND_PCM_STREAM_CAPTURE, 0)) < 0) { + fprintf (stderr, ALSA_PRINT_PREFIX"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 ) @@ -297,7 +297,7 @@ static struct CNFADriverAlsa * InitALSA( struct CNFADriverAlsa * r ) 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)); + printf(ALSA_PRINT_PREFIX"Playback callback handler error: %s\n", snd_strerror(err)); } } @@ -308,7 +308,7 @@ static struct CNFADriverAlsa * InitALSA( struct CNFADriverAlsa * r ) 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)); + printf(ALSA_PRINT_PREFIX"Record callback handler error: %s\n", snd_strerror(err)); } } #endif @@ -318,7 +318,7 @@ static struct CNFADriverAlsa * InitALSA( struct CNFADriverAlsa * r ) err = snd_pcm_link ( r->playback_handle, r->record_handle ); if(err < 0) { - printf("snd_pcm_link error: %s\n", snd_strerror(err)); + printf(ALSA_PRINT_PREFIX"snd_pcm_link error: %s\n", snd_strerror(err)); } } @@ -332,7 +332,7 @@ static struct CNFADriverAlsa * InitALSA( struct CNFADriverAlsa * r ) 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 ); + printf( ALSA_PRINT_PREFIX"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; @@ -343,7 +343,7 @@ static struct CNFADriverAlsa * InitALSA( struct CNFADriverAlsa * r ) if( r->record_handle ) snd_pcm_close (r->record_handle); free( r ); } - fprintf( stderr, "Error: ALSA failed to start.\n" ); + fprintf( stderr, ALSA_PRINT_PREFIX"ALSA failed to start.\n" ); return 0; } diff --git a/CNFA_pulse.c b/CNFA_pulse.c index 2d4b60c..e5a98a0 100644 --- a/CNFA_pulse.c +++ b/CNFA_pulse.c @@ -13,6 +13,8 @@ #include #include +#define PULSE_PRINT_PREFIX "[CNFA][Pulse]: " + #define BUFFERSETS 3 @@ -112,7 +114,7 @@ static void stream_record_cb(pa_stream *s, size_t length, void *userdata) uint16_t * bufr; if (pa_stream_peek(r->rec, (const void**)&bufr, &length) < 0) { - fprintf(stderr, ("pa_stream_peek() failed: %s\n"), pa_strerror(pa_context_errno(r->pa_ctx))); + fprintf(stderr, (PULSE_PRINT_PREFIX"pa_stream_peek() failed: %s\n"), pa_strerror(pa_context_errno(r->pa_ctx))); return; } @@ -127,7 +129,7 @@ static void stream_record_cb(pa_stream *s, size_t length, void *userdata) static void stream_underflow_cb(pa_stream *s, void *userdata) { - printf("underflow\n"); + printf(PULSE_PRINT_PREFIX"underflow\n"); } @@ -167,14 +169,14 @@ void * InitCNFAPulse( CNFACBType cb, const char * your_name, int reqSPSPlay, int r->pa_ml = pa_mainloop_new(); if( !r->pa_ml ) { - fprintf( stderr, "Failed to initialize pa_mainloop_new()\n" ); + fprintf( stderr, PULSE_PRINT_PREFIX"Failed to initialize pa_mainloop_new()\n" ); goto fail; } pa_mlapi = pa_mainloop_get_api(r->pa_ml); if( !pa_mlapi ) { - fprintf( stderr, "Failed to initialize pa_mainloop_get_api()\n" ); + fprintf( stderr, PULSE_PRINT_PREFIX"Failed to initialize pa_mainloop_get_api()\n" ); goto fail; } @@ -198,7 +200,7 @@ void * InitCNFAPulse( CNFACBType cb, const char * your_name, int reqSPSPlay, int r->rec = 0; r->buffer = sugBufferSize; - printf ("Pulse: from: [O/I] %s/%s (%s) / (%d,%d)x(%d,%d) (%d)\n", r->sourceNamePlay, r->sourceNameRec, title, r->spsPlay, r->spsRec, r->channelsPlay, r->channelsRec, r->buffer ); + printf (PULSE_PRINT_PREFIX"from: [O/I] %s/%s (%s) / (%d,%d)x(%d,%d) (%d)\n", r->sourceNamePlay, r->sourceNameRec, title, r->spsPlay, r->spsRec, r->channelsPlay, r->channelsRec, r->buffer ); memset( &ss, 0, sizeof( ss ) ); @@ -222,7 +224,7 @@ void * InitCNFAPulse( CNFACBType cb, const char * your_name, int reqSPSPlay, int if (!(r->play = pa_stream_new(r->pa_ctx, "Play", &ss, NULL))) { error = -3; //XXX ??? TODO - fprintf(stderr, __FILE__": pa_simple_new() failed: %s\n", pa_strerror(error)); + fprintf(stderr, PULSE_PRINT_PREFIX"pa_simple_new() failed: %s\n", pa_strerror(error)); goto fail; } @@ -275,7 +277,7 @@ void * InitCNFAPulse( CNFACBType cb, const char * your_name, int reqSPSPlay, int PA_STREAM_NOFLAGS, NULL, NULL ); if( ret < 0 ) { - fprintf(stderr, __FILE__": (PLAY) pa_stream_connect_playback() failed: %s\n", pa_strerror(ret)); + fprintf(stderr, PULSE_PRINT_PREFIX"(PLAY) pa_stream_connect_playback() failed: %s\n", pa_strerror(ret)); goto fail; } @@ -288,7 +290,7 @@ void * InitCNFAPulse( CNFACBType cb, const char * your_name, int reqSPSPlay, int if (!(r->rec = pa_stream_new(r->pa_ctx, "Record", &ss, NULL))) { error = -3; //XXX ??? TODO - fprintf(stderr, __FILE__": pa_simple_new() failed: %s\n", pa_strerror(error)); + fprintf(stderr, PULSE_PRINT_PREFIX"pa_simple_new() failed: %s\n", pa_strerror(error)); goto fail; } @@ -305,17 +307,14 @@ void * InitCNFAPulse( CNFACBType cb, const char * your_name, int reqSPSPlay, int // PA_STREAM_AUTO_TIMING_UPDATE // PA_STREAM_NOFLAGS ); - - printf( "PA REC RES: %d\n", ret ); - if( ret < 0 ) { - fprintf(stderr, __FILE__": (REC) pa_stream_connect_playback() failed: %s\n", pa_strerror(ret)); + fprintf(stderr, PULSE_PRINT_PREFIX"(REC) pa_stream_connect_playback() failed: %s\n", pa_strerror(ret)); goto fail; } } - printf( "Pulse initialized.\n" ); + printf( PULSE_PRINT_PREFIX"initialized.\n" ); r->thread = OGCreateThread( CNFAThread, r ); diff --git a/CNFA_sf.h b/CNFA_sf.h index 869d196..44e4fe0 100644 --- a/CNFA_sf.h +++ b/CNFA_sf.h @@ -1,5 +1,5 @@ //This file was automatically generated by Makefile at https://github.com/cnlohr/cnfa -//Generated from files git hash dbe638e0c72e599cab6bc71c2bf0b6da216f50e6 on Thu Jun 13 11:55:47 UTC 2024 (This is not the git hash of this file) +//Generated from files git hash da3569ad3b7ce480747db039037d7495b87bc18a on Wed Dec 31 05:07:19 PM EST 2025 (This is not the git hash of this file) //Copyright <>< 2010-2020 Charles Lohr (And other authors as cited) //CNFA is licensed under the MIT/x11, ColorChord or NewBSD Licenses. You choose. // @@ -7,7 +7,7 @@ // Easily output and input sound on a variety of platforms. // // Options: -// * #define CNFA_IMPLEMENTATION before this header and it will build all +// * #define CNFA_IMPLEMENTATION before this header and it will build all // definitions in. // @@ -101,6 +101,7 @@ void RegCNFADriver( int priority, const char * name, CNFAInitFn * fn ); #define CNFA_SUN 1 #elif defined(__linux) || defined(__linux__) || defined(linux) || defined(__LINUX__) #define CNFA_LINUX 1 +#define CNFA_PULSE 1 #endif #if defined(PULSEAUDIO) @@ -290,11 +291,1416 @@ REGISTER_CNFA( NullCNFA, 1, "NULL", InitCNFANull ); #if CNFA_WINDOWS - #include "CNFA_winmm.c" - #include // This probably won't work on pre-NT systems - #if VER_PRODUCTBUILD >= 7601 - #include "CNFA_wasapi.c" - #endif +//Copyright 2015-2020 <>< Charles Lohr under the ColorChord License, MIT/x11 license or NewBSD Licenses. + +#include +#include "os_generic.h" +#include +#include +#include + +//Include -lwinmm, or, C:/windows/system32/winmm.dll + +#if defined(_MSC_VER) +#if CNFA_WINDOWS +#ifndef strdup +#define strdup _strdup +#endif +#endif + +#if defined(WIN32) +#pragma comment(lib,"winmm.lib") +#endif +#endif + +#define BUFFS 3 + +struct CNFADriverWin +{ + //Standard header - must remain. + void (*CloseFn)( void * object ); + int (*StateFn)( void * object ); + CNFACBType callback; + short channelsPlay; + short channelsRec; + int spsPlay; + int spsRec; + void * opaque; + + char * sInputDev; + char * sOutputDev; + + int buffer; + int isEnding; + int GOBUFFRec; + int GOBUFFPlay; + + int recording; + int playing; + + HWAVEIN hMyWaveIn; + HWAVEOUT hMyWaveOut; + WAVEHDR WavBuffIn[BUFFS]; + WAVEHDR WavBuffOut[BUFFS]; +}; + + +static struct CNFADriverWin * w; + +void CloseCNFAWin( void * v ) +{ + struct CNFADriverWin * r = (struct CNFADriverWin *)v; + int i; + + if( r ) + { + if( r->hMyWaveIn ) + { + waveInStop(r->hMyWaveIn); + waveInReset(r->hMyWaveIn); + for ( i=0;ihMyWaveIn,&(r->WavBuffIn[i]),sizeof(WAVEHDR)); + free ((r->WavBuffIn[i]).lpData); + } + waveInClose(r->hMyWaveIn); + } + + if( r->hMyWaveOut ) + { + waveOutPause(r->hMyWaveOut); + waveOutReset(r->hMyWaveOut); + + for ( i=0;ihMyWaveIn,&(r->WavBuffOut[i]),sizeof(WAVEHDR)); + free ((r->WavBuffOut[i]).lpData); + } + waveInClose(r->hMyWaveIn); + waveOutClose(r->hMyWaveOut); + } + free( r ); + } +} + +int CNFAStateWin( void * v ) +{ + struct CNFADriverWin * soundobject = (struct CNFADriverWin *)v; + + return soundobject->recording | (soundobject->playing?2:0); +} + +void CALLBACK HANDLEMIC(HWAVEIN hwi, UINT umsg, DWORD dwi, DWORD hdr, DWORD dwparm) +{ + int ob; + unsigned int maxWave=0; + + if (w->isEnding) return; + + switch (umsg) + { + case MM_WIM_OPEN: + printf( "Mic Open.\n" ); + w->recording = 1; + break; + + case MM_WIM_DATA: + ob = (w->GOBUFFRec+(BUFFS))%BUFFS; + w->callback( (struct CNFADriver*)w, 0, (short*)(w->WavBuffIn[w->GOBUFFRec]).lpData, 0, w->buffer ); + waveInAddBuffer(w->hMyWaveIn,&(w->WavBuffIn[w->GOBUFFRec]),sizeof(WAVEHDR)); + w->GOBUFFRec = ( w->GOBUFFRec + 1 ) % BUFFS; + break; + } +} + + +void CALLBACK HANDLESINK(HWAVEIN hwi, UINT umsg, DWORD dwi, DWORD hdr, DWORD dwparm) +{ + unsigned int maxWave=0; + + if (w->isEnding) return; + + switch (umsg) + { + case MM_WOM_OPEN: + printf( "Sink Open.\n" ); + w->playing = 1; + break; + + case MM_WOM_DONE: + w->callback( (struct CNFADriver*)w, (short*)(w->WavBuffOut[w->GOBUFFPlay]).lpData, 0, w->buffer, 0 ); + waveOutWrite( w->hMyWaveOut, &(w->WavBuffOut[w->GOBUFFPlay]),sizeof(WAVEHDR) ); + w->GOBUFFPlay = ( w->GOBUFFPlay + 1 ) % BUFFS; + break; + } +} + + +static struct CNFADriverWin * InitWinCNFA( struct CNFADriverWin * r ) +{ + int i; + WAVEFORMATEX wfmt; + long dwdeviceR, dwdeviceP; + memset( &wfmt, 0, sizeof(wfmt) ); + printf ("WFMT Size (debugging temp for TCC): %zu\n", sizeof(wfmt) ); + printf( "WFMT: %d %d %d\n", r->channelsRec, r->spsRec, r->spsRec * r->channelsRec ); + w = r; + + wfmt.wFormatTag = WAVE_FORMAT_PCM; + wfmt.nChannels = r->channelsRec; + wfmt.nAvgBytesPerSec = r->spsRec * r->channelsRec; + wfmt.nBlockAlign = r->channelsRec * 2; + wfmt.nSamplesPerSec = r->spsRec; + wfmt.wBitsPerSample = 16; + wfmt.cbSize = 0; + + dwdeviceR = r->sInputDev?atoi(r->sInputDev):WAVE_MAPPER; + dwdeviceP = r->sOutputDev?atoi(r->sOutputDev):WAVE_MAPPER; + + if( r->channelsRec ) + { + int p; + printf( "In Wave Devs: %d; WAVE_MAPPER: %d; Selected Input: %ld\n", waveInGetNumDevs(), WAVE_MAPPER, dwdeviceR ); + p = waveInOpen(&r->hMyWaveIn, dwdeviceR, &wfmt, (intptr_t)(&HANDLEMIC), 0, CALLBACK_FUNCTION); + if( p ) + { + fprintf( stderr, "Error performing waveInOpen. Received code: %d\n", p ); + } + printf( "waveInOpen: %d\n", p ); + for ( i=0;iWavBuffIn[i]), 0, sizeof(r->WavBuffIn[i]) ); + (r->WavBuffIn[i]).dwBufferLength = r->buffer*2*r->channelsRec; + (r->WavBuffIn[i]).dwLoops = 1; + (r->WavBuffIn[i]).lpData=(char*) malloc(r->buffer*r->channelsRec*2); + printf( "buffer gen size: %d: %p\n", r->buffer*r->channelsRec*2, (r->WavBuffIn[i]).lpData ); + p = waveInPrepareHeader(r->hMyWaveIn,&(r->WavBuffIn[i]),sizeof(WAVEHDR)); + printf( "WIPr: %d\n", p ); + waveInAddBuffer(r->hMyWaveIn,&(r->WavBuffIn[i]),sizeof(WAVEHDR)); + printf( "WIAr: %d\n", p ); + } + p = waveInStart(r->hMyWaveIn); + if( p ) + { + fprintf( stderr, "Error performing waveInStart. Received code %d\n", p ); + } + } + + wfmt.nChannels = r->channelsPlay; + wfmt.nAvgBytesPerSec = r->spsPlay * r->channelsPlay; + wfmt.nBlockAlign = r->channelsPlay * 2; + wfmt.nSamplesPerSec = r->spsPlay; + + if( r->channelsPlay ) + { + int p; + printf( "Out Wave Devs: %d; WAVE_MAPPER: %d; Selected Input: %ld\n", waveOutGetNumDevs(), WAVE_MAPPER, dwdeviceP ); + p = waveOutOpen( &r->hMyWaveOut, dwdeviceP, &wfmt, (intptr_t)(void*)(&HANDLESINK), (intptr_t)r, CALLBACK_FUNCTION); + if( p ) + { + fprintf( stderr, "Error performing waveOutOpen. Received code: %d\n", p ); + } + printf( "waveOutOpen: %d\n", p ); + for ( i=0;iWavBuffOut[i]), 0, sizeof(r->WavBuffOut[i]) ); + (r->WavBuffOut[i]).dwBufferLength = r->buffer*2*r->channelsPlay; + (r->WavBuffOut[i]).dwLoops = 1; + size = r->buffer*r->channelsPlay*2; + buf = (r->WavBuffOut[i]).lpData=(char*) malloc(size); + memset( buf, 0, size ); + p = waveOutPrepareHeader(r->hMyWaveOut,&(r->WavBuffOut[i]),sizeof(WAVEHDR)); + waveOutWrite( r->hMyWaveOut, &(r->WavBuffOut[i]),sizeof(WAVEHDR)); + } + } + + return r; +} + + + +void * InitCNFAWin( 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 CNFADriverWin * r = (struct CNFADriverWin *)malloc( sizeof( struct CNFADriverWin ) ); + memset( r, 0, sizeof(*r) ); + r->CloseFn = CloseCNFAWin; + r->StateFn = CNFAStateWin; + r->callback = cb; + r->opaque = opaque; + r->spsPlay = reqSPSPlay; + r->spsRec = reqSPSRec; + r->channelsPlay = reqChannelsPlay; + r->channelsRec = reqChannelsRec; + r->buffer = sugBufferSize; + r->sInputDev = inputSelect?strdup(inputSelect):0; + r->sOutputDev = outputSelect?strdup(outputSelect):0; + + r->recording = 0; + r->playing = 0; + r->isEnding = 0; + r->GOBUFFPlay = 0; + r->GOBUFFRec = 0; + + return InitWinCNFA(r); +} + +REGISTER_CNFA( WinCNFA, 10, "WIN", InitCNFAWin ); + + +#include // This probably won't work on pre-NT systems +#if VER_PRODUCTBUILD >= 7601 + +//Needed libraries: -lmmdevapi -lavrt -lole32 +//Or DLLs: C:/windows/system32/avrt.dll C:/windows/system32/ole32.dll + +#ifdef TCC +#define NO_WIN_HEADERS +#endif + +#ifdef NO_WIN_HEADERS +#ifndef _CNFA_WASAPI_UTILS_H +#define _CNFA_WASAPI_UTILS_H + +//#include "ole2.h" + +#ifndef REFPROPERTYKEY +#define REFPROPERTYKEY const PROPERTYKEY * __MIDL_CONST +#endif //REFPROPERTYKEY + +// Necessary definitions +#define _ANONYMOUS_STRUCT +#define BEGIN_INTERFACE +#define END_INTERFACE +#define DEVICE_STATE_ACTIVE 0x00000001 +#define AUDCLNT_STREAMFLAGS_CROSSPROCESS 0x00010000 +#define AUDCLNT_STREAMFLAGS_LOOPBACK 0x00020000 +#define AUDCLNT_STREAMFLAGS_EVENTCALLBACK 0x00040000 +#define AUDCLNT_STREAMFLAGS_NOPERSIST 0x00080000 +#define AUDCLNT_STREAMFLAGS_RATEADJUST 0x00100000 +#define AUDCLNT_STREAMFLAGS_PREVENT_LOOPBACK_CAPTURE 0x01000000 +#define AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY 0x08000000 +#define AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM 0x80000000 +#define AUDCLNT_SESSIONFLAGS_EXPIREWHENUNOWNED 0x10000000 +#define AUDCLNT_SESSIONFLAGS_DISPLAY_HIDE 0x20000000 +#define AUDCLNT_SESSIONFLAGS_DISPLAY_HIDEWHENEXPIRED 0x40000000 +enum _AUDCLNT_BUFFERFLAGS +{ + AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY = 0x1, + AUDCLNT_BUFFERFLAGS_SILENT = 0x2, + AUDCLNT_BUFFERFLAGS_TIMESTAMP_ERROR = 0x4 +} ; + + +#ifndef REFIID +#define REFIID const IID * __MIDL_CONST +#endif + +#ifndef PropVariantInit +#define PropVariantInit(pvar) memset ( (pvar), 0, sizeof(PROPVARIANT) ) +#endif + +#if defined (__TINYC__) +#define _COM_Outptr_ +#define _In_ +#define _Out_ +#define _Outptr_ +#define _In_opt_ +#define _Out_opt_ +#define __RPC__in +#define __RPC__out +#define interface struct +#define CONST_VTBL +#define _Outptr_result_buffer_(X) +#define _Inexpressible_(X) +#define REFPROPVARIANT const PROPVARIANT * __MIDL_CONST +typedef struct tagPROPVARIANT PROPVARIANT; +typedef struct tWAVEFORMATEX WAVEFORMATEX; +typedef IID GUID; +typedef void* HANDLE; + +#define CLSCTX_INPROC_SERVER 0x1 +#define CLSCTX_INPROC_HANDLER 0x2 +#define CLSCTX_LOCAL_SERVER 0x4 +#define CLSCTX_REMOTE_SERVER 0x10 +#define STGM_READ 0x00000000L +#define CLSCTX_ALL (CLSCTX_INPROC_SERVER| \ + CLSCTX_INPROC_HANDLER| \ + CLSCTX_LOCAL_SERVER| \ + CLSCTX_REMOTE_SERVER) +typedef unsigned short VARTYPE; + +typedef struct _tagpropertykey { + GUID fmtid; + DWORD pid; +} PROPERTYKEY; + +#ifndef __wtypes_h__ +typedef struct tagDEC { + USHORT wReserved; + BYTE scale; + BYTE sign; + ULONG Hi32; + ULONGLONG Lo64; +} DECIMAL; + +// Property varient struct, used for getting the device name info +typedef BYTE PROPVAR_PAD1; +typedef BYTE PROPVAR_PAD2; +typedef ULONG PROPVAR_PAD3; + +struct tagPROPVARIANT { + union { + struct tag_inner_PROPVARIANT + { + VARTYPE vt; + PROPVAR_PAD1 wReserved1; + PROPVAR_PAD2 wReserved2; + PROPVAR_PAD3 wReserved3; + union + { + double dblVal; // Filler for the largest object we need to store + LPWSTR pwszVal; // This is the only parameter we actually use + }; + } ; + DECIMAL decVal; + }; +}; + +#endif + +#define _Inout_updates_(dwCount) +#define FAR + + +typedef interface IUnknown IUnknown; +typedef IUnknown *LPUNKNOWN; +#endif + +#ifdef NO_WIN_HEADERS +#undef DEFINE_GUID +#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + EXTERN_C const GUID DECLSPEC_SELECTANY name \ + = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } } +#undef DEFINE_PROPERTYKEY +#define DEFINE_PROPERTYKEY(name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8,pid) \ + EXTERN_C const PROPERTYKEY DECLSPEC_SELECTANY name \ + = { { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }, pid } + +// stuff to be able to read device names +DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14); + +#ifndef WINOLEAPI +#define WINOLEAPI EXTERN_C DECLSPEC_IMPORT HRESULT STDAPICALLTYPE +#define WINOLEAPI_(type) EXTERN_C DECLSPEC_IMPORT type STDAPICALLTYPE +#endif + +// Define necessary functions +WINOLEAPI_(HANDLE) +AvSetMmThreadCharacteristicsW(LPCWSTR TaskName, LPDWORD TaskIndex); + +WINOLEAPI_(BOOL) +AvRevertMmThreadCharacteristics(HANDLE AvrtHandle); + +WINOLEAPI CoInitialize(LPVOID pvReserved); +WINOLEAPI_(void) CoUninitialize(); +WINOLEAPI_(void) CoTaskMemFree(LPVOID pv); + +WINOLEAPI CoCreateInstance( + REFCLSID rclsid, + LPUNKNOWN pUnkOuter, + DWORD dwClsContext, + REFIID riid, + LPVOID FAR* ppv); + +// WINOLEAPI CoCreateInstanceEx( +// REFCLSID Clsid, +// IUnknown *punkOuter, +// DWORD dwClsCtx, +// COSERVERINFO *pServerInfo, +// DWORD dwCount, +// MULTI_QI *pResults ); + +#endif //NO_WIN_HEADERS + +// forward declarations +typedef struct IMMDevice IMMDevice; +typedef struct IMMDeviceCollection IMMDeviceCollection; +typedef struct IMMDeviceEnumerator IMMDeviceEnumerator; +typedef struct IMMNotificationClient IMMNotificationClient; +typedef struct IPropertyStore IPropertyStore; +typedef struct IAudioClient IAudioClient; +typedef struct IAudioCaptureClient IAudioCaptureClient; + +// So the linker doesn't complain +extern const IID CLSID_MMDeviceEnumerator; +extern const IID IID_IMMDeviceEnumerator; +extern const IID IID_IAudioClient; +extern const IID CNFA_GUID; +extern const IID IID_IAudioCaptureClient; + +typedef enum __MIDL___MIDL_itf_mmdeviceapi_0000_0000_0001 +{ + eRender = 0, + eCapture = ( eRender + 1 ) , + eAll = ( eCapture + 1 ) , + EDataFlow_enum_count = ( eAll + 1 ) +} EDataFlow; + +typedef enum __MIDL___MIDL_itf_mmdeviceapi_0000_0000_0002 +{ + eConsole = 0, + eMultimedia = ( eConsole + 1 ) , + eCommunications = ( eMultimedia + 1 ) , + ERole_enum_count = ( eCommunications + 1 ) +} ERole; + +typedef struct IMMDeviceEnumeratorVtbl +{ + BEGIN_INTERFACE + + HRESULT ( STDMETHODCALLTYPE *QueryInterface )( + IMMDeviceEnumerator * This, + /* [in] */ REFIID riid, + /* [annotation][iid_is][out] */ + _COM_Outptr_ void **ppvObject); + + ULONG ( STDMETHODCALLTYPE *AddRef )( + IMMDeviceEnumerator * This); + + ULONG ( STDMETHODCALLTYPE *Release )( + IMMDeviceEnumerator * This); + + /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *EnumAudioEndpoints )( + IMMDeviceEnumerator * This, + /* [annotation][in] */ + _In_ EDataFlow dataFlow, + /* [annotation][in] */ + _In_ DWORD dwStateMask, + /* [annotation][out] */ + _Out_ IMMDeviceCollection **ppDevices); + + /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *GetDefaultAudioEndpoint )( + IMMDeviceEnumerator * This, + /* [annotation][in] */ + _In_ EDataFlow dataFlow, + /* [annotation][in] */ + _In_ ERole role, + /* [annotation][out] */ + _Out_ IMMDevice **ppEndpoint); + + /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *GetDevice )( + IMMDeviceEnumerator * This, + /* [annotation][in] */ + _In_ LPCWSTR pwstrId, + /* [annotation][out] */ + _Out_ IMMDevice **ppDevice); + + /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *RegisterEndpointNotificationCallback )( + IMMDeviceEnumerator * This, + /* [annotation][in] */ + _In_ IMMNotificationClient *pClient); + + /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *UnregisterEndpointNotificationCallback )( + IMMDeviceEnumerator * This, + /* [annotation][in] */ + _In_ IMMNotificationClient *pClient); + + END_INTERFACE +} IMMDeviceEnumeratorVtbl; + +interface IMMDeviceEnumerator +{ + CONST_VTBL struct IMMDeviceEnumeratorVtbl *lpVtbl; +}; + +typedef struct IMMDeviceCollectionVtbl +{ + BEGIN_INTERFACE + + HRESULT ( STDMETHODCALLTYPE *QueryInterface )( + IMMDeviceCollection * This, + /* [in] */ REFIID riid, + /* [annotation][iid_is][out] */ + _COM_Outptr_ void **ppvObject); + + ULONG ( STDMETHODCALLTYPE *AddRef )( + IMMDeviceCollection * This); + + ULONG ( STDMETHODCALLTYPE *Release )( + IMMDeviceCollection * This); + + /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *GetCount )( + IMMDeviceCollection * This, + /* [annotation][out] */ + _Out_ UINT *pcDevices); + + /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *Item )( + IMMDeviceCollection * This, + /* [annotation][in] */ + _In_ UINT nDevice, + /* [annotation][out] */ + _Out_ IMMDevice **ppDevice); + + END_INTERFACE +} IMMDeviceCollectionVtbl; + +interface IMMDeviceCollection +{ + CONST_VTBL struct IMMDeviceCollectionVtbl *lpVtbl; +}; + +typedef struct IMMDeviceVtbl +{ + BEGIN_INTERFACE + + HRESULT ( STDMETHODCALLTYPE *QueryInterface )( + IMMDevice * This, + /* [in] */ REFIID riid, + /* [annotation][iid_is][out] */ + _COM_Outptr_ void **ppvObject); + + ULONG ( STDMETHODCALLTYPE *AddRef )( + IMMDevice * This); + + ULONG ( STDMETHODCALLTYPE *Release )( + IMMDevice * This); + + /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *Activate )( + IMMDevice * This, + /* [annotation][in] */ + _In_ REFIID iid, + /* [annotation][in] */ + _In_ DWORD dwClsCtx, + /* [annotation][unique][in] */ + _In_opt_ PROPVARIANT *pActivationParams, + /* [annotation][iid_is][out] */ + _Out_ void **ppInterface); + + /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *OpenPropertyStore )( + IMMDevice * This, + /* [annotation][in] */ + _In_ DWORD stgmAccess, + /* [annotation][out] */ + _Out_ IPropertyStore **ppProperties); + + /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *GetId )( + IMMDevice * This, + /* [annotation][out] */ + _Outptr_ LPWSTR *ppstrId); + + /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *GetState )( + IMMDevice * This, + /* [annotation][out] */ + _Out_ DWORD *pdwState); + + END_INTERFACE +} IMMDeviceVtbl; + +interface IMMDevice +{ + CONST_VTBL struct IMMDeviceVtbl *lpVtbl; +}; + +typedef struct IMMNotificationClientVtbl +{ + BEGIN_INTERFACE + + HRESULT ( STDMETHODCALLTYPE *QueryInterface )( + IMMNotificationClient * This, + /* [in] */ REFIID riid, + /* [annotation][iid_is][out] */ + _COM_Outptr_ void **ppvObject); + + ULONG ( STDMETHODCALLTYPE *AddRef )( + IMMNotificationClient * This); + + ULONG ( STDMETHODCALLTYPE *Release )( + IMMNotificationClient * This); + + /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *OnDeviceStateChanged )( + IMMNotificationClient * This, + /* [annotation][in] */ + _In_ LPCWSTR pwstrDeviceId, + /* [annotation][in] */ + _In_ DWORD dwNewState); + + /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *OnDeviceAdded )( + IMMNotificationClient * This, + /* [annotation][in] */ + _In_ LPCWSTR pwstrDeviceId); + + /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *OnDeviceRemoved )( + IMMNotificationClient * This, + /* [annotation][in] */ + _In_ LPCWSTR pwstrDeviceId); + + /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *OnDefaultDeviceChanged )( + IMMNotificationClient * This, + /* [annotation][in] */ + _In_ EDataFlow flow, + /* [annotation][in] */ + _In_ ERole role, + /* [annotation][in] */ + _In_ LPCWSTR pwstrDefaultDeviceId); + + /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *OnPropertyValueChanged )( + IMMNotificationClient * This, + /* [annotation][in] */ + _In_ LPCWSTR pwstrDeviceId, + /* [annotation][in] */ + _In_ const PROPERTYKEY key); + + END_INTERFACE +} IMMNotificationClientVtbl; + +interface IMMNotificationClient +{ + CONST_VTBL struct IMMNotificationClientVtbl *lpVtbl; +}; + +typedef struct IPropertyStoreVtbl +{ + BEGIN_INTERFACE + + HRESULT ( STDMETHODCALLTYPE *QueryInterface )( + __RPC__in IPropertyStore * This, + /* [in] */ __RPC__in REFIID riid, + /* [annotation][iid_is][out] */ + _COM_Outptr_ void **ppvObject); + + ULONG ( STDMETHODCALLTYPE *AddRef )( + __RPC__in IPropertyStore * This); + + ULONG ( STDMETHODCALLTYPE *Release )( + __RPC__in IPropertyStore * This); + + HRESULT ( STDMETHODCALLTYPE *GetCount )( + __RPC__in IPropertyStore * This, + /* [out] */ __RPC__out DWORD *cProps); + + HRESULT ( STDMETHODCALLTYPE *GetAt )( + __RPC__in IPropertyStore * This, + /* [in] */ DWORD iProp, + /* [out] */ __RPC__out PROPERTYKEY *pkey); + + HRESULT ( STDMETHODCALLTYPE *GetValue )( + __RPC__in IPropertyStore * This, + /* [in] */ __RPC__in REFPROPERTYKEY key, + /* [out] */ __RPC__out PROPVARIANT *pv); + + HRESULT ( STDMETHODCALLTYPE *SetValue )( + __RPC__in IPropertyStore * This, + /* [in] */ __RPC__in REFPROPERTYKEY key, + /* [in] */ __RPC__in REFPROPVARIANT propvar); + + HRESULT ( STDMETHODCALLTYPE *Commit )( + __RPC__in IPropertyStore * This); + + END_INTERFACE +} IPropertyStoreVtbl; + +interface IPropertyStore +{ + CONST_VTBL struct IPropertyStoreVtbl *lpVtbl; +}; + +// ----- audioclient.h ----- + +typedef enum _AUDCLNT_SHAREMODE +{ + AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_SHAREMODE_EXCLUSIVE +} AUDCLNT_SHAREMODE; + +typedef LONGLONG REFERENCE_TIME; + +typedef struct IAudioClientVtbl +{ + BEGIN_INTERFACE + + HRESULT ( STDMETHODCALLTYPE *QueryInterface )( + IAudioClient * This, + /* [in] */ REFIID riid, + /* [annotation][iid_is][out] */ + _COM_Outptr_ void **ppvObject); + + ULONG ( STDMETHODCALLTYPE *AddRef )( + IAudioClient * This); + + ULONG ( STDMETHODCALLTYPE *Release )( + IAudioClient * This); + + HRESULT ( STDMETHODCALLTYPE *Initialize )( + IAudioClient * This, + /* [annotation][in] */ + _In_ AUDCLNT_SHAREMODE ShareMode, + /* [annotation][in] */ + _In_ DWORD StreamFlags, + /* [annotation][in] */ + _In_ REFERENCE_TIME hnsBufferDuration, + /* [annotation][in] */ + _In_ REFERENCE_TIME hnsPeriodicity, + /* [annotation][in] */ + _In_ const WAVEFORMATEX *pFormat, + /* [annotation][in] */ + _In_opt_ LPCGUID AudioSessionGuid); + + HRESULT ( STDMETHODCALLTYPE *GetBufferSize )( + IAudioClient * This, + /* [annotation][out] */ + _Out_ UINT32 *pNumBufferFrames); + + HRESULT ( STDMETHODCALLTYPE *GetStreamLatency )( + IAudioClient * This, + /* [annotation][out] */ + _Out_ REFERENCE_TIME *phnsLatency); + + HRESULT ( STDMETHODCALLTYPE *GetCurrentPadding )( + IAudioClient * This, + /* [annotation][out] */ + _Out_ UINT32 *pNumPaddingFrames); + + HRESULT ( STDMETHODCALLTYPE *IsFormatSupported )( + IAudioClient * This, + /* [annotation][in] */ + _In_ AUDCLNT_SHAREMODE ShareMode, + /* [annotation][in] */ + _In_ const WAVEFORMATEX *pFormat, + /* [unique][annotation][out] */ + _Out_opt_ WAVEFORMATEX **ppClosestMatch); + + HRESULT ( STDMETHODCALLTYPE *GetMixFormat )( + IAudioClient * This, + /* [annotation][out] */ + _Out_ WAVEFORMATEX **ppDeviceFormat); + + HRESULT ( STDMETHODCALLTYPE *GetDevicePeriod )( + IAudioClient * This, + /* [annotation][out] */ + _Out_opt_ REFERENCE_TIME *phnsDefaultDevicePeriod, + /* [annotation][out] */ + _Out_opt_ REFERENCE_TIME *phnsMinimumDevicePeriod); + + HRESULT ( STDMETHODCALLTYPE *Start )( + IAudioClient * This); + + HRESULT ( STDMETHODCALLTYPE *Stop )( + IAudioClient * This); + + HRESULT ( STDMETHODCALLTYPE *Reset )( + IAudioClient * This); + + HRESULT ( STDMETHODCALLTYPE *SetEventHandle )( + IAudioClient * This, + /* [in] */ HANDLE eventHandle); + + HRESULT ( STDMETHODCALLTYPE *GetService )( + IAudioClient * This, + /* [annotation][in] */ + _In_ REFIID riid, + /* [annotation][iid_is][out] */ + _Out_ void **ppv); + + END_INTERFACE +} IAudioClientVtbl; + +interface IAudioClient +{ + CONST_VTBL struct IAudioClientVtbl *lpVtbl; +}; + +typedef struct IAudioCaptureClientVtbl +{ + BEGIN_INTERFACE + + HRESULT ( STDMETHODCALLTYPE *QueryInterface )( + IAudioCaptureClient * This, + /* [in] */ REFIID riid, + /* [annotation][iid_is][out] */ + _COM_Outptr_ void **ppvObject); + + ULONG ( STDMETHODCALLTYPE *AddRef )( + IAudioCaptureClient * This); + + ULONG ( STDMETHODCALLTYPE *Release )( + IAudioCaptureClient * This); + + HRESULT ( STDMETHODCALLTYPE *GetBuffer )( + IAudioCaptureClient * This, + /* [annotation][out] */ + _Outptr_result_buffer_(_Inexpressible_("*pNumFramesToRead * pFormat->nBlockAlign")) BYTE **ppData, + /* [annotation][out] */ + _Out_ UINT32 *pNumFramesToRead, + /* [annotation][out] */ + _Out_ DWORD *pdwFlags, + /* [annotation][unique][out] */ + _Out_opt_ UINT64 *pu64DevicePosition, + /* [annotation][unique][out] */ + _Out_opt_ UINT64 *pu64QPCPosition); + + HRESULT ( STDMETHODCALLTYPE *ReleaseBuffer )( + IAudioCaptureClient * This, + /* [annotation][in] */ + _In_ UINT32 NumFramesRead); + + HRESULT ( STDMETHODCALLTYPE *GetNextPacketSize )( + IAudioCaptureClient * This, + /* [annotation][out] */ + _Out_ UINT32 *pNumFramesInNextPacket); + + END_INTERFACE +} IAudioCaptureClientVtbl; + +interface IAudioCaptureClient +{ + CONST_VTBL struct IAudioCaptureClientVtbl *lpVtbl; +}; + +typedef interface IMMEndpoint IMMEndpoint; + + typedef struct IMMEndpointVtbl + { + BEGIN_INTERFACE + + HRESULT ( STDMETHODCALLTYPE *QueryInterface )( + IMMEndpoint * This, + /* [in] */ REFIID riid, + /* [annotation][iid_is][out] */ + _COM_Outptr_ void **ppvObject); + + ULONG ( STDMETHODCALLTYPE *AddRef )( + IMMEndpoint * This); + + ULONG ( STDMETHODCALLTYPE *Release )( + IMMEndpoint * This); + + /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *GetDataFlow )( + IMMEndpoint * This, + /* [annotation][out] */ + _Out_ EDataFlow *pDataFlow); + + END_INTERFACE + } IMMEndpointVtbl; + + interface IMMEndpoint + { + CONST_VTBL struct IMMEndpointVtbl *lpVtbl; + }; + +#define DEVICE_STATE_ACTIVE 0x00000001 +#define DEVICE_STATE_DISABLED 0x00000002 +#define DEVICE_STATE_NOTPRESENT 0x00000004 +#define DEVICE_STATE_UNPLUGGED 0x00000008 +#define DEVICE_STATEMASK_ALL 0x0000000f + +#endif // _CNFA_WASAPI_UTILS_H +#else +#include +#include // Render and capturing audio +#include // Audio device handling +#include // Property keys for audio devices +#include // Thread management +#include "windows.h" +#endif + +#include "os_generic.h" + +#if defined(WIN32) && !defined( TCC ) +#pragma comment(lib,"avrt.lib") +#pragma comment(lib,"ole32.lib") +//And maybe mmdevapi.lib +#endif + +#define WASAPIPRINT(message) (printf("[CNFA][WASAPI]: %s\n", message)) +#define WASAPIERROR(error, message) (printf("[CNFA][WASAPI][ERR]: %s HRESULT: 0x%lX\n", message, error)) +#define PRINTGUID(guid) (printf("{%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}", guid.Data1, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7])) + +#define WASAPI_EXTRA_DEBUG FALSE + +// Forward declarations +void CloseCNFAWASAPI(void* stateObj); +int CNFAStateWASAPI(void* object); +static struct CNFADriverWASAPI* StartWASAPIDriver(struct CNFADriverWASAPI* initState); +static IMMDevice* WASAPIGetDefaultDevice(BOOL isCapture, BOOL isMultimedia); +static void WASAPIPrintAllDeviceLists(); +static void WASAPIPrintDeviceList(EDataFlow dataFlow); +void* ProcessEventAudioIn(void* stateObj); +void* InitCNFAWASAPIDriver( + CNFACBType callback, const char *session_name, + int reqSampleRateOut, int reqSampleRateIn, + int reqChannelsOut, int reqChannelsIn, int sugBufferSize, + const char * inputDevice, const char * outputDevice, + void * opaque +); + +DEFINE_GUID(CLSID_MMDeviceEnumerator, 0xBCDE0395L, 0xE52F, 0x467C, 0x8E, 0x3D, 0xC4, 0x57, 0x92, 0x91, 0x69, 0x2E); +DEFINE_GUID(IID_IMMDeviceEnumerator, 0xA95664D2L, 0x9614, 0x4F35, 0xA7, 0x46, 0xDE, 0x8D, 0xB6, 0x36, 0x17, 0xE6); +DEFINE_GUID(IID_IMMEndpoint, 0x1BE09788L, 0x6894, 0x4089, 0x85, 0x86, 0x9A, 0x2A, 0x6C, 0x26, 0x5A, 0xC5); +DEFINE_GUID(IID_IAudioClient, 0x1CB9AD4CL, 0xDBFA, 0x4c32, 0xB1, 0x78, 0xC2, 0xF5, 0x68, 0xA7, 0x03, 0xB2); +DEFINE_GUID(IID_IAudioCaptureClient, 0xC8ADBD64L, 0xE71E, 0x48a0, 0xA4, 0xDE, 0x18, 0x5C, 0x39, 0x5C, 0xD3, 0x17); + +// This is a fallback if the client application does not provide a GUID. +DEFINE_GUID(CNFA_GUID, 0x899081C7L, 0x9428, 0x4103, 0x87, 0x93, 0x26, 0x47, 0xE5, 0xEA, 0xA2, 0xB4); + +struct CNFADriverWASAPI +{ + // Common CNFA items + void (*CloseFn)(void* object); + int (*StateFn)(void* object); + CNFACBType Callback; + short ChannelCountOut; // Not yet used. + short ChannelCountIn; // How many cahnnels the input stream has per frame. E.g. stereo = 2. + int SampleRateOut; + int SampleRateIn; + void* Opaque; // Not relevant to us + + // Adjustable WASAPI-specific items + const char* SessionName; // The name to give our audio sessions. Otherwise, defaults to using embedded EXE name, Window title, or EXE file name directly. + const GUID* SessionID; // In order to have different CNFA-based applications individually controllable from the volume mixer, this should be set differently for every client program, but constant across all runs/builds of that application. + + // Everything below here is for internal use only. Do not attempt to interact with these items. + const char* InputDeviceID; // The device to use for getting input from. Can be a render device (operating in loopback), or a capture device. + const char* OutputDeviceID; // Not yet used. + IMMDeviceEnumerator* DeviceEnumerator; // The base object that allows us to look through the system's devices, and from there get everything else. + IMMDevice* Device; // The device we are taking input from. + IAudioClient* Client; // The base client we use for getting input. + IAudioCaptureClient* CaptureClient; // The specific client we use for getting input. + WAVEFORMATEX* MixFormat; // The format of the input stream. + INT32 BytesPerFrame; // The number of bytes of one full frame of audio. AKA (channel count) * (sample bit depth), in Bytes. + BOOL StreamReady; // Whether the input stream is ready for data retrieval. + BOOL KeepGoing; // Whether to continue interacting with the streams, or shutdown the driver. + og_thread_t ThreadOut; // Not yet used. + og_thread_t ThreadIn; // The thread used to grab input data. + HANDLE EventHandleOut; // Not yet used. + HANDLE EventHandleIn; // The handle used to wait for more input data to be ready in the input thread. + HANDLE TaskHandleOut; // The task used to request output thread priority changes. + HANDLE TaskHandleIn; // The task used to request input thread priority changes. +}; + +// This is where the driver's current state is stored. +static struct CNFADriverWASAPI* WASAPIState; + +// Stops streams, ends threads, and cleans up all resources used by the driver. +void CloseCNFAWASAPI(void* stateObj) +{ + struct CNFADriverWASAPI* state = (struct CNFADriverWASAPI*)stateObj; + if(state != NULL) + { + // TODO: See if there are any other items that need cleanup. + state->KeepGoing = FALSE; + if (state->ThreadOut != NULL) { OGJoinThread(state->ThreadOut); } + if (state->ThreadIn != NULL) { OGJoinThread(state->ThreadIn); } + if (state->EventHandleOut != NULL) { CloseHandle(state->EventHandleOut); } + if (state->EventHandleIn != NULL) { CloseHandle(state->EventHandleIn); } + CoTaskMemFree(state->MixFormat); + if (state->CaptureClient != NULL) { state->CaptureClient->lpVtbl->Release(state->CaptureClient); } + if (state->Client != NULL) { state->Client->lpVtbl->Release(state->Client); } + if (state->Device != NULL) { state->Device->lpVtbl->Release(state->Device); } + if (state->DeviceEnumerator != NULL) { state->DeviceEnumerator->lpVtbl->Release(state->DeviceEnumerator); } + free(stateObj); + CoUninitialize(); + printf("[CNFA][WASAPI]: Cleanup completed. Goodbye.\n"); + } +} + +// Gets the current state of the driver. +// 0 = No streams active +// 1 = Input stream active +// 2 = Output stream active +// 3 = Both streams active +int CNFAStateWASAPI(void* stateObj) +{ + struct CNFADriverWASAPI* state = (struct CNFADriverWASAPI*)stateObj; + if(state != NULL) + { + if (state->StreamReady) { return 1; } // TODO: Output the correct status when output is implemented. + } + return 0; +} + +// Reads the desired configuration, interfaces with WASAPI to get the current system information, and starts the input stream. +static struct CNFADriverWASAPI* StartWASAPIDriver(struct CNFADriverWASAPI* initState) +{ + WASAPIState = initState; + WASAPIState->StreamReady = FALSE; + WASAPIState->SessionID = &CNFA_GUID; + + HRESULT ErrorCode; + #ifndef BUILD_DLL + // A library should never call CoInitialize, as it needs to be done from the host program according to its threading model needs. + // NOTE: If you are getting errors, and you are using CNFA as a DLL, you need to call CoInitialize yourself with an appropriate threading model for your needs! + // When the host program is something like ColorChord on the other hand, it cannot be expected to call CoInitialize itself, so we do it on its behalf. + // This restricts the threading model of direct consumers of CNFA, but we can address that if it does ever become an issue. + ErrorCode = CoInitialize(NULL); + if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "COM INIT FAILED!"); return WASAPIState; } + #endif + + if(WASAPI_EXTRA_DEBUG) + { + printf("[CNFA][WASAPI]: CLSID for MMDeviceEnumerator: "); + PRINTGUID(CLSID_MMDeviceEnumerator); + printf("\n[CNFA][WASAPI]: IID for IMMDeviceEnumerator: "); + PRINTGUID(IID_IMMDeviceEnumerator); + printf("\n[CNFA][WASAPI]: IID for IAudioClient: "); + PRINTGUID(IID_IAudioClient); + printf("\n[CNFA][WASAPI]: IID for IAudioCaptureClient: "); + PRINTGUID(IID_IAudioCaptureClient); + printf("\n[CNFA][WASAPI]: IID for IMMEndpoint: "); + PRINTGUID(IID_IMMEndpoint); + printf("\n"); + } + + ErrorCode = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, &IID_IMMDeviceEnumerator, (void**)&(WASAPIState->DeviceEnumerator)); + if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get device enumerator. "); return WASAPIState; } + + WASAPIPrintAllDeviceLists(); + + // We need to find the appropriate device to use. + BYTE DeviceDirection = 2; // 0 = Render, 1 = Capture, 2 = Unknown + + if (WASAPIState->InputDeviceID == NULL) + { + WASAPIPRINT("No device specified, attempting to use system default multimedia capture device as input."); + WASAPIState->Device = WASAPIGetDefaultDevice(TRUE, TRUE); + DeviceDirection = 1; + } + else if (strcmp(WASAPIState->InputDeviceID, "defaultRender") == 0) + { + WASAPIPRINT("Attempting to use system default render device as input."); + WASAPIState->Device = WASAPIGetDefaultDevice(FALSE, TRUE); + DeviceDirection = 0; + } + else if (strncmp("defaultCapture", WASAPIState->InputDeviceID, strlen("defaultCapture")) == 0) + { + BOOL IsMultimedia = TRUE; + if (strstr(WASAPIState->InputDeviceID, "Comm") != NULL) { IsMultimedia = FALSE; } + printf("[CNFA][WASAPI]: Attempting to use system default %s capture device as input.\n", (IsMultimedia ? "multimedia" : "communications")); + WASAPIState->Device = WASAPIGetDefaultDevice(TRUE, IsMultimedia); + DeviceDirection = 1; + } + else // A specific device was selected by ID. + { + LPWSTR DeviceIDasLPWSTR; + DeviceIDasLPWSTR = malloc((strlen(WASAPIState->InputDeviceID) + 1) * sizeof(WCHAR)); + mbstowcs(DeviceIDasLPWSTR, WASAPIState->InputDeviceID, strlen(WASAPIState->InputDeviceID) + 1); + printf("[CNFA][WASAPI]: Attempting to find specified device \"%ls\".\n", DeviceIDasLPWSTR); + + ErrorCode = WASAPIState->DeviceEnumerator->lpVtbl->GetDevice(WASAPIState->DeviceEnumerator, DeviceIDasLPWSTR, &(WASAPIState->Device)); + if (FAILED(ErrorCode)) + { + WASAPIERROR(ErrorCode, "Failed to get audio device from the given ID. Using default multimedia capture device instead."); + WASAPIState->Device = WASAPIGetDefaultDevice(TRUE, TRUE); + DeviceDirection = 1; + } + else + { + printf("[CNFA][WASAPI]: Found specified device.\n"); + DWORD DeviceState; + ErrorCode = WASAPIState->Device->lpVtbl->GetState(WASAPIState->Device, &DeviceState); + if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get device state."); } + + if ((DeviceState & DEVICE_STATE_DISABLED) == DEVICE_STATE_DISABLED) { WASAPIERROR(E_FAIL, "The specified device is currently disabled."); } + if ((DeviceState & DEVICE_STATE_NOTPRESENT) == DEVICE_STATE_NOTPRESENT) { WASAPIERROR(E_FAIL, "The specified device is not currently present."); } + if ((DeviceState & DEVICE_STATE_UNPLUGGED) == DEVICE_STATE_UNPLUGGED) { WASAPIERROR(E_FAIL, "The specified device is currently unplugged."); } + } + } + + if (DeviceDirection == 2) // We still don't know what type of device we are trying to use. Query the endpoint to find out. + { + IMMEndpoint* Endpoint; + ErrorCode = WASAPIState->Device->lpVtbl->QueryInterface(WASAPIState->Device, &IID_IMMEndpoint, (void**)&Endpoint); + if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get endpoint of device."); } + + EDataFlow DataFlow; + ErrorCode = Endpoint->lpVtbl->GetDataFlow(Endpoint, &DataFlow); + if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Could not determine endpoint type."); } + + DeviceDirection = (DataFlow == eRender) ? 0 : 1; + + if (Endpoint != NULL) { Endpoint->lpVtbl->Release(Endpoint); } + } + + // We should have a device now. + char* DeviceDirectionDesc = (DeviceDirection == 0) ? "render" : ((DeviceDirection == 1) ? "capture" : "UNKNOWN"); + + LPWSTR DeviceID; + ErrorCode = WASAPIState->Device->lpVtbl->GetId(WASAPIState->Device, &DeviceID); + if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get audio device ID."); return WASAPIState; } + else { printf("[CNFA][WASAPI]: Using device ID \"%ls\", which is a %s device.\n", DeviceID, DeviceDirectionDesc); } + + // Start an audio client and get info about the stream format. + ErrorCode = WASAPIState->Device->lpVtbl->Activate(WASAPIState->Device, &IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&(WASAPIState->Client)); + if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get audio client. "); return WASAPIState; } + + ErrorCode = WASAPIState->Client->lpVtbl->GetMixFormat(WASAPIState->Client, &(WASAPIState->MixFormat)); + if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get mix format. "); return WASAPIState; } + printf("[CNFA][WASAPI]: Mix format is %d channel, %luHz sample rate, %db per sample.\n", WASAPIState->MixFormat->nChannels, WASAPIState->MixFormat->nSamplesPerSec, WASAPIState->MixFormat->wBitsPerSample); + printf("[CNFA][WASAPI]: Mix format is format %d, %dB block-aligned, with %dB of extra data in this definition.\n", WASAPIState->MixFormat->wFormatTag, WASAPIState->MixFormat->nBlockAlign, WASAPIState->MixFormat->cbSize); + + // We'll request PCM, 16bpS data from the system. It should be able to do this conversion for us, as long as we are not in exclusive mode. + // TODO: This isn't working, no matter what combination I try to ask it for. Figure this out, so we don't have to do the conversion ourselves. + // Also, we probably don't handle channel counts > 2 with this current setup. + //WASAPIState->MixFormat->wFormatTag = WAVE_FORMAT_PCM; + //WASAPIState->MixFormat->wBitsPerSample = 16 * WASAPIState->MixFormat->nChannels; + //WASAPIState->MixFormat->nBlockAlign = 2 * WASAPIState->MixFormat->nChannels; + //WASAPIState->MixFormat->nAvgBytesPerSec = WASAPIState->MixFormat->nSamplesPerSec * WASAPIState->MixFormat->nBlockAlign; + + WASAPIState->ChannelCountIn = WASAPIState->MixFormat->nChannels; + WASAPIState->SampleRateIn = WASAPIState->MixFormat->nSamplesPerSec; + WASAPIState->BytesPerFrame = WASAPIState->MixFormat->nChannels * (WASAPIState->MixFormat->wBitsPerSample / 8); + + REFERENCE_TIME DefaultInterval, MinimumInterval; + ErrorCode = WASAPIState->Client->lpVtbl->GetDevicePeriod(WASAPIState->Client, &DefaultInterval, &MinimumInterval); + if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get device timing info. "); return WASAPIState; } + printf("[CNFA][WASAPI]: Default transaction period is %lld ticks, minimum is %lld ticks.\n", DefaultInterval, MinimumInterval); + + // Configure a capture client. + UINT32 StreamFlags; + if (DeviceDirection == 1) { StreamFlags = AUDCLNT_STREAMFLAGS_NOPERSIST | AUDCLNT_STREAMFLAGS_EVENTCALLBACK; } + else if (DeviceDirection == 0) { StreamFlags = (AUDCLNT_STREAMFLAGS_LOOPBACK | AUDCLNT_STREAMFLAGS_EVENTCALLBACK); } + else { WASAPIPRINT("[ERR] Device type was not determined!"); return WASAPIState; } + + // TODO: Allow the target application to influence the interval we choose. Super realtime apps may require MinimumInterval. + ErrorCode = WASAPIState->Client->lpVtbl->Initialize(WASAPIState->Client, AUDCLNT_SHAREMODE_SHARED, StreamFlags, DefaultInterval, DefaultInterval, WASAPIState->MixFormat, WASAPIState->SessionID); + if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Could not init audio client."); return WASAPIState; } + + WASAPIState->EventHandleIn = CreateEvent(NULL, FALSE, FALSE, NULL); + if (WASAPIState->EventHandleIn == NULL) { WASAPIERROR(E_FAIL, "Failed to make event handle."); return WASAPIState; } + + ErrorCode = WASAPIState->Client->lpVtbl->SetEventHandle(WASAPIState->Client, WASAPIState->EventHandleIn); + if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to set event handler."); return WASAPIState; } + + UINT32 BufferFrameCount; + ErrorCode = WASAPIState->Client->lpVtbl->GetBufferSize(WASAPIState->Client, &BufferFrameCount); + if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Could not get audio client buffer size."); return WASAPIState; } + + ErrorCode = WASAPIState->Client->lpVtbl->GetService(WASAPIState->Client, &IID_IAudioCaptureClient, (void**)&(WASAPIState->CaptureClient)); + if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Could not get audio capture client."); return WASAPIState; } + + // Begin capturing audio. It will be received on a separate thread. + ErrorCode = WASAPIState->Client->lpVtbl->Start(WASAPIState->Client); + if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Could not start audio client."); return WASAPIState; } + WASAPIState->StreamReady = TRUE; + + WASAPIState->KeepGoing = TRUE; + WASAPIState->ThreadIn = OGCreateThread(ProcessEventAudioIn, WASAPIState); + + return WASAPIState; +} + +// Gets the default render or capture device. +// isCapture: If true, gets the default capture device, otherwise gets the default render device. +// isMultimedia: If true, gets the system default devide for "multimedia" use, otheriwse for "communication" use. +static IMMDevice* WASAPIGetDefaultDevice(BOOL isCapture, BOOL isMultimedia) +{ + HRESULT ErrorCode; + IMMDevice* Device; + ErrorCode = WASAPIState->DeviceEnumerator->lpVtbl->GetDefaultAudioEndpoint(WASAPIState->DeviceEnumerator, isCapture ? eCapture : eRender, isMultimedia ? eMultimedia : eCommunications, &Device); + if (FAILED(ErrorCode)) + { + WASAPIERROR(ErrorCode, "Failed to get default device."); + return NULL; + } + return Device; +} + +// Prints all available devices to the console. +static void WASAPIPrintAllDeviceLists() +{ + WASAPIPrintDeviceList(eRender); + WASAPIPrintDeviceList(eCapture); +} + +// Prints a list of all available devices of a specified data flow direction to the console. +static void WASAPIPrintDeviceList(EDataFlow dataFlow) +{ + printf("[CNFA][WASAPI]: %s Devices:\n", (dataFlow == eCapture ? "Capture" : "Render")); + IMMDeviceCollection* Devices; + HRESULT ErrorCode = WASAPIState->DeviceEnumerator->lpVtbl->EnumAudioEndpoints(WASAPIState->DeviceEnumerator, dataFlow, (WASAPI_EXTRA_DEBUG ? DEVICE_STATEMASK_ALL : DEVICE_STATE_ACTIVE), &Devices); + if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get audio endpoints."); return; } + + UINT32 DeviceCount; + ErrorCode = Devices->lpVtbl->GetCount(Devices, &DeviceCount); + if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get audio endpoint count."); return; } + + for (UINT32 DeviceIndex = 0; DeviceIndex < DeviceCount; DeviceIndex++) + { + IMMDevice* Device; + ErrorCode = Devices->lpVtbl->Item(Devices, DeviceIndex, &Device); + if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get audio device."); continue; } + + LPWSTR DeviceID; + ErrorCode = Device->lpVtbl->GetId(Device, &DeviceID); + if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get audio device ID."); continue; } + + IPropertyStore* Properties; + ErrorCode = Device->lpVtbl->OpenPropertyStore(Device, STGM_READ, &Properties); + if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get device properties."); continue; } + + PROPVARIANT Variant; + PropVariantInit(&Variant); + + ErrorCode = Properties->lpVtbl->GetValue(Properties, &PKEY_Device_FriendlyName, &Variant); + if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get device friendly name."); } + + LPWSTR DeviceFriendlyName = L"[Name Retrieval Failed]"; + if (Variant.pwszVal != NULL) { DeviceFriendlyName = Variant.pwszVal; } + + printf("[CNFA][WASAPI]: [%d]: \"%ls\" = \"%ls\"\n", DeviceIndex, DeviceFriendlyName, DeviceID); + + CoTaskMemFree(DeviceID); + DeviceID = NULL; + PropVariantClear(&Variant); + if (Properties != NULL) { Properties->lpVtbl->Release(Properties); } + if (Device != NULL) { Device->lpVtbl->Release(Device); } + } + + if (Devices != NULL) { Devices->lpVtbl->Release(Devices); } +} + +// Runs on a thread. Waits for audio data to be ready from the system, then forwards it to the registered callback. +void* ProcessEventAudioIn(void* stateObj) +{ + struct CNFADriverWASAPI* state = (struct CNFADriverWASAPI*)stateObj; + HRESULT ErrorCode; + UINT32 PacketLength; + + // TODO: Set this based on our device period requested. If we are using 10ms or higher, just request "Audio", not "Pro Audio". + DWORD TaskIndex = 0; + state->TaskHandleIn = AvSetMmThreadCharacteristicsW(L"Pro Audio", &TaskIndex); + if (state->TaskHandleIn == NULL) { WASAPIERROR(E_FAIL, "Failed to request thread priority elevation on input task."); } + + while (state->KeepGoing) + { + // Waits up to 500ms to get the next audio buffer from the system. + // The timeout is used because if no audio sessions are active, WASAPI stops sending buffers after a few that indicate silence. + // This means that if the client tries to exit, this loop would not complete, and therefore the thread would not exit, until the next buffer is received. + // This is mostly an issue in loopback mode, where true silence is common, not so much on microphones. + DWORD WaitResult = WaitForSingleObject(state->EventHandleIn, 500); + if (WaitResult == WAIT_TIMEOUT) { continue; } // We are in a period of silence. Keep waiting for audio. + else if (WaitResult != WAIT_OBJECT_0) { WASAPIERROR(E_FAIL, "Something went wrong while waiting for an audio event."); continue; } + + ErrorCode = state->CaptureClient->lpVtbl->GetNextPacketSize(state->CaptureClient, &PacketLength); + if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get audio packet size."); continue; } + + BYTE* DataBuffer; + UINT32 FramesAvailable; + DWORD BufferStatus; + BOOL Released = FALSE; + ErrorCode = state->CaptureClient->lpVtbl->GetBuffer(state->CaptureClient, &DataBuffer, &FramesAvailable, &BufferStatus, NULL, NULL); + if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get audio buffer."); continue; } + + // "The data in the packet is not correlated with the previous packet's device position; this is possibly due to a stream state transition or timing glitch." + // There's no real way for us to notify the client about this... + if ((BufferStatus & AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY) == AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY) + { + WASAPIPRINT("A data discontinuity was detected."); + } + + if ((BufferStatus & AUDCLNT_BUFFERFLAGS_SILENT) == AUDCLNT_BUFFERFLAGS_SILENT) + { + UINT32 Length = FramesAvailable * state->MixFormat->nChannels; + if (Length == 0) { Length = state->MixFormat->nChannels; } + INT16* AudioData = malloc(Length * 2); + for (int i = 0; i < Length; i++) { AudioData[i] = 0; } + + ErrorCode = state->CaptureClient->lpVtbl->ReleaseBuffer(state->CaptureClient, FramesAvailable); + if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to release audio buffer."); } + else { Released = TRUE; } + + if (WASAPI_EXTRA_DEBUG) { printf("[CNFA][WASAPI]: SILENCE buffer received. Passing on %d samples.\n", Length); } + + WASAPIState->Callback((struct CNFADriver*)WASAPIState, 0, AudioData, 0, Length / state->MixFormat->nChannels ); + free(AudioData); + } + else + { + // TODO: This assumes that data is coming in at 32b float format. While this appears to be the format that WASAPI uses internally in all cases I've seen, I don't think it's guaranteed. + // We should instead read the MixFormat information and properly handle the data in other cases. + // Ideally, we could request 16b signed PCM data from WASAPI, so we don't even have to do any conversion. But I couldn't get this working yet. + UINT32 Size = FramesAvailable * state->BytesPerFrame; // Size in bytes + FLOAT* DataAsFloat = (FLOAT*)DataBuffer; // The raw input data, reinterpreted as floats. + INT16* AudioData = malloc((FramesAvailable * state->MixFormat->nChannels) * 2); // The data we are passing to the consumer. + for (INT32 i = 0; i < Size / 4; i++) { AudioData[i] = (INT16)(DataAsFloat[i] * 32767.5F); } + + ErrorCode = state->CaptureClient->lpVtbl->ReleaseBuffer(state->CaptureClient, FramesAvailable); + if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to release audio buffer."); } + else { Released = TRUE; } + + if (WASAPI_EXTRA_DEBUG) { printf("[CNFA][WASAPI]: Got %d bytes of audio data in %d frames. Fowarding to %p.\n", Size, FramesAvailable, (void*) WASAPIState->Callback); } + + WASAPIState->Callback((struct CNFADriver*)WASAPIState, 0, AudioData, 0, FramesAvailable ); + free(AudioData); + } + + if (!Released) + { + ErrorCode = state->CaptureClient->lpVtbl->ReleaseBuffer(state->CaptureClient, FramesAvailable); + if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to release audio buffer."); } + } + + } + + ErrorCode = state->Client->lpVtbl->Stop(state->Client); + if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to stop audio client."); } + + if(state->TaskHandleIn != NULL) { AvRevertMmThreadCharacteristics(state->TaskHandleIn); } + + state->StreamReady = FALSE; + return 0; +} + +// Begins preparation of the WASAPI driver. +// callback: The user application's function where audio data is placed when received from the system and/or audio data is retrieved from to give to the system. +// sessionName: How your session will appear to the end user if you play audio. +// reqSampleRateIn/Out: Sample rate you'd like to request. Ignored, as this is determined by the system. See note below. +// reqChannelsIn: Input channel count you'd like to request. Ignored, as this is determined by the system. See note below. +// reqChannelsOut: Output channel count you'd like to request. Ignored, as this is determined by the system. See note below. +// sugBufferSize: Buffer size you'd like to request. Ignored, as this is determined by the system. See note below. +// inputDevice: The device you want to receive audio from. Loopback is supported, so this can be either a capture or render device. +// To get the default render device, specify "defaultRender" +// To get the default multimedia capture device, specify "defaultCapture" +// To get the default communications capture device, specify "defaultCaptureComm" +// A device ID as presented by WASAPI can be specified, regardless of what type it is. If it is invalid, the default capture device is used as fallback. +// If you do not wish to receive audio, specify null. NOT YET IMPLEMENTED +// outputDevice: The device you want to output audio to. OUTPUT IS NOT IMPLEMENTED. +// NOTES: +// Regarding format requests: Sample rate and channel count is determined by the system settings, and cannot be changed. Resampling/mixing will be required in your application if you cannot accept the current system mode. Make sure to check `WASAPIState` for the current system mode. +// Note also that both sample rate and channel count can vary between input and output! +// Currently audio output (playing) is not yet implemented. +void* InitCNFAWASAPIDriver( + CNFACBType callback, const char *sessionName, + int reqSampleRateOut, int reqSampleRateIn, + int reqChannelsOut, int reqChannelsIn, int sugBufferSize, + const char * outputDevice, const char * inputDevice, + void * opaque) +{ + struct CNFADriverWASAPI * InitState = malloc(sizeof(struct CNFADriverWASAPI)); + memset(InitState, 0, sizeof(*InitState)); + InitState->CloseFn = CloseCNFAWASAPI; + InitState->StateFn = CNFAStateWASAPI; + InitState->Callback = callback; + InitState->Opaque = opaque; + // TODO: Waiting for CNFA to support directional sample rates. + InitState->SampleRateIn = reqSampleRateIn; // Will be overridden by the actual system setting. + InitState->SampleRateOut = reqSampleRateOut; // Will be overridden by the actual system setting. + InitState->ChannelCountIn = reqChannelsIn; // Will be overridden by the actual system setting. + InitState->ChannelCountOut = reqChannelsOut; // Will be overridden by the actual system setting. + InitState->InputDeviceID = inputDevice; + InitState->OutputDeviceID = outputDevice; + + InitState->SessionName = sessionName; + + WASAPIPRINT("WASAPI Init"); + + return StartWASAPIDriver(InitState); +} + +REGISTER_CNFA(cnfa_wasapi, 20, "WASAPI", InitCNFAWASAPIDriver); + +#endif #elif CNFA_ANDROID //Copyright 2019-2020 <>< Charles Lohr under the ColorChord License, MIT/x11 license or NewBSD Licenses. // This was originally to be used with rawdrawandroid @@ -625,6 +2031,8 @@ REGISTER_CNFA( AndroidCNFA, 10, "ANDROID", InitCNFAAndroid ); #include #include +#define SUN_PRINT_PREFIX "[CNFA][Sun]: " + struct CNFADriverSun { void (*CloseFn)( void * object ); @@ -691,14 +2099,14 @@ void * RecThread( void * v ) int nread = read( r->record_handle, r->samplesRec, nbytes ); if( nread < 0 ) { - fprintf( stderr, "Warning: Sun Recording Failed\n" ); + fprintf( stderr, SUN_PRINT_PREFIX"Recording Failed\n" ); break; } r->recording = 1; r->callback( (struct CNFADriver *)r, NULL, r->samplesRec, 0, (nread / 2) / r->channelsRec); } while( 1 ); r->recording = 0; - fprintf( stderr, "Sun Recording Stopped\n" ); + fprintf( stderr, SUN_PRINT_PREFIX"Recording Stopped\n" ); return 0; } @@ -718,7 +2126,7 @@ void * PlayThread( void * v ) r->playing = 1; } r->playing = 0; - fprintf( stderr, "Sun Playback Stopped\n" ); + fprintf( stderr, SUN_PRINT_PREFIX"Playback Stopped\n" ); return 0; } @@ -738,13 +2146,13 @@ static struct CNFADriverSun * InitSun( struct CNFADriverSun * r ) devPlay = "/dev/audio"; } - printf( "CNFA Sun Init -> devPlay: %s, channelsPlay: %d, spsPlay: %d, devRec: %s, channelsRec: %d, spsRec: %d\n", devPlay, r->channelsPlay, r->spsPlay, devRec, r->channelsRec, r->spsRec); + printf( SUN_PRINT_PREFIX"Init -> devPlay: %s, channelsPlay: %d, spsPlay: %d, devRec: %s, channelsRec: %d, spsRec: %d\n", devPlay, r->channelsPlay, r->spsPlay, devRec, r->channelsRec, r->spsRec); if( r->channelsPlay && r->channelsRec && strcmp (devPlay, devRec) == 0 ) { if ( (r->playback_handle = r->record_handle = open (devPlay, O_RDWR)) < 0 ) { - fprintf (stderr, "cannot open audio device (%s)\n", + fprintf (stderr, SUN_PRINT_PREFIX"cannot open audio device (%s)\n", strerror (errno)); goto fail; } @@ -755,7 +2163,7 @@ static struct CNFADriverSun * InitSun( struct CNFADriverSun * r ) { if ( (r->playback_handle = open (devPlay, O_WRONLY)) < 0 ) { - fprintf (stderr, "cannot open output audio device %s (%s)\n", + fprintf (stderr, SUN_PRINT_PREFIX"cannot open output audio device %s (%s)\n", r->devPlay, strerror (errno)); goto fail; } @@ -765,7 +2173,7 @@ static struct CNFADriverSun * InitSun( struct CNFADriverSun * r ) { if ( (r->record_handle = open (devRec, O_RDONLY)) < 0 ) { - fprintf (stderr, "cannot open input audio device %s (%s)\n", + fprintf (stderr, SUN_PRINT_PREFIX"cannot open input audio device %s (%s)\n", r->devRec, strerror (errno)); goto fail; } @@ -783,14 +2191,14 @@ static struct CNFADriverSun * InitSun( struct CNFADriverSun * r ) if ( ioctl(r->playback_handle, AUDIO_SETINFO, &pinfo) < 0 ) { - fprintf (stderr, "cannot set audio playback format (%s)\n", + fprintf (stderr, SUN_PRINT_PREFIX"cannot set audio playback format (%s)\n", strerror (errno)); goto fail; } if ( ioctl(r->playback_handle, AUDIO_GETINFO, &pinfo) < 0 ) { - fprintf (stderr, "cannot get audio record format (%s)\n", + fprintf (stderr, SUN_PRINT_PREFIX"cannot get audio record format (%s)\n", strerror (errno)); goto fail; } @@ -815,14 +2223,14 @@ static struct CNFADriverSun * InitSun( struct CNFADriverSun * r ) if ( ioctl(r->record_handle, AUDIO_SETINFO, &rinfo) < 0 ) { - fprintf (stderr, "cannot set audio record format (%s)\n", + fprintf (stderr, SUN_PRINT_PREFIX"cannot set audio record format (%s)\n", strerror (errno)); goto fail; } if ( ioctl(r->record_handle, AUDIO_GETINFO, &rinfo) < 0 ) { - fprintf (stderr, "cannot get audio record format (%s)\n", + fprintf (stderr, SUN_PRINT_PREFIX"cannot get audio record format (%s)\n", strerror (errno)); goto fail; } @@ -846,7 +2254,7 @@ static struct CNFADriverSun * InitSun( struct CNFADriverSun * r ) r->threadRec = OGCreateThread( RecThread, r ); } - printf( "CNFA Sun Init Out -> channelsPlay: %d, spsPlay: %d, channelsRec: %d, spsRec: %d\n", r->channelsPlay, r->spsPlay, r->channelsRec, r->spsRec); + printf( SUN_PRINT_PREFIX"Init Out -> channelsPlay: %d, spsPlay: %d, channelsRec: %d, spsRec: %d\n", r->channelsPlay, r->spsPlay, r->channelsRec, r->spsRec); return r; @@ -900,6 +2308,8 @@ REGISTER_CNFA( SUN, 10, "Sun", InitSunDriver ); #include #include +#define ALSA_PRINT_PREFIX "[CNFA][ALSA]: " + struct CNFADriverAlsa { void (*CloseFn)( void * object ); @@ -959,37 +2369,37 @@ static int SetHWParams( snd_pcm_t * handle, int * samplerate, short * channels, 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", + fprintf (stderr, ALSA_PRINT_PREFIX"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", + fprintf (stderr, ALSA_PRINT_PREFIX"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", + fprintf (stderr, ALSA_PRINT_PREFIX"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", + fprintf (stderr, ALSA_PRINT_PREFIX"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", + fprintf (stderr, ALSA_PRINT_PREFIX"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", + fprintf (stderr, ALSA_PRINT_PREFIX"cannot set channel count (%s)\n", snd_strerror (err)); goto fail; } @@ -997,7 +2407,7 @@ static int SetHWParams( snd_pcm_t * handle, int * samplerate, short * channels, 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", + fprintf( stderr, ALSA_PRINT_PREFIX"cannot set period size. (%s)\n", snd_strerror(err) ); goto fail; } @@ -1006,14 +2416,14 @@ static int SetHWParams( snd_pcm_t * handle, int * samplerate, short * channels, 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", + fprintf( stderr, ALSA_PRINT_PREFIX"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", + fprintf (stderr, ALSA_PRINT_PREFIX"cannot set parameters (%s)\n", snd_strerror (err)); goto fail; } @@ -1035,37 +2445,37 @@ static int SetSWParams( struct CNFADriverAlsa * d, snd_pcm_t * handle, int isrec if( !isrec ) { if ((err = snd_pcm_sw_params_malloc (&sw_params)) < 0) { - fprintf (stderr, "cannot allocate software parameters structure (%s)\n", + fprintf (stderr, ALSA_PRINT_PREFIX"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", + fprintf (stderr, ALSA_PRINT_PREFIX"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 ); + printf( ALSA_PRINT_PREFIX"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", + fprintf (stderr, ALSA_PRINT_PREFIX"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", + // fprintf (stderr, ALSA_PRINT_PREFIX"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", + fprintf (stderr, ALSA_PRINT_PREFIX"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", + fprintf (stderr, ALSA_PRINT_PREFIX"cannot set software parameters (%s)\n", snd_strerror (err)); goto fail; } @@ -1075,7 +2485,7 @@ static int SetSWParams( struct CNFADriverAlsa * d, snd_pcm_t * handle, int isrec } if ((err = snd_pcm_prepare (handle)) < 0) { - fprintf (stderr, "cannot prepare audio interface for use (%s)\n", + fprintf (stderr, ALSA_PRINT_PREFIX"cannot prepare audio interface for use (%s)\n", snd_strerror (err)); goto fail; } @@ -1100,18 +2510,18 @@ void * RecThread( void * v ) int err = snd_pcm_readi( r->record_handle, samples, r->bufsize ); if( err < 0 ) { - fprintf( stderr, "Warning: ALSA Recording Failed\n" ); + fprintf( stderr, ALSA_PRINT_PREFIX"Warning: ALSA Recording Failed\n" ); break; } if( err != r->bufsize ) { - fprintf( stderr, "Warning: ALSA Recording Underflow\n" ); + fprintf( stderr, ALSA_PRINT_PREFIX"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" ); + fprintf( stderr, ALSA_PRINT_PREFIX"ALSA Recording Stopped\n" ); return 0; } @@ -1129,29 +2539,29 @@ void * PlayThread( void * v ) while( err >= 0 ) { // int avail = snd_pcm_avail(r->playback_handle); - // printf( "avail: %d\n", avail ); + // printf( ALSA_PRINT_PREFIX"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" ); + fprintf( stderr, ALSA_PRINT_PREFIX"Warning: ALSA Playback Overflow\n" ); } r->playing = 1; } r->playing = 0; - fprintf( stderr, "ALSA Playback Stopped\n" ); + fprintf( stderr, ALSA_PRINT_PREFIX"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 ); + printf( ALSA_PRINT_PREFIX"initialized %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", + if ((err = snd_pcm_open (&r->playback_handle, r->devPlay?r->devPlay:"hw:0,0", SND_PCM_STREAM_PLAYBACK, 0)) < 0) { + fprintf (stderr, ALSA_PRINT_PREFIX"cannot open output audio device (%s)\n", snd_strerror (err)); goto fail; } @@ -1159,15 +2569,13 @@ static struct CNFADriverAlsa * InitALSA( struct CNFADriverAlsa * r ) 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", + if ((err = snd_pcm_open (&r->record_handle, r->devRec?r->devRec:"hw:0,0", SND_PCM_STREAM_CAPTURE, 0)) < 0) { + fprintf (stderr, ALSA_PRINT_PREFIX"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 ) @@ -1192,7 +2600,7 @@ static struct CNFADriverAlsa * InitALSA( struct CNFADriverAlsa * r ) 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)); + printf(ALSA_PRINT_PREFIX"Playback callback handler error: %s\n", snd_strerror(err)); } } @@ -1203,7 +2611,7 @@ static struct CNFADriverAlsa * InitALSA( struct CNFADriverAlsa * r ) 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)); + printf(ALSA_PRINT_PREFIX"Record callback handler error: %s\n", snd_strerror(err)); } } #endif @@ -1213,7 +2621,7 @@ static struct CNFADriverAlsa * InitALSA( struct CNFADriverAlsa * r ) err = snd_pcm_link ( r->playback_handle, r->record_handle ); if(err < 0) { - printf("snd_pcm_link error: %s\n", snd_strerror(err)); + printf(ALSA_PRINT_PREFIX"snd_pcm_link error: %s\n", snd_strerror(err)); } } @@ -1227,7 +2635,7 @@ static struct CNFADriverAlsa * InitALSA( struct CNFADriverAlsa * r ) 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 ); + printf( ALSA_PRINT_PREFIX"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; @@ -1238,7 +2646,7 @@ static struct CNFADriverAlsa * InitALSA( struct CNFADriverAlsa * r ) if( r->record_handle ) snd_pcm_close (r->record_handle); free( r ); } - fprintf( stderr, "Error: ALSA failed to start.\n" ); + fprintf( stderr, ALSA_PRINT_PREFIX"ALSA failed to start.\n" ); return 0; } @@ -1285,6 +2693,8 @@ REGISTER_CNFA( ALSA, 10, "ALSA", InitALSADriver ); #include #include +#define PULSE_PRINT_PREFIX "[CNFA][Pulse]: " + #define BUFFERSETS 3 @@ -1384,7 +2794,7 @@ static void stream_record_cb(pa_stream *s, size_t length, void *userdata) uint16_t * bufr; if (pa_stream_peek(r->rec, (const void**)&bufr, &length) < 0) { - fprintf(stderr, ("pa_stream_peek() failed: %s\n"), pa_strerror(pa_context_errno(r->pa_ctx))); + fprintf(stderr, (PULSE_PRINT_PREFIX"pa_stream_peek() failed: %s\n"), pa_strerror(pa_context_errno(r->pa_ctx))); return; } @@ -1399,7 +2809,7 @@ static void stream_record_cb(pa_stream *s, size_t length, void *userdata) static void stream_underflow_cb(pa_stream *s, void *userdata) { - printf("underflow\n"); + printf(PULSE_PRINT_PREFIX"underflow\n"); } @@ -1439,14 +2849,14 @@ void * InitCNFAPulse( CNFACBType cb, const char * your_name, int reqSPSPlay, int r->pa_ml = pa_mainloop_new(); if( !r->pa_ml ) { - fprintf( stderr, "Failed to initialize pa_mainloop_new()\n" ); + fprintf( stderr, PULSE_PRINT_PREFIX"Failed to initialize pa_mainloop_new()\n" ); goto fail; } pa_mlapi = pa_mainloop_get_api(r->pa_ml); if( !pa_mlapi ) { - fprintf( stderr, "Failed to initialize pa_mainloop_get_api()\n" ); + fprintf( stderr, PULSE_PRINT_PREFIX"Failed to initialize pa_mainloop_get_api()\n" ); goto fail; } @@ -1470,7 +2880,7 @@ void * InitCNFAPulse( CNFACBType cb, const char * your_name, int reqSPSPlay, int r->rec = 0; r->buffer = sugBufferSize; - printf ("Pulse: from: [O/I] %s/%s (%s) / (%d,%d)x(%d,%d) (%d)\n", r->sourceNamePlay, r->sourceNameRec, title, r->spsPlay, r->spsRec, r->channelsPlay, r->channelsRec, r->buffer ); + printf (PULSE_PRINT_PREFIX"from: [O/I] %s/%s (%s) / (%d,%d)x(%d,%d) (%d)\n", r->sourceNamePlay, r->sourceNameRec, title, r->spsPlay, r->spsRec, r->channelsPlay, r->channelsRec, r->buffer ); memset( &ss, 0, sizeof( ss ) ); @@ -1494,7 +2904,7 @@ void * InitCNFAPulse( CNFACBType cb, const char * your_name, int reqSPSPlay, int if (!(r->play = pa_stream_new(r->pa_ctx, "Play", &ss, NULL))) { error = -3; //XXX ??? TODO - fprintf(stderr, __FILE__": pa_simple_new() failed: %s\n", pa_strerror(error)); + fprintf(stderr, PULSE_PRINT_PREFIX"pa_simple_new() failed: %s\n", pa_strerror(error)); goto fail; } @@ -1547,7 +2957,7 @@ void * InitCNFAPulse( CNFACBType cb, const char * your_name, int reqSPSPlay, int PA_STREAM_NOFLAGS, NULL, NULL ); if( ret < 0 ) { - fprintf(stderr, __FILE__": (PLAY) pa_stream_connect_playback() failed: %s\n", pa_strerror(ret)); + fprintf(stderr, PULSE_PRINT_PREFIX"(PLAY) pa_stream_connect_playback() failed: %s\n", pa_strerror(ret)); goto fail; } @@ -1560,7 +2970,7 @@ void * InitCNFAPulse( CNFACBType cb, const char * your_name, int reqSPSPlay, int if (!(r->rec = pa_stream_new(r->pa_ctx, "Record", &ss, NULL))) { error = -3; //XXX ??? TODO - fprintf(stderr, __FILE__": pa_simple_new() failed: %s\n", pa_strerror(error)); + fprintf(stderr, PULSE_PRINT_PREFIX"pa_simple_new() failed: %s\n", pa_strerror(error)); goto fail; } @@ -1577,17 +2987,14 @@ void * InitCNFAPulse( CNFACBType cb, const char * your_name, int reqSPSPlay, int // PA_STREAM_AUTO_TIMING_UPDATE // PA_STREAM_NOFLAGS ); - - printf( "PA REC RES: %d\n", ret ); - if( ret < 0 ) { - fprintf(stderr, __FILE__": (REC) pa_stream_connect_playback() failed: %s\n", pa_strerror(ret)); + fprintf(stderr, PULSE_PRINT_PREFIX"(REC) pa_stream_connect_playback() failed: %s\n", pa_strerror(ret)); goto fail; } } - printf( "Pulse initialized.\n" ); + printf( PULSE_PRINT_PREFIX"initialized.\n" ); r->thread = OGCreateThread( CNFAThread, r ); @@ -1616,7 +3023,247 @@ REGISTER_CNFA( PulseCNFA, 11, "PULSE", InitCNFAPulse ); #endif #elif defined(__APPLE__) -#include "CNFA_coreaudio.c" +//Copyright 2015-2020 <>< Charles Lohr under the MIT/x11, NewBSD or ColorChord License. You choose. + +// TODO: +// - Test recording +// - Device selection +// - dylib build + +#include "os_generic.h" +#include + +#include +#include +#include +#include + +#define BUFFERSETS 3 + + +// https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/CoreAudioOverview/CoreAudioEssentials/CoreAudioEssentials.html + + +struct CNFADriverCoreAudio +{ + void (*CloseFn)( void * object ); + int (*StateFn)( void * object ); + CNFACBType callback; + short channelsPlay; + short channelsRec; + int spsPlay; + int spsRec; + void * opaque; + + // CoreAudio-specific fields + char * sourceNamePlay; + char * sourceNameRec; + + AudioQueueRef play; + AudioQueueRef rec; + + AudioQueueBufferRef playBuffers[BUFFERSETS]; + AudioQueueBufferRef recBuffers[BUFFERSETS]; + + int buffer; +}; + +int CNFAStateCoreAudio( void * v ) +{ + struct CNFADriverCoreAudio * soundobject = (struct CNFADriverCoreAudio *)v; + return ((soundobject->play)?2:0) | ((soundobject->rec)?1:0); +} + +void CloseCNFACoreAudio( void * v ) +{ + struct CNFADriverCoreAudio * r = (struct CNFADriverCoreAudio *)v; + if( r ) + { + if( r->play ) + { + AudioQueueDispose(r->play, true); + r->play = 0; + } + + if( r->rec ) + { + AudioQueueDispose(r->rec, true); + r->rec = 0; + } + + if( r->sourceNamePlay ) free( r->sourceNamePlay ); + if( r->sourceNameRec ) free( r->sourceNameRec ); + free( r ); + } +} + +static void CoreAudioOutputCallback(void *userdata, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer) +{ + // Write our data to the input buffer + struct CNFADriverCoreAudio * r = (struct CNFADriverCoreAudio*)userdata; + if (!r->play) + { + return; + } + + r->callback((struct CNFADriver*)r, (short*)inBuffer->mAudioData, NULL, inBuffer->mAudioDataBytesCapacity / sizeof(short) / r->channelsPlay, 0); + // Assume the buffer was completely filled? + inBuffer->mAudioDataByteSize = inBuffer->mAudioDataBytesCapacity; + + // I think this isn't necessary for non-compressed audio formats + if (inBuffer->mPacketDescriptionCapacity > 0) + { + inBuffer->mPacketDescriptionCount = 1; + inBuffer->mPacketDescriptions[0].mDataByteSize = inBuffer->mAudioDataBytesCapacity; + inBuffer->mPacketDescriptions[0].mStartOffset = 0; + inBuffer->mPacketDescriptions[0].mVariableFramesInPacket = 0; + } + + // Actually send the buffer to be played! + AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, NULL); +} + +static void CoreAudioInputCallback(void *userdata, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer, const AudioTimeStamp *inStartTime, UInt32 inNumberPacketDescriptions, const AudioStreamPacketDescription *inPacketDescs) +{ + // Read our data from the output buffer + struct CNFADriverCoreAudio * r = (struct CNFADriverCoreAudio*)userdata; + r->callback((struct CNFADriver*)r, NULL, (short*)inBuffer->mAudioData, 0, inBuffer->mAudioDataByteSize / sizeof(short)); +} + +void * InitCNFACoreAudio( CNFACBType cb, const char * your_name, int reqSPSPlay, int reqSPSRec, int reqChannelsPlay, int reqChannelsRec, int sugBufferSize, const char * outputSelect, const char * inputSelect, void * opaque ) +{ + const char * title = your_name; + + struct CNFADriverCoreAudio * r = (struct CNFADriverCoreAudio *)malloc( sizeof( struct CNFADriverCoreAudio ) ); + + r->CloseFn = CloseCNFACoreAudio; + r->StateFn = CNFAStateCoreAudio; + r->callback = cb; + r->opaque = opaque; + r->spsPlay = reqSPSPlay; + r->spsRec = reqSPSRec; + r->channelsPlay = reqChannelsPlay; + r->channelsRec = reqChannelsRec; + r->sourceNamePlay = outputSelect?strdup(outputSelect):0; + r->sourceNameRec = inputSelect?strdup(inputSelect):0; + + memset(r->playBuffers, 0, sizeof(r->playBuffers)); + memset(r->recBuffers, 0, sizeof(r->recBuffers)); + + r->play = 0; + r->rec = 0; + r->buffer = sugBufferSize; + + printf ("CoreAudio: from: [O/I] %s/%s (%s) / (%d,%d)x(%d,%d) (%d)\n", r->sourceNamePlay, r->sourceNameRec, title, r->spsPlay, r->spsRec, r->channelsPlay, r->channelsRec, r->buffer ); + + int bufBytesPlay = r->buffer * sizeof(short) * r->channelsPlay; + int bufBytesRec = r->buffer * sizeof(short) * r->channelsRec; + + if( r->channelsPlay ) + { + AudioStreamBasicDescription playDesc = {0}; + playDesc.mFormatID = kAudioFormatLinearPCM; + playDesc.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; + playDesc.mSampleRate = r->spsPlay; + playDesc.mBitsPerChannel = 8 * sizeof(short); + playDesc.mChannelsPerFrame = r->channelsPlay; + // Bytes per channel, multiplied by number of channels + playDesc.mBytesPerFrame = r->channelsPlay * (playDesc.mBitsPerChannel / 8); + // Always 1 for uncompressed audio + playDesc.mFramesPerPacket = 1; + playDesc.mBytesPerPacket = playDesc.mBytesPerFrame * playDesc.mFramesPerPacket; + playDesc.mReserved = 0; + + OSStatus result = AudioQueueNewOutput(&playDesc, CoreAudioOutputCallback, (void*)r, NULL /* Default run loop*/, NULL /* Equivalent to kCFRunLoopCommonModes */, 0 /* flags, reserved*/, &r->play); + + if( 0 != result ) + { + fprintf(stderr, __FILE__": (PLAY) AudioQueueNewOutput() failed: %s\n", strerror(result)); + goto fail; + } + + for (int i = 0; i < BUFFERSETS; i++) + { + result = AudioQueueAllocateBuffer(r->play, bufBytesPlay, &r->playBuffers[i]); + + if (0 != result) + { + fprintf(stderr, __FILE__": (PLAY) AudioQueueNewOutput() failed: %s\n", strerror(result)); + goto fail; + } + + // Prefill buffer + CoreAudioOutputCallback(r, r->play, r->playBuffers[i]); + } + + AudioQueueStart(r->play, NULL); + } + + if( r->channelsRec ) + { + AudioStreamBasicDescription recDesc = {0}; + recDesc.mFormatID = kAudioFormatLinearPCM; + recDesc.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; + recDesc.mSampleRate = r->spsRec; + recDesc.mBitsPerChannel = 8 * sizeof(short); + recDesc.mChannelsPerFrame = r->channelsRec; + // Bytes per channel, multiplied by number of channels + recDesc.mBytesPerFrame = recDesc.mChannelsPerFrame * (recDesc.mBitsPerChannel / 8); + // Always 1 for uncompressed audio + recDesc.mFramesPerPacket = 1; + recDesc.mBytesPerPacket = recDesc.mBytesPerFrame * recDesc.mFramesPerPacket; + + OSStatus result = AudioQueueNewInput(&recDesc, CoreAudioInputCallback, (void*)r, NULL, NULL, 0, &r->rec); + + if (0 != result) + { + fprintf(stderr, __FILE__": (RECORD) AudioQueueNewInput() failed: %s\n", strerror(result)); + goto fail; + } + + for (int i = 0; i < BUFFERSETS; i++) + { + result = AudioQueueAllocateBuffer(r->rec, bufBytesRec, &r->recBuffers[i]); + + if (0 != result) + { + fprintf(stderr, __FILE__": (RECORD) AudioQueueNewOutput() failed: %s\n", strerror(result)); + goto fail; + } + + // No buffer prefill for input + } + + AudioQueueStart(r->rec, NULL); + } + + printf( "CoreAudio initialized.\n" ); + + return r; + +fail: + if( r ) + { + if( r->play ) + { + // Buffers are automatically freed with the queue + AudioQueueDispose (r->play, true); + } + + if( r->rec ) + { + // Buffers are automatically freed with the queue + AudioQueueDispose (r->rec, true); + } + free( r ); + } + return 0; +} + + + +REGISTER_CNFA( CoreAudioCNFA, 10, "COREAUDIO", InitCNFACoreAudio ); + #if defined(PULSEAUDIO) //Copyright 2015-2020 <>< Charles Lohr under the MIT/x11, NewBSD or ColorChord License. You choose. @@ -1632,6 +3279,8 @@ REGISTER_CNFA( PulseCNFA, 11, "PULSE", InitCNFAPulse ); #include #include +#define PULSE_PRINT_PREFIX "[CNFA][Pulse]: " + #define BUFFERSETS 3 @@ -1731,7 +3380,7 @@ static void stream_record_cb(pa_stream *s, size_t length, void *userdata) uint16_t * bufr; if (pa_stream_peek(r->rec, (const void**)&bufr, &length) < 0) { - fprintf(stderr, ("pa_stream_peek() failed: %s\n"), pa_strerror(pa_context_errno(r->pa_ctx))); + fprintf(stderr, (PULSE_PRINT_PREFIX"pa_stream_peek() failed: %s\n"), pa_strerror(pa_context_errno(r->pa_ctx))); return; } @@ -1746,7 +3395,7 @@ static void stream_record_cb(pa_stream *s, size_t length, void *userdata) static void stream_underflow_cb(pa_stream *s, void *userdata) { - printf("underflow\n"); + printf(PULSE_PRINT_PREFIX"underflow\n"); } @@ -1786,14 +3435,14 @@ void * InitCNFAPulse( CNFACBType cb, const char * your_name, int reqSPSPlay, int r->pa_ml = pa_mainloop_new(); if( !r->pa_ml ) { - fprintf( stderr, "Failed to initialize pa_mainloop_new()\n" ); + fprintf( stderr, PULSE_PRINT_PREFIX"Failed to initialize pa_mainloop_new()\n" ); goto fail; } pa_mlapi = pa_mainloop_get_api(r->pa_ml); if( !pa_mlapi ) { - fprintf( stderr, "Failed to initialize pa_mainloop_get_api()\n" ); + fprintf( stderr, PULSE_PRINT_PREFIX"Failed to initialize pa_mainloop_get_api()\n" ); goto fail; } @@ -1817,7 +3466,7 @@ void * InitCNFAPulse( CNFACBType cb, const char * your_name, int reqSPSPlay, int r->rec = 0; r->buffer = sugBufferSize; - printf ("Pulse: from: [O/I] %s/%s (%s) / (%d,%d)x(%d,%d) (%d)\n", r->sourceNamePlay, r->sourceNameRec, title, r->spsPlay, r->spsRec, r->channelsPlay, r->channelsRec, r->buffer ); + printf (PULSE_PRINT_PREFIX"from: [O/I] %s/%s (%s) / (%d,%d)x(%d,%d) (%d)\n", r->sourceNamePlay, r->sourceNameRec, title, r->spsPlay, r->spsRec, r->channelsPlay, r->channelsRec, r->buffer ); memset( &ss, 0, sizeof( ss ) ); @@ -1841,7 +3490,7 @@ void * InitCNFAPulse( CNFACBType cb, const char * your_name, int reqSPSPlay, int if (!(r->play = pa_stream_new(r->pa_ctx, "Play", &ss, NULL))) { error = -3; //XXX ??? TODO - fprintf(stderr, __FILE__": pa_simple_new() failed: %s\n", pa_strerror(error)); + fprintf(stderr, PULSE_PRINT_PREFIX"pa_simple_new() failed: %s\n", pa_strerror(error)); goto fail; } @@ -1894,7 +3543,7 @@ void * InitCNFAPulse( CNFACBType cb, const char * your_name, int reqSPSPlay, int PA_STREAM_NOFLAGS, NULL, NULL ); if( ret < 0 ) { - fprintf(stderr, __FILE__": (PLAY) pa_stream_connect_playback() failed: %s\n", pa_strerror(ret)); + fprintf(stderr, PULSE_PRINT_PREFIX"(PLAY) pa_stream_connect_playback() failed: %s\n", pa_strerror(ret)); goto fail; } @@ -1907,7 +3556,7 @@ void * InitCNFAPulse( CNFACBType cb, const char * your_name, int reqSPSPlay, int if (!(r->rec = pa_stream_new(r->pa_ctx, "Record", &ss, NULL))) { error = -3; //XXX ??? TODO - fprintf(stderr, __FILE__": pa_simple_new() failed: %s\n", pa_strerror(error)); + fprintf(stderr, PULSE_PRINT_PREFIX"pa_simple_new() failed: %s\n", pa_strerror(error)); goto fail; } @@ -1924,17 +3573,14 @@ void * InitCNFAPulse( CNFACBType cb, const char * your_name, int reqSPSPlay, int // PA_STREAM_AUTO_TIMING_UPDATE // PA_STREAM_NOFLAGS ); - - printf( "PA REC RES: %d\n", ret ); - if( ret < 0 ) { - fprintf(stderr, __FILE__": (REC) pa_stream_connect_playback() failed: %s\n", pa_strerror(ret)); + fprintf(stderr, PULSE_PRINT_PREFIX"(REC) pa_stream_connect_playback() failed: %s\n", pa_strerror(ret)); goto fail; } } - printf( "Pulse initialized.\n" ); + printf( PULSE_PRINT_PREFIX"initialized.\n" ); r->thread = OGCreateThread( CNFAThread, r ); diff --git a/CNFA_sun.c b/CNFA_sun.c index 5d04bf7..0c0e347 100644 --- a/CNFA_sun.c +++ b/CNFA_sun.c @@ -8,6 +8,8 @@ #include #include +#define SUN_PRINT_PREFIX "[CNFA][Sun]: " + struct CNFADriverSun { void (*CloseFn)( void * object ); @@ -74,14 +76,14 @@ void * RecThread( void * v ) int nread = read( r->record_handle, r->samplesRec, nbytes ); if( nread < 0 ) { - fprintf( stderr, "Warning: Sun Recording Failed\n" ); + fprintf( stderr, SUN_PRINT_PREFIX"Recording Failed\n" ); break; } r->recording = 1; r->callback( (struct CNFADriver *)r, NULL, r->samplesRec, 0, (nread / 2) / r->channelsRec); } while( 1 ); r->recording = 0; - fprintf( stderr, "Sun Recording Stopped\n" ); + fprintf( stderr, SUN_PRINT_PREFIX"Recording Stopped\n" ); return 0; } @@ -101,7 +103,7 @@ void * PlayThread( void * v ) r->playing = 1; } r->playing = 0; - fprintf( stderr, "Sun Playback Stopped\n" ); + fprintf( stderr, SUN_PRINT_PREFIX"Playback Stopped\n" ); return 0; } @@ -121,13 +123,13 @@ static struct CNFADriverSun * InitSun( struct CNFADriverSun * r ) devPlay = "/dev/audio"; } - printf( "CNFA Sun Init -> devPlay: %s, channelsPlay: %d, spsPlay: %d, devRec: %s, channelsRec: %d, spsRec: %d\n", devPlay, r->channelsPlay, r->spsPlay, devRec, r->channelsRec, r->spsRec); + printf( SUN_PRINT_PREFIX"Init -> devPlay: %s, channelsPlay: %d, spsPlay: %d, devRec: %s, channelsRec: %d, spsRec: %d\n", devPlay, r->channelsPlay, r->spsPlay, devRec, r->channelsRec, r->spsRec); if( r->channelsPlay && r->channelsRec && strcmp (devPlay, devRec) == 0 ) { if ( (r->playback_handle = r->record_handle = open (devPlay, O_RDWR)) < 0 ) { - fprintf (stderr, "cannot open audio device (%s)\n", + fprintf (stderr, SUN_PRINT_PREFIX"cannot open audio device (%s)\n", strerror (errno)); goto fail; } @@ -138,7 +140,7 @@ static struct CNFADriverSun * InitSun( struct CNFADriverSun * r ) { if ( (r->playback_handle = open (devPlay, O_WRONLY)) < 0 ) { - fprintf (stderr, "cannot open output audio device %s (%s)\n", + fprintf (stderr, SUN_PRINT_PREFIX"cannot open output audio device %s (%s)\n", r->devPlay, strerror (errno)); goto fail; } @@ -148,7 +150,7 @@ static struct CNFADriverSun * InitSun( struct CNFADriverSun * r ) { if ( (r->record_handle = open (devRec, O_RDONLY)) < 0 ) { - fprintf (stderr, "cannot open input audio device %s (%s)\n", + fprintf (stderr, SUN_PRINT_PREFIX"cannot open input audio device %s (%s)\n", r->devRec, strerror (errno)); goto fail; } @@ -166,14 +168,14 @@ static struct CNFADriverSun * InitSun( struct CNFADriverSun * r ) if ( ioctl(r->playback_handle, AUDIO_SETINFO, &pinfo) < 0 ) { - fprintf (stderr, "cannot set audio playback format (%s)\n", + fprintf (stderr, SUN_PRINT_PREFIX"cannot set audio playback format (%s)\n", strerror (errno)); goto fail; } if ( ioctl(r->playback_handle, AUDIO_GETINFO, &pinfo) < 0 ) { - fprintf (stderr, "cannot get audio record format (%s)\n", + fprintf (stderr, SUN_PRINT_PREFIX"cannot get audio record format (%s)\n", strerror (errno)); goto fail; } @@ -198,14 +200,14 @@ static struct CNFADriverSun * InitSun( struct CNFADriverSun * r ) if ( ioctl(r->record_handle, AUDIO_SETINFO, &rinfo) < 0 ) { - fprintf (stderr, "cannot set audio record format (%s)\n", + fprintf (stderr, SUN_PRINT_PREFIX"cannot set audio record format (%s)\n", strerror (errno)); goto fail; } if ( ioctl(r->record_handle, AUDIO_GETINFO, &rinfo) < 0 ) { - fprintf (stderr, "cannot get audio record format (%s)\n", + fprintf (stderr, SUN_PRINT_PREFIX"cannot get audio record format (%s)\n", strerror (errno)); goto fail; } @@ -229,7 +231,7 @@ static struct CNFADriverSun * InitSun( struct CNFADriverSun * r ) r->threadRec = OGCreateThread( RecThread, r ); } - printf( "CNFA Sun Init Out -> channelsPlay: %d, spsPlay: %d, channelsRec: %d, spsRec: %d\n", r->channelsPlay, r->spsPlay, r->channelsRec, r->spsRec); + printf( SUN_PRINT_PREFIX"Init Out -> channelsPlay: %d, spsPlay: %d, channelsRec: %d, spsRec: %d\n", r->channelsPlay, r->spsPlay, r->channelsRec, r->spsRec); return r; diff --git a/CNFA_wasapi.c b/CNFA_wasapi.c index 0eba70b..75dfd16 100644 --- a/CNFA_wasapi.c +++ b/CNFA_wasapi.c @@ -26,8 +26,8 @@ //And maybe mmdevapi.lib #endif -#define WASAPIPRINT(message) (printf("[WASAPI] %s\n", message)) -#define WASAPIERROR(error, message) (printf("[WASAPI][ERR] %s HRESULT: 0x%lX\n", message, error)) +#define WASAPIPRINT(message) (printf("[CNFA][WASAPI]: %s\n", message)) +#define WASAPIERROR(error, message) (printf("[CNFA][WASAPI][ERR]: %s HRESULT: 0x%lX\n", message, error)) #define PRINTGUID(guid) (printf("{%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}", guid.Data1, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7])) #define WASAPI_EXTRA_DEBUG FALSE @@ -114,7 +114,7 @@ void CloseCNFAWASAPI(void* stateObj) if (state->DeviceEnumerator != NULL) { state->DeviceEnumerator->lpVtbl->Release(state->DeviceEnumerator); } free(stateObj); CoUninitialize(); - printf("[WASAPI] Cleanup completed. Goodbye.\n"); + printf("[CNFA][WASAPI]: Cleanup completed. Goodbye.\n"); } } @@ -152,15 +152,15 @@ static struct CNFADriverWASAPI* StartWASAPIDriver(struct CNFADriverWASAPI* initS if(WASAPI_EXTRA_DEBUG) { - printf("[WASAPI] CLSID for MMDeviceEnumerator: "); + printf("[CNFA][WASAPI]: CLSID for MMDeviceEnumerator: "); PRINTGUID(CLSID_MMDeviceEnumerator); - printf("\n[WASAPI] IID for IMMDeviceEnumerator: "); + printf("\n[CNFA][WASAPI]: IID for IMMDeviceEnumerator: "); PRINTGUID(IID_IMMDeviceEnumerator); - printf("\n[WASAPI] IID for IAudioClient: "); + printf("\n[CNFA][WASAPI]: IID for IAudioClient: "); PRINTGUID(IID_IAudioClient); - printf("\n[WASAPI] IID for IAudioCaptureClient: "); + printf("\n[CNFA][WASAPI]: IID for IAudioCaptureClient: "); PRINTGUID(IID_IAudioCaptureClient); - printf("\n[WASAPI] IID for IMMEndpoint: "); + printf("\n[CNFA][WASAPI]: IID for IMMEndpoint: "); PRINTGUID(IID_IMMEndpoint); printf("\n"); } @@ -189,7 +189,7 @@ static struct CNFADriverWASAPI* StartWASAPIDriver(struct CNFADriverWASAPI* initS { BOOL IsMultimedia = TRUE; if (strstr(WASAPIState->InputDeviceID, "Comm") != NULL) { IsMultimedia = FALSE; } - printf("[WASAPI] Attempting to use system default %s capture device as input.\n", (IsMultimedia ? "multimedia" : "communications")); + printf("[CNFA][WASAPI]: Attempting to use system default %s capture device as input.\n", (IsMultimedia ? "multimedia" : "communications")); WASAPIState->Device = WASAPIGetDefaultDevice(TRUE, IsMultimedia); DeviceDirection = 1; } @@ -198,7 +198,7 @@ static struct CNFADriverWASAPI* StartWASAPIDriver(struct CNFADriverWASAPI* initS LPWSTR DeviceIDasLPWSTR; DeviceIDasLPWSTR = malloc((strlen(WASAPIState->InputDeviceID) + 1) * sizeof(WCHAR)); mbstowcs(DeviceIDasLPWSTR, WASAPIState->InputDeviceID, strlen(WASAPIState->InputDeviceID) + 1); - printf("[WASAPI] Attempting to find specified device \"%ls\".\n", DeviceIDasLPWSTR); + printf("[CNFA][WASAPI]: Attempting to find specified device \"%ls\".\n", DeviceIDasLPWSTR); ErrorCode = WASAPIState->DeviceEnumerator->lpVtbl->GetDevice(WASAPIState->DeviceEnumerator, DeviceIDasLPWSTR, &(WASAPIState->Device)); if (FAILED(ErrorCode)) @@ -209,7 +209,7 @@ static struct CNFADriverWASAPI* StartWASAPIDriver(struct CNFADriverWASAPI* initS } else { - printf("[WASAPI] Found specified device.\n"); + printf("[CNFA][WASAPI]: Found specified device.\n"); DWORD DeviceState; ErrorCode = WASAPIState->Device->lpVtbl->GetState(WASAPIState->Device, &DeviceState); if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get device state."); } @@ -241,7 +241,7 @@ static struct CNFADriverWASAPI* StartWASAPIDriver(struct CNFADriverWASAPI* initS LPWSTR DeviceID; ErrorCode = WASAPIState->Device->lpVtbl->GetId(WASAPIState->Device, &DeviceID); if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get audio device ID."); return WASAPIState; } - else { printf("[WASAPI] Using device ID \"%ls\", which is a %s device.\n", DeviceID, DeviceDirectionDesc); } + else { printf("[CNFA][WASAPI]: Using device ID \"%ls\", which is a %s device.\n", DeviceID, DeviceDirectionDesc); } // Start an audio client and get info about the stream format. ErrorCode = WASAPIState->Device->lpVtbl->Activate(WASAPIState->Device, &IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&(WASAPIState->Client)); @@ -249,8 +249,8 @@ static struct CNFADriverWASAPI* StartWASAPIDriver(struct CNFADriverWASAPI* initS ErrorCode = WASAPIState->Client->lpVtbl->GetMixFormat(WASAPIState->Client, &(WASAPIState->MixFormat)); if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get mix format. "); return WASAPIState; } - printf("[WASAPI] Mix format is %d channel, %luHz sample rate, %db per sample.\n", WASAPIState->MixFormat->nChannels, WASAPIState->MixFormat->nSamplesPerSec, WASAPIState->MixFormat->wBitsPerSample); - printf("[WASAPI] Mix format is format %d, %dB block-aligned, with %dB of extra data in this definition.\n", WASAPIState->MixFormat->wFormatTag, WASAPIState->MixFormat->nBlockAlign, WASAPIState->MixFormat->cbSize); + printf("[CNFA][WASAPI]: Mix format is %d channel, %luHz sample rate, %db per sample.\n", WASAPIState->MixFormat->nChannels, WASAPIState->MixFormat->nSamplesPerSec, WASAPIState->MixFormat->wBitsPerSample); + printf("[CNFA][WASAPI]: Mix format is format %d, %dB block-aligned, with %dB of extra data in this definition.\n", WASAPIState->MixFormat->wFormatTag, WASAPIState->MixFormat->nBlockAlign, WASAPIState->MixFormat->cbSize); // We'll request PCM, 16bpS data from the system. It should be able to do this conversion for us, as long as we are not in exclusive mode. // TODO: This isn't working, no matter what combination I try to ask it for. Figure this out, so we don't have to do the conversion ourselves. @@ -267,7 +267,7 @@ static struct CNFADriverWASAPI* StartWASAPIDriver(struct CNFADriverWASAPI* initS REFERENCE_TIME DefaultInterval, MinimumInterval; ErrorCode = WASAPIState->Client->lpVtbl->GetDevicePeriod(WASAPIState->Client, &DefaultInterval, &MinimumInterval); if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get device timing info. "); return WASAPIState; } - printf("[WASAPI] Default transaction period is %lld ticks, minimum is %lld ticks.\n", DefaultInterval, MinimumInterval); + printf("[CNFA][WASAPI]: Default transaction period is %lld ticks, minimum is %lld ticks.\n", DefaultInterval, MinimumInterval); // Configure a capture client. UINT32 StreamFlags; @@ -329,7 +329,7 @@ static void WASAPIPrintAllDeviceLists() // Prints a list of all available devices of a specified data flow direction to the console. static void WASAPIPrintDeviceList(EDataFlow dataFlow) { - printf("[WASAPI] %s Devices:\n", (dataFlow == eCapture ? "Capture" : "Render")); + printf("[CNFA][WASAPI]: %s Devices:\n", (dataFlow == eCapture ? "Capture" : "Render")); IMMDeviceCollection* Devices; HRESULT ErrorCode = WASAPIState->DeviceEnumerator->lpVtbl->EnumAudioEndpoints(WASAPIState->DeviceEnumerator, dataFlow, (WASAPI_EXTRA_DEBUG ? DEVICE_STATEMASK_ALL : DEVICE_STATE_ACTIVE), &Devices); if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get audio endpoints."); return; } @@ -361,7 +361,7 @@ static void WASAPIPrintDeviceList(EDataFlow dataFlow) LPWSTR DeviceFriendlyName = L"[Name Retrieval Failed]"; if (Variant.pwszVal != NULL) { DeviceFriendlyName = Variant.pwszVal; } - printf("[WASAPI] [%d]: \"%ls\" = \"%ls\"\n", DeviceIndex, DeviceFriendlyName, DeviceID); + printf("[CNFA][WASAPI]: [%d]: \"%ls\" = \"%ls\"\n", DeviceIndex, DeviceFriendlyName, DeviceID); CoTaskMemFree(DeviceID); DeviceID = NULL; @@ -423,7 +423,7 @@ void* ProcessEventAudioIn(void* stateObj) if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to release audio buffer."); } else { Released = TRUE; } - if (WASAPI_EXTRA_DEBUG) { printf("[WASAPI] SILENCE buffer received. Passing on %d samples.\n", Length); } + if (WASAPI_EXTRA_DEBUG) { printf("[CNFA][WASAPI]: SILENCE buffer received. Passing on %d samples.\n", Length); } WASAPIState->Callback((struct CNFADriver*)WASAPIState, 0, AudioData, 0, Length / state->MixFormat->nChannels ); free(AudioData); @@ -442,7 +442,7 @@ void* ProcessEventAudioIn(void* stateObj) if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to release audio buffer."); } else { Released = TRUE; } - if (WASAPI_EXTRA_DEBUG) { printf("[WASAPI] Got %d bytes of audio data in %d frames. Fowarding to %p.\n", Size, FramesAvailable, (void*) WASAPIState->Callback); } + if (WASAPI_EXTRA_DEBUG) { printf("[CNFA][WASAPI]: Got %d bytes of audio data in %d frames. Fowarding to %p.\n", Size, FramesAvailable, (void*) WASAPIState->Callback); } WASAPIState->Callback((struct CNFADriver*)WASAPIState, 0, AudioData, 0, FramesAvailable ); free(AudioData); diff --git a/Makefile b/Makefile index 76aab05..4d25dcf 100644 --- a/Makefile +++ b/Makefile @@ -56,7 +56,13 @@ CNFA_sf.h : tools/single_file_creator CNFA.h echo "//This file was automatically generated by Makefile at https://github.com/cnlohr/cnfa" > $@ echo "//This single-file feature is still in early alpha testing." >> $@f echo "//Generated from files git hash $(shell git rev-parse HEAD) on $(shell date) (This is not the git hash of this file)" >> $@ - ./tools/single_file_creator CNFA.h CNFA.c CNFA_android.c CNFA_alsa.c CNFA_null.c CNFA_pulse.c CNFA_sun.c CNFA_wasapi.c CNFA_winmm.c >> $@ + ./tools/single_file_creator \ + CNFA.h CNFA.c CNFA_null.c \ + CNFA_wasapi.c CNFA_wasapi_utils.h CNFA_winmm.c\ + CNFA_alsa.c CNFA_pulse.c \ + CNFA_sun.c \ + CNFA_coreaudio.c \ + CNFA_android.c >> $@ clean : -rm -rf *.o *~ example libCNFA.so wav_player