209 lines
6.8 KiB
C
209 lines
6.8 KiB
C
/*
|
|
* Copyright (C) 2022 BlueKitchen GmbH
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. Neither the name of the copyright holders nor the names of
|
|
* contributors may be used to endorse or promote products derived
|
|
* from this software without specific prior written permission.
|
|
* 4. Any redistribution, use, or modification is done solely for
|
|
* personal benefit and not for any commercial purpose or for
|
|
* monetary gain.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS
|
|
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLUEKITCHEN
|
|
* GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
|
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
|
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
|
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*
|
|
* Please inquire about commercial licensing options at
|
|
* contact@bluekitchen-gmbh.com
|
|
*
|
|
*/
|
|
|
|
#define BTSTACK_FILE__ "btstack_audio_pico.c"
|
|
|
|
/*
|
|
* btstack_audio_pico.c
|
|
*
|
|
* Implementation of btstack_audio.h using pico_i2s
|
|
*
|
|
*/
|
|
|
|
#include "btstack_config.h"
|
|
|
|
#include "btstack_debug.h"
|
|
#include "btstack_audio.h"
|
|
#include "btstack_run_loop.h"
|
|
|
|
#include <stddef.h>
|
|
#include <hardware/dma.h>
|
|
|
|
#include "pico/audio_i2s.h"
|
|
|
|
#define DRIVER_POLL_INTERVAL_MS 5
|
|
#define SAMPLES_PER_BUFFER 512
|
|
|
|
// client
|
|
static void (*playback_callback)(int16_t * buffer, uint16_t num_samples);
|
|
|
|
// timer to fill output ring buffer
|
|
static btstack_timer_source_t driver_timer_sink;
|
|
|
|
|
|
static bool btstack_audio_pico_sink_active;
|
|
|
|
// from pico-playground/audio/sine_wave/sine_wave.c
|
|
|
|
static audio_format_t btstack_audio_pico_audio_format;
|
|
static audio_buffer_format_t btstack_audio_pico_producer_format;
|
|
static audio_buffer_pool_t * btstack_audio_pico_audio_buffer_pool;
|
|
static uint8_t btstack_audio_pico_channel_count;
|
|
|
|
static audio_buffer_pool_t *init_audio(uint32_t sample_frequency, uint8_t channel_count) {
|
|
|
|
// num channels requested by application
|
|
btstack_audio_pico_channel_count = channel_count;
|
|
|
|
// always use stereo
|
|
btstack_audio_pico_audio_format.format = AUDIO_BUFFER_FORMAT_PCM_S16;
|
|
btstack_audio_pico_audio_format.sample_freq = sample_frequency;
|
|
btstack_audio_pico_audio_format.channel_count = 2;
|
|
|
|
btstack_audio_pico_producer_format.format = &btstack_audio_pico_audio_format;
|
|
btstack_audio_pico_producer_format.sample_stride = 2 * 2;
|
|
|
|
audio_buffer_pool_t * producer_pool = audio_new_producer_pool(&btstack_audio_pico_producer_format, 3, SAMPLES_PER_BUFFER); // todo correct size
|
|
|
|
audio_i2s_config_t config;
|
|
config.data_pin = PICO_AUDIO_I2S_DATA_PIN;
|
|
config.clock_pin_base = PICO_AUDIO_I2S_CLOCK_PIN_BASE;
|
|
config.dma_channel = (int8_t) dma_claim_unused_channel(true);
|
|
config.pio_sm = 0;
|
|
|
|
// audio_i2s_setup claims the channel again https://github.com/raspberrypi/pico-extras/issues/48
|
|
dma_channel_unclaim(config.dma_channel);
|
|
const audio_format_t * output_format = audio_i2s_setup(&btstack_audio_pico_audio_format, &config);
|
|
if (!output_format) {
|
|
panic("PicoAudio: Unable to open audio device.\n");
|
|
}
|
|
|
|
bool ok = audio_i2s_connect(producer_pool);
|
|
assert(ok);
|
|
(void)ok;
|
|
|
|
return producer_pool;
|
|
}
|
|
|
|
static void btstack_audio_pico_sink_fill_buffers(void){
|
|
while (true){
|
|
audio_buffer_t * audio_buffer = take_audio_buffer(btstack_audio_pico_audio_buffer_pool, false);
|
|
if (audio_buffer == NULL){
|
|
break;
|
|
}
|
|
|
|
int16_t * buffer16 = (int16_t *) audio_buffer->buffer->bytes;
|
|
(*playback_callback)(buffer16, audio_buffer->max_sample_count);
|
|
|
|
// duplicate samples for mono
|
|
if (btstack_audio_pico_channel_count == 1){
|
|
int16_t i;
|
|
for (i = SAMPLES_PER_BUFFER - 1 ; i >= 0; i--){
|
|
buffer16[2*i ] = buffer16[i];
|
|
buffer16[2*i+1] = buffer16[i];
|
|
}
|
|
}
|
|
|
|
audio_buffer->sample_count = audio_buffer->max_sample_count;
|
|
give_audio_buffer(btstack_audio_pico_audio_buffer_pool, audio_buffer);
|
|
}
|
|
}
|
|
|
|
static void driver_timer_handler_sink(btstack_timer_source_t * ts){
|
|
|
|
// refill
|
|
btstack_audio_pico_sink_fill_buffers();
|
|
|
|
// re-set timer
|
|
btstack_run_loop_set_timer(ts, DRIVER_POLL_INTERVAL_MS);
|
|
btstack_run_loop_add_timer(ts);
|
|
}
|
|
|
|
static int btstack_audio_pico_sink_init(
|
|
uint8_t channels,
|
|
uint32_t samplerate,
|
|
void (*playback)(int16_t * buffer, uint16_t num_samples)
|
|
){
|
|
btstack_assert(playback != NULL);
|
|
btstack_assert(channels != 0);
|
|
|
|
playback_callback = playback;
|
|
|
|
btstack_audio_pico_audio_buffer_pool = init_audio(samplerate, channels);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void btstack_audio_pico_sink_set_volume(uint8_t volume){
|
|
UNUSED(volume);
|
|
}
|
|
|
|
static void btstack_audio_pico_sink_start_stream(void){
|
|
|
|
// pre-fill HAL buffers
|
|
btstack_audio_pico_sink_fill_buffers();
|
|
|
|
// start timer
|
|
btstack_run_loop_set_timer_handler(&driver_timer_sink, &driver_timer_handler_sink);
|
|
btstack_run_loop_set_timer(&driver_timer_sink, DRIVER_POLL_INTERVAL_MS);
|
|
btstack_run_loop_add_timer(&driver_timer_sink);
|
|
|
|
// state
|
|
btstack_audio_pico_sink_active = true;
|
|
|
|
audio_i2s_set_enabled(true);
|
|
}
|
|
|
|
static void btstack_audio_pico_sink_stop_stream(void){
|
|
|
|
audio_i2s_set_enabled(false);
|
|
|
|
// stop timer
|
|
btstack_run_loop_remove_timer(&driver_timer_sink);
|
|
// state
|
|
btstack_audio_pico_sink_active = false;
|
|
}
|
|
|
|
static void btstack_audio_pico_sink_close(void){
|
|
// stop stream if needed
|
|
if (btstack_audio_pico_sink_active){
|
|
btstack_audio_pico_sink_stop_stream();
|
|
}
|
|
}
|
|
|
|
static const btstack_audio_sink_t btstack_audio_pico_sink = {
|
|
.init = &btstack_audio_pico_sink_init,
|
|
.set_volume = &btstack_audio_pico_sink_set_volume,
|
|
.start_stream = &btstack_audio_pico_sink_start_stream,
|
|
.stop_stream = &btstack_audio_pico_sink_stop_stream,
|
|
.close = &btstack_audio_pico_sink_close,
|
|
};
|
|
|
|
const btstack_audio_sink_t * btstack_audio_pico_sink_get_instance(void){
|
|
return &btstack_audio_pico_sink;
|
|
}
|