1a89df2dfSMatthias Ringwald /*
2a89df2dfSMatthias Ringwald * Copyright (C) 2017 BlueKitchen GmbH
3a89df2dfSMatthias Ringwald *
4a89df2dfSMatthias Ringwald * Redistribution and use in source and binary forms, with or without
5a89df2dfSMatthias Ringwald * modification, are permitted provided that the following conditions
6a89df2dfSMatthias Ringwald * are met:
7a89df2dfSMatthias Ringwald *
8a89df2dfSMatthias Ringwald * 1. Redistributions of source code must retain the above copyright
9a89df2dfSMatthias Ringwald * notice, this list of conditions and the following disclaimer.
10a89df2dfSMatthias Ringwald * 2. Redistributions in binary form must reproduce the above copyright
11a89df2dfSMatthias Ringwald * notice, this list of conditions and the following disclaimer in the
12a89df2dfSMatthias Ringwald * documentation and/or other materials provided with the distribution.
13a89df2dfSMatthias Ringwald * 3. Neither the name of the copyright holders nor the names of
14a89df2dfSMatthias Ringwald * contributors may be used to endorse or promote products derived
15a89df2dfSMatthias Ringwald * from this software without specific prior written permission.
16a89df2dfSMatthias Ringwald * 4. Any redistribution, use, or modification is done solely for
17a89df2dfSMatthias Ringwald * personal benefit and not for any commercial purpose or for
18a89df2dfSMatthias Ringwald * monetary gain.
19a89df2dfSMatthias Ringwald *
20a89df2dfSMatthias Ringwald * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS
21a89df2dfSMatthias Ringwald * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22a89df2dfSMatthias Ringwald * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
232fca4dadSMilanka Ringwald * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLUEKITCHEN
242fca4dadSMilanka Ringwald * GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25a89df2dfSMatthias Ringwald * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26a89df2dfSMatthias Ringwald * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
27a89df2dfSMatthias Ringwald * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
28a89df2dfSMatthias Ringwald * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29a89df2dfSMatthias Ringwald * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
30a89df2dfSMatthias Ringwald * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31a89df2dfSMatthias Ringwald * SUCH DAMAGE.
32a89df2dfSMatthias Ringwald *
33a89df2dfSMatthias Ringwald * Please inquire about commercial licensing options at
34a89df2dfSMatthias Ringwald * [email protected]
35a89df2dfSMatthias Ringwald *
36a89df2dfSMatthias Ringwald */
37a89df2dfSMatthias Ringwald
38e501bae0SMatthias Ringwald #define BTSTACK_FILE__ "btstack_audio_portaudio.c"
39a89df2dfSMatthias Ringwald
40a89df2dfSMatthias Ringwald
41a89df2dfSMatthias Ringwald #include <stdint.h>
42a89df2dfSMatthias Ringwald #include <string.h>
43a89df2dfSMatthias Ringwald #include "btstack_debug.h"
44a89df2dfSMatthias Ringwald #include "btstack_audio.h"
45a89df2dfSMatthias Ringwald #include "btstack_run_loop.h"
46a89df2dfSMatthias Ringwald
47a89df2dfSMatthias Ringwald #ifdef HAVE_PORTAUDIO
48a89df2dfSMatthias Ringwald
49a89df2dfSMatthias Ringwald #define PA_SAMPLE_TYPE paInt16
50a89df2dfSMatthias Ringwald #define NUM_FRAMES_PER_PA_BUFFER 512
51595b1738SMatthias Ringwald #define NUM_OUTPUT_BUFFERS 5
5214200bb8SDirk Helbig #define NUM_INPUT_BUFFERS 5
53a89df2dfSMatthias Ringwald #define DRIVER_POLL_INTERVAL_MS 5
54a89df2dfSMatthias Ringwald
5530b419f4SMatthias Ringwald #ifndef MAX_NR_AUDIO_CHANNELS
5630b419f4SMatthias Ringwald #define MAX_NR_AUDIO_CHANNELS 2
5730b419f4SMatthias Ringwald #endif
5830b419f4SMatthias Ringwald
59a89df2dfSMatthias Ringwald #include <portaudio.h>
60a89df2dfSMatthias Ringwald
61a89df2dfSMatthias Ringwald // config
621a551b1bSMatthias Ringwald static int num_channels_sink;
631a551b1bSMatthias Ringwald static int num_channels_source;
64bcd51729SMatthias Ringwald static int num_bytes_per_sample_sink;
65bcd51729SMatthias Ringwald static int num_bytes_per_sample_source;
66a89df2dfSMatthias Ringwald
67*409f627cSMatthias Ringwald static const char * sink_device_name;
68*409f627cSMatthias Ringwald static const char * source_device_name = "4-chan";
69*409f627cSMatthias Ringwald
70a89df2dfSMatthias Ringwald // portaudio
713d3351f3SMatthias Ringwald static int portaudio_initialized;
723d3351f3SMatthias Ringwald
733d3351f3SMatthias Ringwald // state
743d3351f3SMatthias Ringwald static int source_initialized;
753d3351f3SMatthias Ringwald static int sink_initialized;
763d3351f3SMatthias Ringwald static int source_active;
773d3351f3SMatthias Ringwald static int sink_active;
783d3351f3SMatthias Ringwald
791a551b1bSMatthias Ringwald static uint8_t sink_volume;
801a551b1bSMatthias Ringwald
813d3351f3SMatthias Ringwald static PaStream * stream_source;
823d3351f3SMatthias Ringwald static PaStream * stream_sink;
83a89df2dfSMatthias Ringwald
84a89df2dfSMatthias Ringwald // client
85a89df2dfSMatthias Ringwald static void (*playback_callback)(int16_t * buffer, uint16_t num_samples);
86a89df2dfSMatthias Ringwald static void (*recording_callback)(const int16_t * buffer, uint16_t num_samples);
87a89df2dfSMatthias Ringwald
88a89df2dfSMatthias Ringwald // output buffer
8930b419f4SMatthias Ringwald static int16_t output_buffer_storage[NUM_OUTPUT_BUFFERS * NUM_FRAMES_PER_PA_BUFFER * MAX_NR_AUDIO_CHANNELS];
90ee37d7ffSMatthias Ringwald static int16_t * output_buffers[NUM_OUTPUT_BUFFERS];
91a89df2dfSMatthias Ringwald static int output_buffer_to_play;
92a89df2dfSMatthias Ringwald static int output_buffer_to_fill;
93a89df2dfSMatthias Ringwald
94a89df2dfSMatthias Ringwald // input buffer
9514200bb8SDirk Helbig static int16_t input_buffer_storage[NUM_INPUT_BUFFERS * NUM_FRAMES_PER_PA_BUFFER * MAX_NR_AUDIO_CHANNELS];
9614200bb8SDirk Helbig static int16_t * input_buffers[NUM_INPUT_BUFFERS];
97a89df2dfSMatthias Ringwald static int input_buffer_to_record;
98a89df2dfSMatthias Ringwald static int input_buffer_to_fill;
99a89df2dfSMatthias Ringwald
100a89df2dfSMatthias Ringwald
101a89df2dfSMatthias Ringwald // timer to fill output ring buffer
1023d3351f3SMatthias Ringwald static btstack_timer_source_t driver_timer_sink;
1033d3351f3SMatthias Ringwald static btstack_timer_source_t driver_timer_source;
104a89df2dfSMatthias Ringwald
portaudio_callback_sink(const void * inputBuffer,void * outputBuffer,unsigned long frames_per_buffer,const PaStreamCallbackTimeInfo * timeInfo,PaStreamCallbackFlags statusFlags,void * userData)1053d3351f3SMatthias Ringwald static int portaudio_callback_sink( const void * inputBuffer,
106a89df2dfSMatthias Ringwald void * outputBuffer,
107ee37d7ffSMatthias Ringwald unsigned long frames_per_buffer,
108a89df2dfSMatthias Ringwald const PaStreamCallbackTimeInfo * timeInfo,
109a89df2dfSMatthias Ringwald PaStreamCallbackFlags statusFlags,
110a89df2dfSMatthias Ringwald void * userData ) {
111a89df2dfSMatthias Ringwald
112a89df2dfSMatthias Ringwald /** portaudio_callback is called from different thread, don't use hci_dump / log_info here without additional checks */
113a89df2dfSMatthias Ringwald
114a89df2dfSMatthias Ringwald (void) timeInfo; /* Prevent unused variable warnings. */
115a89df2dfSMatthias Ringwald (void) statusFlags;
116a89df2dfSMatthias Ringwald (void) userData;
117ee37d7ffSMatthias Ringwald (void) frames_per_buffer;
1183d3351f3SMatthias Ringwald (void) inputBuffer;
119a89df2dfSMatthias Ringwald
1201a551b1bSMatthias Ringwald // simplified volume control
1211a551b1bSMatthias Ringwald uint16_t index;
1221a551b1bSMatthias Ringwald int16_t * from_buffer = output_buffers[output_buffer_to_play];
1231a551b1bSMatthias Ringwald int16_t * to_buffer = (int16_t *) outputBuffer;
124ee37d7ffSMatthias Ringwald btstack_assert(frames_per_buffer == NUM_FRAMES_PER_PA_BUFFER);
1251a551b1bSMatthias Ringwald
1261a551b1bSMatthias Ringwald #if 0
1271a551b1bSMatthias Ringwald // up to 8 right shifts
1281a551b1bSMatthias Ringwald int right_shift = 8 - btstack_min(8, ((sink_volume + 15) / 16));
1291a551b1bSMatthias Ringwald for (index = 0; index < (NUM_FRAMES_PER_PA_BUFFER * num_channels_sink); index++){
1301a551b1bSMatthias Ringwald *to_buffer++ = (*from_buffer++) >> right_shift;
1311a551b1bSMatthias Ringwald }
1321a551b1bSMatthias Ringwald #else
1331a551b1bSMatthias Ringwald // multiply with volume ^ 4
1341a551b1bSMatthias Ringwald int16_t x2 = sink_volume * sink_volume;
1351a551b1bSMatthias Ringwald int16_t x4 = (x2 * x2) >> 14;
1361a551b1bSMatthias Ringwald for (index = 0; index < (NUM_FRAMES_PER_PA_BUFFER * num_channels_sink); index++){
1371a551b1bSMatthias Ringwald *to_buffer++ = ((*from_buffer++) * x4) >> 14;
1381a551b1bSMatthias Ringwald }
1391a551b1bSMatthias Ringwald #endif
140a89df2dfSMatthias Ringwald
141a89df2dfSMatthias Ringwald // next
142a89df2dfSMatthias Ringwald output_buffer_to_play = (output_buffer_to_play + 1 ) % NUM_OUTPUT_BUFFERS;
1433d3351f3SMatthias Ringwald
1443d3351f3SMatthias Ringwald return 0;
145a89df2dfSMatthias Ringwald }
146a89df2dfSMatthias Ringwald
portaudio_callback_source(const void * inputBuffer,void * outputBuffer,unsigned long samples_per_buffer,const PaStreamCallbackTimeInfo * timeInfo,PaStreamCallbackFlags statusFlags,void * userData)1473d3351f3SMatthias Ringwald static int portaudio_callback_source( const void * inputBuffer,
1483d3351f3SMatthias Ringwald void * outputBuffer,
1493d3351f3SMatthias Ringwald unsigned long samples_per_buffer,
1503d3351f3SMatthias Ringwald const PaStreamCallbackTimeInfo * timeInfo,
1513d3351f3SMatthias Ringwald PaStreamCallbackFlags statusFlags,
1523d3351f3SMatthias Ringwald void * userData ) {
1533d3351f3SMatthias Ringwald
1543d3351f3SMatthias Ringwald /** portaudio_callback is called from different thread, don't use hci_dump / log_info here without additional checks */
1553d3351f3SMatthias Ringwald
1563d3351f3SMatthias Ringwald (void) timeInfo; /* Prevent unused variable warnings. */
1573d3351f3SMatthias Ringwald (void) statusFlags;
1583d3351f3SMatthias Ringwald (void) userData;
1593d3351f3SMatthias Ringwald (void) samples_per_buffer;
1603d3351f3SMatthias Ringwald (void) outputBuffer;
161a89df2dfSMatthias Ringwald
162a89df2dfSMatthias Ringwald // store in one of our buffers
163bcd51729SMatthias Ringwald memcpy(input_buffers[input_buffer_to_fill], inputBuffer, NUM_FRAMES_PER_PA_BUFFER * num_bytes_per_sample_source);
164a89df2dfSMatthias Ringwald
165a89df2dfSMatthias Ringwald // next
166a89df2dfSMatthias Ringwald input_buffer_to_fill = (input_buffer_to_fill + 1 ) % NUM_INPUT_BUFFERS;
167a89df2dfSMatthias Ringwald
168a89df2dfSMatthias Ringwald return 0;
169a89df2dfSMatthias Ringwald }
170a89df2dfSMatthias Ringwald
driver_timer_handler_sink(btstack_timer_source_t * ts)1713d3351f3SMatthias Ringwald static void driver_timer_handler_sink(btstack_timer_source_t * ts){
172a89df2dfSMatthias Ringwald
173a89df2dfSMatthias Ringwald // playback buffer ready to fill
174a0473563SMatthias Ringwald while (output_buffer_to_play != output_buffer_to_fill){
175a89df2dfSMatthias Ringwald (*playback_callback)(output_buffers[output_buffer_to_fill], NUM_FRAMES_PER_PA_BUFFER);
176a89df2dfSMatthias Ringwald
177a89df2dfSMatthias Ringwald // next
178a89df2dfSMatthias Ringwald output_buffer_to_fill = (output_buffer_to_fill + 1 ) % NUM_OUTPUT_BUFFERS;
179a89df2dfSMatthias Ringwald }
180a89df2dfSMatthias Ringwald
1813d3351f3SMatthias Ringwald // re-set timer
1823d3351f3SMatthias Ringwald btstack_run_loop_set_timer(ts, DRIVER_POLL_INTERVAL_MS);
1833d3351f3SMatthias Ringwald btstack_run_loop_add_timer(ts);
1843d3351f3SMatthias Ringwald }
1853d3351f3SMatthias Ringwald
driver_timer_handler_source(btstack_timer_source_t * ts)1863d3351f3SMatthias Ringwald static void driver_timer_handler_source(btstack_timer_source_t * ts){
1873d3351f3SMatthias Ringwald
188a89df2dfSMatthias Ringwald // recording buffer ready to process
1893d3351f3SMatthias Ringwald if (input_buffer_to_record != input_buffer_to_fill){
190a89df2dfSMatthias Ringwald
191a89df2dfSMatthias Ringwald (*recording_callback)(input_buffers[input_buffer_to_record], NUM_FRAMES_PER_PA_BUFFER);
192a89df2dfSMatthias Ringwald
193a89df2dfSMatthias Ringwald // next
194a89df2dfSMatthias Ringwald input_buffer_to_record = (input_buffer_to_record + 1 ) % NUM_INPUT_BUFFERS;
195a89df2dfSMatthias Ringwald }
196a89df2dfSMatthias Ringwald
197a89df2dfSMatthias Ringwald // re-set timer
198a89df2dfSMatthias Ringwald btstack_run_loop_set_timer(ts, DRIVER_POLL_INTERVAL_MS);
199a89df2dfSMatthias Ringwald btstack_run_loop_add_timer(ts);
200a89df2dfSMatthias Ringwald }
201a89df2dfSMatthias Ringwald
btstack_audio_portaudio_sink_init(uint8_t channels,uint32_t samplerate,void (* playback)(int16_t * buffer,uint16_t num_samples))2023d3351f3SMatthias Ringwald static int btstack_audio_portaudio_sink_init(
203a89df2dfSMatthias Ringwald uint8_t channels,
204a89df2dfSMatthias Ringwald uint32_t samplerate,
2053d3351f3SMatthias Ringwald void (*playback)(int16_t * buffer, uint16_t num_samples)
206a89df2dfSMatthias Ringwald ){
2073d3351f3SMatthias Ringwald PaError err;
208a89df2dfSMatthias Ringwald
20930b419f4SMatthias Ringwald btstack_assert(channels <= MAX_NR_AUDIO_CHANNELS);
21030b419f4SMatthias Ringwald
2111a551b1bSMatthias Ringwald num_channels_sink = channels;
212bcd51729SMatthias Ringwald num_bytes_per_sample_sink = 2 * channels;
213a89df2dfSMatthias Ringwald
2143d3351f3SMatthias Ringwald if (!playback){
2153d3351f3SMatthias Ringwald log_error("No playback callback");
2163d3351f3SMatthias Ringwald return 1;
2173d3351f3SMatthias Ringwald }
2183d3351f3SMatthias Ringwald
21914200bb8SDirk Helbig for (int i=0;i<NUM_OUTPUT_BUFFERS;i++){
22014200bb8SDirk Helbig output_buffers[i] = &output_buffer_storage[i * NUM_FRAMES_PER_PA_BUFFER * MAX_NR_AUDIO_CHANNELS];
22114200bb8SDirk Helbig }
22214200bb8SDirk Helbig
223a89df2dfSMatthias Ringwald /* -- initialize PortAudio -- */
2243d3351f3SMatthias Ringwald if (!portaudio_initialized){
2253d3351f3SMatthias Ringwald err = Pa_Initialize();
226a89df2dfSMatthias Ringwald if (err != paNoError){
22724cc6cccSMatthias Ringwald log_error("Portudio: error initializing portaudio: \"%s\"\n", Pa_GetErrorText(err));
228a89df2dfSMatthias Ringwald return err;
229a89df2dfSMatthias Ringwald }
2303d3351f3SMatthias Ringwald portaudio_initialized = 1;
2313d3351f3SMatthias Ringwald }
232a89df2dfSMatthias Ringwald
233*409f627cSMatthias Ringwald /* -- find output device by name if requested -- */
234*409f627cSMatthias Ringwald PaDeviceIndex device_index = -1;
235*409f627cSMatthias Ringwald const PaDeviceInfo *output_device_info;
236*409f627cSMatthias Ringwald if (sink_device_name != NULL){
237*409f627cSMatthias Ringwald int num_devices = Pa_GetDeviceCount();
238*409f627cSMatthias Ringwald for (int i = 0; i < num_devices; i++) {
239*409f627cSMatthias Ringwald output_device_info = Pa_GetDeviceInfo(i);
240*409f627cSMatthias Ringwald // Match device by prefix
241*409f627cSMatthias Ringwald if (strncmp(output_device_info->name, sink_device_name, strlen(sink_device_name)) == 0) {
242*409f627cSMatthias Ringwald device_index = i;
243*409f627cSMatthias Ringwald break;
244*409f627cSMatthias Ringwald }
245*409f627cSMatthias Ringwald }
246*409f627cSMatthias Ringwald }
247a89df2dfSMatthias Ringwald
248*409f627cSMatthias Ringwald /* -- use default device otherwise -- */
249*409f627cSMatthias Ringwald if (device_index < 0){
250*409f627cSMatthias Ringwald device_index = Pa_GetDefaultOutputDevice();
251*409f627cSMatthias Ringwald output_device_info = Pa_GetDeviceInfo(device_index );
252*409f627cSMatthias Ringwald }
253*409f627cSMatthias Ringwald /* -- setup output -- */
254*409f627cSMatthias Ringwald PaStreamParameters output_parameters;
255*409f627cSMatthias Ringwald output_parameters.device = device_index;
256*409f627cSMatthias Ringwald output_parameters.channelCount = channels;
257*409f627cSMatthias Ringwald output_parameters.sampleFormat = PA_SAMPLE_TYPE;
258*409f627cSMatthias Ringwald output_parameters.suggestedLatency = output_device_info->defaultHighOutputLatency;
259*409f627cSMatthias Ringwald output_parameters.hostApiSpecificStreamInfo = NULL;
260*409f627cSMatthias Ringwald
261*409f627cSMatthias Ringwald log_info("PortAudio: sink device: %s", output_device_info->name);
262*409f627cSMatthias Ringwald UNUSED(output_device_info);
263a89df2dfSMatthias Ringwald
2643d3351f3SMatthias Ringwald /* -- setup stream -- */
2653d3351f3SMatthias Ringwald err = Pa_OpenStream(
2663abb6826SMatthias Ringwald &stream_sink,
2673d3351f3SMatthias Ringwald NULL,
268*409f627cSMatthias Ringwald &output_parameters,
2693d3351f3SMatthias Ringwald samplerate,
2703d3351f3SMatthias Ringwald NUM_FRAMES_PER_PA_BUFFER,
2713d3351f3SMatthias Ringwald paClipOff, /* we won't output out of range samples so don't bother clipping them */
2723d3351f3SMatthias Ringwald portaudio_callback_sink, /* use callback */
2733d3351f3SMatthias Ringwald NULL );
2743d3351f3SMatthias Ringwald
2753d3351f3SMatthias Ringwald if (err != paNoError){
27624cc6cccSMatthias Ringwald log_error("Portudio: error initializing portaudio: \"%s\"\n", Pa_GetErrorText(err));
2773d3351f3SMatthias Ringwald return err;
2783d3351f3SMatthias Ringwald }
27924cc6cccSMatthias Ringwald log_info("PortAudio: sink stream created");
2803d3351f3SMatthias Ringwald
2813abb6826SMatthias Ringwald const PaStreamInfo * stream_info = Pa_GetStreamInfo(stream_sink);
28224cc6cccSMatthias Ringwald log_info("PortAudio: sink latency: %f", stream_info->outputLatency);
283894c930cSMatthias Ringwald UNUSED(stream_info);
2843d3351f3SMatthias Ringwald
2853d3351f3SMatthias Ringwald playback_callback = playback;
2863d3351f3SMatthias Ringwald
2873d3351f3SMatthias Ringwald sink_initialized = 1;
2881a551b1bSMatthias Ringwald sink_volume = 127;
2893d3351f3SMatthias Ringwald
2903d3351f3SMatthias Ringwald return 0;
2913d3351f3SMatthias Ringwald }
2923d3351f3SMatthias Ringwald
btstack_audio_portaudio_source_init(uint8_t channels,uint32_t samplerate,void (* recording)(const int16_t * buffer,uint16_t num_samples))2933d3351f3SMatthias Ringwald static int btstack_audio_portaudio_source_init(
2943d3351f3SMatthias Ringwald uint8_t channels,
2953d3351f3SMatthias Ringwald uint32_t samplerate,
2963d3351f3SMatthias Ringwald void (*recording)(const int16_t * buffer, uint16_t num_samples)
2973d3351f3SMatthias Ringwald ){
2983d3351f3SMatthias Ringwald PaError err;
2993d3351f3SMatthias Ringwald
30030b419f4SMatthias Ringwald btstack_assert(channels <= MAX_NR_AUDIO_CHANNELS);
30130b419f4SMatthias Ringwald
3021a551b1bSMatthias Ringwald num_channels_source = channels;
303bcd51729SMatthias Ringwald num_bytes_per_sample_source = 2 * channels;
3043d3351f3SMatthias Ringwald
3053d3351f3SMatthias Ringwald if (!recording){
3063d3351f3SMatthias Ringwald log_error("No recording callback");
3073d3351f3SMatthias Ringwald return 1;
3083d3351f3SMatthias Ringwald }
3093d3351f3SMatthias Ringwald
31014200bb8SDirk Helbig for (int i=0;i<NUM_INPUT_BUFFERS;i++){
31114200bb8SDirk Helbig input_buffers[i] = &input_buffer_storage[i * NUM_FRAMES_PER_PA_BUFFER * MAX_NR_AUDIO_CHANNELS];
31214200bb8SDirk Helbig }
31314200bb8SDirk Helbig
3143d3351f3SMatthias Ringwald /* -- initialize PortAudio -- */
3153d3351f3SMatthias Ringwald if (!portaudio_initialized){
3163d3351f3SMatthias Ringwald err = Pa_Initialize();
3173d3351f3SMatthias Ringwald if (err != paNoError){
31824cc6cccSMatthias Ringwald log_error("Portudio: Error initializing portaudio: \"%s\"\n", Pa_GetErrorText(err));
3193d3351f3SMatthias Ringwald return err;
3203d3351f3SMatthias Ringwald }
3213d3351f3SMatthias Ringwald portaudio_initialized = 1;
322a89df2dfSMatthias Ringwald }
323a89df2dfSMatthias Ringwald
324*409f627cSMatthias Ringwald /* -- find input device by name if requested -- */
325*409f627cSMatthias Ringwald PaDeviceIndex device_index = -1;
326*409f627cSMatthias Ringwald const PaDeviceInfo *input_device_info;
327*409f627cSMatthias Ringwald if (source_device_name != NULL){
328*409f627cSMatthias Ringwald int num_devices = Pa_GetDeviceCount();
329*409f627cSMatthias Ringwald for (int i = 0; i < num_devices; i++) {
330*409f627cSMatthias Ringwald input_device_info = Pa_GetDeviceInfo(i);
331*409f627cSMatthias Ringwald // Match device by prefix
332*409f627cSMatthias Ringwald if (strncmp(input_device_info->name, source_device_name, strlen(source_device_name)) == 0) {
333*409f627cSMatthias Ringwald device_index = i;
334*409f627cSMatthias Ringwald break;
335*409f627cSMatthias Ringwald }
336*409f627cSMatthias Ringwald }
337*409f627cSMatthias Ringwald }
338*409f627cSMatthias Ringwald
339*409f627cSMatthias Ringwald /* -- use default device otherwise -- */
340*409f627cSMatthias Ringwald if (device_index < 0){
341*409f627cSMatthias Ringwald device_index = Pa_GetDefaultInputDevice();
342*409f627cSMatthias Ringwald input_device_info = Pa_GetDeviceInfo(device_index );
343*409f627cSMatthias Ringwald }
344*409f627cSMatthias Ringwald
345a89df2dfSMatthias Ringwald /* -- setup input -- */
3463d3351f3SMatthias Ringwald PaStreamParameters theInputParameters;
347*409f627cSMatthias Ringwald theInputParameters.device = device_index;
348a89df2dfSMatthias Ringwald theInputParameters.channelCount = channels;
349a89df2dfSMatthias Ringwald theInputParameters.sampleFormat = PA_SAMPLE_TYPE;
350*409f627cSMatthias Ringwald theInputParameters.suggestedLatency = input_device_info->defaultHighInputLatency;
351a89df2dfSMatthias Ringwald theInputParameters.hostApiSpecificStreamInfo = NULL;
352a89df2dfSMatthias Ringwald
353*409f627cSMatthias Ringwald log_info("PortAudio: source device: %s", input_device_info->name);
354*409f627cSMatthias Ringwald UNUSED(input_device_info);
355a89df2dfSMatthias Ringwald
356a89df2dfSMatthias Ringwald /* -- setup stream -- */
357a89df2dfSMatthias Ringwald err = Pa_OpenStream(
3583abb6826SMatthias Ringwald &stream_source,
3593d3351f3SMatthias Ringwald &theInputParameters,
3603d3351f3SMatthias Ringwald NULL,
361a89df2dfSMatthias Ringwald samplerate,
362a89df2dfSMatthias Ringwald NUM_FRAMES_PER_PA_BUFFER,
363a89df2dfSMatthias Ringwald paClipOff, /* we won't output out of range samples so don't bother clipping them */
3643d3351f3SMatthias Ringwald portaudio_callback_source, /* use callback */
365a89df2dfSMatthias Ringwald NULL );
366a89df2dfSMatthias Ringwald
367a89df2dfSMatthias Ringwald if (err != paNoError){
368a89df2dfSMatthias Ringwald log_error("Error initializing portaudio: \"%s\"\n", Pa_GetErrorText(err));
369a89df2dfSMatthias Ringwald return err;
370a89df2dfSMatthias Ringwald }
37124cc6cccSMatthias Ringwald log_info("PortAudio: source stream created");
372a89df2dfSMatthias Ringwald
3733abb6826SMatthias Ringwald const PaStreamInfo * stream_info = Pa_GetStreamInfo(stream_source);
37424cc6cccSMatthias Ringwald log_info("PortAudio: source latency: %f", stream_info->inputLatency);
375894c930cSMatthias Ringwald UNUSED(stream_info);
376a89df2dfSMatthias Ringwald
377a89df2dfSMatthias Ringwald recording_callback = recording;
378a89df2dfSMatthias Ringwald
3793d3351f3SMatthias Ringwald source_initialized = 1;
3803d3351f3SMatthias Ringwald
381a89df2dfSMatthias Ringwald return 0;
382a89df2dfSMatthias Ringwald }
383a89df2dfSMatthias Ringwald
btstack_audio_portaudio_sink_get_samplerate(void)3846926c297SDirk Helbig static uint32_t btstack_audio_portaudio_sink_get_samplerate(void) {
3856926c297SDirk Helbig const PaStreamInfo *stream_info = Pa_GetStreamInfo(stream_sink);
3866926c297SDirk Helbig return stream_info->sampleRate;
3876926c297SDirk Helbig }
3886926c297SDirk Helbig
btstack_audio_portaudio_source_get_samplerate(void)3896926c297SDirk Helbig static uint32_t btstack_audio_portaudio_source_get_samplerate(void) {
3906926c297SDirk Helbig const PaStreamInfo *stream_info = Pa_GetStreamInfo(stream_source);
3916926c297SDirk Helbig return stream_info->sampleRate;
3926926c297SDirk Helbig }
3936926c297SDirk Helbig
btstack_audio_portaudio_sink_set_volume(uint8_t volume)3941b7f8fa1SMatthias Ringwald static void btstack_audio_portaudio_sink_set_volume(uint8_t volume){
3951a551b1bSMatthias Ringwald sink_volume = volume;
3961b7f8fa1SMatthias Ringwald }
3971b7f8fa1SMatthias Ringwald
btstack_audio_portaudio_source_set_gain(uint8_t gain)3981b7f8fa1SMatthias Ringwald static void btstack_audio_portaudio_source_set_gain(uint8_t gain){
3991b7f8fa1SMatthias Ringwald UNUSED(gain);
4001b7f8fa1SMatthias Ringwald }
4011b7f8fa1SMatthias Ringwald
btstack_audio_portaudio_sink_start_stream(void)4023d3351f3SMatthias Ringwald static void btstack_audio_portaudio_sink_start_stream(void){
4033d3351f3SMatthias Ringwald
4043d3351f3SMatthias Ringwald if (!playback_callback) return;
405a89df2dfSMatthias Ringwald
406ee37d7ffSMatthias Ringwald // fill buffers once
407ee37d7ffSMatthias Ringwald uint8_t i;
408ee37d7ffSMatthias Ringwald for (i=0;i<NUM_OUTPUT_BUFFERS-1;i++){
40930b419f4SMatthias Ringwald (*playback_callback)(&output_buffer_storage[i * NUM_FRAMES_PER_PA_BUFFER * MAX_NR_AUDIO_CHANNELS], NUM_FRAMES_PER_PA_BUFFER);
410ee37d7ffSMatthias Ringwald }
411a89df2dfSMatthias Ringwald output_buffer_to_play = 0;
412ee37d7ffSMatthias Ringwald output_buffer_to_fill = NUM_OUTPUT_BUFFERS-1;
413a89df2dfSMatthias Ringwald
414a89df2dfSMatthias Ringwald /* -- start stream -- */
415df991ce9SMatthias Ringwald PaError err = Pa_StartStream(stream_sink);
416a89df2dfSMatthias Ringwald if (err != paNoError){
41724cc6cccSMatthias Ringwald log_error("PortAudio: error starting sink stream: \"%s\"\n", Pa_GetErrorText(err));
418a89df2dfSMatthias Ringwald return;
419a89df2dfSMatthias Ringwald }
420a89df2dfSMatthias Ringwald
421a89df2dfSMatthias Ringwald // start timer
4223d3351f3SMatthias Ringwald btstack_run_loop_set_timer_handler(&driver_timer_sink, &driver_timer_handler_sink);
4233d3351f3SMatthias Ringwald btstack_run_loop_set_timer(&driver_timer_sink, DRIVER_POLL_INTERVAL_MS);
4243d3351f3SMatthias Ringwald btstack_run_loop_add_timer(&driver_timer_sink);
4253d3351f3SMatthias Ringwald
4263d3351f3SMatthias Ringwald sink_active = 1;
427a89df2dfSMatthias Ringwald }
428a89df2dfSMatthias Ringwald
btstack_audio_portaudio_source_start_stream(void)4293d3351f3SMatthias Ringwald static void btstack_audio_portaudio_source_start_stream(void){
4303d3351f3SMatthias Ringwald
4313d3351f3SMatthias Ringwald if (!recording_callback) return;
4323d3351f3SMatthias Ringwald
4333d3351f3SMatthias Ringwald /* -- start stream -- */
434df991ce9SMatthias Ringwald PaError err = Pa_StartStream(stream_source);
4353d3351f3SMatthias Ringwald if (err != paNoError){
43624cc6cccSMatthias Ringwald log_error("PortAudio: error starting source stream: \"%s\"\n", Pa_GetErrorText(err));
4373d3351f3SMatthias Ringwald return;
4383d3351f3SMatthias Ringwald }
4393d3351f3SMatthias Ringwald
4403d3351f3SMatthias Ringwald // start timer
4413d3351f3SMatthias Ringwald btstack_run_loop_set_timer_handler(&driver_timer_source, &driver_timer_handler_source);
4423d3351f3SMatthias Ringwald btstack_run_loop_set_timer(&driver_timer_source, DRIVER_POLL_INTERVAL_MS);
4433d3351f3SMatthias Ringwald btstack_run_loop_add_timer(&driver_timer_source);
4443d3351f3SMatthias Ringwald
4453d3351f3SMatthias Ringwald source_active = 1;
4463d3351f3SMatthias Ringwald }
4473d3351f3SMatthias Ringwald
btstack_audio_portaudio_sink_stop_stream(void)4483d3351f3SMatthias Ringwald static void btstack_audio_portaudio_sink_stop_stream(void){
4493d3351f3SMatthias Ringwald
4503d3351f3SMatthias Ringwald if (!playback_callback) return;
4513d3351f3SMatthias Ringwald if (!sink_active) return;
452a89df2dfSMatthias Ringwald
453a89df2dfSMatthias Ringwald // stop timer
4543d3351f3SMatthias Ringwald btstack_run_loop_remove_timer(&driver_timer_sink);
455a89df2dfSMatthias Ringwald
4563d3351f3SMatthias Ringwald PaError err = Pa_StopStream(stream_sink);
457a89df2dfSMatthias Ringwald if (err != paNoError){
45824cc6cccSMatthias Ringwald log_error("PortAudio: error stopping sink stream: \"%s\"", Pa_GetErrorText(err));
459a89df2dfSMatthias Ringwald return;
460a89df2dfSMatthias Ringwald }
46124cc6cccSMatthias Ringwald
46224cc6cccSMatthias Ringwald sink_active = 0;
4633d3351f3SMatthias Ringwald }
4643d3351f3SMatthias Ringwald
btstack_audio_portaudio_source_stop_stream(void)4653d3351f3SMatthias Ringwald static void btstack_audio_portaudio_source_stop_stream(void){
4663d3351f3SMatthias Ringwald
4673d3351f3SMatthias Ringwald if (!recording_callback) return;
4683d3351f3SMatthias Ringwald if (!source_active) return;
4693d3351f3SMatthias Ringwald
4703d3351f3SMatthias Ringwald // stop timer
4713d3351f3SMatthias Ringwald btstack_run_loop_remove_timer(&driver_timer_source);
4723d3351f3SMatthias Ringwald
4733d3351f3SMatthias Ringwald PaError err = Pa_StopStream(stream_source);
474a89df2dfSMatthias Ringwald if (err != paNoError){
47524cc6cccSMatthias Ringwald log_error("PortAudio: error stopping source stream: \"%s\"", Pa_GetErrorText(err));
476a89df2dfSMatthias Ringwald return;
477a89df2dfSMatthias Ringwald }
47824cc6cccSMatthias Ringwald
47924cc6cccSMatthias Ringwald source_active = 0;
4803d3351f3SMatthias Ringwald }
4813d3351f3SMatthias Ringwald
btstack_audio_portaudio_close_pa_if_not_needed(void)4823d3351f3SMatthias Ringwald static void btstack_audio_portaudio_close_pa_if_not_needed(void){
4833d3351f3SMatthias Ringwald if (source_initialized) return;
4843d3351f3SMatthias Ringwald if (sink_initialized) return;
4853d3351f3SMatthias Ringwald PaError err = Pa_Terminate();
486a89df2dfSMatthias Ringwald if (err != paNoError){
48724cc6cccSMatthias Ringwald log_error("Portudio: Error terminating portaudio: \"%s\"", Pa_GetErrorText(err));
488a89df2dfSMatthias Ringwald return;
489a89df2dfSMatthias Ringwald }
4901faf5e55SMatthias Ringwald portaudio_initialized = 0;
491a89df2dfSMatthias Ringwald }
492a89df2dfSMatthias Ringwald
btstack_audio_portaudio_sink_close(void)4933d3351f3SMatthias Ringwald static void btstack_audio_portaudio_sink_close(void){
4943d3351f3SMatthias Ringwald
4953d3351f3SMatthias Ringwald if (!playback_callback) return;
4963d3351f3SMatthias Ringwald
4973d3351f3SMatthias Ringwald if (sink_active){
4983d3351f3SMatthias Ringwald btstack_audio_portaudio_sink_stop_stream();
4993d3351f3SMatthias Ringwald }
5003d3351f3SMatthias Ringwald
5011247fb8fSMatthias Ringwald PaError err = Pa_CloseStream(stream_sink);
5023d3351f3SMatthias Ringwald if (err != paNoError){
50324cc6cccSMatthias Ringwald log_error("PortAudio: error closing sink stream: \"%s\"", Pa_GetErrorText(err));
5043d3351f3SMatthias Ringwald return;
5053d3351f3SMatthias Ringwald }
5063d3351f3SMatthias Ringwald
5073d3351f3SMatthias Ringwald sink_initialized = 0;
5083d3351f3SMatthias Ringwald btstack_audio_portaudio_close_pa_if_not_needed();
5093d3351f3SMatthias Ringwald }
5103d3351f3SMatthias Ringwald
btstack_audio_portaudio_source_close(void)5113d3351f3SMatthias Ringwald static void btstack_audio_portaudio_source_close(void){
5123d3351f3SMatthias Ringwald
5133d3351f3SMatthias Ringwald if (!recording_callback) return;
5143d3351f3SMatthias Ringwald
5153d3351f3SMatthias Ringwald if (source_active){
5162d66e797SMatthias Ringwald btstack_audio_portaudio_source_stop_stream();
5173d3351f3SMatthias Ringwald }
5183d3351f3SMatthias Ringwald
5191247fb8fSMatthias Ringwald PaError err = Pa_CloseStream(stream_source);
5203d3351f3SMatthias Ringwald if (err != paNoError){
52124cc6cccSMatthias Ringwald log_error("PortAudio: error closing source stream: \"%s\"", Pa_GetErrorText(err));
5223d3351f3SMatthias Ringwald return;
5233d3351f3SMatthias Ringwald }
5243d3351f3SMatthias Ringwald
5253d3351f3SMatthias Ringwald source_initialized = 0;
5263d3351f3SMatthias Ringwald btstack_audio_portaudio_close_pa_if_not_needed();
5273d3351f3SMatthias Ringwald }
5283d3351f3SMatthias Ringwald
5293d3351f3SMatthias Ringwald static const btstack_audio_sink_t btstack_audio_portaudio_sink = {
5303d3351f3SMatthias Ringwald /* int (*init)(..);*/ &btstack_audio_portaudio_sink_init,
5316926c297SDirk Helbig /* uint32_t (*get_samplerate)() */ &btstack_audio_portaudio_sink_get_samplerate,
5321b7f8fa1SMatthias Ringwald /* void (*set_volume)(uint8_t volume); */ &btstack_audio_portaudio_sink_set_volume,
5333d3351f3SMatthias Ringwald /* void (*start_stream(void));*/ &btstack_audio_portaudio_sink_start_stream,
5341247fb8fSMatthias Ringwald /* void (*stop_stream)(void) */ &btstack_audio_portaudio_sink_stop_stream,
5353d3351f3SMatthias Ringwald /* void (*close)(void); */ &btstack_audio_portaudio_sink_close
536a89df2dfSMatthias Ringwald };
537a89df2dfSMatthias Ringwald
5383d3351f3SMatthias Ringwald static const btstack_audio_source_t btstack_audio_portaudio_source = {
5393d3351f3SMatthias Ringwald /* int (*init)(..);*/ &btstack_audio_portaudio_source_init,
5406926c297SDirk Helbig /* uint32_t (*get_samplerate)() */ &btstack_audio_portaudio_source_get_samplerate,
5411b7f8fa1SMatthias Ringwald /* void (*set_gain)(uint8_t gain); */ &btstack_audio_portaudio_source_set_gain,
5423d3351f3SMatthias Ringwald /* void (*start_stream(void));*/ &btstack_audio_portaudio_source_start_stream,
5431247fb8fSMatthias Ringwald /* void (*stop_stream)(void) */ &btstack_audio_portaudio_source_stop_stream,
5443d3351f3SMatthias Ringwald /* void (*close)(void); */ &btstack_audio_portaudio_source_close
5453d3351f3SMatthias Ringwald };
5463d3351f3SMatthias Ringwald
btstack_audio_portaudio_sink_get_instance(void)5473d3351f3SMatthias Ringwald const btstack_audio_sink_t * btstack_audio_portaudio_sink_get_instance(void){
5483d3351f3SMatthias Ringwald return &btstack_audio_portaudio_sink;
5493d3351f3SMatthias Ringwald }
5503d3351f3SMatthias Ringwald
btstack_audio_portaudio_source_get_instance(void)5513d3351f3SMatthias Ringwald const btstack_audio_source_t * btstack_audio_portaudio_source_get_instance(void){
5523d3351f3SMatthias Ringwald return &btstack_audio_portaudio_source;
553a89df2dfSMatthias Ringwald }
554a89df2dfSMatthias Ringwald
btstack_audio_portaudio_sink_set_device(const char * device_name)555*409f627cSMatthias Ringwald void btstack_audio_portaudio_sink_set_device(const char * device_name){
556*409f627cSMatthias Ringwald sink_device_name = device_name;
557*409f627cSMatthias Ringwald }
558*409f627cSMatthias Ringwald
btstack_audio_portaudio_source_set_device(const char * device_name)559*409f627cSMatthias Ringwald void btstack_audio_portaudio_source_set_device(const char * device_name){
560*409f627cSMatthias Ringwald source_device_name = device_name;
561*409f627cSMatthias Ringwald }
562*409f627cSMatthias Ringwald
563a89df2dfSMatthias Ringwald #endif
564