xref: /btstack/platform/posix/btstack_audio_portaudio.c (revision 30b419f46682e332985488ba122296aa4d36bc48)
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               5
52 #define NUM_INPUT_BUFFERS                2
53 #define DRIVER_POLL_INTERVAL_MS          5
54 
55 #ifndef MAX_NR_AUDIO_CHANNELS
56 #define MAX_NR_AUDIO_CHANNELS 2
57 #endif
58 
59 #include <portaudio.h>
60 
61 // config
62 static int                    num_channels_sink;
63 static int                    num_channels_source;
64 static int                    num_bytes_per_sample_sink;
65 static int                    num_bytes_per_sample_source;
66 
67 // portaudio
68 static int portaudio_initialized;
69 
70 // state
71 static int source_initialized;
72 static int sink_initialized;
73 static int source_active;
74 static int sink_active;
75 
76 static uint8_t sink_volume;
77 
78 static PaStream * stream_source;
79 static PaStream * stream_sink;
80 
81 // client
82 static void (*playback_callback)(int16_t * buffer, uint16_t num_samples);
83 static void (*recording_callback)(const int16_t * buffer, uint16_t num_samples);
84 
85 // output buffer
86 static int16_t               output_buffer_storage[NUM_OUTPUT_BUFFERS * NUM_FRAMES_PER_PA_BUFFER * MAX_NR_AUDIO_CHANNELS];
87 static int16_t             * output_buffers[NUM_OUTPUT_BUFFERS];
88 static int                   output_buffer_to_play;
89 static int                   output_buffer_to_fill;
90 
91 // input buffer
92 static int16_t               input_buffer_a[NUM_FRAMES_PER_PA_BUFFER * MAX_NR_AUDIO_CHANNELS];
93 static int16_t               input_buffer_b[NUM_FRAMES_PER_PA_BUFFER * MAX_NR_AUDIO_CHANNELS];
94 static int16_t             * input_buffers[NUM_INPUT_BUFFERS] = { input_buffer_a, input_buffer_b};
95 static int                   input_buffer_to_record;
96 static int                   input_buffer_to_fill;
97 
98 
99 // timer to fill output ring buffer
100 static btstack_timer_source_t  driver_timer_sink;
101 static btstack_timer_source_t  driver_timer_source;
102 
103 static int portaudio_callback_sink( const void *                     inputBuffer,
104                                     void *                           outputBuffer,
105                                     unsigned long                    frames_per_buffer,
106                                     const PaStreamCallbackTimeInfo * timeInfo,
107                                     PaStreamCallbackFlags            statusFlags,
108                                     void *                           userData ) {
109 
110     /** portaudio_callback is called from different thread, don't use hci_dump / log_info here without additional checks */
111 
112     (void) timeInfo; /* Prevent unused variable warnings. */
113     (void) statusFlags;
114     (void) userData;
115     (void) frames_per_buffer;
116     (void) inputBuffer;
117 
118     // simplified volume control
119     uint16_t index;
120     int16_t * from_buffer = output_buffers[output_buffer_to_play];
121     int16_t * to_buffer = (int16_t *) outputBuffer;
122     btstack_assert(frames_per_buffer == NUM_FRAMES_PER_PA_BUFFER);
123 
124 #if 0
125     // up to 8 right shifts
126     int right_shift = 8 - btstack_min(8, ((sink_volume + 15) / 16));
127     for (index = 0; index < (NUM_FRAMES_PER_PA_BUFFER * num_channels_sink); index++){
128         *to_buffer++ = (*from_buffer++) >> right_shift;
129     }
130 #else
131     // multiply with volume ^ 4
132     int16_t x2 = sink_volume * sink_volume;
133     int16_t x4 = (x2 * x2) >> 14;
134     for (index = 0; index < (NUM_FRAMES_PER_PA_BUFFER * num_channels_sink); index++){
135         *to_buffer++ = ((*from_buffer++) * x4) >> 14;
136     }
137 #endif
138 
139     // next
140     output_buffer_to_play = (output_buffer_to_play + 1 ) % NUM_OUTPUT_BUFFERS;
141 
142     return 0;
143 }
144 
145 static int portaudio_callback_source( const void *                     inputBuffer,
146                                       void *                           outputBuffer,
147                                       unsigned long                    samples_per_buffer,
148                                       const PaStreamCallbackTimeInfo * timeInfo,
149                                       PaStreamCallbackFlags            statusFlags,
150                                       void *                           userData ) {
151 
152     /** portaudio_callback is called from different thread, don't use hci_dump / log_info here without additional checks */
153 
154     (void) timeInfo; /* Prevent unused variable warnings. */
155     (void) statusFlags;
156     (void) userData;
157     (void) samples_per_buffer;
158     (void) outputBuffer;
159 
160     // store in one of our buffers
161     memcpy(input_buffers[input_buffer_to_fill], inputBuffer, NUM_FRAMES_PER_PA_BUFFER * num_bytes_per_sample_source);
162 
163     // next
164     input_buffer_to_fill = (input_buffer_to_fill + 1 ) % NUM_INPUT_BUFFERS;
165 
166     return 0;
167 }
168 
169 static void driver_timer_handler_sink(btstack_timer_source_t * ts){
170 
171     // playback buffer ready to fill
172     while (output_buffer_to_play != output_buffer_to_fill){
173         (*playback_callback)(output_buffers[output_buffer_to_fill], NUM_FRAMES_PER_PA_BUFFER);
174 
175         // next
176         output_buffer_to_fill = (output_buffer_to_fill + 1 ) % NUM_OUTPUT_BUFFERS;
177     }
178 
179     // re-set timer
180     btstack_run_loop_set_timer(ts, DRIVER_POLL_INTERVAL_MS);
181     btstack_run_loop_add_timer(ts);
182 }
183 
184 static void driver_timer_handler_source(btstack_timer_source_t * ts){
185 
186     // recording buffer ready to process
187     if (input_buffer_to_record != input_buffer_to_fill){
188 
189         (*recording_callback)(input_buffers[input_buffer_to_record], NUM_FRAMES_PER_PA_BUFFER);
190 
191         // next
192         input_buffer_to_record = (input_buffer_to_record + 1 ) % NUM_INPUT_BUFFERS;
193     }
194 
195     // re-set timer
196     btstack_run_loop_set_timer(ts, DRIVER_POLL_INTERVAL_MS);
197     btstack_run_loop_add_timer(ts);
198 }
199 
200 static int btstack_audio_portaudio_sink_init(
201     uint8_t channels,
202     uint32_t samplerate,
203     void (*playback)(int16_t * buffer, uint16_t num_samples)
204 ){
205     PaError err;
206 
207     btstack_assert(channels <= MAX_NR_AUDIO_CHANNELS);
208 
209     num_channels_sink = channels;
210     num_bytes_per_sample_sink = 2 * channels;
211 
212     uint8_t i;
213     for (i=0;i<NUM_OUTPUT_BUFFERS;i++){
214         output_buffers[i] = &output_buffer_storage[i * NUM_FRAMES_PER_PA_BUFFER * MAX_NR_AUDIO_CHANNELS];
215     }
216 
217     if (!playback){
218         log_error("No playback callback");
219         return 1;
220     }
221 
222     /* -- initialize PortAudio -- */
223     if (!portaudio_initialized){
224         err = Pa_Initialize();
225         if (err != paNoError){
226             log_error("Portudio: error initializing portaudio: \"%s\"\n",  Pa_GetErrorText(err));
227             return err;
228         }
229         portaudio_initialized = 1;
230     }
231 
232     /* -- setup output -- */
233     PaStreamParameters theOutputParameters;
234     theOutputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */
235     theOutputParameters.channelCount = channels;
236     theOutputParameters.sampleFormat = PA_SAMPLE_TYPE;
237     theOutputParameters.suggestedLatency = Pa_GetDeviceInfo( theOutputParameters.device )->defaultHighOutputLatency;
238     theOutputParameters.hostApiSpecificStreamInfo = NULL;
239 
240     const PaDeviceInfo *outputDeviceInfo;
241     outputDeviceInfo = Pa_GetDeviceInfo( theOutputParameters.device );
242     log_info("PortAudio: sink device: %s", outputDeviceInfo->name);
243     UNUSED(outputDeviceInfo);
244 
245     /* -- setup stream -- */
246     err = Pa_OpenStream(
247            &stream_sink,
248            NULL,
249            &theOutputParameters,
250            samplerate,
251            NUM_FRAMES_PER_PA_BUFFER,
252            paClipOff,           /* we won't output out of range samples so don't bother clipping them */
253            portaudio_callback_sink,  /* use callback */
254            NULL );
255 
256     if (err != paNoError){
257         log_error("Portudio: error initializing portaudio: \"%s\"\n",  Pa_GetErrorText(err));
258         return err;
259     }
260     log_info("PortAudio: sink stream created");
261 
262     const PaStreamInfo * stream_info = Pa_GetStreamInfo(stream_sink);
263     log_info("PortAudio: sink latency: %f", stream_info->outputLatency);
264     UNUSED(stream_info);
265 
266     playback_callback  = playback;
267 
268     sink_initialized = 1;
269     sink_volume = 127;
270 
271     return 0;
272 }
273 
274 static int btstack_audio_portaudio_source_init(
275     uint8_t channels,
276     uint32_t samplerate,
277     void (*recording)(const int16_t * buffer, uint16_t num_samples)
278 ){
279     PaError err;
280 
281     btstack_assert(channels <= MAX_NR_AUDIO_CHANNELS);
282 
283     num_channels_source = channels;
284     num_bytes_per_sample_source = 2 * channels;
285 
286     if (!recording){
287         log_error("No recording callback");
288         return 1;
289     }
290 
291     /* -- initialize PortAudio -- */
292     if (!portaudio_initialized){
293         err = Pa_Initialize();
294         if (err != paNoError){
295             log_error("Portudio: Error initializing portaudio: \"%s\"\n",  Pa_GetErrorText(err));
296             return err;
297         }
298         portaudio_initialized = 1;
299     }
300 
301     /* -- setup input -- */
302     PaStreamParameters theInputParameters;
303     theInputParameters.device = Pa_GetDefaultInputDevice(); /* default input device */
304     theInputParameters.channelCount = channels;
305     theInputParameters.sampleFormat = PA_SAMPLE_TYPE;
306     theInputParameters.suggestedLatency = Pa_GetDeviceInfo( theInputParameters.device )->defaultHighInputLatency;
307     theInputParameters.hostApiSpecificStreamInfo = NULL;
308 
309     const PaDeviceInfo *inputDeviceInfo;
310     inputDeviceInfo = Pa_GetDeviceInfo( theInputParameters.device );
311     log_info("PortAudio: source device: %s", inputDeviceInfo->name);
312     UNUSED(inputDeviceInfo);
313 
314     /* -- setup stream -- */
315     err = Pa_OpenStream(
316            &stream_source,
317            &theInputParameters,
318            NULL,
319            samplerate,
320            NUM_FRAMES_PER_PA_BUFFER,
321            paClipOff,           /* we won't output out of range samples so don't bother clipping them */
322            portaudio_callback_source,  /* use callback */
323            NULL );
324 
325     if (err != paNoError){
326         log_error("Error initializing portaudio: \"%s\"\n",  Pa_GetErrorText(err));
327         return err;
328     }
329     log_info("PortAudio: source stream created");
330 
331     const PaStreamInfo * stream_info = Pa_GetStreamInfo(stream_source);
332     log_info("PortAudio: source latency: %f", stream_info->inputLatency);
333     UNUSED(stream_info);
334 
335     recording_callback = recording;
336 
337     source_initialized = 1;
338 
339     return 0;
340 }
341 
342 static uint32_t btstack_audio_portaudio_sink_get_samplerate(void) {
343     const PaStreamInfo *stream_info = Pa_GetStreamInfo(stream_sink);
344     return stream_info->sampleRate;
345 }
346 
347 static uint32_t btstack_audio_portaudio_source_get_samplerate(void) {
348     const PaStreamInfo *stream_info = Pa_GetStreamInfo(stream_source);
349     return stream_info->sampleRate;
350 }
351 
352 static void btstack_audio_portaudio_sink_set_volume(uint8_t volume){
353     sink_volume = volume;
354 }
355 
356 static void btstack_audio_portaudio_source_set_gain(uint8_t gain){
357     UNUSED(gain);
358 }
359 
360 static void btstack_audio_portaudio_sink_start_stream(void){
361 
362     if (!playback_callback) return;
363 
364     // fill buffers once
365     uint8_t i;
366     for (i=0;i<NUM_OUTPUT_BUFFERS-1;i++){
367         (*playback_callback)(&output_buffer_storage[i * NUM_FRAMES_PER_PA_BUFFER * MAX_NR_AUDIO_CHANNELS], NUM_FRAMES_PER_PA_BUFFER);
368     }
369     output_buffer_to_play = 0;
370     output_buffer_to_fill = NUM_OUTPUT_BUFFERS-1;
371 
372     /* -- start stream -- */
373     PaError err = Pa_StartStream(stream_sink);
374     if (err != paNoError){
375         log_error("PortAudio: error starting sink stream: \"%s\"\n",  Pa_GetErrorText(err));
376         return;
377     }
378 
379     // start timer
380     btstack_run_loop_set_timer_handler(&driver_timer_sink, &driver_timer_handler_sink);
381     btstack_run_loop_set_timer(&driver_timer_sink, DRIVER_POLL_INTERVAL_MS);
382     btstack_run_loop_add_timer(&driver_timer_sink);
383 
384     sink_active = 1;
385 }
386 
387 static void btstack_audio_portaudio_source_start_stream(void){
388 
389     if (!recording_callback) return;
390 
391     /* -- start stream -- */
392     PaError err = Pa_StartStream(stream_source);
393     if (err != paNoError){
394         log_error("PortAudio: error starting source stream: \"%s\"\n",  Pa_GetErrorText(err));
395         return;
396     }
397 
398     // start timer
399     btstack_run_loop_set_timer_handler(&driver_timer_source, &driver_timer_handler_source);
400     btstack_run_loop_set_timer(&driver_timer_source, DRIVER_POLL_INTERVAL_MS);
401     btstack_run_loop_add_timer(&driver_timer_source);
402 
403     source_active = 1;
404 }
405 
406 static void btstack_audio_portaudio_sink_stop_stream(void){
407 
408     if (!playback_callback) return;
409     if (!sink_active)       return;
410 
411     // stop timer
412     btstack_run_loop_remove_timer(&driver_timer_sink);
413 
414     PaError err = Pa_StopStream(stream_sink);
415     if (err != paNoError){
416         log_error("PortAudio: error stopping sink stream: \"%s\"",  Pa_GetErrorText(err));
417         return;
418     }
419 
420     sink_active = 0;
421 }
422 
423 static void btstack_audio_portaudio_source_stop_stream(void){
424 
425     if (!recording_callback) return;
426     if (!source_active)      return;
427 
428     // stop timer
429     btstack_run_loop_remove_timer(&driver_timer_source);
430 
431     PaError err = Pa_StopStream(stream_source);
432     if (err != paNoError){
433         log_error("PortAudio: error stopping source stream: \"%s\"",  Pa_GetErrorText(err));
434         return;
435     }
436 
437     source_active = 0;
438 }
439 
440 static void btstack_audio_portaudio_close_pa_if_not_needed(void){
441     if (source_initialized) return;
442     if (sink_initialized) return;
443     PaError err = Pa_Terminate();
444     if (err != paNoError){
445         log_error("Portudio: Error terminating portaudio: \"%s\"",  Pa_GetErrorText(err));
446         return;
447     }
448     portaudio_initialized = 0;
449 }
450 
451 static void btstack_audio_portaudio_sink_close(void){
452 
453     if (!playback_callback) return;
454 
455     if (sink_active){
456         btstack_audio_portaudio_sink_stop_stream();
457     }
458 
459     PaError err = Pa_CloseStream(stream_sink);
460     if (err != paNoError){
461         log_error("PortAudio: error closing sink stream: \"%s\"",  Pa_GetErrorText(err));
462         return;
463     }
464 
465     sink_initialized = 0;
466     btstack_audio_portaudio_close_pa_if_not_needed();
467 }
468 
469 static void btstack_audio_portaudio_source_close(void){
470 
471     if (!recording_callback) return;
472 
473     if (source_active){
474         btstack_audio_portaudio_source_stop_stream();
475     }
476 
477     PaError err = Pa_CloseStream(stream_source);
478     if (err != paNoError){
479         log_error("PortAudio: error closing source stream: \"%s\"",  Pa_GetErrorText(err));
480         return;
481     }
482 
483     source_initialized = 0;
484     btstack_audio_portaudio_close_pa_if_not_needed();
485 }
486 
487 static const btstack_audio_sink_t btstack_audio_portaudio_sink = {
488     /* int (*init)(..);*/                                       &btstack_audio_portaudio_sink_init,
489     /* uint32_t (*get_samplerate)() */                          &btstack_audio_portaudio_sink_get_samplerate,
490     /* void (*set_volume)(uint8_t volume); */                   &btstack_audio_portaudio_sink_set_volume,
491     /* void (*start_stream(void));*/                            &btstack_audio_portaudio_sink_start_stream,
492     /* void (*stop_stream)(void)  */                            &btstack_audio_portaudio_sink_stop_stream,
493     /* void (*close)(void); */                                  &btstack_audio_portaudio_sink_close
494 };
495 
496 static const btstack_audio_source_t btstack_audio_portaudio_source = {
497     /* int (*init)(..);*/                                       &btstack_audio_portaudio_source_init,
498     /* uint32_t (*get_samplerate)() */                          &btstack_audio_portaudio_source_get_samplerate,
499     /* void (*set_gain)(uint8_t gain); */                       &btstack_audio_portaudio_source_set_gain,
500     /* void (*start_stream(void));*/                            &btstack_audio_portaudio_source_start_stream,
501     /* void (*stop_stream)(void)  */                            &btstack_audio_portaudio_source_stop_stream,
502     /* void (*close)(void); */                                  &btstack_audio_portaudio_source_close
503 };
504 
505 const btstack_audio_sink_t * btstack_audio_portaudio_sink_get_instance(void){
506     return &btstack_audio_portaudio_sink;
507 }
508 
509 const btstack_audio_source_t * btstack_audio_portaudio_source_get_instance(void){
510     return &btstack_audio_portaudio_source;
511 }
512 
513 #endif
514