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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
|
#include "audio_decoder.hpp"
#include <string.h>
#include <cstddef>
#include <cstdint>
#include <memory>
#include "freertos/FreeRTOS.h"
#include "esp_heap_caps.h"
#include "freertos/message_buffer.h"
#include "freertos/portmacro.h"
#include "audio_element.hpp"
#include "chunk.hpp"
#include "fatfs_audio_input.hpp"
#include "stream_info.hpp"
namespace audio {
static const char* kTag = "DEC";
static const std::size_t kSamplesPerChunk = 1024;
AudioDecoder::AudioDecoder()
: IAudioElement(),
stream_info_({}),
has_samples_to_send_(false),
needs_more_input_(true) {}
AudioDecoder::~AudioDecoder() {}
auto AudioDecoder::HasUnprocessedInput() -> bool {
return !needs_more_input_ || has_samples_to_send_;
}
auto AudioDecoder::ProcessStreamInfo(const StreamInfo& info)
-> cpp::result<void, AudioProcessingError> {
stream_info_ = info;
if (info.chunk_size) {
chunk_reader_.emplace(info.chunk_size.value());
} else {
ESP_LOGE(kTag, "no chunk size given");
return cpp::fail(UNSUPPORTED_STREAM);
}
// Reuse the existing codec if we can. This will help with gapless playback,
// since we can potentially just continue to decode as we were before,
// without any setup overhead.
if (current_codec_ != nullptr &&
current_codec_->CanHandleFile(info.path.value_or(""))) {
current_codec_->ResetForNewStream();
return {};
}
auto result = codecs::CreateCodecForFile(info.path.value_or(""));
if (result.has_value()) {
current_codec_ = std::move(result.value());
} else {
ESP_LOGE(kTag, "no codec for this file");
return cpp::fail(UNSUPPORTED_STREAM);
}
stream_info_ = info;
has_sent_stream_info_ = false;
return {};
}
auto AudioDecoder::ProcessChunk(const cpp::span<std::byte>& chunk)
-> cpp::result<size_t, AudioProcessingError> {
if (current_codec_ == nullptr || !chunk_reader_) {
// Should never happen, but fail explicitly anyway.
ESP_LOGW(kTag, "received chunk without chunk size or codec");
return cpp::fail(UNSUPPORTED_STREAM);
}
ESP_LOGI(kTag, "received new chunk (size %u)", chunk.size());
current_codec_->SetInput(chunk_reader_->HandleNewData(chunk));
needs_more_input_ = false;
return {};
}
auto AudioDecoder::ProcessEndOfStream() -> void {
has_samples_to_send_ = false;
needs_more_input_ = true;
current_codec_.reset();
SendOrBufferEvent(std::unique_ptr<StreamEvent>(
StreamEvent::CreateEndOfStream(input_events_)));
}
auto AudioDecoder::Process() -> cpp::result<void, AudioProcessingError> {
if (has_samples_to_send_) {
// Writing samples is relatively quick (it's just a bunch of memcopy's), so
// do them all at once.
while (has_samples_to_send_ && !IsOverBuffered()) {
if (!has_sent_stream_info_) {
has_sent_stream_info_ = true;
auto format = current_codec_->GetOutputFormat();
stream_info_->bits_per_sample = format.bits_per_sample;
stream_info_->sample_rate = format.sample_rate_hz;
stream_info_->channels = format.num_channels;
chunk_size_ = kSamplesPerChunk * (*stream_info_->bits_per_sample);
stream_info_->chunk_size = chunk_size_;
ESP_LOGI(kTag, "pcm stream chunk size: %u bytes", chunk_size_);
auto event =
StreamEvent::CreateStreamInfo(input_events_, *stream_info_);
SendOrBufferEvent(std::unique_ptr<StreamEvent>(event));
}
auto chunk = std::unique_ptr<StreamEvent>(
StreamEvent::CreateChunkData(input_events_, chunk_size_));
auto write_res =
current_codec_->WriteOutputSamples(chunk->chunk_data.bytes);
chunk->chunk_data.bytes = chunk->chunk_data.bytes.first(write_res.first);
has_samples_to_send_ = !write_res.second;
if (!SendOrBufferEvent(std::move(chunk))) {
return {};
}
}
// We will process the next frame during the next call to this method.
return {};
}
if (!needs_more_input_) {
auto res = current_codec_->ProcessNextFrame();
if (res.has_error()) {
// todo
return {};
}
needs_more_input_ = res.value();
has_samples_to_send_ = true;
if (needs_more_input_) {
chunk_reader_->HandleBytesUsed(current_codec_->GetInputPosition());
}
}
return {};
}
} // namespace audio
|