BMO_state_t *bmo_pa_start( BMO_state_t *state, uint32_t channels, uint32_t rate, uint32_t buf_len, uint32_t flags ) { (void)flags; // TODO if (!state->ringbuffer) { state->ringbuffer = bmo_init_rb(buf_len, channels); } state->driver_id = BMO_PORTAUDIO_DRIVER; state->driver.pa.error_num = Pa_Initialize(); if (state->driver.pa.error_num) { bmo_err( "Cannot initialise PortAudio:\n" "%s\n", Pa_GetErrorText(state->driver.pa.error_num) ); return NULL; } /* TODO should be enumerating available devices, and choosing best option, rather than falling back on given defaults */ state->driver.pa.output_params.device = Pa_GetDefaultOutputDevice(); if (state->driver.pa.output_params.device == paNoDevice) { bmo_err("No default portaudio output device available.\n"); } // portaudio uses a signed int here. We are passing an unisgned int assert(((int64_t)channels) < INT_MAX); state->driver.pa.output_params.channelCount = (int)channels; state->n_playback_ch = channels; state->driver.pa.output_params.sampleFormat = paFloat32 | paNonInterleaved; state->driver.pa.output_params.suggestedLatency = \ Pa_GetDeviceInfo(state->driver.pa.output_params.device)->defaultLowOutputLatency; state->driver.pa.output_params.hostApiSpecificStreamInfo = NULL; /* Open an audio I/O stream. */ bmo_debug("opening stream\n"); state->driver.pa.error_num = Pa_OpenStream( &(state->driver.pa.stream), NULL, // input parameters aren't accounted for yet TODO &(state->driver.pa.output_params), (double)rate, (unsigned long)buf_len, paClipOff, _bmo_pa_process_cb, state ); if (state->driver.pa.error_num != paNoError) { bmo_err( "couldn't open portaudio stream:" "%s\n", Pa_GetErrorText(state->driver.pa.error_num) ); return NULL; } bmo_debug("setting callbacks\n"); state->driver.pa.error_num = Pa_SetStreamFinishedCallback( state->driver.pa.stream, _bmo_pa_finished_cb ); if (state->driver.pa.error_num != paNoError) { bmo_err( "couldn't set exit conditions for portaudio stream:" "%s\n", Pa_GetErrorText(state->driver.pa.error_num) ); return NULL; } bmo_debug("starting stream\n"); state->driver.pa.error_num = Pa_StartStream(state->driver.pa.stream); if (state->driver.pa.error_num != paNoError) { bmo_err( "couldn't start portaudio stream:" "%s\n", Pa_GetErrorText(state->driver.pa.error_num) ); return NULL; } return state; }
int main(void) { PaStreamParameters outputParameters; PaStream *stream; PaError err; TestData data; int i, j; printf( "PortAudio Test: output sine wave. SR = %d, BufSize = %d\n", SAMPLE_RATE, FRAMES_PER_BUFFER ); /* initialise sinusoidal wavetable */ for( i=0; i<TABLE_SIZE; i++ ) { data.sine[i] = (float) sin( ((double)i/(double)TABLE_SIZE) * M_PI * 2. ); } err = Pa_Initialize(); if( err != paNoError ) goto error; outputParameters.device = Pa_GetDefaultOutputDevice(); if (outputParameters.device == paNoDevice) { fprintf(stderr,"Error: No default output device.\n"); goto error; } outputParameters.channelCount = 2; /* stereo output */ outputParameters.sampleFormat = paFloat32; /* 32 bit floating point output */ outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency; outputParameters.hostApiSpecificStreamInfo = NULL; err = Pa_OpenStream( &stream, NULL, /* no input */ &outputParameters, SAMPLE_RATE, FRAMES_PER_BUFFER, paClipOff, /* output will be in-range, so no need to clip */ TestCallback, &data ); if( err != paNoError ) goto error; sprintf( data.message, "Loop: XX" ); err = Pa_SetStreamFinishedCallback( stream, &StreamFinished ); if( err != paNoError ) goto error; printf("Repeating test %d times.\n", NUM_LOOPS ); for( i=0; i < NUM_LOOPS; ++i ) { data.phase = 0; data.generatedFramesCount = 0; data.callbackReturnedPaComplete = 0; data.callbackInvokedAfterReturningPaComplete = 0; sprintf( data.message, "Loop: %d", i ); err = Pa_StartStream( stream ); if( err != paNoError ) goto error; printf("Play for %d seconds.\n", NUM_SECONDS ); /* wait for the callback to complete generating NUM_SECONDS of tone */ do { Pa_Sleep( 500 ); } while( !data.callbackReturnedPaComplete ); printf( "Callback returned paComplete.\n" ); printf( "Waiting for buffers to finish playing...\n" ); /* wait for stream to become inactive, or for a timeout of approximately NUM_SECONDS */ j = 0; while( (err = Pa_IsStreamActive( stream )) == 1 && j < NUM_SECONDS * 2 ) { printf(".\n" ); Pa_Sleep( 500 ); ++j; } if( err < 0 ) { goto error; } else if( err == 1 ) { printf( "TEST FAILED: Timed out waiting for buffers to finish playing.\n" ); } else { printf("Buffers finished.\n" ); } if( data.callbackInvokedAfterReturningPaComplete ) { printf( "TEST FAILED: Callback was invoked after returning paComplete.\n" ); } err = Pa_StopStream( stream ); if( err != paNoError ) goto error; printf( "sleeping for 1 second...\n" ); Pa_Sleep( 1000 ); } err = Pa_CloseStream( stream ); if( err != paNoError ) goto error; Pa_Terminate(); printf("Test finished.\n"); return err; error: Pa_Terminate(); fprintf( stderr, "An error occured while using the portaudio stream\n" ); fprintf( stderr, "Error number: %d\n", err ); fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); return err; }