Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/framework/mlt.vers
Original file line number Diff line number Diff line change
Expand Up @@ -709,4 +709,6 @@ MLT_7.42.0 {
MLT_7.44.0 {
global:
mlt_properties_revision;
mlt_frame_get_resize_alpha;
mlt_frame_set_resize_alpha;
} MLT_7.42.0;
45 changes: 42 additions & 3 deletions src/framework/mlt_frame.c
Original file line number Diff line number Diff line change
Expand Up @@ -1030,15 +1030,54 @@ void mlt_frame_copy_convert_image(mlt_frame dst, mlt_frame src)
NULL);
}

/** Get the padding alpha requested of filter_resize.
*
* Missing and an explicit 0 are different: missing keeps the default of padding
* only an existing alpha mask, while an explicit value other than 255 also asks
* filter_resize to create a mask for an opaque image. Use this pair instead of
* get_int/set_int, which turn missing into 0.
* \public \memberof mlt_frame_s
* \param self a frame
* \param[out] value the padding alpha if set; may be NULL
* \return true if resize_alpha is set
*/

int mlt_frame_get_resize_alpha(mlt_frame self, int *value)
{
mlt_properties properties = MLT_FRAME_PROPERTIES(self);
int is_set = mlt_properties_exists(properties, "resize_alpha");
if (value)
*value = is_set ? mlt_properties_get_int(properties, "resize_alpha") : 0;
return is_set;
}

/** Set or clear the padding alpha requested of filter_resize.
*
* \public \memberof mlt_frame_s
* \param self a frame
* \param is_set false to remove the request
* \param value the padding alpha
* \see mlt_frame_get_resize_alpha
*/

void mlt_frame_set_resize_alpha(mlt_frame self, int is_set, int value)
{
mlt_properties properties = MLT_FRAME_PROPERTIES(self);
if (is_set)
mlt_properties_set_int(properties, "resize_alpha", value);
else
mlt_properties_clear(properties, "resize_alpha");
}

/** Copy consumer scaling hints from \p src onto \p dst. */
static void copy_consumer_image_hints(mlt_frame dst, mlt_frame src)
{
mlt_properties dst_properties = MLT_FRAME_PROPERTIES(dst);
mlt_properties src_properties = MLT_FRAME_PROPERTIES(src);
int resize_alpha;
int has_resize_alpha = mlt_frame_get_resize_alpha(src, &resize_alpha);

mlt_properties_set_int(dst_properties,
"resize_alpha",
mlt_properties_get_int(src_properties, "resize_alpha"));
mlt_frame_set_resize_alpha(dst, has_resize_alpha, resize_alpha);
mlt_properties_set_int(dst_properties,
"distort",
mlt_properties_get_int(src_properties, "distort"));
Expand Down
2 changes: 2 additions & 0 deletions src/framework/mlt_frame.h
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,8 @@ MLT_EXPORT int mlt_frame_next_convert_image(mlt_frame self,
mlt_image_format *format,
mlt_image_format output);
MLT_EXPORT void mlt_frame_copy_convert_image(mlt_frame dst, mlt_frame src);
MLT_EXPORT int mlt_frame_get_resize_alpha(mlt_frame self, int *value);
MLT_EXPORT void mlt_frame_set_resize_alpha(mlt_frame self, int is_set, int value);
MLT_EXPORT mlt_frame mlt_frame_clone(mlt_frame self, int is_deep);
MLT_EXPORT mlt_frame mlt_frame_clone_audio(mlt_frame self, int is_deep);
MLT_EXPORT mlt_frame mlt_frame_clone_image(mlt_frame self, int is_deep);
Expand Down
2 changes: 2 additions & 0 deletions src/modules/avformat/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ add_library(mltavformat MODULE
filter_sws_colortransform.c
# avformat
producer_avformat.c
conceal.c
conceal.h
consumer_avformat.c
# avfilter
filter_avfilter.c
Expand Down
86 changes: 86 additions & 0 deletions src/modules/avformat/conceal.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* conceal.c -- last good picture for avformat error concealment
* Copyright (C) 2026 Meltytech, LLC
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

#include "conceal.h"

/* Not a valid image-cache key. Zero is a valid key, so the empty state cannot be 0. */
#define CONCEAL_NO_CACHE_POSITION ((mlt_position) -1)

void avformat_conceal_init(avformat_conceal *self)
{
self->owned = NULL;
self->cache_position = CONCEAL_NO_CACHE_POSITION;
/* Same initial watermark as the previous zeroed int: position 0 is recorded. */
self->source_position = 0;
}

void avformat_conceal_close(avformat_conceal *self)
{
if (self->owned)
mlt_frame_close(self->owned);
self->owned = NULL;
self->cache_position = CONCEAL_NO_CACHE_POSITION;
}

void avformat_conceal_record(avformat_conceal *self,
mlt_frame frame,
mlt_cache cache,
int source_position,
mlt_position cache_position)
{
if (source_position < self->source_position)
return;

self->source_position = source_position;
if (self->owned)
mlt_frame_close(self->owned);
if (cache) {
self->owned = NULL;
self->cache_position = cache_position;
} else {
self->owned = mlt_frame_clone(frame, 1);
self->cache_position = CONCEAL_NO_CACHE_POSITION;
}
}

void avformat_conceal_detach(avformat_conceal *self, mlt_cache cache)
{
if (!self->owned && cache && self->cache_position != CONCEAL_NO_CACHE_POSITION)
self->owned = mlt_cache_get_frame(cache, self->cache_position);
}

void avformat_conceal_detach_if_older(avformat_conceal *self, mlt_cache cache, int source_position)
{
if (source_position < self->source_position)
avformat_conceal_detach(self, cache);
}

mlt_frame avformat_conceal_fetch(avformat_conceal *self, mlt_cache cache)
{
if (self->owned)
return mlt_frame_clone(self->owned, 1);
if (cache && self->cache_position != CONCEAL_NO_CACHE_POSITION)
return mlt_cache_get_frame(cache, self->cache_position);
return NULL;
}

void avformat_conceal_reset_source(avformat_conceal *self)
{
self->source_position = -1;
}
74 changes: 74 additions & 0 deletions src/modules/avformat/conceal.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* conceal.h -- last good picture for avformat error concealment
* Copyright (C) 2026 Meltytech, LLC
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

#ifndef AVFORMAT_CONCEAL_H
#define AVFORMAT_CONCEAL_H

#include <framework/mlt.h>

#ifdef __cplusplus
extern "C" {
#endif

/* The last good picture is either an owned frame or a cache key, never both
* as the source of a fetch. Once the picture has been detached, owned is set
* and fetch clones that frame. Otherwise owned is NULL and fetch loads
* cache_position from the image cache. source_position is only the watermark
* that tells record() whether a newer success may replace the picture.
* While the producer is open, record and detach are the only writers of the
* picture. fetch does not modify it. close releases the owner. reset_source
* moves only the watermark.
*/
typedef struct avformat_conceal_s
{
mlt_frame owned;
mlt_position cache_position;
int source_position;
} avformat_conceal;

void avformat_conceal_init(avformat_conceal *self);
void avformat_conceal_close(avformat_conceal *self);

/* Remember a successful frame. A source_position older than the watermark
* does not replace the picture. With a cache, only the cache key is kept.
* Without one, a deep copy is owned.
*/
void avformat_conceal_record(avformat_conceal *self,
mlt_frame frame,
mlt_cache cache,
int source_position,
mlt_position cache_position);

/* Copy the picture out of the cache before a put or a cache reset can drop it. */
void avformat_conceal_detach(avformat_conceal *self, mlt_cache cache);

/* Detach when this frame is older than the watermark, so caching it can evict. */
void avformat_conceal_detach_if_older(avformat_conceal *self, mlt_cache cache, int source_position);

/* A new frame the caller closes, or NULL. Prefers the owned frame after detach. */
mlt_frame avformat_conceal_fetch(avformat_conceal *self, mlt_cache cache);

/* Allow the next record() to replace the picture without dropping the one held now. */
void avformat_conceal_reset_source(avformat_conceal *self);

#ifdef __cplusplus
}
#endif

#endif
67 changes: 36 additions & 31 deletions src/modules/avformat/producer_avformat.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#endif

#include "common.h"
#include "conceal.h"

// MLT Header files
#include <framework/mlt_cache.h>
Expand Down Expand Up @@ -129,8 +130,7 @@ struct producer_avformat_s
int packets_thread_stop; // non-zero when packets_thread is to stop
int is_thread_init;
AVRational video_time_base;
mlt_frame last_good_frame; // for video error concealment
int last_good_position; // for video error concealment
avformat_conceal conceal; // last good picture for video error concealment
AVFilterGraph *vfilter_graph;
AVFilterContext *vfilter_in;
AVFilterContext *vfilter_out;
Expand Down Expand Up @@ -230,6 +230,7 @@ mlt_producer producer_avformat_init(mlt_profile profile, const char *service, ch
if (file) {
// Construct the producer
producer_avformat self = calloc(1, sizeof(struct producer_avformat_s));
avformat_conceal_init(&self->conceal);
producer = calloc(1, sizeof(struct mlt_producer_s));

// Initialise it
Expand Down Expand Up @@ -2801,6 +2802,7 @@ static int producer_get_image(mlt_frame frame,

if (self->reset_image_cache) {
self->reset_image_cache = 0;
avformat_conceal_detach(&self->conceal, self->image_cache);
mlt_cache_close(self->image_cache);
self->image_cache = NULL;
av_frame_free(&self->video_frame);
Expand Down Expand Up @@ -3033,7 +3035,7 @@ static int producer_get_image(mlt_frame frame,
mlt_log_warning(MLT_PRODUCER_SERVICE(producer),
"video decoding error %d\n",
error);
self->last_good_position = POSITION_INVALID;
avformat_conceal_reset_source(&self->conceal);
}
} else {
if (self->hwaccel.device_ctx
Expand Down Expand Up @@ -3240,6 +3242,8 @@ static int producer_get_image(mlt_frame frame,

if (image_size > 0) {
mlt_properties_set_int(frame_properties, "format", *format);
// An earlier frame does not become the last good one, but caching it may evict that.
avformat_conceal_detach_if_older(&self->conceal, self->image_cache, self->current_position);
// Cache the image for rapid repeated access.
if (self->image_cache) {
if (is_album_art(self)) {
Expand All @@ -3251,33 +3255,34 @@ static int producer_get_image(mlt_frame frame,
mlt_cache_put_frame_image(self->image_cache, frame);
}
}
// Clone frame for error concealment.
if (self->current_position >= self->last_good_position) {
self->last_good_position = self->current_position;
if (self->last_good_frame)
mlt_frame_close(self->last_good_frame);
self->last_good_frame = mlt_frame_clone(frame, 1);
}
} else if (self->last_good_frame) {
// For error concealment; avoid a second copy when the image cache already has one.
avformat_conceal_record(&self->conceal,
frame,
self->image_cache,
self->current_position,
position);
} else {
// Use last known good frame if there was a decoding failure.
mlt_frame original = mlt_frame_clone(self->last_good_frame, 1);
mlt_properties orig_props = MLT_FRAME_PROPERTIES(original);
int size = 0;

*buffer = mlt_frame_get_alpha_size(original, &size);
if (*buffer)
mlt_frame_set_alpha(frame, *buffer, size, NULL);
*buffer = mlt_properties_get_data(orig_props, "image", &size);
mlt_frame_set_image(frame, *buffer, size, NULL);
mlt_properties_set_data(frame_properties,
"avformat.conceal_error",
original,
0,
(mlt_destructor) mlt_frame_close,
NULL);
*format = mlt_properties_get_int(orig_props, "format");
set_image_size(self, width, height);
got_picture = 1;
mlt_frame original = avformat_conceal_fetch(&self->conceal, self->image_cache);
if (original) {
mlt_properties orig_props = MLT_FRAME_PROPERTIES(original);
int size = 0;

*buffer = mlt_frame_get_alpha_size(original, &size);
if (*buffer)
mlt_frame_set_alpha(frame, *buffer, size, NULL);
*buffer = mlt_properties_get_data(orig_props, "image", &size);
mlt_frame_set_image(frame, *buffer, size, NULL);
mlt_properties_set_data(frame_properties,
"avformat.conceal_error",
original,
0,
(mlt_destructor) mlt_frame_close,
NULL);
*format = mlt_properties_get_int(orig_props, "format");
set_image_size(self, width, height);
got_picture = 1;
}
}

// Regardless of speed, we expect to get the next frame (cos we ain't too bright)
Expand Down Expand Up @@ -4476,6 +4481,7 @@ static int producer_get_frame(mlt_producer producer, mlt_frame_ptr frame, int in
producer_avformat created = calloc(1, sizeof(struct producer_avformat_s));
if (!created)
break;
avformat_conceal_init(&created->conceal);
created->parent = producer;
// The mutexes must exist before the state is visible to anyone else: it can
// be evicted and destroyed, or used by the code below, right away.
Expand Down Expand Up @@ -4654,8 +4660,7 @@ static void producer_avformat_close(producer_avformat self)
// Cleanup caches.
mlt_cache_close(self->image_cache);
mlt_cache_close(self->audio_cache);
if (self->last_good_frame)
mlt_frame_close(self->last_good_frame);
avformat_conceal_close(&self->conceal);

// Cleanup the mutexes
if (self->is_mutex_init) {
Expand Down
Loading
Loading