Blender V5.0
movie_read.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
2 * SPDX-FileCopyrightText: 2024-2025 Blender Authors
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later */
5
9
10#include <algorithm>
11#include <cctype>
12#include <climits>
13#include <cmath>
14#include <cstdio>
15#include <cstdlib>
16#include <sys/types.h>
17
18#include "BLI_path_utils.hh"
19#include "BLI_string.h"
20#include "BLI_string_utf8.h"
21#include "BLI_task.hh"
22#include "BLI_utildefines.h"
23
24#include "DNA_scene_types.h"
25
26#include "MEM_guardedalloc.h"
27
29#include "IMB_imbuf.hh"
30#include "IMB_imbuf_types.hh"
31
32#include "CLG_log.h"
33
34#include "MOV_read.hh"
35
36#include "IMB_metadata.hh"
38#include "movie_read.hh"
39
40#ifdef WITH_FFMPEG
41# include "ffmpeg_swscale.hh"
42# include "movie_util.hh"
43
44extern "C" {
45# include <libavcodec/avcodec.h>
46# include <libavformat/avformat.h>
47# include <libavutil/imgutils.h>
48# include <libavutil/rational.h>
49# include <libswscale/swscale.h>
50
51# include "ffmpeg_compat.h"
52}
53
54#endif /* WITH_FFMPEG */
55
56#ifdef WITH_FFMPEG
57static CLG_LogRef LOG = {"video.read"};
58#endif
59
60#ifdef WITH_FFMPEG
61static void free_anim_ffmpeg(MovieReader *anim);
62#endif
63
64static bool anim_getnew(MovieReader *anim);
65
67{
68 if (anim == nullptr) {
69 return;
70 }
71
72#ifdef WITH_FFMPEG
73 free_anim_ffmpeg(anim);
74#endif
77
78 MEM_delete(anim);
79}
80
81void MOV_get_filename(const MovieReader *anim, char *filename, int filename_maxncpy)
82{
83 BLI_path_split_file_part(anim->filepath, filename, filename_maxncpy);
84}
85
87{
88 if (anim->state == MovieReader::State::Valid) {
89#ifdef WITH_FFMPEG
90 BLI_assert(anim->pFormatCtx != nullptr);
91 av_log(anim->pFormatCtx, AV_LOG_DEBUG, "METADATA FETCH\n");
92
93 AVDictionaryEntry *entry = nullptr;
94 while (true) {
95 entry = av_dict_get(anim->pFormatCtx->metadata, "", entry, AV_DICT_IGNORE_SUFFIX);
96 if (entry == nullptr) {
97 break;
98 }
99
100 /* Delay creation of the property group until there is actual metadata to put in there. */
102 IMB_metadata_set_field(anim->metadata, entry->key, entry->value);
103 }
104#endif
105 }
106 return anim->metadata;
107}
108
109static void probe_video_colorspace(MovieReader *anim, char r_colorspace_name[IM_MAX_SPACE])
110{
111 /* Use default role as fallback (i.e. it is an unknown combination of colorspace and primaries)
112 */
113 BLI_strncpy_utf8(r_colorspace_name,
116
118 if (!anim_getnew(anim)) {
119 return;
120 }
121 }
122
123#ifdef WITH_FFMPEG
124 /* Note that the ffmpeg enums are documented to match CICP codes. */
125 const int cicp[4] = {anim->pCodecCtx->color_primaries,
126 anim->pCodecCtx->color_trc,
127 anim->pCodecCtx->colorspace,
128 anim->pCodecCtx->color_range};
131
132 if (colorspace == nullptr) {
133 return;
134 }
135
137 r_colorspace_name, IMB_colormanagement_colorspace_get_name(colorspace), IM_MAX_SPACE);
138#endif /* WITH_FFMPEG */
139}
140
141MovieReader *MOV_open_file(const char *filepath,
142 const int ib_flags,
143 const int streamindex,
144 const bool keep_original_colorspace,
145 char colorspace[IM_MAX_SPACE])
146{
147 MovieReader *anim;
148
149 BLI_assert(!BLI_path_is_rel(filepath));
150
151 anim = MEM_new<MovieReader>("anim struct");
152 if (anim != nullptr) {
153
154 STRNCPY(anim->filepath, filepath);
155 anim->ib_flags = ib_flags;
156 anim->streamindex = streamindex;
157 anim->keep_original_colorspace = keep_original_colorspace;
158
159 if (colorspace && colorspace[0] != '\0') {
160 /* Use colorspace from argument, if provided. */
161 STRNCPY_UTF8(anim->colorspace, colorspace);
162 }
163 else {
164 /* Try to initialize colorspace from the FFmpeg stream by interpreting color information from
165 * it. */
166 char file_colorspace[IM_MAX_SPACE];
167 probe_video_colorspace(anim, file_colorspace);
168 STRNCPY_UTF8(anim->colorspace, file_colorspace);
169 if (colorspace) {
170 /* Copy the used colorspace into output argument. */
171 BLI_strncpy_utf8(colorspace, file_colorspace, IM_MAX_SPACE);
172 }
173 }
174 }
175 return anim;
176}
177
179{
180#if !defined(WITH_FFMPEG)
181 UNUSED_VARS(anim);
182#endif
183
184#ifdef WITH_FFMPEG
185 if (anim->pCodecCtx != nullptr) {
186 return true;
187 }
188#endif
189 return false;
190}
191
192void MOV_set_multiview_suffix(MovieReader *anim, const char *suffix)
193{
194 STRNCPY(anim->suffix, suffix);
195}
196
197#ifdef WITH_FFMPEG
198
199static double ffmpeg_stream_start_time_get(const AVStream *stream)
200{
201 if (stream->start_time == AV_NOPTS_VALUE) {
202 return 0.0;
203 }
204
205 return stream->start_time * av_q2d(stream->time_base);
206}
207
208static int ffmpeg_container_frame_count_get(const AVFormatContext *pFormatCtx,
209 const AVStream *video_stream,
210 const double frame_rate)
211{
212 /* Find audio stream to guess the duration of the video.
213 * Sometimes the audio AND the video stream have a start offset.
214 * The difference between these is the offset we want to use to
215 * calculate the video duration.
216 */
217 const double video_start = ffmpeg_stream_start_time_get(video_stream);
218 double audio_start = 0;
219
220 for (int i = 0; i < pFormatCtx->nb_streams; i++) {
221 if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
222 AVStream *audio_stream = pFormatCtx->streams[i];
223 audio_start = ffmpeg_stream_start_time_get(audio_stream);
224 break;
225 }
226 }
227
228 double stream_dur;
229
230 if (video_start > audio_start) {
231 stream_dur = double(pFormatCtx->duration) / AV_TIME_BASE - (video_start - audio_start);
232 }
233 else {
234 /* The video stream starts before or at the same time as the audio stream!
235 * We have to assume that the video stream is as long as the full pFormatCtx->duration.
236 */
237 stream_dur = double(pFormatCtx->duration) / AV_TIME_BASE;
238 }
239
240 return lround(stream_dur * frame_rate);
241}
242
243static int ffmpeg_frame_count_get(const AVFormatContext *pFormatCtx,
244 const AVStream *video_stream,
245 const double frame_rate)
246{
247 /* Use stream duration to determine frame count. */
248 if (video_stream->duration != AV_NOPTS_VALUE) {
249 const double stream_dur = video_stream->duration * av_q2d(video_stream->time_base);
250 return lround(stream_dur * frame_rate);
251 }
252
253 /* Fall back to manually estimating the video stream duration.
254 * This is because the video stream duration can be shorter than the `pFormatCtx->duration`.
255 */
256 if (pFormatCtx->duration != AV_NOPTS_VALUE) {
257 return ffmpeg_container_frame_count_get(pFormatCtx, video_stream, frame_rate);
258 }
259
260 /* Read frame count from the stream if we can. Note, that this value can not be trusted. */
261 if (video_stream->nb_frames != 0) {
262 return video_stream->nb_frames;
263 }
264
265 /* The duration has not been set, happens for single JPEG2000 images.
266 * NOTE: Leave the duration zeroed, although it could set to 1 so the file is recognized
267 * as a movie with 1 frame, leave as-is since image loading code-paths are preferred
268 * in this case. The following assertion should be valid in this case. */
269 BLI_assert(pFormatCtx->duration == AV_NOPTS_VALUE);
270 return 0;
271}
272
273static int calc_pix_fmt_max_component_bits(AVPixelFormat fmt)
274{
275 const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(fmt);
276 if (desc == nullptr) {
277 return 0;
278 }
279 int bits = 0;
280 for (int i = 0; i < desc->nb_components; i++) {
281 bits = max_ii(bits, desc->comp[i].depth);
282 }
283 return bits;
284}
285
286static AVFormatContext *init_format_context(const char *filepath,
287 int video_stream_index,
288 int &r_stream_index,
289 const AVCodec *forced_video_decoder)
290{
291 AVFormatContext *format_ctx = nullptr;
292 if (forced_video_decoder != nullptr) {
293 format_ctx = avformat_alloc_context();
294 format_ctx->video_codec_id = forced_video_decoder->id;
295 format_ctx->video_codec = forced_video_decoder;
296 }
297
298 if (avformat_open_input(&format_ctx, filepath, nullptr, nullptr) != 0) {
299 return nullptr;
300 }
301
302 if (avformat_find_stream_info(format_ctx, nullptr) < 0) {
303 avformat_close_input(&format_ctx);
304 return nullptr;
305 }
306
307 av_dump_format(format_ctx, 0, filepath, 0);
308
309 /* Find the video stream */
310 r_stream_index = -1;
311 for (int i = 0; i < format_ctx->nb_streams; i++) {
312 if (format_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
313 if (video_stream_index > 0) {
314 video_stream_index--;
315 continue;
316 }
317 r_stream_index = i;
318 break;
319 }
320 }
321
322 if (r_stream_index == -1) {
323 avformat_close_input(&format_ctx);
324 return nullptr;
325 }
326
327 return format_ctx;
328}
329
330static AVFormatContext *init_format_context_vpx_workarounds(const char *filepath,
331 int video_stream_index,
332 int &r_stream_index,
333 const AVCodec *&r_codec)
334{
335 AVFormatContext *format_ctx = init_format_context(
336 filepath, video_stream_index, r_stream_index, nullptr);
337 if (format_ctx == nullptr) {
338 return nullptr;
339 }
340
341 /* By default FFMPEG uses built-in VP8/VP9 decoders, however those do not detect
342 * alpha channel (see FFMPEG issue #8344 https://trac.ffmpeg.org/ticket/8344).
343 * The trick for VP8/VP9 is to explicitly force use of LIBVPX decoder.
344 * Only do this where alpha_mode=1 metadata is set. Note that in order to work,
345 * the previously initialized format context must be closed and a fresh one
346 * with explicitly requested codec must be created. */
347 r_codec = nullptr;
348 const AVStream *video_stream = format_ctx->streams[r_stream_index];
349 if (ELEM(video_stream->codecpar->codec_id, AV_CODEC_ID_VP8, AV_CODEC_ID_VP9)) {
350 AVDictionaryEntry *tag = nullptr;
351 tag = av_dict_get(video_stream->metadata, "alpha_mode", tag, AV_DICT_IGNORE_SUFFIX);
352 if (tag && STREQ(tag->value, "1")) {
353 r_codec = avcodec_find_decoder_by_name(
354 video_stream->codecpar->codec_id == AV_CODEC_ID_VP8 ? "libvpx" : "libvpx-vp9");
355 if (r_codec != nullptr) {
356 avformat_close_input(&format_ctx);
357 format_ctx = avformat_alloc_context();
358 format_ctx = init_format_context(filepath, video_stream_index, r_stream_index, r_codec);
359 if (format_ctx == nullptr) {
360 return nullptr;
361 }
362 }
363 }
364 }
365
366 if (r_codec == nullptr) {
367 /* Use default decoder. */
368 r_codec = avcodec_find_decoder(video_stream->codecpar->codec_id);
369 }
370
371 return format_ctx;
372}
373
374static int startffmpeg(MovieReader *anim)
375{
376 if (anim == nullptr) {
377 return -1;
378 }
379
380 int video_stream_index;
381 const AVCodec *pCodec = nullptr;
382 AVFormatContext *pFormatCtx = init_format_context_vpx_workarounds(
383 anim->filepath, anim->streamindex, video_stream_index, pCodec);
384 if (pFormatCtx == nullptr || pCodec == nullptr) {
385 avformat_close_input(&pFormatCtx);
386 return -1;
387 }
388
389 AVCodecContext *pCodecCtx = avcodec_alloc_context3(nullptr);
390 AVStream *video_stream = pFormatCtx->streams[video_stream_index];
391 avcodec_parameters_to_context(pCodecCtx, video_stream->codecpar);
392 pCodecCtx->workaround_bugs = FF_BUG_AUTODETECT;
393
394 if (pCodec->capabilities & AV_CODEC_CAP_OTHER_THREADS) {
395 pCodecCtx->thread_count = 0;
396 }
397 else {
398 pCodecCtx->thread_count = MOV_thread_count();
399 }
400
401 if (pCodec->capabilities & AV_CODEC_CAP_FRAME_THREADS) {
402 pCodecCtx->thread_type = FF_THREAD_FRAME;
403 }
404 else if (pCodec->capabilities & AV_CODEC_CAP_SLICE_THREADS) {
405 pCodecCtx->thread_type = FF_THREAD_SLICE;
406 }
407
408 if (avcodec_open2(pCodecCtx, pCodec, nullptr) < 0) {
409 avformat_close_input(&pFormatCtx);
410 return -1;
411 }
412 if (pCodecCtx->pix_fmt == AV_PIX_FMT_NONE) {
413 avcodec_free_context(&anim->pCodecCtx);
414 avformat_close_input(&pFormatCtx);
415 return -1;
416 }
417
418 /* Check if we need the "never seek, only decode one frame" ffmpeg bug workaround. */
419 const bool is_ogg_container = STREQ(pFormatCtx->iformat->name, "ogg");
420 const bool is_non_ogg_video = video_stream->codecpar->codec_id != AV_CODEC_ID_THEORA;
421 const bool is_video_thumbnail = (video_stream->disposition & AV_DISPOSITION_ATTACHED_PIC) != 0;
422 anim->never_seek_decode_one_frame = is_ogg_container && is_non_ogg_video && is_video_thumbnail;
423
424 anim->frame_rate = av_guess_frame_rate(pFormatCtx, video_stream, nullptr);
425 if (anim->never_seek_decode_one_frame) {
426 /* Files that need this workaround have nonsensical frame rates too, resulting
427 * in "millions of frames" if done through regular math. Treat frame-rate as 24/1 instead. */
428 anim->frame_rate = {24, 1};
429 }
430 int frs_num = anim->frame_rate.num;
431 double frs_den = anim->frame_rate.den;
432
433 frs_den *= AV_TIME_BASE;
434
435 while (frs_num % 10 == 0 && frs_den >= 2.0 && frs_num > 10) {
436 frs_num /= 10;
437 frs_den /= 10;
438 }
439
440 anim->frs_sec = frs_num;
441 anim->frs_sec_base = frs_den / AV_TIME_BASE;
442 /* Save the relative start time for the video. IE the start time in relation to where playback
443 * starts. */
444 anim->start_offset = ffmpeg_stream_start_time_get(video_stream);
445 anim->duration_in_frames = ffmpeg_frame_count_get(
446 pFormatCtx, video_stream, av_q2d(anim->frame_rate));
447
448 anim->x = pCodecCtx->width;
449 anim->y = pCodecCtx->height;
450 anim->video_rotation = ffmpeg_get_video_rotation(video_stream);
451
452 /* Decode >8bit videos into floating point image. */
453 anim->is_float = calc_pix_fmt_max_component_bits(pCodecCtx->pix_fmt) > 8;
454
455 anim->pFormatCtx = pFormatCtx;
456 anim->pCodecCtx = pCodecCtx;
457 anim->pCodec = pCodec;
458 anim->videoStream = video_stream_index;
459
460 anim->cur_position = 0;
461 anim->cur_pts = -1;
462 anim->cur_key_frame_pts = -1;
463 anim->cur_packet = av_packet_alloc();
464 anim->cur_packet->stream_index = -1;
465
466 anim->pFrame = av_frame_alloc();
467 anim->pFrame_backup = av_frame_alloc();
468 anim->pFrame_backup_complete = false;
469 anim->pFrame_complete = false;
470 anim->pFrameDeinterlaced = av_frame_alloc();
471 anim->pFrameRGB = av_frame_alloc();
472 /* Ideally we'd use AV_PIX_FMT_RGBAF32LE for floats, but currently (ffmpeg 6.1)
473 * swscale does not support that as destination. So using AV_PIX_FMT_GBRAPF32LE
474 * with manual interleaving to RGBA floats. */
475 anim->pFrameRGB->format = anim->is_float ? AV_PIX_FMT_GBRAPF32LE : AV_PIX_FMT_RGBA;
476 anim->pFrameRGB->width = anim->x;
477 anim->pFrameRGB->height = anim->y;
478
479 const size_t align = ffmpeg_get_buffer_alignment();
480 if (av_frame_get_buffer(anim->pFrameRGB, align) < 0) {
481 CLOG_ERROR(&LOG, "Could not allocate frame data.");
482 avcodec_free_context(&anim->pCodecCtx);
483 avformat_close_input(&anim->pFormatCtx);
484 av_packet_free(&anim->cur_packet);
485 av_frame_free(&anim->pFrameRGB);
486 av_frame_free(&anim->pFrameDeinterlaced);
487 av_frame_free(&anim->pFrame);
488 av_frame_free(&anim->pFrame_backup);
489 anim->pCodecCtx = nullptr;
490 return -1;
491 }
492
493 if (anim->ib_flags & IB_animdeinterlace) {
494 anim->pFrameDeinterlaced->format = anim->pCodecCtx->pix_fmt;
495 anim->pFrameDeinterlaced->width = anim->pCodecCtx->width;
496 anim->pFrameDeinterlaced->height = anim->pCodecCtx->height;
497 av_image_fill_arrays(
498 anim->pFrameDeinterlaced->data,
499 anim->pFrameDeinterlaced->linesize,
501 av_image_get_buffer_size(
502 anim->pCodecCtx->pix_fmt, anim->pCodecCtx->width, anim->pCodecCtx->height, 1),
503 "ffmpeg deinterlace"),
504 anim->pCodecCtx->pix_fmt,
505 anim->pCodecCtx->width,
506 anim->pCodecCtx->height,
507 1);
508 }
509
510 /* Use full_chroma_int + accurate_rnd YUV->RGB conversion flags. Otherwise
511 * the conversion is not fully accurate and introduces some banding and color
512 * shifts, particularly in dark regions. See issue #111703 or upstream
513 * ffmpeg ticket https://trac.ffmpeg.org/ticket/1582 */
514 anim->img_convert_ctx = ffmpeg_sws_get_context(anim->x,
515 anim->y,
516 anim->pCodecCtx->pix_fmt,
517 anim->pCodecCtx->color_range == AVCOL_RANGE_JPEG,
518 anim->pCodecCtx->colorspace,
519 anim->x,
520 anim->y,
521 anim->pFrameRGB->format,
522 false,
523 -1,
524 SWS_POINT | SWS_FULL_CHR_H_INT |
525 SWS_ACCURATE_RND);
526
527 if (!anim->img_convert_ctx) {
529 "ffmpeg: swscale can't transform from pixel format %s to %s (%s)",
530 av_get_pix_fmt_name(anim->pCodecCtx->pix_fmt),
531 av_get_pix_fmt_name((AVPixelFormat)anim->pFrameRGB->format),
532 anim->filepath);
533 avcodec_free_context(&anim->pCodecCtx);
534 avformat_close_input(&anim->pFormatCtx);
535 av_packet_free(&anim->cur_packet);
536 av_frame_free(&anim->pFrameRGB);
537 av_frame_free(&anim->pFrameDeinterlaced);
538 av_frame_free(&anim->pFrame);
539 av_frame_free(&anim->pFrame_backup);
540 anim->pCodecCtx = nullptr;
541 return -1;
542 }
543
544 return 0;
545}
546
547static double ffmpeg_steps_per_frame_get(const MovieReader *anim)
548{
549 const AVStream *v_st = anim->pFormatCtx->streams[anim->videoStream];
550 const AVRational time_base = v_st->time_base;
551 return av_q2d(av_inv_q(av_mul_q(anim->frame_rate, time_base)));
552}
553
554/* Store backup frame.
555 * With VFR movies, if PTS is not matched perfectly, scanning continues to look for next PTS.
556 * It is likely to overshoot and scanning stops. Having previous frame backed up, it is possible
557 * to use it when overshoot happens.
558 */
559static void ffmpeg_double_buffer_backup_frame_store(MovieReader *anim, int64_t pts_to_search)
560{
561 /* `anim->pFrame` is beyond `pts_to_search`. Don't store it. */
562 if (anim->pFrame_backup_complete && anim->cur_pts >= pts_to_search) {
563 return;
564 }
565 if (!anim->pFrame_complete) {
566 return;
567 }
568
569 if (anim->pFrame_backup_complete) {
570 av_frame_unref(anim->pFrame_backup);
571 }
572
573 av_frame_move_ref(anim->pFrame_backup, anim->pFrame);
574 anim->pFrame_backup_complete = true;
575}
576
577/* Free stored backup frame. */
578static void ffmpeg_double_buffer_backup_frame_clear(MovieReader *anim)
579{
580 if (anim->pFrame_backup_complete) {
581 av_frame_unref(anim->pFrame_backup);
582 }
583 anim->pFrame_backup_complete = false;
584}
585
586/* Return recently decoded frame. If it does not exist, return frame from backup buffer. */
587static AVFrame *ffmpeg_double_buffer_frame_fallback_get(MovieReader *anim)
588{
589 av_log(anim->pFormatCtx, AV_LOG_ERROR, "DECODE UNHAPPY: PTS not matched!\n");
590
591 if (anim->pFrame_complete) {
592 return anim->pFrame;
593 }
594 if (anim->pFrame_backup_complete) {
595 return anim->pFrame_backup;
596 }
597 return nullptr;
598}
599
600/* Convert from ffmpeg planar GBRA layout to ImBuf interleaved RGBA, applying
601 * video rotation in the same go if needed. */
602static void float_planar_to_interleaved(const AVFrame *frame, const int rotation, ImBuf *ibuf)
603{
604 using namespace blender;
605 const size_t src_linesize = frame->linesize[0];
606 BLI_assert_msg(frame->linesize[1] == src_linesize && frame->linesize[2] == src_linesize &&
607 frame->linesize[3] == src_linesize,
608 "ffmpeg frame should be 4 same size planes for a floating point image case");
609 threading::parallel_for(IndexRange(ibuf->y), 256, [&](const IndexRange y_range) {
610 const int size_x = ibuf->x;
611 const int size_y = ibuf->y;
612 if (rotation == 90) {
613 /* 90 degree rotation. */
614 for (const int64_t y : y_range) {
615 int64_t src_offset = src_linesize * (size_y - y - 1);
616 const float *src_g = reinterpret_cast<const float *>(frame->data[0] + src_offset);
617 const float *src_b = reinterpret_cast<const float *>(frame->data[1] + src_offset);
618 const float *src_r = reinterpret_cast<const float *>(frame->data[2] + src_offset);
619 const float *src_a = reinterpret_cast<const float *>(frame->data[3] + src_offset);
620 float *dst = ibuf->float_buffer.data + (y + (size_x - 1) * size_y) * 4;
621 for (int x = 0; x < size_x; x++) {
622 dst[0] = *src_r++;
623 dst[1] = *src_g++;
624 dst[2] = *src_b++;
625 dst[3] = *src_a++;
626 dst -= size_y * 4;
627 }
628 }
629 }
630 else if (rotation == 180) {
631 /* 180 degree rotation. */
632 for (const int64_t y : y_range) {
633 int64_t src_offset = src_linesize * (size_y - y - 1);
634 const float *src_g = reinterpret_cast<const float *>(frame->data[0] + src_offset);
635 const float *src_b = reinterpret_cast<const float *>(frame->data[1] + src_offset);
636 const float *src_r = reinterpret_cast<const float *>(frame->data[2] + src_offset);
637 const float *src_a = reinterpret_cast<const float *>(frame->data[3] + src_offset);
638 float *dst = ibuf->float_buffer.data + ((size_y - y - 1) * size_x + size_x - 1) * 4;
639 for (int x = 0; x < size_x; x++) {
640 dst[0] = *src_r++;
641 dst[1] = *src_g++;
642 dst[2] = *src_b++;
643 dst[3] = *src_a++;
644 dst -= 4;
645 }
646 }
647 }
648 else if (rotation == 270) {
649 /* 270 degree rotation. */
650 for (const int64_t y : y_range) {
651 int64_t src_offset = src_linesize * (size_y - y - 1);
652 const float *src_g = reinterpret_cast<const float *>(frame->data[0] + src_offset);
653 const float *src_b = reinterpret_cast<const float *>(frame->data[1] + src_offset);
654 const float *src_r = reinterpret_cast<const float *>(frame->data[2] + src_offset);
655 const float *src_a = reinterpret_cast<const float *>(frame->data[3] + src_offset);
656 float *dst = ibuf->float_buffer.data + (size_y - y - 1) * 4;
657 for (int x = 0; x < size_x; x++) {
658 dst[0] = *src_r++;
659 dst[1] = *src_g++;
660 dst[2] = *src_b++;
661 dst[3] = *src_a++;
662 dst += size_y * 4;
663 }
664 }
665 }
666 else if (rotation == 0) {
667 /* No rotation. */
668 for (const int64_t y : y_range) {
669 int64_t src_offset = src_linesize * (size_y - y - 1);
670 const float *src_g = reinterpret_cast<const float *>(frame->data[0] + src_offset);
671 const float *src_b = reinterpret_cast<const float *>(frame->data[1] + src_offset);
672 const float *src_r = reinterpret_cast<const float *>(frame->data[2] + src_offset);
673 const float *src_a = reinterpret_cast<const float *>(frame->data[3] + src_offset);
674 float *dst = ibuf->float_buffer.data + size_x * y * 4;
675 for (int x = 0; x < size_x; x++) {
676 *dst++ = *src_r++;
677 *dst++ = *src_g++;
678 *dst++ = *src_b++;
679 *dst++ = *src_a++;
680 }
681 }
682 }
683 });
684
685 if (ELEM(rotation, 90, 270)) {
686 std::swap(ibuf->x, ibuf->y);
687 }
688}
689
695static void ffmpeg_postprocess(MovieReader *anim, AVFrame *input, ImBuf *ibuf)
696{
697 int filter_y = 0;
698
699 /* This means the data wasn't read properly,
700 * this check stops crashing */
701 if (input->data[0] == nullptr && input->data[1] == nullptr && input->data[2] == nullptr &&
702 input->data[3] == nullptr)
703 {
705 "ffmpeg_fetchibuf: "
706 "data not read properly...");
707 return;
708 }
709
710 av_log(anim->pFormatCtx,
711 AV_LOG_DEBUG,
712 " POSTPROC: AVFrame planes: %p %p %p %p\n",
713 input->data[0],
714 input->data[1],
715 input->data[2],
716 input->data[3]);
717
718 if (anim->ib_flags & IB_animdeinterlace) {
719 if (ffmpeg_deinterlace(anim->pFrameDeinterlaced,
720 anim->pFrame,
721 anim->pCodecCtx->pix_fmt,
722 anim->pCodecCtx->width,
723 anim->pCodecCtx->height) < 0)
724 {
725 filter_y = true;
726 }
727 else {
728 input = anim->pFrameDeinterlaced;
729 }
730 }
731
732 bool already_rotated = false;
733 if (anim->is_float) {
734 /* Float images are converted into planar GBRA layout by swscale (since
735 * it does not support direct YUV->RGBA float interleaved conversion).
736 * Do vertical flip and interleave into RGBA manually. */
737 /* Decode, then do vertical flip into destination. */
738 ffmpeg_sws_scale_frame(anim->img_convert_ctx, anim->pFrameRGB, input);
739
740 float_planar_to_interleaved(anim->pFrameRGB, anim->video_rotation, ibuf);
741 already_rotated = true;
742 }
743 else {
744 /* If final destination image layout matches that of decoded RGB frame (including
745 * any line padding done by ffmpeg for SIMD alignment), we can directly
746 * decode into that, doing the vertical flip in the same step. Otherwise have
747 * to do a separate flip. */
748 const int ibuf_linesize = ibuf->x * 4;
749 const int rgb_linesize = anim->pFrameRGB->linesize[0];
750 bool scale_to_ibuf = (rgb_linesize == ibuf_linesize);
751 /* swscale on arm64 before ffmpeg 6.0 (libswscale major version 7)
752 * could not handle negative line sizes. That has been fixed in all major
753 * ffmpeg releases in early 2023, but easier to just check for "below 7". */
754# if (defined(__aarch64__) || defined(_M_ARM64)) && (LIBSWSCALE_VERSION_MAJOR < 7)
755 scale_to_ibuf = false;
756# endif
757 uint8_t *rgb_data = anim->pFrameRGB->data[0];
758
759 if (scale_to_ibuf) {
760 /* Decode RGB and do vertical flip directly into destination image, by using negative
761 * line size. */
762 anim->pFrameRGB->linesize[0] = -ibuf_linesize;
763 anim->pFrameRGB->data[0] = ibuf->byte_buffer.data + (ibuf->y - 1) * ibuf_linesize;
764
765 ffmpeg_sws_scale_frame(anim->img_convert_ctx, anim->pFrameRGB, input);
766
767 anim->pFrameRGB->linesize[0] = rgb_linesize;
768 anim->pFrameRGB->data[0] = rgb_data;
769 }
770 else {
771 /* Decode, then do vertical flip into destination. */
772 ffmpeg_sws_scale_frame(anim->img_convert_ctx, anim->pFrameRGB, input);
773
774 /* Use negative line size to do vertical image flip. */
775 const int src_linesize[4] = {-rgb_linesize, 0, 0, 0};
776 const uint8_t *const src[4] = {
777 rgb_data + (anim->y - 1) * rgb_linesize, nullptr, nullptr, nullptr};
778 int dst_size = av_image_get_buffer_size(AVPixelFormat(anim->pFrameRGB->format),
779 anim->pFrameRGB->width,
780 anim->pFrameRGB->height,
781 1);
782 av_image_copy_to_buffer(ibuf->byte_buffer.data,
783 dst_size,
784 src,
785 src_linesize,
786 AVPixelFormat(anim->pFrameRGB->format),
787 anim->x,
788 anim->y,
789 1);
790 }
791 }
792
793 if (filter_y) {
794 IMB_filtery(ibuf);
795 }
796
797 /* Rotate video if display matrix is multiple of 90 degrees. */
798 if (!already_rotated && ELEM(anim->video_rotation, 90, 180, 270)) {
800 }
801}
802
803static void final_frame_log(MovieReader *anim,
804 int64_t frame_pts_start,
805 int64_t frame_pts_end,
806 const char *str)
807{
808 av_log(anim->pFormatCtx,
809 AV_LOG_INFO,
810 "DECODE HAPPY: %s frame PTS range %" PRId64 " - %" PRId64 ".\n",
811 str,
812 frame_pts_start,
813 frame_pts_end);
814}
815
816static bool ffmpeg_pts_isect(int64_t pts_start, int64_t pts_end, int64_t pts_to_search)
817{
818 return pts_start <= pts_to_search && pts_to_search < pts_end;
819}
820
821/* Return frame that matches `pts_to_search`, nullptr if matching frame does not exist. */
822static AVFrame *ffmpeg_frame_by_pts_get(MovieReader *anim, int64_t pts_to_search)
823{
824 /* NOTE: `frame->pts + frame->pkt_duration` does not always match pts of next frame.
825 * See footage from #86361. Here it is OK to use, because PTS must match current or backup frame.
826 * If there is no current frame, return nullptr.
827 */
828 if (!anim->pFrame_complete) {
829 return nullptr;
830 }
831
832 if (anim->never_seek_decode_one_frame) {
833 /* If we only decode one frame, return it. */
834 return anim->pFrame;
835 }
836
837 const bool backup_frame_ready = anim->pFrame_backup_complete;
838 const int64_t recent_start = av_get_pts_from_frame(anim->pFrame);
839 const int64_t recent_end = recent_start + av_get_frame_duration_in_pts_units(anim->pFrame);
840 const int64_t backup_start = backup_frame_ready ? av_get_pts_from_frame(anim->pFrame_backup) : 0;
841
842 AVFrame *best_frame = nullptr;
843 if (ffmpeg_pts_isect(recent_start, recent_end, pts_to_search)) {
844 final_frame_log(anim, recent_start, recent_end, "Recent");
845 best_frame = anim->pFrame;
846 }
847 else if (backup_frame_ready && ffmpeg_pts_isect(backup_start, recent_start, pts_to_search)) {
848 final_frame_log(anim, backup_start, recent_start, "Backup");
849 best_frame = anim->pFrame_backup;
850 }
851 return best_frame;
852}
853
854static void ffmpeg_decode_store_frame_pts(MovieReader *anim)
855{
856 anim->cur_pts = av_get_pts_from_frame(anim->pFrame);
857
858# ifdef FFMPEG_OLD_KEY_FRAME_QUERY_METHOD
859 if (anim->pFrame->key_frame)
860# else
861 if (anim->pFrame->flags & AV_FRAME_FLAG_KEY)
862# endif
863 {
864 anim->cur_key_frame_pts = anim->cur_pts;
865 }
866
867 av_log(anim->pFormatCtx,
868 AV_LOG_DEBUG,
869 " FRAME DONE: cur_pts=%" PRId64 ", guessed_pts=%" PRId64 "\n",
870 av_get_pts_from_frame(anim->pFrame),
871 int64_t(anim->cur_pts));
872}
873
874static int ffmpeg_read_video_frame(MovieReader *anim, AVPacket *packet)
875{
876 int ret = 0;
877 while ((ret = av_read_frame(anim->pFormatCtx, packet)) >= 0) {
878 if (packet->stream_index == anim->videoStream) {
879 break;
880 }
881 av_packet_unref(packet);
882 packet->stream_index = -1;
883 }
884
885 return ret;
886}
887
888/* decode one video frame also considering the packet read into cur_packet */
889static int ffmpeg_decode_video_frame(MovieReader *anim)
890{
891 av_log(anim->pFormatCtx, AV_LOG_DEBUG, " DECODE VIDEO FRAME\n");
892
893 /* Sometimes, decoder returns more than one frame per sent packet. Check if frames are available.
894 * This frames must be read, otherwise decoding will fail. See #91405. */
895 anim->pFrame_complete = avcodec_receive_frame(anim->pCodecCtx, anim->pFrame) == 0;
896 if (anim->pFrame_complete) {
897 av_log(anim->pFormatCtx, AV_LOG_DEBUG, " DECODE FROM CODEC BUFFER\n");
898 ffmpeg_decode_store_frame_pts(anim);
899 return 1;
900 }
901
902 int rval = 0;
903 if (anim->cur_packet->stream_index == anim->videoStream) {
904 av_packet_unref(anim->cur_packet);
905 anim->cur_packet->stream_index = -1;
906 }
907
908 while ((rval = ffmpeg_read_video_frame(anim, anim->cur_packet)) >= 0) {
909 if (anim->cur_packet->stream_index != anim->videoStream) {
910 continue;
911 }
912
913 av_log(anim->pFormatCtx,
914 AV_LOG_DEBUG,
915 "READ: strID=%d dts=%" PRId64 " pts=%" PRId64 " %s\n",
916 anim->cur_packet->stream_index,
917 (anim->cur_packet->dts == AV_NOPTS_VALUE) ? -1 : int64_t(anim->cur_packet->dts),
918 (anim->cur_packet->pts == AV_NOPTS_VALUE) ? -1 : int64_t(anim->cur_packet->pts),
919 (anim->cur_packet->flags & AV_PKT_FLAG_KEY) ? " KEY" : "");
920
921 avcodec_send_packet(anim->pCodecCtx, anim->cur_packet);
922 anim->pFrame_complete = avcodec_receive_frame(anim->pCodecCtx, anim->pFrame) == 0;
923
924 if (anim->pFrame_complete) {
925 ffmpeg_decode_store_frame_pts(anim);
926 break;
927 }
928 av_packet_unref(anim->cur_packet);
929 anim->cur_packet->stream_index = -1;
930 }
931
932 if (rval == AVERROR_EOF) {
933 /* Flush any remaining frames out of the decoder. */
934 avcodec_send_packet(anim->pCodecCtx, nullptr);
935 anim->pFrame_complete = avcodec_receive_frame(anim->pCodecCtx, anim->pFrame) == 0;
936
937 if (anim->pFrame_complete) {
938 ffmpeg_decode_store_frame_pts(anim);
939 rval = 0;
940 }
941 }
942
943 if (rval < 0) {
944 av_packet_unref(anim->cur_packet);
945 anim->cur_packet->stream_index = -1;
946
947 char error_str[AV_ERROR_MAX_STRING_SIZE];
948 av_make_error_string(error_str, AV_ERROR_MAX_STRING_SIZE, rval);
949
950 av_log(anim->pFormatCtx,
951 AV_LOG_ERROR,
952 " DECODE READ FAILED: av_read_frame() "
953 "returned error: %s\n",
954 error_str);
955 }
956
957 return (rval >= 0);
958}
959
960static int64_t ffmpeg_get_seek_pts(MovieReader *anim, int64_t pts_to_search)
961{
962 /* FFMPEG seeks internally using DTS values instead of PTS. In some files DTS and PTS values are
963 * offset and sometimes FFMPEG fails to take this into account when seeking.
964 * Therefore we need to seek backwards a certain offset to make sure the frame we want is in
965 * front of us. It is not possible to determine the exact needed offset,
966 * this value is determined experimentally.
967 * NOTE: Too big offset can impact performance. Current 3 frame offset has no measurable impact.
968 */
969 int64_t seek_pts = pts_to_search - (ffmpeg_steps_per_frame_get(anim) * 3);
970
971 seek_pts = std::max<int64_t>(seek_pts, 0);
972 return seek_pts;
973}
974
975/* This gives us an estimate of which pts our requested frame will have.
976 * Note that this might be off a bit in certain video files, but it should still be close enough.
977 */
978static int64_t ffmpeg_get_pts_to_search(MovieReader *anim,
979 const MovieIndex *tc_index,
980 int position)
981{
982 int64_t pts_to_search;
983
984 if (tc_index) {
985 int new_frame_index = tc_index->get_frame_index(position);
986 pts_to_search = tc_index->get_pts(new_frame_index);
987 }
988 else {
989 AVStream *v_st = anim->pFormatCtx->streams[anim->videoStream];
990 int64_t start_pts = v_st->start_time;
991
992 pts_to_search = round(position * ffmpeg_steps_per_frame_get(anim));
993
994 if (start_pts != AV_NOPTS_VALUE) {
995 pts_to_search += start_pts;
996 }
997 }
998 return pts_to_search;
999}
1000
1001static bool ffmpeg_is_first_frame_decode(MovieReader *anim)
1002{
1003 return anim->pFrame_complete == false;
1004}
1005
1006static void ffmpeg_scan_log(MovieReader *anim, int64_t pts_to_search)
1007{
1008 int64_t frame_pts_start = av_get_pts_from_frame(anim->pFrame);
1009 int64_t frame_pts_end = frame_pts_start + av_get_frame_duration_in_pts_units(anim->pFrame);
1010 av_log(anim->pFormatCtx,
1011 AV_LOG_DEBUG,
1012 " SCAN WHILE: PTS range %" PRId64 " - %" PRId64 " in search of %" PRId64 "\n",
1013 frame_pts_start,
1014 frame_pts_end,
1015 pts_to_search);
1016}
1017
1018/* Decode frames one by one until its PTS matches pts_to_search. */
1019static void ffmpeg_decode_video_frame_scan(MovieReader *anim, int64_t pts_to_search)
1020{
1021 const int64_t start_gop_frame = anim->cur_key_frame_pts;
1022 bool decode_error = false;
1023
1024 while (!decode_error && anim->cur_pts < pts_to_search) {
1025 ffmpeg_scan_log(anim, pts_to_search);
1026 ffmpeg_double_buffer_backup_frame_store(anim, pts_to_search);
1027 decode_error = ffmpeg_decode_video_frame(anim) < 1;
1028
1029 /* We should not get a new GOP keyframe while scanning if seeking is working as intended.
1030 * If this condition triggers, there may be and error in our seeking code.
1031 * NOTE: This seems to happen if DTS value is used for seeking in ffmpeg internally. There
1032 * seems to be no good way to handle such case. */
1033 if (anim->seek_before_decode && start_gop_frame != anim->cur_key_frame_pts) {
1034 av_log(anim->pFormatCtx, AV_LOG_ERROR, "SCAN: Frame belongs to an unexpected GOP!\n");
1035 }
1036 }
1037}
1038
1039/* Wrapper over av_seek_frame(), for formats that doesn't have its own read_seek() or
1040 * read_seek2() functions defined. When seeking in these formats, rule to seek to last
1041 * necessary I-frame is not honored. It is not even guaranteed that I-frame, that must be
1042 * decoded will be read. See https://trac.ffmpeg.org/ticket/1607 & #86944. */
1043static int ffmpeg_generic_seek_workaround(MovieReader *anim,
1044 int64_t *requested_pts,
1045 int64_t pts_to_search)
1046{
1047 AVStream *v_st = anim->pFormatCtx->streams[anim->videoStream];
1048 int64_t start_pts = v_st->start_time;
1049 int64_t current_pts = *requested_pts;
1050 int64_t offset = 0;
1051
1052 int64_t cur_pts;
1053 // int64_t prev_pts = -1; /* UNUSED. */
1054
1055 /* Step backward frame by frame until we find the key frame we are looking for. */
1056 while (current_pts != 0) {
1057 current_pts = *requested_pts - int64_t(round(offset * ffmpeg_steps_per_frame_get(anim)));
1058 current_pts = std::max(current_pts, int64_t(0));
1059
1060 /* Seek to timestamp. */
1061 if (av_seek_frame(anim->pFormatCtx, anim->videoStream, current_pts, AVSEEK_FLAG_BACKWARD) < 0)
1062 {
1063 break;
1064 }
1065
1066 /* Read first video stream packet. */
1067 AVPacket *read_packet = av_packet_alloc();
1068 while (av_read_frame(anim->pFormatCtx, read_packet) >= 0) {
1069 if (read_packet->stream_index == anim->videoStream) {
1070 break;
1071 }
1072 av_packet_unref(read_packet);
1073 }
1074
1075 /* If this packet contains an I-frame, this could be the frame that we need. */
1076 bool is_key_frame = read_packet->flags & AV_PKT_FLAG_KEY;
1077 /* We need to check the packet timestamp as the key frame could be for a GOP forward in the
1078 * video stream. So if it has a larger timestamp than the frame we want, ignore it.
1079 */
1080 cur_pts = timestamp_from_pts_or_dts(read_packet->pts, read_packet->dts);
1081 av_packet_free(&read_packet);
1082
1083 if (is_key_frame) {
1084 if (cur_pts <= pts_to_search) {
1085 /* We found the I-frame we were looking for! */
1086 break;
1087 }
1088 }
1089
1090 /* We have hit the beginning of the stream. */
1091 if (cur_pts <= start_pts) {
1092 break;
1093 }
1094
1095 // prev_pts = cur_pts; /* UNUSED. */
1096 offset++;
1097 }
1098
1099 *requested_pts = current_pts;
1100
1101 /* Re-seek to timestamp that gave I-frame, so it can be read by decode function. */
1102 return av_seek_frame(anim->pFormatCtx, anim->videoStream, current_pts, AVSEEK_FLAG_BACKWARD);
1103}
1104
1105/* Read packet until timestamp matches `anim->cur_packet`, thus recovering internal `anim` stream
1106 * position state. */
1107static void ffmpeg_seek_recover_stream_position(MovieReader *anim)
1108{
1109 AVPacket *temp_packet = av_packet_alloc();
1110 while (ffmpeg_read_video_frame(anim, temp_packet) >= 0) {
1111 int64_t current_pts = timestamp_from_pts_or_dts(anim->cur_packet->pts, anim->cur_packet->dts);
1112 int64_t temp_pts = timestamp_from_pts_or_dts(temp_packet->pts, temp_packet->dts);
1113 av_packet_unref(temp_packet);
1114
1115 if (current_pts == temp_pts) {
1116 break;
1117 }
1118 }
1119 av_packet_free(&temp_packet);
1120}
1121
1122/* Check if seeking and mainly flushing codec buffers is needed. */
1123static bool ffmpeg_seek_buffers_need_flushing(MovieReader *anim, int position, int64_t seek_pos)
1124{
1125 /* Get timestamp of packet read after seeking. */
1126 AVPacket *temp_packet = av_packet_alloc();
1127 ffmpeg_read_video_frame(anim, temp_packet);
1128 int64_t gop_pts = timestamp_from_pts_or_dts(temp_packet->pts, temp_packet->dts);
1129 av_packet_unref(temp_packet);
1130 av_packet_free(&temp_packet);
1131
1132 /* Seeking gives packet, that is currently read. No seeking was necessary, so buffers don't have
1133 * to be flushed. */
1134 if (gop_pts == timestamp_from_pts_or_dts(anim->cur_packet->pts, anim->cur_packet->dts)) {
1135 return false;
1136 }
1137
1138 /* Packet after seeking is same key frame as current, and further in time. No seeking was
1139 * necessary, so buffers don't have to be flushed. But stream position has to be recovered. */
1140 if (gop_pts == anim->cur_key_frame_pts && position > anim->cur_position) {
1141 ffmpeg_seek_recover_stream_position(anim);
1142 return false;
1143 }
1144
1145 /* Seeking was necessary, but we have read packets. Therefore we must seek again. */
1146 av_seek_frame(anim->pFormatCtx, anim->videoStream, seek_pos, AVSEEK_FLAG_BACKWARD);
1147 anim->cur_key_frame_pts = gop_pts;
1148 return true;
1149}
1150
1151/* Seek to last necessary key frame. */
1152static int ffmpeg_seek_to_key_frame(MovieReader *anim,
1153 int position,
1154 const MovieIndex *tc_index,
1155 int64_t pts_to_search)
1156{
1157 int64_t seek_pos;
1158 int ret;
1159
1160 if (tc_index) {
1161 /* We can use timestamps generated from our indexer to seek. */
1162 int new_frame_index = tc_index->get_frame_index(position);
1163
1164 uint64_t pts = tc_index->get_seek_pos_pts(new_frame_index);
1165 uint64_t dts = tc_index->get_seek_pos_dts(new_frame_index);
1166
1167 anim->cur_key_frame_pts = timestamp_from_pts_or_dts(pts, dts);
1168
1169 av_log(anim->pFormatCtx, AV_LOG_DEBUG, "TC INDEX seek pts = %" PRIu64 "\n", pts);
1170 av_log(anim->pFormatCtx, AV_LOG_DEBUG, "TC INDEX seek dts = %" PRIu64 "\n", dts);
1171
1172 av_log(anim->pFormatCtx, AV_LOG_DEBUG, "Using PTS from timecode as seek_pos\n");
1173 ret = av_seek_frame(anim->pFormatCtx, anim->videoStream, pts, AVSEEK_FLAG_BACKWARD);
1174 }
1175 else {
1176 /* We have to manually seek with ffmpeg to get to the key frame we want to start decoding from.
1177 */
1178 seek_pos = ffmpeg_get_seek_pts(anim, pts_to_search);
1179 av_log(
1180 anim->pFormatCtx, AV_LOG_DEBUG, "NO INDEX final seek seek_pos = %" PRId64 "\n", seek_pos);
1181
1182 AVFormatContext *format_ctx = anim->pFormatCtx;
1183
1184 /* This used to check if the codec implemented "read_seek" or "read_seek2". However this is
1185 * now hidden from us in FFMPEG 7.0. While not as accurate, usually the AVFMT_TS_DISCONT is
1186 * set for formats where we need to apply the seek workaround to (like in MPEGTS). */
1187 if (!(format_ctx->iformat->flags & AVFMT_TS_DISCONT)) {
1188 ret = av_seek_frame(anim->pFormatCtx, anim->videoStream, seek_pos, AVSEEK_FLAG_BACKWARD);
1189 }
1190 else {
1191 ret = ffmpeg_generic_seek_workaround(anim, &seek_pos, pts_to_search);
1192 av_log(anim->pFormatCtx,
1193 AV_LOG_DEBUG,
1194 "Adjusted final seek seek_pos = %" PRId64 "\n",
1195 seek_pos);
1196 }
1197
1198 if (ret <= 0 && !ffmpeg_seek_buffers_need_flushing(anim, position, seek_pos)) {
1199 return 0;
1200 }
1201 }
1202
1203 if (ret < 0) {
1204 av_log(anim->pFormatCtx,
1205 AV_LOG_ERROR,
1206 "FETCH: "
1207 "error while seeking to DTS = %" PRId64 " (frameno = %d, PTS = %" PRId64
1208 "): errcode = %d\n",
1209 seek_pos,
1210 position,
1211 pts_to_search,
1212 ret);
1213 }
1214 /* Flush the internal buffers of ffmpeg. This needs to be done after seeking to avoid decoding
1215 * errors. */
1216 avcodec_flush_buffers(anim->pCodecCtx);
1217 ffmpeg_double_buffer_backup_frame_clear(anim);
1218
1219 anim->cur_pts = -1;
1220
1221 if (anim->cur_packet->stream_index == anim->videoStream) {
1222 av_packet_unref(anim->cur_packet);
1223 anim->cur_packet->stream_index = -1;
1224 }
1225
1226 return ret;
1227}
1228
1229static bool ffmpeg_must_decode(MovieReader *anim, int position)
1230{
1231 return !anim->pFrame_complete || anim->cur_position != position;
1232}
1233
1234static bool ffmpeg_must_seek(MovieReader *anim, int position)
1235{
1236 bool must_seek = position != anim->cur_position + 1 || ffmpeg_is_first_frame_decode(anim);
1237 anim->seek_before_decode = must_seek;
1238 return must_seek;
1239}
1240
1241static ImBuf *ffmpeg_fetchibuf(MovieReader *anim, int position, IMB_Timecode_Type tc)
1242{
1243 if (anim == nullptr) {
1244 return nullptr;
1245 }
1246
1247 av_log(anim->pFormatCtx, AV_LOG_DEBUG, "FETCH: seek_pos=%d\n", position);
1248
1249 const MovieIndex *tc_index = movie_open_index(anim, tc);
1250 int64_t pts_to_search = ffmpeg_get_pts_to_search(anim, tc_index, position);
1251 AVStream *v_st = anim->pFormatCtx->streams[anim->videoStream];
1252 double frame_rate = av_q2d(v_st->r_frame_rate);
1253 double pts_time_base = av_q2d(v_st->time_base);
1254 int64_t start_pts = v_st->start_time;
1255
1256 if (anim->never_seek_decode_one_frame) {
1257 /* If we must only ever decode one frame, and never seek, do so here. */
1258 if (!anim->pFrame_complete) {
1259 ffmpeg_decode_video_frame(anim);
1260 }
1261 }
1262 else {
1263 /* For all regular video files, do the seek/decode as needed. */
1264 av_log(anim->pFormatCtx,
1265 AV_LOG_DEBUG,
1266 "FETCH: looking for PTS=%" PRId64 " (pts_timebase=%g, frame_rate=%g, start_pts=%" PRId64
1267 ")\n",
1268 int64_t(pts_to_search),
1269 pts_time_base,
1270 frame_rate,
1271 start_pts);
1272
1273 if (ffmpeg_must_decode(anim, position)) {
1274 if (ffmpeg_must_seek(anim, position)) {
1275 ffmpeg_seek_to_key_frame(anim, position, tc_index, pts_to_search);
1276 }
1277
1278 ffmpeg_decode_video_frame_scan(anim, pts_to_search);
1279 }
1280 }
1281
1282 /* Update resolution as it can change per-frame with WebM. See #100741 & #100081. */
1283 anim->x = anim->pCodecCtx->width;
1284 anim->y = anim->pCodecCtx->height;
1285
1286 const AVPixFmtDescriptor *pix_fmt_descriptor = av_pix_fmt_desc_get(anim->pCodecCtx->pix_fmt);
1287
1288 int planes = R_IMF_PLANES_RGBA;
1289 if ((pix_fmt_descriptor->flags & AV_PIX_FMT_FLAG_ALPHA) == 0) {
1290 planes = R_IMF_PLANES_RGB;
1291 }
1292
1293 ImBuf *cur_frame_final = IMB_allocImBuf(anim->x, anim->y, planes, 0);
1294
1295 /* Allocate the storage explicitly to ensure the memory is aligned. */
1296 const size_t align = ffmpeg_get_buffer_alignment();
1297 const size_t pixel_size = anim->is_float ? 16 : 4;
1298 uint8_t *buffer_data = static_cast<uint8_t *>(
1299 MEM_mallocN_aligned(pixel_size * anim->x * anim->y, align, "ffmpeg ibuf"));
1300 if (anim->is_float) {
1301 IMB_assign_float_buffer(cur_frame_final, (float *)buffer_data, IB_TAKE_OWNERSHIP);
1302 }
1303 else {
1304 IMB_assign_byte_buffer(cur_frame_final, buffer_data, IB_TAKE_OWNERSHIP);
1305 }
1306
1307 AVFrame *final_frame = ffmpeg_frame_by_pts_get(anim, pts_to_search);
1308 if (final_frame == nullptr) {
1309 /* No valid frame was decoded for requested PTS, fall back on most recent decoded frame, even
1310 * if it is incorrect. */
1311 final_frame = ffmpeg_double_buffer_frame_fallback_get(anim);
1312 }
1313
1314 /* Even with the fallback from above it is possible that the current decode frame is nullptr. In
1315 * this case skip post-processing and return current image buffer. */
1316 if (final_frame != nullptr) {
1317 ffmpeg_postprocess(anim, final_frame, cur_frame_final);
1318 }
1319
1320 if (anim->is_float) {
1321 if (anim->keep_original_colorspace) {
1322 /* Movie has been explicitly requested to keep original colorspace, regardless of the nature
1323 * of the buffer. */
1325 anim->colorspace);
1326 }
1327 else {
1328 /* Float buffers are expected to be in the scene linear color space.
1329 * Linearize the buffer if it is in a different space.
1330 *
1331 * It might not be the most optimal thing to do from the playback performance in the
1332 * sequencer perspective, but it ensures that other areas in Blender do not run into obscure
1333 * color space mismatches. */
1335 cur_frame_final, anim->colorspace, ColorManagedFileOutput::Video);
1336 }
1337 }
1338 else {
1339 /* Colorspace conversion is lossy for byte buffers, so only assign the colorspace.
1340 * It is up to artists to ensure operations on byte buffers do not involve mixing different
1341 * colorspaces. */
1343 }
1344
1345 anim->cur_position = position;
1346
1347 return cur_frame_final;
1348}
1349
1350static void free_anim_ffmpeg(MovieReader *anim)
1351{
1352 if (anim == nullptr) {
1353 return;
1354 }
1355
1356 if (anim->pCodecCtx) {
1357 avcodec_free_context(&anim->pCodecCtx);
1358 avformat_close_input(&anim->pFormatCtx);
1359 av_packet_free(&anim->cur_packet);
1360
1361 av_frame_free(&anim->pFrame);
1362 av_frame_free(&anim->pFrame_backup);
1363 av_frame_free(&anim->pFrameRGB);
1364 if (anim->pFrameDeinterlaced->data[0] != nullptr) {
1365 MEM_freeN(anim->pFrameDeinterlaced->data[0]);
1366 }
1367 av_frame_free(&anim->pFrameDeinterlaced);
1368 ffmpeg_sws_release_context(anim->img_convert_ctx);
1369 }
1370 anim->duration_in_frames = 0;
1371}
1372
1373#endif
1374
1379static bool anim_getnew(MovieReader *anim)
1380{
1381 if (anim == nullptr) {
1382 /* Nothing to initialize. */
1383 return false;
1384 }
1385
1387
1388#ifdef WITH_FFMPEG
1389 free_anim_ffmpeg(anim);
1390 if (startffmpeg(anim)) {
1392 return false;
1393 }
1394#endif
1396 return true;
1397}
1398
1400{
1401 ImBuf *ibuf = nullptr;
1402 int position = 0;
1403
1405 if (ibuf) {
1406 IMB_freeImBuf(ibuf);
1407 position = anim->duration_in_frames / 2;
1408 ibuf = MOV_decode_frame(anim, position, IMB_TC_NONE, IMB_PROXY_NONE);
1409
1410 char value[128];
1412 SNPRINTF_UTF8(value, "%i", anim->x);
1413 IMB_metadata_set_field(ibuf->metadata, "Thumb::Video::Width", value);
1414 SNPRINTF_UTF8(value, "%i", anim->y);
1415 IMB_metadata_set_field(ibuf->metadata, "Thumb::Video::Height", value);
1416 SNPRINTF_UTF8(value, "%i", anim->duration_in_frames);
1417 IMB_metadata_set_field(ibuf->metadata, "Thumb::Video::Frames", value);
1418
1419#ifdef WITH_FFMPEG
1420 if (anim->pFormatCtx) {
1421 AVStream *v_st = anim->pFormatCtx->streams[anim->videoStream];
1422 AVRational frame_rate = av_guess_frame_rate(anim->pFormatCtx, v_st, nullptr);
1423 if (frame_rate.num != 0) {
1424 double duration = anim->duration_in_frames / av_q2d(frame_rate);
1425 SNPRINTF_UTF8(value, "%g", av_q2d(frame_rate));
1426 IMB_metadata_set_field(ibuf->metadata, "Thumb::Video::FPS", value);
1427 SNPRINTF_UTF8(value, "%g", duration);
1428 IMB_metadata_set_field(ibuf->metadata, "Thumb::Video::Duration", value);
1429 IMB_metadata_set_field(ibuf->metadata, "Thumb::Video::Codec", anim->pCodec->long_name);
1430 }
1431 }
1432#endif
1433 }
1434 return ibuf;
1435}
1436
1438 int position,
1440 IMB_Proxy_Size preview_size)
1441{
1442 ImBuf *ibuf = nullptr;
1443 if (anim == nullptr) {
1444 return nullptr;
1445 }
1446
1447 if (preview_size == IMB_PROXY_NONE) {
1449 if (!anim_getnew(anim)) {
1450 return nullptr;
1451 }
1452 }
1453
1454 if (position < 0) {
1455 return nullptr;
1456 }
1457 if (position >= anim->duration_in_frames) {
1458 return nullptr;
1459 }
1460 }
1461 else {
1462 MovieReader *proxy = movie_open_proxy(anim, preview_size);
1463
1464 if (proxy) {
1465 position = MOV_calc_frame_index_with_timecode(anim, tc, position);
1466
1467 return MOV_decode_frame(proxy, position, IMB_TC_NONE, IMB_PROXY_NONE);
1468 }
1469 }
1470
1471#ifdef WITH_FFMPEG
1472 if (anim->state == MovieReader::State::Valid) {
1473 ibuf = ffmpeg_fetchibuf(anim, position, tc);
1474 if (ibuf) {
1475 anim->cur_position = position;
1476 }
1477 }
1478#endif
1479
1480 if (ibuf) {
1481 STRNCPY(ibuf->filepath, anim->filepath);
1482 ibuf->fileframe = anim->cur_position + 1;
1483 }
1484 return ibuf;
1485}
1486
1488{
1489 if (tc == IMB_TC_NONE) {
1490 return anim->duration_in_frames;
1491 }
1492
1493 const MovieIndex *idx = movie_open_index(anim, tc);
1494 if (!idx) {
1495 return anim->duration_in_frames;
1496 }
1497
1498 return idx->get_duration();
1499}
1500
1502{
1503 return anim->start_offset;
1504}
1505
1506float MOV_get_fps(const MovieReader *anim)
1507{
1508 if (anim->frs_sec > 0 && anim->frs_sec_base > 0) {
1509 return float(double(anim->frs_sec) / anim->frs_sec_base);
1510 }
1511 return 0.0f;
1512}
1513
1514bool MOV_get_fps_num_denom(const MovieReader *anim, short &r_fps_num, float &r_fps_denom)
1515{
1516 if (anim->frs_sec > 0 && anim->frs_sec_base > 0) {
1517 if (anim->frs_sec > SHRT_MAX) {
1518 /* If numerator is larger than the max short, we need to approximate. */
1519 r_fps_num = SHRT_MAX;
1520 r_fps_denom = float(anim->frs_sec_base * double(SHRT_MAX) / double(anim->frs_sec));
1521 }
1522 else {
1523 r_fps_num = anim->frs_sec;
1524 r_fps_denom = float(anim->frs_sec_base);
1525 }
1526 return true;
1527 }
1528 return false;
1529}
1530
1532{
1533 return ELEM(anim->video_rotation, 90, 270) ? anim->y : anim->x;
1534}
1535
1537{
1538 return ELEM(anim->video_rotation, 90, 270) ? anim->x : anim->y;
1539}
blender::ocio::ColorSpace ColorSpace
Definition BLF_api.hh:38
#define BLI_assert(a)
Definition BLI_assert.h:46
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:53
MINLINE int max_ii(int a, int b)
void void void BLI_path_split_file_part(const char *filepath, char *file, size_t file_maxncpy) ATTR_NONNULL(1
bool BLI_path_is_rel(const char *path) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT
char * STRNCPY(char(&dst)[N], const char *src)
Definition BLI_string.h:693
#define SNPRINTF_UTF8(dst, format,...)
char * BLI_strncpy_utf8(char *__restrict dst, const char *__restrict src, size_t dst_maxncpy) ATTR_NONNULL(1
#define STRNCPY_UTF8(dst, src)
#define UNUSED_VARS(...)
#define ELEM(...)
#define STREQ(a, b)
#define CLOG_ERROR(clg_ref,...)
Definition CLG_log.h:188
@ R_IMF_PLANES_RGB
@ R_IMF_PLANES_RGBA
@ COLOR_ROLE_DEFAULT_BYTE
const char * IMB_colormanagement_colorspace_get_name(const ColorSpace *colorspace)
const char * IMB_colormanagement_role_colorspace_name_get(int role)
const ColorSpace * IMB_colormanagement_space_from_cicp(const int cicp[4], const ColorManagedFileOutput output)
void IMB_assign_float_buffer(ImBuf *ibuf, float *buffer_data, ImBufOwnership ownership)
void IMB_freeImBuf(ImBuf *ibuf)
bool IMB_rotate_orthogonal(ImBuf *ibuf, int degrees)
ImBuf * IMB_allocImBuf(unsigned int x, unsigned int y, unsigned char planes, unsigned int flags)
void IMB_filtery(ImBuf *ibuf)
Definition filter.cc:63
void IMB_assign_byte_buffer(ImBuf *ibuf, uint8_t *buffer_data, ImBufOwnership ownership)
IMB_Proxy_Size
@ IMB_PROXY_NONE
#define IM_MAX_SPACE
@ IB_TAKE_OWNERSHIP
@ IB_animdeinterlace
void IMB_metadata_set_field(IDProperty *metadata, const char *key, const char *value)
Definition metadata.cc:68
void IMB_metadata_ensure(IDProperty **metadata)
Definition metadata.cc:23
void IMB_metadata_free(IDProperty *metadata)
Definition metadata.cc:32
Read Guarded memory(de)allocation.
IMB_Timecode_Type
Definition MOV_enums.hh:44
@ IMB_TC_NONE
Definition MOV_enums.hh:46
long long int int64_t
unsigned long long int uint64_t
nullptr float
const ColorSpace * colormanage_colorspace_get_named(const char *name)
void colormanage_imbuf_make_linear(ImBuf *ibuf, const char *from_colorspace, const ColorManagedFileOutput output)
#define str(s)
FFMPEG_INLINE int64_t av_get_frame_duration_in_pts_units(const AVFrame *picture)
FFMPEG_INLINE size_t ffmpeg_get_buffer_alignment()
FFMPEG_INLINE int64_t timestamp_from_pts_or_dts(int64_t pts, int64_t dts)
FFMPEG_INLINE int64_t av_get_pts_from_frame(AVFrame *picture)
FFMPEG_INLINE int ffmpeg_get_video_rotation(const AVStream *stream)
#define input
#define round
#define PRIu64
Definition inttypes.h:132
#define PRId64
Definition inttypes.h:78
#define LOG(level)
Definition log.h:97
void * MEM_calloc_arrayN(size_t len, size_t size, const char *str)
Definition mallocn.cc:123
void * MEM_mallocN_aligned(size_t len, size_t alignment, const char *str)
Definition mallocn.cc:138
void MEM_freeN(void *vmemh)
Definition mallocn.cc:113
const MovieIndex * movie_open_index(MovieReader *anim, IMB_Timecode_Type tc)
void MOV_close_proxies(MovieReader *anim)
int MOV_calc_frame_index_with_timecode(MovieReader *anim, IMB_Timecode_Type tc, int position)
MovieReader * movie_open_proxy(MovieReader *anim, IMB_Proxy_Size preview_size)
static bool anim_getnew(MovieReader *anim)
ImBuf * MOV_decode_preview_frame(MovieReader *anim)
void MOV_close(MovieReader *anim)
Definition movie_read.cc:66
double MOV_get_start_offset_seconds(const MovieReader *anim)
int MOV_get_duration_frames(MovieReader *anim, IMB_Timecode_Type tc)
static void probe_video_colorspace(MovieReader *anim, char r_colorspace_name[IM_MAX_SPACE])
bool MOV_is_initialized_and_valid(const MovieReader *anim)
void MOV_get_filename(const MovieReader *anim, char *filename, int filename_maxncpy)
Definition movie_read.cc:81
MovieReader * MOV_open_file(const char *filepath, const int ib_flags, const int streamindex, const bool keep_original_colorspace, char colorspace[IM_MAX_SPACE])
void MOV_set_multiview_suffix(MovieReader *anim, const char *suffix)
int MOV_get_image_width(const MovieReader *anim)
float MOV_get_fps(const MovieReader *anim)
IDProperty * MOV_load_metadata(MovieReader *anim)
Definition movie_read.cc:86
ImBuf * MOV_decode_frame(MovieReader *anim, int position, IMB_Timecode_Type tc, IMB_Proxy_Size preview_size)
bool MOV_get_fps_num_denom(const MovieReader *anim, short &r_fps_num, float &r_fps_denom)
int MOV_get_image_height(const MovieReader *anim)
void parallel_for(const IndexRange range, const int64_t grain_size, const Function &function, const TaskSizeHints &size_hints=detail::TaskSizeHints_Static(1))
Definition BLI_task.hh:93
return ret
const ColorSpace * colorspace
const ColorSpace * colorspace
char filepath[IMB_FILEPATH_SIZE]
ImBufFloatBuffer float_buffer
ImBufByteBuffer byte_buffer
IDProperty * metadata
int get_duration() const
int get_frame_index(int frameno) const
uint64_t get_seek_pos_dts(int frame_index) const
uint64_t get_pts(int frame_index) const
uint64_t get_seek_pos_pts(int frame_index) const
char colorspace[64]
Definition movie_read.hh:93
double start_offset
Definition movie_read.hh:41
double frs_sec_base
Definition movie_read.hh:40
char filepath[1024]
Definition movie_read.hh:47
int duration_in_frames
Definition movie_read.hh:38
char suffix[64]
Definition movie_read.hh:95
IDProperty * metadata
Definition movie_read.hh:97
int video_rotation
Definition movie_read.hh:44
bool keep_original_colorspace
Definition movie_read.hh:51
i
Definition text_draw.cc:230