blob: 5d14954e8e8db6bf4fc256c20daa1f0ead3ab464 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/media/audio/audio_core/tap_stage.h"
#include <lib/syslog/cpp/macros.h>
#include <lib/trace/event.h>
namespace media::audio {
TapStage::TapStage(std::shared_ptr<ReadableStream> source, std::shared_ptr<WritableStream> tap)
: ReadableStream(source->format()),
source_(std::move(source)),
tap_(std::move(tap)),
output_producer_(OutputProducer::Select(tap_->format().stream_type())) {
FX_CHECK(source_->format() == tap_->format());
FX_CHECK(source_->reference_clock() == tap_->reference_clock());
}
std::optional<ReadableStream::Buffer> TapStage::ReadLock(Fixed dest_frame, size_t frame_count) {
TRACE_DURATION("audio", "TapStage::ReadLock", "frame", dest_frame.Floor(), "length", frame_count);
// The source and tap may have different frame timelines.
auto source_frac_frame_to_tap_frac_frame = SourceFracFrameToTapFracFrame();
// The source and destinations, however, share the same frame timelines, therefore we have no need
// to translate these parameters.
auto source_buffer = source_->ReadLock(dest_frame, frame_count);
// We need to write some silence to the tap if some part of the frame range is not available in
// the source stream. If a single write buffer extends beyond the end of the silent region it
// is returned so that we can copy some frames into the remaining portion of the buffer.
std::optional<WritableStream::Buffer> write_buffer;
const int64_t silent_frames =
source_buffer ? (source_buffer->start().Floor() - dest_frame.Floor()) : frame_count;
if (silent_frames > 0) {
const int64_t first_tap_frame =
Fixed::FromRaw(source_frac_frame_to_tap_frac_frame.Apply(dest_frame.raw_value())).Floor();
write_buffer = WriteSilenceToTap(first_tap_frame, silent_frames);
}
// If we have a source buffer, we need to copy frames into the tap.
if (source_buffer) {
// This is the first frame we need to populate in the tap stream.
const Fixed first_tap_frame = Fixed::FromRaw(
source_frac_frame_to_tap_frac_frame.Apply(source_buffer->start().raw_value()));
// If we don't have a write buffer left over from writing silence, acquire one now. If we can't
// get a write buffer then we have nothing to do.
if (!write_buffer) {
write_buffer = tap_->WriteLock(first_tap_frame.Floor(), source_buffer->length().Floor());
}
if (write_buffer) {
CopyFrames(std::move(write_buffer), *source_buffer, source_frac_frame_to_tap_frac_frame);
}
}
return source_buffer;
}
std::optional<WritableStream::Buffer> TapStage::WriteSilenceToTap(int64_t frame,
int64_t frame_count) {
int64_t last_frame_exclusive = frame + frame_count;
while (frame_count > 0) {
auto tap_buffer = tap_->WriteLock(frame, frame_count);
if (!tap_buffer) {
break;
}
size_t silent_frames =
std::min(tap_buffer->end().Floor(), last_frame_exclusive) - tap_buffer->start().Floor();
output_producer_->FillWithSilence(tap_buffer->payload(), silent_frames);
if (tap_buffer->end() > last_frame_exclusive) {
return tap_buffer;
}
frame = tap_buffer->end().Floor();
frame_count = last_frame_exclusive - frame;
}
return std::nullopt;
}
void TapStage::CopyFrames(std::optional<WritableStream::Buffer> tap_buffer,
const ReadableStream::Buffer& source,
const TimelineFunction& source_frac_frame_to_tap_frac_frame) {
Fixed first_available_frame =
Fixed::FromRaw(source_frac_frame_to_tap_frac_frame.Apply(source.start().raw_value()));
Fixed last_available_frame =
Fixed::FromRaw(source_frac_frame_to_tap_frac_frame.Apply(source.end().raw_value()));
while (tap_buffer) {
// Compute the overlap between the read/write buffers.
Fixed first_tap_frame_to_copy = std::max(tap_buffer->start(), first_available_frame);
Fixed last_tap_frame_to_copy = tap_buffer->end();
FX_CHECK(last_tap_frame_to_copy > first_tap_frame_to_copy);
Fixed frames_to_copy = last_tap_frame_to_copy - first_tap_frame_to_copy;
size_t bytes_to_copy = frames_to_copy.Floor() * format().bytes_per_frame();
// We might have an offset into the source buffer.
ssize_t source_buffer_offset =
Fixed(first_tap_frame_to_copy - first_available_frame).Floor() * format().bytes_per_frame();
void* source_ptr = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(source.payload()) +
source_buffer_offset);
// We may also have an offset into the tap buffer if some part of this buffer was populated
// with silence above.
size_t tap_buffer_offset =
Fixed(first_tap_frame_to_copy - tap_buffer->start()).Floor() * format().bytes_per_frame();
void* tap_ptr = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(tap_buffer->payload()) +
tap_buffer_offset);
memmove(tap_ptr, source_ptr, bytes_to_copy);
// Get another buffer if needed.
int64_t frames_remaining = Fixed(last_available_frame - tap_buffer->end()).Floor();
if (frames_remaining <= 0) {
break;
}
tap_buffer = tap_->WriteLock(tap_buffer->end().Floor(), frames_remaining);
}
}
void TapStage::SetPresentationDelay(zx::duration external_delay) {
// The tap does not introduce extra delay.
ReadableStream::SetPresentationDelay(external_delay);
source_->SetPresentationDelay(external_delay);
}
const TimelineFunction& TapStage::SourceFracFrameToTapFracFrame() {
FX_DCHECK(source_->reference_clock() == tap_->reference_clock());
auto source_snapshot = source_->ref_time_to_frac_presentation_frame();
auto tap_snapshot = tap_->ref_time_to_frac_presentation_frame();
if (source_snapshot.generation != source_generation_ ||
tap_snapshot.generation != tap_generation_) {
source_frac_frame_to_tap_frac_frame_ =
tap_snapshot.timeline_function * source_snapshot.timeline_function.Inverse();
source_generation_ = source_snapshot.generation;
tap_generation_ = tap_snapshot.generation;
}
return source_frac_frame_to_tap_frac_frame_;
}
} // namespace media::audio