diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index dd20e15..a5292c4 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -22,6 +22,9 @@ jobs: - name: Galactic Unicorn board: galactic cmake-args: '-DDISPLAY_PATH=display/galactic/galactic_unicorn.cmake' + - name: Headless Unicorn + board: none + cmake-args: '-DHEADLESS=1' runs-on: ubuntu-20.04 diff --git a/CMakeLists.txt b/CMakeLists.txt index 4381971..e64c3b3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,10 @@ set(CMAKE_CXX_STANDARD 17) pico_sdk_init() include(bluetooth/bluetooth.cmake) + +if(HEADLESS) +set(DISPLAY_NAME "Headless Unicorn") +else() include(effect/rainbow_fft.cmake) include(effect/classic_fft.cmake) @@ -21,6 +25,8 @@ else() message(WARNING "Using default display (display/galactic/galactic_unicorn.cmake)...") include(display/galactic/galactic_unicorn.cmake) endif() +endif() + add_executable(${NAME} ${CMAKE_CURRENT_LIST_DIR}/src/main.cpp @@ -37,9 +43,6 @@ target_link_libraries(${NAME} picow_bt_example_common pico_audio_i2s pico_multicore - display - rainbow_fft - classic_fft ) message(WARNING "Display: ${DISPLAY_NAME}") @@ -50,6 +53,18 @@ target_compile_definitions(${NAME} PRIVATE BLUETOOTH_DEVICE_NAME="${DISPLAY_NAME}" ) +if(HEADLESS) +target_compile_definitions(${NAME} PRIVATE + HEADLESS=1 +) +else() +target_link_libraries(${NAME} + display + rainbow_fft + classic_fft +) +endif() + pico_enable_stdio_usb(${NAME} 1) pico_add_extra_outputs(${NAME}) diff --git a/README.md b/README.md index 658ecbe..548628c 100644 --- a/README.md +++ b/README.md @@ -38,4 +38,12 @@ For Cosmic Unicorn: mkdir build.cosmic cd build.cosmic cmake .. -DPICO_SDK_PATH=../../pico-sdk -DPICO_EXTRAS_PATH=../../pico-extras -DPICO_BOARD=pico_w -DDISPLAY_PATH=display/cosmic/cosmic_unicorn.cmake -DCMAKE_BUILD_TYPE=Release +``` + +For Pico Audio, ie. without a display: + +```bash +mkdir build.headless +cd build.headless +cmake .. -DPICO_SDK_PATH=../../pico-sdk -DPICO_EXTRAS_PATH=../../pico-extras -DPICO_BOARD=none -DHEADLESS=1 -DCMAKE_BUILD_TYPE=Release ``` \ No newline at end of file diff --git a/src/a2dp_sink.cpp b/src/a2dp_sink.cpp index e045890..56948cd 100644 --- a/src/a2dp_sink.cpp +++ b/src/a2dp_sink.cpp @@ -945,7 +945,7 @@ static void stdin_process(char cmd){ switch (cmd){ case 'b': - status = a2dp_sink_establish_stream(device_addr, stream_endpoint->a2dp_local_seid, &a2dp_connection->a2dp_cid); + status = a2dp_sink_establish_stream(device_addr, /*stream_endpoint->a2dp_local_seid, */&a2dp_connection->a2dp_cid); printf(" - Create AVDTP connection to addr %s, and local seid %d, cid 0x%02x.\n", bd_addr_to_str(device_addr), a2dp_connection->a2dp_local_seid, a2dp_connection->a2dp_cid); break; diff --git a/src/btstack_audio_pico.cpp b/src/btstack_audio_pico.cpp index ff78d55..d789347 100644 --- a/src/btstack_audio_pico.cpp +++ b/src/btstack_audio_pico.cpp @@ -58,6 +58,7 @@ #include "pico/multicore.h" #include "pico/sync.h" +#ifndef HEADLESS #include "display.hpp" #include "effect.hpp" #include "lib/fixed_fft.hpp" @@ -78,10 +79,28 @@ uint32_t core1_stack[512]; int16_t effect_buf[SAMPLE_COUNT] = {0}; #endif +#ifdef EFFECTS_ON_CORE1 +auto_init_mutex(core1_effect_update); + +void core1_entry() { + while(1) { + mutex_enter_blocking(&core1_effect_update); + effects[0]->update(effect_buf, SAMPLE_COUNT); + mutex_exit(&core1_effect_update); + } +} +#endif + +#else +// If display is disabled, we'll set the sample count to the same as fixed_fft.hpp does anyway. +constexpr unsigned int SAMPLE_COUNT = 1024u; +#define DRIVER_POLL_INTERVAL_MS 5 + +#endif + static constexpr unsigned int BUFFERS_PER_FFT_SAMPLE = 2; static constexpr unsigned int SAMPLES_PER_AUDIO_BUFFER = SAMPLE_COUNT / BUFFERS_PER_FFT_SAMPLE; - // client static void (*playback_callback)(int16_t * buffer, uint16_t num_samples); @@ -99,18 +118,6 @@ static uint8_t btstack_audio_pico_channel_count; static uint8_t btstack_volume; static uint8_t btstack_last_sample_idx; -auto_init_mutex(core1_effect_update); - -#ifdef EFFECTS_ON_CORE1 -void core1_entry() { - while(1) { - mutex_enter_blocking(&core1_effect_update); - effects[0]->update(effect_buf, SAMPLE_COUNT); - mutex_exit(&core1_effect_update); - } -} -#endif - static audio_buffer_pool_t *init_audio(uint32_t sample_frequency, uint8_t channel_count) { // num channels requested by application @@ -147,6 +154,7 @@ static audio_buffer_pool_t *init_audio(uint32_t sample_frequency, uint8_t channe assert(ok); (void)ok; +#ifndef HEADLESS effects.push_back(&rainbow_fft); effects.push_back(&classic_fft); @@ -159,6 +167,8 @@ static audio_buffer_pool_t *init_audio(uint32_t sample_frequency, uint8_t channe #ifdef EFFECTS_ON_CORE1 multicore_launch_core1_with_stack(core1_entry, core1_stack, core1_stack_len); +#endif + #endif return producer_pool; @@ -171,6 +181,7 @@ static void btstack_audio_pico_sink_fill_buffers(void){ break; } +#ifndef HEADLESS if (!gpio_get(Display::SWITCH_A)) { current_effect = 0; } @@ -178,10 +189,12 @@ static void btstack_audio_pico_sink_fill_buffers(void){ if (!gpio_get(Display::SWITCH_B)) { current_effect = 1; } +#endif int16_t * buffer16 = (int16_t *) audio_buffer->buffer->bytes; (*playback_callback)(buffer16, audio_buffer->max_sample_count); +#ifndef HEADLESS #ifndef EFFECTS_ON_CORE1 effects[current_effect]->update(buffer16, SAMPLE_COUNT); #endif @@ -189,14 +202,20 @@ static void btstack_audio_pico_sink_fill_buffers(void){ #ifdef EFFECTS_ON_CORE1 mutex_enter_blocking(&core1_effect_update); #endif +#endif + for (auto i = 0u; i < SAMPLE_COUNT; i++) { +#ifndef HEADLESS #ifdef EFFECTS_ON_CORE1 effect_buf[i] = buffer16[i]; +#endif #endif buffer16[i] = (int32_t(buffer16[i]) * int32_t(btstack_volume)) >> 8; } +#ifndef HEADLESS #ifdef EFFECTS_ON_CORE1 mutex_exit(&core1_effect_update); +#endif #endif // duplicate samples for mono @@ -267,7 +286,9 @@ static void btstack_audio_pico_sink_stop_stream(void){ // state btstack_audio_pico_sink_active = false; +#ifndef HEADLESS display.clear(); +#endif } static void btstack_audio_pico_sink_close(void){