|
|
@ -28,7 +28,7 @@ |
|
|
|
[[maybe_unused]] static constexpr char kTag[] = "mixer"; |
|
|
|
[[maybe_unused]] static constexpr char kTag[] = "mixer"; |
|
|
|
|
|
|
|
|
|
|
|
static constexpr std::size_t kSampleBufferLength = |
|
|
|
static constexpr std::size_t kSampleBufferLength = |
|
|
|
drivers::kI2SBufferLengthFrames * sizeof(sample::Sample); |
|
|
|
drivers::kI2SBufferLengthFrames * sizeof(sample::Sample) * 2; |
|
|
|
static constexpr std::size_t kSourceBufferLength = kSampleBufferLength * 2; |
|
|
|
static constexpr std::size_t kSourceBufferLength = kSampleBufferLength * 2; |
|
|
|
|
|
|
|
|
|
|
|
namespace audio { |
|
|
|
namespace audio { |
|
|
@ -68,24 +68,32 @@ auto SampleConverter::SetOutput(std::shared_ptr<IAudioOutput> output) -> void { |
|
|
|
sink_ = output; |
|
|
|
sink_ = output; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
auto SampleConverter::ConvertSamples(cpp::span<sample::Sample> input, |
|
|
|
auto SampleConverter::beginStream(std::shared_ptr<TrackInfo> track) -> void { |
|
|
|
const IAudioOutput::Format& format, |
|
|
|
|
|
|
|
bool is_eos) -> void { |
|
|
|
|
|
|
|
Args args{ |
|
|
|
Args args{ |
|
|
|
.format = format, |
|
|
|
.track = new std::shared_ptr<TrackInfo>(track), |
|
|
|
|
|
|
|
.samples_available = 0, |
|
|
|
|
|
|
|
.is_end_of_stream = false, |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
xQueueSend(commands_, &args, portMAX_DELAY); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
auto SampleConverter::continueStream(cpp::span<sample::Sample> input) -> void { |
|
|
|
|
|
|
|
Args args{ |
|
|
|
|
|
|
|
.track = nullptr, |
|
|
|
.samples_available = input.size(), |
|
|
|
.samples_available = input.size(), |
|
|
|
.is_end_of_stream = is_eos, |
|
|
|
.is_end_of_stream = false, |
|
|
|
}; |
|
|
|
}; |
|
|
|
xQueueSend(commands_, &args, portMAX_DELAY); |
|
|
|
xQueueSend(commands_, &args, portMAX_DELAY); |
|
|
|
|
|
|
|
xStreamBufferSend(source_, input.data(), input.size_bytes(), portMAX_DELAY); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
cpp::span<std::byte> input_as_bytes = { |
|
|
|
auto SampleConverter::endStream() -> void { |
|
|
|
reinterpret_cast<std::byte*>(input.data()), input.size_bytes()}; |
|
|
|
Args args{ |
|
|
|
size_t bytes_sent = 0; |
|
|
|
.track = nullptr, |
|
|
|
while (bytes_sent < input_as_bytes.size()) { |
|
|
|
.samples_available = 0, |
|
|
|
bytes_sent += xStreamBufferSend( |
|
|
|
.is_end_of_stream = true, |
|
|
|
source_, input_as_bytes.subspan(bytes_sent).data(), |
|
|
|
}; |
|
|
|
input_as_bytes.size() - bytes_sent, pdMS_TO_TICKS(100)); |
|
|
|
xQueueSend(commands_, &args, portMAX_DELAY); |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
auto SampleConverter::Main() -> void { |
|
|
|
auto SampleConverter::Main() -> void { |
|
|
@ -93,12 +101,28 @@ auto SampleConverter::Main() -> void { |
|
|
|
Args args; |
|
|
|
Args args; |
|
|
|
while (!xQueueReceive(commands_, &args, portMAX_DELAY)) { |
|
|
|
while (!xQueueReceive(commands_, &args, portMAX_DELAY)) { |
|
|
|
} |
|
|
|
} |
|
|
|
if (args.format != source_format_) { |
|
|
|
|
|
|
|
|
|
|
|
if (args.track) { |
|
|
|
|
|
|
|
handleBeginStream(*args.track); |
|
|
|
|
|
|
|
delete args.track; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (args.samples_available) { |
|
|
|
|
|
|
|
handleContinueStream(args.samples_available); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (args.is_end_of_stream) { |
|
|
|
|
|
|
|
handleEndStream(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
auto SampleConverter::handleBeginStream(std::shared_ptr<TrackInfo> track) |
|
|
|
|
|
|
|
-> void { |
|
|
|
|
|
|
|
if (track->format != source_format_) { |
|
|
|
resampler_.reset(); |
|
|
|
resampler_.reset(); |
|
|
|
source_format_ = args.format; |
|
|
|
source_format_ = track->format; |
|
|
|
leftover_bytes_ = 0; |
|
|
|
leftover_bytes_ = 0; |
|
|
|
|
|
|
|
|
|
|
|
auto new_target = sink_->PrepareFormat(args.format); |
|
|
|
auto new_target = sink_->PrepareFormat(track->format); |
|
|
|
if (new_target != target_format_) { |
|
|
|
if (new_target != target_format_) { |
|
|
|
// The new format is different to the old one. Wait for the sink to
|
|
|
|
// The new format is different to the old one. Wait for the sink to
|
|
|
|
// drain before continuing.
|
|
|
|
// drain before continuing.
|
|
|
@ -112,26 +136,22 @@ auto SampleConverter::Main() -> void { |
|
|
|
sink_->Configure(new_target); |
|
|
|
sink_->Configure(new_target); |
|
|
|
} |
|
|
|
} |
|
|
|
target_format_ = new_target; |
|
|
|
target_format_ = new_target; |
|
|
|
|
|
|
|
|
|
|
|
// Send a final sample count for the previous sample rate.
|
|
|
|
|
|
|
|
if (samples_sunk_ > 0) { |
|
|
|
|
|
|
|
events::Audio().Dispatch(internal::ConverterProgress{ |
|
|
|
|
|
|
|
.samples_sunk = samples_sunk_, |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
samples_sunk_ = 0; |
|
|
|
samples_sunk_ = 0; |
|
|
|
events::Audio().Dispatch(internal::ConverterConfigurationChanged{ |
|
|
|
events::Audio().Dispatch(internal::StreamStarted{ |
|
|
|
|
|
|
|
.track = track, |
|
|
|
.src_format = source_format_, |
|
|
|
.src_format = source_format_, |
|
|
|
.dst_format = target_format_, |
|
|
|
.dst_format = target_format_, |
|
|
|
}); |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
auto SampleConverter::handleContinueStream(size_t samples_available) -> void { |
|
|
|
// Loop until we finish reading all the bytes indicated. There might be
|
|
|
|
// Loop until we finish reading all the bytes indicated. There might be
|
|
|
|
// leftovers from each iteration, and from this process as a whole,
|
|
|
|
// leftovers from each iteration, and from this process as a whole,
|
|
|
|
// depending on the resampling stage.
|
|
|
|
// depending on the resampling stage.
|
|
|
|
size_t bytes_read = 0; |
|
|
|
size_t bytes_read = 0; |
|
|
|
size_t bytes_to_read = args.samples_available * sizeof(sample::Sample); |
|
|
|
size_t bytes_to_read = samples_available * sizeof(sample::Sample); |
|
|
|
while (bytes_read < bytes_to_read) { |
|
|
|
while (bytes_read < bytes_to_read) { |
|
|
|
// First top up the input buffer, taking care not to overwrite anything
|
|
|
|
// First top up the input buffer, taking care not to overwrite anything
|
|
|
|
// remaining from a previous iteration.
|
|
|
|
// remaining from a previous iteration.
|
|
|
@ -146,9 +166,7 @@ auto SampleConverter::Main() -> void { |
|
|
|
size_t bytes_in_buffer = bytes_read_this_it + leftover_bytes_; |
|
|
|
size_t bytes_in_buffer = bytes_read_this_it + leftover_bytes_; |
|
|
|
size_t samples_in_buffer = bytes_in_buffer / sizeof(sample::Sample); |
|
|
|
size_t samples_in_buffer = bytes_in_buffer / sizeof(sample::Sample); |
|
|
|
|
|
|
|
|
|
|
|
size_t samples_used = |
|
|
|
size_t samples_used = handleSamples(input_buffer_.first(samples_in_buffer)); |
|
|
|
HandleSamples(input_buffer_.first(samples_in_buffer), |
|
|
|
|
|
|
|
args.is_end_of_stream && bytes_read == bytes_to_read); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Maybe the resampler didn't consume everything. Maybe the last few
|
|
|
|
// Maybe the resampler didn't consume everything. Maybe the last few
|
|
|
|
// bytes we read were half a frame. Either way, we need to calculate the
|
|
|
|
// bytes we read were half a frame. Either way, we need to calculate the
|
|
|
@ -160,19 +178,16 @@ auto SampleConverter::Main() -> void { |
|
|
|
leftover_bytes_ = bytes_in_buffer - bytes_used; |
|
|
|
leftover_bytes_ = bytes_in_buffer - bytes_used; |
|
|
|
if (leftover_bytes_ > 0) { |
|
|
|
if (leftover_bytes_ > 0) { |
|
|
|
std::memmove(input_buffer_as_bytes_.data(), |
|
|
|
std::memmove(input_buffer_as_bytes_.data(), |
|
|
|
input_buffer_as_bytes_.data() + bytes_used, |
|
|
|
input_buffer_as_bytes_.data() + bytes_used, leftover_bytes_); |
|
|
|
leftover_bytes_); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
auto SampleConverter::HandleSamples(cpp::span<sample::Sample> input, |
|
|
|
auto SampleConverter::handleSamples(cpp::span<sample::Sample> input) -> size_t { |
|
|
|
bool is_eos) -> size_t { |
|
|
|
|
|
|
|
if (source_format_ == target_format_) { |
|
|
|
if (source_format_ == target_format_) { |
|
|
|
// The happiest possible case: the input format matches the output
|
|
|
|
// The happiest possible case: the input format matches the output
|
|
|
|
// format already.
|
|
|
|
// format already.
|
|
|
|
SendToSink(input); |
|
|
|
sendToSink(input); |
|
|
|
return input.size(); |
|
|
|
return input.size(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -190,7 +205,7 @@ auto SampleConverter::HandleSamples(cpp::span<sample::Sample> input, |
|
|
|
|
|
|
|
|
|
|
|
size_t read, written; |
|
|
|
size_t read, written; |
|
|
|
std::tie(read, written) = resampler_->Process(input.subspan(samples_used), |
|
|
|
std::tie(read, written) = resampler_->Process(input.subspan(samples_used), |
|
|
|
resampled_buffer_, is_eos); |
|
|
|
resampled_buffer_, false); |
|
|
|
samples_used += read; |
|
|
|
samples_used += read; |
|
|
|
|
|
|
|
|
|
|
|
if (read == 0 && written == 0) { |
|
|
|
if (read == 0 && written == 0) { |
|
|
@ -203,18 +218,40 @@ auto SampleConverter::HandleSamples(cpp::span<sample::Sample> input, |
|
|
|
samples_used = input.size(); |
|
|
|
samples_used = input.size(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
SendToSink(output_source); |
|
|
|
sendToSink(output_source); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return samples_used; |
|
|
|
return samples_used; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
auto SampleConverter::SendToSink(cpp::span<sample::Sample> samples) -> void { |
|
|
|
auto SampleConverter::handleEndStream() -> void { |
|
|
|
|
|
|
|
if (resampler_) { |
|
|
|
|
|
|
|
size_t read, written; |
|
|
|
|
|
|
|
std::tie(read, written) = resampler_->Process({}, resampled_buffer_, true); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (written > 0) { |
|
|
|
|
|
|
|
sendToSink(resampled_buffer_.first(written)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Send a final update to finish off this stream's samples.
|
|
|
|
|
|
|
|
if (samples_sunk_ > 0) { |
|
|
|
|
|
|
|
events::Audio().Dispatch(internal::StreamUpdate{ |
|
|
|
|
|
|
|
.samples_sunk = samples_sunk_, |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
samples_sunk_ = 0; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
events::Audio().Dispatch(internal::StreamEnded{}); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
auto SampleConverter::sendToSink(cpp::span<sample::Sample> samples) -> void { |
|
|
|
// Update the number of samples sunk so far *before* actually sinking them,
|
|
|
|
// Update the number of samples sunk so far *before* actually sinking them,
|
|
|
|
// since writing to the stream buffer will block when the buffer gets full.
|
|
|
|
// since writing to the stream buffer will block when the buffer gets full.
|
|
|
|
samples_sunk_ += samples.size(); |
|
|
|
samples_sunk_ += samples.size(); |
|
|
|
if (samples_sunk_ >= |
|
|
|
if (samples_sunk_ >= |
|
|
|
target_format_.sample_rate * target_format_.num_channels) { |
|
|
|
target_format_.sample_rate * target_format_.num_channels) { |
|
|
|
events::Audio().Dispatch(internal::ConverterProgress{ |
|
|
|
events::Audio().Dispatch(internal::StreamUpdate{ |
|
|
|
.samples_sunk = samples_sunk_, |
|
|
|
.samples_sunk = samples_sunk_, |
|
|
|
}); |
|
|
|
}); |
|
|
|
samples_sunk_ = 0; |
|
|
|
samples_sunk_ = 0; |
|
|
|