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