diff --git a/intern/CMakeLists.txt b/intern/CMakeLists.txt index 851e46077cf..16e0c11b413 100644 --- a/intern/CMakeLists.txt +++ b/intern/CMakeLists.txt @@ -64,10 +64,6 @@ if(WITH_QUADRIFLOW) add_subdirectory(quadriflow) endif() -if(WITH_CODEC_FFMPEG) - add_subdirectory(ffmpeg) -endif() - if(UNIX AND NOT APPLE) add_subdirectory(libc_compat) endif() diff --git a/intern/ffmpeg/CMakeLists.txt b/intern/ffmpeg/CMakeLists.txt deleted file mode 100644 index bd7ae8b0ca9..00000000000 --- a/intern/ffmpeg/CMakeLists.txt +++ /dev/null @@ -1,27 +0,0 @@ -# SPDX-FileCopyrightText: 2020-2024 Blender Authors -# -# SPDX-License-Identifier: GPL-2.0-or-later - -if(WITH_GTESTS) - set(TEST_SRC - tests/ffmpeg_codecs.cc - tests/ffmpeg_cpu_flags.cc - ) - set(TEST_INC - . - ) - set(TEST_INC_SYS - ${FFMPEG_INCLUDE_DIRS} - ${PNG_INCLUDE_DIRS} - ${ZLIB_INCLUDE_DIRS} - ) - set(TEST_LIB - ${PNG_LIBRARIES} - ${FFMPEG_LIBRARIES} - ${ZLIB_LIBRARIES} - ) - if(WITH_IMAGE_OPENJPEG) - set(TEST_LIB ${TEST_LIB} ${OPENJPEG_LIBRARIES}) - endif() - blender_add_test_suite_lib(ffmpeg_libs "${TEST_SRC}" "${TEST_INC}" "${TEST_INC_SYS}" "${TEST_LIB}") -endif() diff --git a/scripts/startup/bl_ui/properties_output.py b/scripts/startup/bl_ui/properties_output.py index 8c4815acba6..d71d0ea6c3e 100644 --- a/scripts/startup/bl_ui/properties_output.py +++ b/scripts/startup/bl_ui/properties_output.py @@ -446,7 +446,7 @@ class RENDER_PT_encoding_video(RenderOutputButtonsPanel, Panel): return # Color depth. List of codecs needs to be in sync with - # `BKE_ffmpeg_valid_bit_depths` in source code. + # `IMB_ffmpeg_valid_bit_depths` in source code. use_bpp = needs_codec and ffmpeg.codec in {'H264', 'H265', 'AV1'} if use_bpp: image_settings = context.scene.render.image_settings diff --git a/source/blender/blenkernel/BKE_writeffmpeg.hh b/source/blender/blenkernel/BKE_writeffmpeg.hh deleted file mode 100644 index 3ab2d540f1c..00000000000 --- a/source/blender/blenkernel/BKE_writeffmpeg.hh +++ /dev/null @@ -1,99 +0,0 @@ -/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved. - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -#pragma once - -/** \file - * \ingroup bke - */ - -#ifdef WITH_FFMPEG - -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, -}; - -struct AVFrame; -struct ImageFormatData; -struct ImBuf; -struct RenderData; -struct ReportList; -struct Scene; -struct SwsContext; - -bool BKE_ffmpeg_start(void *context_v, - const Scene *scene, - RenderData *rd, - int rectx, - int recty, - ReportList *reports, - bool preview, - const char *suffix); -void BKE_ffmpeg_end(void *context_v); -bool BKE_ffmpeg_append(void *context_v, - RenderData *rd, - int start_frame, - int frame, - const ImBuf *image, - const char *suffix, - ReportList *reports); -void BKE_ffmpeg_filepath_get(char filepath[/*FILE_MAX*/ 1024], - const RenderData *rd, - bool preview, - const char *suffix); - -void BKE_ffmpeg_preset_set(RenderData *rd, int preset); -void BKE_ffmpeg_image_type_verify(RenderData *rd, const ImageFormatData *imf); -bool BKE_ffmpeg_alpha_channel_is_supported(const RenderData *rd); -bool BKE_ffmpeg_codec_supports_crf(int av_codec_id); -/** - * Which pixel bit depths are supported by a given video codec. - * Returns bitmask of `R_IMF_CHAN_DEPTH_` flags. - */ -int BKE_ffmpeg_valid_bit_depths(int av_codec_id); - -void *BKE_ffmpeg_context_create(); -void BKE_ffmpeg_context_free(void *context_v); - -void BKE_ffmpeg_exit(); - -/** - * Gets a `libswscale` context for given size and format parameters. - * After you're done using the context, call #BKE_ffmpeg_sws_release_context - * to release it. Internally the contexts are coming from the context - * pool/cache. - */ -SwsContext *BKE_ffmpeg_sws_get_context(int src_width, - int src_height, - int av_src_format, - int dst_width, - int dst_height, - int av_dst_format, - int sws_flags); -void BKE_ffmpeg_sws_release_context(SwsContext *ctx); - -void BKE_ffmpeg_sws_scale_frame(SwsContext *ctx, AVFrame *dst, const AVFrame *src); - -#endif diff --git a/source/blender/blenkernel/BKE_writemovie.hh b/source/blender/blenkernel/BKE_writemovie.hh deleted file mode 100644 index f8a14fc971b..00000000000 --- a/source/blender/blenkernel/BKE_writemovie.hh +++ /dev/null @@ -1,54 +0,0 @@ -/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved. - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -#pragma once - -/** \file - * \ingroup bke - */ - -/* generic blender movie support, could move to own module */ - -struct ImBuf; -struct RenderData; -struct ReportList; -struct Scene; - -struct bMovieHandle { - bool (*start_movie)(void *context_v, - const Scene *scene, - RenderData *rd, - int rectx, - int recty, - ReportList *reports, - bool preview, - const char *suffix); - bool (*append_movie)(void *context_v, - RenderData *rd, - int start_frame, - int frame, - const ImBuf *image, - const char *suffix, - ReportList *reports); - void (*end_movie)(void *context_v); - - /* Optional function. */ - void (*get_movie_path)(char filepath[/*FILE_MAX*/ 1024], - const RenderData *rd, - bool preview, - const char *suffix); - - void *(*context_create)(); - void (*context_free)(void *context_v); -}; - -bMovieHandle *BKE_movie_handle_get(char imtype); - -/** - * \note Similar to #BKE_image_path_from_imformat() - */ -void BKE_movie_filepath_get(char filepath[/*FILE_MAX*/ 1024], - const RenderData *rd, - bool preview, - const char *suffix); diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index 9f256ea5046..33ed89dec3e 100644 --- a/source/blender/blenkernel/CMakeLists.txt +++ b/source/blender/blenkernel/CMakeLists.txt @@ -322,7 +322,6 @@ set(SRC intern/wm_runtime.cc intern/workspace.cc intern/world.cc - intern/writemovie.cc BKE_action.hh BKE_addon.h @@ -535,7 +534,6 @@ set(SRC BKE_wm_runtime.hh BKE_workspace.hh BKE_world.h - BKE_writemovie.hh nla_private.h particle_private.h @@ -574,6 +572,7 @@ set(LIB bf_gpu bf_ikplugin bf_imbuf + bf::imbuf_movie PRIVATE bf::intern::clog bf_intern_ghost PRIVATE bf::intern::guardedalloc @@ -654,19 +653,6 @@ if(WITH_IMAGE_WEBP) endif() if(WITH_CODEC_FFMPEG) - list(APPEND SRC - intern/writeffmpeg.cc - BKE_writeffmpeg.hh - ) - list(APPEND INC - ../../../intern/ffmpeg - ) - list(APPEND INC_SYS - ${FFMPEG_INCLUDE_DIRS} - ) - list(APPEND LIB - ${FFMPEG_LIBRARIES} - ) add_definitions(-DWITH_FFMPEG) endif() diff --git a/source/blender/blenkernel/intern/blender.cc b/source/blender/blenkernel/intern/blender.cc index abac9b686ba..aac50be750a 100644 --- a/source/blender/blenkernel/intern/blender.cc +++ b/source/blender/blenkernel/intern/blender.cc @@ -18,6 +18,7 @@ #include "BLI_string.h" #include "BLI_utildefines.h" +#include "IMB_anim.hh" #include "IMB_imbuf.hh" #include "IMB_moviecache.hh" @@ -36,7 +37,6 @@ #include "BKE_report.hh" #include "BKE_screen.hh" #include "BKE_studiolight.h" -#include "BKE_writeffmpeg.hh" #include "DEG_depsgraph.hh" @@ -79,9 +79,7 @@ void BKE_blender_free() IMB_moviecache_destruct(); SEQ_fontmap_clear(); -#ifdef WITH_FFMPEG - BKE_ffmpeg_exit(); -#endif + IMB_ffmpeg_exit(); blender::bke::node_system_exit(); } diff --git a/source/blender/blenkernel/intern/image.cc b/source/blender/blenkernel/intern/image.cc index 8df27be323e..70e723a6e07 100644 --- a/source/blender/blenkernel/intern/image.cc +++ b/source/blender/blenkernel/intern/image.cc @@ -29,6 +29,7 @@ #include "MEM_guardedalloc.h" +#include "IMB_anim.hh" #include "IMB_colormanagement.hh" #include "IMB_imbuf.hh" #include "IMB_imbuf_types.hh" diff --git a/source/blender/blenkernel/intern/image_format.cc b/source/blender/blenkernel/intern/image_format.cc index 212d04503e9..fb94212a528 100644 --- a/source/blender/blenkernel/intern/image_format.cc +++ b/source/blender/blenkernel/intern/image_format.cc @@ -15,16 +15,13 @@ #include "BLI_string.h" #include "BLI_utildefines.h" +#include "IMB_anim.hh" #include "IMB_colormanagement.hh" #include "IMB_imbuf_types.hh" #include "BKE_colortools.hh" #include "BKE_image_format.hh" -#ifdef WITH_FFMPEG -# include "BKE_writeffmpeg.hh" -#endif - /* Init/Copy/Free */ void BKE_image_format_init(ImageFormatData *imf, const bool render) @@ -329,16 +326,14 @@ char BKE_imtype_valid_depths_with_video(char imtype, const ID *owner_id) UNUSED_VARS(owner_id); /* Might be unused depending on build options. */ int depths = BKE_imtype_valid_depths(imtype); -#ifdef WITH_FFMPEG /* Depending on video codec selected, valid color bit depths might vary. */ if (imtype == R_IMF_IMTYPE_FFMPEG) { const bool is_render_out = (owner_id && GS(owner_id->name) == ID_SCE); if (is_render_out) { const Scene *scene = (const Scene *)owner_id; - depths |= BKE_ffmpeg_valid_bit_depths(scene->r.ffcodecdata.codec); + depths |= IMB_ffmpeg_valid_bit_depths(scene->r.ffcodecdata.codec); } } -#endif return depths; } diff --git a/source/blender/blenkernel/intern/movieclip.cc b/source/blender/blenkernel/intern/movieclip.cc index b7a2a3b364f..585ea5ddce4 100644 --- a/source/blender/blenkernel/intern/movieclip.cc +++ b/source/blender/blenkernel/intern/movieclip.cc @@ -55,6 +55,7 @@ #include "BKE_node_tree_update.hh" #include "BKE_tracking.h" +#include "IMB_anim.hh" #include "IMB_colormanagement.hh" #include "IMB_imbuf.hh" #include "IMB_imbuf_types.hh" diff --git a/source/blender/blenkernel/intern/writemovie.cc b/source/blender/blenkernel/intern/writemovie.cc deleted file mode 100644 index 30b1fbe365e..00000000000 --- a/source/blender/blenkernel/intern/writemovie.cc +++ /dev/null @@ -1,105 +0,0 @@ -/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved. - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -/** \file - * Functions for writing movie files. - * \ingroup bke - */ - -#include - -#include "MEM_guardedalloc.h" - -#include "DNA_scene_types.h" - -#include "BLI_utildefines.h" - -#include "BKE_report.hh" - -#ifdef WITH_FFMPEG -# include "BKE_writeffmpeg.hh" -#endif - -#include "BKE_writemovie.hh" - -static bool start_stub(void * /*context_v*/, - const Scene * /*scene*/, - RenderData * /*rd*/, - int /*rectx*/, - int /*recty*/, - ReportList * /*reports*/, - bool /*preview*/, - const char * /*suffix*/) -{ - return false; -} - -static void end_stub(void * /*context_v*/) {} - -static bool append_stub(void * /*context_v*/, - RenderData * /*rd*/, - int /*start_frame*/, - int /*frame*/, - const ImBuf * /*image*/, - const char * /*suffix*/, - ReportList * /*reports*/) -{ - return false; -} - -static void *context_create_stub() -{ - return nullptr; -} - -static void context_free_stub(void * /*context_v*/) {} - -bMovieHandle *BKE_movie_handle_get(const char imtype) -{ - static bMovieHandle mh = {nullptr}; - /* Stub callbacks in case ffmpeg is not compiled in. */ - mh.start_movie = start_stub; - mh.append_movie = append_stub; - mh.end_movie = end_stub; - mh.get_movie_path = nullptr; - mh.context_create = context_create_stub; - mh.context_free = context_free_stub; - -#ifdef WITH_FFMPEG - if (ELEM(imtype, - R_IMF_IMTYPE_AVIRAW, - R_IMF_IMTYPE_AVIJPEG, - R_IMF_IMTYPE_FFMPEG, - R_IMF_IMTYPE_H264, - R_IMF_IMTYPE_XVID, - R_IMF_IMTYPE_THEORA, - R_IMF_IMTYPE_AV1)) - { - mh.start_movie = BKE_ffmpeg_start; - mh.append_movie = BKE_ffmpeg_append; - mh.end_movie = BKE_ffmpeg_end; - mh.get_movie_path = BKE_ffmpeg_filepath_get; - mh.context_create = BKE_ffmpeg_context_create; - mh.context_free = BKE_ffmpeg_context_free; - } -#else - (void)imtype; -#endif - - return (mh.append_movie != append_stub) ? &mh : nullptr; -} - -void BKE_movie_filepath_get(char filepath[/*FILE_MAX*/ 1024], - const RenderData *rd, - bool preview, - const char *suffix) -{ - bMovieHandle *mh = BKE_movie_handle_get(rd->im_format.imtype); - if (mh && mh->get_movie_path) { - mh->get_movie_path(filepath, rd, preview, suffix); - } - else { - filepath[0] = '\0'; - } -} diff --git a/source/blender/blenloader/CMakeLists.txt b/source/blender/blenloader/CMakeLists.txt index c7a71e612b6..3093b9fdfff 100644 --- a/source/blender/blenloader/CMakeLists.txt +++ b/source/blender/blenloader/CMakeLists.txt @@ -11,6 +11,7 @@ set(INC ../bmesh ../gpu ../imbuf + ../imbuf/movie ../makesrna ../nodes ../render diff --git a/source/blender/blenloader/intern/versioning_260.cc b/source/blender/blenloader/intern/versioning_260.cc index 7ebceb85222..6eb1f5cc0a9 100644 --- a/source/blender/blenloader/intern/versioning_260.cc +++ b/source/blender/blenloader/intern/versioning_260.cc @@ -67,11 +67,8 @@ #include "SEQ_modifier.hh" #include "SEQ_utils.hh" -#ifdef WITH_FFMPEG -# include "BKE_writeffmpeg.hh" -#endif - -#include "IMB_imbuf.hh" /* for proxy / time-code versioning stuff. */ +#include "IMB_imbuf_enums.h" +#include "IMB_movie_enums.hh" #include "NOD_common.h" #include "NOD_composite.hh" @@ -2754,12 +2751,10 @@ void blo_do_versions_260(FileData *fd, Library * /*lib*/, Main *bmain) scene->toolsettings->snap_node_mode = 8; /* SCE_SNAP_TO_GRID */ } -#ifdef WITH_FFMPEG /* Update for removed "sound-only" option in FFMPEG export settings. */ if (scene->r.ffcodecdata.type >= FFMPEG_INVALID) { scene->r.ffcodecdata.type = FFMPEG_AVI; } -#endif } } diff --git a/source/blender/blenloader/intern/versioning_400.cc b/source/blender/blenloader/intern/versioning_400.cc index 6cecf33fe01..2d143244ebe 100644 --- a/source/blender/blenloader/intern/versioning_400.cc +++ b/source/blender/blenloader/intern/versioning_400.cc @@ -76,7 +76,7 @@ #include "BKE_screen.hh" #include "BKE_tracking.h" -#include "IMB_imbuf_enums.h" +#include "IMB_movie_enums.hh" #include "SEQ_iterator.hh" #include "SEQ_retiming.hh" diff --git a/source/blender/editors/interface/CMakeLists.txt b/source/blender/editors/interface/CMakeLists.txt index 8d063fa8910..248504bfe97 100644 --- a/source/blender/editors/interface/CMakeLists.txt +++ b/source/blender/editors/interface/CMakeLists.txt @@ -14,6 +14,7 @@ set(INC ../../functions ../../gpu ../../imbuf + ../../imbuf/movie ../../makesrna ../../nodes ../../python diff --git a/source/blender/editors/interface/regions/interface_region_tooltip.cc b/source/blender/editors/interface/regions/interface_region_tooltip.cc index 781970481b5..43fee32055c 100644 --- a/source/blender/editors/interface/regions/interface_region_tooltip.cc +++ b/source/blender/editors/interface/regions/interface_region_tooltip.cc @@ -54,6 +54,7 @@ #include "GPU_immediate_util.hh" #include "GPU_state.hh" +#include "IMB_anim.hh" #include "IMB_imbuf.hh" #include "IMB_imbuf_types.hh" #include "IMB_thumbs.hh" diff --git a/source/blender/editors/render/CMakeLists.txt b/source/blender/editors/render/CMakeLists.txt index 7f37ae1e9a9..d421c9f5d9f 100644 --- a/source/blender/editors/render/CMakeLists.txt +++ b/source/blender/editors/render/CMakeLists.txt @@ -11,6 +11,7 @@ set(INC ../../draw ../../gpu ../../imbuf + ../../imbuf/movie ../../makesrna ../../nodes ../../render diff --git a/source/blender/editors/render/render_opengl.cc b/source/blender/editors/render/render_opengl.cc index b56bf2d2ebe..8fdea3d5461 100644 --- a/source/blender/editors/render/render_opengl.cc +++ b/source/blender/editors/render/render_opengl.cc @@ -19,6 +19,7 @@ #include "BLI_task.hh" #include "BLI_threads.h" #include "BLI_utildefines.h" +#include "BLI_vector.hh" #include "DNA_action_types.h" #include "DNA_anim_types.h" @@ -40,7 +41,6 @@ #include "BKE_main.hh" #include "BKE_report.hh" #include "BKE_scene.hh" -#include "BKE_writemovie.hh" #include "DEG_depsgraph.hh" #include "DEG_depsgraph_query.hh" @@ -57,6 +57,7 @@ #include "IMB_imbuf.hh" #include "IMB_imbuf_types.hh" +#include "IMB_movie_write.hh" #include "RE_pipeline.h" @@ -120,7 +121,6 @@ struct OGLRender { GPUViewport *viewport; ReportList *reports; - bMovieHandle *mh; int cfrao, nfra; int totvideos; @@ -138,7 +138,7 @@ struct OGLRender { /** Use to check if running modal or not (invoked or executed). */ wmTimer *timer; - void **movie_ctx_arr; + blender::Vector movie_writers; TaskPool *task_pool; bool pool_ok; @@ -847,8 +847,6 @@ static bool screen_opengl_render_init(bContext *C, wmOperator *op) oglrender->win = win; oglrender->totvideos = 0; - oglrender->mh = nullptr; - oglrender->movie_ctx_arr = nullptr; if (is_animation) { if (is_render_keyed_only) { @@ -881,7 +879,6 @@ static bool screen_opengl_render_init(bContext *C, wmOperator *op) static void screen_opengl_render_end(bContext *C, OGLRender *oglrender) { Scene *scene = oglrender->scene; - int i; if (oglrender->is_animation) { /* Trickery part for movie output: @@ -913,17 +910,13 @@ static void screen_opengl_render_end(bContext *C, OGLRender *oglrender) MEM_SAFE_FREE(oglrender->render_frames); - if (oglrender->mh) { + if (!oglrender->movie_writers.is_empty()) { if (BKE_imtype_is_movie(scene->r.im_format.imtype)) { - for (i = 0; i < oglrender->totvideos; i++) { - oglrender->mh->end_movie(oglrender->movie_ctx_arr[i]); - oglrender->mh->context_free(oglrender->movie_ctx_arr[i]); + for (ImbMovieWriter *writer : oglrender->movie_writers) { + IMB_movie_write_end(writer); } } - - if (oglrender->movie_ctx_arr) { - MEM_freeN(oglrender->movie_ctx_arr); - } + oglrender->movie_writers.clear_and_shrink(); } if (oglrender->timer) { /* exec will not have a timer */ @@ -982,34 +975,25 @@ static bool screen_opengl_render_anim_init(bContext *C, wmOperator *op) BKE_scene_multiview_videos_dimensions_get( &scene->r, oglrender->sizex, oglrender->sizey, &width, &height); - oglrender->mh = BKE_movie_handle_get(scene->r.im_format.imtype); - - if (oglrender->mh == nullptr) { - BKE_report(oglrender->reports, RPT_ERROR, "Movie format unsupported"); - screen_opengl_render_end(C, oglrender); - return false; - } - - oglrender->movie_ctx_arr = static_cast( - MEM_mallocN(sizeof(void *) * oglrender->totvideos, "Movies")); + oglrender->movie_writers.reserve(oglrender->totvideos); for (i = 0; i < oglrender->totvideos; i++) { Scene *scene_eval = DEG_get_evaluated_scene(oglrender->depsgraph); const char *suffix = BKE_scene_multiview_view_id_suffix_get(&scene->r, i); - - oglrender->movie_ctx_arr[i] = oglrender->mh->context_create(); - if (!oglrender->mh->start_movie(oglrender->movie_ctx_arr[i], - scene_eval, - &scene->r, - oglrender->sizex, - oglrender->sizey, - oglrender->reports, - PRVRANGEON != 0, - suffix)) - { + ImbMovieWriter *writer = IMB_movie_write_begin(scene->r.im_format.imtype, + scene_eval, + &scene->r, + oglrender->sizex, + oglrender->sizey, + oglrender->reports, + PRVRANGEON != 0, + suffix); + if (writer == nullptr) { + BKE_report(oglrender->reports, RPT_ERROR, "Movie format unsupported"); screen_opengl_render_end(C, oglrender); return false; } + oglrender->movie_writers.append(writer); } } @@ -1059,8 +1043,7 @@ static void write_result(TaskPool *__restrict pool, WriteTaskData *task_data) rr, scene, &scene->r, - oglrender->mh, - oglrender->movie_ctx_arr, + oglrender->movie_writers.data(), oglrender->totvideos, PRVRANGEON != 0); } diff --git a/source/blender/editors/space_clip/CMakeLists.txt b/source/blender/editors/space_clip/CMakeLists.txt index 128f5794a14..4f496eb4523 100644 --- a/source/blender/editors/space_clip/CMakeLists.txt +++ b/source/blender/editors/space_clip/CMakeLists.txt @@ -9,6 +9,7 @@ set(INC ../../blentranslation ../../gpu ../../imbuf + ../../imbuf/movie ../../makesrna ../../windowmanager diff --git a/source/blender/editors/space_clip/clip_buttons.cc b/source/blender/editors/space_clip/clip_buttons.cc index e0ed2909268..5bdb72048c1 100644 --- a/source/blender/editors/space_clip/clip_buttons.cc +++ b/source/blender/editors/space_clip/clip_buttons.cc @@ -41,6 +41,7 @@ #include "WM_api.hh" #include "WM_types.hh" +#include "IMB_anim.hh" #include "IMB_imbuf.hh" #include "IMB_imbuf_types.hh" diff --git a/source/blender/editors/space_clip/clip_ops.cc b/source/blender/editors/space_clip/clip_ops.cc index b3d7c48695c..49a192afa6d 100644 --- a/source/blender/editors/space_clip/clip_ops.cc +++ b/source/blender/editors/space_clip/clip_ops.cc @@ -44,6 +44,7 @@ #include "WM_api.hh" #include "WM_types.hh" +#include "IMB_anim.hh" #include "IMB_imbuf.hh" #include "IMB_imbuf_types.hh" diff --git a/source/blender/editors/space_file/CMakeLists.txt b/source/blender/editors/space_file/CMakeLists.txt index c645f5f6abc..5a2c2401970 100644 --- a/source/blender/editors/space_file/CMakeLists.txt +++ b/source/blender/editors/space_file/CMakeLists.txt @@ -11,6 +11,7 @@ set(INC ../../blentranslation ../../gpu ../../imbuf + ../../imbuf/movie ../../makesrna ../../render ../../windowmanager diff --git a/source/blender/editors/space_file/filelist.cc b/source/blender/editors/space_file/filelist.cc index 597a48334e9..89bfbca7584 100644 --- a/source/blender/editors/space_file/filelist.cc +++ b/source/blender/editors/space_file/filelist.cc @@ -64,6 +64,7 @@ #include "ED_datafiles.h" #include "ED_fileselect.hh" +#include "IMB_anim.hh" #include "IMB_imbuf.hh" #include "IMB_imbuf_types.hh" #include "IMB_thumbs.hh" diff --git a/source/blender/editors/space_image/CMakeLists.txt b/source/blender/editors/space_image/CMakeLists.txt index e6908db4d5d..d6e1ba42e1a 100644 --- a/source/blender/editors/space_image/CMakeLists.txt +++ b/source/blender/editors/space_image/CMakeLists.txt @@ -12,6 +12,7 @@ set(INC ../../draw ../../gpu ../../imbuf + ../../imbuf/movie ../../makesrna ../../render ../../windowmanager diff --git a/source/blender/editors/space_image/image_buttons.cc b/source/blender/editors/space_image/image_buttons.cc index 93bfa0f14b3..95d68d8773e 100644 --- a/source/blender/editors/space_image/image_buttons.cc +++ b/source/blender/editors/space_image/image_buttons.cc @@ -27,6 +27,7 @@ #include "RE_pipeline.h" +#include "IMB_anim.hh" #include "IMB_colormanagement.hh" #include "IMB_imbuf.hh" #include "IMB_imbuf_types.hh" diff --git a/source/blender/editors/space_image/image_ops.cc b/source/blender/editors/space_image/image_ops.cc index 21a7af853fd..6b5a06e047e 100644 --- a/source/blender/editors/space_image/image_ops.cc +++ b/source/blender/editors/space_image/image_ops.cc @@ -49,6 +49,7 @@ #include "DEG_depsgraph.hh" +#include "IMB_anim.hh" #include "IMB_colormanagement.hh" #include "IMB_imbuf.hh" #include "IMB_imbuf_types.hh" diff --git a/source/blender/editors/space_sequencer/CMakeLists.txt b/source/blender/editors/space_sequencer/CMakeLists.txt index 42924fb4049..d2cd36a371b 100644 --- a/source/blender/editors/space_sequencer/CMakeLists.txt +++ b/source/blender/editors/space_sequencer/CMakeLists.txt @@ -10,6 +10,7 @@ set(INC ../../draw ../../gpu ../../imbuf + ../../imbuf/movie ../../makesrna ../../sequencer ../../windowmanager diff --git a/source/blender/editors/space_sequencer/sequencer_drag_drop.cc b/source/blender/editors/space_sequencer/sequencer_drag_drop.cc index ee30b5ba511..833a9bdcb5c 100644 --- a/source/blender/editors/space_sequencer/sequencer_drag_drop.cc +++ b/source/blender/editors/space_sequencer/sequencer_drag_drop.cc @@ -33,6 +33,7 @@ #include "ED_screen.hh" #include "ED_transform.hh" +#include "IMB_anim.hh" #include "IMB_imbuf.hh" #include "IMB_imbuf_types.hh" diff --git a/source/blender/imbuf/CMakeLists.txt b/source/blender/imbuf/CMakeLists.txt index ad5676cc40b..85d029a6f63 100644 --- a/source/blender/imbuf/CMakeLists.txt +++ b/source/blender/imbuf/CMakeLists.txt @@ -2,8 +2,11 @@ # # SPDX-License-Identifier: GPL-2.0-or-later +add_subdirectory(movie) + set(INC . + ./movie ../blenkernel ../blenloader ../gpu @@ -20,7 +23,6 @@ set(INC_SYS set(SRC intern/allocimbuf.cc - intern/anim_movie.cc intern/colormanagement.cc intern/colormanagement_inline.h intern/divers.cc @@ -35,7 +37,6 @@ set(SRC intern/format_targa.cc intern/format_tiff.cc intern/imageprocess.cc - intern/indexer.cc intern/interp.cc intern/iris.cc intern/jpeg.cc @@ -65,15 +66,10 @@ set(SRC IMB_openexr.hh IMB_thumbs.hh intern/IMB_allocimbuf.hh - intern/IMB_anim.hh intern/IMB_colormanagement_intern.hh intern/IMB_filetype.hh intern/IMB_filter.hh - intern/IMB_indexer.hh intern/imbuf.hh - - # orphan include - ../../../intern/ffmpeg/ffmpeg_compat.h ) set(LIB @@ -123,20 +119,6 @@ if(WITH_IMAGE_OPENJPEG) add_definitions(-DWITH_OPENJPEG ${OPENJPEG_DEFINES}) endif() -if(WITH_CODEC_FFMPEG) - list(APPEND INC - ../../../intern/ffmpeg - ) - list(APPEND INC_SYS - ${FFMPEG_INCLUDE_DIRS} - ) - list(APPEND LIB - ${FFMPEG_LIBRARIES} - ${OPENJPEG_LIBRARIES} - ) - add_definitions(-DWITH_FFMPEG) -endif() - if(WITH_IMAGE_CINEON) list(APPEND SRC intern/format_dpx.cc diff --git a/source/blender/imbuf/IMB_imbuf.hh b/source/blender/imbuf/IMB_imbuf.hh index ac6ea9546cc..4e1e3f31468 100644 --- a/source/blender/imbuf/IMB_imbuf.hh +++ b/source/blender/imbuf/IMB_imbuf.hh @@ -52,8 +52,6 @@ struct ImBuf; struct rctf; struct rcti; -struct ImBufAnim; - struct ColorManagedDisplay; struct GSet; @@ -293,94 +291,6 @@ enum eIMBInterpolationFilterMode { IMB_FILTER_BOX, }; -/** - * Defaults to BL_proxy within the directory of the animation. - */ -void IMB_anim_set_index_dir(ImBufAnim *anim, const char *dir); -void IMB_anim_get_filename(ImBufAnim *anim, char *filename, int filename_maxncpy); - -int IMB_anim_index_get_frame_index(ImBufAnim *anim, IMB_Timecode_Type tc, int position); - -int IMB_anim_proxy_get_existing(ImBufAnim *anim); - -struct IndexBuildContext; - -/** - * Prepare context for proxies/time-codes builder - */ -IndexBuildContext *IMB_anim_index_rebuild_context(ImBufAnim *anim, - IMB_Timecode_Type tcs_in_use, - int proxy_sizes_in_use, - int quality, - const bool overwrite, - GSet *file_list, - bool build_only_on_bad_performance); - -/** - * Will rebuild all used indices and proxies at once. - */ -void IMB_anim_index_rebuild(IndexBuildContext *context, - bool *stop, - bool *do_update, - float *progress); - -/** - * Finish rebuilding proxies/time-codes and free temporary contexts used. - */ -void IMB_anim_index_rebuild_finish(IndexBuildContext *context, bool stop); - -/** - * Return the length (in frames) of the given \a anim. - */ -int IMB_anim_get_duration(ImBufAnim *anim, IMB_Timecode_Type tc); - -/** - * Return the encoded start offset (in seconds) of the given \a anim. - */ -double IMD_anim_get_offset(ImBufAnim *anim); - -/** - * Return the fps contained in movie files (function rval is false, - * and frs_sec and frs_sec_base untouched if none available!) - */ -bool IMB_anim_get_fps(const ImBufAnim *anim, - bool no_av_base, - short *r_frs_sec, - float *r_frs_sec_base); - -ImBufAnim *IMB_open_anim(const char *filepath, - int ib_flags, - int streamindex, - char colorspace[IM_MAX_SPACE]); -void IMB_suffix_anim(ImBufAnim *anim, const char *suffix); -void IMB_close_anim(ImBufAnim *anim); -void IMB_close_anim_proxies(ImBufAnim *anim); -bool IMB_anim_can_produce_frames(const ImBufAnim *anim); - -int IMB_anim_get_image_width(ImBufAnim *anim); -int IMB_anim_get_image_height(ImBufAnim *anim); -bool IMB_get_gop_decode_time(ImBufAnim *anim); - -/** - * Fetches a frame from a movie at given frame position. - * - * 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. - */ -ImBuf *IMB_anim_absolute(ImBufAnim *anim, - int position, - IMB_Timecode_Type tc /* = 1 = IMB_TC_RECORD_RUN */, - IMB_Proxy_Size preview_size /* = 0 = IMB_PROXY_NONE */); - -/** - * fetches a define preview-frame, usually half way into the movie. - */ -ImBuf *IMB_anim_previewframe(ImBufAnim *anim); - -void IMB_free_anim(ImBufAnim *anim); - #define FILTER_MASK_NULL 0 #define FILTER_MASK_MARGIN 1 #define FILTER_MASK_USED 2 @@ -450,12 +360,6 @@ bool IMB_ispic_type_matches(const char *filepath, int filetype); int IMB_ispic_type_from_memory(const unsigned char *buf, size_t buf_size); int IMB_ispic_type(const char *filepath); -/** - * Test if the file is a video file (known format, has a video stream and - * supported video codec). - */ -bool IMB_isanim(const char *filepath); - /** * Test if color-space conversions of pixels in buffer need to take into account alpha. */ @@ -726,11 +630,6 @@ void IMB_transform(const ImBuf *src, const float transform_matrix[4][4], const rctf *src_crop); -/* FFMPEG */ - -void IMB_ffmpeg_init(); -const char *IMB_ffmpeg_last_error(); - GPUTexture *IMB_create_gpu_texture(const char *name, ImBuf *ibuf, bool use_high_bitdepth, diff --git a/source/blender/imbuf/IMB_imbuf_enums.h b/source/blender/imbuf/IMB_imbuf_enums.h index a7a2a997cd8..2c55a9acb48 100644 --- a/source/blender/imbuf/IMB_imbuf_enums.h +++ b/source/blender/imbuf/IMB_imbuf_enums.h @@ -45,33 +45,6 @@ enum eImbFileType { #endif }; -/** - * Time-code files contain timestamps (PTS, DTS) and packet seek position. - * These values are obtained by decoding each frame in movie stream. Time-code types define how - * these map to frame index in Blender. This is used when seeking in movie stream. Note, that - * meaning of terms time-code and record run here has little connection to their actual meaning. - */ -typedef enum IMB_Timecode_Type { - /** Don't use time-code files at all. Use FFmpeg API to seek to PTS calculated on the fly. */ - IMB_TC_NONE = 0, - /** - * TC entries (and therefore frames in movie stream) are mapped to frame index, such that - * timestamp in Blender matches timestamp in the movie stream. This assumes, that time starts at - * 0 in both cases. - * - * Simplified formula is `frame_index = movie_stream_timestamp * FPS`. - */ - IMB_TC_RECORD_RUN = 1, - /** - * Each TC entry (and therefore frame in movie stream) is mapped to new frame index in Blender. - * - * For example: FFmpeg may say, that a frame should be displayed for 0.5 seconds, but this option - * ignores that and only displays it in one particular frame index in Blender. - */ - IMB_TC_RECORD_RUN_NO_GAPS = 8, - IMB_TC_NUM_TYPES = 2, -} IMB_Timecode_Type; - typedef enum IMB_Proxy_Size { IMB_PROXY_NONE = 0, IMB_PROXY_25 = 1, diff --git a/source/blender/imbuf/intern/thumbs.cc b/source/blender/imbuf/intern/thumbs.cc index 1bd3737bd96..7d0a652b099 100644 --- a/source/blender/imbuf/intern/thumbs.cc +++ b/source/blender/imbuf/intern/thumbs.cc @@ -27,6 +27,7 @@ #include "DNA_space_types.h" /* For FILE_MAX_LIBEXTRA */ +#include "IMB_anim.hh" #include "IMB_imbuf.hh" #include "IMB_imbuf_types.hh" #include "IMB_metadata.hh" diff --git a/source/blender/imbuf/intern/util.cc b/source/blender/imbuf/intern/util.cc index 48a1f1e3971..f1c4b94d90f 100644 --- a/source/blender/imbuf/intern/util.cc +++ b/source/blender/imbuf/intern/util.cc @@ -24,24 +24,6 @@ #include "IMB_imbuf_types.hh" #include "imbuf.hh" -#include "IMB_anim.hh" - -#ifdef WITH_FFMPEG -# include "BLI_string.h" /* BLI_vsnprintf */ - -# include "BKE_global.hh" /* G.debug */ - -extern "C" { -# include -# include -# include -# include - -# include "ffmpeg_compat.h" /* Keep for compatibility. */ -} - -#endif - #define UTIL_DEBUG 0 const char *imb_ext_image[] = { @@ -170,148 +152,3 @@ bool IMB_ispic(const char *filepath) { return (IMB_ispic_type(filepath) != IMB_FTYPE_NONE); } - -#ifdef WITH_FFMPEG - -/* BLI_vsnprintf in ffmpeg_log_callback() causes invalid warning */ -# ifdef __GNUC__ -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wmissing-format-attribute" -# endif - -static char ffmpeg_last_error[1024]; - -static void ffmpeg_log_callback(void *ptr, int level, const char *format, va_list arg) -{ - if (ELEM(level, AV_LOG_FATAL, AV_LOG_ERROR)) { - size_t n; - va_list args_cpy; - - va_copy(args_cpy, arg); - n = VSNPRINTF(ffmpeg_last_error, format, args_cpy); - va_end(args_cpy); - - /* strip trailing \n */ - ffmpeg_last_error[n - 1] = '\0'; - } - - if (G.debug & G_DEBUG_FFMPEG) { - /* call default logger to print all message to console */ - av_log_default_callback(ptr, level, format, arg); - } -} - -# ifdef __GNUC__ -# pragma GCC diagnostic pop -# endif - -void IMB_ffmpeg_init() -{ - avdevice_register_all(); - - ffmpeg_last_error[0] = '\0'; - - if (G.debug & G_DEBUG_FFMPEG) { - av_log_set_level(AV_LOG_DEBUG); - } - - /* set separate callback which could store last error to report to UI */ - av_log_set_callback(ffmpeg_log_callback); -} - -const char *IMB_ffmpeg_last_error() -{ - return ffmpeg_last_error; -} - -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) { - if (UTIL_DEBUG) { - fprintf(stderr, "isffmpeg: av_open_input_file failed\n"); - } - return 0; - } - - if (avformat_find_stream_info(pFormatCtx, nullptr) < 0) { - if (UTIL_DEBUG) { - fprintf(stderr, "isffmpeg: avformat_find_stream_info failed\n"); - } - avformat_close_input(&pFormatCtx); - return 0; - } - - if (UTIL_DEBUG) { - av_dump_format(pFormatCtx, 0, filepath, 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; -} -#endif - -bool IMB_isanim(const char *filepath) -{ - BLI_assert(!BLI_path_is_rel(filepath)); - - if (UTIL_DEBUG) { - printf("%s: %s\n", __func__, filepath); - } - -#ifdef WITH_FFMPEG - if (isffmpeg(filepath)) { - return true; - } -#endif - - return false; -} diff --git a/source/blender/imbuf/movie/CMakeLists.txt b/source/blender/imbuf/movie/CMakeLists.txt new file mode 100644 index 00000000000..873fd53f4cc --- /dev/null +++ b/source/blender/imbuf/movie/CMakeLists.txt @@ -0,0 +1,85 @@ +# SPDX-FileCopyrightText: 2024 Blender Authors +# +# SPDX-License-Identifier: GPL-2.0-or-later + +set(INC + PUBLIC . + .. + ../../blenkernel +) + +set(INC_SYS +) + +set(SRC + IMB_anim.hh + IMB_movie_enums.hh + IMB_movie_write.hh + + intern/ffmpeg_compat.h + intern/ffmpeg_util.cc + intern/ffmpeg_util.hh + intern/movie_proxy_indexer.cc + intern/movie_proxy_indexer.hh + intern/movie_read.cc + intern/movie_read.hh + intern/movie_write.cc +) + +set(LIB + PRIVATE bf_blenkernel + PRIVATE bf::blenlib + PRIVATE bf_imbuf + PRIVATE bf::intern::guardedalloc +) + +if(WITH_CODEC_FFMPEG) + list(APPEND SRC + intern/ffmpeg_swscale.cc + intern/ffmpeg_swscale.hh + ) + list(APPEND INC_SYS + ${FFMPEG_INCLUDE_DIRS} + ) + list(APPEND LIB + ${FFMPEG_LIBRARIES} + ${OPENJPEG_LIBRARIES} + ) + add_definitions(-DWITH_FFMPEG) +endif() + +if(WITH_AUDASPACE) + list(APPEND INC_SYS + ${AUDASPACE_C_INCLUDE_DIRS} + ) + if(WITH_SYSTEM_AUDASPACE) + list(APPEND LIB + ${AUDASPACE_C_LIBRARIES} + ) + endif() + add_definitions(-DWITH_AUDASPACE) +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 + ${FFMPEG_INCLUDE_DIRS} + ) + set(TEST_LIB + ${FFMPEG_LIBRARIES} + ) + if(WITH_IMAGE_OPENJPEG) + set(TEST_LIB ${TEST_LIB} ${OPENJPEG_LIBRARIES}) + endif() + blender_add_test_suite_lib(ffmpeg_libs "${TEST_SRC}" "${TEST_INC}" "${TEST_INC_SYS}" "${TEST_LIB}") + +endif() diff --git a/source/blender/imbuf/movie/IMB_anim.hh b/source/blender/imbuf/movie/IMB_anim.hh new file mode 100644 index 00000000000..fc9abab78ad --- /dev/null +++ b/source/blender/imbuf/movie/IMB_anim.hh @@ -0,0 +1,121 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup imbuf + */ + +#pragma once + +#include "IMB_imbuf_enums.h" +#include "IMB_movie_enums.hh" + +struct ImageFormatData; +struct ImBuf; +struct ImBufAnim; +struct IndexBuildContext; +struct GSet; +struct RenderData; + +/** + * Defaults to BL_proxy within the directory of the animation. + */ +void IMB_anim_set_index_dir(ImBufAnim *anim, const char *dir); +void IMB_anim_get_filename(ImBufAnim *anim, char *filename, int filename_maxncpy); + +int IMB_anim_index_get_frame_index(ImBufAnim *anim, IMB_Timecode_Type tc, int position); + +int IMB_anim_proxy_get_existing(ImBufAnim *anim); + +/** + * Prepare context for proxies/time-codes builder + */ +IndexBuildContext *IMB_anim_index_rebuild_context(ImBufAnim *anim, + IMB_Timecode_Type tcs_in_use, + int proxy_sizes_in_use, + int quality, + const bool overwrite, + GSet *file_list, + bool build_only_on_bad_performance); + +/** + * Will rebuild all used indices and proxies at once. + */ +void IMB_anim_index_rebuild(IndexBuildContext *context, + bool *stop, + bool *do_update, + float *progress); + +/** + * Finish rebuilding proxies/time-codes and free temporary contexts used. + */ +void IMB_anim_index_rebuild_finish(IndexBuildContext *context, bool stop); + +/** + * Return the length (in frames) of the given \a anim. + */ +int IMB_anim_get_duration(ImBufAnim *anim, IMB_Timecode_Type tc); + +/** + * Return the encoded start offset (in seconds) of the given \a anim. + */ +double IMB_anim_get_offset(ImBufAnim *anim); + +/** + * Return the fps contained in movie files (function rval is false, + * and frs_sec and frs_sec_base untouched if none available!) + */ +bool IMB_anim_get_fps(const ImBufAnim *anim, + bool no_av_base, + short *r_frs_sec, + float *r_frs_sec_base); + +ImBufAnim *IMB_open_anim(const char *filepath, int ib_flags, int streamindex, char *colorspace); +void IMB_suffix_anim(ImBufAnim *anim, const char *suffix); +void IMB_close_anim(ImBufAnim *anim); +void IMB_close_anim_proxies(ImBufAnim *anim); +bool IMB_anim_can_produce_frames(const ImBufAnim *anim); + +int IMB_anim_get_image_width(ImBufAnim *anim); +int IMB_anim_get_image_height(ImBufAnim *anim); +bool IMB_get_gop_decode_time(ImBufAnim *anim); + +/** + * Fetches a frame from a movie at given frame position. + * + * 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. + */ +ImBuf *IMB_anim_absolute(ImBufAnim *anim, + int position, + IMB_Timecode_Type tc /* = 1 = IMB_TC_RECORD_RUN */, + IMB_Proxy_Size preview_size /* = 0 = IMB_PROXY_NONE */); + +/** + * fetches a define preview-frame, usually half way into the movie. + */ +ImBuf *IMB_anim_previewframe(ImBufAnim *anim); + +void IMB_free_anim(ImBufAnim *anim); + +/** + * Test if the file is a video file (known format, has a video stream and + * supported video codec). + */ +bool IMB_isanim(const char *filepath); + +void IMB_ffmpeg_init(); +void IMB_ffmpeg_exit(); + +bool IMB_ffmpeg_alpha_channel_is_supported(int av_codec_id); +bool IMB_ffmpeg_codec_supports_crf(int av_codec_id); +void IMB_ffmpeg_image_type_verify(RenderData *rd, const ImageFormatData *imf); + +/** + * Which pixel bit depths are supported by a given video codec. + * Returns bitmask of `R_IMF_CHAN_DEPTH_` flags. + */ +int IMB_ffmpeg_valid_bit_depths(int av_codec_id); diff --git a/source/blender/imbuf/movie/IMB_movie_enums.hh b/source/blender/imbuf/movie/IMB_movie_enums.hh new file mode 100644 index 00000000000..341e2acede7 --- /dev/null +++ b/source/blender/imbuf/movie/IMB_movie_enums.hh @@ -0,0 +1,89 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +/** \file + * \ingroup imbuf + */ + +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, +}; + +/* Note: match ffmpeg AVCodecID enum values. */ +enum IMB_Ffmpeg_Codec_ID { + FFMPEG_CODEC_ID_NONE = 0, + FFMPEG_CODEC_ID_MPEG1VIDEO = 1, + FFMPEG_CODEC_ID_MPEG2VIDEO = 2, + FFMPEG_CODEC_ID_MPEG4 = 12, + FFMPEG_CODEC_ID_FLV1 = 21, + FFMPEG_CODEC_ID_DVVIDEO = 24, + FFMPEG_CODEC_ID_HUFFYUV = 25, + FFMPEG_CODEC_ID_H264 = 27, + FFMPEG_CODEC_ID_THEORA = 30, + FFMPEG_CODEC_ID_FFV1 = 33, + FFMPEG_CODEC_ID_QTRLE = 55, + FFMPEG_CODEC_ID_PNG = 61, + FFMPEG_CODEC_ID_DNXHD = 99, + FFMPEG_CODEC_ID_VP9 = 167, + FFMPEG_CODEC_ID_H265 = 173, + FFMPEG_CODEC_ID_AV1 = 226, + FFMPEG_CODEC_ID_PCM_S16LE = 65536, + FFMPEG_CODEC_ID_MP2 = 86016, + FFMPEG_CODEC_ID_MP3 = 86017, + FFMPEG_CODEC_ID_AAC = 86018, + FFMPEG_CODEC_ID_AC3 = 86019, + FFMPEG_CODEC_ID_VORBIS = 86021, + FFMPEG_CODEC_ID_FLAC = 86028, + FFMPEG_CODEC_ID_OPUS = 86076, +}; + +/** + * Time-code files contain timestamps (PTS, DTS) and packet seek position. + * These values are obtained by decoding each frame in movie stream. Time-code types define how + * these map to frame index in Blender. This is used when seeking in movie stream. Note, that + * meaning of terms time-code and record run here has little connection to their actual meaning. + */ +enum IMB_Timecode_Type { + /** Don't use time-code files at all. Use FFmpeg API to seek to PTS calculated on the fly. */ + IMB_TC_NONE = 0, + /** + * TC entries (and therefore frames in movie stream) are mapped to frame index, such that + * timestamp in Blender matches timestamp in the movie stream. This assumes, that time starts at + * 0 in both cases. + * + * Simplified formula is `frame_index = movie_stream_timestamp * FPS`. + */ + IMB_TC_RECORD_RUN = 1, + /** + * Each TC entry (and therefore frame in movie stream) is mapped to new frame index in Blender. + * + * For example: FFmpeg may say, that a frame should be displayed for 0.5 seconds, but this option + * ignores that and only displays it in one particular frame index in Blender. + */ + IMB_TC_RECORD_RUN_NO_GAPS = 8, + IMB_TC_NUM_TYPES = 2, +}; diff --git a/source/blender/imbuf/movie/IMB_movie_write.hh b/source/blender/imbuf/movie/IMB_movie_write.hh new file mode 100644 index 00000000000..cd5d65264fc --- /dev/null +++ b/source/blender/imbuf/movie/IMB_movie_write.hh @@ -0,0 +1,41 @@ +/* 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 + */ + +struct ImBuf; +struct ImbMovieWriter; +struct RenderData; +struct ReportList; +struct Scene; + +ImbMovieWriter *IMB_movie_write_begin(const char imtype, + const Scene *scene, + RenderData *rd, + int rectx, + int recty, + ReportList *reports, + bool preview, + const char *suffix); +bool IMB_movie_write_append(ImbMovieWriter *writer, + RenderData *rd, + int start_frame, + int frame, + const ImBuf *image, + const char *suffix, + ReportList *reports); +void IMB_movie_write_end(ImbMovieWriter *writer); + +/** + * \note Similar to #BKE_image_path_from_imformat() + */ +void IMB_movie_filepath_get(char filepath[/*FILE_MAX*/ 1024], + const RenderData *rd, + bool preview, + const char *suffix); diff --git a/intern/ffmpeg/ffmpeg_compat.h b/source/blender/imbuf/movie/intern/ffmpeg_compat.h similarity index 51% rename from intern/ffmpeg/ffmpeg_compat.h rename to source/blender/imbuf/movie/intern/ffmpeg_compat.h index 4d5b30eef37..5374f9e12f5 100644 --- a/intern/ffmpeg/ffmpeg_compat.h +++ b/source/blender/imbuf/movie/intern/ffmpeg_compat.h @@ -207,209 +207,4 @@ FFMPEG_INLINE int ffmpeg_get_video_rotation(const AVStream *stream) return 0; } -/* -------------------------------------------------------------------- */ -/** \name Deinterlace code block - * - * NOTE: The code in this block are from FFmpeg 2.6.4, which is licensed by 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++; - } -} - -/* deinterlacing : 2 temporal taps, 3 spatial taps linear filter. The - * top field is copied as is, but the bottom field is deinterlaced - * 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; -} - -FFMPEG_INLINE -int av_image_deinterlace( - AVFrame *dst, const AVFrame *src, enum AVPixelFormat pix_fmt, int width, int height) -{ - int i, ret; - - if (pix_fmt != AV_PIX_FMT_YUV420P && pix_fmt != AV_PIX_FMT_YUVJ420P && - pix_fmt != AV_PIX_FMT_YUV422P && pix_fmt != AV_PIX_FMT_YUVJ422P && - pix_fmt != AV_PIX_FMT_YUV444P && pix_fmt != AV_PIX_FMT_YUV411P && - pix_fmt != 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; -} - -/** \} Deinterlace code block */ - #endif diff --git a/source/blender/imbuf/movie/intern/ffmpeg_swscale.cc b/source/blender/imbuf/movie/intern/ffmpeg_swscale.cc new file mode 100644 index 00000000000..9e17fa14532 --- /dev/null +++ b/source/blender/imbuf/movie/intern/ffmpeg_swscale.cc @@ -0,0 +1,224 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup imbuf + */ + +#ifdef WITH_FFMPEG +# include "ffmpeg_swscale.hh" + +# include +# include + +# include "BLI_threads.h" +# include "BLI_vector.hh" + +extern "C" { +# include +# include +# include + +# include "ffmpeg_compat.h" +} + +/* 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; + int flags = 0; + + SwsContext *context = nullptr; + int64_t last_use_timestamp = 0; + bool is_used = false; +}; + +static std::mutex swscale_cache_lock; +static int64_t swscale_cache_timestamp = 0; +static blender::Vector *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", BLI_system_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 blender::Vector(); + 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, + int dst_width, + int dst_height, + int av_dst_format, + 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.dst_width == dst_width && c.dst_height == dst_height && + c.dst_format == av_dst_format && 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); + 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.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 */ diff --git a/source/blender/imbuf/movie/intern/ffmpeg_swscale.hh b/source/blender/imbuf/movie/intern/ffmpeg_swscale.hh new file mode 100644 index 00000000000..e7e5ddb8a22 --- /dev/null +++ b/source/blender/imbuf/movie/intern/ffmpeg_swscale.hh @@ -0,0 +1,35 @@ +/* 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; + +/** + * 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. + */ +SwsContext *ffmpeg_sws_get_context(int src_width, + int src_height, + int av_src_format, + int dst_width, + int dst_height, + int av_dst_format, + 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(); + +#endif /* WITH_FFMPEG */ diff --git a/source/blender/imbuf/movie/intern/ffmpeg_util.cc b/source/blender/imbuf/movie/intern/ffmpeg_util.cc new file mode 100644 index 00000000000..c4ca0aa6ac4 --- /dev/null +++ b/source/blender/imbuf/movie/intern/ffmpeg_util.cc @@ -0,0 +1,522 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup imbuf + */ + +#include "BLI_path_utils.hh" +#include "BLI_utildefines.h" + +#include "DNA_scene_types.h" + +#include "IMB_anim.hh" + +#include "ffmpeg_swscale.hh" +#include "ffmpeg_util.hh" + +#ifdef WITH_FFMPEG + +# include "BLI_string.h" + +# include "BKE_global.hh" + +extern "C" { +# include "ffmpeg_compat.h" +# include +# include +# include +# include +} + +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 void ffmpeg_log_callback(void *ptr, int level, const char *format, va_list arg) +{ + if (ELEM(level, AV_LOG_FATAL, AV_LOG_ERROR)) { + size_t n; + va_list args_cpy; + + va_copy(args_cpy, arg); + n = VSNPRINTF(ffmpeg_last_error_buffer, format, args_cpy); + va_end(args_cpy); + + /* strip trailing \n */ + ffmpeg_last_error_buffer[n - 1] = '\0'; + } + + if (G.debug & G_DEBUG_FFMPEG) { + /* call default logger to print all message to console */ + av_log_default_callback(ptr, level, format, arg); + } +} + +# 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 deinterlacing. 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++; + } +} + +/* deinterlacing : 2 temporal taps, 3 spatial taps linear filter. The + * top field is copied as is, but the bottom field is deinterlaced + * 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 (pix_fmt != AV_PIX_FMT_YUV420P && pix_fmt != AV_PIX_FMT_YUVJ420P && + pix_fmt != AV_PIX_FMT_YUV422P && pix_fmt != AV_PIX_FMT_YUVJ422P && + pix_fmt != AV_PIX_FMT_YUV444P && pix_fmt != AV_PIX_FMT_YUV411P && + pix_fmt != 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; +} + +#endif /* WITH_FFMPEG */ + +bool IMB_isanim(const char *filepath) +{ + BLI_assert(!BLI_path_is_rel(filepath)); + +#ifdef WITH_FFMPEG + if (isffmpeg(filepath)) { + return true; + } +#endif + + return false; +} + +void IMB_ffmpeg_init() +{ +#ifdef WITH_FFMPEG + avdevice_register_all(); + + ffmpeg_last_error_buffer[0] = '\0'; + + if (G.debug & G_DEBUG_FFMPEG) { + av_log_set_level(AV_LOG_DEBUG); + } + + /* set separate callback which could store last error to report to UI */ + av_log_set_callback(ffmpeg_log_callback); +#endif +} + +void IMB_ffmpeg_exit() +{ +#ifdef WITH_FFMPEG + ffmpeg_sws_exit(); +#endif +} + +int IMB_ffmpeg_valid_bit_depths(int av_codec_id) +{ + int bit_depths = R_IMF_CHAN_DEPTH_8; +#ifdef WITH_FFMPEG + /* 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)) { + bit_depths |= R_IMF_CHAN_DEPTH_10; + } + if (ELEM(av_codec_id, AV_CODEC_ID_H265, AV_CODEC_ID_AV1)) { + bit_depths |= R_IMF_CHAN_DEPTH_12; + } +#else + UNUSED_VARS(av_codec_id); +#endif + return bit_depths; +} + +#ifdef WITH_FFMPEG +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 = AV_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 = AV_CODEC_ID_MPEG4; + } + else if (preset == FFMPEG_PRESET_THEORA) { + rd->ffcodecdata.type = FFMPEG_OGG; /* XXX broken */ + rd->ffcodecdata.codec = AV_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 = AV_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; + } +} +#endif + +void IMB_ffmpeg_image_type_verify(RenderData *rd, const ImageFormatData *imf) +{ +#ifdef WITH_FFMPEG + int audio = 0; + + if (imf->imtype == R_IMF_IMTYPE_FFMPEG) { + if (rd->ffcodecdata.type <= 0 || rd->ffcodecdata.codec <= 0 || + rd->ffcodecdata.audio_codec <= 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; + } + + audio = 1; + } + else if (imf->imtype == R_IMF_IMTYPE_H264) { + if (rd->ffcodecdata.codec != AV_CODEC_ID_H264) { + ffmpeg_preset_set(rd, FFMPEG_PRESET_H264); + audio = 1; + } + } + else if (imf->imtype == R_IMF_IMTYPE_XVID) { + if (rd->ffcodecdata.codec != AV_CODEC_ID_MPEG4) { + ffmpeg_preset_set(rd, FFMPEG_PRESET_XVID); + audio = 1; + } + } + else if (imf->imtype == R_IMF_IMTYPE_THEORA) { + if (rd->ffcodecdata.codec != AV_CODEC_ID_THEORA) { + ffmpeg_preset_set(rd, FFMPEG_PRESET_THEORA); + audio = 1; + } + } + else if (imf->imtype == R_IMF_IMTYPE_AV1) { + if (rd->ffcodecdata.codec != AV_CODEC_ID_AV1) { + ffmpeg_preset_set(rd, FFMPEG_PRESET_AV1); + audio = 1; + } + } + + if (audio && rd->ffcodecdata.audio_codec < 0) { + rd->ffcodecdata.audio_codec = AV_CODEC_ID_NONE; + rd->ffcodecdata.audio_bitrate = 128; + } +#else + UNUSED_VARS(rd, imf); +#endif +} + +bool IMB_ffmpeg_alpha_channel_is_supported(int av_codec_id) +{ +#if WITH_FFMPEG + 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); +#else + UNUSED_VARS(av_codec_id); + return false; +#endif +} + +bool IMB_ffmpeg_codec_supports_crf(int av_codec_id) +{ +#if WITH_FFMPEG + 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); +#else + UNUSED_VARS(av_codec_id); + return false; +#endif +} diff --git a/source/blender/imbuf/movie/intern/ffmpeg_util.hh b/source/blender/imbuf/movie/intern/ffmpeg_util.hh new file mode 100644 index 00000000000..5313692978f --- /dev/null +++ b/source/blender/imbuf/movie/intern/ffmpeg_util.hh @@ -0,0 +1,24 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +/** \file + * \ingroup imbuf + */ + +#ifdef WITH_FFMPEG + +extern "C" { +# include +} + +struct AVFrame; + +int ffmpeg_deinterlace( + AVFrame *dst, const AVFrame *src, enum AVPixelFormat pix_fmt, int width, int height); + +const char *ffmpeg_last_error(); + +#endif /* WITH_FFMPEG */ diff --git a/source/blender/imbuf/intern/indexer.cc b/source/blender/imbuf/movie/intern/movie_proxy_indexer.cc similarity index 98% rename from source/blender/imbuf/intern/indexer.cc rename to source/blender/imbuf/movie/intern/movie_proxy_indexer.cc index a3c4ea165d1..f1329ac72bb 100644 --- a/source/blender/imbuf/intern/indexer.cc +++ b/source/blender/imbuf/movie/intern/movie_proxy_indexer.cc @@ -1,4 +1,5 @@ /* SPDX-FileCopyrightText: 2011 Peter Schlaile . + * SPDX-FileCopyrightText: 2024 Blender Authors * * SPDX-License-Identifier: GPL-2.0-or-later */ @@ -32,11 +33,11 @@ # include "BLI_winstuff.h" #endif -#include "BKE_writeffmpeg.hh" - #include "IMB_anim.hh" -#include "IMB_imbuf.hh" -#include "IMB_indexer.hh" + +#include "ffmpeg_swscale.hh" +#include "movie_proxy_indexer.hh" +#include "movie_read.hh" #ifdef WITH_FFMPEG extern "C" { @@ -599,13 +600,13 @@ static proxy_output_ctx *alloc_proxy_output_ffmpeg(ImBufAnim *anim, rv->frame->height = height; av_frame_get_buffer(rv->frame, align); - rv->sws_ctx = BKE_ffmpeg_sws_get_context(st->codecpar->width, - rv->orig_height, - AVPixelFormat(st->codecpar->format), - width, - height, - rv->c->pix_fmt, - SWS_FAST_BILINEAR); + rv->sws_ctx = ffmpeg_sws_get_context(st->codecpar->width, + rv->orig_height, + AVPixelFormat(st->codecpar->format), + width, + height, + rv->c->pix_fmt, + SWS_FAST_BILINEAR); } ret = avformat_write_header(rv->of, nullptr); @@ -640,7 +641,7 @@ static void add_to_proxy_output_ffmpeg(proxy_output_ctx *ctx, AVFrame *frame) if (ctx->sws_ctx && frame && (frame->data[0] || frame->data[1] || frame->data[2] || frame->data[3])) { - BKE_ffmpeg_sws_scale_frame(ctx->sws_ctx, ctx->frame, frame); + ffmpeg_sws_scale_frame(ctx->sws_ctx, ctx->frame, frame); } frame = ctx->sws_ctx ? (frame ? ctx->frame : nullptr) : frame; @@ -731,7 +732,7 @@ static void free_proxy_output_ffmpeg(proxy_output_ctx *ctx, int rollback) avformat_free_context(ctx->of); if (ctx->sws_ctx) { - BKE_ffmpeg_sws_release_context(ctx->sws_ctx); + ffmpeg_sws_release_context(ctx->sws_ctx); ctx->sws_ctx = nullptr; } if (ctx->frame) { diff --git a/source/blender/imbuf/intern/IMB_indexer.hh b/source/blender/imbuf/movie/intern/movie_proxy_indexer.hh similarity index 96% rename from source/blender/imbuf/intern/IMB_indexer.hh rename to source/blender/imbuf/movie/intern/movie_proxy_indexer.hh index 904138fd085..e1a4723a98d 100644 --- a/source/blender/imbuf/intern/IMB_indexer.hh +++ b/source/blender/imbuf/movie/intern/movie_proxy_indexer.hh @@ -1,4 +1,4 @@ -/* SPDX-FileCopyrightText: 2023 Blender Authors +/* SPDX-FileCopyrightText: 2023-2024 Blender Authors * * SPDX-License-Identifier: GPL-2.0-or-later */ @@ -12,7 +12,8 @@ # include #endif -#include "IMB_anim.hh" +#include "IMB_movie_enums.hh" +#include "movie_read.hh" #include #include /* diff --git a/source/blender/imbuf/intern/anim_movie.cc b/source/blender/imbuf/movie/intern/movie_read.cc similarity index 97% rename from source/blender/imbuf/intern/anim_movie.cc rename to source/blender/imbuf/movie/intern/movie_read.cc index 302fd4c6855..6471d36e31d 100644 --- a/source/blender/imbuf/intern/anim_movie.cc +++ b/source/blender/imbuf/movie/intern/movie_read.cc @@ -1,4 +1,5 @@ /* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved. + * SPDX-FileCopyrightText: 2024 Blender Authors * * SPDX-License-Identifier: GPL-2.0-or-later */ @@ -28,18 +29,19 @@ #include "MEM_guardedalloc.h" +#include "IMB_anim.hh" +#include "IMB_colormanagement.hh" #include "IMB_imbuf.hh" #include "IMB_imbuf_types.hh" +#include "intern/IMB_colormanagement_intern.hh" -#include "IMB_colormanagement.hh" -#include "IMB_colormanagement_intern.hh" - -#include "IMB_anim.hh" -#include "IMB_indexer.hh" #include "IMB_metadata.hh" +#include "movie_proxy_indexer.hh" +#include "movie_read.hh" #ifdef WITH_FFMPEG -# include "BKE_writeffmpeg.hh" +# include "ffmpeg_swscale.hh" +# include "ffmpeg_util.hh" extern "C" { # include @@ -420,14 +422,14 @@ static int startffmpeg(ImBufAnim *anim) * the conversion is not fully accurate and introduces some banding and color * shifts, particularly in dark regions. See issue #111703 or upstream * ffmpeg ticket https://trac.ffmpeg.org/ticket/1582 */ - anim->img_convert_ctx = BKE_ffmpeg_sws_get_context(anim->x, - anim->y, - anim->pCodecCtx->pix_fmt, - anim->x, - anim->y, - anim->pFrameRGB->format, - SWS_POINT | SWS_FULL_CHR_H_INT | - SWS_ACCURATE_RND); + anim->img_convert_ctx = ffmpeg_sws_get_context(anim->x, + anim->y, + anim->pCodecCtx->pix_fmt, + anim->x, + anim->y, + anim->pFrameRGB->format, + SWS_POINT | SWS_FULL_CHR_H_INT | + SWS_ACCURATE_RND); if (!anim->img_convert_ctx) { fprintf(stderr, @@ -561,11 +563,11 @@ static void ffmpeg_postprocess(ImBufAnim *anim, AVFrame *input, ImBuf *ibuf) input->data[3]); if (anim->ib_flags & IB_animdeinterlace) { - if (av_image_deinterlace(anim->pFrameDeinterlaced, - anim->pFrame, - anim->pCodecCtx->pix_fmt, - anim->pCodecCtx->width, - anim->pCodecCtx->height) < 0) + if (ffmpeg_deinterlace(anim->pFrameDeinterlaced, + anim->pFrame, + anim->pCodecCtx->pix_fmt, + anim->pCodecCtx->width, + anim->pCodecCtx->height) < 0) { filter_y = true; } @@ -579,7 +581,7 @@ static void ffmpeg_postprocess(ImBufAnim *anim, AVFrame *input, ImBuf *ibuf) * it does not support direct YUV->RGBA float interleaved conversion). * Do vertical flip and interleave into RGBA manually. */ /* Decode, then do vertical flip into destination. */ - BKE_ffmpeg_sws_scale_frame(anim->img_convert_ctx, anim->pFrameRGB, input); + ffmpeg_sws_scale_frame(anim->img_convert_ctx, anim->pFrameRGB, input); const size_t src_linesize = anim->pFrameRGB->linesize[0]; BLI_assert_msg(anim->pFrameRGB->linesize[1] == src_linesize && @@ -623,14 +625,14 @@ static void ffmpeg_postprocess(ImBufAnim *anim, AVFrame *input, ImBuf *ibuf) anim->pFrameRGB->linesize[0] = -ibuf_linesize; anim->pFrameRGB->data[0] = ibuf->byte_buffer.data + (ibuf->y - 1) * ibuf_linesize; - BKE_ffmpeg_sws_scale_frame(anim->img_convert_ctx, anim->pFrameRGB, input); + ffmpeg_sws_scale_frame(anim->img_convert_ctx, anim->pFrameRGB, input); anim->pFrameRGB->linesize[0] = rgb_linesize; anim->pFrameRGB->data[0] = rgb_data; } else { /* Decode, then do vertical flip into destination. */ - BKE_ffmpeg_sws_scale_frame(anim->img_convert_ctx, anim->pFrameRGB, input); + ffmpeg_sws_scale_frame(anim->img_convert_ctx, anim->pFrameRGB, input); /* Use negative line size to do vertical image flip. */ const int src_linesize[4] = {-rgb_linesize, 0, 0, 0}; @@ -1187,7 +1189,7 @@ static void free_anim_ffmpeg(ImBufAnim *anim) MEM_freeN(anim->pFrameDeinterlaced->data[0]); } av_frame_free(&anim->pFrameDeinterlaced); - BKE_ffmpeg_sws_release_context(anim->img_convert_ctx); + ffmpeg_sws_release_context(anim->img_convert_ctx); } anim->duration_in_frames = 0; } @@ -1322,7 +1324,7 @@ int IMB_anim_get_duration(ImBufAnim *anim, IMB_Timecode_Type tc) return IMB_indexer_get_duration(idx); } -double IMD_anim_get_offset(ImBufAnim *anim) +double IMB_anim_get_offset(ImBufAnim *anim) { return anim->start_offset; } diff --git a/source/blender/imbuf/intern/IMB_anim.hh b/source/blender/imbuf/movie/intern/movie_read.hh similarity index 96% rename from source/blender/imbuf/intern/IMB_anim.hh rename to source/blender/imbuf/movie/intern/movie_read.hh index 48fff879974..aeb482c8269 100644 --- a/source/blender/imbuf/intern/IMB_anim.hh +++ b/source/blender/imbuf/movie/intern/movie_read.hh @@ -1,4 +1,5 @@ /* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved. + * SPDX-FileCopyrightText: 2024 Blender Authors * * SPDX-License-Identifier: GPL-2.0-or-later */ diff --git a/source/blender/blenkernel/intern/writeffmpeg.cc b/source/blender/imbuf/movie/intern/movie_write.cc similarity index 80% rename from source/blender/blenkernel/intern/writeffmpeg.cc rename to source/blender/imbuf/movie/intern/movie_write.cc index a08c66c5087..9a2c1097c5b 100644 --- a/source/blender/blenkernel/intern/writeffmpeg.cc +++ b/source/blender/imbuf/movie/intern/movie_write.cc @@ -1,12 +1,16 @@ -/* SPDX-FileCopyrightText: 2023 Blender Authors +/* SPDX-FileCopyrightText: 2006 Peter Schlaile. + * SPDX-FileCopyrightText: 2023-2024 Blender Authors * - * SPDX-License-Identifier: GPL-2.0-or-later - * Partial Copyright 2006 Peter Schlaile. */ + * SPDX-License-Identifier: GPL-2.0-or-later */ /** \file - * \ingroup bke + * \ingroup imbuf */ +#include "DNA_scene_types.h" + +#include "IMB_movie_write.hh" + #ifdef WITH_FFMPEG # include # include @@ -15,8 +19,6 @@ # include "MEM_guardedalloc.h" -# include "DNA_scene_types.h" - # include "BLI_blenlib.h" # ifdef WITH_AUDASPACE @@ -28,17 +30,19 @@ # include "BLI_math_base.h" # include "BLI_threads.h" # include "BLI_utildefines.h" -# include "BLI_vector.hh" # include "BKE_global.hh" # include "BKE_image.hh" # include "BKE_main.hh" # include "BKE_report.hh" # include "BKE_sound.h" -# include "BKE_writeffmpeg.hh" +# include "IMB_anim.hh" # include "IMB_imbuf.hh" +# include "ffmpeg_swscale.hh" +# include "ffmpeg_util.hh" + /* This needs to be included after BLI_math_base.h otherwise it will redefine some math defines * like M_SQRT1_2 leading to warnings with MSVC */ extern "C" { @@ -50,34 +54,13 @@ extern "C" { # include # include # include -# include # include "ffmpeg_compat.h" } struct StampData; -/* libswscale context creation and destruction is expensive. - * Maintain a cache of already created contexts. */ - -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; - int flags = 0; - - SwsContext *context = nullptr; - int64_t last_use_timestamp = 0; - bool is_used = false; -}; - -static ThreadMutex swscale_cache_lock = PTHREAD_MUTEX_INITIALIZER; -static int64_t swscale_cache_timestamp = 0; -static blender::Vector *swscale_cache = nullptr; - -struct FFMpegContext { +struct ImbMovieWriter { int ffmpeg_type; AVCodecID ffmpeg_codec; AVCodecID ffmpeg_audio_codec; @@ -126,7 +109,8 @@ struct FFMpegContext { printf static void ffmpeg_dict_set_int(AVDictionary **dict, const char *key, int value); -static void ffmpeg_filepath_get(FFMpegContext *context, +static void ffmpeg_movie_close(ImbMovieWriter *context); +static void ffmpeg_filepath_get(ImbMovieWriter *context, char filepath[FILE_MAX], const RenderData *rd, bool preview, @@ -150,7 +134,7 @@ static int request_float_audio_buffer(int codec_id) # ifdef WITH_AUDASPACE -static int write_audio_frame(FFMpegContext *context) +static int write_audio_frame(ImbMovieWriter *context) { AVFrame *frame = nullptr; AVCodecContext *c = context->audio_codec; @@ -340,7 +324,7 @@ static const char **get_file_extensions(int format) } /* Write a frame to the output file */ -static bool write_video_frame(FFMpegContext *context, AVFrame *frame, ReportList *reports) +static bool write_video_frame(ImbMovieWriter *context, AVFrame *frame, ReportList *reports) { int ret, success = 1; AVPacket *packet = av_packet_alloc(); @@ -396,7 +380,7 @@ static bool write_video_frame(FFMpegContext *context, AVFrame *frame, ReportList } /* read and encode a frame of video from the buffer */ -static AVFrame *generate_video_frame(FFMpegContext *context, const ImBuf *image) +static AVFrame *generate_video_frame(ImbMovieWriter *context, const ImBuf *image) { const uint8_t *pixels = image->byte_buffer.data; const float *pixels_fl = image->float_buffer.data; @@ -482,7 +466,7 @@ static AVFrame *generate_video_frame(FFMpegContext *context, const ImBuf *image) BLI_assert(context->img_convert_ctx != NULL); /* Ensure the frame we are scaling to is writable as well. */ av_frame_make_writable(context->current_frame); - BKE_ffmpeg_sws_scale_frame(context->img_convert_ctx, context->current_frame, rgb_frame); + ffmpeg_sws_scale_frame(context->img_convert_ctx, context->current_frame, rgb_frame); } return context->current_frame; @@ -531,7 +515,7 @@ static AVRational calc_time_base(uint den, double num, int codec_id) } static const AVCodec *get_av1_encoder( - FFMpegContext *context, RenderData *rd, AVDictionary **opts, int rectx, int recty) + ImbMovieWriter *context, RenderData *rd, AVDictionary **opts, int rectx, int recty) { /* There are three possible encoders for AV1: `libaom-av1`, librav1e, and `libsvtav1`. librav1e * tends to give the best compression quality while `libsvtav1` tends to be the fastest encoder. @@ -712,189 +696,6 @@ static const AVCodec *get_av1_encoder( return codec; } -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", BLI_system_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 blender::Vector(); - 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 *BKE_ffmpeg_sws_get_context(int src_width, - int src_height, - int av_src_format, - int dst_width, - int dst_height, - int av_dst_format, - int sws_flags) -{ - BLI_mutex_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.dst_width == dst_width && c.dst_height == dst_height && - c.dst_format == av_dst_format && 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); - 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.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(); - } - - BLI_mutex_unlock(&swscale_cache_lock); - return ctx; -} - -void BKE_ffmpeg_sws_release_context(SwsContext *ctx) -{ - BLI_mutex_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(); - - BLI_mutex_unlock(&swscale_cache_lock); -} - -void BKE_ffmpeg_exit() -{ - BLI_mutex_lock(&swscale_cache_lock); - if (swscale_cache != nullptr) { - for (SwscaleContext &c : *swscale_cache) { - sws_freeContext(c.context); - } - delete swscale_cache; - swscale_cache = nullptr; - } - BLI_mutex_unlock(&swscale_cache_lock); -} - -void BKE_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 -} - /* Remap H.264 CRF to H.265 CRF: 17..32 range (23 default) to 20..37 range (28 default). * https://trac.ffmpeg.org/wiki/Encode/H.265 */ static int remap_crf_to_h265_crf(int crf, bool is_10_or_12_bpp) @@ -927,7 +728,7 @@ static int remap_crf_to_h264_10bpp_crf(int crf) return crf; } -static void set_quality_rate_options(const FFMpegContext *context, +static void set_quality_rate_options(const ImbMovieWriter *context, const AVCodecID codec_id, const RenderData *rd, AVDictionary **opts) @@ -935,7 +736,7 @@ static void set_quality_rate_options(const FFMpegContext *context, AVCodecContext *c = context->video_codec; /* Handle constant bit rate (CBR) case. */ - if (!BKE_ffmpeg_codec_supports_crf(codec_id) || context->ffmpeg_crf < 0) { + if (!IMB_ffmpeg_codec_supports_crf(codec_id) || context->ffmpeg_crf < 0) { c->bit_rate = context->ffmpeg_video_bitrate * 1000; c->rc_max_rate = rd->ffcodecdata.rc_max_rate * 1000; c->rc_min_rate = rd->ffcodecdata.rc_min_rate * 1000; @@ -1006,7 +807,7 @@ static void set_quality_rate_options(const FFMpegContext *context, /* prepare a video stream for the output file */ -static AVStream *alloc_video_stream(FFMpegContext *context, +static AVStream *alloc_video_stream(ImbMovieWriter *context, RenderData *rd, AVCodecID codec_id, AVFormatContext *of, @@ -1234,7 +1035,7 @@ static AVStream *alloc_video_stream(FFMpegContext *context, char error_str[AV_ERROR_MAX_STRING_SIZE]; av_make_error_string(error_str, AV_ERROR_MAX_STRING_SIZE, ret); fprintf(stderr, "Couldn't initialize video codec: %s\n", error_str); - BLI_strncpy(error, IMB_ffmpeg_last_error(), error_size); + BLI_strncpy(error, ffmpeg_last_error(), error_size); av_dict_free(&opts); avcodec_free_context(&c); context->video_codec = nullptr; @@ -1254,7 +1055,7 @@ static AVStream *alloc_video_stream(FFMpegContext *context, /* Output pixel format is different, allocate frame for conversion. */ AVPixelFormat src_format = is_10_bpp || is_12_bpp ? AV_PIX_FMT_GBRAPF32LE : AV_PIX_FMT_RGBA; context->img_convert_frame = alloc_picture(src_format, c->width, c->height); - context->img_convert_ctx = BKE_ffmpeg_sws_get_context( + context->img_convert_ctx = ffmpeg_sws_get_context( c->width, c->height, src_format, c->width, c->height, c->pix_fmt, SWS_BICUBIC); /* Setup BT.709 coefficients for RGB->YUV conversion, if needed. */ @@ -1288,7 +1089,7 @@ static AVStream *alloc_video_stream(FFMpegContext *context, return st; } -static AVStream *alloc_audio_stream(FFMpegContext *context, +static AVStream *alloc_audio_stream(ImbMovieWriter *context, RenderData *rd, AVCodecID codec_id, AVFormatContext *of, @@ -1399,7 +1200,7 @@ static AVStream *alloc_audio_stream(FFMpegContext *context, char error_str[AV_ERROR_MAX_STRING_SIZE]; av_make_error_string(error_str, AV_ERROR_MAX_STRING_SIZE, ret); fprintf(stderr, "Couldn't initialize audio codec: %s\n", error_str); - BLI_strncpy(error, IMB_ffmpeg_last_error(), error_size); + BLI_strncpy(error, ffmpeg_last_error(), error_size); avcodec_free_context(&c); context->audio_codec = nullptr; return nullptr; @@ -1458,7 +1259,7 @@ static void ffmpeg_add_metadata_callback(void *data, av_dict_set(metadata, propname, propvalue, 0); } -static bool start_ffmpeg_impl(FFMpegContext *context, +static bool start_ffmpeg_impl(ImbMovieWriter *context, RenderData *rd, int rectx, int recty, @@ -1733,7 +1534,7 @@ static void flush_ffmpeg(AVCodecContext *c, AVStream *stream, AVFormatContext *o * ********************************************************************** */ /* Get the output filename-- similar to the other output formats */ -static void ffmpeg_filepath_get(FFMpegContext *context, +static void ffmpeg_filepath_get(ImbMovieWriter *context, char filepath[FILE_MAX], const RenderData *rd, bool preview, @@ -1802,30 +1603,40 @@ static void ffmpeg_filepath_get(FFMpegContext *context, BLI_path_suffix(filepath, FILE_MAX, suffix, ""); } -void BKE_ffmpeg_filepath_get(char filepath[/*FILE_MAX*/ 1024], - const RenderData *rd, - bool preview, - const char *suffix) +static void ffmpeg_get_filepath(char filepath[/*FILE_MAX*/ 1024], + const RenderData *rd, + bool preview, + const char *suffix) { ffmpeg_filepath_get(nullptr, filepath, rd, preview, suffix); } -bool BKE_ffmpeg_start(void *context_v, - const Scene *scene, - RenderData *rd, - int rectx, - int recty, - ReportList *reports, - bool preview, - const char *suffix) +static ImbMovieWriter *ffmpeg_movie_open(const Scene *scene, + RenderData *rd, + int rectx, + int recty, + ReportList *reports, + bool preview, + const char *suffix) { - FFMpegContext *context = static_cast(context_v); + ImbMovieWriter *context = static_cast( + MEM_callocN(sizeof(ImbMovieWriter), "new FFMPEG context")); + + context->ffmpeg_codec = AV_CODEC_ID_MPEG4; + context->ffmpeg_audio_codec = AV_CODEC_ID_NONE; + context->ffmpeg_video_bitrate = 1150; + context->ffmpeg_audio_bitrate = 128; + context->ffmpeg_gop_size = 12; + context->ffmpeg_autosplit = 0; + context->stamp_data = nullptr; + context->audio_time_total = 0.0; context->ffmpeg_autosplit_count = 0; context->ffmpeg_preview = preview; context->stamp_data = BKE_stamp_info_from_scene_static(scene); bool success = start_ffmpeg_impl(context, rd, rectx, recty, suffix, reports); + # ifdef WITH_AUDASPACE if (context->audio_stream) { AVCodecContext *c = context->audio_codec; @@ -1854,21 +1665,29 @@ bool BKE_ffmpeg_start(void *context_v, specs.format = AUD_FORMAT_FLOAT64; break; default: - return -31415; + success = false; + break; } specs.rate = rd->ffcodecdata.audio_mixrate; - context->audio_mixdown_device = BKE_sound_mixdown( - scene, specs, preview ? rd->psfra : rd->sfra, rd->ffcodecdata.audio_volume); + if (success) { + context->audio_mixdown_device = BKE_sound_mixdown( + scene, specs, preview ? rd->psfra : rd->sfra, rd->ffcodecdata.audio_volume); + } } # endif - return success; + + if (!success) { + ffmpeg_movie_close(context); + return nullptr; + } + return context; } -static void end_ffmpeg_impl(FFMpegContext *context, int is_autosplit); +static void end_ffmpeg_impl(ImbMovieWriter *context, int is_autosplit); # ifdef WITH_AUDASPACE -static void write_audio_frames(FFMpegContext *context, double to_pts) +static void write_audio_frames(ImbMovieWriter *context, double to_pts) { AVCodecContext *c = context->audio_codec; @@ -1882,15 +1701,14 @@ static void write_audio_frames(FFMpegContext *context, double to_pts) } # endif -bool BKE_ffmpeg_append(void *context_v, - RenderData *rd, - int start_frame, - int frame, - const ImBuf *image, - const char *suffix, - ReportList *reports) +static bool ffmpeg_movie_append(ImbMovieWriter *context, + RenderData *rd, + int start_frame, + int frame, + const ImBuf *image, + const char *suffix, + ReportList *reports) { - FFMpegContext *context = static_cast(context_v); AVFrame *avframe; bool success = true; @@ -1920,7 +1738,7 @@ bool BKE_ffmpeg_append(void *context_v, return success; } -static void end_ffmpeg_impl(FFMpegContext *context, int is_autosplit) +static void end_ffmpeg_impl(ImbMovieWriter *context, int is_autosplit) { PRINT("Closing FFMPEG...\n"); @@ -2000,179 +1818,17 @@ static void end_ffmpeg_impl(FFMpegContext *context, int is_autosplit) } if (context->img_convert_ctx != nullptr) { - BKE_ffmpeg_sws_release_context(context->img_convert_ctx); + ffmpeg_sws_release_context(context->img_convert_ctx); context->img_convert_ctx = nullptr; } } -void BKE_ffmpeg_end(void *context_v) +static void ffmpeg_movie_close(ImbMovieWriter *context) { - FFMpegContext *context = static_cast(context_v); - end_ffmpeg_impl(context, false); -} - -void BKE_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 = AV_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 = AV_CODEC_ID_MPEG4; - } - else if (preset == FFMPEG_PRESET_THEORA) { - rd->ffcodecdata.type = FFMPEG_OGG; /* XXX broken */ - rd->ffcodecdata.codec = AV_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 = AV_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; - } -} - -void BKE_ffmpeg_image_type_verify(RenderData *rd, const ImageFormatData *imf) -{ - int audio = 0; - - if (imf->imtype == R_IMF_IMTYPE_FFMPEG) { - if (rd->ffcodecdata.type <= 0 || rd->ffcodecdata.codec <= 0 || - rd->ffcodecdata.audio_codec <= 0 || rd->ffcodecdata.video_bitrate <= 1) - { - BKE_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; - } - - audio = 1; - } - else if (imf->imtype == R_IMF_IMTYPE_H264) { - if (rd->ffcodecdata.codec != AV_CODEC_ID_H264) { - BKE_ffmpeg_preset_set(rd, FFMPEG_PRESET_H264); - audio = 1; - } - } - else if (imf->imtype == R_IMF_IMTYPE_XVID) { - if (rd->ffcodecdata.codec != AV_CODEC_ID_MPEG4) { - BKE_ffmpeg_preset_set(rd, FFMPEG_PRESET_XVID); - audio = 1; - } - } - else if (imf->imtype == R_IMF_IMTYPE_THEORA) { - if (rd->ffcodecdata.codec != AV_CODEC_ID_THEORA) { - BKE_ffmpeg_preset_set(rd, FFMPEG_PRESET_THEORA); - audio = 1; - } - } - else if (imf->imtype == R_IMF_IMTYPE_AV1) { - if (rd->ffcodecdata.codec != AV_CODEC_ID_AV1) { - BKE_ffmpeg_preset_set(rd, FFMPEG_PRESET_AV1); - audio = 1; - } - } - - if (audio && rd->ffcodecdata.audio_codec < 0) { - rd->ffcodecdata.audio_codec = AV_CODEC_ID_NONE; - rd->ffcodecdata.audio_bitrate = 128; - } -} - -bool BKE_ffmpeg_alpha_channel_is_supported(const RenderData *rd) -{ - int codec = rd->ffcodecdata.codec; - - return ELEM(codec, - AV_CODEC_ID_FFV1, - AV_CODEC_ID_QTRLE, - AV_CODEC_ID_PNG, - AV_CODEC_ID_VP9, - AV_CODEC_ID_HUFFYUV); -} - -bool BKE_ffmpeg_codec_supports_crf(int 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 BKE_ffmpeg_valid_bit_depths(int av_codec_id) -{ - int 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)) { - bit_depths |= R_IMF_CHAN_DEPTH_10; - } - if (ELEM(av_codec_id, AV_CODEC_ID_H265, AV_CODEC_ID_AV1)) { - bit_depths |= R_IMF_CHAN_DEPTH_12; - } - return bit_depths; -} - -void *BKE_ffmpeg_context_create() -{ - /* New FFMPEG data struct. */ - FFMpegContext *context = static_cast( - MEM_callocN(sizeof(FFMpegContext), "new FFMPEG context")); - - context->ffmpeg_codec = AV_CODEC_ID_MPEG4; - context->ffmpeg_audio_codec = AV_CODEC_ID_NONE; - context->ffmpeg_video_bitrate = 1150; - context->ffmpeg_audio_bitrate = 128; - context->ffmpeg_gop_size = 12; - context->ffmpeg_autosplit = 0; - context->ffmpeg_autosplit_count = 0; - context->ffmpeg_preview = false; - context->stamp_data = nullptr; - context->audio_time_total = 0.0; - - return context; -} - -void BKE_ffmpeg_context_free(void *context_v) -{ - FFMpegContext *context = static_cast(context_v); if (context == nullptr) { return; } + end_ffmpeg_impl(context, false); if (context->stamp_data) { MEM_freeN(context->stamp_data); } @@ -2180,3 +1836,85 @@ void BKE_ffmpeg_context_free(void *context_v) } #endif /* WITH_FFMPEG */ + +static bool is_imtype_ffmpeg(const char imtype) +{ + return ELEM(imtype, + R_IMF_IMTYPE_AVIRAW, + R_IMF_IMTYPE_AVIJPEG, + R_IMF_IMTYPE_FFMPEG, + R_IMF_IMTYPE_H264, + R_IMF_IMTYPE_XVID, + R_IMF_IMTYPE_THEORA, + R_IMF_IMTYPE_AV1); +} + +ImbMovieWriter *IMB_movie_write_begin(const char imtype, + const Scene *scene, + RenderData *rd, + int rectx, + int recty, + ReportList *reports, + bool preview, + const char *suffix) +{ + if (!is_imtype_ffmpeg(imtype)) { + return nullptr; + } + + ImbMovieWriter *writer = nullptr; +#ifdef WITH_FFMPEG + writer = ffmpeg_movie_open(scene, rd, rectx, recty, reports, preview, suffix); +#else + UNUSED_VARS(scene, rd, rectx, recty, reports, preview, suffix); +#endif + return writer; +} + +bool IMB_movie_write_append(ImbMovieWriter *writer, + RenderData *rd, + int start_frame, + int frame, + const ImBuf *image, + const char *suffix, + ReportList *reports) +{ + if (writer == nullptr) { + return false; + } + +#ifdef WITH_FFMPEG + bool ok = ffmpeg_movie_append(writer, rd, start_frame, frame, image, suffix, reports); + return ok; +#else + UNUSED_VARS(rd, start_frame, frame, image, suffix, reports); + return false; +#endif +} + +void IMB_movie_write_end(ImbMovieWriter *writer) +{ +#ifdef WITH_FFMPEG + if (writer) { + ffmpeg_movie_close(writer); + } +#else + UNUSED_VARS(writer); +#endif +} + +void IMB_movie_filepath_get(char filepath[/*FILE_MAX*/ 1024], + const RenderData *rd, + bool preview, + const char *suffix) +{ +#ifdef WITH_FFMPEG + if (is_imtype_ffmpeg(rd->im_format.imtype)) { + ffmpeg_get_filepath(filepath, rd, preview, suffix); + return; + } +#else + UNUSED_VARS(rd, preview, suffix); +#endif + filepath[0] = '\0'; +} diff --git a/intern/ffmpeg/tests/ffmpeg_codecs.cc b/source/blender/imbuf/movie/tests/ffmpeg_codecs.cc similarity index 99% rename from intern/ffmpeg/tests/ffmpeg_codecs.cc rename to source/blender/imbuf/movie/tests/ffmpeg_codecs.cc index ab3a1c84e09..ad7bd3968d1 100644 --- a/intern/ffmpeg/tests/ffmpeg_codecs.cc +++ b/source/blender/imbuf/movie/tests/ffmpeg_codecs.cc @@ -1,4 +1,4 @@ -/* SPDX-FileCopyrightText: 2020-2023 Blender Authors +/* SPDX-FileCopyrightText: 2020-2024 Blender Authors * * SPDX-License-Identifier: GPL-2.0-or-later */ diff --git a/intern/ffmpeg/tests/ffmpeg_cpu_flags.cc b/source/blender/imbuf/movie/tests/ffmpeg_cpu_flags.cc similarity index 100% rename from intern/ffmpeg/tests/ffmpeg_cpu_flags.cc rename to source/blender/imbuf/movie/tests/ffmpeg_cpu_flags.cc diff --git a/source/blender/makesdna/intern/CMakeLists.txt b/source/blender/makesdna/intern/CMakeLists.txt index ed2a738e970..3432159c507 100644 --- a/source/blender/makesdna/intern/CMakeLists.txt +++ b/source/blender/makesdna/intern/CMakeLists.txt @@ -7,6 +7,7 @@ set(INC PUBLIC ${CMAKE_CURRENT_BINARY_DIR} ../../blenlib ../../imbuf + ../../imbuf/movie ) set(INC_SYS diff --git a/source/blender/makesdna/intern/dna_defaults.c b/source/blender/makesdna/intern/dna_defaults.c index e48b6ee25e5..b14f27135e5 100644 --- a/source/blender/makesdna/intern/dna_defaults.c +++ b/source/blender/makesdna/intern/dna_defaults.c @@ -74,6 +74,7 @@ #include "BLI_utildefines.h" #include "IMB_imbuf_enums.h" +#include "movie/IMB_movie_enums.hh" #include "DNA_defaults.h" diff --git a/source/blender/makesrna/intern/CMakeLists.txt b/source/blender/makesrna/intern/CMakeLists.txt index 95f5aad5d86..bbdc0c66ba1 100644 --- a/source/blender/makesrna/intern/CMakeLists.txt +++ b/source/blender/makesrna/intern/CMakeLists.txt @@ -249,6 +249,7 @@ set(INC ../../gpu ../../ikplugin ../../imbuf + ../../imbuf/movie ../../io/usd ../../modifiers ../../nodes @@ -315,15 +316,6 @@ if(WITH_AUDASPACE) endif() if(WITH_CODEC_FFMPEG) - list(APPEND INC - ../../../../intern/ffmpeg - ) - list(APPEND INC_SYS - ${FFMPEG_INCLUDE_DIRS} - ) - list(APPEND LIB - ${FFMPEG_LIBRARIES} - ) add_definitions(-DWITH_FFMPEG) endif() diff --git a/source/blender/makesrna/intern/rna_color.cc b/source/blender/makesrna/intern/rna_color.cc index 395825247dd..038e8d33df9 100644 --- a/source/blender/makesrna/intern/rna_color.cc +++ b/source/blender/makesrna/intern/rna_color.cc @@ -63,6 +63,7 @@ const EnumPropertyItem rna_enum_color_space_convert_default_items[] = { # include "ED_node.hh" +# include "IMB_anim.hh" # include "IMB_colormanagement.hh" # include "IMB_imbuf.hh" diff --git a/source/blender/makesrna/intern/rna_image.cc b/source/blender/makesrna/intern/rna_image.cc index cdf8d75d89f..338d62376db 100644 --- a/source/blender/makesrna/intern/rna_image.cc +++ b/source/blender/makesrna/intern/rna_image.cc @@ -26,6 +26,8 @@ #include "RNA_define.hh" #include "RNA_enum_types.hh" +#include "IMB_anim.hh" + #include "rna_internal.hh" #include "WM_api.hh" diff --git a/source/blender/makesrna/intern/rna_movieclip.cc b/source/blender/makesrna/intern/rna_movieclip.cc index 2a6d63170e2..aa5c5ea4595 100644 --- a/source/blender/makesrna/intern/rna_movieclip.cc +++ b/source/blender/makesrna/intern/rna_movieclip.cc @@ -27,6 +27,7 @@ #include "IMB_imbuf.hh" #include "IMB_imbuf_types.hh" #include "IMB_metadata.hh" +#include "IMB_movie_enums.hh" #ifdef RNA_RUNTIME diff --git a/source/blender/makesrna/intern/rna_scene.cc b/source/blender/makesrna/intern/rna_scene.cc index 67205a1807f..8bf7e04f56e 100644 --- a/source/blender/makesrna/intern/rna_scene.cc +++ b/source/blender/makesrna/intern/rna_scene.cc @@ -53,12 +53,7 @@ #include "RE_engine.h" #include "RE_pipeline.h" -#ifdef WITH_FFMPEG -# include "BKE_writeffmpeg.hh" -# include "ffmpeg_compat.h" -# include -# include -#endif +#include "IMB_anim.hh" #include "ED_render.hh" #include "ED_transform.hh" @@ -1349,10 +1344,7 @@ static void rna_ImageFormatSettings_file_format_set(PointerRNA *ptr, int value) if (id && GS(id->name) == ID_SCE) { Scene *scene = (Scene *)ptr->owner_id; RenderData *rd = &scene->r; -# ifdef WITH_FFMPEG - BKE_ffmpeg_image_type_verify(rd, imf); -# endif - (void)rd; + IMB_ffmpeg_image_type_verify(rd, imf); } BKE_image_format_update_color_space_for_type(imf); @@ -1388,7 +1380,6 @@ static const EnumPropertyItem *rna_ImageFormatSettings_color_mode_itemf(bContext char chan_flag = BKE_imtype_valid_channels(imf->imtype, true) | (is_render ? IMA_CHAN_FLAG_BW : 0); -# ifdef WITH_FFMPEG /* a WAY more crappy case than B&W flag: depending on codec, file format MIGHT support * alpha channel. for example MPEG format with h264 codec can't do alpha channel, but * the same MPEG format with QTRLE codec can easily handle alpha channel. @@ -1397,11 +1388,10 @@ static const EnumPropertyItem *rna_ImageFormatSettings_color_mode_itemf(bContext Scene *scene = (Scene *)ptr->owner_id; RenderData *rd = &scene->r; - if (BKE_ffmpeg_alpha_channel_is_supported(rd)) { + if (IMB_ffmpeg_alpha_channel_is_supported(rd->ffcodecdata.codec)) { chan_flag |= IMA_CHAN_FLAG_RGBA; } } -# endif if (chan_flag == (IMA_CHAN_FLAG_BW | IMA_CHAN_FLAG_RGB | IMA_CHAN_FLAG_RGBA)) { return rna_enum_image_color_mode_items; @@ -2947,7 +2937,7 @@ static std::optional rna_FFmpegSettings_path(const PointerRNA * /*p static void rna_FFmpegSettings_codec_update(Main * /*bmain*/, Scene * /*scene*/, PointerRNA *ptr) { FFMpegCodecData *codec_data = (FFMpegCodecData *)ptr->data; - if (!BKE_ffmpeg_codec_supports_crf(codec_data->codec)) { + if (!IMB_ffmpeg_codec_supports_crf(codec_data->codec)) { /* Constant Rate Factor (CRF) setting is only available for some codecs. Change encoder quality * mode to CBR for others. */ codec_data->constant_rate_factor = FFM_CRF_NONE; @@ -6426,24 +6416,28 @@ static void rna_def_scene_ffmpeg_settings(BlenderRNA *brna) }; static const EnumPropertyItem ffmpeg_codec_items[] = { - {AV_CODEC_ID_NONE, "NONE", 0, "No Video", "Disables video output, for audio-only renders"}, - {AV_CODEC_ID_AV1, "AV1", 0, "AV1", ""}, - {AV_CODEC_ID_H264, "H264", 0, "H.264", ""}, - {AV_CODEC_ID_H265, "H265", 0, "H.265 / HEVC", ""}, - {AV_CODEC_ID_VP9, "WEBM", 0, "WebM / VP9", ""}, + {FFMPEG_CODEC_ID_NONE, + "NONE", + 0, + "No Video", + "Disables video output, for audio-only renders"}, + {FFMPEG_CODEC_ID_AV1, "AV1", 0, "AV1", ""}, + {FFMPEG_CODEC_ID_H264, "H264", 0, "H.264", ""}, + {FFMPEG_CODEC_ID_H265, "H265", 0, "H.265 / HEVC", ""}, + {FFMPEG_CODEC_ID_VP9, "WEBM", 0, "WebM / VP9", ""}, /* Legacy / rare codecs. */ RNA_ENUM_ITEM_SEPR, - {AV_CODEC_ID_DNXHD, "DNXHD", 0, "DNxHD", ""}, - {AV_CODEC_ID_DVVIDEO, "DV", 0, "DV", ""}, - {AV_CODEC_ID_FFV1, "FFV1", 0, "FFmpeg video codec #1", ""}, - {AV_CODEC_ID_FLV1, "FLASH", 0, "Flash Video", ""}, - {AV_CODEC_ID_HUFFYUV, "HUFFYUV", 0, "HuffYUV", ""}, - {AV_CODEC_ID_MPEG1VIDEO, "MPEG1", 0, "MPEG-1", ""}, - {AV_CODEC_ID_MPEG2VIDEO, "MPEG2", 0, "MPEG-2", ""}, - {AV_CODEC_ID_MPEG4, "MPEG4", 0, "MPEG-4 (divx)", ""}, - {AV_CODEC_ID_PNG, "PNG", 0, "PNG", ""}, - {AV_CODEC_ID_QTRLE, "QTRLE", 0, "QuickTime Animation", ""}, - {AV_CODEC_ID_THEORA, "THEORA", 0, "Theora", ""}, + {FFMPEG_CODEC_ID_DNXHD, "DNXHD", 0, "DNxHD", ""}, + {FFMPEG_CODEC_ID_DVVIDEO, "DV", 0, "DV", ""}, + {FFMPEG_CODEC_ID_FFV1, "FFV1", 0, "FFmpeg video codec #1", ""}, + {FFMPEG_CODEC_ID_FLV1, "FLASH", 0, "Flash Video", ""}, + {FFMPEG_CODEC_ID_HUFFYUV, "HUFFYUV", 0, "HuffYUV", ""}, + {FFMPEG_CODEC_ID_MPEG1VIDEO, "MPEG1", 0, "MPEG-1", ""}, + {FFMPEG_CODEC_ID_MPEG2VIDEO, "MPEG2", 0, "MPEG-2", ""}, + {FFMPEG_CODEC_ID_MPEG4, "MPEG4", 0, "MPEG-4 (divx)", ""}, + {FFMPEG_CODEC_ID_PNG, "PNG", 0, "PNG", ""}, + {FFMPEG_CODEC_ID_QTRLE, "QTRLE", 0, "QuickTime Animation", ""}, + {FFMPEG_CODEC_ID_THEORA, "THEORA", 0, "Theora", ""}, {0, nullptr, 0, nullptr, nullptr}, }; @@ -6478,15 +6472,19 @@ static void rna_def_scene_ffmpeg_settings(BlenderRNA *brna) }; static const EnumPropertyItem ffmpeg_audio_codec_items[] = { - {AV_CODEC_ID_NONE, "NONE", 0, "No Audio", "Disables audio output, for video-only renders"}, - {AV_CODEC_ID_AAC, "AAC", 0, "AAC", ""}, - {AV_CODEC_ID_AC3, "AC3", 0, "AC3", ""}, - {AV_CODEC_ID_FLAC, "FLAC", 0, "FLAC", ""}, - {AV_CODEC_ID_MP2, "MP2", 0, "MP2", ""}, - {AV_CODEC_ID_MP3, "MP3", 0, "MP3", ""}, - {AV_CODEC_ID_OPUS, "OPUS", 0, "Opus", ""}, - {AV_CODEC_ID_PCM_S16LE, "PCM", 0, "PCM", ""}, - {AV_CODEC_ID_VORBIS, "VORBIS", 0, "Vorbis", ""}, + {FFMPEG_CODEC_ID_NONE, + "NONE", + 0, + "No Audio", + "Disables audio output, for video-only renders"}, + {FFMPEG_CODEC_ID_AAC, "AAC", 0, "AAC", ""}, + {FFMPEG_CODEC_ID_AC3, "AC3", 0, "AC3", ""}, + {FFMPEG_CODEC_ID_FLAC, "FLAC", 0, "FLAC", ""}, + {FFMPEG_CODEC_ID_MP2, "MP2", 0, "MP2", ""}, + {FFMPEG_CODEC_ID_MP3, "MP3", 0, "MP3", ""}, + {FFMPEG_CODEC_ID_OPUS, "OPUS", 0, "Opus", ""}, + {FFMPEG_CODEC_ID_PCM_S16LE, "PCM", 0, "PCM", ""}, + {FFMPEG_CODEC_ID_VORBIS, "VORBIS", 0, "Vorbis", ""}, {0, nullptr, 0, nullptr, nullptr}, }; # endif @@ -6525,7 +6523,7 @@ static void rna_def_scene_ffmpeg_settings(BlenderRNA *brna) RNA_def_property_enum_bitflag_sdna(prop, nullptr, "codec"); RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); RNA_def_property_enum_items(prop, ffmpeg_codec_items); - RNA_def_property_enum_default(prop, AV_CODEC_ID_H264); + RNA_def_property_enum_default(prop, FFMPEG_CODEC_ID_H264); RNA_def_property_ui_text(prop, "Video Codec", "FFmpeg codec to use for video output"); RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, "rna_FFmpegSettings_codec_update"); diff --git a/source/blender/makesrna/intern/rna_scene_api.cc b/source/blender/makesrna/intern/rna_scene_api.cc index e4c12b61819..ec03d52ef36 100644 --- a/source/blender/makesrna/intern/rna_scene_api.cc +++ b/source/blender/makesrna/intern/rna_scene_api.cc @@ -34,7 +34,6 @@ # include "BKE_global.hh" # include "BKE_image.hh" # include "BKE_scene.hh" -# include "BKE_writemovie.hh" # include "DEG_depsgraph_query.hh" @@ -42,6 +41,8 @@ # include "ED_transform_snap_object_context.hh" # include "ED_uvedit.hh" +# include "IMB_movie_write.hh" + # ifdef WITH_PYTHON # include "BPY_extern.hh" # endif @@ -114,7 +115,7 @@ static void rna_SceneRender_get_frame_path( } if (BKE_imtype_is_movie(rd->im_format.imtype)) { - BKE_movie_filepath_get(filepath, rd, preview != 0, suffix); + IMB_movie_filepath_get(filepath, rd, preview != 0, suffix); } else { BKE_image_path_from_imformat(filepath, diff --git a/source/blender/makesrna/intern/rna_sequencer.cc b/source/blender/makesrna/intern/rna_sequencer.cc index 97cac45b4f8..0f997e31dec 100644 --- a/source/blender/makesrna/intern/rna_sequencer.cc +++ b/source/blender/makesrna/intern/rna_sequencer.cc @@ -127,6 +127,7 @@ const EnumPropertyItem rna_enum_strip_color_items[] = { # include "DEG_depsgraph.hh" # include "DEG_depsgraph_build.hh" +# include "IMB_anim.hh" # include "IMB_imbuf.hh" # include "SEQ_edit.hh" diff --git a/source/blender/nodes/geometry/CMakeLists.txt b/source/blender/nodes/geometry/CMakeLists.txt index 63ae97b11fa..1ee24d060ed 100644 --- a/source/blender/nodes/geometry/CMakeLists.txt +++ b/source/blender/nodes/geometry/CMakeLists.txt @@ -16,6 +16,7 @@ set(INC ../../geometry ../../gpu ../../imbuf + ../../imbuf/movie ../../makesrna ../../modifiers ../../render diff --git a/source/blender/nodes/geometry/nodes/node_geo_image_info.cc b/source/blender/nodes/geometry/nodes/node_geo_image_info.cc index 72dd4c25e52..9cfe0d4ee63 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_image_info.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_image_info.cc @@ -4,6 +4,7 @@ #include "BKE_image.hh" +#include "IMB_anim.hh" #include "IMB_imbuf.hh" #include "IMB_imbuf_types.hh" diff --git a/source/blender/render/CMakeLists.txt b/source/blender/render/CMakeLists.txt index 657dd973c3a..09bd5db31d2 100644 --- a/source/blender/render/CMakeLists.txt +++ b/source/blender/render/CMakeLists.txt @@ -15,6 +15,7 @@ set(INC ../gpu ../gpu/intern ../imbuf + ../imbuf/movie ../makesrna ../nodes ../sequencer diff --git a/source/blender/render/RE_pipeline.h b/source/blender/render/RE_pipeline.h index 7bfd27e7178..382b8ef2d43 100644 --- a/source/blender/render/RE_pipeline.h +++ b/source/blender/render/RE_pipeline.h @@ -18,6 +18,7 @@ struct GPUTexture; struct ImBuf; struct Image; struct ImageFormatData; +struct ImbMovieWriter; struct Main; struct Object; struct RenderData; @@ -26,7 +27,6 @@ struct ReportList; struct Scene; struct StampData; struct ViewLayer; -struct bMovieHandle; #ifdef __cplusplus extern "C" { @@ -344,8 +344,7 @@ bool RE_WriteRenderViewsMovie(struct ReportList *reports, struct RenderResult *rr, struct Scene *scene, struct RenderData *rd, - struct bMovieHandle *mh, - void **movie_ctx_arr, + struct ImbMovieWriter **movie_writers, int totvideos, bool preview); diff --git a/source/blender/render/intern/pipeline.cc b/source/blender/render/intern/pipeline.cc index b259d96ad26..4f332bbc442 100644 --- a/source/blender/render/intern/pipeline.cc +++ b/source/blender/render/intern/pipeline.cc @@ -63,7 +63,6 @@ #include "BKE_report.hh" #include "BKE_scene.hh" #include "BKE_sound.h" -#include "BKE_writemovie.hh" #include "NOD_composite.hh" @@ -78,6 +77,7 @@ #include "IMB_imbuf.hh" #include "IMB_imbuf_types.hh" #include "IMB_metadata.hh" +#include "IMB_movie_write.hh" #include "RE_engine.h" #include "RE_pipeline.h" @@ -167,12 +167,8 @@ static void render_callback_exec_id(Render *re, Main *bmain, ID *id, eCbEvent ev /** \name Allocation & Free * \{ */ -static bool do_write_image_or_movie(Render *re, - Main *bmain, - Scene *scene, - bMovieHandle *mh, - const int totvideos, - const char *filepath_override); +static bool do_write_image_or_movie( + Render *re, Main *bmain, Scene *scene, const int totvideos, const char *filepath_override); /* default callbacks, set in each new render */ static void result_nothing(void * /*arg*/, RenderResult * /*rr*/) {} @@ -2053,9 +2049,7 @@ void RE_RenderFrame(Render *re, (rd.scemode & R_EXTENSION) != 0, false, nullptr); - - /* reports only used for Movie */ - do_write_image_or_movie(re, bmain, scene, nullptr, 0, filepath_override); + do_write_image_or_movie(re, bmain, scene, 0, filepath_override); } } @@ -2152,8 +2146,7 @@ bool RE_WriteRenderViewsMovie(ReportList *reports, RenderResult *rr, Scene *scene, RenderData *rd, - bMovieHandle *mh, - void **movie_ctx_arr, + ImbMovieWriter **movie_writers, const int totvideos, bool preview) { @@ -2177,13 +2170,14 @@ bool RE_WriteRenderViewsMovie(ReportList *reports, IMB_colormanagement_imbuf_for_write(ibuf, true, false, &image_format); - if (!mh->append_movie(movie_ctx_arr[view_id], - rd, - preview ? scene->r.psfra : scene->r.sfra, - scene->r.cfra, - ibuf, - suffix, - reports)) + BLI_assert(movie_writers[view_id] != nullptr); + if (!IMB_movie_write_append(movie_writers[view_id], + rd, + preview ? scene->r.psfra : scene->r.sfra, + scene->r.cfra, + ibuf, + suffix, + reports)) { ok = false; } @@ -2211,13 +2205,14 @@ bool RE_WriteRenderViewsMovie(ReportList *reports, ibuf_arr[2] = IMB_stereo3d_ImBuf(&image_format, ibuf_arr[0], ibuf_arr[1]); - if (!mh->append_movie(movie_ctx_arr[0], - rd, - preview ? scene->r.psfra : scene->r.sfra, - scene->r.cfra, - ibuf_arr[2], - "", - reports)) + BLI_assert(movie_writers[0] != nullptr); + if (!IMB_movie_write_append(movie_writers[0], + rd, + preview ? scene->r.psfra : scene->r.sfra, + scene->r.cfra, + ibuf_arr[2], + "", + reports)) { ok = false; } @@ -2233,12 +2228,8 @@ bool RE_WriteRenderViewsMovie(ReportList *reports, return ok; } -static bool do_write_image_or_movie(Render *re, - Main *bmain, - Scene *scene, - bMovieHandle *mh, - const int totvideos, - const char *filepath_override) +static bool do_write_image_or_movie( + Render *re, Main *bmain, Scene *scene, const int totvideos, const char *filepath_override) { char filepath[FILE_MAX]; RenderResult rres; @@ -2256,7 +2247,7 @@ static bool do_write_image_or_movie(Render *re, /* write movie or image */ if (BKE_imtype_is_movie(scene->r.im_format.imtype)) { RE_WriteRenderViewsMovie( - re->reports, &rres, scene, &re->r, mh, re->movie_ctx_arr, totvideos, false); + re->reports, &rres, scene, &re->r, re->movie_writers.data(), totvideos, false); } else { if (filepath_override) { @@ -2334,16 +2325,12 @@ static void get_videos_dimensions(const Render *re, BKE_scene_multiview_videos_dimensions_get(rd, width, height, r_width, r_height); } -static void re_movie_free_all(Render *re, bMovieHandle *mh, int totvideos) +static void re_movie_free_all(Render *re) { - int i; - - for (i = 0; i < totvideos; i++) { - mh->end_movie(re->movie_ctx_arr[i]); - mh->context_free(re->movie_ctx_arr[i]); + for (ImbMovieWriter *writer : re->movie_writers) { + IMB_movie_write_end(writer); } - - MEM_SAFE_FREE(re->movie_ctx_arr); + re->movie_writers.clear_and_shrink(); } void RE_RenderAnim(Render *re, @@ -2361,7 +2348,6 @@ void RE_RenderAnim(Render *re, RenderData rd; memcpy(&rd, &scene->r, sizeof(rd)); - bMovieHandle *mh = nullptr; const int cfra_old = rd.cfra; const float subframe_old = rd.subframe; int nfra, totrendered = 0, totskipped = 0; @@ -2385,42 +2371,30 @@ void RE_RenderAnim(Render *re, if (is_movie && do_write_file) { size_t width, height; - int i; - bool is_error = false; - get_videos_dimensions(re, &rd, &width, &height); - mh = BKE_movie_handle_get(rd.im_format.imtype); - if (mh == nullptr) { - render_pipeline_free(re); - BKE_report(re->reports, RPT_ERROR, "Movie format unsupported"); - return; - } - - re->movie_ctx_arr = MEM_cnew_array(totvideos, "Movies' Context"); - - for (i = 0; i < totvideos; i++) { + bool is_error = false; + re->movie_writers.reserve(totvideos); + for (int i = 0; i < totvideos; i++) { const char *suffix = BKE_scene_multiview_view_id_suffix_get(&re->r, i); - - re->movie_ctx_arr[i] = mh->context_create(); - - if (!mh->start_movie(re->movie_ctx_arr[i], - re->pipeline_scene_eval, - &re->r, - width, - height, - re->reports, - false, - suffix)) - { + ImbMovieWriter *writer = IMB_movie_write_begin(rd.im_format.imtype, + re->pipeline_scene_eval, + &re->r, + width, + height, + re->reports, + false, + suffix); + if (writer == nullptr) { is_error = true; break; } + re->movie_writers.append(writer); } if (is_error) { - /* report is handled above */ - re_movie_free_all(re, mh, i + 1); + BKE_report(re->reports, RPT_ERROR, "Movie format unsupported"); + re_movie_free_all(re); render_pipeline_free(re); return; } @@ -2558,7 +2532,7 @@ void RE_RenderAnim(Render *re, const bool should_write = !(re->flag & R_SKIP_WRITE); if (re->test_break_cb(re->tbh) == 0) { if (!G.is_break && should_write) { - if (!do_write_image_or_movie(re, bmain, scene, mh, totvideos, nullptr)) { + if (!do_write_image_or_movie(re, bmain, scene, totvideos, nullptr)) { G.is_break = true; } } @@ -2610,7 +2584,7 @@ void RE_RenderAnim(Render *re, /* end movie */ if (is_movie && do_write_file) { - re_movie_free_all(re, mh, totvideos); + re_movie_free_all(re); } if (totskipped && totrendered == 0) { diff --git a/source/blender/render/intern/render_types.h b/source/blender/render/intern/render_types.h index b025650846c..094695a3584 100644 --- a/source/blender/render/intern/render_types.h +++ b/source/blender/render/intern/render_types.h @@ -245,7 +245,7 @@ struct Render : public BaseRender { */ struct ReportList *reports = nullptr; - void **movie_ctx_arr = nullptr; + blender::Vector movie_writers; char viewname[MAX_NAME] = ""; /* TODO: replace by a whole draw manager. */ diff --git a/source/blender/sequencer/CMakeLists.txt b/source/blender/sequencer/CMakeLists.txt index a643118421f..020c4da795f 100644 --- a/source/blender/sequencer/CMakeLists.txt +++ b/source/blender/sequencer/CMakeLists.txt @@ -10,6 +10,7 @@ set(INC ../blenloader ../blentranslation ../imbuf + ../imbuf/movie ../makesrna ../render ../windowmanager diff --git a/source/blender/sequencer/intern/multiview.cc b/source/blender/sequencer/intern/multiview.cc index aacc0e5d358..07ea0d7e11c 100644 --- a/source/blender/sequencer/intern/multiview.cc +++ b/source/blender/sequencer/intern/multiview.cc @@ -14,6 +14,7 @@ #include "BKE_scene.hh" +#include "IMB_anim.hh" #include "IMB_imbuf.hh" #include "multiview.hh" diff --git a/source/blender/sequencer/intern/proxy.cc b/source/blender/sequencer/intern/proxy.cc index 1d4d1ac7f12..28e91c92ef6 100644 --- a/source/blender/sequencer/intern/proxy.cc +++ b/source/blender/sequencer/intern/proxy.cc @@ -32,6 +32,7 @@ #include "WM_types.hh" +#include "IMB_anim.hh" #include "IMB_imbuf.hh" #include "IMB_imbuf_types.hh" #include "IMB_metadata.hh" diff --git a/source/blender/sequencer/intern/render.cc b/source/blender/sequencer/intern/render.cc index 77baba44ca4..1de122a565c 100644 --- a/source/blender/sequencer/intern/render.cc +++ b/source/blender/sequencer/intern/render.cc @@ -42,6 +42,7 @@ #include "DEG_depsgraph.hh" #include "DEG_depsgraph_query.hh" +#include "IMB_anim.hh" #include "IMB_colormanagement.hh" #include "IMB_imbuf.hh" #include "IMB_imbuf_types.hh" diff --git a/source/blender/sequencer/intern/sequencer.cc b/source/blender/sequencer/intern/sequencer.cc index 483c8aa379c..c4144fa9280 100644 --- a/source/blender/sequencer/intern/sequencer.cc +++ b/source/blender/sequencer/intern/sequencer.cc @@ -35,6 +35,7 @@ #include "DEG_depsgraph.hh" +#include "IMB_anim.hh" #include "IMB_imbuf.hh" #include "SEQ_channels.hh" diff --git a/source/blender/sequencer/intern/strip_add.cc b/source/blender/sequencer/intern/strip_add.cc index 29d61289a01..1e6877af056 100644 --- a/source/blender/sequencer/intern/strip_add.cc +++ b/source/blender/sequencer/intern/strip_add.cc @@ -33,6 +33,7 @@ #include "DEG_depsgraph_query.hh" +#include "IMB_anim.hh" #include "IMB_colormanagement.hh" #include "IMB_imbuf.hh" #include "IMB_imbuf_types.hh" @@ -453,7 +454,7 @@ Sequence *SEQ_add_movie_strip(Main *bmain, Scene *scene, ListBase *seqbase, SeqL DEG_id_tag_update(&scene->id, ID_RECALC_AUDIO_FPS | ID_RECALC_SEQUENCER_STRIPS); } - load_data->r_video_stream_start = IMD_anim_get_offset(anim_arr[0]); + load_data->r_video_stream_start = IMB_anim_get_offset(anim_arr[0]); } Sequence *seq = SEQ_sequence_alloc( diff --git a/source/blender/sequencer/intern/strip_relations.cc b/source/blender/sequencer/intern/strip_relations.cc index 57cc5cbf09c..6f305142030 100644 --- a/source/blender/sequencer/intern/strip_relations.cc +++ b/source/blender/sequencer/intern/strip_relations.cc @@ -21,6 +21,7 @@ #include "DEG_depsgraph.hh" +#include "IMB_anim.hh" #include "IMB_imbuf.hh" #include "SEQ_iterator.hh" diff --git a/source/blender/sequencer/intern/strip_time.cc b/source/blender/sequencer/intern/strip_time.cc index 98f1e8811b4..bdf449c260e 100644 --- a/source/blender/sequencer/intern/strip_time.cc +++ b/source/blender/sequencer/intern/strip_time.cc @@ -21,6 +21,7 @@ #include "DNA_sound_types.h" +#include "IMB_anim.hh" #include "IMB_imbuf.hh" #include "SEQ_channels.hh" diff --git a/source/blender/sequencer/intern/thumbnail_cache.cc b/source/blender/sequencer/intern/thumbnail_cache.cc index a882cbd0d19..5e104e670bb 100644 --- a/source/blender/sequencer/intern/thumbnail_cache.cc +++ b/source/blender/sequencer/intern/thumbnail_cache.cc @@ -19,6 +19,7 @@ #include "DNA_scene_types.h" #include "DNA_sequence_types.h" +#include "IMB_anim.hh" #include "IMB_imbuf.hh" #include "SEQ_render.hh" diff --git a/source/blender/sequencer/intern/utils.cc b/source/blender/sequencer/intern/utils.cc index 43aaad9a448..9b96c3cc100 100644 --- a/source/blender/sequencer/intern/utils.cc +++ b/source/blender/sequencer/intern/utils.cc @@ -37,6 +37,7 @@ #include "SEQ_time.hh" #include "SEQ_utils.hh" +#include "IMB_anim.hh" #include "IMB_imbuf.hh" #include "IMB_imbuf_types.hh" diff --git a/source/blender/windowmanager/CMakeLists.txt b/source/blender/windowmanager/CMakeLists.txt index 26229886bdc..35dea7f85d1 100644 --- a/source/blender/windowmanager/CMakeLists.txt +++ b/source/blender/windowmanager/CMakeLists.txt @@ -15,6 +15,7 @@ set(INC ../draw ../gpu ../imbuf + ../imbuf/movie ../makesrna ../nodes ../render diff --git a/source/blender/windowmanager/intern/wm_playanim.cc b/source/blender/windowmanager/intern/wm_playanim.cc index 9fd8320df22..fb09f6debad 100644 --- a/source/blender/windowmanager/intern/wm_playanim.cc +++ b/source/blender/windowmanager/intern/wm_playanim.cc @@ -40,6 +40,7 @@ #include "BLI_time.h" #include "BLI_utildefines.h" +#include "IMB_anim.hh" #include "IMB_colormanagement.hh" #include "IMB_imbuf.hh" #include "IMB_imbuf_types.hh" @@ -1717,9 +1718,7 @@ static bool wm_main_playanim_intern(int argc, const char **argv, PlayArgs *args_ ps.font_id = -1; IMB_init(); -#ifdef WITH_FFMPEG IMB_ffmpeg_init(); -#endif STRNCPY(ps.display_ctx.display_settings.display_device, IMB_colormanagement_role_colorspace_name_get(COLOR_ROLE_DEFAULT_BYTE)); diff --git a/source/creator/CMakeLists.txt b/source/creator/CMakeLists.txt index 452b90c2a83..3f1658b2bff 100644 --- a/source/creator/CMakeLists.txt +++ b/source/creator/CMakeLists.txt @@ -7,6 +7,7 @@ set(INC ../blender/editors/include ../blender/gpu ../blender/imbuf + ../blender/imbuf/movie ../blender/io/usd ../blender/bmesh ../blender/makesrna diff --git a/source/creator/creator.cc b/source/creator/creator.cc index 053d94a3dda..5c7f742ea89 100644 --- a/source/creator/creator.cc +++ b/source/creator/creator.cc @@ -59,6 +59,7 @@ #include "DEG_depsgraph.hh" +#include "IMB_anim.hh" #include "IMB_imbuf.hh" /* For #IMB_init. */ #include "RE_engine.h" @@ -495,10 +496,8 @@ int main(int argc, /* Must be initialized after #BKE_appdir_init to account for color-management paths. */ IMB_init(); -#ifdef WITH_FFMPEG /* Keep after #ARG_PASS_SETTINGS since debug flags are checked. */ IMB_ffmpeg_init(); -#endif /* After #ARG_PASS_SETTINGS arguments, this is so #WM_main_playanim skips #RNA_init. */ RNA_init();