xref: /btstack/platform/posix/btstack_audio_portaudio.c (revision 46d6c6044a82d8cc98f35a2ca93e895adb50ca82)
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