Put more audio pipeline wiring in control of the audio fsm
This commit is contained in:
+16
-13
@@ -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));
|
||||
|
||||
AudioTask::Start(sFileSource.get(), sI2SOutput.get());
|
||||
// AudioTask::Start(sFileSource.get(), sBtOutput.get());
|
||||
sMixer.reset(new SinkMixer());
|
||||
sMixer->SetOutput(sOutput);
|
||||
|
||||
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,
|
||||
|
||||
Reference in New Issue
Block a user