/* * Copyright (c) 2025, Luke Wilde * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include namespace Media::FFmpeg { FFmpegDemuxer::FFmpegDemuxer(NonnullOwnPtr stream, NonnullOwnPtr io_context) : m_stream(move(stream)) , m_io_context(move(io_context)) { } FFmpegDemuxer::~FFmpegDemuxer() { if (m_packet != nullptr) av_packet_free(&m_packet); if (m_codec_context != nullptr) avcodec_free_context(&m_codec_context); if (m_format_context != nullptr) avformat_close_input(&m_format_context); } ErrorOr> FFmpegDemuxer::create(NonnullOwnPtr stream) { auto io_context = TRY(Media::FFmpeg::FFmpegIOContext::create(*stream)); auto demuxer = make(move(stream), move(io_context)); // Open the container demuxer->m_format_context = avformat_alloc_context(); if (demuxer->m_format_context == nullptr) return Error::from_string_literal("Failed to allocate format context"); demuxer->m_format_context->pb = demuxer->m_io_context->avio_context(); if (avformat_open_input(&demuxer->m_format_context, nullptr, nullptr, nullptr) < 0) return Error::from_string_literal("Failed to open input for format parsing"); // Read stream info; doing this is required for headerless formats like MPEG if (avformat_find_stream_info(demuxer->m_format_context, nullptr) < 0) return Error::from_string_literal("Failed to find stream info"); demuxer->m_packet = av_packet_alloc(); if (demuxer->m_packet == nullptr) return Error::from_string_literal("Failed to allocate packet"); return demuxer; } DecoderErrorOr FFmpegDemuxer::duration_of_track_in_milliseconds(Track const& track) { VERIFY(track.identifier() < m_format_context->nb_streams); auto* stream = m_format_context->streams[track.identifier()]; if (stream->duration >= 0) { auto time_base = av_q2d(stream->time_base); double duration_in_milliseconds = static_cast(stream->duration) * time_base * 1000.0; return AK::Duration::from_milliseconds(AK::round_to(duration_in_milliseconds)); } // If the stream doesn't specify the duration, fallback to what the container says the duration is. // If the container doesn't know the duration, then we're out of luck. Return an error. if (m_format_context->duration < 0) return DecoderError::format(DecoderErrorCategory::Unknown, "Negative stream duration"); double duration_in_milliseconds = (static_cast(m_format_context->duration) / AV_TIME_BASE) * 1000.0; return AK::Duration::from_milliseconds(AK::round_to(duration_in_milliseconds)); } DecoderErrorOr> FFmpegDemuxer::get_tracks_for_type(TrackType type) { AVMediaType media_type; switch (type) { case TrackType::Video: media_type = AVMediaType::AVMEDIA_TYPE_VIDEO; break; case TrackType::Audio: media_type = AVMediaType::AVMEDIA_TYPE_AUDIO; break; case TrackType::Subtitles: media_type = AVMediaType::AVMEDIA_TYPE_SUBTITLE; break; } // Find the best stream to play within the container int best_stream_index = av_find_best_stream(m_format_context, media_type, -1, -1, nullptr, 0); if (best_stream_index == AVERROR_STREAM_NOT_FOUND) return DecoderError::format(DecoderErrorCategory::Unknown, "No stream for given type found in container"); if (best_stream_index == AVERROR_DECODER_NOT_FOUND) return DecoderError::format(DecoderErrorCategory::Unknown, "No suitable decoder found for stream"); if (best_stream_index < 0) return DecoderError::format(DecoderErrorCategory::Unknown, "Failed to find a stream for the given type"); auto* stream = m_format_context->streams[best_stream_index]; Track track(type, best_stream_index); if (type == TrackType::Video) { track.set_video_data({ .duration = TRY(duration_of_track_in_milliseconds(track)), .pixel_width = static_cast(stream->codecpar->width), .pixel_height = static_cast(stream->codecpar->height), }); } Vector tracks; tracks.append(move(track)); return tracks; } DecoderErrorOr> FFmpegDemuxer::seek_to_most_recent_keyframe(Track track, AK::Duration timestamp, Optional earliest_available_sample) { // FIXME: What do we do with this here? (void)earliest_available_sample; VERIFY(track.identifier() < m_format_context->nb_streams); auto* stream = m_format_context->streams[track.identifier()]; auto time_base = av_q2d(stream->time_base); auto time_in_seconds = static_cast(timestamp.to_milliseconds()) / 1000.0 / time_base; auto sample_timestamp = AK::round_to(time_in_seconds); if (av_seek_frame(m_format_context, stream->index, sample_timestamp, AVSEEK_FLAG_BACKWARD) < 0) return DecoderError::format(DecoderErrorCategory::Unknown, "Failed to seek"); return timestamp; } DecoderErrorOr FFmpegDemuxer::duration(Track track) { return duration_of_track_in_milliseconds(track); } DecoderErrorOr FFmpegDemuxer::get_codec_id_for_track(Track track) { VERIFY(track.identifier() < m_format_context->nb_streams); auto* stream = m_format_context->streams[track.identifier()]; return media_codec_id_from_ffmpeg_codec_id(stream->codecpar->codec_id); } DecoderErrorOr FFmpegDemuxer::get_codec_initialization_data_for_track(Track track) { VERIFY(track.identifier() < m_format_context->nb_streams); auto* stream = m_format_context->streams[track.identifier()]; return ReadonlyBytes { stream->codecpar->extradata, static_cast(stream->codecpar->extradata_size) }; } DecoderErrorOr FFmpegDemuxer::get_next_sample_for_track(Track track) { VERIFY(track.identifier() < m_format_context->nb_streams); auto* stream = m_format_context->streams[track.identifier()]; for (;;) { auto read_frame_error = av_read_frame(m_format_context, m_packet); if (read_frame_error < 0) { if (read_frame_error == AVERROR_EOF) return DecoderError::format(DecoderErrorCategory::EndOfStream, "End of stream"); return DecoderError::format(DecoderErrorCategory::Unknown, "Failed to read frame"); } if (m_packet->stream_index != stream->index) { av_packet_unref(m_packet); continue; } auto color_primaries = static_cast(stream->codecpar->color_primaries); auto transfer_characteristics = static_cast(stream->codecpar->color_trc); auto matrix_coefficients = static_cast(stream->codecpar->color_space); auto color_range = [stream] { switch (stream->codecpar->color_range) { case AVColorRange::AVCOL_RANGE_MPEG: return VideoFullRangeFlag::Studio; case AVColorRange::AVCOL_RANGE_JPEG: return VideoFullRangeFlag::Full; default: return VideoFullRangeFlag::Unspecified; } }(); auto time_base = av_q2d(stream->time_base); double timestamp_in_milliseconds = static_cast(m_packet->pts) * time_base * 1000.0; // Copy the packet data so that we have a permanent reference to it whilst the Sample is alive, which allows us // to wipe the packet afterwards. auto packet_data = DECODER_TRY_ALLOC(ByteBuffer::copy(m_packet->data, m_packet->size)); auto sample = Sample( AK::Duration::from_milliseconds(AK::round_to(timestamp_in_milliseconds)), move(packet_data), VideoSampleData(CodingIndependentCodePoints(color_primaries, transfer_characteristics, matrix_coefficients, color_range))); // Wipe the packet now that the data is safe. av_packet_unref(m_packet); return sample; } } }