/* * Copyright (c) 2026-present, the Ladybird developers. * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include namespace Media { ErrorOr> AudioTimeStretchProcessor::try_create() { return adopt_nonnull_ref_or_enomem(new (nothrow) AudioTimeStretchProcessor); } AudioTimeStretchProcessor::AudioTimeStretchProcessor() = default; AudioTimeStretchProcessor::~AudioTimeStretchProcessor() { Sync::MutexLocker locker { m_mutex }; if (m_input != nullptr) m_input->set_wake_handler(nullptr); } ErrorOr AudioTimeStretchProcessor::connect_input(NonnullRefPtr const& input) { Sync::MutexLocker locker { m_mutex }; VERIFY(m_input == nullptr); m_input = input; input->set_wake_handler([this] { bool should_wake_downstream; { Sync::MutexLocker locker { m_mutex }; auto status = PipelineStatus::HaveData; if (m_pending_block.is_empty()) status = produce_block_while_locked(m_pending_block); if (!m_pending_block.is_empty()) status = PipelineStatus::HaveData; should_wake_downstream = m_downstream_needs_wake && resolves_seek(status); } if (should_wake_downstream) dispatch_wake(); }); if (m_sample_specification.is_valid()) { if (auto result = input->set_output_sample_specification(m_sample_specification); result.is_error()) { input->set_wake_handler(nullptr); m_input = nullptr; return result.release_error(); } if (m_started) input->start(); } return {}; } void AudioTimeStretchProcessor::disconnect_input(NonnullRefPtr const& input) { Sync::MutexLocker locker { m_mutex }; VERIFY(m_input == input); input->set_wake_handler(nullptr); m_input = nullptr; } void AudioTimeStretchProcessor::seek(AK::Duration timestamp) { RefPtr input; { Sync::MutexLocker locker { m_mutex }; VERIFY(m_sample_specification.is_valid()); auto sample_rate = m_sample_specification.sample_rate(); auto target_frame = timestamp.to_time_units(1, sample_rate); auto output_frame = target_frame; ensure_stretcher_while_locked(); auto prerolled_target_frame = max(target_frame - m_stretcher->preroll_frame_count(), 0); auto actual_preroll_delta = target_frame - prerolled_target_frame; target_frame = prerolled_target_frame; output_frame = output_frame - AK::round_to(static_cast(actual_preroll_delta) / m_playback_rate); m_next_emit_media_time = AK::Duration::from_time_units(target_frame, 1, sample_rate); m_next_output_frame = output_frame; m_stretcher->flush(m_next_emit_media_time, m_next_output_frame); m_moved_position_pending = true; m_pending_block.clear(); m_downstream_needs_wake = true; m_stretcher_reached_eos = false; input = m_input; timestamp = m_next_emit_media_time; } if (input != nullptr) { input->seek(timestamp); return; } dispatch_wake(); } ErrorOr AudioTimeStretchProcessor::set_output_sample_specification(Audio::SampleSpecification sample_specification) { Sync::MutexLocker locker { m_mutex }; if (m_sample_specification == sample_specification) return {}; m_sample_specification = sample_specification; m_stretcher = nullptr; m_pending_block.clear(); m_stretcher_reached_eos = false; if (m_input != nullptr) TRY(m_input->set_output_sample_specification(sample_specification)); return {}; } void AudioTimeStretchProcessor::start() { Sync::MutexLocker locker { m_mutex }; m_started = true; if (m_input != nullptr) m_input->start(); } void AudioTimeStretchProcessor::set_wake_handler(PipelineWakeHandler handler) { m_wake_handler = move(handler); } void AudioTimeStretchProcessor::dispatch_wake() { { Sync::MutexLocker locker { m_mutex }; m_downstream_needs_wake = false; } if (m_wake_handler) m_wake_handler(); } void AudioTimeStretchProcessor::set_playback_rate(float rate) { VERIFY(isfinite(rate)); VERIFY(rate > 0.0f); bool should_wake_downstream = false; { Sync::MutexLocker locker { m_mutex }; if (m_playback_rate == rate) return; m_playback_rate = rate; should_wake_downstream = m_downstream_needs_wake; } if (should_wake_downstream) dispatch_wake(); } void AudioTimeStretchProcessor::ensure_stretcher_while_locked() const { if (m_stretcher) { m_stretcher->set_rate(m_playback_rate); return; } VERIFY(m_sample_specification.is_valid()); m_stretcher = MUST(Audio::WSOLATimeStretcher::create(m_sample_specification)); m_stretcher->set_rate(m_playback_rate); m_stretcher->flush(m_next_emit_media_time, m_next_output_frame); } void AudioTimeStretchProcessor::maybe_recover_from_stale_upstream_eos_while_locked() const { if (!m_stretcher_reached_eos) return; auto status = m_input->status(); while (status == PipelineStatus::MovedPosition) { m_input->pull(m_input_block); VERIFY(m_input_block.is_empty()); status = m_input->status(); } if (is_terminal(status)) return; m_stretcher->flush(m_next_emit_media_time, m_next_output_frame); m_input_block.clear(); m_stretcher_reached_eos = false; } PipelineStatus AudioTimeStretchProcessor::produce_block_while_locked(AudioBlock& into) const { if (m_input == nullptr || !m_sample_specification.is_valid()) return PipelineStatus::Pending; VERIFY(m_playback_rate != 0.0f); auto pull_input = [&](AudioBlock& input_block) -> PipelineStatus { auto status = m_input->status(); while (status == PipelineStatus::MovedPosition) { m_input->pull(input_block); VERIFY(input_block.is_empty()); status = m_input->status(); } if (status == PipelineStatus::HaveData) m_input->pull(input_block); else input_block.clear(); return status; }; ensure_stretcher_while_locked(); maybe_recover_from_stale_upstream_eos_while_locked(); while (true) { auto result = m_stretcher->retrieve_block(); if (!result.is_error()) { into = result.release_value(); m_next_output_frame = into.end_frame_index(); m_next_emit_media_time = into.media_time_end(); return PipelineStatus::HaveData; } if (result.error().category() == DecoderErrorCategory::EndOfStream) { into.clear(); m_stretcher_reached_eos = true; return PipelineStatus::EndOfStream; } if (result.error().category() != DecoderErrorCategory::NeedsMoreInput) { into.clear(); return PipelineStatus::Error; } auto status = pull_input(m_input_block); if (status == PipelineStatus::EndOfStream) { VERIFY(m_input_block.is_empty()); m_stretcher->signal_end_of_stream(); m_stretcher_reached_eos = false; continue; } if (m_input_block.is_empty()) { into.clear(); return status; } VERIFY(status == PipelineStatus::HaveData); VERIFY(m_input_block.sample_specification() == m_sample_specification); m_stretcher->push_block(m_input_block); } } PipelineStatus AudioTimeStretchProcessor::status() const { Sync::MutexLocker locker { m_mutex }; auto status = PipelineStatus::HaveData; if (m_pending_block.is_empty()) status = produce_block_while_locked(m_pending_block); if (!m_pending_block.is_empty()) status = PipelineStatus::HaveData; if (m_moved_position_pending) status = PipelineStatus::MovedPosition; m_downstream_needs_wake = is_waiting_for_data(status); return status; } void AudioTimeStretchProcessor::pull(AudioBlock& into) { Sync::MutexLocker locker { m_mutex }; if (m_moved_position_pending) { m_moved_position_pending = false; into.clear(); return; } if (!m_pending_block.is_empty()) { into.initialize(m_pending_block.sample_specification(), m_pending_block.first_frame_index(), m_pending_block.frame_count()); for (size_t channel = 0; channel < into.channel_count(); channel++) AK::TypedTransfer::copy(into.channel_data(channel).data(), m_pending_block.channel_data(channel).data(), into.frame_count()); into.set_media_time_start(m_pending_block.media_time_start()); into.set_media_time_duration(m_pending_block.media_time_duration()); m_pending_block.clear(); return; } into.clear(); } }