1 /* 2 * Copyright (C) 2017 BlueKitchen GmbH 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. Neither the name of the copyright holders nor the names of 14 * contributors may be used to endorse or promote products derived 15 * from this software without specific prior written permission. 16 * 4. Any redistribution, use, or modification is done solely for 17 * personal benefit and not for any commercial purpose or for 18 * monetary gain. 19 * 20 * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS 21 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 23 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLUEKITCHEN 24 * GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 25 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 26 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 27 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 28 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 30 * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31 * SUCH DAMAGE. 32 * 33 * Please inquire about commercial licensing options at 34 * [email protected] 35 * 36 */ 37 38 #define BTSTACK_FILE__ "btstack_audio_portaudio.c" 39 40 41 #include <stdint.h> 42 #include <string.h> 43 #include "btstack_debug.h" 44 #include "btstack_audio.h" 45 #include "btstack_run_loop.h" 46 47 #ifdef HAVE_PORTAUDIO 48 49 #define PA_SAMPLE_TYPE paInt16 50 #define NUM_FRAMES_PER_PA_BUFFER 512 51 #define NUM_OUTPUT_BUFFERS 3 52 #define NUM_INPUT_BUFFERS 2 53 #define DRIVER_POLL_INTERVAL_MS 5 54 55 #include <portaudio.h> 56 57 // config 58 static int num_channels_sink; 59 static int num_channels_source; 60 static int num_bytes_per_sample_sink; 61 static int num_bytes_per_sample_source; 62 63 // portaudio 64 static int portaudio_initialized; 65 66 // state 67 static int source_initialized; 68 static int sink_initialized; 69 static int source_active; 70 static int sink_active; 71 72 static uint8_t sink_volume; 73 74 static PaStream * stream_source; 75 static PaStream * stream_sink; 76 77 // client 78 static void (*playback_callback)(int16_t * buffer, uint16_t num_samples); 79 static void (*recording_callback)(const int16_t * buffer, uint16_t num_samples); 80 81 // output buffer 82 static int16_t output_buffer_a[NUM_FRAMES_PER_PA_BUFFER * 2]; // stereo 83 static int16_t output_buffer_b[NUM_FRAMES_PER_PA_BUFFER * 2]; // stereo 84 static int16_t output_buffer_c[NUM_FRAMES_PER_PA_BUFFER * 2]; // stereo 85 static int16_t * output_buffers[NUM_OUTPUT_BUFFERS] = { output_buffer_a, output_buffer_b, output_buffer_c}; 86 static int output_buffer_to_play; 87 static int output_buffer_to_fill; 88 89 // input buffer 90 static int16_t input_buffer_a[NUM_FRAMES_PER_PA_BUFFER * 2]; // stereo 91 static int16_t input_buffer_b[NUM_FRAMES_PER_PA_BUFFER * 2]; // stereo 92 static int16_t * input_buffers[NUM_INPUT_BUFFERS] = { input_buffer_a, input_buffer_b}; 93 static int input_buffer_to_record; 94 static int input_buffer_to_fill; 95 96 97 // timer to fill output ring buffer 98 static btstack_timer_source_t driver_timer_sink; 99 static btstack_timer_source_t driver_timer_source; 100 101 static int portaudio_callback_sink( const void * inputBuffer, 102 void * outputBuffer, 103 unsigned long samples_per_buffer, 104 const PaStreamCallbackTimeInfo * timeInfo, 105 PaStreamCallbackFlags statusFlags, 106 void * userData ) { 107 108 /** portaudio_callback is called from different thread, don't use hci_dump / log_info here without additional checks */ 109 110 (void) timeInfo; /* Prevent unused variable warnings. */ 111 (void) statusFlags; 112 (void) userData; 113 (void) samples_per_buffer; 114 (void) inputBuffer; 115 116 // simplified volume control 117 uint16_t index; 118 int16_t * from_buffer = output_buffers[output_buffer_to_play]; 119 int16_t * to_buffer = (int16_t *) outputBuffer; 120 121 #if 0 122 // up to 8 right shifts 123 int right_shift = 8 - btstack_min(8, ((sink_volume + 15) / 16)); 124 for (index = 0; index < (NUM_FRAMES_PER_PA_BUFFER * num_channels_sink); index++){ 125 *to_buffer++ = (*from_buffer++) >> right_shift; 126 } 127 #else 128 // multiply with volume ^ 4 129 int16_t x2 = sink_volume * sink_volume; 130 int16_t x4 = (x2 * x2) >> 14; 131 for (index = 0; index < (NUM_FRAMES_PER_PA_BUFFER * num_channels_sink); index++){ 132 *to_buffer++ = ((*from_buffer++) * x4) >> 14; 133 } 134 #endif 135 136 // next 137 output_buffer_to_play = (output_buffer_to_play + 1 ) % NUM_OUTPUT_BUFFERS; 138 139 return 0; 140 } 141 142 static int portaudio_callback_source( const void * inputBuffer, 143 void * outputBuffer, 144 unsigned long samples_per_buffer, 145 const PaStreamCallbackTimeInfo * timeInfo, 146 PaStreamCallbackFlags statusFlags, 147 void * userData ) { 148 149 /** portaudio_callback is called from different thread, don't use hci_dump / log_info here without additional checks */ 150 151 (void) timeInfo; /* Prevent unused variable warnings. */ 152 (void) statusFlags; 153 (void) userData; 154 (void) samples_per_buffer; 155 (void) outputBuffer; 156 157 // store in one of our buffers 158 memcpy(input_buffers[input_buffer_to_fill], inputBuffer, NUM_FRAMES_PER_PA_BUFFER * num_bytes_per_sample_source); 159 160 // next 161 input_buffer_to_fill = (input_buffer_to_fill + 1 ) % NUM_INPUT_BUFFERS; 162 163 return 0; 164 } 165 166 static void driver_timer_handler_sink(btstack_timer_source_t * ts){ 167 168 // playback buffer ready to fill 169 if (output_buffer_to_play != output_buffer_to_fill){ 170 (*playback_callback)(output_buffers[output_buffer_to_fill], NUM_FRAMES_PER_PA_BUFFER); 171 172 // next 173 output_buffer_to_fill = (output_buffer_to_fill + 1 ) % NUM_OUTPUT_BUFFERS; 174 } 175 176 // re-set timer 177 btstack_run_loop_set_timer(ts, DRIVER_POLL_INTERVAL_MS); 178 btstack_run_loop_add_timer(ts); 179 } 180 181 static void driver_timer_handler_source(btstack_timer_source_t * ts){ 182 183 // recording buffer ready to process 184 if (input_buffer_to_record != input_buffer_to_fill){ 185 186 (*recording_callback)(input_buffers[input_buffer_to_record], NUM_FRAMES_PER_PA_BUFFER); 187 188 // next 189 input_buffer_to_record = (input_buffer_to_record + 1 ) % NUM_INPUT_BUFFERS; 190 } 191 192 // re-set timer 193 btstack_run_loop_set_timer(ts, DRIVER_POLL_INTERVAL_MS); 194 btstack_run_loop_add_timer(ts); 195 } 196 197 static int btstack_audio_portaudio_sink_init( 198 uint8_t channels, 199 uint32_t samplerate, 200 void (*playback)(int16_t * buffer, uint16_t num_samples) 201 ){ 202 PaError err; 203 204 num_channels_sink = channels; 205 num_bytes_per_sample_sink = 2 * channels; 206 207 if (!playback){ 208 log_error("No playback callback"); 209 return 1; 210 } 211 212 /* -- initialize PortAudio -- */ 213 if (!portaudio_initialized){ 214 err = Pa_Initialize(); 215 if (err != paNoError){ 216 log_error("Portudio: error initializing portaudio: \"%s\"\n", Pa_GetErrorText(err)); 217 return err; 218 } 219 portaudio_initialized = 1; 220 } 221 222 /* -- setup output -- */ 223 PaStreamParameters theOutputParameters; 224 theOutputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */ 225 theOutputParameters.channelCount = channels; 226 theOutputParameters.sampleFormat = PA_SAMPLE_TYPE; 227 theOutputParameters.suggestedLatency = Pa_GetDeviceInfo( theOutputParameters.device )->defaultHighOutputLatency; 228 theOutputParameters.hostApiSpecificStreamInfo = NULL; 229 230 const PaDeviceInfo *outputDeviceInfo; 231 outputDeviceInfo = Pa_GetDeviceInfo( theOutputParameters.device ); 232 log_info("PortAudio: sink device: %s", outputDeviceInfo->name); 233 UNUSED(outputDeviceInfo); 234 235 /* -- setup stream -- */ 236 err = Pa_OpenStream( 237 &stream_sink, 238 NULL, 239 &theOutputParameters, 240 samplerate, 241 NUM_FRAMES_PER_PA_BUFFER, 242 paClipOff, /* we won't output out of range samples so don't bother clipping them */ 243 portaudio_callback_sink, /* use callback */ 244 NULL ); 245 246 if (err != paNoError){ 247 log_error("Portudio: error initializing portaudio: \"%s\"\n", Pa_GetErrorText(err)); 248 return err; 249 } 250 log_info("PortAudio: sink stream created"); 251 252 const PaStreamInfo * stream_info = Pa_GetStreamInfo(stream_sink); 253 log_info("PortAudio: sink latency: %f", stream_info->outputLatency); 254 UNUSED(stream_info); 255 256 playback_callback = playback; 257 258 sink_initialized = 1; 259 sink_volume = 127; 260 261 return 0; 262 } 263 264 static int btstack_audio_portaudio_source_init( 265 uint8_t channels, 266 uint32_t samplerate, 267 void (*recording)(const int16_t * buffer, uint16_t num_samples) 268 ){ 269 PaError err; 270 271 num_channels_source = channels; 272 num_bytes_per_sample_source = 2 * channels; 273 274 if (!recording){ 275 log_error("No recording callback"); 276 return 1; 277 } 278 279 /* -- initialize PortAudio -- */ 280 if (!portaudio_initialized){ 281 err = Pa_Initialize(); 282 if (err != paNoError){ 283 log_error("Portudio: Error initializing portaudio: \"%s\"\n", Pa_GetErrorText(err)); 284 return err; 285 } 286 portaudio_initialized = 1; 287 } 288 289 /* -- setup input -- */ 290 PaStreamParameters theInputParameters; 291 theInputParameters.device = Pa_GetDefaultInputDevice(); /* default input device */ 292 theInputParameters.channelCount = channels; 293 theInputParameters.sampleFormat = PA_SAMPLE_TYPE; 294 theInputParameters.suggestedLatency = Pa_GetDeviceInfo( theInputParameters.device )->defaultHighInputLatency; 295 theInputParameters.hostApiSpecificStreamInfo = NULL; 296 297 const PaDeviceInfo *inputDeviceInfo; 298 inputDeviceInfo = Pa_GetDeviceInfo( theInputParameters.device ); 299 log_info("PortAudio: source device: %s", inputDeviceInfo->name); 300 UNUSED(inputDeviceInfo); 301 302 /* -- setup stream -- */ 303 err = Pa_OpenStream( 304 &stream_source, 305 &theInputParameters, 306 NULL, 307 samplerate, 308 NUM_FRAMES_PER_PA_BUFFER, 309 paClipOff, /* we won't output out of range samples so don't bother clipping them */ 310 portaudio_callback_source, /* use callback */ 311 NULL ); 312 313 if (err != paNoError){ 314 log_error("Error initializing portaudio: \"%s\"\n", Pa_GetErrorText(err)); 315 return err; 316 } 317 log_info("PortAudio: source stream created"); 318 319 const PaStreamInfo * stream_info = Pa_GetStreamInfo(stream_source); 320 log_info("PortAudio: source latency: %f", stream_info->inputLatency); 321 UNUSED(stream_info); 322 323 recording_callback = recording; 324 325 source_initialized = 1; 326 327 return 0; 328 } 329 330 static void btstack_audio_portaudio_sink_set_volume(uint8_t volume){ 331 sink_volume = volume; 332 } 333 334 static void btstack_audio_portaudio_source_set_gain(uint8_t gain){ 335 UNUSED(gain); 336 } 337 338 static void btstack_audio_portaudio_sink_start_stream(void){ 339 340 if (!playback_callback) return; 341 342 // fill buffer once 343 (*playback_callback)(output_buffer_a, NUM_FRAMES_PER_PA_BUFFER); 344 (*playback_callback)(output_buffer_b, NUM_FRAMES_PER_PA_BUFFER); 345 output_buffer_to_play = 0; 346 output_buffer_to_fill = 2; 347 348 /* -- start stream -- */ 349 PaError err = Pa_StartStream(stream_sink); 350 if (err != paNoError){ 351 log_error("PortAudio: error starting sink stream: \"%s\"\n", Pa_GetErrorText(err)); 352 return; 353 } 354 355 // start timer 356 btstack_run_loop_set_timer_handler(&driver_timer_sink, &driver_timer_handler_sink); 357 btstack_run_loop_set_timer(&driver_timer_sink, DRIVER_POLL_INTERVAL_MS); 358 btstack_run_loop_add_timer(&driver_timer_sink); 359 360 sink_active = 1; 361 } 362 363 static void btstack_audio_portaudio_source_start_stream(void){ 364 365 if (!recording_callback) return; 366 367 /* -- start stream -- */ 368 PaError err = Pa_StartStream(stream_source); 369 if (err != paNoError){ 370 log_error("PortAudio: error starting source stream: \"%s\"\n", Pa_GetErrorText(err)); 371 return; 372 } 373 374 // start timer 375 btstack_run_loop_set_timer_handler(&driver_timer_source, &driver_timer_handler_source); 376 btstack_run_loop_set_timer(&driver_timer_source, DRIVER_POLL_INTERVAL_MS); 377 btstack_run_loop_add_timer(&driver_timer_source); 378 379 source_active = 1; 380 } 381 382 static void btstack_audio_portaudio_sink_stop_stream(void){ 383 384 if (!playback_callback) return; 385 if (!sink_active) return; 386 387 // stop timer 388 btstack_run_loop_remove_timer(&driver_timer_sink); 389 390 PaError err = Pa_StopStream(stream_sink); 391 if (err != paNoError){ 392 log_error("PortAudio: error stopping sink stream: \"%s\"", Pa_GetErrorText(err)); 393 return; 394 } 395 396 sink_active = 0; 397 } 398 399 static void btstack_audio_portaudio_source_stop_stream(void){ 400 401 if (!recording_callback) return; 402 if (!source_active) return; 403 404 // stop timer 405 btstack_run_loop_remove_timer(&driver_timer_source); 406 407 PaError err = Pa_StopStream(stream_source); 408 if (err != paNoError){ 409 log_error("PortAudio: error stopping source stream: \"%s\"", Pa_GetErrorText(err)); 410 return; 411 } 412 413 source_active = 0; 414 } 415 416 static void btstack_audio_portaudio_close_pa_if_not_needed(void){ 417 if (source_initialized) return; 418 if (sink_initialized) return; 419 PaError err = Pa_Terminate(); 420 if (err != paNoError){ 421 log_error("Portudio: Error terminating portaudio: \"%s\"", Pa_GetErrorText(err)); 422 return; 423 } 424 portaudio_initialized = 0; 425 } 426 427 static void btstack_audio_portaudio_sink_close(void){ 428 429 if (!playback_callback) return; 430 431 if (sink_active){ 432 btstack_audio_portaudio_sink_stop_stream(); 433 } 434 435 PaError err = Pa_CloseStream(stream_sink); 436 if (err != paNoError){ 437 log_error("PortAudio: error closing sink stream: \"%s\"", Pa_GetErrorText(err)); 438 return; 439 } 440 441 sink_initialized = 0; 442 btstack_audio_portaudio_close_pa_if_not_needed(); 443 } 444 445 static void btstack_audio_portaudio_source_close(void){ 446 447 if (!recording_callback) return; 448 449 if (source_active){ 450 btstack_audio_portaudio_source_stop_stream(); 451 } 452 453 PaError err = Pa_CloseStream(stream_source); 454 if (err != paNoError){ 455 log_error("PortAudio: error closing source stream: \"%s\"", Pa_GetErrorText(err)); 456 return; 457 } 458 459 source_initialized = 0; 460 btstack_audio_portaudio_close_pa_if_not_needed(); 461 } 462 463 static const btstack_audio_sink_t btstack_audio_portaudio_sink = { 464 /* int (*init)(..);*/ &btstack_audio_portaudio_sink_init, 465 /* void (*set_volume)(uint8_t volume); */ &btstack_audio_portaudio_sink_set_volume, 466 /* void (*start_stream(void));*/ &btstack_audio_portaudio_sink_start_stream, 467 /* void (*stop_stream)(void) */ &btstack_audio_portaudio_sink_stop_stream, 468 /* void (*close)(void); */ &btstack_audio_portaudio_sink_close 469 }; 470 471 static const btstack_audio_source_t btstack_audio_portaudio_source = { 472 /* int (*init)(..);*/ &btstack_audio_portaudio_source_init, 473 /* void (*set_gain)(uint8_t gain); */ &btstack_audio_portaudio_source_set_gain, 474 /* void (*start_stream(void));*/ &btstack_audio_portaudio_source_start_stream, 475 /* void (*stop_stream)(void) */ &btstack_audio_portaudio_source_stop_stream, 476 /* void (*close)(void); */ &btstack_audio_portaudio_source_close 477 }; 478 479 const btstack_audio_sink_t * btstack_audio_portaudio_sink_get_instance(void){ 480 return &btstack_audio_portaudio_sink; 481 } 482 483 const btstack_audio_source_t * btstack_audio_portaudio_source_get_instance(void){ 484 return &btstack_audio_portaudio_source; 485 } 486 487 #endif 488