Skip to content

Commit 7e238cd

Browse files
Zaggy1024gmta
authored andcommitted
LibMedia: Add separate classes managing decoding and displaying video
These are unused in this commit, but will later be used to output video via PlaybackManager, or to decode video directly to some consumer.
1 parent dfbad09 commit 7e238cd

File tree

11 files changed

+620
-0
lines changed

11 files changed

+620
-0
lines changed

Libraries/LibMedia/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ set(SOURCES
1111
Containers/Matroska/MatroskaDemuxer.cpp
1212
Containers/Matroska/Reader.cpp
1313
PlaybackManager.cpp
14+
Providers/VideoDataProvider.cpp
15+
Sinks/DisplayingVideoSink.cpp
1416
VideoFrame.cpp
1517
)
1618

Libraries/LibMedia/Forward.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/*
22
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
3+
* Copyright (c) 2025, Gregory Bertilson <gregory@ladybird.org>
34
*
45
* SPDX-License-Identifier: BSD-2-Clause
56
*/
@@ -10,10 +11,16 @@ namespace Media {
1011

1112
class CodedFrame;
1213
class DecoderError;
14+
class Demuxer;
15+
class DisplayingVideoSink;
1316
class FrameQueueItem;
17+
class MediaTimeProvider;
18+
class MutexedDemuxer;
1419
class PlaybackManager;
1520
class Track;
21+
class VideoDataProvider;
1622
class VideoDecoder;
1723
class VideoFrame;
24+
class VideoSink;
1825

1926
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
* Copyright (c) 2022, Gregory Bertilson <zaggy1024@gmail.com>
3+
*
4+
* SPDX-License-Identifier: BSD-2-Clause
5+
*/
6+
7+
#pragma once
8+
9+
#include <AK/Forward.h>
10+
#include <LibMedia/DecoderError.h>
11+
#include <LibThreading/MutexProtected.h>
12+
13+
#include "Demuxer.h"
14+
15+
namespace Media {
16+
17+
class MutexedDemuxer final : public Demuxer {
18+
public:
19+
MutexedDemuxer(NonnullRefPtr<Demuxer> demuxer)
20+
: m_demuxer(move(demuxer))
21+
{
22+
}
23+
24+
virtual ~MutexedDemuxer() override
25+
{
26+
m_demuxer.with_locked([](auto& demuxer) {
27+
auto to_destroy = move(demuxer);
28+
});
29+
}
30+
31+
virtual DecoderErrorOr<Vector<Track>> get_tracks_for_type(TrackType type) override
32+
{
33+
return m_demuxer.with_locked([&](auto& demuxer) {
34+
return demuxer->get_tracks_for_type(type);
35+
});
36+
}
37+
virtual DecoderErrorOr<Optional<Track>> get_preferred_track_for_type(TrackType type) override
38+
{
39+
return m_demuxer.with_locked([&](auto& demuxer) {
40+
return demuxer->get_preferred_track_for_type(type);
41+
});
42+
}
43+
44+
virtual DecoderErrorOr<CodedFrame> get_next_sample_for_track(Track track) override
45+
{
46+
return m_demuxer.with_locked([&](auto& demuxer) {
47+
return demuxer->get_next_sample_for_track(track);
48+
});
49+
}
50+
51+
virtual DecoderErrorOr<CodecID> get_codec_id_for_track(Track track) override
52+
{
53+
return m_demuxer.with_locked([&](auto& demuxer) {
54+
return demuxer->get_codec_id_for_track(track);
55+
});
56+
}
57+
58+
virtual DecoderErrorOr<ReadonlyBytes> get_codec_initialization_data_for_track(Track track) override
59+
{
60+
return m_demuxer.with_locked([&](auto& demuxer) {
61+
return demuxer->get_codec_initialization_data_for_track(track);
62+
});
63+
}
64+
65+
virtual DecoderErrorOr<Optional<AK::Duration>> seek_to_most_recent_keyframe(Track track, AK::Duration timestamp, Optional<AK::Duration> earliest_available_sample = {}) override
66+
{
67+
return m_demuxer.with_locked([&](auto& demuxer) {
68+
return demuxer->seek_to_most_recent_keyframe(track, timestamp, earliest_available_sample);
69+
});
70+
}
71+
72+
virtual DecoderErrorOr<AK::Duration> duration_of_track(Track const& track) override
73+
{
74+
return m_demuxer.with_locked([&](auto& demuxer) {
75+
return demuxer->duration_of_track(track);
76+
});
77+
}
78+
virtual DecoderErrorOr<AK::Duration> total_duration() override
79+
{
80+
return m_demuxer.with_locked([&](auto& demuxer) {
81+
return demuxer->total_duration();
82+
});
83+
}
84+
85+
private:
86+
Threading::MutexProtected<NonnullRefPtr<Demuxer>> m_demuxer;
87+
};
88+
89+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Copyright (c) 2025, Gregory Bertilson <gregory@ladybird.org>
3+
*
4+
* SPDX-License-Identifier: BSD-2-Clause
5+
*/
6+
7+
#pragma once
8+
9+
#include <AK/AtomicRefCounted.h>
10+
#include <AK/Time.h>
11+
12+
namespace Media {
13+
14+
class MediaTimeProvider : public AtomicRefCounted<MediaTimeProvider> {
15+
public:
16+
virtual ~MediaTimeProvider() = default;
17+
18+
virtual AK::Duration current_time() const = 0;
19+
};
20+
21+
}
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
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

Comments
 (0)