|
| 1 | +/* |
| 2 | + * Copyright (c) 2025, Gregory Bertilson <gregory@ladybird.org> |
| 3 | + * |
| 4 | + * SPDX-License-Identifier: BSD-2-Clause |
| 5 | + */ |
| 6 | + |
| 7 | +#include <LibCore/EventLoop.h> |
| 8 | +#include <LibMedia/FFmpeg/FFmpegVideoDecoder.h> |
| 9 | +#include <LibMedia/MutexedDemuxer.h> |
| 10 | +#include <LibMedia/Providers/MediaTimeProvider.h> |
| 11 | +#include <LibMedia/Sinks/VideoSink.h> |
| 12 | +#include <LibMedia/VideoDecoder.h> |
| 13 | +#include <LibMedia/VideoFrame.h> |
| 14 | +#include <LibThreading/Thread.h> |
| 15 | + |
| 16 | +#include "VideoDataProvider.h" |
| 17 | + |
| 18 | +namespace Media { |
| 19 | + |
| 20 | +DecoderErrorOr<NonnullRefPtr<VideoDataProvider>> VideoDataProvider::try_create(NonnullRefPtr<MutexedDemuxer> const& demuxer, Track const& track, RefPtr<MediaTimeProvider> const& time_provider) |
| 21 | +{ |
| 22 | + auto codec_id = TRY(demuxer->get_codec_id_for_track(track)); |
| 23 | + auto codec_initialization_data = TRY(demuxer->get_codec_initialization_data_for_track(track)); |
| 24 | + auto decoder = DECODER_TRY_ALLOC(FFmpeg::FFmpegVideoDecoder::try_create(codec_id, codec_initialization_data)); |
| 25 | + |
| 26 | + auto thread_data = DECODER_TRY_ALLOC(try_make_ref_counted<VideoDataProvider::ThreadData>(demuxer, track, move(decoder), time_provider)); |
| 27 | + auto provider = DECODER_TRY_ALLOC(try_make_ref_counted<VideoDataProvider>(thread_data)); |
| 28 | + |
| 29 | + auto thread = DECODER_TRY_ALLOC(Threading::Thread::try_create([thread_data]() -> int { |
| 30 | + while (!thread_data->should_thread_exit()) |
| 31 | + thread_data->push_data_and_decode_some_frames(); |
| 32 | + return 0; |
| 33 | + })); |
| 34 | + thread->start(); |
| 35 | + thread->detach(); |
| 36 | + |
| 37 | + return provider; |
| 38 | +} |
| 39 | + |
| 40 | +DecoderErrorOr<NonnullRefPtr<VideoDataProvider>> VideoDataProvider::try_create(NonnullRefPtr<Demuxer> const& demuxer, Track const& track, RefPtr<MediaTimeProvider> const& time_provider) |
| 41 | +{ |
| 42 | + auto mutexed_demuxer = DECODER_TRY_ALLOC(try_make_ref_counted<MutexedDemuxer>(demuxer)); |
| 43 | + return try_create(mutexed_demuxer, track, time_provider); |
| 44 | +} |
| 45 | + |
| 46 | +VideoDataProvider::VideoDataProvider(NonnullRefPtr<ThreadData> const& thread_state) |
| 47 | + : m_thread_data(thread_state) |
| 48 | +{ |
| 49 | +} |
| 50 | + |
| 51 | +VideoDataProvider::~VideoDataProvider() |
| 52 | +{ |
| 53 | + m_thread_data->exit(); |
| 54 | +} |
| 55 | + |
| 56 | +void VideoDataProvider::set_error_handler(ErrorHandler&& handler) |
| 57 | +{ |
| 58 | + m_thread_data->set_error_handler(move(handler)); |
| 59 | +} |
| 60 | + |
| 61 | +TimedImage VideoDataProvider::retrieve_frame() |
| 62 | +{ |
| 63 | + auto locker = m_thread_data->take_lock(); |
| 64 | + if (m_thread_data->queue().is_empty()) |
| 65 | + return TimedImage(); |
| 66 | + auto result = m_thread_data->queue().dequeue(); |
| 67 | + m_thread_data->wake(); |
| 68 | + return result; |
| 69 | +} |
| 70 | + |
| 71 | +void VideoDataProvider::seek(AK::Duration timestamp) |
| 72 | +{ |
| 73 | + m_thread_data->seek(timestamp); |
| 74 | +} |
| 75 | + |
| 76 | +VideoDataProvider::ThreadData::ThreadData(NonnullRefPtr<MutexedDemuxer> const& demuxer, Track const& track, NonnullOwnPtr<VideoDecoder>&& decoder, RefPtr<MediaTimeProvider> const& time_provider) |
| 77 | + : m_main_thread_event_loop(Core::EventLoop::current()) |
| 78 | + , m_demuxer(demuxer) |
| 79 | + , m_track(track) |
| 80 | + , m_decoder(move(decoder)) |
| 81 | + , m_time_provider(time_provider) |
| 82 | +{ |
| 83 | +} |
| 84 | + |
| 85 | +VideoDataProvider::ThreadData::~ThreadData() = default; |
| 86 | + |
| 87 | +void VideoDataProvider::ThreadData::set_error_handler(ErrorHandler&& handler) |
| 88 | +{ |
| 89 | + auto locker = take_lock(); |
| 90 | + m_error_handler = move(handler); |
| 91 | + m_wait_condition.broadcast(); |
| 92 | +} |
| 93 | + |
| 94 | +void VideoDataProvider::ThreadData::exit() |
| 95 | +{ |
| 96 | + m_exit = true; |
| 97 | + m_wait_condition.broadcast(); |
| 98 | +} |
| 99 | + |
| 100 | +VideoDataProvider::ImageQueue& VideoDataProvider::ThreadData::queue() |
| 101 | +{ |
| 102 | + return m_queue; |
| 103 | +} |
| 104 | + |
| 105 | +void VideoDataProvider::ThreadData::seek(AK::Duration timestamp) |
| 106 | +{ |
| 107 | + auto seek_result = m_demuxer->seek_to_most_recent_keyframe(m_track, timestamp); |
| 108 | + if (seek_result.is_error()) { |
| 109 | + m_error_handler(seek_result.release_error()); |
| 110 | + } else { |
| 111 | + auto locker = take_lock(); |
| 112 | + m_is_in_error_state = false; |
| 113 | + m_wait_condition.broadcast(); |
| 114 | + } |
| 115 | +} |
| 116 | + |
| 117 | +bool VideoDataProvider::ThreadData::should_thread_exit() const |
| 118 | +{ |
| 119 | + return m_exit; |
| 120 | +} |
| 121 | + |
| 122 | +void VideoDataProvider::ThreadData::push_data_and_decode_some_frames() |
| 123 | +{ |
| 124 | + // FIXME: Check if the PlaybackManager's current time is ahead of the next keyframe, and seek to it if so. |
| 125 | + // Demuxers currently can't report the next keyframe in a convenient way, so that will need implementing |
| 126 | + // before this functionality can exist. |
| 127 | + |
| 128 | + auto set_error_and_wait_for_seek = [this](DecoderError&& error) { |
| 129 | + auto locker = take_lock(); |
| 130 | + m_is_in_error_state = true; |
| 131 | + while (!m_error_handler) |
| 132 | + m_wait_condition.wait(); |
| 133 | + m_main_thread_event_loop.deferred_invoke([this, error = move(error)] mutable { |
| 134 | + m_error_handler(move(error)); |
| 135 | + }); |
| 136 | + dbgln_if(PLAYBACK_MANAGER_DEBUG, "Video Data Provider: Encountered an error, waiting for a seek to start decoding again..."); |
| 137 | + while (m_is_in_error_state) |
| 138 | + m_wait_condition.wait(); |
| 139 | + }; |
| 140 | + |
| 141 | + auto sample_result = m_demuxer->get_next_sample_for_track(m_track); |
| 142 | + if (sample_result.is_error()) { |
| 143 | + // FIXME: Handle the end of the stream. |
| 144 | + set_error_and_wait_for_seek(sample_result.release_error()); |
| 145 | + return; |
| 146 | + } |
| 147 | + |
| 148 | + auto coded_frame = sample_result.release_value(); |
| 149 | + auto decode_result = m_decoder->receive_coded_data(coded_frame.timestamp(), coded_frame.data()); |
| 150 | + if (decode_result.is_error()) { |
| 151 | + set_error_and_wait_for_seek(decode_result.release_error()); |
| 152 | + return; |
| 153 | + } |
| 154 | + |
| 155 | + while (true) { |
| 156 | + auto frame_result = m_decoder->get_decoded_frame(); |
| 157 | + if (frame_result.is_error()) { |
| 158 | + if (frame_result.error().category() == DecoderErrorCategory::NeedsMoreInput) |
| 159 | + break; |
| 160 | + set_error_and_wait_for_seek(frame_result.release_error()); |
| 161 | + break; |
| 162 | + } |
| 163 | + |
| 164 | + auto frame = frame_result.release_value(); |
| 165 | + |
| 166 | + // Convert the frame for display. |
| 167 | + auto& cicp = frame->cicp(); |
| 168 | + auto container_cicp = coded_frame.auxiliary_data().get<CodedVideoFrameData>().container_cicp(); |
| 169 | + cicp.adopt_specified_values(container_cicp); |
| 170 | + cicp.default_code_points_if_unspecified({ ColorPrimaries::BT709, TransferCharacteristics::BT709, MatrixCoefficients::BT709, VideoFullRangeFlag::Studio }); |
| 171 | + |
| 172 | + // BT.470 M, B/G, BT.601, BT.709 and BT.2020 have a similar transfer function to sRGB, so other applications |
| 173 | + // (Chromium, VLC) forgo transfer characteristics conversion. We will emulate that behavior by |
| 174 | + // handling those as sRGB instead, which causes no transfer function change in the output, |
| 175 | + // unless display color management is later implemented. |
| 176 | + switch (cicp.transfer_characteristics()) { |
| 177 | + case TransferCharacteristics::BT470BG: |
| 178 | + case TransferCharacteristics::BT470M: |
| 179 | + case TransferCharacteristics::BT601: |
| 180 | + case TransferCharacteristics::BT709: |
| 181 | + case TransferCharacteristics::BT2020BitDepth10: |
| 182 | + case TransferCharacteristics::BT2020BitDepth12: |
| 183 | + cicp.set_transfer_characteristics(TransferCharacteristics::SRGB); |
| 184 | + break; |
| 185 | + default: |
| 186 | + break; |
| 187 | + } |
| 188 | + |
| 189 | + auto bitmap_result = frame->to_bitmap(); |
| 190 | + |
| 191 | + if (bitmap_result.is_error()) { |
| 192 | + set_error_and_wait_for_seek(bitmap_result.release_error()); |
| 193 | + return; |
| 194 | + } |
| 195 | + |
| 196 | + { |
| 197 | + auto locker = take_lock(); |
| 198 | + while (m_queue.size() >= m_queue_max_size) { |
| 199 | + m_wait_condition.wait(); |
| 200 | + if (should_thread_exit()) |
| 201 | + return; |
| 202 | + } |
| 203 | + m_queue.enqueue(TimedImage(frame->timestamp(), bitmap_result.release_value())); |
| 204 | + } |
| 205 | + } |
| 206 | +} |
| 207 | + |
| 208 | +} |
0 commit comments