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