diff --git a/Libraries/LibMedia/Audio/FFmpegLoader.cpp b/Libraries/LibMedia/Audio/FFmpegLoader.cpp index 4ce6768670c..587a662f72e 100644 --- a/Libraries/LibMedia/Audio/FFmpegLoader.cpp +++ b/Libraries/LibMedia/Audio/FFmpegLoader.cpp @@ -19,73 +19,7 @@ namespace Audio { static constexpr int BUFFER_MAX_PROBE_SIZE = 64 * KiB; -FFmpegIOContext::FFmpegIOContext(AVIOContext* avio_context) - : m_avio_context(avio_context) -{ -} - -FFmpegIOContext::~FFmpegIOContext() -{ - // NOTE: free the buffer inside the AVIO context, since it might be changed since its initial allocation - av_free(m_avio_context->buffer); - avio_context_free(&m_avio_context); -} - -ErrorOr> FFmpegIOContext::create(AK::SeekableStream& stream) -{ - auto* avio_buffer = av_malloc(PAGE_SIZE); - if (avio_buffer == nullptr) - return Error::from_string_literal("Failed to allocate AVIO buffer"); - - // This AVIOContext explains to avformat how to interact with our stream - auto* avio_context = avio_alloc_context( - static_cast(avio_buffer), - PAGE_SIZE, - 0, - &stream, - [](void* opaque, u8* buffer, int size) -> int { - auto& stream = *static_cast(opaque); - AK::Bytes buffer_bytes { buffer, AK::min(size, PAGE_SIZE) }; - auto read_bytes_or_error = stream.read_some(buffer_bytes); - if (read_bytes_or_error.is_error()) { - if (read_bytes_or_error.error().code() == EOF) - return AVERROR_EOF; - return AVERROR_UNKNOWN; - } - int number_of_bytes_read = read_bytes_or_error.value().size(); - if (number_of_bytes_read == 0) - return AVERROR_EOF; - return number_of_bytes_read; - }, - nullptr, - [](void* opaque, int64_t offset, int whence) -> int64_t { - whence &= ~AVSEEK_FORCE; - - auto& stream = *static_cast(opaque); - if (whence == AVSEEK_SIZE) - return static_cast(stream.size().value()); - - auto seek_mode_from_whence = [](int origin) -> SeekMode { - if (origin == SEEK_CUR) - return SeekMode::FromCurrentPosition; - if (origin == SEEK_END) - return SeekMode::FromEndPosition; - return SeekMode::SetPosition; - }; - auto offset_or_error = stream.seek(offset, seek_mode_from_whence(whence)); - if (offset_or_error.is_error()) - return -EIO; - return 0; - }); - if (avio_context == nullptr) { - av_free(avio_buffer); - return Error::from_string_literal("Failed to allocate AVIO context"); - } - - return make(avio_context); -} - -FFmpegLoaderPlugin::FFmpegLoaderPlugin(NonnullOwnPtr stream, NonnullOwnPtr io_context) +FFmpegLoaderPlugin::FFmpegLoaderPlugin(NonnullOwnPtr stream, NonnullOwnPtr io_context) : LoaderPlugin(move(stream)) , m_io_context(move(io_context)) { @@ -105,7 +39,7 @@ FFmpegLoaderPlugin::~FFmpegLoaderPlugin() ErrorOr> FFmpegLoaderPlugin::create(NonnullOwnPtr stream) { - auto io_context = TRY(FFmpegIOContext::create(*stream)); + auto io_context = TRY(Media::FFmpeg::FFmpegIOContext::create(*stream)); auto loader = make(move(stream), move(io_context)); TRY(loader->initialize()); return loader; @@ -180,7 +114,7 @@ double FFmpegLoaderPlugin::time_base() const bool FFmpegLoaderPlugin::sniff(SeekableStream& stream) { - auto io_context = MUST(FFmpegIOContext::create(stream)); + auto io_context = MUST(Media::FFmpeg::FFmpegIOContext::create(stream)); #ifdef USE_CONSTIFIED_POINTERS AVInputFormat const* detected_format {}; #else diff --git a/Libraries/LibMedia/Audio/FFmpegLoader.h b/Libraries/LibMedia/Audio/FFmpegLoader.h index 51ca96d6d5e..b00878481dc 100644 --- a/Libraries/LibMedia/Audio/FFmpegLoader.h +++ b/Libraries/LibMedia/Audio/FFmpegLoader.h @@ -9,6 +9,7 @@ #include "Loader.h" #include #include +#include extern "C" { #include @@ -17,22 +18,9 @@ extern "C" { namespace Audio { -class FFmpegIOContext { -public: - explicit FFmpegIOContext(AVIOContext*); - ~FFmpegIOContext(); - - static ErrorOr> create(AK::SeekableStream& stream); - - AVIOContext* avio_context() const { return m_avio_context; } - -private: - AVIOContext* m_avio_context { nullptr }; -}; - class FFmpegLoaderPlugin : public LoaderPlugin { public: - explicit FFmpegLoaderPlugin(NonnullOwnPtr, NonnullOwnPtr); + explicit FFmpegLoaderPlugin(NonnullOwnPtr, NonnullOwnPtr); virtual ~FFmpegLoaderPlugin(); static bool sniff(SeekableStream& stream); @@ -58,7 +46,7 @@ private: AVCodecContext* m_codec_context { nullptr }; AVFormatContext* m_format_context { nullptr }; AVFrame* m_frame { nullptr }; - NonnullOwnPtr m_io_context; + NonnullOwnPtr m_io_context; int m_loaded_samples { 0 }; AVPacket* m_packet { nullptr }; int m_total_samples { 0 }; diff --git a/Libraries/LibMedia/CMakeLists.txt b/Libraries/LibMedia/CMakeLists.txt index a2a667b8827..f10a4c401b3 100644 --- a/Libraries/LibMedia/CMakeLists.txt +++ b/Libraries/LibMedia/CMakeLists.txt @@ -22,6 +22,7 @@ target_link_libraries(LibMedia PRIVATE LibCore LibCrypto LibRIFF LibIPC LibGfx L if (NOT ANDROID) target_sources(LibMedia PRIVATE Audio/FFmpegLoader.cpp + FFmpeg/FFmpegIOContext.cpp FFmpeg/FFmpegVideoDecoder.cpp ) target_link_libraries(LibMedia PRIVATE PkgConfig::AVCODEC PkgConfig::AVFORMAT PkgConfig::AVUTIL) diff --git a/Libraries/LibMedia/FFmpeg/FFmpegIOContext.cpp b/Libraries/LibMedia/FFmpeg/FFmpegIOContext.cpp new file mode 100644 index 00000000000..6bd05964a9f --- /dev/null +++ b/Libraries/LibMedia/FFmpeg/FFmpegIOContext.cpp @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2024, Jelle Raaijmakers + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace Media::FFmpeg { + +FFmpegIOContext::FFmpegIOContext(AVIOContext* avio_context) + : m_avio_context(avio_context) +{ +} + +FFmpegIOContext::~FFmpegIOContext() +{ + // NOTE: free the buffer inside the AVIO context, since it might be changed since its initial allocation + av_free(m_avio_context->buffer); + avio_context_free(&m_avio_context); +} + +ErrorOr> FFmpegIOContext::create(AK::SeekableStream& stream) +{ + auto* avio_buffer = av_malloc(PAGE_SIZE); + if (avio_buffer == nullptr) + return Error::from_string_literal("Failed to allocate AVIO buffer"); + + // This AVIOContext explains to avformat how to interact with our stream + auto* avio_context = avio_alloc_context( + static_cast(avio_buffer), + PAGE_SIZE, + 0, + &stream, + [](void* opaque, u8* buffer, int size) -> int { + auto& stream = *static_cast(opaque); + AK::Bytes buffer_bytes { buffer, AK::min(size, PAGE_SIZE) }; + auto read_bytes_or_error = stream.read_some(buffer_bytes); + if (read_bytes_or_error.is_error()) { + if (read_bytes_or_error.error().code() == EOF) + return AVERROR_EOF; + return AVERROR_UNKNOWN; + } + int number_of_bytes_read = read_bytes_or_error.value().size(); + if (number_of_bytes_read == 0) + return AVERROR_EOF; + return number_of_bytes_read; + }, + nullptr, + [](void* opaque, int64_t offset, int whence) -> int64_t { + whence &= ~AVSEEK_FORCE; + + auto& stream = *static_cast(opaque); + if (whence == AVSEEK_SIZE) + return static_cast(stream.size().value()); + + auto seek_mode_from_whence = [](int origin) -> SeekMode { + if (origin == SEEK_CUR) + return SeekMode::FromCurrentPosition; + if (origin == SEEK_END) + return SeekMode::FromEndPosition; + return SeekMode::SetPosition; + }; + auto offset_or_error = stream.seek(offset, seek_mode_from_whence(whence)); + if (offset_or_error.is_error()) + return -EIO; + return 0; + }); + if (avio_context == nullptr) { + av_free(avio_buffer); + return Error::from_string_literal("Failed to allocate AVIO context"); + } + + return make(avio_context); +} + +} diff --git a/Libraries/LibMedia/FFmpeg/FFmpegIOContext.h b/Libraries/LibMedia/FFmpeg/FFmpegIOContext.h new file mode 100644 index 00000000000..a407b53726e --- /dev/null +++ b/Libraries/LibMedia/FFmpeg/FFmpegIOContext.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024, Jelle Raaijmakers + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +extern "C" { +#include +#include +} + +namespace Media::FFmpeg { + +class FFmpegIOContext { +public: + explicit FFmpegIOContext(AVIOContext*); + ~FFmpegIOContext(); + + static ErrorOr> create(AK::SeekableStream& stream); + + AVIOContext* avio_context() const { return m_avio_context; } + +private: + AVIOContext* m_avio_context { nullptr }; +}; + +}