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 <variant>
#include "audio_sink.hpp"
#include "esp_log.h"
#include "freertos/portmacro.h"
#include "freertos/projdefs.h"
@ -23,6 +24,7 @@
#include "future_fetcher.hpp"
#include "i2s_audio_output.hpp"
#include "i2s_dac.hpp"
#include "sink_mixer.hpp"
#include "system_events.hpp"
#include "track.hpp"
#include "track_queue.hpp"
@ -36,9 +38,10 @@ std::shared_ptr<drivers::I2SDac> AudioState::sDac;
std::weak_ptr<database::Database> AudioState::sDatabase;
std::unique_ptr<AudioTask> AudioState::sTask;
std::unique_ptr<FatfsAudioInput> AudioState::sFileSource;
std::unique_ptr<I2SAudioOutput> AudioState::sI2SOutput;
std::unique_ptr<BluetoothAudioOutput> AudioState::sBtOutput;
std::shared_ptr<FatfsAudioInput> AudioState::sFileSource;
std::shared_ptr<SinkMixer> AudioState::sMixer;
std::shared_ptr<IAudioOutput> AudioState::sOutput;
TrackQueue* AudioState::sTrackQueue;
std::optional<database::TrackId> AudioState::sCurrentTrack;
@ -59,11 +62,13 @@ auto AudioState::Init(drivers::IGpios* gpio_expander,
sDatabase = database;
sFileSource.reset(new FatfsAudioInput(tag_parser));
sI2SOutput.reset(new I2SAudioOutput(sIGpios, sDac));
sBtOutput.reset(new BluetoothAudioOutput(bluetooth));
sOutput.reset(new I2SAudioOutput(sIGpios, sDac));
// sOutput.reset(new BluetoothAudioOutput(bluetooth));
sMixer.reset(new SinkMixer());
sMixer->SetOutput(sOutput);
AudioTask::Start(sFileSource.get(), sI2SOutput.get());
// AudioTask::Start(sFileSource.get(), sBtOutput.get());
AudioTask::Start(sFileSource, sMixer);
return true;
}
@ -73,14 +78,14 @@ void AudioState::react(const system_fsm::StorageMounted& ev) {
}
void AudioState::react(const system_fsm::KeyUpChanged& ev) {
if (ev.falling && sI2SOutput->AdjustVolumeUp()) {
if (ev.falling && sOutput->AdjustVolumeUp()) {
ESP_LOGI(kTag, "volume up!");
events::Ui().Dispatch(VolumeChanged{});
}
}
void AudioState::react(const system_fsm::KeyDownChanged& ev) {
if (ev.falling && sI2SOutput->AdjustVolumeDown()) {
if (ev.falling && sOutput->AdjustVolumeDown()) {
ESP_LOGI(kTag, "volume down!");
events::Ui().Dispatch(VolumeChanged{});
}
@ -131,8 +136,7 @@ void Standby::react(const QueueUpdate& ev) {
void Playback::entry() {
ESP_LOGI(kTag, "beginning playback");
sI2SOutput->SetInUse(true);
// sBtOutput->SetInUse(true);
sOutput->SetInUse(true);
}
void Playback::exit() {
@ -140,8 +144,7 @@ void Playback::exit() {
// TODO(jacqueline): Second case where it's useful to wait for the i2s buffer
// to drain.
vTaskDelay(pdMS_TO_TICKS(250));
sI2SOutput->SetInUse(false);
// sBtOutput->SetInUse(false);
sOutput->SetInUse(false);
}
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);
tasks::StartPersistent<tasks::Type::kAudio>([=]() { task->Main(); });
return task;
}
AudioTask::AudioTask(IAudioSource* source, IAudioSink* sink)
: source_(source),
sink_(sink),
codec_(),
mixer_(new SinkMixer(sink)),
timer_(),
current_format_() {
AudioTask::AudioTask(std::shared_ptr<IAudioSource> source,
std::shared_ptr<SinkMixer> mixer)
: source_(source), mixer_(mixer), codec_(), timer_(), current_format_() {
codec_buffer_ = {
reinterpret_cast<sample::Sample*>(heap_caps_calloc(
kCodecBufferLength, sizeof(sample::Sample), MALLOC_CAP_SPIRAM)),
@ -133,7 +130,7 @@ auto AudioTask::BeginDecoding(std::shared_ptr<codecs::IStream> stream) -> bool {
timer_.reset();
}
current_sink_format_ = IAudioSink::Format{
current_sink_format_ = IAudioOutput::Format{
.sample_rate = open_res->sample_rate_hz,
.num_channels = open_res->num_channels,
.bits_per_sample = 16,

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

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

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

@ -15,17 +15,28 @@
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:
StreamBufferHandle_t stream_;
public:
IAudioSink(size_t buffer_size, uint32_t caps)
IAudioOutput(size_t buffer_size, uint32_t 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 SetVolume(uint_fast8_t percent) -> void = 0;

@ -34,7 +34,8 @@ class Timer {
class AudioTask {
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;
@ -42,21 +43,21 @@ class AudioTask {
AudioTask& operator=(const AudioTask&) = delete;
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 ContinueDecoding() -> bool;
IAudioSource* source_;
IAudioSink* sink_;
std::shared_ptr<IAudioSource> source_;
std::shared_ptr<SinkMixer> mixer_;
std::shared_ptr<codecs::IStream> stream_;
std::unique_ptr<codecs::ICodec> codec_;
std::unique_ptr<SinkMixer> mixer_;
std::unique_ptr<Timer> timer_;
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_;
};

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

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

@ -9,36 +9,37 @@
#include <cstdint>
#include <memory>
#include "resample.hpp"
#include "sample.hpp"
#include "audio_sink.hpp"
#include "audio_source.hpp"
#include "codec.hpp"
#include "resample.hpp"
#include "sample.hpp"
namespace 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 {
public:
SinkMixer(IAudioSink* sink);
SinkMixer();
~SinkMixer();
auto SetOutput(std::shared_ptr<IAudioOutput>) -> void;
auto MixAndSend(cpp::span<sample::Sample>,
const IAudioSink::Format& format,
const IAudioOutput::Format& format,
bool is_eos) -> void;
private:
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;
struct Args {
IAudioSink::Format format;
IAudioOutput::Format format;
size_t samples_available;
bool is_end_of_stream;
};
@ -52,9 +53,9 @@ class SinkMixer {
cpp::span<sample::Sample> resampled_buffer_;
IAudioSink* sink_;
IAudioSink::Format source_format_;
IAudioSink::Format target_format_;
std::shared_ptr<IAudioOutput> sink_;
IAudioOutput::Format source_format_;
IAudioOutput::Format target_format_;
size_t leftover_bytes_;
size_t leftover_offset_;
};

@ -10,6 +10,7 @@
#include <cmath>
#include <cstdint>
#include "audio_sink.hpp"
#include "esp_heap_caps.h"
#include "esp_log.h"
#include "freertos/portmacro.h"
@ -27,13 +28,12 @@ static constexpr std::size_t kSampleBufferLength = 240 * 2;
namespace audio {
SinkMixer::SinkMixer(IAudioSink* sink)
SinkMixer::SinkMixer()
: commands_(xQueueCreate(1, sizeof(Args))),
resampler_(nullptr),
source_(xStreamBufferCreateWithCaps(kSourceBufferLength,
1,
MALLOC_CAP_SPIRAM)),
sink_(sink) {
MALLOC_CAP_SPIRAM)) {
input_buffer_ = {
reinterpret_cast<sample::Sample*>(heap_caps_calloc(
kSampleBufferLength, sizeof(sample::Sample), MALLOC_CAP_SPIRAM)),
@ -54,8 +54,15 @@ SinkMixer::~SinkMixer() {
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,
const IAudioSink::Format& format,
const IAudioOutput::Format& format,
bool is_eos) -> void {
Args args{
.format = format,

Loading…
Cancel
Save