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