- Provider -> producer - (Audio|Video)DataProvider -> Decoded(Audio|Video)Producer - MediaTimeProvider remains suffixed Provider, moves out of the Providers folder to the root of LibMedia This brings the naming more in line with the intended split functionality split between different nodes in the pipeline.
575 lines
18 KiB
C++
575 lines
18 KiB
C++
/*
|
|
* Copyright (c) 2025, Gregory Bertilson <gregory@ladybird.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <LibCore/EventLoop.h>
|
|
#include <LibMedia/Demuxer.h>
|
|
#include <LibMedia/FFmpeg/FFmpegVideoDecoder.h>
|
|
#include <LibMedia/MediaTimeProvider.h>
|
|
#include <LibMedia/Sinks/VideoSink.h>
|
|
#include <LibMedia/VideoDecoder.h>
|
|
#include <LibMedia/VideoFrame.h>
|
|
#include <LibThreading/Thread.h>
|
|
|
|
#include "DecodedVideoProducer.h"
|
|
|
|
namespace Media {
|
|
|
|
DecoderErrorOr<NonnullRefPtr<DecodedVideoProducer>> DecodedVideoProducer::try_create(NonnullRefPtr<Core::WeakEventLoopReference> const& main_thread_event_loop, NonnullRefPtr<Demuxer> const& demuxer, Track const& track, RefPtr<MediaTimeProvider> const& time_provider)
|
|
{
|
|
TRY(demuxer->create_context_for_track(track));
|
|
auto duration = TRY(demuxer->duration_of_track(track));
|
|
auto thread_data = DECODER_TRY_ALLOC(try_make_ref_counted<DecodedVideoProducer::ThreadData>(main_thread_event_loop, demuxer, track, duration, time_provider));
|
|
TRY(thread_data->create_decoder());
|
|
auto producer = DECODER_TRY_ALLOC(try_make_ref_counted<DecodedVideoProducer>(thread_data));
|
|
|
|
auto thread = DECODER_TRY_ALLOC(Threading::Thread::try_create("Video Decoder"sv, [thread_data]() -> int {
|
|
thread_data->wait_for_start();
|
|
while (!thread_data->should_thread_exit()) {
|
|
if (thread_data->handle_suspension())
|
|
continue;
|
|
thread_data->handle_seek();
|
|
thread_data->push_data_and_decode_some_frames();
|
|
}
|
|
return 0;
|
|
}));
|
|
thread->start();
|
|
thread->detach();
|
|
|
|
return producer;
|
|
}
|
|
|
|
DecodedVideoProducer::DecodedVideoProducer(NonnullRefPtr<ThreadData> const& thread_state)
|
|
: m_thread_data(thread_state)
|
|
{
|
|
}
|
|
|
|
DecodedVideoProducer::~DecodedVideoProducer()
|
|
{
|
|
m_thread_data->exit();
|
|
}
|
|
|
|
void DecodedVideoProducer::set_error_handler(ErrorHandler&& handler)
|
|
{
|
|
m_thread_data->set_error_handler(move(handler));
|
|
}
|
|
|
|
void DecodedVideoProducer::set_duration_change_handler(FrameEndTimeHandler&& handler)
|
|
{
|
|
m_thread_data->set_duration_change_handler(move(handler));
|
|
}
|
|
|
|
void DecodedVideoProducer::start()
|
|
{
|
|
m_thread_data->start();
|
|
}
|
|
|
|
void DecodedVideoProducer::suspend()
|
|
{
|
|
m_thread_data->suspend();
|
|
}
|
|
|
|
void DecodedVideoProducer::resume()
|
|
{
|
|
m_thread_data->resume();
|
|
}
|
|
|
|
void DecodedVideoProducer::set_frames_queue_is_full_handler(FramesQueueIsFullHandler&& handler)
|
|
{
|
|
m_thread_data->set_frames_queue_is_full_handler(move(handler));
|
|
}
|
|
|
|
RefPtr<VideoFrame> DecodedVideoProducer::retrieve_frame()
|
|
{
|
|
auto locker = m_thread_data->take_lock();
|
|
if (m_thread_data->queue().is_empty())
|
|
return nullptr;
|
|
auto result = m_thread_data->take_frame();
|
|
m_thread_data->wake();
|
|
return result;
|
|
}
|
|
|
|
void DecodedVideoProducer::seek(AK::Duration timestamp, SeekMode seek_mode, SeekCompletionHandler&& completion_handler)
|
|
{
|
|
m_thread_data->seek(timestamp, seek_mode, move(completion_handler));
|
|
}
|
|
|
|
DecodedVideoProducer::ThreadData::ThreadData(NonnullRefPtr<Core::WeakEventLoopReference> const& main_thread_event_loop, NonnullRefPtr<Demuxer> const& demuxer, Track const& track, AK::Duration duration, RefPtr<MediaTimeProvider> const& time_provider)
|
|
: m_main_thread_event_loop(main_thread_event_loop)
|
|
, m_demuxer(demuxer)
|
|
, m_track(track)
|
|
, m_duration(duration)
|
|
, m_time_provider(time_provider)
|
|
{
|
|
}
|
|
|
|
DecoderErrorOr<void> DecodedVideoProducer::ThreadData::create_decoder()
|
|
{
|
|
auto codec_id = TRY(m_demuxer->get_codec_id_for_track(m_track));
|
|
auto codec_initialization_data = TRY(m_demuxer->get_codec_initialization_data_for_track(m_track));
|
|
m_decoder = TRY(FFmpeg::FFmpegVideoDecoder::try_create(codec_id, codec_initialization_data));
|
|
return {};
|
|
}
|
|
|
|
bool DecodedVideoProducer::is_blocked() const
|
|
{
|
|
return m_thread_data->is_blocked();
|
|
}
|
|
|
|
TimeRanges DecodedVideoProducer::buffered_time_ranges() const
|
|
{
|
|
return m_thread_data->buffered_time_ranges();
|
|
}
|
|
|
|
DecodedVideoProducer::ThreadData::~ThreadData() = default;
|
|
|
|
void DecodedVideoProducer::ThreadData::set_error_handler(ErrorHandler&& handler)
|
|
{
|
|
m_error_handler = move(handler);
|
|
}
|
|
|
|
void DecodedVideoProducer::ThreadData::start()
|
|
{
|
|
auto locker = take_lock();
|
|
if (m_requested_state != RequestedState::None)
|
|
return;
|
|
m_requested_state = RequestedState::Running;
|
|
wake();
|
|
}
|
|
|
|
void DecodedVideoProducer::ThreadData::set_duration_change_handler(FrameEndTimeHandler&& handler)
|
|
{
|
|
m_duration_change_handler = move(handler);
|
|
}
|
|
|
|
void DecodedVideoProducer::ThreadData::set_frames_queue_is_full_handler(FramesQueueIsFullHandler&& handler)
|
|
{
|
|
m_frames_queue_is_full_handler = move(handler);
|
|
}
|
|
|
|
void DecodedVideoProducer::ThreadData::suspend()
|
|
{
|
|
auto locker = take_lock();
|
|
VERIFY(m_requested_state != RequestedState::Exit);
|
|
m_requested_state = RequestedState::Suspended;
|
|
wake();
|
|
}
|
|
|
|
void DecodedVideoProducer::ThreadData::resume()
|
|
{
|
|
auto locker = take_lock();
|
|
VERIFY(m_requested_state != RequestedState::Exit);
|
|
m_requested_state = RequestedState::Running;
|
|
wake();
|
|
}
|
|
|
|
void DecodedVideoProducer::ThreadData::exit()
|
|
{
|
|
auto locker = take_lock();
|
|
m_requested_state = RequestedState::Exit;
|
|
wake();
|
|
}
|
|
|
|
DecodedVideoProducer::FrameQueue& DecodedVideoProducer::ThreadData::queue()
|
|
{
|
|
return m_queue;
|
|
}
|
|
|
|
NonnullRefPtr<VideoFrame> DecodedVideoProducer::ThreadData::take_frame()
|
|
{
|
|
return m_queue.dequeue();
|
|
}
|
|
|
|
void DecodedVideoProducer::ThreadData::seek(AK::Duration timestamp, SeekMode seek_mode, SeekCompletionHandler&& completion_handler)
|
|
{
|
|
auto locker = take_lock();
|
|
m_seek_id++;
|
|
m_seek_completion_handler = move(completion_handler);
|
|
m_seek_timestamp = timestamp;
|
|
m_seek_mode = seek_mode;
|
|
m_demuxer->set_blocking_reads_aborted_for_track(m_track);
|
|
wake();
|
|
}
|
|
|
|
void DecodedVideoProducer::ThreadData::wait_for_start()
|
|
{
|
|
auto locker = take_lock();
|
|
while (m_requested_state == RequestedState::None)
|
|
m_wait_condition.wait();
|
|
}
|
|
|
|
bool DecodedVideoProducer::ThreadData::should_thread_exit_while_locked() const
|
|
{
|
|
return m_requested_state == RequestedState::Exit;
|
|
}
|
|
|
|
bool DecodedVideoProducer::ThreadData::should_thread_exit() const
|
|
{
|
|
auto locker = take_lock();
|
|
return should_thread_exit_while_locked();
|
|
}
|
|
|
|
template<typename Invokee>
|
|
void DecodedVideoProducer::ThreadData::invoke_on_main_thread_while_locked(Invokee invokee)
|
|
{
|
|
if (m_requested_state == RequestedState::Exit)
|
|
return;
|
|
auto event_loop = m_main_thread_event_loop->take();
|
|
if (!event_loop.is_alive())
|
|
return;
|
|
event_loop->deferred_invoke([self = NonnullRefPtr(*this), invokee = move(invokee)] mutable {
|
|
invokee(self);
|
|
});
|
|
}
|
|
|
|
bool DecodedVideoProducer::ThreadData::handle_suspension()
|
|
{
|
|
{
|
|
auto locker = take_lock();
|
|
if (m_requested_state != RequestedState::Suspended)
|
|
return false;
|
|
|
|
m_queue.clear();
|
|
m_decoder.clear();
|
|
m_decoder_needs_keyframe_next_seek = true;
|
|
|
|
while (m_requested_state == RequestedState::Suspended)
|
|
m_wait_condition.wait();
|
|
|
|
if (m_requested_state != RequestedState::Running)
|
|
return true;
|
|
|
|
auto result = create_decoder();
|
|
if (result.is_error()) {
|
|
m_is_in_error_state = true;
|
|
invoke_on_main_thread_while_locked([error = result.release_error()](auto const& self) mutable {
|
|
self->dispatch_error(move(error));
|
|
});
|
|
}
|
|
}
|
|
|
|
// Suspension must be woken with a seek, or we will throw decoding errors.
|
|
while (!handle_seek()) {
|
|
auto locker = take_lock();
|
|
m_wait_condition.wait();
|
|
if (should_thread_exit_while_locked())
|
|
return true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
template<typename Invokee>
|
|
void DecodedVideoProducer::ThreadData::invoke_on_main_thread(Invokee invokee)
|
|
{
|
|
auto locker = take_lock();
|
|
invoke_on_main_thread_while_locked(move(invokee));
|
|
}
|
|
|
|
void DecodedVideoProducer::ThreadData::dispatch_frame_end_time(CodedFrame const& frame)
|
|
{
|
|
auto end_time = frame.timestamp() + frame.duration();
|
|
if (end_time < m_duration)
|
|
return;
|
|
m_duration = end_time;
|
|
invoke_on_main_thread([end_time](auto const& self) {
|
|
if (self->m_duration_change_handler)
|
|
self->m_duration_change_handler(end_time);
|
|
});
|
|
}
|
|
|
|
void DecodedVideoProducer::ThreadData::queue_frame(NonnullRefPtr<VideoFrame> const& frame)
|
|
{
|
|
m_queue.enqueue(frame);
|
|
}
|
|
|
|
void DecodedVideoProducer::ThreadData::dispatch_error(DecoderError&& error)
|
|
{
|
|
if (error.category() == DecoderErrorCategory::Aborted)
|
|
return;
|
|
if (m_error_handler)
|
|
m_error_handler(move(error));
|
|
}
|
|
|
|
template<typename Callback>
|
|
void DecodedVideoProducer::ThreadData::process_seek_on_main_thread(u32 seek_id, Callback callback)
|
|
{
|
|
m_last_processed_seek_id = seek_id;
|
|
invoke_on_main_thread_while_locked([seek_id, callback = move(callback)](auto& self) mutable {
|
|
if (self->m_seek_id != seek_id)
|
|
return;
|
|
callback(self);
|
|
});
|
|
}
|
|
|
|
void DecodedVideoProducer::ThreadData::resolve_seek(u32 seek_id, AK::Duration const& timestamp)
|
|
{
|
|
m_is_in_error_state = false;
|
|
process_seek_on_main_thread(seek_id, [timestamp](auto& self) {
|
|
auto handler = move(self->m_seek_completion_handler);
|
|
if (handler)
|
|
handler(timestamp);
|
|
});
|
|
}
|
|
|
|
bool DecodedVideoProducer::ThreadData::handle_seek()
|
|
{
|
|
VERIFY(m_decoder);
|
|
|
|
auto seek_id = m_seek_id.load();
|
|
if (m_last_processed_seek_id == seek_id)
|
|
return false;
|
|
|
|
auto handle_error = [&](DecoderError&& error) {
|
|
m_is_in_error_state = true;
|
|
{
|
|
auto locker = take_lock();
|
|
m_queue.clear();
|
|
process_seek_on_main_thread(seek_id,
|
|
[error = move(error)](auto& self) mutable {
|
|
self->dispatch_error(move(error));
|
|
self->m_seek_completion_handler = nullptr;
|
|
});
|
|
}
|
|
};
|
|
|
|
AK::Duration timestamp;
|
|
SeekMode mode { SeekMode::Accurate };
|
|
|
|
while (true) {
|
|
{
|
|
auto locker = take_lock();
|
|
seek_id = m_seek_id;
|
|
timestamp = m_seek_timestamp;
|
|
mode = m_seek_mode;
|
|
m_demuxer->reset_blocking_reads_aborted_for_track(m_track);
|
|
}
|
|
|
|
auto seek_options = mode == SeekMode::Accurate ? DemuxerSeekOptions::None : DemuxerSeekOptions::Force;
|
|
if (m_decoder_needs_keyframe_next_seek) {
|
|
seek_options |= DemuxerSeekOptions::Force;
|
|
m_decoder_needs_keyframe_next_seek = false;
|
|
}
|
|
auto demuxer_seek_result_or_error = m_demuxer->seek_to_most_recent_keyframe(m_track, timestamp, seek_options);
|
|
if (demuxer_seek_result_or_error.is_error() && demuxer_seek_result_or_error.error().category() != DecoderErrorCategory::EndOfStream) {
|
|
handle_error(demuxer_seek_result_or_error.release_error());
|
|
return true;
|
|
}
|
|
auto demuxer_seek_result = demuxer_seek_result_or_error.value_or(DemuxerSeekResult::MovedPosition);
|
|
|
|
if (demuxer_seek_result == DemuxerSeekResult::MovedPosition)
|
|
m_decoder->flush();
|
|
|
|
auto is_desired_coded_frame = [mode, timestamp](CodedFrame const& frame) {
|
|
if (mode == SeekMode::Accurate)
|
|
return true;
|
|
if (mode == SeekMode::FastBefore)
|
|
return frame.is_keyframe();
|
|
if (mode == SeekMode::FastAfter)
|
|
return frame.is_keyframe() && frame.timestamp() > timestamp;
|
|
VERIFY_NOT_REACHED();
|
|
};
|
|
|
|
auto is_desired_decoded_frame = [mode, timestamp](VideoFrame const& frame) {
|
|
if (mode == SeekMode::Accurate)
|
|
return frame.timestamp() > timestamp;
|
|
return true;
|
|
};
|
|
|
|
auto resolved_time = [mode, timestamp](VideoFrame const& frame) {
|
|
if (mode == SeekMode::Accurate)
|
|
return timestamp;
|
|
if (mode == SeekMode::FastBefore)
|
|
return min(timestamp, frame.timestamp());
|
|
if (mode == SeekMode::FastAfter)
|
|
return max(timestamp, frame.timestamp());
|
|
VERIFY_NOT_REACHED();
|
|
};
|
|
|
|
auto new_seek_id = m_seek_id.load();
|
|
auto found_desired_keyframe = false;
|
|
RefPtr<VideoFrame> last_frame;
|
|
|
|
while (new_seek_id == seek_id) {
|
|
auto coded_frame_result = m_demuxer->get_next_sample_for_track(m_track);
|
|
if (coded_frame_result.is_error()) {
|
|
if (coded_frame_result.error().category() == DecoderErrorCategory::EndOfStream) {
|
|
if (mode == SeekMode::FastAfter) {
|
|
// If we're fast seeking after the provided timestamp and reach the end of the stream, that means we have
|
|
// nothing to display. Restart the seek as an accurate seek.
|
|
auto locker = take_lock();
|
|
seek_id = ++m_seek_id;
|
|
m_seek_mode = SeekMode::Accurate;
|
|
continue;
|
|
}
|
|
|
|
m_decoder->signal_end_of_stream();
|
|
} else {
|
|
handle_error(coded_frame_result.release_error());
|
|
return true;
|
|
}
|
|
} else {
|
|
auto coded_frame = coded_frame_result.release_value();
|
|
dispatch_frame_end_time(coded_frame);
|
|
|
|
if (!found_desired_keyframe)
|
|
found_desired_keyframe = is_desired_coded_frame(coded_frame);
|
|
|
|
if (!found_desired_keyframe)
|
|
continue;
|
|
|
|
auto decode_result = m_decoder->receive_coded_data(coded_frame.timestamp(), coded_frame.duration(), coded_frame.data());
|
|
if (decode_result.is_error()) {
|
|
handle_error(decode_result.release_error());
|
|
return true;
|
|
}
|
|
}
|
|
|
|
while (new_seek_id == seek_id) {
|
|
auto frame_result = m_decoder->get_decoded_frame(m_track.video_data().cicp);
|
|
if (frame_result.is_error()) {
|
|
if (frame_result.error().category() == DecoderErrorCategory::EndOfStream) {
|
|
auto locker = take_lock();
|
|
if (last_frame != nullptr)
|
|
queue_frame(last_frame.release_nonnull());
|
|
|
|
resolve_seek(seek_id, timestamp);
|
|
return true;
|
|
}
|
|
|
|
if (frame_result.error().category() == DecoderErrorCategory::NeedsMoreInput)
|
|
break;
|
|
|
|
handle_error(frame_result.release_error());
|
|
return true;
|
|
}
|
|
|
|
auto current_frame = frame_result.release_value();
|
|
if (is_desired_decoded_frame(*current_frame)) {
|
|
auto locker = take_lock();
|
|
m_queue.clear();
|
|
|
|
if (last_frame != nullptr)
|
|
queue_frame(last_frame.release_nonnull());
|
|
|
|
queue_frame(current_frame);
|
|
|
|
resolve_seek(seek_id, resolved_time(*current_frame));
|
|
return true;
|
|
}
|
|
|
|
last_frame = move(current_frame);
|
|
|
|
new_seek_id = m_seek_id;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void DecodedVideoProducer::ThreadData::push_data_and_decode_some_frames()
|
|
{
|
|
VERIFY(m_decoder);
|
|
|
|
// FIXME: Check if the PlaybackManager's current time is ahead of the next keyframe, and seek to it if so.
|
|
// Demuxers currently can't report the next keyframe in a convenient way, so that will need implementing
|
|
// before this functionality can exist.
|
|
|
|
auto set_error_and_wait_for_seek = [this](DecoderError&& error) {
|
|
{
|
|
auto locker = take_lock();
|
|
m_is_in_error_state = true;
|
|
invoke_on_main_thread_while_locked([error = move(error)](auto const& self) mutable {
|
|
self->dispatch_error(move(error));
|
|
});
|
|
}
|
|
|
|
dbgln_if(PLAYBACK_MANAGER_DEBUG, "Decoded Video Producer: Encountered an error, waiting for a seek to start decoding again...");
|
|
while (m_is_in_error_state) {
|
|
if (handle_seek())
|
|
break;
|
|
{
|
|
auto locker = take_lock();
|
|
m_wait_condition.wait();
|
|
if (should_thread_exit_while_locked())
|
|
return;
|
|
}
|
|
}
|
|
};
|
|
|
|
auto sample_result = m_demuxer->get_next_sample_for_track(m_track);
|
|
if (sample_result.is_error()) {
|
|
if (sample_result.error().category() == DecoderErrorCategory::EndOfStream) {
|
|
m_decoder->signal_end_of_stream();
|
|
} else {
|
|
set_error_and_wait_for_seek(sample_result.release_error());
|
|
return;
|
|
}
|
|
} else {
|
|
auto coded_frame = sample_result.release_value();
|
|
dispatch_frame_end_time(coded_frame);
|
|
|
|
auto decode_result = m_decoder->receive_coded_data(coded_frame.timestamp(), coded_frame.duration(), coded_frame.data());
|
|
if (decode_result.is_error()) {
|
|
set_error_and_wait_for_seek(decode_result.release_error());
|
|
return;
|
|
}
|
|
}
|
|
|
|
while (true) {
|
|
auto frame_result = m_decoder->get_decoded_frame(m_track.video_data().cicp);
|
|
if (frame_result.is_error()) {
|
|
if (frame_result.error().category() == DecoderErrorCategory::NeedsMoreInput)
|
|
break;
|
|
set_error_and_wait_for_seek(frame_result.release_error());
|
|
break;
|
|
}
|
|
|
|
auto frame = frame_result.release_value();
|
|
|
|
{
|
|
auto queue_size = [&] {
|
|
auto locker = take_lock();
|
|
return m_queue.size();
|
|
}();
|
|
|
|
while (queue_size >= m_queue_max_size) {
|
|
if (m_frames_queue_is_full_handler) {
|
|
invoke_on_main_thread([](auto const& self) {
|
|
self->m_frames_queue_is_full_handler();
|
|
});
|
|
}
|
|
|
|
if (handle_seek())
|
|
return;
|
|
|
|
if (handle_suspension())
|
|
return;
|
|
|
|
{
|
|
auto locker = take_lock();
|
|
m_wait_condition.wait();
|
|
if (should_thread_exit_while_locked())
|
|
return;
|
|
queue_size = m_queue.size();
|
|
}
|
|
}
|
|
|
|
auto locker = take_lock();
|
|
queue_frame(frame);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool DecodedVideoProducer::ThreadData::is_blocked() const
|
|
{
|
|
return m_demuxer->is_read_blocked_for_track(m_track);
|
|
}
|
|
|
|
TimeRanges DecodedVideoProducer::ThreadData::buffered_time_ranges() const
|
|
{
|
|
return m_demuxer->buffered_time_ranges();
|
|
}
|
|
|
|
}
|