LibMedia: Pass Matroska codec private data to the FFmpeg decoder

This is necessary to give the H.264 decoder the data it needs to
initialize, including frame size and profile.
This commit is contained in:
Zaggy1024 2024-06-19 18:22:21 -05:00 committed by Andrew Kaster
parent 81001b37ce
commit 457a69786b
Notes: sideshowbarker 2024-07-17 00:25:35 +09:00
8 changed files with 40 additions and 3 deletions

View file

@ -8,6 +8,7 @@
#include <AK/ByteBuffer.h>
#include <AK/ByteString.h>
#include <AK/FixedArray.h>
#include <AK/FlyString.h>
#include <AK/HashMap.h>
#include <AK/OwnPtr.h>
@ -119,6 +120,12 @@ public:
void set_language(FlyString const& language) { m_language = language; }
FlyString codec_id() const { return m_codec_id; }
void set_codec_id(FlyString const& codec_id) { m_codec_id = codec_id; }
ReadonlyBytes codec_private_data() const { return m_codec_private_data.span(); }
ErrorOr<void> set_codec_private_data(ReadonlyBytes codec_private_data)
{
m_codec_private_data = TRY(FixedArray<u8>::create(codec_private_data));
return {};
}
double timestamp_scale() const { return m_timestamp_scale; }
void set_timestamp_scale(double timestamp_scale) { m_timestamp_scale = timestamp_scale; }
u64 codec_delay() const { return m_codec_delay; }
@ -146,6 +153,7 @@ private:
TrackType m_track_type { Invalid };
FlyString m_language = "eng"_fly_string;
FlyString m_codec_id;
FixedArray<u8> m_codec_private_data;
double m_timestamp_scale { 1 };
u64 m_codec_delay { 0 };
u64 m_timestamp_offset { 0 };

View file

@ -104,6 +104,11 @@ DecoderErrorOr<CodecID> MatroskaDemuxer::get_codec_id_for_track(Track track)
return get_codec_id_for_string(codec_id);
}
DecoderErrorOr<ReadonlyBytes> MatroskaDemuxer::get_codec_initialization_data_for_track(Track track)
{
return TRY(m_reader.track_for_track_number(track.identifier()))->codec_private_data();
}
DecoderErrorOr<Optional<Duration>> MatroskaDemuxer::seek_to_most_recent_keyframe(Track track, Duration timestamp, Optional<Duration> earliest_available_sample)
{
// Removing the track status will cause us to start from the beginning.

View file

@ -35,6 +35,8 @@ public:
DecoderErrorOr<CodecID> get_codec_id_for_track(Track track) override;
DecoderErrorOr<ReadonlyBytes> get_codec_initialization_data_for_track(Track track) override;
DecoderErrorOr<Sample> get_next_sample_for_track(Track track) override;
private:

View file

@ -50,6 +50,7 @@ constexpr u32 TRACK_UID_ID = 0x73C5;
constexpr u32 TRACK_TYPE_ID = 0x83;
constexpr u32 TRACK_LANGUAGE_ID = 0x22B59C;
constexpr u32 TRACK_CODEC_ID = 0x86;
constexpr u32 TRACK_CODEC_PRIVATE = 0x63A2;
constexpr u32 TRACK_TIMESTAMP_SCALE_ID = 0x23314F;
constexpr u32 TRACK_OFFSET_ID = 0x537F;
constexpr u32 TRACK_VIDEO_ID = 0xE0;
@ -480,6 +481,12 @@ static DecoderErrorOr<NonnullRefPtr<TrackEntry>> parse_track_entry(Streamer& str
track_entry->set_codec_id(DECODER_TRY_ALLOC(String::from_byte_string(TRY_READ(streamer.read_string()))));
dbgln_if(MATROSKA_TRACE_DEBUG, "Read Track's CodecID attribute: {}", track_entry->codec_id());
break;
case TRACK_CODEC_PRIVATE: {
auto codec_private_data = TRY_READ(streamer.read_raw_octets(TRY_READ(streamer.read_variable_size_integer())));
DECODER_TRY_ALLOC(track_entry->set_codec_private_data(codec_private_data));
dbgln_if(MATROSKA_TRACE_DEBUG, "Read Track's CodecID attribute: {}", track_entry->codec_id());
break;
}
case TRACK_TIMESTAMP_SCALE_ID:
track_entry->set_timestamp_scale(TRY_READ(streamer.read_float()));
dbgln_if(MATROSKA_TRACE_DEBUG, "Read Track's TrackTimestampScale attribute: {}", track_entry->timestamp_scale());

View file

@ -26,6 +26,8 @@ public:
virtual DecoderErrorOr<CodecID> get_codec_id_for_track(Track track) = 0;
virtual DecoderErrorOr<ReadonlyBytes> get_codec_initialization_data_for_track(Track track) = 0;
// Returns the timestamp of the keyframe that was seeked to.
// The value is `Optional` to allow the demuxer to decide not to seek so that it can keep its position
// in the case that the timestamp is closer to the current time than the nearest keyframe.

View file

@ -34,7 +34,7 @@ static AVPixelFormat negotiate_output_format(AVCodecContext*, AVPixelFormat cons
return AV_PIX_FMT_NONE;
}
DecoderErrorOr<NonnullOwnPtr<FFmpegVideoDecoder>> FFmpegVideoDecoder::try_create(CodecID codec_id)
DecoderErrorOr<NonnullOwnPtr<FFmpegVideoDecoder>> FFmpegVideoDecoder::try_create(CodecID codec_id, ReadonlyBytes codec_initialization_data)
{
AVCodecContext* codec_context = nullptr;
AVPacket* packet = nullptr;
@ -60,6 +60,18 @@ DecoderErrorOr<NonnullOwnPtr<FFmpegVideoDecoder>> FFmpegVideoDecoder::try_create
codec_context->thread_count = static_cast<int>(min(Core::System::hardware_concurrency(), 4));
if (!codec_initialization_data.is_empty()) {
if (codec_initialization_data.size() > NumericLimits<int>::max())
return DecoderError::corrupted("Codec initialization data is too large"sv);
codec_context->extradata = static_cast<u8*>(av_malloc(codec_initialization_data.size() + AV_INPUT_BUFFER_PADDING_SIZE));
if (!codec_context->extradata)
return DecoderError::with_description(DecoderErrorCategory::Memory, "Failed to allocate codec initialization data buffer for FFmpeg codec"sv);
memcpy(codec_context->extradata, codec_initialization_data.data(), codec_initialization_data.size());
codec_context->extradata_size = static_cast<int>(codec_initialization_data.size());
}
if (avcodec_open2(codec_context, codec, nullptr) < 0)
return DecoderError::format(DecoderErrorCategory::Unknown, "Unknown error occurred when opening FFmpeg codec {}", codec_id);

View file

@ -15,7 +15,7 @@ namespace Media::FFmpeg {
class FFmpegVideoDecoder final : public VideoDecoder {
public:
static DecoderErrorOr<NonnullOwnPtr<FFmpegVideoDecoder>> try_create(CodecID);
static DecoderErrorOr<NonnullOwnPtr<FFmpegVideoDecoder>> try_create(CodecID, ReadonlyBytes codec_initialization_data);
FFmpegVideoDecoder(AVCodecContext* codec_context, AVPacket* packet, AVFrame* frame);
~FFmpegVideoDecoder();

View file

@ -701,7 +701,8 @@ DecoderErrorOr<NonnullOwnPtr<PlaybackManager>> PlaybackManager::create(NonnullOw
dbgln_if(PLAYBACK_MANAGER_DEBUG, "Selecting video track number {}", track.identifier());
auto codec_id = TRY(demuxer->get_codec_id_for_track(track));
NonnullOwnPtr<VideoDecoder> decoder = TRY(FFmpeg::FFmpegVideoDecoder::try_create(codec_id));
auto codec_initialization_data = TRY(demuxer->get_codec_initialization_data_for_track(track));
NonnullOwnPtr<VideoDecoder> decoder = TRY(FFmpeg::FFmpegVideoDecoder::try_create(codec_id, codec_initialization_data));
auto frame_queue = DECODER_TRY_ALLOC(VideoFrameQueue::create());
auto playback_manager = DECODER_TRY_ALLOC(try_make<PlaybackManager>(demuxer, track, move(decoder), move(frame_queue)));