1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
|
#include "i2s_audio_output.hpp"
#include <algorithm>
#include "esp_err.h"
#include "freertos/portmacro.h"
#include "audio_element.hpp"
#include "dac.hpp"
#include "gpio_expander.hpp"
#include "result.hpp"
static const TickType_t kIdleTimeBeforeMute = pdMS_TO_TICKS(1000);
static const char* kTag = "I2SOUT";
namespace audio {
auto I2SAudioOutput::create(drivers::GpioExpander* expander)
-> cpp::result<std::shared_ptr<I2SAudioOutput>, Error> {
// First, we need to perform initial configuration of the DAC chip.
auto dac_result = drivers::AudioDac::create(expander);
if (dac_result.has_error()) {
ESP_LOGE(kTag, "failed to init dac: %d", dac_result.error());
return cpp::fail(DAC_CONFIG);
}
std::unique_ptr<drivers::AudioDac> dac = std::move(dac_result.value());
// Soft mute immediately, in order to minimise any clicks and pops caused by
// the initial output element and pipeline configuration.
// dac->WriteVolume(255);
dac->WriteVolume(120); // for testing
return std::make_shared<I2SAudioOutput>(expander, std::move(dac));
}
I2SAudioOutput::I2SAudioOutput(drivers::GpioExpander* expander,
std::unique_ptr<drivers::AudioDac> dac)
: expander_(expander),
dac_(std::move(dac)),
volume_(255),
is_soft_muted_(false) {}
I2SAudioOutput::~I2SAudioOutput() {
// TODO: power down the DAC.
}
auto I2SAudioOutput::ProcessStreamInfo(const StreamInfo& info)
-> cpp::result<void, AudioProcessingError> {
// TODO(jacqueline): probs do something with the channel hey
if (!info.BitsPerSample() && !info.SampleRate()) {
return cpp::fail(UNSUPPORTED_STREAM);
}
drivers::AudioDac::BitsPerSample bps;
switch (*info.BitsPerSample()) {
case 16:
bps = drivers::AudioDac::BPS_16;
break;
case 24:
bps = drivers::AudioDac::BPS_24;
break;
case 32:
bps = drivers::AudioDac::BPS_32;
break;
default:
return cpp::fail(UNSUPPORTED_STREAM);
}
drivers::AudioDac::SampleRate sample_rate;
switch (*info.SampleRate()) {
case 44100:
sample_rate = drivers::AudioDac::SAMPLE_RATE_44_1;
break;
case 48000:
sample_rate = drivers::AudioDac::SAMPLE_RATE_48;
break;
default:
return cpp::fail(UNSUPPORTED_STREAM);
}
dac_->Reconfigure(bps, sample_rate);
return {};
}
auto I2SAudioOutput::ProcessChunk(const cpp::span<std::byte>& chunk)
-> cpp::result<std::size_t, AudioProcessingError> {
SetSoftMute(false);
// TODO(jacqueline): write smaller parts with a small delay so that we can
// be responsive to pause and seek commands.
return dac_->WriteData(chunk, portMAX_DELAY);
}
auto I2SAudioOutput::IdleTimeout() const -> TickType_t {
return kIdleTimeBeforeMute;
}
auto I2SAudioOutput::ProcessIdle() -> cpp::result<void, AudioProcessingError> {
// TODO(jacqueline): Consider powering down the dac completely maybe?
SetSoftMute(true);
return {};
}
auto I2SAudioOutput::PrepareForPause() -> void {
// TODO(jacqueline): We ideally want to ensure we have enough samples in the
// DMA buffer here, so that soft mute can work properly.
SetSoftMute(true);
}
auto I2SAudioOutput::SetVolume(uint8_t volume) -> void {
volume_ = volume;
if (!is_soft_muted_) {
dac_->WriteVolume(volume);
}
}
auto I2SAudioOutput::SetSoftMute(bool enabled) -> void {
if (enabled == is_soft_muted_) {
return;
}
is_soft_muted_ = enabled;
if (is_soft_muted_) {
dac_->WriteVolume(255);
} else {
dac_->WriteVolume(volume_);
}
}
} // namespace audio
|