xref: /btstack/platform/posix/btstack_audio_portaudio.c (revision b28dc8004dd8d4fb9020a6dcd2bc81f05d36a008)
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 
234     /* -- setup stream -- */
235     err = Pa_OpenStream(
236            &stream_sink,
237            NULL,
238            &theOutputParameters,
239            samplerate,
240            NUM_FRAMES_PER_PA_BUFFER,
241            paClipOff,           /* we won't output out of range samples so don't bother clipping them */
242            portaudio_callback_sink,  /* use callback */
243            NULL );
244 
245     if (err != paNoError){
246         log_error("Portudio: error initializing portaudio: \"%s\"\n",  Pa_GetErrorText(err));
247         return err;
248     }
249     log_info("PortAudio: sink stream created");
250 
251     const PaStreamInfo * stream_info = Pa_GetStreamInfo(stream_sink);
252     log_info("PortAudio: sink latency: %f", stream_info->outputLatency);
253     UNUSED(stream_info);
254 
255     playback_callback  = playback;
256 
257     sink_initialized = 1;
258     sink_volume = 127;
259 
260     return 0;
261 }
262 
263 static int btstack_audio_portaudio_source_init(
264     uint8_t channels,
265     uint32_t samplerate,
266     void (*recording)(const int16_t * buffer, uint16_t num_samples)
267 ){
268     PaError err;
269 
270     num_channels_source = channels;
271     num_bytes_per_sample_source = 2 * channels;
272 
273     if (!recording){
274         log_error("No recording callback");
275         return 1;
276     }
277 
278     /* -- initialize PortAudio -- */
279     if (!portaudio_initialized){
280         err = Pa_Initialize();
281         if (err != paNoError){
282             log_error("Portudio: Error initializing portaudio: \"%s\"\n",  Pa_GetErrorText(err));
283             return err;
284         }
285         portaudio_initialized = 1;
286     }
287 
288     /* -- setup input -- */
289     PaStreamParameters theInputParameters;
290     theInputParameters.device = Pa_GetDefaultInputDevice(); /* default input device */
291     theInputParameters.channelCount = channels;
292     theInputParameters.sampleFormat = PA_SAMPLE_TYPE;
293     theInputParameters.suggestedLatency = Pa_GetDeviceInfo( theInputParameters.device )->defaultHighInputLatency;
294     theInputParameters.hostApiSpecificStreamInfo = NULL;
295 
296     const PaDeviceInfo *inputDeviceInfo;
297     inputDeviceInfo = Pa_GetDeviceInfo( theInputParameters.device );
298     log_info("PortAudio: source device: %s", inputDeviceInfo->name);
299 
300     /* -- setup stream -- */
301     err = Pa_OpenStream(
302            &stream_source,
303            &theInputParameters,
304            NULL,
305            samplerate,
306            NUM_FRAMES_PER_PA_BUFFER,
307            paClipOff,           /* we won't output out of range samples so don't bother clipping them */
308            portaudio_callback_source,  /* use callback */
309            NULL );
310 
311     if (err != paNoError){
312         log_error("Error initializing portaudio: \"%s\"\n",  Pa_GetErrorText(err));
313         return err;
314     }
315     log_info("PortAudio: source stream created");
316 
317     const PaStreamInfo * stream_info = Pa_GetStreamInfo(stream_source);
318     log_info("PortAudio: source latency: %f", stream_info->inputLatency);
319     UNUSED(stream_info);
320 
321     recording_callback = recording;
322 
323     source_initialized = 1;
324 
325     return 0;
326 }
327 
328 static void btstack_audio_portaudio_sink_set_volume(uint8_t volume){
329     sink_volume = volume;
330 }
331 
332 static void btstack_audio_portaudio_source_set_gain(uint8_t gain){
333     UNUSED(gain);
334 }
335 
336 static void btstack_audio_portaudio_sink_start_stream(void){
337 
338     if (!playback_callback) return;
339 
340     // fill buffer once
341     (*playback_callback)(output_buffer_a, NUM_FRAMES_PER_PA_BUFFER);
342     (*playback_callback)(output_buffer_b, NUM_FRAMES_PER_PA_BUFFER);
343     output_buffer_to_play = 0;
344     output_buffer_to_fill = 2;
345 
346     /* -- start stream -- */
347     PaError err = Pa_StartStream(stream_sink);
348     if (err != paNoError){
349         log_error("PortAudio: error starting sink stream: \"%s\"\n",  Pa_GetErrorText(err));
350         return;
351     }
352 
353     // start timer
354     btstack_run_loop_set_timer_handler(&driver_timer_sink, &driver_timer_handler_sink);
355     btstack_run_loop_set_timer(&driver_timer_sink, DRIVER_POLL_INTERVAL_MS);
356     btstack_run_loop_add_timer(&driver_timer_sink);
357 
358     sink_active = 1;
359 }
360 
361 static void btstack_audio_portaudio_source_start_stream(void){
362 
363     if (!recording_callback) return;
364 
365     /* -- start stream -- */
366     PaError err = Pa_StartStream(stream_source);
367     if (err != paNoError){
368         log_error("PortAudio: error starting source stream: \"%s\"\n",  Pa_GetErrorText(err));
369         return;
370     }
371 
372     // start timer
373     btstack_run_loop_set_timer_handler(&driver_timer_source, &driver_timer_handler_source);
374     btstack_run_loop_set_timer(&driver_timer_source, DRIVER_POLL_INTERVAL_MS);
375     btstack_run_loop_add_timer(&driver_timer_source);
376 
377     source_active = 1;
378 }
379 
380 static void btstack_audio_portaudio_sink_stop_stream(void){
381 
382     if (!playback_callback) return;
383     if (!sink_active)       return;
384 
385     // stop timer
386     btstack_run_loop_remove_timer(&driver_timer_sink);
387 
388     PaError err = Pa_StopStream(stream_sink);
389     if (err != paNoError){
390         log_error("PortAudio: error stopping sink stream: \"%s\"",  Pa_GetErrorText(err));
391         return;
392     }
393 
394     sink_active = 0;
395 }
396 
397 static void btstack_audio_portaudio_source_stop_stream(void){
398 
399     if (!recording_callback) return;
400     if (!source_active)      return;
401 
402     // stop timer
403     btstack_run_loop_remove_timer(&driver_timer_source);
404 
405     PaError err = Pa_StopStream(stream_source);
406     if (err != paNoError){
407         log_error("PortAudio: error stopping source stream: \"%s\"",  Pa_GetErrorText(err));
408         return;
409     }
410 
411     source_active = 0;
412 }
413 
414 static void btstack_audio_portaudio_close_pa_if_not_needed(void){
415     if (source_initialized) return;
416     if (sink_initialized) return;
417     PaError err = Pa_Terminate();
418     if (err != paNoError){
419         log_error("Portudio: Error terminating portaudio: \"%s\"",  Pa_GetErrorText(err));
420         return;
421     }
422     portaudio_initialized = 0;
423 }
424 
425 static void btstack_audio_portaudio_sink_close(void){
426 
427     if (!playback_callback) return;
428 
429     if (sink_active){
430         btstack_audio_portaudio_sink_stop_stream();
431     }
432 
433     PaError err = Pa_CloseStream(stream_sink);
434     if (err != paNoError){
435         log_error("PortAudio: error closing sink stream: \"%s\"",  Pa_GetErrorText(err));
436         return;
437     }
438 
439     sink_initialized = 0;
440     btstack_audio_portaudio_close_pa_if_not_needed();
441 }
442 
443 static void btstack_audio_portaudio_source_close(void){
444 
445     if (!recording_callback) return;
446 
447     if (source_active){
448         btstack_audio_portaudio_source_stop_stream();
449     }
450 
451     PaError err = Pa_CloseStream(stream_source);
452     if (err != paNoError){
453         log_error("PortAudio: error closing source stream: \"%s\"",  Pa_GetErrorText(err));
454         return;
455     }
456 
457     source_initialized = 0;
458     btstack_audio_portaudio_close_pa_if_not_needed();
459 }
460 
461 static const btstack_audio_sink_t btstack_audio_portaudio_sink = {
462     /* int (*init)(..);*/                                       &btstack_audio_portaudio_sink_init,
463     /* void (*set_volume)(uint8_t volume); */                   &btstack_audio_portaudio_sink_set_volume,
464     /* void (*start_stream(void));*/                            &btstack_audio_portaudio_sink_start_stream,
465     /* void (*stop_stream)(void)  */                            &btstack_audio_portaudio_sink_stop_stream,
466     /* void (*close)(void); */                                  &btstack_audio_portaudio_sink_close
467 };
468 
469 static const btstack_audio_source_t btstack_audio_portaudio_source = {
470     /* int (*init)(..);*/                                       &btstack_audio_portaudio_source_init,
471     /* void (*set_gain)(uint8_t gain); */                       &btstack_audio_portaudio_source_set_gain,
472     /* void (*start_stream(void));*/                            &btstack_audio_portaudio_source_start_stream,
473     /* void (*stop_stream)(void)  */                            &btstack_audio_portaudio_source_stop_stream,
474     /* void (*close)(void); */                                  &btstack_audio_portaudio_source_close
475 };
476 
477 const btstack_audio_sink_t * btstack_audio_portaudio_sink_get_instance(void){
478     return &btstack_audio_portaudio_sink;
479 }
480 
481 const btstack_audio_source_t * btstack_audio_portaudio_source_get_instance(void){
482     return &btstack_audio_portaudio_source;
483 }
484 
485 #endif
486