Put more audio pipeline wiring in control of the audio fsm

custom
jacqueline 2 years ago
parent 4e27de21e4
commit c635d5011c
  1. 29
      src/audio/audio_fsm.cpp
  2. 15
      src/audio/audio_task.cpp
  3. 2
      src/audio/bt_audio_output.cpp
  4. 2
      src/audio/i2s_audio_output.cpp
  5. 7
      src/audio/include/audio_fsm.hpp
  6. 19
      src/audio/include/audio_sink.hpp
  7. 13
      src/audio/include/audio_task.hpp
  8. 2
      src/audio/include/bt_audio_output.hpp
  9. 2
      src/audio/include/i2s_audio_output.hpp
  10. 23
      src/audio/include/sink_mixer.hpp
  11. 15
      src/audio/sink_mixer.cpp

@ -10,6 +10,7 @@
#include <memory> #include <memory>
#include <variant> #include <variant>
#include "audio_sink.hpp"
#include "esp_log.h" #include "esp_log.h"
#include "freertos/portmacro.h" #include "freertos/portmacro.h"
#include "freertos/projdefs.h" #include "freertos/projdefs.h"
@ -23,6 +24,7 @@
#include "future_fetcher.hpp" #include "future_fetcher.hpp"
#include "i2s_audio_output.hpp" #include "i2s_audio_output.hpp"
#include "i2s_dac.hpp" #include "i2s_dac.hpp"
#include "sink_mixer.hpp"
#include "system_events.hpp" #include "system_events.hpp"
#include "track.hpp" #include "track.hpp"
#include "track_queue.hpp" #include "track_queue.hpp"
@ -36,9 +38,10 @@ std::shared_ptr<drivers::I2SDac> AudioState::sDac;
std::weak_ptr<database::Database> AudioState::sDatabase; std::weak_ptr<database::Database> AudioState::sDatabase;
std::unique_ptr<AudioTask> AudioState::sTask; std::unique_ptr<AudioTask> AudioState::sTask;
std::unique_ptr<FatfsAudioInput> AudioState::sFileSource;
std::unique_ptr<I2SAudioOutput> AudioState::sI2SOutput; std::shared_ptr<FatfsAudioInput> AudioState::sFileSource;
std::unique_ptr<BluetoothAudioOutput> AudioState::sBtOutput; std::shared_ptr<SinkMixer> AudioState::sMixer;
std::shared_ptr<IAudioOutput> AudioState::sOutput;
TrackQueue* AudioState::sTrackQueue; TrackQueue* AudioState::sTrackQueue;
std::optional<database::TrackId> AudioState::sCurrentTrack; std::optional<database::TrackId> AudioState::sCurrentTrack;
@ -59,11 +62,13 @@ auto AudioState::Init(drivers::IGpios* gpio_expander,
sDatabase = database; sDatabase = database;
sFileSource.reset(new FatfsAudioInput(tag_parser)); sFileSource.reset(new FatfsAudioInput(tag_parser));
sI2SOutput.reset(new I2SAudioOutput(sIGpios, sDac)); sOutput.reset(new I2SAudioOutput(sIGpios, sDac));
sBtOutput.reset(new BluetoothAudioOutput(bluetooth)); // sOutput.reset(new BluetoothAudioOutput(bluetooth));
sMixer.reset(new SinkMixer());
sMixer->SetOutput(sOutput);
AudioTask::Start(sFileSource.get(), sI2SOutput.get()); AudioTask::Start(sFileSource, sMixer);
// AudioTask::Start(sFileSource.get(), sBtOutput.get());
return true; return true;
} }
@ -73,14 +78,14 @@ void AudioState::react(const system_fsm::StorageMounted& ev) {
} }
void AudioState::react(const system_fsm::KeyUpChanged& ev) { void AudioState::react(const system_fsm::KeyUpChanged& ev) {
if (ev.falling && sI2SOutput->AdjustVolumeUp()) { if (ev.falling && sOutput->AdjustVolumeUp()) {
ESP_LOGI(kTag, "volume up!"); ESP_LOGI(kTag, "volume up!");
events::Ui().Dispatch(VolumeChanged{}); events::Ui().Dispatch(VolumeChanged{});
} }
} }
void AudioState::react(const system_fsm::KeyDownChanged& ev) { void AudioState::react(const system_fsm::KeyDownChanged& ev) {
if (ev.falling && sI2SOutput->AdjustVolumeDown()) { if (ev.falling && sOutput->AdjustVolumeDown()) {
ESP_LOGI(kTag, "volume down!"); ESP_LOGI(kTag, "volume down!");
events::Ui().Dispatch(VolumeChanged{}); events::Ui().Dispatch(VolumeChanged{});
} }
@ -131,8 +136,7 @@ void Standby::react(const QueueUpdate& ev) {
void Playback::entry() { void Playback::entry() {
ESP_LOGI(kTag, "beginning playback"); ESP_LOGI(kTag, "beginning playback");
sI2SOutput->SetInUse(true); sOutput->SetInUse(true);
// sBtOutput->SetInUse(true);
} }
void Playback::exit() { void Playback::exit() {
@ -140,8 +144,7 @@ void Playback::exit() {
// TODO(jacqueline): Second case where it's useful to wait for the i2s buffer // TODO(jacqueline): Second case where it's useful to wait for the i2s buffer
// to drain. // to drain.
vTaskDelay(pdMS_TO_TICKS(250)); vTaskDelay(pdMS_TO_TICKS(250));
sI2SOutput->SetInUse(false); sOutput->SetInUse(false);
// sBtOutput->SetInUse(false);
} }
void Playback::react(const QueueUpdate& ev) { void Playback::react(const QueueUpdate& ev) {

@ -76,19 +76,16 @@ auto Timer::AddSamples(std::size_t samples) -> void {
} }
} }
auto AudioTask::Start(IAudioSource* source, IAudioSink* sink) -> AudioTask* { auto AudioTask::Start(std::shared_ptr<IAudioSource> source,
std::shared_ptr<SinkMixer> sink) -> AudioTask* {
AudioTask* task = new AudioTask(source, sink); AudioTask* task = new AudioTask(source, sink);
tasks::StartPersistent<tasks::Type::kAudio>([=]() { task->Main(); }); tasks::StartPersistent<tasks::Type::kAudio>([=]() { task->Main(); });
return task; return task;
} }
AudioTask::AudioTask(IAudioSource* source, IAudioSink* sink) AudioTask::AudioTask(std::shared_ptr<IAudioSource> source,
: source_(source), std::shared_ptr<SinkMixer> mixer)
sink_(sink), : source_(source), mixer_(mixer), codec_(), timer_(), current_format_() {
codec_(),
mixer_(new SinkMixer(sink)),
timer_(),
current_format_() {
codec_buffer_ = { codec_buffer_ = {
reinterpret_cast<sample::Sample*>(heap_caps_calloc( reinterpret_cast<sample::Sample*>(heap_caps_calloc(
kCodecBufferLength, sizeof(sample::Sample), MALLOC_CAP_SPIRAM)), kCodecBufferLength, sizeof(sample::Sample), MALLOC_CAP_SPIRAM)),
@ -133,7 +130,7 @@ auto AudioTask::BeginDecoding(std::shared_ptr<codecs::IStream> stream) -> bool {
timer_.reset(); timer_.reset();
} }
current_sink_format_ = IAudioSink::Format{ current_sink_format_ = IAudioOutput::Format{
.sample_rate = open_res->sample_rate_hz, .sample_rate = open_res->sample_rate_hz,
.num_channels = open_res->num_channels, .num_channels = open_res->num_channels,
.bits_per_sample = 16, .bits_per_sample = 16,

@ -30,7 +30,7 @@ namespace audio {
static constexpr size_t kDrainBufferSize = 48 * 1024; static constexpr size_t kDrainBufferSize = 48 * 1024;
BluetoothAudioOutput::BluetoothAudioOutput(drivers::Bluetooth* bt) BluetoothAudioOutput::BluetoothAudioOutput(drivers::Bluetooth* bt)
: IAudioSink(kDrainBufferSize, MALLOC_CAP_SPIRAM), bluetooth_(bt) {} : IAudioOutput(kDrainBufferSize, MALLOC_CAP_SPIRAM), bluetooth_(bt) {}
BluetoothAudioOutput::~BluetoothAudioOutput() {} BluetoothAudioOutput::~BluetoothAudioOutput() {}

@ -43,7 +43,7 @@ static constexpr size_t kDrainBufferSize = 8 * 1024;
I2SAudioOutput::I2SAudioOutput(drivers::IGpios* expander, I2SAudioOutput::I2SAudioOutput(drivers::IGpios* expander,
std::weak_ptr<drivers::I2SDac> dac) std::weak_ptr<drivers::I2SDac> dac)
: IAudioSink(kDrainBufferSize, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT), : IAudioOutput(kDrainBufferSize, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT),
expander_(expander), expander_(expander),
dac_(dac.lock()), dac_(dac.lock()),
current_config_(), current_config_(),

@ -10,6 +10,7 @@
#include <memory> #include <memory>
#include <vector> #include <vector>
#include "audio_sink.hpp"
#include "tinyfsm.hpp" #include "tinyfsm.hpp"
#include "audio_events.hpp" #include "audio_events.hpp"
@ -68,9 +69,9 @@ class AudioState : public tinyfsm::Fsm<AudioState> {
static std::weak_ptr<database::Database> sDatabase; static std::weak_ptr<database::Database> sDatabase;
static std::unique_ptr<AudioTask> sTask; static std::unique_ptr<AudioTask> sTask;
static std::unique_ptr<FatfsAudioInput> sFileSource; static std::shared_ptr<FatfsAudioInput> sFileSource;
static std::unique_ptr<I2SAudioOutput> sI2SOutput; static std::shared_ptr<SinkMixer> sMixer;
static std::unique_ptr<BluetoothAudioOutput> sBtOutput; static std::shared_ptr<IAudioOutput> sOutput;
static TrackQueue* sTrackQueue; static TrackQueue* sTrackQueue;
static std::optional<database::TrackId> sCurrentTrack; static std::optional<database::TrackId> sCurrentTrack;

@ -15,17 +15,28 @@
namespace audio { namespace audio {
class IAudioSink { /*
* Interface for classes that use PCM samples to create noises for the user.
*
* These classes do not generally have any specific task for their work, and
* simply help to mediate working out the correct PCM format, and then sending
* those samples to the appropriate hardware driver.
*/
class IAudioOutput {
private: private:
StreamBufferHandle_t stream_; StreamBufferHandle_t stream_;
public: public:
IAudioSink(size_t buffer_size, uint32_t caps) IAudioOutput(size_t buffer_size, uint32_t caps)
: stream_(xStreamBufferCreateWithCaps(buffer_size, 1, caps)) {} : stream_(xStreamBufferCreateWithCaps(buffer_size, 1, caps)) {}
virtual ~IAudioSink() { vStreamBufferDeleteWithCaps(stream_); } virtual ~IAudioOutput() { vStreamBufferDeleteWithCaps(stream_); }
virtual auto SetInUse(bool) -> void {} /*
* Indicates whether this output is currently being sent samples. If this is
* false, the output should place itself into a low power state.
*/
virtual auto SetInUse(bool) -> void = 0;
virtual auto SetVolumeImbalance(int_fast8_t balance) -> void = 0; virtual auto SetVolumeImbalance(int_fast8_t balance) -> void = 0;
virtual auto SetVolume(uint_fast8_t percent) -> void = 0; virtual auto SetVolume(uint_fast8_t percent) -> void = 0;

@ -34,7 +34,8 @@ class Timer {
class AudioTask { class AudioTask {
public: public:
static auto Start(IAudioSource* source, IAudioSink* sink) -> AudioTask*; static auto Start(std::shared_ptr<IAudioSource> source,
std::shared_ptr<SinkMixer> mixer) -> AudioTask*;
auto Main() -> void; auto Main() -> void;
@ -42,21 +43,21 @@ class AudioTask {
AudioTask& operator=(const AudioTask&) = delete; AudioTask& operator=(const AudioTask&) = delete;
private: private:
AudioTask(IAudioSource* source, IAudioSink* sink); AudioTask(std::shared_ptr<IAudioSource> source,
std::shared_ptr<SinkMixer> mixer);
auto BeginDecoding(std::shared_ptr<codecs::IStream>) -> bool; auto BeginDecoding(std::shared_ptr<codecs::IStream>) -> bool;
auto ContinueDecoding() -> bool; auto ContinueDecoding() -> bool;
IAudioSource* source_; std::shared_ptr<IAudioSource> source_;
IAudioSink* sink_; std::shared_ptr<SinkMixer> mixer_;
std::shared_ptr<codecs::IStream> stream_; std::shared_ptr<codecs::IStream> stream_;
std::unique_ptr<codecs::ICodec> codec_; std::unique_ptr<codecs::ICodec> codec_;
std::unique_ptr<SinkMixer> mixer_;
std::unique_ptr<Timer> timer_; std::unique_ptr<Timer> timer_;
std::optional<codecs::ICodec::OutputFormat> current_format_; std::optional<codecs::ICodec::OutputFormat> current_format_;
std::optional<IAudioSink::Format> current_sink_format_; std::optional<IAudioOutput::Format> current_sink_format_;
cpp::span<sample::Sample> codec_buffer_; cpp::span<sample::Sample> codec_buffer_;
}; };

@ -19,7 +19,7 @@
namespace audio { namespace audio {
class BluetoothAudioOutput : public IAudioSink { class BluetoothAudioOutput : public IAudioOutput {
public: public:
BluetoothAudioOutput(drivers::Bluetooth* bt); BluetoothAudioOutput(drivers::Bluetooth* bt);
~BluetoothAudioOutput(); ~BluetoothAudioOutput();

@ -17,7 +17,7 @@
namespace audio { namespace audio {
class I2SAudioOutput : public IAudioSink { class I2SAudioOutput : public IAudioOutput {
public: public:
I2SAudioOutput(drivers::IGpios* expander, std::weak_ptr<drivers::I2SDac> dac); I2SAudioOutput(drivers::IGpios* expander, std::weak_ptr<drivers::I2SDac> dac);
~I2SAudioOutput(); ~I2SAudioOutput();

@ -9,36 +9,37 @@
#include <cstdint> #include <cstdint>
#include <memory> #include <memory>
#include "resample.hpp"
#include "sample.hpp"
#include "audio_sink.hpp" #include "audio_sink.hpp"
#include "audio_source.hpp" #include "audio_source.hpp"
#include "codec.hpp" #include "codec.hpp"
#include "resample.hpp"
#include "sample.hpp"
namespace audio { namespace audio {
/* /*
* Handles the final downmix + resample + quantisation stage of audio, * Handles the final downmix + resample + quantisation stage of audio,
* generation sending the result directly to an IAudioSink. * generation sending the result directly to an IAudioOutput.
*/ */
class SinkMixer { class SinkMixer {
public: public:
SinkMixer(IAudioSink* sink); SinkMixer();
~SinkMixer(); ~SinkMixer();
auto SetOutput(std::shared_ptr<IAudioOutput>) -> void;
auto MixAndSend(cpp::span<sample::Sample>, auto MixAndSend(cpp::span<sample::Sample>,
const IAudioSink::Format& format, const IAudioOutput::Format& format,
bool is_eos) -> void; bool is_eos) -> void;
private: private:
auto Main() -> void; auto Main() -> void;
auto SetTargetFormat(const IAudioSink::Format& format) -> void; auto SetTargetFormat(const IAudioOutput::Format& format) -> void;
auto HandleSamples(cpp::span<sample::Sample>, bool) -> size_t; auto HandleSamples(cpp::span<sample::Sample>, bool) -> size_t;
struct Args { struct Args {
IAudioSink::Format format; IAudioOutput::Format format;
size_t samples_available; size_t samples_available;
bool is_end_of_stream; bool is_end_of_stream;
}; };
@ -52,9 +53,9 @@ class SinkMixer {
cpp::span<sample::Sample> resampled_buffer_; cpp::span<sample::Sample> resampled_buffer_;
IAudioSink* sink_; std::shared_ptr<IAudioOutput> sink_;
IAudioSink::Format source_format_; IAudioOutput::Format source_format_;
IAudioSink::Format target_format_; IAudioOutput::Format target_format_;
size_t leftover_bytes_; size_t leftover_bytes_;
size_t leftover_offset_; size_t leftover_offset_;
}; };

@ -10,6 +10,7 @@
#include <cmath> #include <cmath>
#include <cstdint> #include <cstdint>
#include "audio_sink.hpp"
#include "esp_heap_caps.h" #include "esp_heap_caps.h"
#include "esp_log.h" #include "esp_log.h"
#include "freertos/portmacro.h" #include "freertos/portmacro.h"
@ -27,13 +28,12 @@ static constexpr std::size_t kSampleBufferLength = 240 * 2;
namespace audio { namespace audio {
SinkMixer::SinkMixer(IAudioSink* sink) SinkMixer::SinkMixer()
: commands_(xQueueCreate(1, sizeof(Args))), : commands_(xQueueCreate(1, sizeof(Args))),
resampler_(nullptr), resampler_(nullptr),
source_(xStreamBufferCreateWithCaps(kSourceBufferLength, source_(xStreamBufferCreateWithCaps(kSourceBufferLength,
1, 1,
MALLOC_CAP_SPIRAM)), MALLOC_CAP_SPIRAM)) {
sink_(sink) {
input_buffer_ = { input_buffer_ = {
reinterpret_cast<sample::Sample*>(heap_caps_calloc( reinterpret_cast<sample::Sample*>(heap_caps_calloc(
kSampleBufferLength, sizeof(sample::Sample), MALLOC_CAP_SPIRAM)), kSampleBufferLength, sizeof(sample::Sample), MALLOC_CAP_SPIRAM)),
@ -54,8 +54,15 @@ SinkMixer::~SinkMixer() {
vStreamBufferDelete(source_); vStreamBufferDelete(source_);
} }
auto SinkMixer::SetOutput(std::shared_ptr<IAudioOutput> output) -> void {
// FIXME: We should add synchronisation here, but we should be careful about
// not impacting performance given that the output will change only very
// rarely (if ever).
sink_ = output;
}
auto SinkMixer::MixAndSend(cpp::span<sample::Sample> input, auto SinkMixer::MixAndSend(cpp::span<sample::Sample> input,
const IAudioSink::Format& format, const IAudioOutput::Format& format,
bool is_eos) -> void { bool is_eos) -> void {
Args args{ Args args{
.format = format, .format = format,

Loading…
Cancel
Save