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