ffmpeg_bridge: new shared library isolating all FFmpeg access behind a pure C API
The library lives outside of app/ and is the only component that includes FFmpeg headers or links FFmpeg libraries. All objects (frames, packets, decoder/encoder instances, scalers, resamplers, audio filter graphs) are identified by opaque handles and never leave the library. Public surface (include/ffmpeg_bridge/ffmpeg_bridge.h): - FBFrame/FBPacket: AVFrame/AVPacket wrappers with field accessors - FBDecoder: demux+decode instance with hwaccel fallback logic - FBProbe: file probing, per-stream details, subtitle reading - FBScaler/FBResampler/FBAudioGraph: swscale/swresample/avfilter wrappers - FBEncoder: full export encoder (video filter graph, audio resampling, subtitles) ported from FFmpegEncoder - FB_* constants mirror AV_* values, verified by static_asserts against the real FFmpeg headers inside the library Two small bugs in the ported encoder were fixed: the resampler is now properly freed with swr_free() (was re-initialized with swr_init() and leaked), and codec option dictionaries are freed after avcodec_open2().
This commit is contained in:
@@ -223,6 +223,10 @@ list(APPEND OLIVE_LIBRARIES
|
||||
FFMPEG::swresample
|
||||
)
|
||||
|
||||
# FFmpeg isolation: all FFmpeg access is being moved behind this shared
|
||||
# library's pure C API. Built early so the app can link it.
|
||||
add_subdirectory(ffmpeg_bridge)
|
||||
|
||||
# Detect FFmpeg pixel formats that may not exist in all versions
|
||||
include(CheckCXXSourceCompiles)
|
||||
set(CMAKE_REQUIRED_INCLUDES ${FFMPEG_INCLUDE_DIRS})
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 Olive Team
|
||||
# Modifications Copyright (C) 2025 mikesolar
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# ffmpeg_bridge: a self-contained shared library that isolates all FFmpeg
|
||||
# access behind a pure C API (see include/ffmpeg_bridge/ffmpeg_bridge.h).
|
||||
# The editor and the render worker link only against this library.
|
||||
|
||||
find_package(FFMPEG 6.0 REQUIRED
|
||||
COMPONENTS
|
||||
avutil
|
||||
avcodec
|
||||
avformat
|
||||
avfilter
|
||||
swscale
|
||||
swresample
|
||||
)
|
||||
|
||||
add_library(ffmpeg_bridge SHARED
|
||||
src/audiograph.cpp
|
||||
src/decoder.cpp
|
||||
src/encoder.cpp
|
||||
src/frame.cpp
|
||||
src/packet.cpp
|
||||
src/probe.cpp
|
||||
src/sws.cpp
|
||||
src/swr.cpp
|
||||
src/utils.cpp
|
||||
src/internal.h
|
||||
include/ffmpeg_bridge/ffmpeg_bridge.h
|
||||
)
|
||||
|
||||
target_compile_features(ffmpeg_bridge PRIVATE cxx_std_17)
|
||||
|
||||
target_include_directories(ffmpeg_bridge
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||
PRIVATE
|
||||
${FFMPEG_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
target_link_libraries(ffmpeg_bridge
|
||||
PRIVATE
|
||||
FFMPEG::avutil
|
||||
FFMPEG::avcodec
|
||||
FFMPEG::avformat
|
||||
FFMPEG::avfilter
|
||||
FFMPEG::swscale
|
||||
FFMPEG::swresample
|
||||
)
|
||||
|
||||
# Static FFmpeg (e.g. the Linux CI build) needs system libs for
|
||||
# libavcodec/libavformat.
|
||||
find_package(ZLIB REQUIRED)
|
||||
target_link_libraries(ffmpeg_bridge PRIVATE ZLIB::ZLIB)
|
||||
|
||||
find_package(BZip2 REQUIRED)
|
||||
target_link_libraries(ffmpeg_bridge PRIVATE BZip2::BZip2)
|
||||
|
||||
find_package(LibLZMA)
|
||||
if (LIBLZMA_FOUND)
|
||||
target_link_libraries(ffmpeg_bridge PRIVATE ${LIBLZMA_LIBRARIES})
|
||||
endif()
|
||||
|
||||
target_compile_definitions(ffmpeg_bridge PRIVATE FFMPEG_BRIDGE_BUILD)
|
||||
|
||||
# Only export the FB_API functions
|
||||
set_target_properties(ffmpeg_bridge PROPERTIES
|
||||
C_VISIBILITY_PRESET hidden
|
||||
CXX_VISIBILITY_PRESET hidden
|
||||
VISIBILITY_INLINES_HIDDEN ON
|
||||
)
|
||||
|
||||
# The shared library lives in its own directory outside of app/
|
||||
set_target_properties(ffmpeg_bridge PROPERTIES
|
||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/ffmpeg_bridge/bin"
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/ffmpeg_bridge/bin"
|
||||
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/ffmpeg_bridge/lib"
|
||||
)
|
||||
|
||||
if (WIN32)
|
||||
set_target_properties(ffmpeg_bridge PROPERTIES PREFIX "")
|
||||
endif ()
|
||||
|
||||
install(TARGETS ffmpeg_bridge
|
||||
RUNTIME DESTINATION ffmpeg_bridge/bin
|
||||
LIBRARY DESTINATION ffmpeg_bridge/bin
|
||||
ARCHIVE DESTINATION ffmpeg_bridge/lib
|
||||
)
|
||||
@@ -0,0 +1,593 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef FFMPEG_BRIDGE_H
|
||||
#define FFMPEG_BRIDGE_H
|
||||
|
||||
/**
|
||||
* ffmpeg_bridge - pure C API isolating all FFmpeg access from the editor.
|
||||
*
|
||||
* All objects (frames, packets, decoders, encoders, scalers, resamplers,
|
||||
* audio graphs) are identified by opaque handles and always live inside the
|
||||
* shared library; callers never see or dereference an FFmpeg structure.
|
||||
*
|
||||
* Error convention: functions returning int return 0 (or a non-negative
|
||||
* value) on success and a negative error code on failure. Negative codes are
|
||||
* FFmpeg error codes and can be converted to text with fb_error_string().
|
||||
* End-of-file is reported as FB_ERROR_EOF.
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#if defined(FFMPEG_BRIDGE_BUILD)
|
||||
#define FB_API __declspec(dllexport)
|
||||
#else
|
||||
#define FB_API __declspec(dllimport)
|
||||
#endif
|
||||
#else
|
||||
#define FB_API __attribute__((visibility("default")))
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* Constants */
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
/** Pass-through of AVERROR_EOF (verified by static_assert in the library). */
|
||||
#define FB_ERROR_EOF (-541478725)
|
||||
|
||||
/** Pass-through of AV_NOPTS_VALUE. */
|
||||
#define FB_NOPTS_VALUE INT64_MIN
|
||||
|
||||
/** Pass-through of AV_TIME_BASE. */
|
||||
#define FB_TIME_BASE 1000000
|
||||
|
||||
/** Scaler algorithm flags (values mirror libswscale). */
|
||||
#define FB_SCALER_POINT 0x10
|
||||
|
||||
/**
|
||||
* Pixel formats. Values deliberately mirror AVPixelFormat so that the library
|
||||
* can use them directly; every value is static_assert'ed against the real
|
||||
* FFmpeg headers inside the library. Callers must treat them as opaque.
|
||||
*/
|
||||
typedef enum FBPixelFormat {
|
||||
FB_PIX_FMT_NONE = -1,
|
||||
FB_PIX_FMT_YUV420P = 0,
|
||||
FB_PIX_FMT_RGB24 = 2,
|
||||
FB_PIX_FMT_YUV422P = 4,
|
||||
FB_PIX_FMT_YUV444P = 5,
|
||||
FB_PIX_FMT_YUV410P = 6,
|
||||
FB_PIX_FMT_YUV411P = 7,
|
||||
FB_PIX_FMT_GRAY8 = 8,
|
||||
FB_PIX_FMT_YUVJ420P = 12,
|
||||
FB_PIX_FMT_YUVJ422P = 13,
|
||||
FB_PIX_FMT_YUVJ444P = 14,
|
||||
FB_PIX_FMT_RGBA = 26,
|
||||
FB_PIX_FMT_GRAY16LE = 30,
|
||||
FB_PIX_FMT_YUVJ440P = 32,
|
||||
FB_PIX_FMT_RGB48LE = 35,
|
||||
FB_PIX_FMT_YUV420P10LE = 62,
|
||||
FB_PIX_FMT_YUV422P10LE = 64,
|
||||
FB_PIX_FMT_YUV444P10LE = 68,
|
||||
FB_PIX_FMT_RGBA64LE = 105,
|
||||
FB_PIX_FMT_YUV420P12LE = 123,
|
||||
FB_PIX_FMT_YUV422P12LE = 127,
|
||||
FB_PIX_FMT_YUV444P12LE = 131,
|
||||
FB_PIX_FMT_YUVJ411P = 138,
|
||||
FB_PIX_FMT_GRAYF32LE = 183,
|
||||
FB_PIX_FMT_RGBAF16LE = 207,
|
||||
FB_PIX_FMT_RGBF32LE = 218,
|
||||
FB_PIX_FMT_RGBAF32LE = 220,
|
||||
FB_PIX_FMT_RGBF16LE = 234,
|
||||
FB_PIX_FMT_GRAYF16LE = 248
|
||||
} FBPixelFormat;
|
||||
|
||||
/**
|
||||
* Sample formats. Values mirror AVSampleFormat (static_assert'ed).
|
||||
*/
|
||||
typedef enum FBSampleFormat {
|
||||
FB_SAMPLE_FMT_NONE = -1,
|
||||
FB_SAMPLE_FMT_U8 = 0,
|
||||
FB_SAMPLE_FMT_S16 = 1,
|
||||
FB_SAMPLE_FMT_S32 = 2,
|
||||
FB_SAMPLE_FMT_FLT = 3,
|
||||
FB_SAMPLE_FMT_DBL = 4,
|
||||
FB_SAMPLE_FMT_U8P = 5,
|
||||
FB_SAMPLE_FMT_S16P = 6,
|
||||
FB_SAMPLE_FMT_S32P = 7,
|
||||
FB_SAMPLE_FMT_FLTP = 8,
|
||||
FB_SAMPLE_FMT_DBLP = 9,
|
||||
FB_SAMPLE_FMT_S64 = 10,
|
||||
FB_SAMPLE_FMT_S64P = 11
|
||||
} FBSampleFormat;
|
||||
|
||||
/** Color ranges. Values mirror AVColorRange (static_assert'ed). */
|
||||
typedef enum FBColorRange {
|
||||
FB_COLOR_RANGE_UNSPEC = 0,
|
||||
FB_COLOR_RANGE_MPEG = 1,
|
||||
FB_COLOR_RANGE_JPEG = 2
|
||||
} FBColorRange;
|
||||
|
||||
/** Color spaces. Values mirror AVColorSpace (static_assert'ed). */
|
||||
typedef enum FBColorSpace {
|
||||
FB_COL_SPC_RGB = 0,
|
||||
FB_COL_SPC_BT709 = 1,
|
||||
FB_COL_SPC_UNSPEC = 2,
|
||||
FB_COL_SPC_FCC = 4,
|
||||
FB_COL_SPC_BT470BG = 5,
|
||||
FB_COL_SPC_SMPTE170M = 6,
|
||||
FB_COL_SPC_SMPTE240M = 7,
|
||||
FB_COL_SPC_BT2020_NCL = 9
|
||||
} FBColorSpace;
|
||||
|
||||
/** Media types. Values mirror AVMediaType (static_assert'ed). */
|
||||
typedef enum FBMediaType {
|
||||
FB_MEDIA_TYPE_VIDEO = 0,
|
||||
FB_MEDIA_TYPE_AUDIO = 1,
|
||||
FB_MEDIA_TYPE_DATA = 2,
|
||||
FB_MEDIA_TYPE_SUBTITLE = 3
|
||||
} FBMediaType;
|
||||
|
||||
/** Field orders. Values mirror AVFieldOrder (static_assert'ed). */
|
||||
typedef enum FBFieldOrder {
|
||||
FB_FIELD_ORDER_UNKNOWN = 0,
|
||||
FB_FIELD_ORDER_PROGRESSIVE = 1,
|
||||
FB_FIELD_ORDER_TT = 2,
|
||||
FB_FIELD_ORDER_BB = 3,
|
||||
FB_FIELD_ORDER_TB = 4,
|
||||
FB_FIELD_ORDER_BT = 5
|
||||
} FBFieldOrder;
|
||||
|
||||
/**
|
||||
* Channel layout masks. Values mirror AV_CH_LAYOUT_* (static_assert'ed).
|
||||
*/
|
||||
#define FB_CH_LAYOUT_MONO ((uint64_t)0x4)
|
||||
#define FB_CH_LAYOUT_STEREO ((uint64_t)0x3)
|
||||
#define FB_CH_LAYOUT_2_1 ((uint64_t)0x103)
|
||||
#define FB_CH_LAYOUT_5POINT1 ((uint64_t)0x60F)
|
||||
#define FB_CH_LAYOUT_7POINT1 ((uint64_t)0x63F)
|
||||
|
||||
/**
|
||||
* Codecs the encoder supports. Opaque ordering; mapped explicitly by the
|
||||
* caller's adapter layer (no value relationship with any app-side enum).
|
||||
*/
|
||||
typedef enum FBCodec {
|
||||
FB_CODEC_NONE = -1,
|
||||
FB_CODEC_H264 = 0,
|
||||
FB_CODEC_H264RGB,
|
||||
FB_CODEC_DNXHD,
|
||||
FB_CODEC_PRORES,
|
||||
FB_CODEC_CINEFORM,
|
||||
FB_CODEC_H265,
|
||||
FB_CODEC_VP9,
|
||||
FB_CODEC_AV1,
|
||||
FB_CODEC_OPENEXR,
|
||||
FB_CODEC_PNG,
|
||||
FB_CODEC_TIFF,
|
||||
FB_CODEC_MP2,
|
||||
FB_CODEC_MP3,
|
||||
FB_CODEC_AAC,
|
||||
FB_CODEC_PCM,
|
||||
FB_CODEC_FLAC,
|
||||
FB_CODEC_OPUS,
|
||||
FB_CODEC_VORBIS,
|
||||
FB_CODEC_SRT
|
||||
} FBCodec;
|
||||
|
||||
/** Cancellation callback: return non-zero to request cancellation. */
|
||||
typedef int (*FBCancelCallback)(void *userdata);
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* Opaque handles */
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
typedef struct FBFrame FBFrame;
|
||||
typedef struct FBPacket FBPacket;
|
||||
typedef struct FBDecoder FBDecoder;
|
||||
typedef struct FBProbe FBProbe;
|
||||
typedef struct FBEncoder FBEncoder;
|
||||
typedef struct FBScaler FBScaler;
|
||||
typedef struct FBResampler FBResampler;
|
||||
typedef struct FBAudioGraph FBAudioGraph;
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* Error strings / version */
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
FB_API void fb_error_string(int error_code, char *buffer, int buffer_size);
|
||||
FB_API const char *fb_version_string(void);
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* Pixel/sample format utilities */
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
FB_API const char *fb_pix_fmt_name(int pix_fmt);
|
||||
FB_API int fb_pix_fmt_from_name(const char *name);
|
||||
FB_API int fb_pix_fmt_bits_per_pixel(int pix_fmt);
|
||||
FB_API int fb_pix_fmt_has_alpha(int pix_fmt);
|
||||
FB_API int fb_pix_fmt_is_planar(int pix_fmt);
|
||||
/** Size in bytes of one sample component (1 for 8-bit formats, 2 for 9-16bit). */
|
||||
FB_API int fb_pix_fmt_component_size(int pix_fmt);
|
||||
|
||||
/**
|
||||
* Find the best pixel format from `list` for storing `pix_fmt` losslessly.
|
||||
* `list` is terminated by FB_PIX_FMT_NONE. Mirrors
|
||||
* avcodec_find_best_pix_fmt_of_list(list, fmt, has_alpha=1).
|
||||
*/
|
||||
FB_API int fb_find_best_pix_fmt_of_list(const int *list, int pix_fmt);
|
||||
|
||||
/** Number of channels in a channel layout mask. */
|
||||
FB_API int fb_channel_layout_get_channels(uint64_t mask);
|
||||
/** Default layout mask for a channel count (mirrors av_channel_layout_default). */
|
||||
FB_API uint64_t fb_channel_layout_default(int nb_channels);
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* Frame (AVFrame wrapper) */
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
FB_API FBFrame *fb_frame_alloc(void);
|
||||
FB_API void fb_frame_free(FBFrame **frame);
|
||||
/** Clear the frame's contents back to defaults (mirrors av_frame_unref). */
|
||||
FB_API void fb_frame_unref(FBFrame *frame);
|
||||
/** Allocate the frame's buffer(s) from its width/height/format fields. */
|
||||
FB_API int fb_frame_get_buffer(FBFrame *frame, int align);
|
||||
FB_API int fb_frame_copy_props(FBFrame *dst, const FBFrame *src);
|
||||
/** Transfer data between a hardware frame and a software frame. */
|
||||
FB_API int fb_frame_hw_transfer_data(FBFrame *dst, const FBFrame *src);
|
||||
FB_API int fb_frame_is_hw(const FBFrame *frame);
|
||||
|
||||
FB_API int fb_frame_get_width(const FBFrame *frame);
|
||||
FB_API void fb_frame_set_width(FBFrame *frame, int width);
|
||||
FB_API int fb_frame_get_height(const FBFrame *frame);
|
||||
FB_API void fb_frame_set_height(FBFrame *frame, int height);
|
||||
FB_API int fb_frame_get_format(const FBFrame *frame);
|
||||
FB_API void fb_frame_set_format(FBFrame *frame, int format);
|
||||
FB_API int64_t fb_frame_get_pts(const FBFrame *frame);
|
||||
FB_API void fb_frame_set_pts(FBFrame *frame, int64_t pts);
|
||||
FB_API int64_t fb_frame_get_best_effort_timestamp(const FBFrame *frame);
|
||||
FB_API int fb_frame_get_nb_samples(const FBFrame *frame);
|
||||
FB_API void fb_frame_set_nb_samples(FBFrame *frame, int nb_samples);
|
||||
FB_API int fb_frame_get_sample_rate(const FBFrame *frame);
|
||||
FB_API void fb_frame_set_sample_rate(FBFrame *frame, int sample_rate);
|
||||
FB_API int fb_frame_get_color_range(const FBFrame *frame);
|
||||
FB_API void fb_frame_set_color_range(FBFrame *frame, int color_range);
|
||||
FB_API int fb_frame_get_colorspace(const FBFrame *frame);
|
||||
FB_API void fb_frame_set_colorspace(FBFrame *frame, int colorspace);
|
||||
FB_API uint64_t fb_frame_get_channel_layout_mask(const FBFrame *frame);
|
||||
FB_API void fb_frame_set_channel_layout_mask(FBFrame *frame, uint64_t mask);
|
||||
|
||||
FB_API uint8_t *fb_frame_get_data(FBFrame *frame, int plane);
|
||||
FB_API const uint8_t *fb_frame_get_data_const(const FBFrame *frame, int plane);
|
||||
FB_API void fb_frame_set_data(FBFrame *frame, int plane, uint8_t *data);
|
||||
FB_API int fb_frame_get_linesize(const FBFrame *frame, int plane);
|
||||
FB_API void fb_frame_set_linesize(FBFrame *frame, int plane, int linesize);
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* Packet (AVPacket wrapper) */
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
FB_API FBPacket *fb_packet_alloc(void);
|
||||
FB_API void fb_packet_free(FBPacket **packet);
|
||||
FB_API void fb_packet_unref(FBPacket *packet);
|
||||
|
||||
FB_API int64_t fb_packet_get_pts(const FBPacket *packet);
|
||||
FB_API int64_t fb_packet_get_duration(const FBPacket *packet);
|
||||
FB_API int fb_packet_get_size(const FBPacket *packet);
|
||||
FB_API const uint8_t *fb_packet_get_data(const FBPacket *packet);
|
||||
FB_API int fb_packet_get_stream_index(const FBPacket *packet);
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* Stream info */
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
typedef struct FBStreamInfo {
|
||||
int index;
|
||||
int codec_type; /* FBMediaType */
|
||||
int codec_id; /* opaque FFmpeg codec id */
|
||||
int has_decoder; /* non-zero if a decoder exists for this stream */
|
||||
|
||||
int width;
|
||||
int height;
|
||||
int pixel_format; /* FBPixelFormat */
|
||||
int field_order; /* FBFieldOrder */
|
||||
int color_range; /* FBColorRange */
|
||||
|
||||
int sample_rate;
|
||||
int sample_format; /* FBSampleFormat */
|
||||
uint64_t channel_layout_mask; /* validated (never zero for valid audio) */
|
||||
|
||||
int64_t start_time;
|
||||
int64_t duration;
|
||||
int time_base_num;
|
||||
int time_base_den;
|
||||
int avg_frame_rate_num;
|
||||
int avg_frame_rate_den;
|
||||
} FBStreamInfo;
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* Decoder */
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
FB_API FBDecoder *fb_decoder_create(void);
|
||||
FB_API void fb_decoder_free(FBDecoder **decoder);
|
||||
|
||||
FB_API int fb_decoder_open(FBDecoder *decoder, const char *filename,
|
||||
int stream_index);
|
||||
FB_API void fb_decoder_close(FBDecoder *decoder);
|
||||
|
||||
/**
|
||||
* Retrieve the next decoded frame. Returns 0 on success, FB_ERROR_EOF at end
|
||||
* of stream, or another negative error code.
|
||||
*/
|
||||
FB_API int fb_decoder_get_frame(FBDecoder *decoder, FBPacket *packet,
|
||||
FBFrame *frame);
|
||||
/** Retrieve the next raw packet of the opened stream. Same return contract. */
|
||||
FB_API int fb_decoder_get_packet(FBDecoder *decoder, FBPacket *packet);
|
||||
FB_API void fb_decoder_seek(FBDecoder *decoder, int64_t timestamp);
|
||||
|
||||
FB_API int fb_decoder_get_stream_info(const FBDecoder *decoder,
|
||||
FBStreamInfo *out);
|
||||
FB_API int64_t fb_decoder_get_format_start_time(const FBDecoder *decoder);
|
||||
|
||||
FB_API int fb_decoder_guess_sample_aspect_ratio(const FBDecoder *decoder,
|
||||
FBFrame *frame, int *num,
|
||||
int *den);
|
||||
FB_API int fb_decoder_guess_frame_rate(const FBDecoder *decoder,
|
||||
FBFrame *frame, int *num, int *den);
|
||||
|
||||
FB_API int fb_decoder_hwaccel_enabled(const FBDecoder *decoder);
|
||||
FB_API int fb_decoder_hw_pix_fmt(const FBDecoder *decoder);
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* Probe */
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
FB_API FBProbe *fb_probe_create(void);
|
||||
FB_API void fb_probe_free(FBProbe **probe);
|
||||
|
||||
FB_API int fb_probe_open(FBProbe *probe, const char *filename);
|
||||
FB_API void fb_probe_close(FBProbe *probe);
|
||||
|
||||
FB_API int fb_probe_get_stream_count(const FBProbe *probe);
|
||||
FB_API int fb_probe_get_stream_info(const FBProbe *probe, int stream_index,
|
||||
FBStreamInfo *out);
|
||||
FB_API int64_t fb_probe_get_duration(const FBProbe *probe);
|
||||
FB_API int64_t fb_probe_get_start_time(const FBProbe *probe);
|
||||
FB_API int fb_probe_duration_from_bitrate(const FBProbe *probe);
|
||||
|
||||
/**
|
||||
* Read a metadata value from the format (stream_index = -1) or from a stream.
|
||||
* Returns 1 if found (buffer filled), 0 otherwise.
|
||||
*/
|
||||
FB_API int fb_probe_get_metadata(FBProbe *probe, int stream_index,
|
||||
const char *key, char *buffer,
|
||||
int buffer_size);
|
||||
|
||||
typedef struct FBVideoStreamDetails {
|
||||
int field_order; /* FBFieldOrder */
|
||||
int pixel_aspect_num;
|
||||
int pixel_aspect_den;
|
||||
int frame_rate_num;
|
||||
int frame_rate_den;
|
||||
int is_still;
|
||||
int64_t decoded_duration; /* last timestamp seen, FB_NOPTS_VALUE if none */
|
||||
} FBVideoStreamDetails;
|
||||
|
||||
/**
|
||||
* Open the file and decode the start of a video stream to determine
|
||||
* interlacing, aspect ratio, frame rate, whether it is a still image, and
|
||||
* (when `decode_full_duration` is non-zero, by decoding to the end) its true
|
||||
* duration.
|
||||
*/
|
||||
FB_API int fb_probe_video_stream_details(const char *filename, int stream_index,
|
||||
FBVideoStreamDetails *out,
|
||||
int decode_full_duration,
|
||||
FBCancelCallback cancel,
|
||||
void *cancel_userdata);
|
||||
|
||||
/** Decode an audio stream to its end to determine its true duration. */
|
||||
FB_API int fb_probe_audio_stream_duration(const char *filename,
|
||||
int stream_index,
|
||||
int64_t *out_duration,
|
||||
FBCancelCallback cancel,
|
||||
void *cancel_userdata);
|
||||
|
||||
/** Called once per subtitle packet. */
|
||||
typedef void (*FBSubtitleCallback)(int64_t pts, int64_t duration,
|
||||
const char *text, int text_size,
|
||||
void *userdata);
|
||||
|
||||
/** Read all subtitle packets of a stream (e.g. SRT) via callback. */
|
||||
FB_API int fb_probe_read_subtitle_stream(const char *filename, int stream_index,
|
||||
FBSubtitleCallback callback,
|
||||
void *userdata);
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* Scaler (libswscale wrapper) */
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
FB_API FBScaler *fb_scaler_create(int src_width, int src_height, int src_format,
|
||||
int dst_width, int dst_height, int dst_format,
|
||||
int flags);
|
||||
FB_API void fb_scaler_free(FBScaler **scaler);
|
||||
/**
|
||||
* Set colorspace details from an AVColorSpace value; the appropriate swscale
|
||||
* coefficient table is looked up internally. `jpeg_range` is non-zero for
|
||||
* full-range (JPEG) content.
|
||||
*/
|
||||
FB_API int fb_scaler_set_colorspace(FBScaler *scaler, int colorspace,
|
||||
int jpeg_range);
|
||||
FB_API int fb_scaler_scale_frame(FBScaler *scaler, FBFrame *dst,
|
||||
const FBFrame *src);
|
||||
FB_API int fb_scaler_scale_slices(FBScaler *scaler,
|
||||
const uint8_t *const *src_data,
|
||||
const int *src_linesize, int src_height,
|
||||
uint8_t *const *dst_data,
|
||||
const int *dst_linesize);
|
||||
|
||||
/**
|
||||
* YUV->RGB conversion coefficients for `colorspace`, normalized to [0,1].
|
||||
* Output order: crv, cbu, cgu, cgv.
|
||||
*/
|
||||
FB_API void fb_get_yuv_coefficients(int colorspace, double out[4]);
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* Resampler (libswresample wrapper) */
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
FB_API FBResampler *fb_resampler_create(uint64_t out_layout_mask, int out_format,
|
||||
int out_rate, uint64_t in_layout_mask,
|
||||
int in_format, int in_rate);
|
||||
FB_API void fb_resampler_free(FBResampler **resampler);
|
||||
FB_API int fb_resampler_get_out_samples(FBResampler *resampler,
|
||||
int in_samples);
|
||||
FB_API int fb_resampler_convert(FBResampler *resampler, uint8_t **out,
|
||||
int out_count, const uint8_t **in,
|
||||
int in_count);
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* Audio filter graph (abuffer/aformat/atempo/abuffersink wrapper) */
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
typedef struct FBAudioGraphConfig {
|
||||
int in_sample_rate;
|
||||
uint64_t in_channel_layout_mask; /* 0 = derive default from in_channels */
|
||||
int in_sample_format; /* FBSampleFormat (planar float in) */
|
||||
int in_channels;
|
||||
|
||||
int out_sample_rate;
|
||||
uint64_t out_channel_layout_mask; /* 0 = derive default from out_channels */
|
||||
int out_sample_format;
|
||||
int out_channels;
|
||||
int out_is_planar;
|
||||
|
||||
double tempo;
|
||||
} FBAudioGraphConfig;
|
||||
|
||||
FB_API FBAudioGraph *fb_audio_graph_create(const FBAudioGraphConfig *config);
|
||||
FB_API void fb_audio_graph_free(FBAudioGraph **graph);
|
||||
/** Push planar samples into the graph. channel_data == NULL flushes the graph. */
|
||||
FB_API int fb_audio_graph_push(FBAudioGraph *graph,
|
||||
const uint8_t *const *channel_data,
|
||||
int nb_samples);
|
||||
/** Pull converted samples. Returns 1 if a frame was produced, 0 if more input
|
||||
* is needed, or a negative error code. */
|
||||
FB_API int fb_audio_graph_pull(FBAudioGraph *graph, FBFrame *out_frame);
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* Encoder */
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
typedef struct FBEncoderConfig {
|
||||
const char *filename;
|
||||
|
||||
int video_enabled;
|
||||
int video_codec; /* FBCodec */
|
||||
int video_width;
|
||||
int video_height;
|
||||
int video_pixel_aspect_num;
|
||||
int video_pixel_aspect_den;
|
||||
int video_time_base_num; /* frame rate expressed as a time base */
|
||||
int video_time_base_den;
|
||||
int video_frame_rate_num;
|
||||
int video_frame_rate_den;
|
||||
const char *video_pix_fmt; /* encoder pixel format name, e.g. "yuv420p" */
|
||||
int video_src_pix_fmt; /* FBPixelFormat of frames passed to write_video_frame */
|
||||
int video_color_range; /* FBColorRange */
|
||||
int video_field_order; /* FBFieldOrder (PROGRESSIVE/TT/BB) */
|
||||
int64_t video_bit_rate;
|
||||
int64_t video_min_bit_rate;
|
||||
int64_t video_max_bit_rate;
|
||||
int64_t video_buffer_size;
|
||||
int video_threads;
|
||||
int video_color_srgb; /* non-zero: tag nclc as sRGB, else Rec.709 */
|
||||
const char **video_opt_keys;
|
||||
const char **video_opt_values;
|
||||
int video_opt_count;
|
||||
|
||||
int audio_enabled;
|
||||
int audio_codec; /* FBCodec */
|
||||
int audio_sample_rate;
|
||||
uint64_t audio_channel_layout_mask;
|
||||
int audio_sample_format; /* FBSampleFormat */
|
||||
int64_t audio_bit_rate;
|
||||
|
||||
int subtitles_enabled;
|
||||
int subtitle_codec; /* FBCodec */
|
||||
const uint8_t *subtitle_header;
|
||||
int subtitle_header_size;
|
||||
} FBEncoderConfig;
|
||||
|
||||
FB_API FBEncoder *fb_encoder_create(const FBEncoderConfig *config);
|
||||
FB_API void fb_encoder_free(FBEncoder **encoder);
|
||||
|
||||
FB_API int fb_encoder_open(FBEncoder *encoder);
|
||||
FB_API void fb_encoder_close(FBEncoder *encoder);
|
||||
|
||||
/**
|
||||
* Write one video frame of raw pixel data. `time_seconds` is the presentation
|
||||
* time in seconds. The data must remain valid until this call returns.
|
||||
*/
|
||||
FB_API int fb_encoder_write_video_frame(FBEncoder *encoder, int width,
|
||||
int height, int pix_fmt,
|
||||
const uint8_t *data, int linesize,
|
||||
double time_seconds);
|
||||
|
||||
/**
|
||||
* Write planar audio samples. `channel_data` has `channels` pointers, each
|
||||
* with `sample_count` samples of `sample_format`. Passing sample_count == 0
|
||||
* flushes the audio encoder.
|
||||
*/
|
||||
FB_API int fb_encoder_write_audio(FBEncoder *encoder,
|
||||
const uint8_t *const *channel_data,
|
||||
int channels, int sample_format,
|
||||
int sample_rate, uint64_t channel_layout_mask,
|
||||
int64_t sample_count);
|
||||
|
||||
FB_API int fb_encoder_write_subtitle(FBEncoder *encoder, const char *utf8_text,
|
||||
double in_seconds, double duration_seconds);
|
||||
|
||||
/** Last error message (empty string if none). Valid until the next call. */
|
||||
FB_API const char *fb_encoder_get_error(const FBEncoder *encoder);
|
||||
|
||||
/**
|
||||
* List the pixel formats an encoder codec supports.
|
||||
* Writes up to `max_names` names into `names`, returns the total count.
|
||||
*/
|
||||
FB_API int fb_encoder_codec_get_pixel_formats(int codec, const char **names,
|
||||
int max_names);
|
||||
/**
|
||||
* List the sample formats (FBSampleFormat) an encoder codec supports.
|
||||
* Writes up to `max_fmts` into `fmts`, returns the total count.
|
||||
*/
|
||||
FB_API int fb_encoder_codec_get_sample_formats(int codec, int *fmts,
|
||||
int max_fmts);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // FFMPEG_BRIDGE_H
|
||||
@@ -0,0 +1,244 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "internal.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
|
||||
struct FBAudioGraph {
|
||||
AVFilterGraph *graph = nullptr;
|
||||
AVFilterContext *buffersrc = nullptr;
|
||||
AVFilterContext *buffersink = nullptr;
|
||||
AVFrame *in_frame = nullptr;
|
||||
|
||||
int in_channels = 0;
|
||||
int in_sample_rate = 0;
|
||||
int in_sample_format = FB_SAMPLE_FMT_NONE;
|
||||
int64_t in_pts = 0;
|
||||
};
|
||||
|
||||
static AVFilterContext *CreateTempoFilter(AVFilterGraph *graph,
|
||||
AVFilterContext *link, double tempo)
|
||||
{
|
||||
char speed_param[20];
|
||||
snprintf(speed_param, sizeof(speed_param), "%f", tempo);
|
||||
|
||||
AVFilterContext *tempo_ctx = nullptr;
|
||||
if (avfilter_graph_create_filter(&tempo_ctx, avfilter_get_by_name("atempo"),
|
||||
"atempo", speed_param, nullptr, graph) >= 0 &&
|
||||
avfilter_link(link, 0, tempo_ctx, 0) == 0) {
|
||||
return tempo_ctx;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FBAudioGraph *fb_audio_graph_create(const FBAudioGraphConfig *config)
|
||||
{
|
||||
if (!config) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FBAudioGraph *g = new FBAudioGraph;
|
||||
|
||||
g->graph = avfilter_graph_alloc();
|
||||
if (!g->graph) {
|
||||
delete g;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AVChannelLayout in_layout, out_layout;
|
||||
fb::ChannelLayoutFromMask(&in_layout, config->in_channel_layout_mask,
|
||||
config->in_channels);
|
||||
fb::ChannelLayoutFromMask(&out_layout, config->out_channel_layout_mask,
|
||||
config->out_channels);
|
||||
|
||||
char filter_args[200];
|
||||
|
||||
// Create buffersrc (input)
|
||||
snprintf(filter_args, sizeof(filter_args),
|
||||
"time_base=1/%d:sample_rate=%d:sample_fmt=%d:channel_layout=0x%" PRIx64,
|
||||
config->in_sample_rate, config->in_sample_rate,
|
||||
config->in_sample_format, in_layout.u.mask);
|
||||
|
||||
int r = avfilter_graph_create_filter(&g->buffersrc,
|
||||
avfilter_get_by_name("abuffer"), "in",
|
||||
filter_args, nullptr, g->graph);
|
||||
if (r < 0) {
|
||||
av_channel_layout_uninit(&in_layout);
|
||||
av_channel_layout_uninit(&out_layout);
|
||||
fb_audio_graph_free(&g);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AVFilterContext *previous_filter = g->buffersrc;
|
||||
|
||||
// Create tempo filter chain: FFmpeg's atempo can only be set between 0.5
|
||||
// and 2.0, so out-of-range speeds must be daisychained.
|
||||
bool create_tempo = config->tempo != 1.0;
|
||||
if (create_tempo) {
|
||||
double base = (config->tempo > 1.0) ? 2.0 : 0.5;
|
||||
double speed_log = log(config->tempo) / log(base);
|
||||
int whole = int(floor(speed_log));
|
||||
speed_log -= whole;
|
||||
|
||||
for (int i = 0; i <= whole; i++) {
|
||||
double filter_tempo = (i == whole) ? pow(base, speed_log) : base;
|
||||
previous_filter =
|
||||
CreateTempoFilter(g->graph, previous_filter, filter_tempo);
|
||||
if (!previous_filter) {
|
||||
av_channel_layout_uninit(&in_layout);
|
||||
av_channel_layout_uninit(&out_layout);
|
||||
fb_audio_graph_free(&g);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create conversion filter if the parameters differ (or if the tempo
|
||||
// filter converted planar input to packed and planar output is desired)
|
||||
if (config->in_sample_rate != config->out_sample_rate ||
|
||||
av_channel_layout_compare(&in_layout, &out_layout) != 0 ||
|
||||
config->in_sample_format != config->out_sample_format ||
|
||||
(config->out_is_planar && create_tempo)) {
|
||||
snprintf(filter_args, sizeof(filter_args),
|
||||
"sample_fmts=%s:sample_rates=%d:channel_layouts=0x%" PRIx64,
|
||||
av_get_sample_fmt_name(
|
||||
static_cast<AVSampleFormat>(config->out_sample_format)),
|
||||
config->out_sample_rate, out_layout.u.mask);
|
||||
|
||||
AVFilterContext *c;
|
||||
r = avfilter_graph_create_filter(&c, avfilter_get_by_name("aformat"),
|
||||
"fmt", filter_args, nullptr, g->graph);
|
||||
if (r < 0 || avfilter_link(previous_filter, 0, c, 0) < 0) {
|
||||
av_channel_layout_uninit(&in_layout);
|
||||
av_channel_layout_uninit(&out_layout);
|
||||
fb_audio_graph_free(&g);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
previous_filter = c;
|
||||
}
|
||||
|
||||
// Create buffersink (output)
|
||||
r = avfilter_graph_create_filter(&g->buffersink,
|
||||
avfilter_get_by_name("abuffersink"), "out",
|
||||
nullptr, nullptr, g->graph);
|
||||
if (r < 0 || avfilter_link(previous_filter, 0, g->buffersink, 0) < 0 ||
|
||||
avfilter_graph_config(g->graph, nullptr) < 0) {
|
||||
av_channel_layout_uninit(&in_layout);
|
||||
av_channel_layout_uninit(&out_layout);
|
||||
fb_audio_graph_free(&g);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Allocate the input frame used for pushes
|
||||
g->in_frame = av_frame_alloc();
|
||||
if (!g->in_frame) {
|
||||
av_channel_layout_uninit(&in_layout);
|
||||
av_channel_layout_uninit(&out_layout);
|
||||
fb_audio_graph_free(&g);
|
||||
return nullptr;
|
||||
}
|
||||
g->in_frame->sample_rate = config->in_sample_rate;
|
||||
g->in_frame->format = config->in_sample_format;
|
||||
g->in_frame->ch_layout = in_layout;
|
||||
g->in_frame->pts = 0;
|
||||
|
||||
av_channel_layout_uninit(&out_layout);
|
||||
|
||||
g->in_channels = in_layout.nb_channels;
|
||||
g->in_sample_rate = config->in_sample_rate;
|
||||
g->in_sample_format = config->in_sample_format;
|
||||
|
||||
return g;
|
||||
}
|
||||
|
||||
void fb_audio_graph_free(FBAudioGraph **graph)
|
||||
{
|
||||
if (graph && *graph) {
|
||||
FBAudioGraph *g = *graph;
|
||||
if (g->graph) {
|
||||
avfilter_graph_free(&g->graph);
|
||||
}
|
||||
if (g->in_frame) {
|
||||
// in_frame owns a reference to its ch_layout (copied at create)
|
||||
av_channel_layout_uninit(&g->in_frame->ch_layout);
|
||||
av_frame_free(&g->in_frame);
|
||||
}
|
||||
delete g;
|
||||
*graph = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
int fb_audio_graph_push(FBAudioGraph *graph,
|
||||
const uint8_t *const *channel_data, int nb_samples)
|
||||
{
|
||||
if (!graph) {
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
if (channel_data && nb_samples > 0) {
|
||||
int bytes_per_sample =
|
||||
av_get_bytes_per_sample(static_cast<AVSampleFormat>(graph->in_sample_format));
|
||||
|
||||
graph->in_frame->nb_samples = nb_samples;
|
||||
for (int i = 0; i < graph->in_channels; i++) {
|
||||
graph->in_frame->data[i] =
|
||||
const_cast<uint8_t *>(channel_data[i]);
|
||||
graph->in_frame->linesize[i] = bytes_per_sample * nb_samples;
|
||||
}
|
||||
|
||||
int r = av_buffersrc_add_frame_flags(graph->buffersrc, graph->in_frame,
|
||||
AV_BUFFERSRC_FLAG_KEEP_REF);
|
||||
if (r < 0) {
|
||||
return r;
|
||||
}
|
||||
} else {
|
||||
// Flush
|
||||
int r = av_buffersrc_add_frame_flags(graph->buffersrc, nullptr,
|
||||
AV_BUFFERSRC_FLAG_KEEP_REF);
|
||||
if (r < 0) {
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fb_audio_graph_pull(FBAudioGraph *graph, FBFrame *out_frame)
|
||||
{
|
||||
if (!graph || !out_frame) {
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
fb_frame_unref(out_frame);
|
||||
int r = av_buffersink_get_frame(graph->buffersink, out_frame->frame);
|
||||
if (r < 0) {
|
||||
if (r == AVERROR(EAGAIN)) {
|
||||
return 0;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1,495 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "internal.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <initializer_list>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
constexpr int64_t kAnalyzeDurationUs = 5000000;
|
||||
constexpr int64_t kProbeSizeBytes = 20000000;
|
||||
|
||||
void ApplyFormatOpenOptions(AVDictionary **opts)
|
||||
{
|
||||
av_dict_set_int(opts, "analyzeduration", kAnalyzeDurationUs, 0);
|
||||
av_dict_set_int(opts, "probesize", kProbeSizeBytes, 0);
|
||||
}
|
||||
|
||||
void TuneFormatContext(AVFormatContext *ctx)
|
||||
{
|
||||
if (!ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
ctx->probesize = kProbeSizeBytes;
|
||||
ctx->max_analyze_duration = kAnalyzeDurationUs;
|
||||
}
|
||||
|
||||
void DiscardSubtitleStreams(AVFormatContext *ctx)
|
||||
{
|
||||
if (!ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < ctx->nb_streams; i++) {
|
||||
AVStream *stream = ctx->streams[i];
|
||||
if (stream && stream->codecpar &&
|
||||
stream->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) {
|
||||
stream->discard = AVDISCARD_ALL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
struct FBDecoder {
|
||||
AVFormatContext *fmt_ctx = nullptr;
|
||||
AVCodecContext *codec_ctx = nullptr;
|
||||
AVStream *avstream = nullptr;
|
||||
AVDictionary *opts = nullptr;
|
||||
|
||||
AVBufferRef *hw_device_ctx = nullptr;
|
||||
AVHWDeviceType hw_device_type = AV_HWDEVICE_TYPE_NONE;
|
||||
AVPixelFormat hw_pix_fmt = AV_PIX_FMT_NONE;
|
||||
bool hwaccel_enabled = false;
|
||||
|
||||
bool Open(const char *filename, int stream_index);
|
||||
void Close();
|
||||
|
||||
static AVHWDeviceType ChooseHardwareDevice();
|
||||
static AVPixelFormat GetHardwareFormat(AVCodecContext *ctx,
|
||||
const AVPixelFormat *pix_fmts);
|
||||
bool InitHardwareAcceleration(const AVCodec *codec);
|
||||
void CleanupHardwareAcceleration();
|
||||
};
|
||||
|
||||
FBDecoder *fb_decoder_create(void)
|
||||
{
|
||||
return new FBDecoder;
|
||||
}
|
||||
|
||||
void fb_decoder_free(FBDecoder **decoder)
|
||||
{
|
||||
if (decoder && *decoder) {
|
||||
(*decoder)->Close();
|
||||
delete *decoder;
|
||||
*decoder = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool FBDecoder::Open(const char *filename, int stream_index)
|
||||
{
|
||||
// Open file in a format context
|
||||
AVDictionary *format_opts = nullptr;
|
||||
ApplyFormatOpenOptions(&format_opts);
|
||||
int error_code = avformat_open_input(&fmt_ctx, filename, nullptr, &format_opts);
|
||||
av_dict_free(&format_opts);
|
||||
TuneFormatContext(fmt_ctx);
|
||||
DiscardSubtitleStreams(fmt_ctx);
|
||||
|
||||
if (error_code != 0) {
|
||||
fprintf(stderr, "ffmpeg_bridge: failed to open input %s (%d)\n", filename,
|
||||
error_code);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get stream information from format
|
||||
error_code = avformat_find_stream_info(fmt_ctx, nullptr);
|
||||
if (error_code < 0) {
|
||||
fprintf(stderr, "ffmpeg_bridge: failed to find stream info (%d)\n",
|
||||
error_code);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get reference to correct AVStream
|
||||
avstream = fmt_ctx->streams[stream_index];
|
||||
|
||||
// Find decoder
|
||||
const AVCodec *codec = avcodec_find_decoder(avstream->codecpar->codec_id);
|
||||
if (codec == nullptr) {
|
||||
fprintf(stderr, "ffmpeg_bridge: no decoder for codec %d\n",
|
||||
avstream->codecpar->codec_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allocate context for the decoder
|
||||
codec_ctx = avcodec_alloc_context3(codec);
|
||||
if (codec_ctx == nullptr) {
|
||||
fprintf(stderr, "ffmpeg_bridge: failed to allocate codec context\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Copy parameters from the AVStream to the AVCodecContext
|
||||
error_code = avcodec_parameters_to_context(codec_ctx, avstream->codecpar);
|
||||
if (error_code < 0) {
|
||||
fprintf(stderr, "ffmpeg_bridge: failed to copy codec parameters\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set multithreading setting
|
||||
error_code = av_dict_set(&opts, "threads", "auto", 0);
|
||||
if (error_code < 0) {
|
||||
fprintf(stderr,
|
||||
"ffmpeg_bridge: failed to set codec options, performance may suffer\n");
|
||||
}
|
||||
|
||||
// Attempt hardware accelerated decoding first, then fall back to software.
|
||||
if (InitHardwareAcceleration(codec)) {
|
||||
error_code = avcodec_open2(codec_ctx, codec, &opts);
|
||||
if (error_code == 0) {
|
||||
hwaccel_enabled = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
fprintf(stderr,
|
||||
"ffmpeg_bridge: failed to open hardware codec, falling back to software (%d)\n",
|
||||
error_code);
|
||||
|
||||
// Free the failed context and recreate it for software decoding.
|
||||
avcodec_free_context(&codec_ctx);
|
||||
CleanupHardwareAcceleration();
|
||||
|
||||
codec_ctx = avcodec_alloc_context3(codec);
|
||||
if (codec_ctx == nullptr) {
|
||||
fprintf(stderr,
|
||||
"ffmpeg_bridge: failed to allocate codec context for software fallback\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
error_code = avcodec_parameters_to_context(codec_ctx, avstream->codecpar);
|
||||
if (error_code < 0) {
|
||||
fprintf(stderr, "ffmpeg_bridge: failed to copy codec parameters\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Open codec (software path, or if hardware was not available)
|
||||
error_code = avcodec_open2(codec_ctx, codec, &opts);
|
||||
if (error_code < 0) {
|
||||
fprintf(stderr, "ffmpeg_bridge: failed to open codec %d (%d)\n", codec->id,
|
||||
error_code);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
AVHWDeviceType FBDecoder::ChooseHardwareDevice()
|
||||
{
|
||||
if (getenv("OAK_DISABLE_HWACCEL") != nullptr) {
|
||||
return AV_HWDEVICE_TYPE_NONE;
|
||||
}
|
||||
|
||||
#if defined(__linux__)
|
||||
// Prefer NVIDIA's NVDEC where available, then VAAPI/VDPAU.
|
||||
for (AVHWDeviceType type :
|
||||
{ AV_HWDEVICE_TYPE_CUDA, AV_HWDEVICE_TYPE_VAAPI, AV_HWDEVICE_TYPE_VDPAU }) {
|
||||
if (av_hwdevice_find_type_by_name(av_hwdevice_get_type_name(type)) !=
|
||||
AV_HWDEVICE_TYPE_NONE) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
#elif defined(_WIN32)
|
||||
for (AVHWDeviceType type :
|
||||
{ AV_HWDEVICE_TYPE_D3D11VA, AV_HWDEVICE_TYPE_DXVA2, AV_HWDEVICE_TYPE_CUDA }) {
|
||||
if (av_hwdevice_find_type_by_name(av_hwdevice_get_type_name(type)) !=
|
||||
AV_HWDEVICE_TYPE_NONE) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
#elif defined(__APPLE__)
|
||||
if (av_hwdevice_find_type_by_name(
|
||||
av_hwdevice_get_type_name(AV_HWDEVICE_TYPE_VIDEOTOOLBOX)) !=
|
||||
AV_HWDEVICE_TYPE_NONE) {
|
||||
return AV_HWDEVICE_TYPE_VIDEOTOOLBOX;
|
||||
}
|
||||
#endif
|
||||
return AV_HWDEVICE_TYPE_NONE;
|
||||
}
|
||||
|
||||
AVPixelFormat FBDecoder::GetHardwareFormat(AVCodecContext *ctx,
|
||||
const AVPixelFormat *pix_fmts)
|
||||
{
|
||||
const FBDecoder *inst = static_cast<const FBDecoder *>(ctx->opaque);
|
||||
for (const AVPixelFormat *p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) {
|
||||
if (*p == inst->hw_pix_fmt) {
|
||||
return *p;
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(stderr,
|
||||
"ffmpeg_bridge: hardware pixel format not supported by decoder, using first software format\n");
|
||||
return pix_fmts[0];
|
||||
}
|
||||
|
||||
bool FBDecoder::InitHardwareAcceleration(const AVCodec *codec)
|
||||
{
|
||||
const AVHWDeviceType device_type = ChooseHardwareDevice();
|
||||
if (device_type == AV_HWDEVICE_TYPE_NONE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find the pixel format associated with this device type for this codec.
|
||||
hw_pix_fmt = AV_PIX_FMT_NONE;
|
||||
for (int i = 0;; i++) {
|
||||
const AVCodecHWConfig *config = avcodec_get_hw_config(codec, i);
|
||||
if (!config) {
|
||||
break;
|
||||
}
|
||||
if ((config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX) &&
|
||||
config->device_type == device_type) {
|
||||
hw_pix_fmt = config->pix_fmt;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hw_pix_fmt == AV_PIX_FMT_NONE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
hw_device_type = device_type;
|
||||
|
||||
int ret = av_hwdevice_ctx_create(&hw_device_ctx, device_type, nullptr, nullptr,
|
||||
0);
|
||||
if (ret < 0) {
|
||||
fprintf(stderr,
|
||||
"ffmpeg_bridge: failed to create hardware device context (%d)\n",
|
||||
ret);
|
||||
CleanupHardwareAcceleration();
|
||||
return false;
|
||||
}
|
||||
|
||||
codec_ctx->hw_device_ctx = av_buffer_ref(hw_device_ctx);
|
||||
codec_ctx->opaque = this;
|
||||
codec_ctx->get_format = GetHardwareFormat;
|
||||
// Most hardware decoders do not support frame threading.
|
||||
av_dict_set(&opts, "threads", "1", 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void FBDecoder::CleanupHardwareAcceleration()
|
||||
{
|
||||
hwaccel_enabled = false;
|
||||
hw_device_type = AV_HWDEVICE_TYPE_NONE;
|
||||
hw_pix_fmt = AV_PIX_FMT_NONE;
|
||||
|
||||
if (hw_device_ctx) {
|
||||
av_buffer_unref(&hw_device_ctx);
|
||||
hw_device_ctx = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void FBDecoder::Close()
|
||||
{
|
||||
if (opts) {
|
||||
av_dict_free(&opts);
|
||||
opts = nullptr;
|
||||
}
|
||||
|
||||
if (codec_ctx) {
|
||||
avcodec_free_context(&codec_ctx);
|
||||
codec_ctx = nullptr;
|
||||
}
|
||||
|
||||
CleanupHardwareAcceleration();
|
||||
|
||||
if (fmt_ctx) {
|
||||
avformat_close_input(&fmt_ctx);
|
||||
fmt_ctx = nullptr;
|
||||
}
|
||||
|
||||
avstream = nullptr;
|
||||
}
|
||||
|
||||
int fb_decoder_open(FBDecoder *decoder, const char *filename, int stream_index)
|
||||
{
|
||||
if (!decoder || !filename) {
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
return decoder->Open(filename, stream_index) ? 0 : AVERROR_EXTERNAL;
|
||||
}
|
||||
|
||||
void fb_decoder_close(FBDecoder *decoder)
|
||||
{
|
||||
if (decoder) {
|
||||
decoder->Close();
|
||||
}
|
||||
}
|
||||
|
||||
int fb_decoder_get_frame(FBDecoder *decoder, FBPacket *packet, FBFrame *frame)
|
||||
{
|
||||
if (!decoder || !decoder->codec_ctx || !packet || !frame) {
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
AVPacket *pkt = packet->pkt;
|
||||
AVFrame *frm = frame->frame;
|
||||
|
||||
bool eof = false;
|
||||
int ret;
|
||||
|
||||
// Clear any previous frames
|
||||
av_frame_unref(frm);
|
||||
|
||||
while ((ret = avcodec_receive_frame(decoder->codec_ctx, frm)) ==
|
||||
AVERROR(EAGAIN) &&
|
||||
!eof) {
|
||||
// Find next packet in the correct stream index
|
||||
ret = fb_decoder_get_packet(decoder, packet);
|
||||
|
||||
if (ret == AVERROR_EOF) {
|
||||
// Don't break so that receive gets called again, but don't try to read again
|
||||
eof = true;
|
||||
|
||||
// Send a null packet to signal end of stream
|
||||
avcodec_send_packet(decoder->codec_ctx, nullptr);
|
||||
} else if (ret < 0) {
|
||||
// Handle other error by breaking loop and returning the code we received
|
||||
break;
|
||||
} else {
|
||||
// Successful read, send the packet
|
||||
ret = avcodec_send_packet(decoder->codec_ctx, pkt);
|
||||
|
||||
// We don't need the packet anymore, so free it
|
||||
av_packet_unref(pkt);
|
||||
|
||||
if (ret < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int fb_decoder_get_packet(FBDecoder *decoder, FBPacket *packet)
|
||||
{
|
||||
if (!decoder || !decoder->fmt_ctx || !packet) {
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
AVPacket *pkt = packet->pkt;
|
||||
int ret;
|
||||
|
||||
do {
|
||||
av_packet_unref(pkt);
|
||||
ret = av_read_frame(decoder->fmt_ctx, pkt);
|
||||
} while (pkt->stream_index != decoder->avstream->index && ret >= 0);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void fb_decoder_seek(FBDecoder *decoder, int64_t timestamp)
|
||||
{
|
||||
if (!decoder || !decoder->fmt_ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
avcodec_flush_buffers(decoder->codec_ctx);
|
||||
av_seek_frame(decoder->fmt_ctx, decoder->avstream->index, timestamp,
|
||||
AVSEEK_FLAG_BACKWARD);
|
||||
}
|
||||
|
||||
int fb_decoder_get_stream_info(const FBDecoder *decoder, FBStreamInfo *out)
|
||||
{
|
||||
if (!decoder || !decoder->avstream || !out) {
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
const AVStream *s = decoder->avstream;
|
||||
const AVCodecParameters *par = s->codecpar;
|
||||
|
||||
memset(out, 0, sizeof(*out));
|
||||
out->index = s->index;
|
||||
out->codec_type = par->codec_type;
|
||||
out->codec_id = par->codec_id;
|
||||
out->has_decoder = 1; // stream is open, so a decoder was found
|
||||
out->width = par->width;
|
||||
out->height = par->height;
|
||||
out->pixel_format = par->format;
|
||||
out->field_order = decoder->codec_ctx ? decoder->codec_ctx->field_order :
|
||||
AV_FIELD_UNKNOWN;
|
||||
out->color_range = par->color_range;
|
||||
out->sample_rate = par->sample_rate;
|
||||
out->sample_format = par->format;
|
||||
out->channel_layout_mask = fb::ValidateStreamChannelLayoutMask(s);
|
||||
out->start_time = s->start_time;
|
||||
out->duration = s->duration;
|
||||
out->time_base_num = s->time_base.num;
|
||||
out->time_base_den = s->time_base.den;
|
||||
out->avg_frame_rate_num = s->avg_frame_rate.num;
|
||||
out->avg_frame_rate_den = s->avg_frame_rate.den;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int64_t fb_decoder_get_format_start_time(const FBDecoder *decoder)
|
||||
{
|
||||
if (!decoder || !decoder->fmt_ctx) {
|
||||
return FB_NOPTS_VALUE;
|
||||
}
|
||||
return decoder->fmt_ctx->start_time;
|
||||
}
|
||||
|
||||
int fb_decoder_guess_sample_aspect_ratio(const FBDecoder *decoder,
|
||||
FBFrame *frame, int *num, int *den)
|
||||
{
|
||||
if (!decoder || !decoder->fmt_ctx || !num || !den) {
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
AVRational r = av_guess_sample_aspect_ratio(
|
||||
decoder->fmt_ctx, decoder->avstream, frame ? frame->frame : nullptr);
|
||||
*num = r.num;
|
||||
*den = r.den;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fb_decoder_guess_frame_rate(const FBDecoder *decoder, FBFrame *frame,
|
||||
int *num, int *den)
|
||||
{
|
||||
if (!decoder || !decoder->fmt_ctx || !num || !den) {
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
AVRational r = av_guess_frame_rate(decoder->fmt_ctx, decoder->avstream,
|
||||
frame ? frame->frame : nullptr);
|
||||
*num = r.num;
|
||||
*den = r.den;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fb_decoder_hwaccel_enabled(const FBDecoder *decoder)
|
||||
{
|
||||
return decoder && decoder->hwaccel_enabled;
|
||||
}
|
||||
|
||||
int fb_decoder_hw_pix_fmt(const FBDecoder *decoder)
|
||||
{
|
||||
return decoder ? int(decoder->hw_pix_fmt) : FB_PIX_FMT_NONE;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,239 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "internal.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
FBFrame *fb_frame_alloc(void)
|
||||
{
|
||||
AVFrame *avf = av_frame_alloc();
|
||||
if (!avf) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FBFrame *f = new FBFrame;
|
||||
f->frame = avf;
|
||||
return f;
|
||||
}
|
||||
|
||||
void fb_frame_free(FBFrame **frame)
|
||||
{
|
||||
if (frame && *frame) {
|
||||
av_frame_free(&(*frame)->frame);
|
||||
delete *frame;
|
||||
*frame = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void fb_frame_unref(FBFrame *frame)
|
||||
{
|
||||
if (frame && frame->frame) {
|
||||
av_frame_unref(frame->frame);
|
||||
}
|
||||
}
|
||||
|
||||
int fb_frame_get_buffer(FBFrame *frame, int align)
|
||||
{
|
||||
if (!frame || !frame->frame) {
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
return av_frame_get_buffer(frame->frame, align);
|
||||
}
|
||||
|
||||
int fb_frame_copy_props(FBFrame *dst, const FBFrame *src)
|
||||
{
|
||||
if (!dst || !src) {
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
return av_frame_copy_props(dst->frame, src->frame);
|
||||
}
|
||||
|
||||
int fb_frame_hw_transfer_data(FBFrame *dst, const FBFrame *src)
|
||||
{
|
||||
if (!dst || !src) {
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
return av_hwframe_transfer_data(dst->frame, src->frame, 0);
|
||||
}
|
||||
|
||||
int fb_frame_is_hw(const FBFrame *frame)
|
||||
{
|
||||
return frame && frame->frame && frame->frame->hw_frames_ctx != nullptr;
|
||||
}
|
||||
|
||||
int fb_frame_get_width(const FBFrame *frame)
|
||||
{
|
||||
return frame ? frame->frame->width : 0;
|
||||
}
|
||||
|
||||
void fb_frame_set_width(FBFrame *frame, int width)
|
||||
{
|
||||
if (frame) {
|
||||
frame->frame->width = width;
|
||||
}
|
||||
}
|
||||
|
||||
int fb_frame_get_height(const FBFrame *frame)
|
||||
{
|
||||
return frame ? frame->frame->height : 0;
|
||||
}
|
||||
|
||||
void fb_frame_set_height(FBFrame *frame, int height)
|
||||
{
|
||||
if (frame) {
|
||||
frame->frame->height = height;
|
||||
}
|
||||
}
|
||||
|
||||
int fb_frame_get_format(const FBFrame *frame)
|
||||
{
|
||||
return frame ? frame->frame->format : FB_PIX_FMT_NONE;
|
||||
}
|
||||
|
||||
void fb_frame_set_format(FBFrame *frame, int format)
|
||||
{
|
||||
if (frame) {
|
||||
frame->frame->format = format;
|
||||
}
|
||||
}
|
||||
|
||||
int64_t fb_frame_get_pts(const FBFrame *frame)
|
||||
{
|
||||
return frame ? frame->frame->pts : FB_NOPTS_VALUE;
|
||||
}
|
||||
|
||||
void fb_frame_set_pts(FBFrame *frame, int64_t pts)
|
||||
{
|
||||
if (frame) {
|
||||
frame->frame->pts = pts;
|
||||
}
|
||||
}
|
||||
|
||||
int64_t fb_frame_get_best_effort_timestamp(const FBFrame *frame)
|
||||
{
|
||||
return frame ? frame->frame->best_effort_timestamp : FB_NOPTS_VALUE;
|
||||
}
|
||||
|
||||
int fb_frame_get_nb_samples(const FBFrame *frame)
|
||||
{
|
||||
return frame ? frame->frame->nb_samples : 0;
|
||||
}
|
||||
|
||||
void fb_frame_set_nb_samples(FBFrame *frame, int nb_samples)
|
||||
{
|
||||
if (frame) {
|
||||
frame->frame->nb_samples = nb_samples;
|
||||
}
|
||||
}
|
||||
|
||||
int fb_frame_get_sample_rate(const FBFrame *frame)
|
||||
{
|
||||
return frame ? frame->frame->sample_rate : 0;
|
||||
}
|
||||
|
||||
void fb_frame_set_sample_rate(FBFrame *frame, int sample_rate)
|
||||
{
|
||||
if (frame) {
|
||||
frame->frame->sample_rate = sample_rate;
|
||||
}
|
||||
}
|
||||
|
||||
int fb_frame_get_color_range(const FBFrame *frame)
|
||||
{
|
||||
return frame ? int(frame->frame->color_range) : FB_COLOR_RANGE_UNSPEC;
|
||||
}
|
||||
|
||||
void fb_frame_set_color_range(FBFrame *frame, int color_range)
|
||||
{
|
||||
if (frame) {
|
||||
frame->frame->color_range = static_cast<AVColorRange>(color_range);
|
||||
}
|
||||
}
|
||||
|
||||
int fb_frame_get_colorspace(const FBFrame *frame)
|
||||
{
|
||||
return frame ? int(frame->frame->colorspace) : FB_COL_SPC_UNSPEC;
|
||||
}
|
||||
|
||||
void fb_frame_set_colorspace(FBFrame *frame, int colorspace)
|
||||
{
|
||||
if (frame) {
|
||||
frame->frame->colorspace = static_cast<AVColorSpace>(colorspace);
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t fb_frame_get_channel_layout_mask(const FBFrame *frame)
|
||||
{
|
||||
if (!frame) {
|
||||
return 0;
|
||||
}
|
||||
if (frame->frame->ch_layout.order == AV_CHANNEL_ORDER_NATIVE) {
|
||||
return frame->frame->ch_layout.u.mask;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void fb_frame_set_channel_layout_mask(FBFrame *frame, uint64_t mask)
|
||||
{
|
||||
if (frame) {
|
||||
av_channel_layout_uninit(&frame->frame->ch_layout);
|
||||
av_channel_layout_from_mask(&frame->frame->ch_layout, mask);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t *fb_frame_get_data(FBFrame *frame, int plane)
|
||||
{
|
||||
if (!frame || plane < 0 || plane >= AV_NUM_DATA_POINTERS) {
|
||||
return nullptr;
|
||||
}
|
||||
return frame->frame->data[plane];
|
||||
}
|
||||
|
||||
const uint8_t *fb_frame_get_data_const(const FBFrame *frame, int plane)
|
||||
{
|
||||
if (!frame || plane < 0 || plane >= AV_NUM_DATA_POINTERS) {
|
||||
return nullptr;
|
||||
}
|
||||
return frame->frame->data[plane];
|
||||
}
|
||||
|
||||
void fb_frame_set_data(FBFrame *frame, int plane, uint8_t *data)
|
||||
{
|
||||
if (frame && plane >= 0 && plane < AV_NUM_DATA_POINTERS) {
|
||||
frame->frame->data[plane] = data;
|
||||
}
|
||||
}
|
||||
|
||||
int fb_frame_get_linesize(const FBFrame *frame, int plane)
|
||||
{
|
||||
if (!frame || plane < 0 || plane >= AV_NUM_DATA_POINTERS) {
|
||||
return 0;
|
||||
}
|
||||
return frame->frame->linesize[plane];
|
||||
}
|
||||
|
||||
void fb_frame_set_linesize(FBFrame *frame, int plane, int linesize)
|
||||
{
|
||||
if (frame && plane >= 0 && plane < AV_NUM_DATA_POINTERS) {
|
||||
frame->frame->linesize[plane] = linesize;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef FFMPEG_BRIDGE_INTERNAL_H
|
||||
#define FFMPEG_BRIDGE_INTERNAL_H
|
||||
|
||||
// Fixes weird define issue when including <avfilter.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavfilter/avfilter.h>
|
||||
#include <libavfilter/buffersink.h>
|
||||
#include <libavfilter/buffersrc.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavutil/channel_layout.h>
|
||||
#include <libavutil/hwcontext.h>
|
||||
#include <libavutil/opt.h>
|
||||
#include <libavutil/pixdesc.h>
|
||||
#include <libswresample/swresample.h>
|
||||
#include <libswscale/swscale.h>
|
||||
}
|
||||
|
||||
#include "ffmpeg_bridge/ffmpeg_bridge.h"
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* Compile-time verification that the public constants match FFmpeg */
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wenum-compare"
|
||||
#endif
|
||||
|
||||
static_assert(FB_ERROR_EOF == AVERROR_EOF, "FB_ERROR_EOF mismatch");
|
||||
static_assert(FB_NOPTS_VALUE == AV_NOPTS_VALUE, "FB_NOPTS_VALUE mismatch");
|
||||
static_assert(FB_TIME_BASE == AV_TIME_BASE, "FB_TIME_BASE mismatch");
|
||||
static_assert(FB_SCALER_POINT == SWS_POINT, "FB_SCALER_POINT mismatch");
|
||||
|
||||
static_assert(FB_PIX_FMT_NONE == AV_PIX_FMT_NONE, "FB_PIX_FMT_NONE mismatch");
|
||||
static_assert(FB_PIX_FMT_YUV420P == AV_PIX_FMT_YUV420P, "pixfmt mismatch");
|
||||
static_assert(FB_PIX_FMT_RGB24 == AV_PIX_FMT_RGB24, "pixfmt mismatch");
|
||||
static_assert(FB_PIX_FMT_YUV422P == AV_PIX_FMT_YUV422P, "pixfmt mismatch");
|
||||
static_assert(FB_PIX_FMT_YUV444P == AV_PIX_FMT_YUV444P, "pixfmt mismatch");
|
||||
static_assert(FB_PIX_FMT_YUV410P == AV_PIX_FMT_YUV410P, "pixfmt mismatch");
|
||||
static_assert(FB_PIX_FMT_YUV411P == AV_PIX_FMT_YUV411P, "pixfmt mismatch");
|
||||
static_assert(FB_PIX_FMT_GRAY8 == AV_PIX_FMT_GRAY8, "pixfmt mismatch");
|
||||
static_assert(FB_PIX_FMT_YUVJ420P == AV_PIX_FMT_YUVJ420P, "pixfmt mismatch");
|
||||
static_assert(FB_PIX_FMT_YUVJ422P == AV_PIX_FMT_YUVJ422P, "pixfmt mismatch");
|
||||
static_assert(FB_PIX_FMT_YUVJ444P == AV_PIX_FMT_YUVJ444P, "pixfmt mismatch");
|
||||
static_assert(FB_PIX_FMT_RGBA == AV_PIX_FMT_RGBA, "pixfmt mismatch");
|
||||
static_assert(FB_PIX_FMT_GRAY16LE == AV_PIX_FMT_GRAY16LE, "pixfmt mismatch");
|
||||
static_assert(FB_PIX_FMT_YUVJ440P == AV_PIX_FMT_YUVJ440P, "pixfmt mismatch");
|
||||
static_assert(FB_PIX_FMT_RGB48LE == AV_PIX_FMT_RGB48LE, "pixfmt mismatch");
|
||||
static_assert(FB_PIX_FMT_YUV420P10LE == AV_PIX_FMT_YUV420P10LE, "pixfmt mismatch");
|
||||
static_assert(FB_PIX_FMT_YUV422P10LE == AV_PIX_FMT_YUV422P10LE, "pixfmt mismatch");
|
||||
static_assert(FB_PIX_FMT_YUV444P10LE == AV_PIX_FMT_YUV444P10LE, "pixfmt mismatch");
|
||||
static_assert(FB_PIX_FMT_RGBA64LE == AV_PIX_FMT_RGBA64LE, "pixfmt mismatch");
|
||||
static_assert(FB_PIX_FMT_YUV420P12LE == AV_PIX_FMT_YUV420P12LE, "pixfmt mismatch");
|
||||
static_assert(FB_PIX_FMT_YUV422P12LE == AV_PIX_FMT_YUV422P12LE, "pixfmt mismatch");
|
||||
static_assert(FB_PIX_FMT_YUV444P12LE == AV_PIX_FMT_YUV444P12LE, "pixfmt mismatch");
|
||||
static_assert(FB_PIX_FMT_YUVJ411P == AV_PIX_FMT_YUVJ411P, "pixfmt mismatch");
|
||||
static_assert(FB_PIX_FMT_GRAYF32LE == AV_PIX_FMT_GRAYF32LE, "pixfmt mismatch");
|
||||
static_assert(FB_PIX_FMT_RGBAF16LE == AV_PIX_FMT_RGBAF16LE, "pixfmt mismatch");
|
||||
static_assert(FB_PIX_FMT_RGBF32LE == AV_PIX_FMT_RGBF32LE, "pixfmt mismatch");
|
||||
static_assert(FB_PIX_FMT_RGBAF32LE == AV_PIX_FMT_RGBAF32LE, "pixfmt mismatch");
|
||||
static_assert(FB_PIX_FMT_RGBF16LE == AV_PIX_FMT_RGBF16LE, "pixfmt mismatch");
|
||||
static_assert(FB_PIX_FMT_GRAYF16LE == AV_PIX_FMT_GRAYF16LE, "pixfmt mismatch");
|
||||
|
||||
static_assert(FB_SAMPLE_FMT_NONE == AV_SAMPLE_FMT_NONE, "samplefmt mismatch");
|
||||
static_assert(FB_SAMPLE_FMT_U8 == AV_SAMPLE_FMT_U8, "samplefmt mismatch");
|
||||
static_assert(FB_SAMPLE_FMT_S16 == AV_SAMPLE_FMT_S16, "samplefmt mismatch");
|
||||
static_assert(FB_SAMPLE_FMT_S32 == AV_SAMPLE_FMT_S32, "samplefmt mismatch");
|
||||
static_assert(FB_SAMPLE_FMT_FLT == AV_SAMPLE_FMT_FLT, "samplefmt mismatch");
|
||||
static_assert(FB_SAMPLE_FMT_DBL == AV_SAMPLE_FMT_DBL, "samplefmt mismatch");
|
||||
static_assert(FB_SAMPLE_FMT_U8P == AV_SAMPLE_FMT_U8P, "samplefmt mismatch");
|
||||
static_assert(FB_SAMPLE_FMT_S16P == AV_SAMPLE_FMT_S16P, "samplefmt mismatch");
|
||||
static_assert(FB_SAMPLE_FMT_S32P == AV_SAMPLE_FMT_S32P, "samplefmt mismatch");
|
||||
static_assert(FB_SAMPLE_FMT_FLTP == AV_SAMPLE_FMT_FLTP, "samplefmt mismatch");
|
||||
static_assert(FB_SAMPLE_FMT_DBLP == AV_SAMPLE_FMT_DBLP, "samplefmt mismatch");
|
||||
static_assert(FB_SAMPLE_FMT_S64 == AV_SAMPLE_FMT_S64, "samplefmt mismatch");
|
||||
static_assert(FB_SAMPLE_FMT_S64P == AV_SAMPLE_FMT_S64P, "samplefmt mismatch");
|
||||
|
||||
static_assert(FB_COLOR_RANGE_UNSPEC == AVCOL_RANGE_UNSPECIFIED, "range mismatch");
|
||||
static_assert(FB_COLOR_RANGE_MPEG == AVCOL_RANGE_MPEG, "range mismatch");
|
||||
static_assert(FB_COLOR_RANGE_JPEG == AVCOL_RANGE_JPEG, "range mismatch");
|
||||
|
||||
static_assert(FB_COL_SPC_RGB == AVCOL_SPC_RGB, "colspace mismatch");
|
||||
static_assert(FB_COL_SPC_BT709 == AVCOL_SPC_BT709, "colspace mismatch");
|
||||
static_assert(FB_COL_SPC_UNSPEC == AVCOL_SPC_UNSPECIFIED, "colspace mismatch");
|
||||
static_assert(FB_COL_SPC_FCC == AVCOL_SPC_FCC, "colspace mismatch");
|
||||
static_assert(FB_COL_SPC_BT470BG == AVCOL_SPC_BT470BG, "colspace mismatch");
|
||||
static_assert(FB_COL_SPC_SMPTE170M == AVCOL_SPC_SMPTE170M, "colspace mismatch");
|
||||
static_assert(FB_COL_SPC_SMPTE240M == AVCOL_SPC_SMPTE240M, "colspace mismatch");
|
||||
static_assert(FB_COL_SPC_BT2020_NCL == AVCOL_SPC_BT2020_NCL, "colspace mismatch");
|
||||
|
||||
static_assert(FB_MEDIA_TYPE_VIDEO == AVMEDIA_TYPE_VIDEO, "mediatype mismatch");
|
||||
static_assert(FB_MEDIA_TYPE_AUDIO == AVMEDIA_TYPE_AUDIO, "mediatype mismatch");
|
||||
static_assert(FB_MEDIA_TYPE_DATA == AVMEDIA_TYPE_DATA, "mediatype mismatch");
|
||||
static_assert(FB_MEDIA_TYPE_SUBTITLE == AVMEDIA_TYPE_SUBTITLE, "mediatype mismatch");
|
||||
|
||||
static_assert(FB_FIELD_ORDER_UNKNOWN == AV_FIELD_UNKNOWN, "fieldorder mismatch");
|
||||
static_assert(FB_FIELD_ORDER_PROGRESSIVE == AV_FIELD_PROGRESSIVE, "fieldorder mismatch");
|
||||
static_assert(FB_FIELD_ORDER_TT == AV_FIELD_TT, "fieldorder mismatch");
|
||||
static_assert(FB_FIELD_ORDER_BB == AV_FIELD_BB, "fieldorder mismatch");
|
||||
static_assert(FB_FIELD_ORDER_TB == AV_FIELD_TB, "fieldorder mismatch");
|
||||
static_assert(FB_FIELD_ORDER_BT == AV_FIELD_BT, "fieldorder mismatch");
|
||||
|
||||
static_assert(FB_CH_LAYOUT_MONO == AV_CH_LAYOUT_MONO, "layout mismatch");
|
||||
static_assert(FB_CH_LAYOUT_STEREO == AV_CH_LAYOUT_STEREO, "layout mismatch");
|
||||
static_assert(FB_CH_LAYOUT_2_1 == AV_CH_LAYOUT_2_1, "layout mismatch");
|
||||
static_assert(FB_CH_LAYOUT_5POINT1 == AV_CH_LAYOUT_5POINT1, "layout mismatch");
|
||||
static_assert(FB_CH_LAYOUT_7POINT1 == AV_CH_LAYOUT_7POINT1, "layout mismatch");
|
||||
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* Handle bodies */
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
struct FBFrame {
|
||||
AVFrame *frame;
|
||||
};
|
||||
|
||||
struct FBPacket {
|
||||
AVPacket *pkt;
|
||||
};
|
||||
|
||||
namespace fb
|
||||
{
|
||||
|
||||
/** Allocate a channel layout from a mask, falling back to a default layout
|
||||
* derived from `fallback_channels` when the mask is zero. */
|
||||
void ChannelLayoutFromMask(AVChannelLayout *layout, uint64_t mask,
|
||||
int fallback_channels);
|
||||
|
||||
/** Validate a stream's channel layout, returning a usable mask (never zero
|
||||
* unless the stream truly has no channels). */
|
||||
uint64_t ValidateStreamChannelLayoutMask(const AVStream *stream);
|
||||
|
||||
/** Map an AVColorSpace to the corresponding SWS_CS_* constant. */
|
||||
int SwsColorspaceFromAVColorSpace(AVColorSpace cs);
|
||||
|
||||
void SetError(char *error_buffer, size_t error_buffer_size, const char *context,
|
||||
int error_code);
|
||||
|
||||
} // namespace fb
|
||||
|
||||
#endif // FFMPEG_BRIDGE_INTERNAL_H
|
||||
@@ -0,0 +1,75 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "internal.h"
|
||||
|
||||
FBPacket *fb_packet_alloc(void)
|
||||
{
|
||||
AVPacket *avp = av_packet_alloc();
|
||||
if (!avp) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FBPacket *p = new FBPacket;
|
||||
p->pkt = avp;
|
||||
return p;
|
||||
}
|
||||
|
||||
void fb_packet_free(FBPacket **packet)
|
||||
{
|
||||
if (packet && *packet) {
|
||||
av_packet_free(&(*packet)->pkt);
|
||||
delete *packet;
|
||||
*packet = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void fb_packet_unref(FBPacket *packet)
|
||||
{
|
||||
if (packet && packet->pkt) {
|
||||
av_packet_unref(packet->pkt);
|
||||
}
|
||||
}
|
||||
|
||||
int64_t fb_packet_get_pts(const FBPacket *packet)
|
||||
{
|
||||
return packet ? packet->pkt->pts : FB_NOPTS_VALUE;
|
||||
}
|
||||
|
||||
int64_t fb_packet_get_duration(const FBPacket *packet)
|
||||
{
|
||||
return packet ? packet->pkt->duration : 0;
|
||||
}
|
||||
|
||||
int fb_packet_get_size(const FBPacket *packet)
|
||||
{
|
||||
return packet ? packet->pkt->size : 0;
|
||||
}
|
||||
|
||||
const uint8_t *fb_packet_get_data(const FBPacket *packet)
|
||||
{
|
||||
return packet ? packet->pkt->data : nullptr;
|
||||
}
|
||||
|
||||
int fb_packet_get_stream_index(const FBPacket *packet)
|
||||
{
|
||||
return packet ? packet->pkt->stream_index : -1;
|
||||
}
|
||||
@@ -0,0 +1,324 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "internal.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
struct FBProbe {
|
||||
AVFormatContext *fmt_ctx = nullptr;
|
||||
};
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
constexpr int64_t kAnalyzeDurationUs = 5000000;
|
||||
constexpr int64_t kProbeSizeBytes = 20000000;
|
||||
|
||||
void FillStreamInfo(const AVStream *s, int has_decoder, FBStreamInfo *out)
|
||||
{
|
||||
const AVCodecParameters *par = s->codecpar;
|
||||
|
||||
memset(out, 0, sizeof(*out));
|
||||
out->index = s->index;
|
||||
out->codec_type = par->codec_type;
|
||||
out->codec_id = par->codec_id;
|
||||
out->has_decoder = has_decoder;
|
||||
out->width = par->width;
|
||||
out->height = par->height;
|
||||
out->pixel_format = par->format;
|
||||
out->field_order = FB_FIELD_ORDER_UNKNOWN;
|
||||
out->color_range = par->color_range;
|
||||
out->sample_rate = par->sample_rate;
|
||||
out->sample_format = par->format;
|
||||
out->channel_layout_mask = fb::ValidateStreamChannelLayoutMask(s);
|
||||
out->start_time = s->start_time;
|
||||
out->duration = s->duration;
|
||||
out->time_base_num = s->time_base.num;
|
||||
out->time_base_den = s->time_base.den;
|
||||
out->avg_frame_rate_num = s->avg_frame_rate.num;
|
||||
out->avg_frame_rate_den = s->avg_frame_rate.den;
|
||||
}
|
||||
|
||||
bool IsCancelled(FBCancelCallback cancel, void *userdata)
|
||||
{
|
||||
return cancel && cancel(userdata);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
FBProbe *fb_probe_create(void)
|
||||
{
|
||||
return new FBProbe;
|
||||
}
|
||||
|
||||
void fb_probe_free(FBProbe **probe)
|
||||
{
|
||||
if (probe && *probe) {
|
||||
fb_probe_close(*probe);
|
||||
delete *probe;
|
||||
*probe = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
int fb_probe_open(FBProbe *probe, const char *filename)
|
||||
{
|
||||
if (!probe || !filename) {
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
AVDictionary *format_opts = nullptr;
|
||||
av_dict_set_int(&format_opts, "analyzeduration", kAnalyzeDurationUs, 0);
|
||||
av_dict_set_int(&format_opts, "probesize", kProbeSizeBytes, 0);
|
||||
|
||||
AVFormatContext *ctx = nullptr;
|
||||
int error_code = avformat_open_input(&ctx, filename, nullptr, &format_opts);
|
||||
av_dict_free(&format_opts);
|
||||
|
||||
if (ctx) {
|
||||
ctx->probesize = kProbeSizeBytes;
|
||||
ctx->max_analyze_duration = kAnalyzeDurationUs;
|
||||
|
||||
// Subtitle streams are read on demand, don't let them slow down probing
|
||||
for (unsigned int i = 0; i < ctx->nb_streams; i++) {
|
||||
AVStream *stream = ctx->streams[i];
|
||||
if (stream && stream->codecpar &&
|
||||
stream->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) {
|
||||
stream->discard = AVDISCARD_ALL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (error_code != 0) {
|
||||
return error_code;
|
||||
}
|
||||
|
||||
avformat_find_stream_info(ctx, nullptr);
|
||||
|
||||
probe->fmt_ctx = ctx;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void fb_probe_close(FBProbe *probe)
|
||||
{
|
||||
if (probe && probe->fmt_ctx) {
|
||||
avformat_close_input(&probe->fmt_ctx);
|
||||
probe->fmt_ctx = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
int fb_probe_get_stream_count(const FBProbe *probe)
|
||||
{
|
||||
if (!probe || !probe->fmt_ctx) {
|
||||
return 0;
|
||||
}
|
||||
return int(probe->fmt_ctx->nb_streams);
|
||||
}
|
||||
|
||||
int fb_probe_get_stream_info(const FBProbe *probe, int stream_index,
|
||||
FBStreamInfo *out)
|
||||
{
|
||||
if (!probe || !probe->fmt_ctx || !out || stream_index < 0 ||
|
||||
stream_index >= int(probe->fmt_ctx->nb_streams)) {
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
const AVStream *s = probe->fmt_ctx->streams[stream_index];
|
||||
int has_decoder = avcodec_find_decoder(s->codecpar->codec_id) != nullptr;
|
||||
FillStreamInfo(s, has_decoder, out);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int64_t fb_probe_get_duration(const FBProbe *probe)
|
||||
{
|
||||
if (!probe || !probe->fmt_ctx) {
|
||||
return FB_NOPTS_VALUE;
|
||||
}
|
||||
return probe->fmt_ctx->duration;
|
||||
}
|
||||
|
||||
int64_t fb_probe_get_start_time(const FBProbe *probe)
|
||||
{
|
||||
if (!probe || !probe->fmt_ctx) {
|
||||
return FB_NOPTS_VALUE;
|
||||
}
|
||||
return probe->fmt_ctx->start_time;
|
||||
}
|
||||
|
||||
int fb_probe_duration_from_bitrate(const FBProbe *probe)
|
||||
{
|
||||
if (!probe || !probe->fmt_ctx) {
|
||||
return 0;
|
||||
}
|
||||
return probe->fmt_ctx->duration_estimation_method ==
|
||||
AVFMT_DURATION_FROM_BITRATE;
|
||||
}
|
||||
|
||||
int fb_probe_get_metadata(FBProbe *probe, int stream_index, const char *key,
|
||||
char *buffer, int buffer_size)
|
||||
{
|
||||
if (!probe || !probe->fmt_ctx || !key || !buffer || buffer_size <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
AVDictionary *metadata = nullptr;
|
||||
if (stream_index < 0) {
|
||||
metadata = probe->fmt_ctx->metadata;
|
||||
} else if (stream_index < int(probe->fmt_ctx->nb_streams)) {
|
||||
metadata = probe->fmt_ctx->streams[stream_index]->metadata;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
||||
AVDictionaryEntry *entry =
|
||||
av_dict_get(metadata, key, nullptr, AV_DICT_IGNORE_SUFFIX);
|
||||
if (!entry) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
snprintf(buffer, size_t(buffer_size), "%s", entry->value);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int fb_probe_video_stream_details(const char *filename, int stream_index,
|
||||
FBVideoStreamDetails *out,
|
||||
int decode_full_duration,
|
||||
FBCancelCallback cancel,
|
||||
void *cancel_userdata)
|
||||
{
|
||||
if (!filename || !out) {
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
memset(out, 0, sizeof(*out));
|
||||
out->field_order = FB_FIELD_ORDER_PROGRESSIVE;
|
||||
out->pixel_aspect_num = 1;
|
||||
out->pixel_aspect_den = 1;
|
||||
out->decoded_duration = FB_NOPTS_VALUE;
|
||||
|
||||
FBDecoder *decoder = fb_decoder_create();
|
||||
if (fb_decoder_open(decoder, filename, stream_index) < 0) {
|
||||
fb_decoder_free(&decoder);
|
||||
return AVERROR_EXTERNAL;
|
||||
}
|
||||
|
||||
FBStreamInfo info;
|
||||
if (fb_decoder_get_stream_info(decoder, &info) == 0) {
|
||||
out->field_order = info.field_order;
|
||||
out->frame_rate_num = info.avg_frame_rate_num;
|
||||
out->frame_rate_den = info.avg_frame_rate_den;
|
||||
}
|
||||
|
||||
FBPacket *pkt = fb_packet_alloc();
|
||||
FBFrame *frame = fb_frame_alloc();
|
||||
|
||||
int ret = 0;
|
||||
|
||||
// Read at least one frame to get more information about this video stream
|
||||
if (fb_decoder_get_frame(decoder, pkt, frame) >= 0) {
|
||||
fb_decoder_guess_sample_aspect_ratio(decoder, frame,
|
||||
&out->pixel_aspect_num,
|
||||
&out->pixel_aspect_den);
|
||||
fb_decoder_guess_frame_rate(decoder, frame, &out->frame_rate_num,
|
||||
&out->frame_rate_den);
|
||||
}
|
||||
|
||||
ret = fb_decoder_get_frame(decoder, pkt, frame);
|
||||
if (ret == FB_ERROR_EOF) {
|
||||
// Only one frame exists: this is a still image
|
||||
out->is_still = 1;
|
||||
} else if (decode_full_duration) {
|
||||
// Decode until the end to determine the true duration
|
||||
int64_t last_ts = fb_frame_get_best_effort_timestamp(frame);
|
||||
while (fb_decoder_get_frame(decoder, pkt, frame) >= 0 &&
|
||||
!IsCancelled(cancel, cancel_userdata)) {
|
||||
last_ts = fb_frame_get_best_effort_timestamp(frame);
|
||||
}
|
||||
out->decoded_duration = last_ts;
|
||||
}
|
||||
|
||||
fb_frame_free(&frame);
|
||||
fb_packet_free(&pkt);
|
||||
fb_decoder_free(&decoder);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fb_probe_audio_stream_duration(const char *filename, int stream_index,
|
||||
int64_t *out_duration,
|
||||
FBCancelCallback cancel,
|
||||
void *cancel_userdata)
|
||||
{
|
||||
if (!filename || !out_duration) {
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
FBDecoder *decoder = fb_decoder_create();
|
||||
if (fb_decoder_open(decoder, filename, stream_index) < 0) {
|
||||
fb_decoder_free(&decoder);
|
||||
return AVERROR_EXTERNAL;
|
||||
}
|
||||
|
||||
FBPacket *pkt = fb_packet_alloc();
|
||||
FBFrame *frame = fb_frame_alloc();
|
||||
|
||||
int64_t duration = 0;
|
||||
do {
|
||||
duration = fb_frame_get_best_effort_timestamp(frame);
|
||||
} while (fb_decoder_get_frame(decoder, pkt, frame) >= 0 &&
|
||||
!IsCancelled(cancel, cancel_userdata));
|
||||
|
||||
fb_frame_free(&frame);
|
||||
fb_packet_free(&pkt);
|
||||
fb_decoder_free(&decoder);
|
||||
|
||||
*out_duration = duration;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fb_probe_read_subtitle_stream(const char *filename, int stream_index,
|
||||
FBSubtitleCallback callback,
|
||||
void *userdata)
|
||||
{
|
||||
if (!filename || !callback) {
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
FBDecoder *decoder = fb_decoder_create();
|
||||
if (fb_decoder_open(decoder, filename, stream_index) < 0) {
|
||||
fb_decoder_free(&decoder);
|
||||
return AVERROR_EXTERNAL;
|
||||
}
|
||||
|
||||
FBPacket *pkt = fb_packet_alloc();
|
||||
|
||||
while (fb_decoder_get_packet(decoder, pkt) >= 0) {
|
||||
callback(fb_packet_get_pts(pkt), fb_packet_get_duration(pkt),
|
||||
reinterpret_cast<const char *>(fb_packet_get_data(pkt)),
|
||||
fb_packet_get_size(pkt), userdata);
|
||||
}
|
||||
|
||||
fb_packet_free(&pkt);
|
||||
fb_decoder_free(&decoder);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "internal.h"
|
||||
|
||||
struct FBResampler {
|
||||
SwrContext *ctx;
|
||||
};
|
||||
|
||||
FBResampler *fb_resampler_create(uint64_t out_layout_mask, int out_format,
|
||||
int out_rate, uint64_t in_layout_mask,
|
||||
int in_format, int in_rate)
|
||||
{
|
||||
AVChannelLayout out_layout, in_layout;
|
||||
fb::ChannelLayoutFromMask(&out_layout, out_layout_mask, 0);
|
||||
fb::ChannelLayoutFromMask(&in_layout, in_layout_mask, 0);
|
||||
|
||||
SwrContext *ctx = nullptr;
|
||||
int r = swr_alloc_set_opts2(&ctx, &out_layout,
|
||||
static_cast<AVSampleFormat>(out_format), out_rate,
|
||||
&in_layout, static_cast<AVSampleFormat>(in_format),
|
||||
in_rate, 0, nullptr);
|
||||
|
||||
av_channel_layout_uninit(&out_layout);
|
||||
av_channel_layout_uninit(&in_layout);
|
||||
|
||||
if (r < 0 || !ctx) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (swr_init(ctx) < 0) {
|
||||
swr_free(&ctx);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FBResampler *resampler = new FBResampler;
|
||||
resampler->ctx = ctx;
|
||||
return resampler;
|
||||
}
|
||||
|
||||
void fb_resampler_free(FBResampler **resampler)
|
||||
{
|
||||
if (resampler && *resampler) {
|
||||
swr_free(&(*resampler)->ctx);
|
||||
delete *resampler;
|
||||
*resampler = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
int fb_resampler_get_out_samples(FBResampler *resampler, int in_samples)
|
||||
{
|
||||
if (!resampler) {
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
return swr_get_out_samples(resampler->ctx, in_samples);
|
||||
}
|
||||
|
||||
int fb_resampler_convert(FBResampler *resampler, uint8_t **out, int out_count,
|
||||
const uint8_t **in, int in_count)
|
||||
{
|
||||
if (!resampler) {
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
return swr_convert(resampler->ctx, out, out_count, in, in_count);
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "internal.h"
|
||||
|
||||
struct FBScaler {
|
||||
SwsContext *ctx;
|
||||
};
|
||||
|
||||
FBScaler *fb_scaler_create(int src_width, int src_height, int src_format,
|
||||
int dst_width, int dst_height, int dst_format,
|
||||
int flags)
|
||||
{
|
||||
SwsContext *ctx = sws_getContext(
|
||||
src_width, src_height, static_cast<AVPixelFormat>(src_format),
|
||||
dst_width, dst_height, static_cast<AVPixelFormat>(dst_format), flags,
|
||||
nullptr, nullptr, nullptr);
|
||||
if (!ctx) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FBScaler *s = new FBScaler;
|
||||
s->ctx = ctx;
|
||||
return s;
|
||||
}
|
||||
|
||||
void fb_scaler_free(FBScaler **scaler)
|
||||
{
|
||||
if (scaler && *scaler) {
|
||||
sws_freeContext((*scaler)->ctx);
|
||||
delete *scaler;
|
||||
*scaler = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
int fb_scaler_set_colorspace(FBScaler *scaler, int colorspace, int jpeg_range)
|
||||
{
|
||||
if (!scaler) {
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
const int *coeffs = sws_getCoefficients(
|
||||
fb::SwsColorspaceFromAVColorSpace(static_cast<AVColorSpace>(colorspace)));
|
||||
return sws_setColorspaceDetails(scaler->ctx, coeffs, jpeg_range, coeffs,
|
||||
jpeg_range, 0, 0x10000, 0x10000);
|
||||
}
|
||||
|
||||
int fb_scaler_scale_frame(FBScaler *scaler, FBFrame *dst, const FBFrame *src)
|
||||
{
|
||||
if (!scaler || !dst || !src) {
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
return sws_scale_frame(scaler->ctx, dst->frame, src->frame);
|
||||
}
|
||||
|
||||
int fb_scaler_scale_slices(FBScaler *scaler, const uint8_t *const *src_data,
|
||||
const int *src_linesize, int src_height,
|
||||
uint8_t *const *dst_data, const int *dst_linesize)
|
||||
{
|
||||
if (!scaler) {
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
return sws_scale(scaler->ctx, src_data, src_linesize, 0, src_height,
|
||||
dst_data, dst_linesize);
|
||||
}
|
||||
|
||||
void fb_get_yuv_coefficients(int colorspace, double out[4])
|
||||
{
|
||||
const int *coeffs = sws_getCoefficients(
|
||||
fb::SwsColorspaceFromAVColorSpace(static_cast<AVColorSpace>(colorspace)));
|
||||
// Matches the historical usage order: crv, cbu, cgu, cgv
|
||||
out[0] = coeffs[0] / 65536.0; // crv
|
||||
out[1] = coeffs[1] / 65536.0; // cbu
|
||||
out[2] = coeffs[2] / 65536.0; // cgu
|
||||
out[3] = coeffs[3] / 65536.0; // cgv
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "internal.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace fb
|
||||
{
|
||||
|
||||
void ChannelLayoutFromMask(AVChannelLayout *layout, uint64_t mask,
|
||||
int fallback_channels)
|
||||
{
|
||||
if (mask != 0) {
|
||||
av_channel_layout_from_mask(layout, mask);
|
||||
return;
|
||||
}
|
||||
|
||||
if (fallback_channels <= 0) {
|
||||
fallback_channels = 2;
|
||||
}
|
||||
av_channel_layout_default(layout, fallback_channels);
|
||||
}
|
||||
|
||||
uint64_t ValidateStreamChannelLayoutMask(const AVStream *stream)
|
||||
{
|
||||
if (!stream || !stream->codecpar) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const AVChannelLayout &layout = stream->codecpar->ch_layout;
|
||||
if (av_channel_layout_check(&layout) &&
|
||||
layout.order == AV_CHANNEL_ORDER_NATIVE && layout.u.mask != 0) {
|
||||
return layout.u.mask;
|
||||
}
|
||||
|
||||
// Fall back to a default layout for the stream's channel count
|
||||
if (layout.nb_channels <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
AVChannelLayout fallback;
|
||||
av_channel_layout_default(&fallback, layout.nb_channels);
|
||||
uint64_t mask = 0;
|
||||
if (fallback.order == AV_CHANNEL_ORDER_NATIVE) {
|
||||
mask = fallback.u.mask;
|
||||
}
|
||||
av_channel_layout_uninit(&fallback);
|
||||
return mask;
|
||||
}
|
||||
|
||||
int SwsColorspaceFromAVColorSpace(AVColorSpace cs)
|
||||
{
|
||||
switch (cs) {
|
||||
case AVCOL_SPC_BT709:
|
||||
return SWS_CS_ITU709;
|
||||
case AVCOL_SPC_FCC:
|
||||
return SWS_CS_FCC;
|
||||
case AVCOL_SPC_BT470BG:
|
||||
return SWS_CS_ITU624;
|
||||
case AVCOL_SPC_SMPTE170M:
|
||||
return SWS_CS_SMPTE170M;
|
||||
case AVCOL_SPC_SMPTE240M:
|
||||
return SWS_CS_SMPTE240M;
|
||||
case AVCOL_SPC_BT2020_NCL:
|
||||
return SWS_CS_BT2020;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return SWS_CS_DEFAULT;
|
||||
}
|
||||
|
||||
void SetError(char *error_buffer, size_t error_buffer_size, const char *context,
|
||||
int error_code)
|
||||
{
|
||||
if (!error_buffer || error_buffer_size == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
char ffmpeg_err[512];
|
||||
av_strerror(error_code, ffmpeg_err, sizeof(ffmpeg_err));
|
||||
snprintf(error_buffer, error_buffer_size, "%s: %s (%d)", context,
|
||||
ffmpeg_err, error_code);
|
||||
}
|
||||
|
||||
} // namespace fb
|
||||
|
||||
void fb_error_string(int error_code, char *buffer, int buffer_size)
|
||||
{
|
||||
if (!buffer || buffer_size <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (error_code == FB_ERROR_EOF) {
|
||||
snprintf(buffer, size_t(buffer_size), "End of file");
|
||||
return;
|
||||
}
|
||||
|
||||
if (av_strerror(error_code, buffer, size_t(buffer_size)) < 0) {
|
||||
snprintf(buffer, size_t(buffer_size), "Unknown error %d", error_code);
|
||||
}
|
||||
}
|
||||
|
||||
const char *fb_version_string(void)
|
||||
{
|
||||
static char version[128];
|
||||
snprintf(version, sizeof(version), "libavcodec %s, libavformat %s, libavutil %s, libswscale %s, libswresample %s, libavfilter %s",
|
||||
LIBAVCODEC_IDENT, LIBAVFORMAT_IDENT, LIBAVUTIL_IDENT,
|
||||
LIBSWSCALE_IDENT, LIBSWRESAMPLE_IDENT, LIBAVFILTER_IDENT);
|
||||
return version;
|
||||
}
|
||||
|
||||
const char *fb_pix_fmt_name(int pix_fmt)
|
||||
{
|
||||
return av_get_pix_fmt_name(static_cast<AVPixelFormat>(pix_fmt));
|
||||
}
|
||||
|
||||
int fb_pix_fmt_from_name(const char *name)
|
||||
{
|
||||
return av_get_pix_fmt(name);
|
||||
}
|
||||
|
||||
int fb_pix_fmt_bits_per_pixel(int pix_fmt)
|
||||
{
|
||||
const AVPixFmtDescriptor *desc =
|
||||
av_pix_fmt_desc_get(static_cast<AVPixelFormat>(pix_fmt));
|
||||
if (!desc) {
|
||||
return 0;
|
||||
}
|
||||
return av_get_bits_per_pixel(desc);
|
||||
}
|
||||
|
||||
int fb_pix_fmt_has_alpha(int pix_fmt)
|
||||
{
|
||||
const AVPixFmtDescriptor *desc =
|
||||
av_pix_fmt_desc_get(static_cast<AVPixelFormat>(pix_fmt));
|
||||
return desc && (desc->flags & AV_PIX_FMT_FLAG_ALPHA);
|
||||
}
|
||||
|
||||
int fb_pix_fmt_is_planar(int pix_fmt)
|
||||
{
|
||||
const AVPixFmtDescriptor *desc =
|
||||
av_pix_fmt_desc_get(static_cast<AVPixelFormat>(pix_fmt));
|
||||
return desc && (desc->flags & AV_PIX_FMT_FLAG_PLANAR);
|
||||
}
|
||||
|
||||
int fb_pix_fmt_component_size(int pix_fmt)
|
||||
{
|
||||
const AVPixFmtDescriptor *desc =
|
||||
av_pix_fmt_desc_get(static_cast<AVPixelFormat>(pix_fmt));
|
||||
if (!desc || desc->nb_components == 0) {
|
||||
return 0;
|
||||
}
|
||||
return desc->comp[0].step;
|
||||
}
|
||||
|
||||
int fb_find_best_pix_fmt_of_list(const int *list, int pix_fmt)
|
||||
{
|
||||
// Count the list
|
||||
int count = 0;
|
||||
while (list[count] != FB_PIX_FMT_NONE) {
|
||||
count++;
|
||||
}
|
||||
|
||||
return avcodec_find_best_pix_fmt_of_list(
|
||||
reinterpret_cast<const AVPixelFormat *>(list),
|
||||
static_cast<AVPixelFormat>(pix_fmt), 1, nullptr);
|
||||
}
|
||||
|
||||
int fb_channel_layout_get_channels(uint64_t mask)
|
||||
{
|
||||
AVChannelLayout layout;
|
||||
av_channel_layout_from_mask(&layout, mask);
|
||||
int channels = layout.nb_channels;
|
||||
av_channel_layout_uninit(&layout);
|
||||
return channels;
|
||||
}
|
||||
|
||||
uint64_t fb_channel_layout_default(int nb_channels)
|
||||
{
|
||||
AVChannelLayout layout;
|
||||
av_channel_layout_default(&layout, nb_channels);
|
||||
uint64_t mask = 0;
|
||||
if (layout.order == AV_CHANNEL_ORDER_NATIVE) {
|
||||
mask = layout.u.mask;
|
||||
}
|
||||
av_channel_layout_uninit(&layout);
|
||||
return mask;
|
||||
}
|
||||
Reference in New Issue
Block a user