Add Chromium-only Blender WebEngine parity work
This commit is contained in:
67
blender-5.2.0/source/blender/imbuf/movie/CMakeLists.txt
Normal file
67
blender-5.2.0/source/blender/imbuf/movie/CMakeLists.txt
Normal file
@@ -0,0 +1,67 @@
|
||||
# SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
set(INC
|
||||
PUBLIC .
|
||||
)
|
||||
|
||||
set(INC_SYS
|
||||
)
|
||||
|
||||
set(SRC
|
||||
MOV_enums.hh
|
||||
MOV_read.hh
|
||||
MOV_util.hh
|
||||
MOV_write.hh
|
||||
|
||||
intern/ffmpeg_compat.h
|
||||
intern/movie_proxy_indexer.cc
|
||||
intern/movie_proxy_indexer.hh
|
||||
intern/movie_read.cc
|
||||
intern/movie_read.hh
|
||||
intern/movie_util.cc
|
||||
intern/movie_util.hh
|
||||
intern/movie_write.cc
|
||||
intern/movie_write.hh
|
||||
intern/movie_write_audio.cc
|
||||
)
|
||||
|
||||
set(LIB
|
||||
PRIVATE bf::blenkernel
|
||||
PRIVATE bf::blenlib
|
||||
PUBLIC bf::imbuf
|
||||
PRIVATE bf::intern::guardedalloc
|
||||
PRIVATE bf::intern::clog
|
||||
PRIVATE bf::dependencies::optional::ffmpeg
|
||||
PRIVATE bf::dependencies::optional::audaspace
|
||||
)
|
||||
|
||||
if(WITH_CODEC_FFMPEG)
|
||||
list(APPEND SRC
|
||||
intern/ffmpeg_swscale.cc
|
||||
intern/ffmpeg_swscale.hh
|
||||
)
|
||||
endif()
|
||||
|
||||
blender_add_lib(bf_imbuf_movie "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
|
||||
add_library(bf::imbuf::movie ALIAS bf_imbuf_movie)
|
||||
|
||||
if(WITH_GTESTS AND WITH_CODEC_FFMPEG)
|
||||
set(TEST_SRC
|
||||
tests/ffmpeg_codecs.cc
|
||||
tests/ffmpeg_cpu_flags.cc
|
||||
)
|
||||
set(TEST_INC
|
||||
intern
|
||||
)
|
||||
set(TEST_INC_SYS
|
||||
)
|
||||
set(TEST_LIB
|
||||
${LIB}
|
||||
PRIVATE bf::dependencies::optional::ffmpeg
|
||||
PRIVATE bf::dependencies::zlib
|
||||
)
|
||||
blender_add_test_suite_lib(ffmpeg_libs "${TEST_SRC}" "${TEST_INC}" "${TEST_INC_SYS}" "${TEST_LIB}")
|
||||
|
||||
endif()
|
||||
38
blender-5.2.0/source/blender/imbuf/movie/MOV_enums.hh
Normal file
38
blender-5.2.0/source/blender/imbuf/movie/MOV_enums.hh
Normal file
@@ -0,0 +1,38 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup imbuf
|
||||
*/
|
||||
|
||||
namespace blender {
|
||||
|
||||
enum {
|
||||
FFMPEG_MPEG1 = 0,
|
||||
FFMPEG_MPEG2 = 1,
|
||||
FFMPEG_MPEG4 = 2,
|
||||
FFMPEG_AVI = 3,
|
||||
FFMPEG_MOV = 4,
|
||||
FFMPEG_DV = 5,
|
||||
FFMPEG_H264 = 6,
|
||||
FFMPEG_XVID = 7,
|
||||
FFMPEG_FLV = 8,
|
||||
FFMPEG_MKV = 9,
|
||||
FFMPEG_OGG = 10,
|
||||
FFMPEG_INVALID = 11,
|
||||
FFMPEG_WEBM = 12,
|
||||
FFMPEG_AV1 = 13,
|
||||
};
|
||||
|
||||
enum {
|
||||
FFMPEG_PRESET_NONE = 0,
|
||||
FFMPEG_PRESET_H264 = 1,
|
||||
FFMPEG_PRESET_THEORA = 2,
|
||||
FFMPEG_PRESET_XVID = 3,
|
||||
FFMPEG_PRESET_AV1 = 4,
|
||||
};
|
||||
|
||||
} // namespace blender
|
||||
194
blender-5.2.0/source/blender/imbuf/movie/MOV_read.hh
Normal file
194
blender-5.2.0/source/blender/imbuf/movie/MOV_read.hh
Normal file
@@ -0,0 +1,194 @@
|
||||
/* SPDX-FileCopyrightText: 2024-2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup imbuf
|
||||
*
|
||||
* Movie file reading / playback functions.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IMB_imbuf_enums.h"
|
||||
|
||||
#include "BLI_function_ref.hh"
|
||||
#include "BLI_set.hh"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct IDProperty;
|
||||
struct ImBuf;
|
||||
struct MovieReader;
|
||||
struct MovieProxyBuilder;
|
||||
|
||||
/**
|
||||
* Opens a movie file for reading / playback.
|
||||
* From ib_flags only ImBufFlags::Deinterlace is taken into account.
|
||||
* streamindex is for multi-track movie files.
|
||||
*
|
||||
* Returned MovieReader object can be used in other playback related functions.
|
||||
* Note that a valid object will be returned even if file does not exist or is
|
||||
* not a video file. The actual initialization is delayed until
|
||||
* #MOV_decode_frame is called.
|
||||
*
|
||||
* When done with playback, use #MOV_close to delete it.
|
||||
*/
|
||||
MovieReader *MOV_open_file(const char *filepath,
|
||||
ImBufFlags ib_flags,
|
||||
int streamindex,
|
||||
bool keep_original_colorspace,
|
||||
char colorspace[IM_MAX_SPACE]);
|
||||
|
||||
/**
|
||||
* Release memory and other resources associated with movie playback.
|
||||
*/
|
||||
void MOV_close(MovieReader *anim);
|
||||
|
||||
/**
|
||||
* Fetches a frame from a movie at given frame position.
|
||||
*
|
||||
* Internally this will seek within the movie as/if needed. For most movie
|
||||
* files, decoding frames sequentially is much more efficient than decoding
|
||||
* random frames.
|
||||
*
|
||||
* If proxy_size is not IMB_PROXY_NONE, a proxy file of given size will
|
||||
* be attempted. If it exists, the frame will be decoded from it. If the
|
||||
* proxy does not exist, original file will be used.
|
||||
*
|
||||
* Movies that are <= 8 bits/color channel are returned as byte images;
|
||||
* higher bit depth movies are returned as float images. Note that the
|
||||
* color space is returned as-is, i.e. a float image might not be in
|
||||
* linear space.
|
||||
*
|
||||
* Returned image can be null if movie file does not exist, is not supported
|
||||
* or failed decoding.
|
||||
*/
|
||||
ImBuf *MOV_decode_frame(MovieReader *anim,
|
||||
int position,
|
||||
IMB_Proxy_Size preview_size /* = 0 = IMB_PROXY_NONE */);
|
||||
|
||||
/**
|
||||
* Fetches a frame from a movie used for preview/thumbnails.
|
||||
* The frame will be halfway into the file duration.
|
||||
* Thumbnail related metadata ("Thumb::Video::*") will be set on the
|
||||
* returned image.
|
||||
*/
|
||||
ImBuf *MOV_decode_preview_frame(MovieReader *anim);
|
||||
|
||||
/**
|
||||
* Returns the number of video streams in the movie file backing `anim`.
|
||||
* Returns 0 if the file cannot be opened or contains no video streams.
|
||||
*/
|
||||
int MOV_get_video_stream_count(MovieReader *anim);
|
||||
|
||||
/**
|
||||
* Return the length (in frames) of the movie.
|
||||
*/
|
||||
int MOV_get_duration_frames(const MovieReader *anim);
|
||||
|
||||
/**
|
||||
* Return the encoded start offset (in seconds) of the movie.
|
||||
*/
|
||||
double MOV_get_start_offset_seconds(const MovieReader *anim);
|
||||
|
||||
/**
|
||||
* Returns the frames per second of the movie, or zero if
|
||||
* the information is not available. Note that if you want the
|
||||
* most accurate representation, use #MOV_get_fps_num_denom.
|
||||
*/
|
||||
float MOV_get_fps(const MovieReader *anim);
|
||||
|
||||
/**
|
||||
* Returns the frames per second of the movie as numerator and
|
||||
* denominator. False will be returned if the information is
|
||||
* not available.
|
||||
*/
|
||||
bool MOV_get_fps_num_denom(const MovieReader *anim, short &r_fps_num, float &r_fps_denom);
|
||||
|
||||
/**
|
||||
* Get movie image width in pixels.
|
||||
*/
|
||||
int MOV_get_image_width(const MovieReader *anim);
|
||||
|
||||
/**
|
||||
* Get movie image height in pixels.
|
||||
*/
|
||||
int MOV_get_image_height(const MovieReader *anim);
|
||||
|
||||
/**
|
||||
* Returns true if movie playback has been fully initialized
|
||||
* and is supported. Note that immediately after #MOV_open_file
|
||||
* the playback is not initialized yet.
|
||||
*/
|
||||
bool MOV_is_initialized_and_valid(const MovieReader *anim);
|
||||
|
||||
/**
|
||||
* Gets filename (without the folder) part of the movie.
|
||||
*/
|
||||
void MOV_get_filename(const MovieReader *anim, char *filename, int filename_maxncpy);
|
||||
|
||||
/**
|
||||
* Loads metadata of the movie.
|
||||
* Metadata is only loaded for already initialized movies.
|
||||
*/
|
||||
IDProperty *MOV_load_metadata(MovieReader *anim);
|
||||
|
||||
/**
|
||||
* Sets multi-view suffix to be used when building proxies for this movie.
|
||||
*/
|
||||
void MOV_set_multiview_suffix(MovieReader *anim, const char *suffix);
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Movie proxy related functionality
|
||||
* \{ */
|
||||
|
||||
/**
|
||||
* Close any internally opened proxies of this movie.
|
||||
*/
|
||||
void MOV_close_proxies(MovieReader *anim);
|
||||
|
||||
/**
|
||||
* Custom directory to be used for loading or building proxies.
|
||||
* By default "BL_proxy" within the directory of the movie file is used.
|
||||
*/
|
||||
void MOV_set_custom_proxy_dir(MovieReader *anim, const char *dir);
|
||||
|
||||
/**
|
||||
* Queries which proxies exist for this movie.
|
||||
*
|
||||
* Note that it does not check whether proxies are up to date,
|
||||
* or valid files; just merely whether the expected files exist.
|
||||
*
|
||||
* Returns bitmask of #IMB_Proxy_Size flags.
|
||||
*/
|
||||
int MOV_get_existing_proxies(const MovieReader *anim);
|
||||
|
||||
/**
|
||||
* Initialize movie proxies builder.
|
||||
*/
|
||||
MovieProxyBuilder *MOV_proxy_builder_start(MovieReader *anim,
|
||||
int proxy_sizes_in_use,
|
||||
int quality,
|
||||
const bool overwrite,
|
||||
Set<std::string> *processed_paths,
|
||||
bool build_only_on_bad_performance);
|
||||
|
||||
/**
|
||||
* Will rebuild all used proxies at once.
|
||||
*/
|
||||
void MOV_proxy_builder_process(MovieProxyBuilder *context,
|
||||
const bool *stop,
|
||||
bool *do_update,
|
||||
blender::FunctionRef<void(float progress)> set_progress_fn);
|
||||
|
||||
/**
|
||||
* Finish building proxies, and delete the builder.
|
||||
*/
|
||||
void MOV_proxy_builder_finish(MovieProxyBuilder *context, bool stop);
|
||||
|
||||
/** \} */
|
||||
|
||||
} // namespace blender
|
||||
56
blender-5.2.0/source/blender/imbuf/movie/MOV_util.hh
Normal file
56
blender-5.2.0/source/blender/imbuf/movie/MOV_util.hh
Normal file
@@ -0,0 +1,56 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup imbuf
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DNA_scene_types.h"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct FFMpegCodecData;
|
||||
struct ImageFormatData;
|
||||
struct RenderData;
|
||||
|
||||
/** Global initialization of movie support. */
|
||||
void MOV_init();
|
||||
|
||||
/** Global de-initialization of movie support. */
|
||||
void MOV_exit();
|
||||
|
||||
/**
|
||||
* Test if the file is a video file (known format, has a video stream and
|
||||
* supported video codec). Note that this can be pretty expensive: it is
|
||||
* not just a file extension check, it will literally try to decode
|
||||
* file headers and find whether it is a video file with some supported
|
||||
* codec.
|
||||
*/
|
||||
bool MOV_is_movie_file(const char *filepath);
|
||||
|
||||
/** Checks whether given FFMPEG codec and profile combination supports alpha channel (RGBA). */
|
||||
bool MOV_codec_supports_alpha(IMB_Ffmpeg_Codec_ID codec_id, int ffmpeg_profile);
|
||||
|
||||
/**
|
||||
* Checks whether given FFMPEG video AVCodecID supports CRF (i.e. "quality level")
|
||||
* setting. For codecs that do not support constant quality, only target bit-rate
|
||||
* can be specified.
|
||||
*/
|
||||
bool MOV_codec_supports_crf(IMB_Ffmpeg_Codec_ID codec_id);
|
||||
|
||||
/**
|
||||
* Which pixel bit depths are supported by a given FFMPEG video CodecID.
|
||||
* Returns bit-mask of `R_IMF_CHAN_DEPTH_` flags.
|
||||
*/
|
||||
eImageFormatDepth MOV_codec_valid_bit_depths(IMB_Ffmpeg_Codec_ID codec_id);
|
||||
|
||||
/**
|
||||
* Given desired output image format type, sets up required FFMPEG
|
||||
* related settings in render data.
|
||||
*/
|
||||
void MOV_validate_output_settings(RenderData *rd, const ImageFormatData *imf);
|
||||
|
||||
} // namespace blender
|
||||
50
blender-5.2.0/source/blender/imbuf/movie/MOV_write.hh
Normal file
50
blender-5.2.0/source/blender/imbuf/movie/MOV_write.hh
Normal file
@@ -0,0 +1,50 @@
|
||||
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
|
||||
* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup imbuf
|
||||
*/
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct ImageFormatData;
|
||||
struct ImBuf;
|
||||
struct MovieWriter;
|
||||
struct RenderData;
|
||||
struct ReportList;
|
||||
struct Scene;
|
||||
|
||||
MovieWriter *MOV_write_begin(const Scene *scene,
|
||||
const RenderData *rd,
|
||||
const ImageFormatData *imf,
|
||||
int rectx,
|
||||
int recty,
|
||||
ReportList *reports,
|
||||
bool preview,
|
||||
const char *suffix);
|
||||
bool MOV_write_append(MovieWriter *writer,
|
||||
const Scene *scene,
|
||||
const RenderData *rd,
|
||||
const ImageFormatData *imf,
|
||||
int start_frame,
|
||||
int frame,
|
||||
const ImBuf *image,
|
||||
const char *suffix,
|
||||
ReportList *reports);
|
||||
void MOV_write_end(MovieWriter *writer);
|
||||
|
||||
/**
|
||||
* \note Similar to #BKE_image_path_from_imformat()
|
||||
*/
|
||||
void MOV_filepath_from_settings(char filepath[/*FILE_MAX*/ 1024],
|
||||
const Scene *scene,
|
||||
const RenderData *rd,
|
||||
bool preview,
|
||||
const char *suffix,
|
||||
ReportList *reports);
|
||||
|
||||
} // namespace blender
|
||||
254
blender-5.2.0/source/blender/imbuf/movie/intern/ffmpeg_compat.h
Normal file
254
blender-5.2.0/source/blender/imbuf/movie/intern/ffmpeg_compat.h
Normal file
@@ -0,0 +1,254 @@
|
||||
/* SPDX-FileCopyrightText: 2011 Peter Schlaile
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ffmpeg
|
||||
*
|
||||
* Compatibility macros to make every FFMPEG installation appear
|
||||
* like the most current installation (wrapping some functionality sometimes)
|
||||
* it also includes all FFMPEG header files at once, no need to do it
|
||||
* separately.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavutil/cpu.h>
|
||||
#include <libavutil/display.h>
|
||||
#include <libswscale/swscale.h>
|
||||
|
||||
/* Check if our FFMPEG is new enough, avoids user complaints.
|
||||
* Minimum supported version is currently 3.2.0 which mean the following library versions:
|
||||
* `libavutil` > 55.30
|
||||
* `libavcodec` > 57.60
|
||||
* `libavformat` > 57.50
|
||||
*
|
||||
* We only check for one of these as they are usually updated in tandem.
|
||||
*/
|
||||
#if (LIBAVFORMAT_VERSION_MAJOR < 57) || \
|
||||
((LIBAVFORMAT_VERSION_MAJOR == 57) && (LIBAVFORMAT_VERSION_MINOR <= 50))
|
||||
# error "FFmpeg 3.2.0 or newer is needed, Upgrade your FFmpeg or disable it"
|
||||
#endif
|
||||
/* end sanity check */
|
||||
|
||||
/* visual studio 2012 does not define inline for C */
|
||||
#ifdef _MSC_VER
|
||||
# define FFMPEG_INLINE static __inline
|
||||
#else
|
||||
# define FFMPEG_INLINE static inline
|
||||
#endif
|
||||
|
||||
#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(58, 29, 100)
|
||||
/* In FFMPEG 6.1 usage of the "key_frame" variable from "AVFrame" has been deprecated.
|
||||
* used the new method to query for the "AV_FRAME_FLAG_KEY" flag instead.
|
||||
*/
|
||||
# define FFMPEG_OLD_KEY_FRAME_QUERY_METHOD
|
||||
#endif
|
||||
|
||||
#if (LIBAVFORMAT_VERSION_MAJOR < 59)
|
||||
/* For versions older than FFMPEG 5.0, use the old channel layout variables.
|
||||
* We intend to only keep this workaround for around two releases (3.5, 3.6).
|
||||
* If it sticks around any longer, then we should consider refactoring this.
|
||||
*/
|
||||
# define FFMPEG_USE_OLD_CHANNEL_VARS
|
||||
#endif
|
||||
|
||||
/* Threaded sws_scale_frame was added in FFMPEG 5.0 (`swscale` version 6.1). */
|
||||
#if (LIBSWSCALE_VERSION_INT >= AV_VERSION_INT(6, 1, 100))
|
||||
# define FFMPEG_SWSCALE_THREADING
|
||||
#endif
|
||||
|
||||
/* AV_CODEC_CAP_AUTO_THREADS was renamed to AV_CODEC_CAP_OTHER_THREADS with
|
||||
* upstream commit
|
||||
* `github.com/FFmpeg/FFmpeg/commit/7d09579190def3ef7562399489e628f3b65714ce`
|
||||
* (`lavc` 58.132.100) and removed with commit
|
||||
* `github.com/FFmpeg/FFmpeg/commit/10c9a0874cb361336237557391d306d26d43f137`
|
||||
* for FFMPEG 6.0.
|
||||
*/
|
||||
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 132, 100)
|
||||
# define AV_CODEC_CAP_OTHER_THREADS AV_CODEC_CAP_AUTO_THREADS
|
||||
#endif
|
||||
|
||||
#if (LIBAVFORMAT_VERSION_MAJOR < 58) || \
|
||||
((LIBAVFORMAT_VERSION_MAJOR == 58) && (LIBAVFORMAT_VERSION_MINOR < 76))
|
||||
# define FFMPEG_USE_DURATION_WORKAROUND 1
|
||||
|
||||
/* Before FFMPEG 4.4, package duration calculation used deprecated variables to calculate the
|
||||
* packet duration. Use the function from commit
|
||||
* `github.com/FFmpeg/FFmpeg/commit/1c0885334dda9ee8652e60c586fa2e3674056586`
|
||||
* to calculate the correct frame-rate for FFMPEG < 4.4.
|
||||
*/
|
||||
|
||||
FFMPEG_INLINE
|
||||
void my_guess_pkt_duration(AVFormatContext *s, AVStream *st, AVPacket *pkt)
|
||||
{
|
||||
if (pkt->duration < 0 && st->codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) {
|
||||
av_log(s,
|
||||
AV_LOG_WARNING,
|
||||
"Packet with invalid duration %" PRId64 " in stream %d\n",
|
||||
pkt->duration,
|
||||
pkt->stream_index);
|
||||
pkt->duration = 0;
|
||||
}
|
||||
|
||||
if (pkt->duration) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (st->codecpar->codec_type) {
|
||||
case AVMEDIA_TYPE_VIDEO:
|
||||
if (st->avg_frame_rate.num > 0 && st->avg_frame_rate.den > 0) {
|
||||
pkt->duration = av_rescale_q(1, av_inv_q(st->avg_frame_rate), st->time_base);
|
||||
}
|
||||
else if (st->time_base.num * 1000LL > st->time_base.den) {
|
||||
pkt->duration = 1;
|
||||
}
|
||||
break;
|
||||
case AVMEDIA_TYPE_AUDIO: {
|
||||
int frame_size = av_get_audio_frame_duration2(st->codecpar, pkt->size);
|
||||
if (frame_size && st->codecpar->sample_rate) {
|
||||
pkt->duration = av_rescale_q(
|
||||
frame_size, (AVRational){1, st->codecpar->sample_rate}, st->time_base);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
FFMPEG_INLINE
|
||||
int64_t timestamp_from_pts_or_dts(int64_t pts, int64_t dts)
|
||||
{
|
||||
/* Some videos do not have any pts values, use dts instead in those cases if
|
||||
* possible. Usually when this happens dts can act as pts because as all frames
|
||||
* should then be presented in their decoded in order. IE pts == dts. */
|
||||
if (pts == AV_NOPTS_VALUE) {
|
||||
return dts;
|
||||
}
|
||||
return pts;
|
||||
}
|
||||
|
||||
FFMPEG_INLINE
|
||||
int64_t av_get_pts_from_frame(AVFrame *picture)
|
||||
{
|
||||
return timestamp_from_pts_or_dts(picture->pts, picture->pkt_dts);
|
||||
}
|
||||
|
||||
/* Duration of the frame, in the same units as pts. 0 if unknown. */
|
||||
FFMPEG_INLINE
|
||||
int64_t av_get_frame_duration_in_pts_units(const AVFrame *picture)
|
||||
{
|
||||
#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 30, 100)
|
||||
return picture->pkt_duration;
|
||||
#else
|
||||
return picture->duration;
|
||||
#endif
|
||||
}
|
||||
|
||||
FFMPEG_INLINE size_t ffmpeg_get_buffer_alignment()
|
||||
{
|
||||
/* NOTE: even if av_frame_get_buffer suggests to pass 0 for alignment,
|
||||
* as of FFMPEG 6.1/7.0 it does not use correct alignment for AVX512
|
||||
* CPU (frame.c get_video_buffer ends up always using 32 alignment,
|
||||
* whereas it should have used 64). Reported upstream:
|
||||
* https://trac.ffmpeg.org/ticket/11116 and the fix on their code
|
||||
* side is to use 64 byte alignment as soon as AVX512 is compiled
|
||||
* in (even if CPU might not support it). So play safe and
|
||||
* use at least 64 byte alignment here too. Currently larger than
|
||||
* 64 alignment would not happen anywhere, but keep on querying
|
||||
* av_cpu_max_align just in case some future platform might. */
|
||||
size_t align = av_cpu_max_align();
|
||||
if (align < 64) {
|
||||
align = 64;
|
||||
}
|
||||
return align;
|
||||
}
|
||||
|
||||
FFMPEG_INLINE void ffmpeg_copy_display_matrix(const AVStream *src, AVStream *dst)
|
||||
{
|
||||
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(60, 29, 100)
|
||||
const AVPacketSideData *src_matrix = av_packet_side_data_get(src->codecpar->coded_side_data,
|
||||
src->codecpar->nb_coded_side_data,
|
||||
AV_PKT_DATA_DISPLAYMATRIX);
|
||||
if (src_matrix != nullptr) {
|
||||
uint8_t *dst_matrix = (uint8_t *)av_memdup(src_matrix->data, src_matrix->size);
|
||||
av_packet_side_data_add(&dst->codecpar->coded_side_data,
|
||||
&dst->codecpar->nb_coded_side_data,
|
||||
AV_PKT_DATA_DISPLAYMATRIX,
|
||||
dst_matrix,
|
||||
src_matrix->size,
|
||||
0);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
FFMPEG_INLINE int ffmpeg_get_video_rotation(const AVStream *stream)
|
||||
{
|
||||
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(60, 29, 100)
|
||||
const AVPacketSideData *src_matrix = av_packet_side_data_get(
|
||||
stream->codecpar->coded_side_data,
|
||||
stream->codecpar->nb_coded_side_data,
|
||||
AV_PKT_DATA_DISPLAYMATRIX);
|
||||
if (src_matrix != nullptr) {
|
||||
/* ffmpeg reports rotation in [-180..+180] range; our image rotation
|
||||
* uses different direction and [0..360] range. */
|
||||
double theta = -av_display_rotation_get((const int32_t *)src_matrix->data);
|
||||
if (theta < 0.0) {
|
||||
theta += 360.0;
|
||||
}
|
||||
return int(theta);
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(61, 13, 100)
|
||||
FFMPEG_INLINE const enum AVPixelFormat *ffmpeg_get_pix_fmts(struct AVCodecContext *context,
|
||||
const AVCodec *codec)
|
||||
{
|
||||
const enum AVPixelFormat *pix_fmts = nullptr;
|
||||
avcodec_get_supported_config(
|
||||
context, codec, AV_CODEC_CONFIG_PIX_FORMAT, 0, (const void **)&pix_fmts, nullptr);
|
||||
return pix_fmts;
|
||||
}
|
||||
|
||||
FFMPEG_INLINE const enum AVSampleFormat *ffmpeg_get_sample_fmts(struct AVCodecContext *context,
|
||||
const AVCodec *codec)
|
||||
{
|
||||
const enum AVSampleFormat *sample_fmts = nullptr;
|
||||
avcodec_get_supported_config(
|
||||
context, codec, AV_CODEC_CONFIG_SAMPLE_FORMAT, 0, (const void **)&sample_fmts, nullptr);
|
||||
return sample_fmts;
|
||||
}
|
||||
|
||||
FFMPEG_INLINE const int *ffmpeg_get_sample_rates(struct AVCodecContext *context,
|
||||
const AVCodec *codec)
|
||||
{
|
||||
const int *sample_rates = nullptr;
|
||||
avcodec_get_supported_config(
|
||||
context, codec, AV_CODEC_CONFIG_SAMPLE_RATE, 0, (const void **)&sample_rates, nullptr);
|
||||
return sample_rates;
|
||||
}
|
||||
#else
|
||||
FFMPEG_INLINE const enum AVPixelFormat *ffmpeg_get_pix_fmts(struct AVCodecContext * /*context*/,
|
||||
const AVCodec *codec)
|
||||
{
|
||||
return codec->pix_fmts;
|
||||
}
|
||||
|
||||
FFMPEG_INLINE const enum AVSampleFormat *ffmpeg_get_sample_fmts(
|
||||
struct AVCodecContext * /*context*/, const AVCodec *codec)
|
||||
{
|
||||
return codec->sample_fmts;
|
||||
}
|
||||
|
||||
FFMPEG_INLINE const int *ffmpeg_get_sample_rates(struct AVCodecContext * /*context*/,
|
||||
const AVCodec *codec)
|
||||
{
|
||||
return codec->supported_samplerates;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,271 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup imbuf
|
||||
*/
|
||||
|
||||
#ifdef WITH_FFMPEG
|
||||
# include "ffmpeg_swscale.hh"
|
||||
|
||||
# include <cstdint>
|
||||
# include <mutex>
|
||||
|
||||
# include "BLI_mutex.hh"
|
||||
# include "BLI_vector.hh"
|
||||
|
||||
# include "movie_util.hh"
|
||||
|
||||
extern "C" {
|
||||
# include <libavutil/opt.h>
|
||||
# include <libavutil/pixfmt.h>
|
||||
# include <libswscale/swscale.h>
|
||||
|
||||
# include "ffmpeg_compat.h"
|
||||
}
|
||||
|
||||
namespace blender {
|
||||
|
||||
/* libswscale context creation and destruction is expensive.
|
||||
* Maintain a cache of already created contexts. */
|
||||
|
||||
static constexpr int64_t swscale_cache_max_entries = 32;
|
||||
|
||||
struct SwscaleContext {
|
||||
int src_width = 0, src_height = 0;
|
||||
int dst_width = 0, dst_height = 0;
|
||||
AVPixelFormat src_format = AV_PIX_FMT_NONE, dst_format = AV_PIX_FMT_NONE;
|
||||
bool src_full_range = false, dst_full_range = false;
|
||||
int src_colorspace = -1, dst_colorspace = -1;
|
||||
int flags = 0;
|
||||
|
||||
SwsContext *context = nullptr;
|
||||
int64_t last_use_timestamp = 0;
|
||||
bool is_used = false;
|
||||
};
|
||||
|
||||
static Mutex swscale_cache_lock;
|
||||
static int64_t swscale_cache_timestamp = 0;
|
||||
static Vector<SwscaleContext> *swscale_cache = nullptr;
|
||||
|
||||
static SwsContext *sws_create_context(int src_width,
|
||||
int src_height,
|
||||
int av_src_format,
|
||||
int dst_width,
|
||||
int dst_height,
|
||||
int av_dst_format,
|
||||
int sws_flags)
|
||||
{
|
||||
# if defined(FFMPEG_SWSCALE_THREADING)
|
||||
/* sws_getContext does not allow passing flags that ask for multi-threaded
|
||||
* scaling context, so do it the hard way. */
|
||||
SwsContext *c = sws_alloc_context();
|
||||
if (c == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
av_opt_set_int(c, "srcw", src_width, 0);
|
||||
av_opt_set_int(c, "srch", src_height, 0);
|
||||
av_opt_set_int(c, "src_format", av_src_format, 0);
|
||||
av_opt_set_int(c, "dstw", dst_width, 0);
|
||||
av_opt_set_int(c, "dsth", dst_height, 0);
|
||||
av_opt_set_int(c, "dst_format", av_dst_format, 0);
|
||||
av_opt_set_int(c, "sws_flags", sws_flags, 0);
|
||||
av_opt_set_int(c, "threads", MOV_thread_count(), 0);
|
||||
|
||||
if (sws_init_context(c, nullptr, nullptr) < 0) {
|
||||
sws_freeContext(c);
|
||||
return nullptr;
|
||||
}
|
||||
# else
|
||||
SwsContext *c = sws_getContext(src_width,
|
||||
src_height,
|
||||
AVPixelFormat(av_src_format),
|
||||
dst_width,
|
||||
dst_height,
|
||||
AVPixelFormat(av_dst_format),
|
||||
sws_flags,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr);
|
||||
# endif
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
static void init_swscale_cache_if_needed()
|
||||
{
|
||||
if (swscale_cache == nullptr) {
|
||||
swscale_cache = new Vector<SwscaleContext>();
|
||||
swscale_cache_timestamp = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static bool remove_oldest_swscale_context()
|
||||
{
|
||||
int64_t oldest_index = -1;
|
||||
int64_t oldest_time = 0;
|
||||
for (int64_t index = 0; index < swscale_cache->size(); index++) {
|
||||
SwscaleContext &ctx = (*swscale_cache)[index];
|
||||
if (ctx.is_used) {
|
||||
continue;
|
||||
}
|
||||
int64_t time = swscale_cache_timestamp - ctx.last_use_timestamp;
|
||||
if (time > oldest_time) {
|
||||
oldest_time = time;
|
||||
oldest_index = index;
|
||||
}
|
||||
}
|
||||
|
||||
if (oldest_index >= 0) {
|
||||
SwscaleContext &ctx = (*swscale_cache)[oldest_index];
|
||||
sws_freeContext(ctx.context);
|
||||
swscale_cache->remove_and_reorder(oldest_index);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void maintain_swscale_cache_size()
|
||||
{
|
||||
while (swscale_cache->size() > swscale_cache_max_entries) {
|
||||
if (!remove_oldest_swscale_context()) {
|
||||
/* Could not remove anything (all contexts are actively used),
|
||||
* stop trying. */
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SwsContext *ffmpeg_sws_get_context(int src_width,
|
||||
int src_height,
|
||||
int av_src_format,
|
||||
bool src_full_range,
|
||||
int src_color_space,
|
||||
int dst_width,
|
||||
int dst_height,
|
||||
int av_dst_format,
|
||||
bool dst_full_range,
|
||||
int dst_color_space,
|
||||
int sws_flags)
|
||||
{
|
||||
std::lock_guard lock(swscale_cache_lock);
|
||||
|
||||
init_swscale_cache_if_needed();
|
||||
|
||||
swscale_cache_timestamp++;
|
||||
|
||||
/* Search for unused context that has suitable parameters. */
|
||||
SwsContext *ctx = nullptr;
|
||||
for (SwscaleContext &c : *swscale_cache) {
|
||||
if (!c.is_used && c.src_width == src_width && c.src_height == src_height &&
|
||||
c.src_format == av_src_format && c.src_full_range == src_full_range &&
|
||||
c.src_colorspace == src_color_space && c.dst_width == dst_width &&
|
||||
c.dst_height == dst_height && c.dst_format == av_dst_format &&
|
||||
c.dst_full_range == dst_full_range && c.dst_colorspace == dst_color_space &&
|
||||
c.flags == sws_flags)
|
||||
{
|
||||
ctx = c.context;
|
||||
/* Mark as used. */
|
||||
c.is_used = true;
|
||||
c.last_use_timestamp = swscale_cache_timestamp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ctx == nullptr) {
|
||||
/* No free matching context in cache: create a new one. */
|
||||
ctx = sws_create_context(
|
||||
src_width, src_height, av_src_format, dst_width, dst_height, av_dst_format, sws_flags);
|
||||
|
||||
int src_range, dst_range, brightness, contrast, saturation;
|
||||
const int *table, *inv_table;
|
||||
if (sws_getColorspaceDetails(ctx,
|
||||
(int **)&inv_table,
|
||||
&src_range,
|
||||
(int **)&table,
|
||||
&dst_range,
|
||||
&brightness,
|
||||
&contrast,
|
||||
&saturation) >= 0)
|
||||
{
|
||||
if (src_full_range) {
|
||||
src_range = 1;
|
||||
}
|
||||
if (dst_full_range) {
|
||||
dst_range = 1;
|
||||
}
|
||||
if (src_color_space >= 0) {
|
||||
inv_table = sws_getCoefficients(src_color_space);
|
||||
}
|
||||
if (dst_color_space >= 0) {
|
||||
table = sws_getCoefficients(dst_color_space);
|
||||
}
|
||||
sws_setColorspaceDetails(
|
||||
ctx, (int *)inv_table, src_range, table, dst_range, brightness, contrast, saturation);
|
||||
}
|
||||
|
||||
SwscaleContext c;
|
||||
c.src_width = src_width;
|
||||
c.src_height = src_height;
|
||||
c.dst_width = dst_width;
|
||||
c.dst_height = dst_height;
|
||||
c.src_format = AVPixelFormat(av_src_format);
|
||||
c.dst_format = AVPixelFormat(av_dst_format);
|
||||
c.src_full_range = src_full_range;
|
||||
c.dst_full_range = dst_full_range;
|
||||
c.src_colorspace = src_color_space;
|
||||
c.dst_colorspace = dst_color_space;
|
||||
c.flags = sws_flags;
|
||||
c.context = ctx;
|
||||
c.is_used = true;
|
||||
c.last_use_timestamp = swscale_cache_timestamp;
|
||||
swscale_cache->append(c);
|
||||
|
||||
maintain_swscale_cache_size();
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
void ffmpeg_sws_release_context(SwsContext *ctx)
|
||||
{
|
||||
std::lock_guard lock(swscale_cache_lock);
|
||||
init_swscale_cache_if_needed();
|
||||
|
||||
bool found = false;
|
||||
for (SwscaleContext &c : *swscale_cache) {
|
||||
if (c.context == ctx) {
|
||||
BLI_assert_msg(c.is_used, "Releasing ffmpeg swscale context that is not in use");
|
||||
c.is_used = false;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
BLI_assert_msg(found, "Releasing ffmpeg swscale context that is not in cache");
|
||||
UNUSED_VARS_NDEBUG(found);
|
||||
maintain_swscale_cache_size();
|
||||
}
|
||||
|
||||
void ffmpeg_sws_exit()
|
||||
{
|
||||
std::lock_guard lock(swscale_cache_lock);
|
||||
if (swscale_cache != nullptr) {
|
||||
for (SwscaleContext &c : *swscale_cache) {
|
||||
sws_freeContext(c.context);
|
||||
}
|
||||
delete swscale_cache;
|
||||
swscale_cache = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void ffmpeg_sws_scale_frame(SwsContext *ctx, AVFrame *dst, const AVFrame *src)
|
||||
{
|
||||
# if defined(FFMPEG_SWSCALE_THREADING)
|
||||
sws_scale_frame(ctx, dst, src);
|
||||
# else
|
||||
sws_scale(ctx, src->data, src->linesize, 0, src->height, dst->data, dst->linesize);
|
||||
# endif
|
||||
}
|
||||
|
||||
#endif /* WITH_FFMPEG */
|
||||
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,53 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup imbuf
|
||||
*/
|
||||
|
||||
#ifdef WITH_FFMPEG
|
||||
|
||||
struct AVFrame;
|
||||
struct SwsContext;
|
||||
|
||||
namespace blender {
|
||||
|
||||
/**
|
||||
* Gets a `libswscale` context for given size and format parameters.
|
||||
* After you're done using the context, call #ffmpeg_sws_release_context
|
||||
* to release it. Internally the contexts are coming from the context
|
||||
* pool/cache.
|
||||
*
|
||||
* \param src_full_range: whether source uses full (pc/jpeg) range or limited (tv/mpeg) range.
|
||||
*
|
||||
* \param src_color_space: -1 for defaults, or AVColorSpace value to override
|
||||
* sws_setColorspaceDetails `inv_table`.
|
||||
*
|
||||
* \param dst_full_range: whether destination uses full (pc/jpeg) range or limited (tv/mpeg) range.
|
||||
*
|
||||
* \param dst_color_space: -1 for defaults, or AVColorSpace value to override
|
||||
* sws_setColorspaceDetails `table`.
|
||||
*/
|
||||
SwsContext *ffmpeg_sws_get_context(int src_width,
|
||||
int src_height,
|
||||
int av_src_format,
|
||||
bool src_full_range,
|
||||
int src_color_space,
|
||||
int dst_width,
|
||||
int dst_height,
|
||||
int av_dst_format,
|
||||
bool dst_full_range,
|
||||
int dst_color_space,
|
||||
int sws_flags);
|
||||
void ffmpeg_sws_release_context(SwsContext *ctx);
|
||||
|
||||
void ffmpeg_sws_scale_frame(SwsContext *ctx, AVFrame *dst, const AVFrame *src);
|
||||
|
||||
void ffmpeg_sws_exit();
|
||||
|
||||
} // namespace blender
|
||||
|
||||
#endif /* WITH_FFMPEG */
|
||||
@@ -0,0 +1,959 @@
|
||||
/* SPDX-FileCopyrightText: 2011 Peter Schlaile <peter [at] schlaile [dot] de>.
|
||||
* SPDX-FileCopyrightText: 2024-2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup imbuf
|
||||
*/
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "BLI_endian_switch.h"
|
||||
#include "BLI_fileops.h"
|
||||
#include "BLI_math_base.h"
|
||||
#include "BLI_math_base.hh"
|
||||
#include "BLI_path_utils.hh"
|
||||
#include "BLI_string.h"
|
||||
#include "BLI_string_utils.hh"
|
||||
#include "BLI_time.h"
|
||||
#include "BLI_utildefines.h"
|
||||
|
||||
#include "CLG_log.h"
|
||||
|
||||
#include "MOV_read.hh"
|
||||
|
||||
#include "ffmpeg_swscale.hh"
|
||||
#include "movie_proxy_indexer.hh"
|
||||
#include "movie_read.hh"
|
||||
#include "movie_util.hh"
|
||||
|
||||
#ifdef WITH_FFMPEG
|
||||
extern "C" {
|
||||
# include "ffmpeg_compat.h"
|
||||
# include <libavutil/imgutils.h>
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace blender {
|
||||
|
||||
static CLG_LogRef LOG = {"video.proxy"};
|
||||
|
||||
static const IMB_Proxy_Size proxy_sizes[] = {
|
||||
IMB_PROXY_25, IMB_PROXY_50, IMB_PROXY_75, IMB_PROXY_100};
|
||||
static const float proxy_fac[] = {0.25, 0.50, 0.75, 1.00};
|
||||
|
||||
static int proxy_size_to_array_index(IMB_Proxy_Size pr_size)
|
||||
{
|
||||
switch (pr_size) {
|
||||
case IMB_PROXY_NONE:
|
||||
return -1;
|
||||
case IMB_PROXY_25:
|
||||
return 0;
|
||||
case IMB_PROXY_50:
|
||||
return 1;
|
||||
case IMB_PROXY_75:
|
||||
return 2;
|
||||
case IMB_PROXY_100:
|
||||
return 3;
|
||||
default:
|
||||
BLI_assert_msg(0, "Unhandled proxy size enum!");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static void get_proxy_dir(const MovieReader *anim, char *proxy_dir, size_t proxy_dir_maxncpy)
|
||||
{
|
||||
if (!anim->proxy_dir[0]) {
|
||||
char filename[FILE_MAXFILE];
|
||||
char dirname[FILE_MAXDIR];
|
||||
BLI_path_split_dir_file(anim->filepath, dirname, sizeof(dirname), filename, sizeof(filename));
|
||||
BLI_path_join(proxy_dir, proxy_dir_maxncpy, dirname, "BL_proxy", filename);
|
||||
}
|
||||
else {
|
||||
BLI_strncpy(proxy_dir, anim->proxy_dir, proxy_dir_maxncpy);
|
||||
}
|
||||
}
|
||||
|
||||
static bool get_proxy_filepath(const MovieReader *anim,
|
||||
IMB_Proxy_Size preview_size,
|
||||
char *filepath,
|
||||
bool temp)
|
||||
{
|
||||
char proxy_dir[FILE_MAXDIR];
|
||||
int i = proxy_size_to_array_index(preview_size);
|
||||
|
||||
BLI_assert(i >= 0);
|
||||
|
||||
char proxy_name[FILE_MAXFILE];
|
||||
char stream_suffix[20];
|
||||
const char *name = (temp) ? "proxy_%d%s_part.avi" : "proxy_%d%s.avi";
|
||||
|
||||
stream_suffix[0] = 0;
|
||||
|
||||
if (anim->streamindex > 0) {
|
||||
SNPRINTF(stream_suffix, "_st%d", anim->streamindex);
|
||||
}
|
||||
|
||||
SNPRINTF(proxy_name, name, int(proxy_fac[i] * 100), stream_suffix, anim->suffix);
|
||||
|
||||
get_proxy_dir(anim, proxy_dir, sizeof(proxy_dir));
|
||||
|
||||
if (BLI_path_ncmp(anim->filepath, proxy_dir, FILE_MAXDIR) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
BLI_path_join(filepath, FILE_MAXFILE + FILE_MAXDIR, proxy_dir, proxy_name);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------
|
||||
* - ffmpeg rebuilder
|
||||
* ---------------------------------------------------------------------- */
|
||||
|
||||
#ifdef WITH_FFMPEG
|
||||
|
||||
struct proxy_output_ctx {
|
||||
AVFormatContext *of;
|
||||
AVStream *st;
|
||||
AVCodecContext *c;
|
||||
const AVCodec *codec;
|
||||
SwsContext *sws_ctx;
|
||||
AVFrame *frame;
|
||||
int cfra;
|
||||
AVRational output_timebase;
|
||||
IMB_Proxy_Size proxy_size;
|
||||
int orig_height;
|
||||
MovieReader *anim;
|
||||
};
|
||||
|
||||
static proxy_output_ctx *alloc_proxy_output_ffmpeg(MovieReader *anim,
|
||||
AVCodecContext *codec_ctx,
|
||||
AVStream *st,
|
||||
IMB_Proxy_Size proxy_size,
|
||||
int width,
|
||||
int height,
|
||||
int quality)
|
||||
{
|
||||
proxy_output_ctx *rv = MEM_new_zeroed<proxy_output_ctx>("alloc_proxy_output");
|
||||
|
||||
char filepath[FILE_MAX];
|
||||
|
||||
rv->proxy_size = proxy_size;
|
||||
rv->anim = anim;
|
||||
|
||||
get_proxy_filepath(rv->anim, rv->proxy_size, filepath, true);
|
||||
if (!BLI_file_ensure_parent_dir_exists(filepath)) {
|
||||
MEM_delete(rv);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
rv->of = avformat_alloc_context();
|
||||
/* Note: we keep on using .avi extension for proxies,
|
||||
* but actual container can not be AVI, since it does not support
|
||||
* video rotation metadata. */
|
||||
rv->of->oformat = av_guess_format("mp4", nullptr, nullptr);
|
||||
|
||||
rv->of->url = av_strdup(filepath);
|
||||
|
||||
rv->st = avformat_new_stream(rv->of, nullptr);
|
||||
rv->st->id = 0;
|
||||
|
||||
rv->codec = avcodec_find_encoder(AV_CODEC_ID_H264);
|
||||
|
||||
rv->c = avcodec_alloc_context3(rv->codec);
|
||||
|
||||
if (!rv->codec) {
|
||||
CLOG_ERROR(&LOG, "Could not build proxy '%s': failed to create video encoder", filepath);
|
||||
avcodec_free_context(&rv->c);
|
||||
avformat_free_context(rv->of);
|
||||
MEM_delete(rv);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
rv->c->width = width;
|
||||
rv->c->height = height;
|
||||
rv->c->gop_size = 10;
|
||||
rv->c->max_b_frames = 0;
|
||||
|
||||
const enum AVPixelFormat *pix_fmts = ffmpeg_get_pix_fmts(rv->c, rv->codec);
|
||||
if (pix_fmts) {
|
||||
rv->c->pix_fmt = pix_fmts[0];
|
||||
}
|
||||
else {
|
||||
rv->c->pix_fmt = AV_PIX_FMT_YUVJ420P;
|
||||
}
|
||||
|
||||
rv->c->sample_aspect_ratio = rv->st->sample_aspect_ratio = st->sample_aspect_ratio;
|
||||
|
||||
/* Use same output timebase as input: we seek within the proxy file
|
||||
* using exact same frame numbers as if it was original file. So we want to
|
||||
* match original frame-rate, plus any variable frames in the source file. */
|
||||
rv->output_timebase = st->time_base;
|
||||
rv->c->time_base = st->time_base;
|
||||
rv->st->time_base = st->time_base;
|
||||
rv->st->avg_frame_rate = st->avg_frame_rate;
|
||||
|
||||
/* This range matches #eFFMpegCrf. `crf_range_min` corresponds to lowest quality,
|
||||
* `crf_range_max` to highest quality. */
|
||||
const int crf_range_min = 32;
|
||||
const int crf_range_max = 17;
|
||||
int crf = round_fl_to_int((quality / 100.0f) * (crf_range_max - crf_range_min) + crf_range_min);
|
||||
|
||||
AVDictionary *codec_opts = nullptr;
|
||||
/* High quality preset value. */
|
||||
av_dict_set_int(&codec_opts, "crf", crf, 0);
|
||||
/* Prefer smaller file-size. Presets from `veryslow` to `veryfast` produce output with very
|
||||
* similar file-size, but there is big difference in performance.
|
||||
* In some cases `veryfast` preset will produce smallest file-size. */
|
||||
av_dict_set(&codec_opts, "preset", "veryfast", 0);
|
||||
av_dict_set(&codec_opts, "tune", "fastdecode", 0);
|
||||
|
||||
if (rv->codec->capabilities & AV_CODEC_CAP_OTHER_THREADS) {
|
||||
rv->c->thread_count = 0;
|
||||
}
|
||||
else {
|
||||
rv->c->thread_count = MOV_thread_count();
|
||||
}
|
||||
|
||||
if (rv->codec->capabilities & AV_CODEC_CAP_FRAME_THREADS) {
|
||||
rv->c->thread_type = FF_THREAD_FRAME;
|
||||
}
|
||||
else if (rv->codec->capabilities & AV_CODEC_CAP_SLICE_THREADS) {
|
||||
rv->c->thread_type = FF_THREAD_SLICE;
|
||||
}
|
||||
|
||||
if (rv->of->oformat->flags & AVFMT_GLOBALHEADER) {
|
||||
rv->c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
|
||||
}
|
||||
|
||||
rv->c->color_range = codec_ctx->color_range;
|
||||
rv->c->color_primaries = codec_ctx->color_primaries;
|
||||
rv->c->color_trc = codec_ctx->color_trc;
|
||||
rv->c->colorspace = codec_ctx->colorspace;
|
||||
|
||||
int ret = avio_open(&rv->of->pb, filepath, AVIO_FLAG_WRITE);
|
||||
|
||||
if (ret < 0) {
|
||||
char error_str[AV_ERROR_MAX_STRING_SIZE];
|
||||
av_make_error_string(error_str, AV_ERROR_MAX_STRING_SIZE, ret);
|
||||
|
||||
CLOG_ERROR(&LOG,
|
||||
"Could not build proxy '%s': failed to create output file (%s)",
|
||||
filepath,
|
||||
error_str);
|
||||
avcodec_free_context(&rv->c);
|
||||
avformat_free_context(rv->of);
|
||||
MEM_delete(rv);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ret = avcodec_open2(rv->c, rv->codec, &codec_opts);
|
||||
if (ret < 0) {
|
||||
char error_str[AV_ERROR_MAX_STRING_SIZE];
|
||||
av_make_error_string(error_str, AV_ERROR_MAX_STRING_SIZE, ret);
|
||||
|
||||
CLOG_ERROR(
|
||||
&LOG, "Could not build proxy '%s': failed to open video codec (%s)", filepath, error_str);
|
||||
avcodec_free_context(&rv->c);
|
||||
avformat_free_context(rv->of);
|
||||
MEM_delete(rv);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
avcodec_parameters_from_context(rv->st->codecpar, rv->c);
|
||||
ffmpeg_copy_display_matrix(st, rv->st);
|
||||
|
||||
rv->orig_height = st->codecpar->height;
|
||||
|
||||
if (st->codecpar->width != width || st->codecpar->height != height ||
|
||||
st->codecpar->format != rv->c->pix_fmt)
|
||||
{
|
||||
const size_t align = ffmpeg_get_buffer_alignment();
|
||||
rv->frame = av_frame_alloc();
|
||||
rv->frame->format = rv->c->pix_fmt;
|
||||
rv->frame->width = width;
|
||||
rv->frame->height = height;
|
||||
av_frame_get_buffer(rv->frame, align);
|
||||
|
||||
rv->sws_ctx = ffmpeg_sws_get_context(st->codecpar->width,
|
||||
rv->orig_height,
|
||||
AVPixelFormat(st->codecpar->format),
|
||||
codec_ctx->color_range == AVCOL_RANGE_JPEG,
|
||||
-1,
|
||||
width,
|
||||
height,
|
||||
rv->c->pix_fmt,
|
||||
codec_ctx->color_range == AVCOL_RANGE_JPEG,
|
||||
-1,
|
||||
SWS_FAST_BILINEAR);
|
||||
}
|
||||
|
||||
ret = avformat_write_header(rv->of, nullptr);
|
||||
if (ret < 0) {
|
||||
char error_str[AV_ERROR_MAX_STRING_SIZE];
|
||||
av_make_error_string(error_str, AV_ERROR_MAX_STRING_SIZE, ret);
|
||||
|
||||
CLOG_ERROR(
|
||||
&LOG, "Could not build proxy '%s': failed to write header (%s)", filepath, error_str);
|
||||
|
||||
if (rv->frame) {
|
||||
av_frame_free(&rv->frame);
|
||||
}
|
||||
|
||||
avcodec_free_context(&rv->c);
|
||||
avformat_free_context(rv->of);
|
||||
MEM_delete(rv);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
static void add_to_proxy_output_ffmpeg(proxy_output_ctx *ctx,
|
||||
AVFrame *frame,
|
||||
AVRational input_timebase)
|
||||
{
|
||||
if (!ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int64_t src_pts = frame ? frame->pts : AV_NOPTS_VALUE;
|
||||
|
||||
if (ctx->sws_ctx && frame &&
|
||||
(frame->data[0] || frame->data[1] || frame->data[2] || frame->data[3]))
|
||||
{
|
||||
ffmpeg_sws_scale_frame(ctx->sws_ctx, ctx->frame, frame);
|
||||
}
|
||||
|
||||
frame = ctx->sws_ctx ? (frame ? ctx->frame : nullptr) : frame;
|
||||
|
||||
if (frame) {
|
||||
if (src_pts != AV_NOPTS_VALUE) {
|
||||
frame->pts = av_rescale_q(src_pts, input_timebase, ctx->output_timebase);
|
||||
}
|
||||
else {
|
||||
frame->pts = ctx->cfra;
|
||||
}
|
||||
ctx->cfra++;
|
||||
}
|
||||
|
||||
int ret = avcodec_send_frame(ctx->c, frame);
|
||||
if (ret < 0) {
|
||||
/* Can't send frame to encoder. This shouldn't happen. */
|
||||
char error_str[AV_ERROR_MAX_STRING_SIZE];
|
||||
av_make_error_string(error_str, AV_ERROR_MAX_STRING_SIZE, ret);
|
||||
|
||||
CLOG_ERROR(
|
||||
&LOG, "Building proxy '%s': failed to send video frame (%s)", ctx->of->url, error_str);
|
||||
return;
|
||||
}
|
||||
AVPacket *packet = av_packet_alloc();
|
||||
|
||||
while (ret >= 0) {
|
||||
ret = avcodec_receive_packet(ctx->c, packet);
|
||||
|
||||
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
|
||||
/* No more packets to flush. */
|
||||
break;
|
||||
}
|
||||
if (ret < 0) {
|
||||
char error_str[AV_ERROR_MAX_STRING_SIZE];
|
||||
av_make_error_string(error_str, AV_ERROR_MAX_STRING_SIZE, ret);
|
||||
|
||||
CLOG_ERROR(&LOG,
|
||||
"Building proxy '%s': error encoding frame #%i (%s)",
|
||||
ctx->of->url,
|
||||
ctx->cfra - 1,
|
||||
error_str);
|
||||
break;
|
||||
}
|
||||
|
||||
packet->stream_index = ctx->st->index;
|
||||
av_packet_rescale_ts(packet, ctx->c->time_base, ctx->st->time_base);
|
||||
# ifdef FFMPEG_USE_DURATION_WORKAROUND
|
||||
my_guess_pkt_duration(ctx->of, ctx->st, packet);
|
||||
# endif
|
||||
|
||||
int write_ret = av_interleaved_write_frame(ctx->of, packet);
|
||||
if (write_ret != 0) {
|
||||
char error_str[AV_ERROR_MAX_STRING_SIZE];
|
||||
av_make_error_string(error_str, AV_ERROR_MAX_STRING_SIZE, write_ret);
|
||||
|
||||
CLOG_ERROR(&LOG,
|
||||
"Building proxy '%s': error writing frame #%i (%s)",
|
||||
ctx->of->url,
|
||||
ctx->cfra - 1,
|
||||
error_str);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
av_packet_free(&packet);
|
||||
}
|
||||
|
||||
static void free_proxy_output_ffmpeg(proxy_output_ctx *ctx, int rollback)
|
||||
{
|
||||
char filepath[FILE_MAX];
|
||||
char filepath_tmp[FILE_MAX];
|
||||
|
||||
if (!ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!rollback) {
|
||||
/* Flush the remaining packets. */
|
||||
add_to_proxy_output_ffmpeg(ctx, nullptr, {1, 1});
|
||||
}
|
||||
|
||||
av_write_trailer(ctx->of);
|
||||
|
||||
if (ctx->of->oformat) {
|
||||
if (!(ctx->of->oformat->flags & AVFMT_NOFILE)) {
|
||||
avio_close(ctx->of->pb);
|
||||
}
|
||||
}
|
||||
avcodec_free_context(&ctx->c);
|
||||
avformat_free_context(ctx->of);
|
||||
|
||||
if (ctx->sws_ctx) {
|
||||
ffmpeg_sws_release_context(ctx->sws_ctx);
|
||||
ctx->sws_ctx = nullptr;
|
||||
}
|
||||
if (ctx->frame) {
|
||||
av_frame_free(&ctx->frame);
|
||||
}
|
||||
|
||||
get_proxy_filepath(ctx->anim, ctx->proxy_size, filepath_tmp, true);
|
||||
|
||||
if (rollback) {
|
||||
BLI_delete(filepath_tmp, false, false);
|
||||
}
|
||||
else {
|
||||
get_proxy_filepath(ctx->anim, ctx->proxy_size, filepath, false);
|
||||
BLI_rename_overwrite(filepath_tmp, filepath);
|
||||
}
|
||||
|
||||
MEM_delete(ctx);
|
||||
}
|
||||
|
||||
struct MovieProxyBuilder {
|
||||
|
||||
AVFormatContext *iFormatCtx;
|
||||
AVCodecContext *iCodecCtx;
|
||||
const AVCodec *iCodec;
|
||||
AVStream *iStream;
|
||||
int videoStream;
|
||||
|
||||
int num_proxy_sizes;
|
||||
|
||||
proxy_output_ctx *proxy_ctx[IMB_PROXY_MAX_SLOT];
|
||||
|
||||
int proxy_sizes_in_use;
|
||||
|
||||
bool build_only_on_bad_performance;
|
||||
bool building_cancelled;
|
||||
};
|
||||
|
||||
static MovieProxyBuilder *proxy_builder_create(MovieReader *anim,
|
||||
int proxy_sizes_in_use,
|
||||
int quality,
|
||||
bool build_only_on_bad_performance)
|
||||
{
|
||||
/* Never build proxies for un-seekable single frame files. */
|
||||
if (anim->never_seek_decode_one_frame) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MovieProxyBuilder *context = MEM_new_zeroed<MovieProxyBuilder>(__func__);
|
||||
int num_proxy_sizes = IMB_PROXY_MAX_SLOT;
|
||||
int i, streamcount;
|
||||
|
||||
context->proxy_sizes_in_use = proxy_sizes_in_use;
|
||||
context->num_proxy_sizes = IMB_PROXY_MAX_SLOT;
|
||||
context->build_only_on_bad_performance = build_only_on_bad_performance;
|
||||
|
||||
memset(context->proxy_ctx, 0, sizeof(context->proxy_ctx));
|
||||
|
||||
if (avformat_open_input(&context->iFormatCtx, anim->filepath, nullptr, nullptr) != 0) {
|
||||
MEM_delete(context);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (avformat_find_stream_info(context->iFormatCtx, nullptr) < 0) {
|
||||
avformat_close_input(&context->iFormatCtx);
|
||||
MEM_delete(context);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
streamcount = anim->streamindex;
|
||||
|
||||
/* Find the video stream */
|
||||
context->videoStream = -1;
|
||||
for (i = 0; i < context->iFormatCtx->nb_streams; i++) {
|
||||
if (context->iFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
|
||||
if (streamcount > 0) {
|
||||
streamcount--;
|
||||
continue;
|
||||
}
|
||||
context->videoStream = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (context->videoStream == -1) {
|
||||
avformat_close_input(&context->iFormatCtx);
|
||||
MEM_delete(context);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
context->iStream = context->iFormatCtx->streams[context->videoStream];
|
||||
|
||||
context->iCodec = avcodec_find_decoder(context->iStream->codecpar->codec_id);
|
||||
|
||||
if (context->iCodec == nullptr) {
|
||||
avformat_close_input(&context->iFormatCtx);
|
||||
MEM_delete(context);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
context->iCodecCtx = avcodec_alloc_context3(nullptr);
|
||||
avcodec_parameters_to_context(context->iCodecCtx, context->iStream->codecpar);
|
||||
context->iCodecCtx->workaround_bugs = FF_BUG_AUTODETECT;
|
||||
|
||||
if (context->iCodec->capabilities & AV_CODEC_CAP_OTHER_THREADS) {
|
||||
context->iCodecCtx->thread_count = 0;
|
||||
}
|
||||
else {
|
||||
context->iCodecCtx->thread_count = MOV_thread_count();
|
||||
}
|
||||
|
||||
if (context->iCodec->capabilities & AV_CODEC_CAP_FRAME_THREADS) {
|
||||
context->iCodecCtx->thread_type = FF_THREAD_FRAME;
|
||||
}
|
||||
else if (context->iCodec->capabilities & AV_CODEC_CAP_SLICE_THREADS) {
|
||||
context->iCodecCtx->thread_type = FF_THREAD_SLICE;
|
||||
}
|
||||
|
||||
if (avcodec_open2(context->iCodecCtx, context->iCodec, nullptr) < 0) {
|
||||
avformat_close_input(&context->iFormatCtx);
|
||||
avcodec_free_context(&context->iCodecCtx);
|
||||
MEM_delete(context);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (i = 0; i < num_proxy_sizes; i++) {
|
||||
if (proxy_sizes_in_use & proxy_sizes[i]) {
|
||||
int width = context->iCodecCtx->width * proxy_fac[i];
|
||||
int height = context->iCodecCtx->height * proxy_fac[i];
|
||||
width += width % 2;
|
||||
height += height % 2;
|
||||
context->proxy_ctx[i] = alloc_proxy_output_ffmpeg(
|
||||
anim, context->iCodecCtx, context->iStream, proxy_sizes[i], width, height, quality);
|
||||
if (!context->proxy_ctx[i]) {
|
||||
proxy_sizes_in_use &= ~int(proxy_sizes[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (context->proxy_ctx[0] == nullptr && context->proxy_ctx[1] == nullptr &&
|
||||
context->proxy_ctx[2] == nullptr && context->proxy_ctx[3] == nullptr)
|
||||
{
|
||||
avformat_close_input(&context->iFormatCtx);
|
||||
avcodec_free_context(&context->iCodecCtx);
|
||||
MEM_delete(context);
|
||||
return nullptr; /* Nothing to transcode. */
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
static void proxy_builder_finish(MovieProxyBuilder *context, const bool stop)
|
||||
{
|
||||
const bool do_rollback = stop || context->building_cancelled;
|
||||
|
||||
for (int i = 0; i < context->num_proxy_sizes; i++) {
|
||||
if (context->proxy_sizes_in_use & proxy_sizes[i]) {
|
||||
free_proxy_output_ffmpeg(context->proxy_ctx[i], do_rollback);
|
||||
}
|
||||
}
|
||||
|
||||
avcodec_free_context(&context->iCodecCtx);
|
||||
avformat_close_input(&context->iFormatCtx);
|
||||
|
||||
MEM_delete(context);
|
||||
}
|
||||
|
||||
static void proxy_builder_proc_decoded_frame(MovieProxyBuilder *context, AVFrame *in_frame)
|
||||
{
|
||||
for (int i = 0; i < context->num_proxy_sizes; i++) {
|
||||
add_to_proxy_output_ffmpeg(context->proxy_ctx[i], in_frame, context->iStream->time_base);
|
||||
}
|
||||
}
|
||||
|
||||
static int proxy_builder_process(MovieProxyBuilder *context,
|
||||
const bool *stop,
|
||||
bool *do_update,
|
||||
const FunctionRef<void(float progress)> set_progress_fn)
|
||||
{
|
||||
AVFrame *in_frame = av_frame_alloc();
|
||||
AVPacket *next_packet = av_packet_alloc();
|
||||
uint64_t stream_size = avio_size(context->iFormatCtx->pb);
|
||||
|
||||
float progress = 0.0f;
|
||||
while (av_read_frame(context->iFormatCtx, next_packet) >= 0) {
|
||||
float next_progress =
|
||||
float(int(floor(double(next_packet->pos) * 100 / double(stream_size) + 0.5))) / 100;
|
||||
|
||||
if (progress != next_progress) {
|
||||
progress = next_progress;
|
||||
*do_update = true;
|
||||
if (set_progress_fn) {
|
||||
set_progress_fn(progress);
|
||||
}
|
||||
}
|
||||
|
||||
if (*stop) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (next_packet->stream_index == context->videoStream) {
|
||||
int ret = avcodec_send_packet(context->iCodecCtx, next_packet);
|
||||
while (ret >= 0) {
|
||||
ret = avcodec_receive_frame(context->iCodecCtx, in_frame);
|
||||
|
||||
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
|
||||
/* No more frames to flush. */
|
||||
break;
|
||||
}
|
||||
if (ret < 0) {
|
||||
char error_str[AV_ERROR_MAX_STRING_SIZE];
|
||||
av_make_error_string(error_str, AV_ERROR_MAX_STRING_SIZE, ret);
|
||||
CLOG_ERROR(&LOG, "Error decoding proxy frame: %s", error_str);
|
||||
break;
|
||||
}
|
||||
|
||||
proxy_builder_proc_decoded_frame(context, in_frame);
|
||||
}
|
||||
}
|
||||
av_packet_unref(next_packet);
|
||||
}
|
||||
|
||||
/* process pictures still stuck in decoder engine after EOF
|
||||
* according to ffmpeg docs using nullptr packets.
|
||||
*
|
||||
* At least, if we haven't already stopped... */
|
||||
|
||||
if (!*stop) {
|
||||
int ret = avcodec_send_packet(context->iCodecCtx, nullptr);
|
||||
|
||||
while (ret >= 0) {
|
||||
ret = avcodec_receive_frame(context->iCodecCtx, in_frame);
|
||||
|
||||
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
|
||||
/* No more frames to flush. */
|
||||
break;
|
||||
}
|
||||
if (ret < 0) {
|
||||
char error_str[AV_ERROR_MAX_STRING_SIZE];
|
||||
av_make_error_string(error_str, AV_ERROR_MAX_STRING_SIZE, ret);
|
||||
CLOG_ERROR(&LOG, "Error flushing proxy frame: %s", error_str);
|
||||
break;
|
||||
}
|
||||
proxy_builder_proc_decoded_frame(context, in_frame);
|
||||
}
|
||||
}
|
||||
|
||||
av_packet_free(&next_packet);
|
||||
av_free(in_frame);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Get number of frames, that can be decoded in specified time period. */
|
||||
static int performance_get_decode_rate(MovieProxyBuilder *context, const double time_period)
|
||||
{
|
||||
AVFrame *in_frame = av_frame_alloc();
|
||||
AVPacket *packet = av_packet_alloc();
|
||||
|
||||
const double start = BLI_time_now_seconds();
|
||||
int frames_decoded = 0;
|
||||
|
||||
while (av_read_frame(context->iFormatCtx, packet) >= 0) {
|
||||
if (packet->stream_index != context->videoStream) {
|
||||
av_packet_unref(packet);
|
||||
continue;
|
||||
}
|
||||
|
||||
int ret = avcodec_send_packet(context->iCodecCtx, packet);
|
||||
while (ret >= 0) {
|
||||
ret = avcodec_receive_frame(context->iCodecCtx, in_frame);
|
||||
|
||||
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (ret < 0) {
|
||||
char error_str[AV_ERROR_MAX_STRING_SIZE];
|
||||
av_make_error_string(error_str, AV_ERROR_MAX_STRING_SIZE, ret);
|
||||
CLOG_ERROR(&LOG, "Error decoding proxy frame: %s", error_str);
|
||||
break;
|
||||
}
|
||||
frames_decoded++;
|
||||
}
|
||||
|
||||
const double end = BLI_time_now_seconds();
|
||||
|
||||
if (end > start + time_period) {
|
||||
break;
|
||||
}
|
||||
av_packet_unref(packet);
|
||||
}
|
||||
|
||||
av_packet_free(&packet);
|
||||
av_frame_free(&in_frame);
|
||||
|
||||
avcodec_flush_buffers(context->iCodecCtx);
|
||||
av_seek_frame(context->iFormatCtx, -1, 0, AVSEEK_FLAG_BACKWARD);
|
||||
return frames_decoded;
|
||||
}
|
||||
|
||||
/* Read up to 10k movie packets and return max GOP size detected.
|
||||
* Number of packets is arbitrary. It should be as large as possible, but processed within
|
||||
* reasonable time period, so detected GOP size is as close to real as possible. */
|
||||
static int performance_get_max_gop_size(MovieProxyBuilder *context)
|
||||
{
|
||||
AVPacket *packet = av_packet_alloc();
|
||||
|
||||
const int packets_max = 10000;
|
||||
int packet_index = 0;
|
||||
int max_gop = 0;
|
||||
int cur_gop = 0;
|
||||
|
||||
while (av_read_frame(context->iFormatCtx, packet) >= 0) {
|
||||
if (packet->stream_index != context->videoStream) {
|
||||
av_packet_unref(packet);
|
||||
continue;
|
||||
}
|
||||
packet_index++;
|
||||
cur_gop++;
|
||||
|
||||
if (packet->flags & AV_PKT_FLAG_KEY) {
|
||||
max_gop = max_ii(max_gop, cur_gop);
|
||||
cur_gop = 0;
|
||||
}
|
||||
|
||||
if (packet_index > packets_max) {
|
||||
break;
|
||||
}
|
||||
av_packet_unref(packet);
|
||||
}
|
||||
|
||||
av_packet_free(&packet);
|
||||
|
||||
av_seek_frame(context->iFormatCtx, -1, 0, AVSEEK_FLAG_BACKWARD);
|
||||
return max_gop;
|
||||
}
|
||||
|
||||
/* Assess scrubbing performance of provided file. This function is not meant to be very exact.
|
||||
* It compares number of frames decoded in reasonable time with largest detected GOP size.
|
||||
* Because seeking happens in single GOP, it means, that maximum seek time can be detected this
|
||||
* way.
|
||||
* Since proxies use GOP size of 10 frames, skip building if detected GOP size is less or
|
||||
* equal.
|
||||
*/
|
||||
static bool need_to_build_proxy(MovieProxyBuilder *context)
|
||||
{
|
||||
if (!context->build_only_on_bad_performance) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Make sure, that file is not cold read. */
|
||||
performance_get_decode_rate(context, 0.1);
|
||||
/* Get decode rate per 100ms. This is arbitrary, but seems to be good baseline cadence of
|
||||
* seeking. */
|
||||
const int decode_rate = performance_get_decode_rate(context, 0.1);
|
||||
const int max_gop_size = performance_get_max_gop_size(context);
|
||||
|
||||
if (max_gop_size <= 10 || max_gop_size < decode_rate) {
|
||||
CLOG_INFO_NOCHECK(&LOG,
|
||||
"Skipping proxy building for %s: Decoding performance is already good.",
|
||||
context->iFormatCtx->url);
|
||||
context->building_cancelled = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/* ----------------------------------------------------------------------
|
||||
* - public API
|
||||
* ---------------------------------------------------------------------- */
|
||||
|
||||
MovieProxyBuilder *MOV_proxy_builder_start(MovieReader *anim,
|
||||
int proxy_sizes_in_use,
|
||||
int quality,
|
||||
const bool overwrite,
|
||||
Set<std::string> *processed_paths,
|
||||
bool build_only_on_bad_performance)
|
||||
{
|
||||
int proxy_sizes_to_build = proxy_sizes_in_use;
|
||||
|
||||
/* Check which proxies are going to be generated in this session already. */
|
||||
if (processed_paths != nullptr) {
|
||||
for (int i = 0; i < IMB_PROXY_MAX_SLOT; i++) {
|
||||
IMB_Proxy_Size proxy_size = proxy_sizes[i];
|
||||
if ((proxy_size & proxy_sizes_to_build) == 0) {
|
||||
continue;
|
||||
}
|
||||
char filepath[FILE_MAX];
|
||||
if (!get_proxy_filepath(anim, proxy_size, filepath, false)) {
|
||||
return nullptr;
|
||||
}
|
||||
if (!processed_paths->add(filepath)) {
|
||||
proxy_sizes_to_build &= ~int(proxy_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* When not overwriting existing proxies, skip the ones that already exist. */
|
||||
if (!overwrite) {
|
||||
int built_proxies = MOV_get_existing_proxies(anim);
|
||||
if (built_proxies != 0) {
|
||||
for (int i = 0; i < IMB_PROXY_MAX_SLOT; i++) {
|
||||
IMB_Proxy_Size proxy_size = proxy_sizes[i];
|
||||
if (proxy_size & built_proxies) {
|
||||
char filepath[FILE_MAX];
|
||||
if (!get_proxy_filepath(anim, proxy_size, filepath, false)) {
|
||||
return nullptr;
|
||||
}
|
||||
CLOG_INFO_NOCHECK(&LOG, "Skipping proxy: %s", filepath);
|
||||
}
|
||||
}
|
||||
}
|
||||
proxy_sizes_to_build &= ~built_proxies;
|
||||
}
|
||||
|
||||
if (proxy_sizes_to_build == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MovieProxyBuilder *context = nullptr;
|
||||
#ifdef WITH_FFMPEG
|
||||
if (anim->state == MovieReader::State::Valid) {
|
||||
context = proxy_builder_create(
|
||||
anim, proxy_sizes_to_build, quality, build_only_on_bad_performance);
|
||||
}
|
||||
#else
|
||||
UNUSED_VARS(build_only_on_bad_performance);
|
||||
#endif
|
||||
|
||||
return context;
|
||||
|
||||
UNUSED_VARS(proxy_sizes_in_use, quality);
|
||||
}
|
||||
|
||||
void MOV_proxy_builder_process(MovieProxyBuilder *context,
|
||||
/* NOLINTNEXTLINE: readability-non-const-parameter. */
|
||||
const bool *stop,
|
||||
/* NOLINTNEXTLINE: readability-non-const-parameter. */
|
||||
bool *do_update,
|
||||
const FunctionRef<void(float progress)> set_progress_fn)
|
||||
{
|
||||
#ifdef WITH_FFMPEG
|
||||
if (context != nullptr) {
|
||||
if (need_to_build_proxy(context)) {
|
||||
proxy_builder_process(context, stop, do_update, set_progress_fn);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
UNUSED_VARS(context, stop, do_update, set_progress_fn);
|
||||
}
|
||||
|
||||
void MOV_proxy_builder_finish(MovieProxyBuilder *context, const bool stop)
|
||||
{
|
||||
#ifdef WITH_FFMPEG
|
||||
if (context != nullptr) {
|
||||
proxy_builder_finish(context, stop);
|
||||
}
|
||||
#endif
|
||||
/* static defined at top of the file */
|
||||
UNUSED_VARS(context, stop, proxy_sizes);
|
||||
}
|
||||
|
||||
void MOV_close_proxies(MovieReader *anim)
|
||||
{
|
||||
if (anim == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < IMB_PROXY_MAX_SLOT; i++) {
|
||||
if (anim->proxy_anim[i]) {
|
||||
MOV_close(anim->proxy_anim[i]);
|
||||
anim->proxy_anim[i] = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
anim->proxies_tried = 0;
|
||||
}
|
||||
|
||||
void MOV_set_custom_proxy_dir(MovieReader *anim, const char *dir)
|
||||
{
|
||||
if (STREQ(anim->proxy_dir, dir)) {
|
||||
return;
|
||||
}
|
||||
STRNCPY(anim->proxy_dir, dir);
|
||||
|
||||
MOV_close_proxies(anim);
|
||||
}
|
||||
|
||||
MovieReader *movie_open_proxy(MovieReader *anim, IMB_Proxy_Size preview_size)
|
||||
{
|
||||
char filepath[FILE_MAX];
|
||||
int i = proxy_size_to_array_index(preview_size);
|
||||
|
||||
if (i < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (anim->proxy_anim[i]) {
|
||||
return anim->proxy_anim[i];
|
||||
}
|
||||
|
||||
if (anim->proxies_tried & preview_size) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
get_proxy_filepath(anim, preview_size, filepath, false);
|
||||
|
||||
/* Proxies are generated in the same color space as animation itself.
|
||||
*
|
||||
* Also skip any colorspace conversion to the color pipeline design as it helps performance and
|
||||
* the image buffers from the proxy builder are not used anywhere else in Blender. */
|
||||
anim->proxy_anim[i] = MOV_open_file(filepath, ImBufFlags::Zero, 0, true, anim->colorspace);
|
||||
|
||||
anim->proxies_tried |= preview_size;
|
||||
|
||||
return anim->proxy_anim[i];
|
||||
}
|
||||
|
||||
int MOV_get_existing_proxies(const MovieReader *anim)
|
||||
{
|
||||
const int num_proxy_sizes = IMB_PROXY_MAX_SLOT;
|
||||
int existing = IMB_PROXY_NONE;
|
||||
for (int i = 0; i < num_proxy_sizes; i++) {
|
||||
IMB_Proxy_Size proxy_size = proxy_sizes[i];
|
||||
char filepath[FILE_MAX];
|
||||
get_proxy_filepath(anim, proxy_size, filepath, false);
|
||||
if (BLI_exists(filepath)) {
|
||||
existing |= int(proxy_size);
|
||||
}
|
||||
}
|
||||
return existing;
|
||||
}
|
||||
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,19 @@
|
||||
/* SPDX-FileCopyrightText: 2023-2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup imbuf
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IMB_imbuf_enums.h"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct MovieReader;
|
||||
|
||||
MovieReader *movie_open_proxy(MovieReader *anim, IMB_Proxy_Size preview_size);
|
||||
|
||||
} // namespace blender
|
||||
1513
blender-5.2.0/source/blender/imbuf/movie/intern/movie_read.cc
Normal file
1513
blender-5.2.0/source/blender/imbuf/movie/intern/movie_read.cc
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,99 @@
|
||||
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
|
||||
* SPDX-FileCopyrightText: 2024-2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup imbuf
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "IMB_imbuf_enums.h"
|
||||
|
||||
struct AVFormatContext;
|
||||
struct AVCodecContext;
|
||||
struct AVCodec;
|
||||
struct AVFrame;
|
||||
struct AVPacket;
|
||||
struct SwsContext;
|
||||
|
||||
#ifdef WITH_FFMPEG
|
||||
|
||||
extern "C" {
|
||||
# include <libavutil/rational.h>
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct IDProperty;
|
||||
|
||||
struct MovieReader {
|
||||
enum class State { Uninitialized, Failed, Valid };
|
||||
ImBufFlags ib_flags = ImBufFlags::Zero;
|
||||
State state = State::Uninitialized;
|
||||
int cur_position = 0; /* index 0 = 1e, 1 = 2e, enz. */
|
||||
int duration_in_frames = 0;
|
||||
int frs_sec = 0;
|
||||
double frs_sec_base = 0.0;
|
||||
double start_offset = 0.0;
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
int video_rotation = 0;
|
||||
|
||||
/* for number */
|
||||
char filepath[/*FILE_MAX*/ 1024] = {};
|
||||
|
||||
int streamindex = 0;
|
||||
|
||||
bool keep_original_colorspace = false;
|
||||
|
||||
#ifdef WITH_FFMPEG
|
||||
AVFormatContext *pFormatCtx = nullptr;
|
||||
AVCodecContext *pCodecCtx = nullptr;
|
||||
const AVCodec *pCodec = nullptr;
|
||||
AVFrame *pFrameRGB = nullptr;
|
||||
AVFrame *pFrameDeinterlaced = nullptr;
|
||||
SwsContext *img_convert_ctx = nullptr;
|
||||
int videoStream = 0;
|
||||
|
||||
AVFrame *pFrame = nullptr;
|
||||
bool pFrame_complete = false;
|
||||
AVFrame *pFrame_backup = nullptr;
|
||||
bool pFrame_backup_complete = false;
|
||||
|
||||
int64_t cur_pts = 0;
|
||||
int64_t cur_key_frame_pts = 0;
|
||||
AVPacket *cur_packet = nullptr;
|
||||
|
||||
AVRational frame_rate = {1, 1};
|
||||
|
||||
bool seek_before_decode = false;
|
||||
bool is_float = false;
|
||||
|
||||
/* When set, never seek within the video, and only ever decode one frame.
|
||||
* This is a workaround for some Ogg files that have full audio but only
|
||||
* one frame of "album art" as a video stream in non-Theora format.
|
||||
* ffmpeg crashes/aborts when trying to seek within them
|
||||
* (https://trac.ffmpeg.org/ticket/10755). */
|
||||
bool never_seek_decode_one_frame = false;
|
||||
#endif
|
||||
|
||||
char proxy_dir[768] = {};
|
||||
|
||||
int proxies_tried = 0;
|
||||
|
||||
MovieReader *proxy_anim[IMB_PROXY_MAX_SLOT] = {};
|
||||
|
||||
char colorspace[/*MAX_COLORSPACE_NAME*/ 64] = {};
|
||||
/** The maximum name from multi-view. */
|
||||
char suffix[/*MAX_NAME*/ 64] = {};
|
||||
|
||||
IDProperty *metadata = nullptr;
|
||||
};
|
||||
|
||||
} // namespace blender
|
||||
656
blender-5.2.0/source/blender/imbuf/movie/intern/movie_util.cc
Normal file
656
blender-5.2.0/source/blender/imbuf/movie/intern/movie_util.cc
Normal file
@@ -0,0 +1,656 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup imbuf
|
||||
*/
|
||||
|
||||
#include "BLI_path_utils.hh"
|
||||
#include "BLI_threads.h"
|
||||
#include "BLI_utildefines.h"
|
||||
|
||||
#include "CLG_log.h"
|
||||
#include "DNA_scene_types.h"
|
||||
|
||||
#include "MOV_enums.hh"
|
||||
#include "MOV_util.hh"
|
||||
|
||||
#include "ffmpeg_swscale.hh"
|
||||
#include "movie_util.hh"
|
||||
#include <mutex>
|
||||
|
||||
#ifdef WITH_FFMPEG
|
||||
# include "BLI_string.h"
|
||||
|
||||
extern "C" {
|
||||
# include "ffmpeg_compat.h"
|
||||
# include <libavcodec/avcodec.h>
|
||||
# include <libavdevice/avdevice.h>
|
||||
# include <libavformat/avformat.h>
|
||||
# include <libavutil/log.h>
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace blender {
|
||||
|
||||
#ifdef WITH_FFMPEG
|
||||
|
||||
static CLG_LogRef LOG = {"video.ffmpeg"};
|
||||
|
||||
static char ffmpeg_last_error_buffer[1024];
|
||||
|
||||
/* BLI_vsnprintf in ffmpeg_log_callback() causes invalid warning */
|
||||
# ifdef __GNUC__
|
||||
# pragma GCC diagnostic push
|
||||
# pragma GCC diagnostic ignored "-Wmissing-format-attribute"
|
||||
# endif
|
||||
|
||||
static size_t ffmpeg_log_to_buffer(char *buffer,
|
||||
const size_t buffer_size,
|
||||
const char *format,
|
||||
va_list arg)
|
||||
{
|
||||
va_list args_cpy;
|
||||
size_t n;
|
||||
|
||||
va_copy(args_cpy, arg);
|
||||
n = BLI_vsnprintf(buffer, buffer_size, format, args_cpy);
|
||||
va_end(args_cpy);
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
static void ffmpeg_log_callback(void * /*ptr*/, int level, const char *format, va_list arg)
|
||||
{
|
||||
CLG_Level clg_level;
|
||||
|
||||
switch (level) {
|
||||
case AV_LOG_PANIC:
|
||||
case AV_LOG_FATAL:
|
||||
/* ffmpeg "fatal" should not quit whole Blender; report as error. */
|
||||
clg_level = CLG_LEVEL_ERROR;
|
||||
break;
|
||||
case AV_LOG_ERROR:
|
||||
case AV_LOG_WARNING:
|
||||
case AV_LOG_INFO:
|
||||
/* Note: most ffmpeg internal errors/warnings are not actionable; treat them as "info"
|
||||
* log level. */
|
||||
clg_level = CLG_LEVEL_INFO;
|
||||
break;
|
||||
case AV_LOG_VERBOSE:
|
||||
case AV_LOG_DEBUG:
|
||||
clg_level = CLG_LEVEL_DEBUG;
|
||||
break;
|
||||
case AV_LOG_TRACE:
|
||||
default:
|
||||
clg_level = CLG_LEVEL_TRACE;
|
||||
break;
|
||||
}
|
||||
|
||||
static std::mutex mutex;
|
||||
std::scoped_lock lock(mutex);
|
||||
|
||||
if (ELEM(level, AV_LOG_PANIC, AV_LOG_FATAL, AV_LOG_ERROR)) {
|
||||
const size_t n = ffmpeg_log_to_buffer(
|
||||
ffmpeg_last_error_buffer, sizeof(ffmpeg_last_error_buffer), format, arg);
|
||||
/* Strip trailing \n. */
|
||||
ffmpeg_last_error_buffer[n - 1] = '\0';
|
||||
}
|
||||
|
||||
if (CLOG_CHECK(&LOG, clg_level)) {
|
||||
/* FFmpeg calls this multiple times without a line ending, so accumulate until
|
||||
* we reach a line ending. This will not work well with multithreading, but the
|
||||
* output would be garbled either way. */
|
||||
static char buffer[1024];
|
||||
static int buffer_used = 0;
|
||||
|
||||
buffer_used += ffmpeg_log_to_buffer(
|
||||
buffer + buffer_used, sizeof(buffer) - buffer_used, format, arg);
|
||||
|
||||
if (buffer_used >= sizeof(buffer) || (buffer_used > 0 && buffer[buffer_used - 1] == '\n')) {
|
||||
if (buffer[buffer_used - 1] == '\n') {
|
||||
buffer[buffer_used - 1] = '\0';
|
||||
}
|
||||
CLOG_STR_AT_LEVEL(&LOG, clg_level, buffer);
|
||||
buffer_used = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ifdef __GNUC__
|
||||
# pragma GCC diagnostic pop
|
||||
# endif
|
||||
|
||||
const char *ffmpeg_last_error()
|
||||
{
|
||||
return ffmpeg_last_error_buffer;
|
||||
}
|
||||
|
||||
static int isffmpeg(const char *filepath)
|
||||
{
|
||||
AVFormatContext *pFormatCtx = nullptr;
|
||||
uint i;
|
||||
int videoStream;
|
||||
const AVCodec *pCodec;
|
||||
|
||||
if (BLI_path_extension_check_n(filepath,
|
||||
".swf",
|
||||
".jpg",
|
||||
".jp2",
|
||||
".j2c",
|
||||
".png",
|
||||
".dds",
|
||||
".tga",
|
||||
".bmp",
|
||||
".tif",
|
||||
".exr",
|
||||
".cin",
|
||||
".wav",
|
||||
nullptr))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (avformat_open_input(&pFormatCtx, filepath, nullptr, nullptr) != 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (avformat_find_stream_info(pFormatCtx, nullptr) < 0) {
|
||||
avformat_close_input(&pFormatCtx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Find the first video stream */
|
||||
videoStream = -1;
|
||||
for (i = 0; i < pFormatCtx->nb_streams; i++) {
|
||||
if (pFormatCtx->streams[i] && pFormatCtx->streams[i]->codecpar &&
|
||||
(pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO))
|
||||
{
|
||||
videoStream = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (videoStream == -1) {
|
||||
avformat_close_input(&pFormatCtx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
AVCodecParameters *codec_par = pFormatCtx->streams[videoStream]->codecpar;
|
||||
|
||||
/* Find the decoder for the video stream */
|
||||
pCodec = avcodec_find_decoder(codec_par->codec_id);
|
||||
if (pCodec == nullptr) {
|
||||
avformat_close_input(&pFormatCtx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
avformat_close_input(&pFormatCtx);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/* AVFrame de-interlacing. Code for this was originally based on FFMPEG 2.6.4 (LGPL). */
|
||||
|
||||
# define MAX_NEG_CROP 1024
|
||||
|
||||
# define times4(x) x, x, x, x
|
||||
# define times256(x) times4(times4(times4(times4(times4(x)))))
|
||||
|
||||
static const uint8_t ff_compat_crop_tab[256 + 2 * MAX_NEG_CROP] = {
|
||||
times256(0x00), 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A,
|
||||
0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
|
||||
0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22,
|
||||
0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E,
|
||||
0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A,
|
||||
0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
|
||||
0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52,
|
||||
0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E,
|
||||
0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A,
|
||||
0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76,
|
||||
0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x80, 0x81, 0x82,
|
||||
0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E,
|
||||
0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A,
|
||||
0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6,
|
||||
0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2,
|
||||
0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE,
|
||||
0xBF, 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA,
|
||||
0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6,
|
||||
0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 0xE0, 0xE1, 0xE2,
|
||||
0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE,
|
||||
0xEF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA,
|
||||
0xFB, 0xFC, 0xFD, 0xFE, 0xFF, times256(0xFF)};
|
||||
|
||||
# undef times4
|
||||
# undef times256
|
||||
|
||||
/* filter parameters: [-1 4 2 4 -1] // 8 */
|
||||
FFMPEG_INLINE void deinterlace_line(uint8_t *dst,
|
||||
const uint8_t *lum_m4,
|
||||
const uint8_t *lum_m3,
|
||||
const uint8_t *lum_m2,
|
||||
const uint8_t *lum_m1,
|
||||
const uint8_t *lum,
|
||||
int size)
|
||||
{
|
||||
const uint8_t *cm = ff_compat_crop_tab + MAX_NEG_CROP;
|
||||
int sum;
|
||||
|
||||
for (; size > 0; size--) {
|
||||
sum = -lum_m4[0];
|
||||
sum += lum_m3[0] << 2;
|
||||
sum += lum_m2[0] << 1;
|
||||
sum += lum_m1[0] << 2;
|
||||
sum += -lum[0];
|
||||
dst[0] = cm[(sum + 4) >> 3];
|
||||
lum_m4++;
|
||||
lum_m3++;
|
||||
lum_m2++;
|
||||
lum_m1++;
|
||||
lum++;
|
||||
dst++;
|
||||
}
|
||||
}
|
||||
|
||||
FFMPEG_INLINE void deinterlace_line_inplace(
|
||||
uint8_t *lum_m4, uint8_t *lum_m3, uint8_t *lum_m2, uint8_t *lum_m1, uint8_t *lum, int size)
|
||||
{
|
||||
const uint8_t *cm = ff_compat_crop_tab + MAX_NEG_CROP;
|
||||
int sum;
|
||||
|
||||
for (; size > 0; size--) {
|
||||
sum = -lum_m4[0];
|
||||
sum += lum_m3[0] << 2;
|
||||
sum += lum_m2[0] << 1;
|
||||
lum_m4[0] = lum_m2[0];
|
||||
sum += lum_m1[0] << 2;
|
||||
sum += -lum[0];
|
||||
lum_m2[0] = cm[(sum + 4) >> 3];
|
||||
lum_m4++;
|
||||
lum_m3++;
|
||||
lum_m2++;
|
||||
lum_m1++;
|
||||
lum++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* De-interlacing: 2 temporal taps, 3 spatial taps linear filter.
|
||||
* The top field is copied as is, but the bottom field is de-interlaced against the top field.
|
||||
*/
|
||||
FFMPEG_INLINE void deinterlace_bottom_field(
|
||||
uint8_t *dst, int dst_wrap, const uint8_t *src1, int src_wrap, int width, int height)
|
||||
{
|
||||
const uint8_t *src_m2, *src_m1, *src_0, *src_p1, *src_p2;
|
||||
int y;
|
||||
|
||||
src_m2 = src1;
|
||||
src_m1 = src1;
|
||||
src_0 = &src_m1[src_wrap];
|
||||
src_p1 = &src_0[src_wrap];
|
||||
src_p2 = &src_p1[src_wrap];
|
||||
for (y = 0; y < (height - 2); y += 2) {
|
||||
memcpy(dst, src_m1, width);
|
||||
dst += dst_wrap;
|
||||
deinterlace_line(dst, src_m2, src_m1, src_0, src_p1, src_p2, width);
|
||||
src_m2 = src_0;
|
||||
src_m1 = src_p1;
|
||||
src_0 = src_p2;
|
||||
src_p1 += 2 * src_wrap;
|
||||
src_p2 += 2 * src_wrap;
|
||||
dst += dst_wrap;
|
||||
}
|
||||
memcpy(dst, src_m1, width);
|
||||
dst += dst_wrap;
|
||||
/* do last line */
|
||||
deinterlace_line(dst, src_m2, src_m1, src_0, src_0, src_0, width);
|
||||
}
|
||||
|
||||
FFMPEG_INLINE int deinterlace_bottom_field_inplace(uint8_t *src1,
|
||||
int src_wrap,
|
||||
int width,
|
||||
int height)
|
||||
{
|
||||
uint8_t *src_m1, *src_0, *src_p1, *src_p2;
|
||||
int y;
|
||||
uint8_t *buf = (uint8_t *)av_malloc(width);
|
||||
if (!buf) {
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
|
||||
src_m1 = src1;
|
||||
memcpy(buf, src_m1, width);
|
||||
src_0 = &src_m1[src_wrap];
|
||||
src_p1 = &src_0[src_wrap];
|
||||
src_p2 = &src_p1[src_wrap];
|
||||
for (y = 0; y < (height - 2); y += 2) {
|
||||
deinterlace_line_inplace(buf, src_m1, src_0, src_p1, src_p2, width);
|
||||
src_m1 = src_p1;
|
||||
src_0 = src_p2;
|
||||
src_p1 += 2 * src_wrap;
|
||||
src_p2 += 2 * src_wrap;
|
||||
}
|
||||
/* do last line */
|
||||
deinterlace_line_inplace(buf, src_m1, src_0, src_0, src_0, width);
|
||||
av_free(buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ffmpeg_deinterlace(
|
||||
AVFrame *dst, const AVFrame *src, enum AVPixelFormat pix_fmt, int width, int height)
|
||||
{
|
||||
int i, ret;
|
||||
|
||||
if (!ELEM(pix_fmt,
|
||||
AV_PIX_FMT_YUV420P,
|
||||
AV_PIX_FMT_YUVJ420P,
|
||||
AV_PIX_FMT_YUV422P,
|
||||
AV_PIX_FMT_YUVJ422P,
|
||||
AV_PIX_FMT_YUV444P,
|
||||
AV_PIX_FMT_YUV411P,
|
||||
AV_PIX_FMT_GRAY8))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if ((width & 3) != 0 || (height & 3) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (i = 0; i < 3; i++) {
|
||||
if (i == 1) {
|
||||
switch (pix_fmt) {
|
||||
case AV_PIX_FMT_YUVJ420P:
|
||||
case AV_PIX_FMT_YUV420P:
|
||||
width >>= 1;
|
||||
height >>= 1;
|
||||
break;
|
||||
case AV_PIX_FMT_YUV422P:
|
||||
case AV_PIX_FMT_YUVJ422P:
|
||||
width >>= 1;
|
||||
break;
|
||||
case AV_PIX_FMT_YUV411P:
|
||||
width >>= 2;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (pix_fmt == AV_PIX_FMT_GRAY8) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (src == dst) {
|
||||
ret = deinterlace_bottom_field_inplace(dst->data[i], dst->linesize[i], width, height);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
else {
|
||||
deinterlace_bottom_field(
|
||||
dst->data[i], dst->linesize[i], src->data[i], src->linesize[i], width, height);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
AVCodecID mov_av_codec_id_get(IMB_Ffmpeg_Codec_ID id)
|
||||
{
|
||||
switch (id) {
|
||||
case FFMPEG_CODEC_ID_NONE:
|
||||
return AV_CODEC_ID_NONE;
|
||||
case FFMPEG_CODEC_ID_MPEG1VIDEO:
|
||||
return AV_CODEC_ID_MPEG1VIDEO;
|
||||
case FFMPEG_CODEC_ID_MPEG2VIDEO:
|
||||
return AV_CODEC_ID_MPEG2VIDEO;
|
||||
case FFMPEG_CODEC_ID_MPEG4:
|
||||
return AV_CODEC_ID_MPEG4;
|
||||
case FFMPEG_CODEC_ID_FLV1:
|
||||
return AV_CODEC_ID_FLV1;
|
||||
case FFMPEG_CODEC_ID_DVVIDEO:
|
||||
return AV_CODEC_ID_DVVIDEO;
|
||||
case FFMPEG_CODEC_ID_HUFFYUV:
|
||||
return AV_CODEC_ID_HUFFYUV;
|
||||
case FFMPEG_CODEC_ID_H264:
|
||||
return AV_CODEC_ID_H264;
|
||||
case FFMPEG_CODEC_ID_THEORA:
|
||||
return AV_CODEC_ID_THEORA;
|
||||
case FFMPEG_CODEC_ID_FFV1:
|
||||
return AV_CODEC_ID_FFV1;
|
||||
case FFMPEG_CODEC_ID_QTRLE:
|
||||
return AV_CODEC_ID_QTRLE;
|
||||
case FFMPEG_CODEC_ID_PNG:
|
||||
return AV_CODEC_ID_PNG;
|
||||
case FFMPEG_CODEC_ID_DNXHD:
|
||||
return AV_CODEC_ID_DNXHD;
|
||||
case FFMPEG_CODEC_ID_VP9:
|
||||
return AV_CODEC_ID_VP9;
|
||||
case FFMPEG_CODEC_ID_H265:
|
||||
return AV_CODEC_ID_H265;
|
||||
case FFMPEG_CODEC_ID_AV1:
|
||||
return AV_CODEC_ID_AV1;
|
||||
case FFMPEG_CODEC_ID_PRORES:
|
||||
return AV_CODEC_ID_PRORES;
|
||||
case FFMPEG_CODEC_ID_PCM_S16LE:
|
||||
return AV_CODEC_ID_PCM_S16LE;
|
||||
case FFMPEG_CODEC_ID_MP2:
|
||||
return AV_CODEC_ID_MP2;
|
||||
case FFMPEG_CODEC_ID_MP3:
|
||||
return AV_CODEC_ID_MP3;
|
||||
case FFMPEG_CODEC_ID_AAC:
|
||||
return AV_CODEC_ID_AAC;
|
||||
case FFMPEG_CODEC_ID_AC3:
|
||||
return AV_CODEC_ID_AC3;
|
||||
case FFMPEG_CODEC_ID_VORBIS:
|
||||
return AV_CODEC_ID_VORBIS;
|
||||
case FFMPEG_CODEC_ID_FLAC:
|
||||
return AV_CODEC_ID_FLAC;
|
||||
case FFMPEG_CODEC_ID_OPUS:
|
||||
return AV_CODEC_ID_OPUS;
|
||||
}
|
||||
|
||||
BLI_assert_unreachable();
|
||||
return AV_CODEC_ID_NONE;
|
||||
}
|
||||
|
||||
static void ffmpeg_preset_set(RenderData *rd, int preset)
|
||||
{
|
||||
bool is_ntsc = (rd->frs_sec != 25);
|
||||
|
||||
switch (preset) {
|
||||
case FFMPEG_PRESET_H264:
|
||||
rd->ffcodecdata.type = FFMPEG_AVI;
|
||||
rd->ffcodecdata.codec_id_set(FFMPEG_CODEC_ID_H264);
|
||||
rd->ffcodecdata.video_bitrate = 6000;
|
||||
rd->ffcodecdata.gop_size = is_ntsc ? 18 : 15;
|
||||
rd->ffcodecdata.rc_max_rate = 9000;
|
||||
rd->ffcodecdata.rc_min_rate = 0;
|
||||
rd->ffcodecdata.rc_buffer_size = 224 * 8;
|
||||
rd->ffcodecdata.mux_packet_size = 2048;
|
||||
rd->ffcodecdata.mux_rate = 10080000;
|
||||
break;
|
||||
|
||||
case FFMPEG_PRESET_THEORA:
|
||||
case FFMPEG_PRESET_XVID:
|
||||
if (preset == FFMPEG_PRESET_XVID) {
|
||||
rd->ffcodecdata.type = FFMPEG_AVI;
|
||||
rd->ffcodecdata.codec_id_set(FFMPEG_CODEC_ID_MPEG4);
|
||||
}
|
||||
else if (preset == FFMPEG_PRESET_THEORA) {
|
||||
rd->ffcodecdata.type = FFMPEG_OGG; /* XXX broken */
|
||||
rd->ffcodecdata.codec_id_set(FFMPEG_CODEC_ID_THEORA);
|
||||
}
|
||||
|
||||
rd->ffcodecdata.video_bitrate = 6000;
|
||||
rd->ffcodecdata.gop_size = is_ntsc ? 18 : 15;
|
||||
rd->ffcodecdata.rc_max_rate = 9000;
|
||||
rd->ffcodecdata.rc_min_rate = 0;
|
||||
rd->ffcodecdata.rc_buffer_size = 224 * 8;
|
||||
rd->ffcodecdata.mux_packet_size = 2048;
|
||||
rd->ffcodecdata.mux_rate = 10080000;
|
||||
break;
|
||||
|
||||
case FFMPEG_PRESET_AV1:
|
||||
rd->ffcodecdata.type = FFMPEG_AV1;
|
||||
rd->ffcodecdata.codec_id_set(FFMPEG_CODEC_ID_AV1);
|
||||
rd->ffcodecdata.video_bitrate = 6000;
|
||||
rd->ffcodecdata.gop_size = is_ntsc ? 18 : 15;
|
||||
rd->ffcodecdata.rc_max_rate = 9000;
|
||||
rd->ffcodecdata.rc_min_rate = 0;
|
||||
rd->ffcodecdata.rc_buffer_size = 224 * 8;
|
||||
rd->ffcodecdata.mux_packet_size = 2048;
|
||||
rd->ffcodecdata.mux_rate = 10080000;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
eImageFormatDepth MOV_codec_valid_bit_depths(AVCodecID av_codec_id)
|
||||
{
|
||||
eImageFormatDepth bit_depths = R_IMF_CHAN_DEPTH_8;
|
||||
/* Note: update properties_output.py `use_bpp` when changing this function. */
|
||||
if (ELEM(av_codec_id,
|
||||
AV_CODEC_ID_H264,
|
||||
AV_CODEC_ID_H265,
|
||||
AV_CODEC_ID_AV1,
|
||||
AV_CODEC_ID_PRORES,
|
||||
AV_CODEC_ID_FFV1))
|
||||
{
|
||||
bit_depths |= R_IMF_CHAN_DEPTH_10;
|
||||
}
|
||||
if (ELEM(av_codec_id, AV_CODEC_ID_H265, AV_CODEC_ID_AV1, AV_CODEC_ID_FFV1)) {
|
||||
bit_depths |= R_IMF_CHAN_DEPTH_12;
|
||||
}
|
||||
if (ELEM(av_codec_id, AV_CODEC_ID_FFV1)) {
|
||||
bit_depths |= R_IMF_CHAN_DEPTH_16;
|
||||
}
|
||||
return bit_depths;
|
||||
}
|
||||
|
||||
bool MOV_codec_supports_alpha(AVCodecID av_codec_id, int ffmpeg_profile)
|
||||
{
|
||||
if (av_codec_id == AV_CODEC_ID_PRORES) {
|
||||
return ELEM(ffmpeg_profile, FFM_PRORES_PROFILE_4444, FFM_PRORES_PROFILE_4444_XQ);
|
||||
}
|
||||
return ELEM(av_codec_id,
|
||||
AV_CODEC_ID_FFV1,
|
||||
AV_CODEC_ID_QTRLE,
|
||||
AV_CODEC_ID_PNG,
|
||||
AV_CODEC_ID_VP9,
|
||||
AV_CODEC_ID_HUFFYUV);
|
||||
}
|
||||
|
||||
bool MOV_codec_supports_crf(AVCodecID av_codec_id)
|
||||
{
|
||||
return ELEM(av_codec_id,
|
||||
AV_CODEC_ID_H264,
|
||||
AV_CODEC_ID_H265,
|
||||
AV_CODEC_ID_MPEG4,
|
||||
AV_CODEC_ID_VP9,
|
||||
AV_CODEC_ID_AV1);
|
||||
}
|
||||
|
||||
int MOV_thread_count()
|
||||
{
|
||||
/* ffmpeg does not recommend thread counts above 16. */
|
||||
return std::min(BLI_system_thread_count(), 16);
|
||||
}
|
||||
|
||||
#endif /* WITH_FFMPEG */
|
||||
|
||||
bool MOV_is_movie_file(const char *filepath)
|
||||
{
|
||||
BLI_assert(!BLI_path_is_rel(filepath));
|
||||
|
||||
#ifdef WITH_FFMPEG
|
||||
if (isffmpeg(filepath)) {
|
||||
return true;
|
||||
}
|
||||
#else
|
||||
UNUSED_VARS(filepath);
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void MOV_init()
|
||||
{
|
||||
#ifdef WITH_FFMPEG
|
||||
avdevice_register_all();
|
||||
|
||||
ffmpeg_last_error_buffer[0] = '\0';
|
||||
|
||||
if (CLOG_CHECK(&LOG, CLG_LEVEL_INFO)) {
|
||||
av_log_set_level(AV_LOG_INFO);
|
||||
}
|
||||
else if (CLOG_CHECK(&LOG, CLG_LEVEL_DEBUG)) {
|
||||
av_log_set_level(AV_LOG_DEBUG);
|
||||
}
|
||||
else if (CLOG_CHECK(&LOG, CLG_LEVEL_TRACE)) {
|
||||
av_log_set_level(AV_LOG_TRACE);
|
||||
}
|
||||
|
||||
/* set separate callback which could store last error to report to UI */
|
||||
av_log_set_callback(ffmpeg_log_callback);
|
||||
#endif
|
||||
}
|
||||
|
||||
void MOV_exit()
|
||||
{
|
||||
#ifdef WITH_FFMPEG
|
||||
ffmpeg_sws_exit();
|
||||
#endif
|
||||
}
|
||||
|
||||
void MOV_validate_output_settings(RenderData *rd, const ImageFormatData *imf)
|
||||
{
|
||||
#ifdef WITH_FFMPEG
|
||||
|
||||
if (imf->imtype == R_IMF_IMTYPE_FFMPEG) {
|
||||
if (rd->ffcodecdata.type <= 0 || rd->ffcodecdata.codec_id_get() <= 0 ||
|
||||
rd->ffcodecdata.video_bitrate <= 1)
|
||||
{
|
||||
ffmpeg_preset_set(rd, FFMPEG_PRESET_H264);
|
||||
rd->ffcodecdata.constant_rate_factor = FFM_CRF_MEDIUM;
|
||||
rd->ffcodecdata.ffmpeg_preset = FFM_PRESET_GOOD;
|
||||
rd->ffcodecdata.type = FFMPEG_MKV;
|
||||
}
|
||||
if (rd->ffcodecdata.type == FFMPEG_OGG) {
|
||||
rd->ffcodecdata.type = FFMPEG_MPEG2;
|
||||
}
|
||||
}
|
||||
#else
|
||||
UNUSED_VARS(rd, imf);
|
||||
#endif
|
||||
}
|
||||
|
||||
eImageFormatDepth MOV_codec_valid_bit_depths(IMB_Ffmpeg_Codec_ID codec_id)
|
||||
{
|
||||
#ifdef WITH_FFMPEG
|
||||
return MOV_codec_valid_bit_depths(mov_av_codec_id_get(codec_id));
|
||||
#else
|
||||
UNUSED_VARS(codec_id);
|
||||
return R_IMF_CHAN_DEPTH_8;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool MOV_codec_supports_alpha(IMB_Ffmpeg_Codec_ID codec_id, int ffmpeg_profile)
|
||||
{
|
||||
#ifdef WITH_FFMPEG
|
||||
return MOV_codec_supports_alpha(mov_av_codec_id_get(codec_id), ffmpeg_profile);
|
||||
#else
|
||||
UNUSED_VARS(codec_id, ffmpeg_profile);
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool MOV_codec_supports_crf(IMB_Ffmpeg_Codec_ID codec_id)
|
||||
{
|
||||
#ifdef WITH_FFMPEG
|
||||
return MOV_codec_supports_crf(mov_av_codec_id_get(codec_id));
|
||||
#else
|
||||
UNUSED_VARS(codec_id);
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,52 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup imbuf
|
||||
*/
|
||||
|
||||
#ifdef WITH_FFMPEG
|
||||
|
||||
extern "C" {
|
||||
# include <libavcodec/avcodec.h>
|
||||
# include <libavutil/pixfmt.h>
|
||||
}
|
||||
# include "DNA_scene_types.h"
|
||||
|
||||
struct AVFrame;
|
||||
|
||||
namespace blender {
|
||||
|
||||
int ffmpeg_deinterlace(
|
||||
AVFrame *dst, const AVFrame *src, enum AVPixelFormat pix_fmt, int width, int height);
|
||||
|
||||
const char *ffmpeg_last_error();
|
||||
AVCodecID mov_av_codec_id_get(IMB_Ffmpeg_Codec_ID id);
|
||||
|
||||
/** Checks whether given FFMPEG codec and profile combination supports alpha channel (RGBA). */
|
||||
bool MOV_codec_supports_alpha(AVCodecID codec_id, int ffmpeg_profile);
|
||||
|
||||
/**
|
||||
* Checks whether given FFMPEG video AVCodecID supports CRF (i.e. "quality level")
|
||||
* setting. For codecs that do not support constant quality, only target bit-rate
|
||||
* can be specified.
|
||||
*/
|
||||
bool MOV_codec_supports_crf(AVCodecID codec_id);
|
||||
|
||||
/**
|
||||
* Which pixel bit depths are supported by a given FFMPEG video CodecID.
|
||||
* Returns bit-mask of `R_IMF_CHAN_DEPTH_` flags.
|
||||
*/
|
||||
eImageFormatDepth MOV_codec_valid_bit_depths(AVCodecID codec_id);
|
||||
|
||||
/**
|
||||
* Returns thread count to be used for ffmpeg.
|
||||
*/
|
||||
int MOV_thread_count();
|
||||
|
||||
} // namespace blender
|
||||
|
||||
#endif /* WITH_FFMPEG */
|
||||
1922
blender-5.2.0/source/blender/imbuf/movie/intern/movie_write.cc
Normal file
1922
blender-5.2.0/source/blender/imbuf/movie/intern/movie_write.cc
Normal file
File diff suppressed because it is too large
Load Diff
112
blender-5.2.0/source/blender/imbuf/movie/intern/movie_write.hh
Normal file
112
blender-5.2.0/source/blender/imbuf/movie/intern/movie_write.hh
Normal file
@@ -0,0 +1,112 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup imbuf
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef WITH_FFMPEG
|
||||
|
||||
# include <cstdint>
|
||||
/* Note: include cmath before ffmpeg headers, since both of them define
|
||||
* M_PI and other macros. This is to avoid warnings about macro redefinition
|
||||
* if later including cmath (MSVC 2019). */
|
||||
# if defined(_MSC_VER) && !defined(_USE_MATH_DEFINES)
|
||||
# define _USE_MATH_DEFINES
|
||||
# endif
|
||||
# include <cmath> // IWYU pragma: keep
|
||||
|
||||
extern "C" {
|
||||
# include <libavcodec/codec_id.h>
|
||||
# include <libavformat/avformat.h>
|
||||
# include <libavutil/buffer.h>
|
||||
# include <libavutil/channel_layout.h>
|
||||
# include <libavutil/imgutils.h>
|
||||
# include <libavutil/mastering_display_metadata.h>
|
||||
# include <libavutil/opt.h>
|
||||
# include <libavutil/rational.h>
|
||||
# include <libavutil/samplefmt.h>
|
||||
# include <libavutil/spherical.h>
|
||||
# include <libavutil/stereo3d.h>
|
||||
|
||||
# include "ffmpeg_compat.h"
|
||||
}
|
||||
|
||||
# ifdef WITH_AUDASPACE
|
||||
# include "BKE_sound_types.hh"
|
||||
# endif
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Scene;
|
||||
struct ReportList;
|
||||
struct StampData;
|
||||
|
||||
struct MovieWriter {
|
||||
int ffmpeg_type = 0;
|
||||
AVCodecID ffmpeg_codec = {};
|
||||
AVCodecID ffmpeg_audio_codec = {};
|
||||
int ffmpeg_video_bitrate = 0;
|
||||
int ffmpeg_audio_bitrate = 0;
|
||||
int ffmpeg_gop_size = 0;
|
||||
int ffmpeg_max_b_frames = 0;
|
||||
int ffmpeg_autosplit_count = 0;
|
||||
bool ffmpeg_autosplit = false;
|
||||
bool ffmpeg_preview = false;
|
||||
|
||||
int ffmpeg_crf = 0; /* set to 0 to not use CRF mode; we have another flag for lossless anyway. */
|
||||
bool custom_crf = false;
|
||||
int ffmpeg_preset = 0; /* see eFFMpegPreset */
|
||||
int ffmpeg_profile = 0;
|
||||
|
||||
AVFormatContext *outfile = nullptr;
|
||||
AVCodecContext *video_codec = nullptr;
|
||||
AVCodecContext *audio_codec = nullptr;
|
||||
AVStream *video_stream = nullptr;
|
||||
AVStream *audio_stream = nullptr;
|
||||
AVFrame *current_frame = nullptr; /* Image frame in output pixel format. */
|
||||
int video_time = 0;
|
||||
|
||||
/* Image frame in Blender's own pixel format, may need conversion to the output pixel format. */
|
||||
AVFrame *img_convert_frame = nullptr;
|
||||
SwsContext *img_convert_ctx = nullptr;
|
||||
|
||||
uint8_t *audio_input_buffer = nullptr;
|
||||
uint8_t *audio_deinterleave_buffer = nullptr;
|
||||
int audio_input_samples = 0;
|
||||
double audio_time = 0.0;
|
||||
double audio_time_total = 0.0;
|
||||
bool audio_deinterleave = false;
|
||||
int audio_sample_size = 0;
|
||||
|
||||
StampData *stamp_data = nullptr;
|
||||
|
||||
# ifdef WITH_AUDASPACE
|
||||
AUD_Device audio_mixdown_device;
|
||||
# endif
|
||||
};
|
||||
|
||||
bool movie_audio_open(MovieWriter *context,
|
||||
const Scene *scene,
|
||||
int start_frame,
|
||||
int mixrate,
|
||||
float volume,
|
||||
ReportList *reports);
|
||||
void movie_audio_close(MovieWriter *context, bool is_autosplit);
|
||||
|
||||
AVStream *alloc_audio_stream(MovieWriter *context,
|
||||
int audio_mixrate,
|
||||
int audio_channels,
|
||||
AVCodecID codec_id,
|
||||
AVFormatContext *of,
|
||||
char *error,
|
||||
int error_size,
|
||||
ReportList *reports);
|
||||
void write_audio_frames(MovieWriter *context, double to_pts);
|
||||
|
||||
} // namespace blender
|
||||
|
||||
#endif /* WITH_FFMPEG */
|
||||
@@ -0,0 +1,450 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup imbuf
|
||||
*/
|
||||
|
||||
#ifdef _MSC_VER
|
||||
/* This needs to be included first to prevent ffmpegs headers adding defines for various math
|
||||
* constants leading to duplicate definitions. */
|
||||
# include <cmath>
|
||||
#endif
|
||||
|
||||
#include "movie_util.hh"
|
||||
#include "movie_write.hh"
|
||||
|
||||
#ifdef WITH_FFMPEG
|
||||
|
||||
# include <cstdio>
|
||||
# include <cstring>
|
||||
|
||||
# include "DNA_scene_types.h"
|
||||
|
||||
# include "BLI_string.h"
|
||||
# include "BLI_utildefines.h"
|
||||
|
||||
# include "BKE_report.hh"
|
||||
# include "BKE_sound.hh"
|
||||
|
||||
# include "CLG_log.h"
|
||||
|
||||
namespace blender {
|
||||
|
||||
static CLG_LogRef LOG = {"video.write"};
|
||||
|
||||
/* If any of these codecs, we prefer the float sample format (if supported) */
|
||||
static bool request_float_audio_buffer(int codec_id)
|
||||
{
|
||||
return ELEM(codec_id, AV_CODEC_ID_AAC, AV_CODEC_ID_AC3, AV_CODEC_ID_VORBIS);
|
||||
}
|
||||
|
||||
# ifdef WITH_AUDASPACE
|
||||
|
||||
static int write_audio_frame(MovieWriter *context)
|
||||
{
|
||||
AVFrame *frame = nullptr;
|
||||
AVCodecContext *c = context->audio_codec;
|
||||
|
||||
bke::sound_device_read(
|
||||
context->audio_mixdown_device, context->audio_input_buffer, context->audio_input_samples);
|
||||
|
||||
frame = av_frame_alloc();
|
||||
frame->pts = context->audio_time / av_q2d(c->time_base);
|
||||
frame->nb_samples = context->audio_input_samples;
|
||||
frame->format = c->sample_fmt;
|
||||
# ifdef FFMPEG_USE_OLD_CHANNEL_VARS
|
||||
frame->channels = c->channels;
|
||||
frame->channel_layout = c->channel_layout;
|
||||
const int num_channels = c->channels;
|
||||
# else
|
||||
av_channel_layout_copy(&frame->ch_layout, &c->ch_layout);
|
||||
const int num_channels = c->ch_layout.nb_channels;
|
||||
# endif
|
||||
|
||||
if (context->audio_deinterleave) {
|
||||
int channel, i;
|
||||
uint8_t *temp;
|
||||
|
||||
for (channel = 0; channel < num_channels; channel++) {
|
||||
for (i = 0; i < frame->nb_samples; i++) {
|
||||
memcpy(context->audio_deinterleave_buffer +
|
||||
(i + channel * frame->nb_samples) * context->audio_sample_size,
|
||||
context->audio_input_buffer +
|
||||
(num_channels * i + channel) * context->audio_sample_size,
|
||||
context->audio_sample_size);
|
||||
}
|
||||
}
|
||||
|
||||
temp = context->audio_deinterleave_buffer;
|
||||
context->audio_deinterleave_buffer = context->audio_input_buffer;
|
||||
context->audio_input_buffer = temp;
|
||||
}
|
||||
|
||||
avcodec_fill_audio_frame(frame,
|
||||
num_channels,
|
||||
c->sample_fmt,
|
||||
context->audio_input_buffer,
|
||||
context->audio_input_samples * num_channels *
|
||||
context->audio_sample_size,
|
||||
1);
|
||||
|
||||
int success = 1;
|
||||
|
||||
char error_str[AV_ERROR_MAX_STRING_SIZE];
|
||||
int ret = avcodec_send_frame(c, frame);
|
||||
if (ret < 0) {
|
||||
/* Can't send frame to encoder. This shouldn't happen. */
|
||||
av_make_error_string(error_str, AV_ERROR_MAX_STRING_SIZE, ret);
|
||||
CLOG_ERROR(&LOG, "Can't send audio frame: %s", error_str);
|
||||
success = -1;
|
||||
}
|
||||
|
||||
AVPacket *pkt = av_packet_alloc();
|
||||
|
||||
while (ret >= 0) {
|
||||
|
||||
ret = avcodec_receive_packet(c, pkt);
|
||||
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
|
||||
break;
|
||||
}
|
||||
if (ret < 0) {
|
||||
av_make_error_string(error_str, AV_ERROR_MAX_STRING_SIZE, ret);
|
||||
CLOG_ERROR(&LOG, "Error encoding audio frame: %s", error_str);
|
||||
success = -1;
|
||||
}
|
||||
|
||||
pkt->stream_index = context->audio_stream->index;
|
||||
av_packet_rescale_ts(pkt, c->time_base, context->audio_stream->time_base);
|
||||
# ifdef FFMPEG_USE_DURATION_WORKAROUND
|
||||
my_guess_pkt_duration(context->outfile, context->audio_stream, pkt);
|
||||
# endif
|
||||
|
||||
pkt->flags |= AV_PKT_FLAG_KEY;
|
||||
|
||||
int write_ret = av_interleaved_write_frame(context->outfile, pkt);
|
||||
if (write_ret != 0) {
|
||||
av_make_error_string(error_str, AV_ERROR_MAX_STRING_SIZE, ret);
|
||||
CLOG_ERROR(&LOG, "Error writing audio packet: %s", error_str);
|
||||
success = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
av_packet_free(&pkt);
|
||||
av_frame_free(&frame);
|
||||
|
||||
return success;
|
||||
}
|
||||
# endif /* #ifdef WITH_AUDASPACE */
|
||||
|
||||
bool movie_audio_open(MovieWriter *context,
|
||||
const Scene *scene,
|
||||
int start_frame,
|
||||
int mixrate,
|
||||
float volume,
|
||||
ReportList *reports)
|
||||
{
|
||||
bool success = true;
|
||||
# ifdef WITH_AUDASPACE
|
||||
if (context->audio_stream) {
|
||||
AVCodecContext *c = context->audio_codec;
|
||||
|
||||
aud::DeviceSpecs specs;
|
||||
# ifdef FFMPEG_USE_OLD_CHANNEL_VARS
|
||||
specs.channels = aud::Channels(c->channels);
|
||||
# else
|
||||
specs.channels = aud::Channels(c->ch_layout.nb_channels);
|
||||
# endif
|
||||
|
||||
switch (av_get_packed_sample_fmt(c->sample_fmt)) {
|
||||
case AV_SAMPLE_FMT_U8:
|
||||
specs.format = aud::FORMAT_U8;
|
||||
break;
|
||||
case AV_SAMPLE_FMT_S16:
|
||||
specs.format = aud::FORMAT_S16;
|
||||
break;
|
||||
case AV_SAMPLE_FMT_S32:
|
||||
specs.format = aud::FORMAT_S32;
|
||||
break;
|
||||
case AV_SAMPLE_FMT_FLT:
|
||||
specs.format = aud::FORMAT_FLOAT32;
|
||||
break;
|
||||
case AV_SAMPLE_FMT_DBL:
|
||||
specs.format = aud::FORMAT_FLOAT64;
|
||||
break;
|
||||
default:
|
||||
BKE_report(reports, RPT_ERROR, "Audio sample format unsupported");
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
|
||||
specs.rate = mixrate;
|
||||
if (success) {
|
||||
context->audio_mixdown_device = BKE_sound_mixdown(scene, specs, start_frame, volume);
|
||||
}
|
||||
}
|
||||
# else
|
||||
UNUSED_VARS(context, scene, start_frame, mixrate, volume, reports);
|
||||
# endif
|
||||
return success;
|
||||
}
|
||||
|
||||
void movie_audio_close(MovieWriter *context, bool is_autosplit)
|
||||
{
|
||||
# ifdef WITH_AUDASPACE
|
||||
if (!is_autosplit) {
|
||||
context->audio_mixdown_device.reset();
|
||||
}
|
||||
# else
|
||||
UNUSED_VARS(context, is_autosplit);
|
||||
# endif
|
||||
}
|
||||
|
||||
AVStream *alloc_audio_stream(MovieWriter *context,
|
||||
int audio_mixrate,
|
||||
int audio_channels,
|
||||
AVCodecID codec_id,
|
||||
AVFormatContext *of,
|
||||
char *error,
|
||||
int error_size,
|
||||
ReportList *reports)
|
||||
{
|
||||
AVStream *st;
|
||||
const AVCodec *codec;
|
||||
|
||||
error[0] = '\0';
|
||||
|
||||
st = avformat_new_stream(of, nullptr);
|
||||
if (!st) {
|
||||
return nullptr;
|
||||
}
|
||||
st->id = 1;
|
||||
|
||||
codec = avcodec_find_encoder(codec_id);
|
||||
if (!codec) {
|
||||
CLOG_ERROR(&LOG, "Couldn't find valid audio codec");
|
||||
context->audio_codec = nullptr;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int channel_layout_mask = 0;
|
||||
int channel_count = 0;
|
||||
switch (audio_channels) {
|
||||
case FFM_CHANNELS_MONO:
|
||||
channel_layout_mask = AV_CH_LAYOUT_MONO;
|
||||
channel_count = 1;
|
||||
break;
|
||||
case FFM_CHANNELS_STEREO:
|
||||
channel_layout_mask = AV_CH_LAYOUT_STEREO;
|
||||
channel_count = 2;
|
||||
break;
|
||||
case FFM_CHANNELS_SURROUND4:
|
||||
channel_layout_mask = AV_CH_LAYOUT_QUAD;
|
||||
channel_count = 4;
|
||||
break;
|
||||
case FFM_CHANNELS_SURROUND51:
|
||||
channel_layout_mask = AV_CH_LAYOUT_5POINT1_BACK;
|
||||
channel_count = 6;
|
||||
break;
|
||||
case FFM_CHANNELS_SURROUND71:
|
||||
channel_layout_mask = AV_CH_LAYOUT_7POINT1;
|
||||
channel_count = 8;
|
||||
break;
|
||||
default:
|
||||
BLI_assert(false);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Clamp audio bitrate and report info if bitrate is set higher than the maximum bitrate of the
|
||||
* codec. */
|
||||
switch (codec_id) {
|
||||
case AV_CODEC_ID_MP2:
|
||||
if (context->ffmpeg_audio_bitrate > 384) {
|
||||
context->ffmpeg_audio_bitrate = 384;
|
||||
BKE_report(reports,
|
||||
RPT_INFO,
|
||||
"The audio is rendered with a bitrate of 384kbit/s, the maximum bitrate MP2 "
|
||||
"supports.");
|
||||
}
|
||||
break;
|
||||
case AV_CODEC_ID_MP3:
|
||||
if (context->ffmpeg_audio_bitrate > 320) {
|
||||
context->ffmpeg_audio_bitrate = 320;
|
||||
BKE_report(reports,
|
||||
RPT_INFO,
|
||||
"The audio is rendered with a bitrate of 320kbit/s, the maximum bitrate MP3 "
|
||||
"supports.");
|
||||
}
|
||||
break;
|
||||
case AV_CODEC_ID_AAC:
|
||||
if (context->ffmpeg_audio_bitrate > 250 * channel_count) {
|
||||
/* AAC doesn't specify a maximum bitrate. Instead, the maximum bitrate is dependent on the
|
||||
* encoder used. Clamping of the bitrate is therefore left to the encoder. */
|
||||
BKE_report(
|
||||
reports,
|
||||
RPT_INFO,
|
||||
"The audio is rendered with a bitrate of roughly 250kbit/s per channel, the maximum "
|
||||
"bitrate AAC supports.");
|
||||
}
|
||||
break;
|
||||
case AV_CODEC_ID_AC3:
|
||||
if (context->ffmpeg_audio_bitrate > 640) {
|
||||
context->ffmpeg_audio_bitrate = 640;
|
||||
BKE_report(reports,
|
||||
RPT_INFO,
|
||||
"The audio is rendered with a bitrate of 640kbit/s, the maximum bitrate AC3 "
|
||||
"supports.");
|
||||
}
|
||||
break;
|
||||
case AV_CODEC_ID_OPUS:
|
||||
if (context->ffmpeg_audio_bitrate > 256 * channel_count) {
|
||||
context->ffmpeg_audio_bitrate = 256 * channel_count;
|
||||
BKE_report(reports,
|
||||
RPT_INFO,
|
||||
"The audio is rendered with a bitrate of 256kbit/s per channel, the maximum "
|
||||
"bitrate Opus supports.");
|
||||
}
|
||||
break;
|
||||
case AV_CODEC_ID_VORBIS:
|
||||
if (context->ffmpeg_audio_bitrate > 240 * channel_count) {
|
||||
context->ffmpeg_audio_bitrate = 240 * channel_count;
|
||||
BKE_report(reports,
|
||||
RPT_INFO,
|
||||
"The audio is rendered with a bitrate of 240kbit/s per channel, the maximum "
|
||||
"bitrate Vorbis supports.");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
/* Default case for suppressing compiler warnings. */
|
||||
break;
|
||||
}
|
||||
|
||||
context->audio_codec = avcodec_alloc_context3(codec);
|
||||
AVCodecContext *c = context->audio_codec;
|
||||
c->thread_count = MOV_thread_count();
|
||||
c->thread_type = FF_THREAD_SLICE;
|
||||
|
||||
c->sample_rate = audio_mixrate;
|
||||
c->bit_rate = context->ffmpeg_audio_bitrate * 1000;
|
||||
c->sample_fmt = AV_SAMPLE_FMT_S16;
|
||||
|
||||
# ifdef FFMPEG_USE_OLD_CHANNEL_VARS
|
||||
c->channels = audio_channels;
|
||||
c->channel_layout = channel_layout_mask;
|
||||
# else
|
||||
av_channel_layout_from_mask(&c->ch_layout, channel_layout_mask);
|
||||
# endif
|
||||
|
||||
if (request_float_audio_buffer(codec_id)) {
|
||||
/* mainly for AAC codec which is experimental */
|
||||
c->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
|
||||
c->sample_fmt = AV_SAMPLE_FMT_FLT;
|
||||
}
|
||||
|
||||
const enum AVSampleFormat *sample_fmts = ffmpeg_get_sample_fmts(c, codec);
|
||||
if (sample_fmts) {
|
||||
/* Check if the preferred sample format for this codec is supported.
|
||||
* this is because, depending on the version of LIBAV,
|
||||
* and with the whole FFMPEG/LIBAV fork situation,
|
||||
* you have various implementations around.
|
||||
* Float samples in particular are not always supported. */
|
||||
const enum AVSampleFormat *p = sample_fmts;
|
||||
for (; *p != -1; p++) {
|
||||
if (*p == c->sample_fmt) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (*p == -1) {
|
||||
/* sample format incompatible with codec. Defaulting to a format known to work */
|
||||
c->sample_fmt = sample_fmts[0];
|
||||
}
|
||||
}
|
||||
|
||||
const int *supported_samplerates = ffmpeg_get_sample_rates(c, codec);
|
||||
if (supported_samplerates) {
|
||||
const int *p = supported_samplerates;
|
||||
int best = 0;
|
||||
int best_dist = INT_MAX;
|
||||
for (; *p; p++) {
|
||||
int dist = abs(c->sample_rate - *p);
|
||||
if (dist < best_dist) {
|
||||
best_dist = dist;
|
||||
best = *p;
|
||||
}
|
||||
}
|
||||
/* best is the closest supported sample rate (same as selected if best_dist == 0) */
|
||||
c->sample_rate = best;
|
||||
}
|
||||
|
||||
if (of->oformat->flags & AVFMT_GLOBALHEADER) {
|
||||
c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
|
||||
}
|
||||
|
||||
int ret = avcodec_open2(c, codec, nullptr);
|
||||
|
||||
if (ret < 0) {
|
||||
char error_str[AV_ERROR_MAX_STRING_SIZE];
|
||||
av_make_error_string(error_str, AV_ERROR_MAX_STRING_SIZE, ret);
|
||||
CLOG_ERROR(&LOG, "Couldn't initialize audio codec: %s", error_str);
|
||||
BLI_strncpy(error, ffmpeg_last_error(), error_size);
|
||||
avcodec_free_context(&c);
|
||||
context->audio_codec = nullptr;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* Need to prevent floating point exception when using VORBIS audio codec,
|
||||
* initialize this value in the same way as it's done in FFMPEG itself (sergey) */
|
||||
c->time_base.num = 1;
|
||||
c->time_base.den = c->sample_rate;
|
||||
|
||||
if (c->codec->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE) {
|
||||
/* If the audio format has a variable frame size, default to 1024.
|
||||
* This is because we won't try to encode any variable frame size.
|
||||
* 1024 seems to be a good compromise between size and speed.
|
||||
*/
|
||||
context->audio_input_samples = 1024;
|
||||
}
|
||||
else {
|
||||
context->audio_input_samples = c->frame_size;
|
||||
}
|
||||
|
||||
context->audio_deinterleave = av_sample_fmt_is_planar(c->sample_fmt);
|
||||
|
||||
context->audio_sample_size = av_get_bytes_per_sample(c->sample_fmt);
|
||||
|
||||
context->audio_input_buffer = (uint8_t *)av_malloc(context->audio_input_samples *
|
||||
audio_channels * context->audio_sample_size);
|
||||
if (context->audio_deinterleave) {
|
||||
context->audio_deinterleave_buffer = (uint8_t *)av_malloc(
|
||||
context->audio_input_samples * audio_channels * context->audio_sample_size);
|
||||
}
|
||||
|
||||
context->audio_time = 0.0f;
|
||||
|
||||
avcodec_parameters_from_context(st->codecpar, c);
|
||||
|
||||
return st;
|
||||
}
|
||||
|
||||
void write_audio_frames(MovieWriter *context, double to_pts)
|
||||
{
|
||||
# ifdef WITH_AUDASPACE
|
||||
AVCodecContext *c = context->audio_codec;
|
||||
|
||||
while (context->audio_stream) {
|
||||
if ((context->audio_time_total >= to_pts) || !write_audio_frame(context)) {
|
||||
break;
|
||||
}
|
||||
context->audio_time_total += double(context->audio_input_samples) / double(c->sample_rate);
|
||||
context->audio_time += double(context->audio_input_samples) / double(c->sample_rate);
|
||||
}
|
||||
# else
|
||||
UNUSED_VARS(context, to_pts);
|
||||
# endif
|
||||
}
|
||||
|
||||
} // namespace blender
|
||||
|
||||
#endif /* WITH_FFMPEG */
|
||||
180
blender-5.2.0/source/blender/imbuf/movie/tests/ffmpeg_codecs.cc
Normal file
180
blender-5.2.0/source/blender/imbuf/movie/tests/ffmpeg_codecs.cc
Normal file
@@ -0,0 +1,180 @@
|
||||
/* SPDX-FileCopyrightText: 2020-2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "testing/testing.h"
|
||||
|
||||
#include "BKE_gtest_base.hh"
|
||||
|
||||
extern "C" {
|
||||
#include "ffmpeg_compat.h"
|
||||
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavutil/channel_layout.h>
|
||||
#include <libavutil/log.h>
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
bool test_vcodec(const AVCodec *codec, AVPixelFormat pixelformat)
|
||||
{
|
||||
av_log_set_level(AV_LOG_QUIET);
|
||||
bool result = false;
|
||||
if (codec) {
|
||||
AVCodecContext *ctx = avcodec_alloc_context3(codec);
|
||||
if (ctx) {
|
||||
ctx->time_base.num = 1;
|
||||
ctx->time_base.den = 25;
|
||||
ctx->pix_fmt = pixelformat;
|
||||
ctx->width = 720;
|
||||
ctx->height = 576;
|
||||
int open = avcodec_open2(ctx, codec, nullptr);
|
||||
if (open >= 0) {
|
||||
avcodec_free_context(&ctx);
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
bool test_acodec(const AVCodec *codec, AVSampleFormat fmt)
|
||||
{
|
||||
av_log_set_level(AV_LOG_QUIET);
|
||||
bool result = false;
|
||||
if (codec) {
|
||||
AVCodecContext *ctx = avcodec_alloc_context3(codec);
|
||||
if (ctx) {
|
||||
ctx->sample_fmt = fmt;
|
||||
ctx->sample_rate = 48000;
|
||||
#ifdef FFMPEG_USE_OLD_CHANNEL_VARS
|
||||
ctx->channel_layout = AV_CH_LAYOUT_MONO;
|
||||
#else
|
||||
av_channel_layout_from_mask(&ctx->ch_layout, AV_CH_LAYOUT_MONO);
|
||||
#endif
|
||||
ctx->bit_rate = 128000;
|
||||
int open = avcodec_open2(ctx, codec, nullptr);
|
||||
if (open >= 0) {
|
||||
avcodec_free_context(&ctx);
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool test_codec_video_by_codecid(AVCodecID codec_id, AVPixelFormat pixelformat)
|
||||
{
|
||||
bool result = false;
|
||||
const AVCodec *codec = avcodec_find_encoder(codec_id);
|
||||
if (codec) {
|
||||
result = test_vcodec(codec, pixelformat);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool test_codec_video_by_name(const char *codecname, AVPixelFormat pixelformat)
|
||||
{
|
||||
bool result = false;
|
||||
const AVCodec *codec = avcodec_find_encoder_by_name(codecname);
|
||||
if (codec) {
|
||||
result = test_vcodec(codec, pixelformat);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool test_codec_audio_by_codecid(AVCodecID codec_id, AVSampleFormat fmt)
|
||||
{
|
||||
bool result = false;
|
||||
const AVCodec *codec = avcodec_find_encoder(codec_id);
|
||||
if (codec) {
|
||||
result = test_acodec(codec, fmt);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool test_codec_audio_by_name(const char *codecname, AVSampleFormat fmt)
|
||||
{
|
||||
bool result = false;
|
||||
const AVCodec *codec = avcodec_find_encoder_by_name(codecname);
|
||||
if (codec) {
|
||||
result = test_acodec(codec, fmt);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
class FFmpegTest : public blender::bke::BlenderGTestBase {};
|
||||
|
||||
#define str(s) #s
|
||||
#define FFMPEG_TEST_VCODEC_ID(codec, fmt) \
|
||||
TEST_F(FFmpegTest, codec##_##fmt) \
|
||||
{ \
|
||||
EXPECT_TRUE(test_codec_video_by_codecid(codec, fmt)); \
|
||||
}
|
||||
|
||||
#define FFMPEG_TEST_VCODEC_NAME(codec, fmt) \
|
||||
TEST_F(FFmpegTest, codec##_##fmt) \
|
||||
{ \
|
||||
EXPECT_TRUE(test_codec_video_by_name(str(codec), fmt)); \
|
||||
}
|
||||
|
||||
#define FFMPEG_TEST_ACODEC_ID(codec, fmt) \
|
||||
TEST_F(FFmpegTest, codec##_##fmt) \
|
||||
{ \
|
||||
EXPECT_TRUE(test_codec_audio_by_codecid(codec, fmt)); \
|
||||
}
|
||||
|
||||
#define FFMPEG_TEST_ACODEC_NAME(codec, fmt) \
|
||||
TEST_F(FFmpegTest, codec) \
|
||||
{ \
|
||||
EXPECT_TRUE(test_codec_audio_by_name(str(codec), fmt)); \
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/* generic codec ID's used in blender */
|
||||
|
||||
FFMPEG_TEST_VCODEC_ID(AV_CODEC_ID_HUFFYUV, AV_PIX_FMT_BGRA)
|
||||
FFMPEG_TEST_VCODEC_ID(AV_CODEC_ID_HUFFYUV, AV_PIX_FMT_RGB32)
|
||||
FFMPEG_TEST_VCODEC_ID(AV_CODEC_ID_FFV1, AV_PIX_FMT_RGB32)
|
||||
FFMPEG_TEST_VCODEC_ID(AV_CODEC_ID_QTRLE, AV_PIX_FMT_ARGB)
|
||||
FFMPEG_TEST_VCODEC_ID(AV_CODEC_ID_VP9, AV_PIX_FMT_YUVA420P)
|
||||
FFMPEG_TEST_VCODEC_ID(AV_CODEC_ID_PNG, AV_PIX_FMT_RGBA)
|
||||
FFMPEG_TEST_VCODEC_ID(AV_CODEC_ID_H264, AV_PIX_FMT_YUV420P)
|
||||
FFMPEG_TEST_VCODEC_ID(AV_CODEC_ID_H264, AV_PIX_FMT_YUV420P10LE)
|
||||
FFMPEG_TEST_VCODEC_ID(AV_CODEC_ID_H265, AV_PIX_FMT_YUV420P)
|
||||
FFMPEG_TEST_VCODEC_ID(AV_CODEC_ID_H265, AV_PIX_FMT_YUV420P10LE)
|
||||
FFMPEG_TEST_VCODEC_ID(AV_CODEC_ID_H265, AV_PIX_FMT_YUV420P12LE)
|
||||
FFMPEG_TEST_VCODEC_ID(AV_CODEC_ID_MPEG4, AV_PIX_FMT_YUV420P)
|
||||
FFMPEG_TEST_VCODEC_ID(AV_CODEC_ID_THEORA, AV_PIX_FMT_YUV420P)
|
||||
FFMPEG_TEST_VCODEC_ID(AV_CODEC_ID_DVVIDEO, AV_PIX_FMT_YUV420P)
|
||||
FFMPEG_TEST_VCODEC_ID(AV_CODEC_ID_MPEG1VIDEO, AV_PIX_FMT_YUV420P)
|
||||
FFMPEG_TEST_VCODEC_ID(AV_CODEC_ID_MPEG2VIDEO, AV_PIX_FMT_YUV420P)
|
||||
FFMPEG_TEST_VCODEC_ID(AV_CODEC_ID_FLV1, AV_PIX_FMT_YUV420P)
|
||||
FFMPEG_TEST_VCODEC_ID(AV_CODEC_ID_AV1, AV_PIX_FMT_YUV420P)
|
||||
|
||||
/* Audio codecs */
|
||||
|
||||
FFMPEG_TEST_ACODEC_ID(AV_CODEC_ID_AAC, AV_SAMPLE_FMT_FLTP)
|
||||
FFMPEG_TEST_ACODEC_ID(AV_CODEC_ID_AC3, AV_SAMPLE_FMT_FLTP)
|
||||
FFMPEG_TEST_ACODEC_ID(AV_CODEC_ID_FLAC, AV_SAMPLE_FMT_S16)
|
||||
FFMPEG_TEST_ACODEC_ID(AV_CODEC_ID_MP2, AV_SAMPLE_FMT_S16)
|
||||
FFMPEG_TEST_ACODEC_ID(AV_CODEC_ID_MP3, AV_SAMPLE_FMT_FLTP)
|
||||
FFMPEG_TEST_ACODEC_ID(AV_CODEC_ID_OPUS, AV_SAMPLE_FMT_FLT)
|
||||
FFMPEG_TEST_ACODEC_ID(AV_CODEC_ID_PCM_S16LE, AV_SAMPLE_FMT_S16)
|
||||
FFMPEG_TEST_ACODEC_ID(AV_CODEC_ID_VORBIS, AV_SAMPLE_FMT_FLTP)
|
||||
|
||||
/* Libraries we count on ffmpeg being linked against */
|
||||
|
||||
FFMPEG_TEST_VCODEC_NAME(libtheora, AV_PIX_FMT_YUV420P)
|
||||
FFMPEG_TEST_VCODEC_NAME(libx264, AV_PIX_FMT_YUV420P)
|
||||
FFMPEG_TEST_VCODEC_NAME(libvpx, AV_PIX_FMT_YUV420P)
|
||||
FFMPEG_TEST_VCODEC_NAME(libopenjpeg, AV_PIX_FMT_YUV420P)
|
||||
/* AOM's AV1 encoder is `libaom-av1`. `FFMPEG_TEST_VCODEC_NAME(libaom-av1, ...)`
|
||||
* will not work because the dash will not work with the test macro. */
|
||||
TEST_F(FFmpegTest, libaom_av1_AV_PIX_FMT_YUV420P)
|
||||
{
|
||||
EXPECT_TRUE(test_codec_video_by_name("libaom-av1", AV_PIX_FMT_YUV420P));
|
||||
}
|
||||
FFMPEG_TEST_ACODEC_NAME(libvorbis, AV_SAMPLE_FMT_FLTP)
|
||||
FFMPEG_TEST_ACODEC_NAME(libopus, AV_SAMPLE_FMT_FLT)
|
||||
FFMPEG_TEST_ACODEC_NAME(libmp3lame, AV_SAMPLE_FMT_FLTP)
|
||||
@@ -0,0 +1,31 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "testing/testing.h"
|
||||
|
||||
namespace blender {
|
||||
|
||||
extern "C" {
|
||||
#include <libavutil/cpu.h>
|
||||
}
|
||||
|
||||
namespace ffmpeg::tests {
|
||||
|
||||
TEST(ffmpeg, correct_av_cpu_flags)
|
||||
{
|
||||
int flags = av_get_cpu_flags();
|
||||
#if defined(_M_X64) || defined(__x86_64__)
|
||||
/* x64 expected to have at least up to SSE4.2. */
|
||||
EXPECT_TRUE((flags & AV_CPU_FLAG_SSE2) != 0);
|
||||
EXPECT_TRUE((flags & AV_CPU_FLAG_SSE4) != 0);
|
||||
EXPECT_TRUE((flags & AV_CPU_FLAG_SSE42) != 0);
|
||||
#elif defined(__aarch64__) || defined(_M_ARM64)
|
||||
/* arm64 expected to have at least NEON. */
|
||||
EXPECT_TRUE((flags & AV_CPU_FLAG_ARMV8) != 0);
|
||||
EXPECT_TRUE((flags & AV_CPU_FLAG_NEON) != 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace ffmpeg::tests
|
||||
} // namespace blender
|
||||
Reference in New Issue
Block a user