Skip to content

Commit

Permalink
Merge branch 'bmatherly-playlist_seam'
Browse files Browse the repository at this point in the history
  • Loading branch information
ddennedy committed Jun 24, 2023
2 parents 7bc867c + 5f0abb1 commit 549ec53
Show file tree
Hide file tree
Showing 11 changed files with 470 additions and 6 deletions.
5 changes: 5 additions & 0 deletions src/framework/mlt.vers
Original file line number Diff line number Diff line change
Expand Up @@ -676,3 +676,8 @@ MLT_7.16.0 {
mlt_frame_clone_audio;
mlt_frame_clone_image;
} MLT_7.14.0;

MLT_7.18.0 {
global:
mlt_audio_free_data;
} MLT_7.16.0;
28 changes: 25 additions & 3 deletions src/framework/mlt_audio.c
Original file line number Diff line number Diff line change
Expand Up @@ -127,15 +127,37 @@ void mlt_audio_alloc_data(mlt_audio self)
if (!self)
return;

if (self->release_data) {
self->release_data(self->data);
}
mlt_audio_free_data(self);

int size = mlt_audio_calculate_size(self);
self->data = mlt_pool_alloc(size);
self->release_data = mlt_pool_release;
}

/** Free the data field using the destructor function.
*
* If the constructor function does not exist, the value will be set to NULL
* without being released.
*
* After this function call, the data and release_data fields will be NULL.
*
* \public \memberof mlt_audio_s
* \param self the Audio object
*/

void mlt_audio_free_data(mlt_audio self)
{
if (!self)
return;

if (self->release_data) {
self->release_data(self->data);
}

self->data = NULL;
self->release_data = NULL;
}

/** Calculate the number of bytes needed for the Audio data.
*
* \public \memberof mlt_audio_s
Expand Down
1 change: 1 addition & 0 deletions src/framework/mlt_audio.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ extern void mlt_audio_get_values(mlt_audio self,
int *samples,
int *channels);
extern void mlt_audio_alloc_data(mlt_audio self);
extern void mlt_audio_free_data(mlt_audio self);
extern int mlt_audio_calculate_size(mlt_audio self);
extern int mlt_audio_plane_count(mlt_audio self);
extern int mlt_audio_plane_size(mlt_audio self);
Expand Down
4 changes: 4 additions & 0 deletions src/framework/mlt_frame.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ typedef int (*mlt_get_audio)(mlt_frame self,
* \properties \em height the vertical resolution of the image
* \properties \em aspect_ratio the sample aspect ratio of the image
* \properties \em full_range set if the video is full range - only applies to Y'CbCr
* \properties \em meta.playlist.clip_position mlt_playlist sets this property
* to the time position of this frame's clip in the playlist
* \properties \em meta.playlist.clip_length mlt_playlist sets this property to
* the playlist index of this frame's clip in the playlist
*/

struct mlt_frame_s
Expand Down
25 changes: 22 additions & 3 deletions src/framework/mlt_playlist.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* \brief playlist service class
* \see mlt_playlist_s
*
* Copyright (C) 2003-2022 Meltytech, LLC
* Copyright (C) 2003-2023 Meltytech, LLC
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
Expand Down Expand Up @@ -391,11 +391,16 @@ static mlt_producer mlt_playlist_locate(mlt_playlist self,
* \private \memberof mlt_playlist_s
* \param self a playlist
* \param[out] progressive true if the producer should be displayed progressively
* \param[out] clip_index the index of the returned service
* \param[out] clip_position the position in the returned service relative to the beginning
* \return the service interface of the producer at the play head
* \see producer_get_frame
*/

static mlt_service mlt_playlist_virtual_seek(mlt_playlist self, int *progressive)
static mlt_service mlt_playlist_virtual_seek(mlt_playlist self,
int *progressive,
int *clip_index,
int *clip_position)
{
// Map playlist position to real producer in virtual playlist
mlt_position position = mlt_producer_frame(&self->parent);
Expand Down Expand Up @@ -459,6 +464,11 @@ static mlt_service mlt_playlist_virtual_seek(mlt_playlist self, int *progressive
producer = &self->blank;
}

if (i < self->count && clip_index && clip_position) {
*clip_index = i;
*clip_position = position;
}

// Determine if we have moved to the next entry in the playlist.
if (original == total - 2) {
mlt_events_fire(properties, "playlist-next", mlt_event_data_from_int(i));
Expand Down Expand Up @@ -2005,9 +2015,11 @@ static int producer_get_frame(mlt_producer producer, mlt_frame_ptr frame, int in

// Need to ensure the frame is deinterlaced when repeating 1 frame
int progressive = 0;
int clip_index = -1;
int clip_position = -1;

// Get the real producer
mlt_service real = mlt_playlist_virtual_seek(self, &progressive);
mlt_service real = mlt_playlist_virtual_seek(self, &progressive, &clip_index, &clip_position);

// Check that we have a producer
if (real == NULL) {
Expand Down Expand Up @@ -2043,6 +2055,13 @@ static int producer_get_frame(mlt_producer producer, mlt_frame_ptr frame, int in
mlt_properties_set_int(properties, "test_audio", 1);
}

if (clip_index >= 0 && clip_index < self->size) {
mlt_properties_set_int(properties, "meta.playlist.clip_position", clip_position);
mlt_properties_set_int(properties,
"meta.playlist.clip_length",
self->list[clip_index]->frame_count);
}

// Check for notifier and call with appropriate argument
mlt_properties playlist_properties = MLT_PRODUCER_PROPERTIES(producer);
void (*notifier)(void *) = mlt_properties_get_data(playlist_properties, "notifier", NULL);
Expand Down
3 changes: 3 additions & 0 deletions src/modules/core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ add_library(mltcore MODULE
filter_audiochannels.c
filter_audioconvert.c
filter_audiomap.c
filter_audioseam.c
filter_audiowave.c
filter_autofade.c
filter_box_blur.c
filter_brightness.c
filter_channelcopy.c
Expand Down Expand Up @@ -76,6 +78,7 @@ install(FILES
consumer_multi.yml
filter_audiomap.yml
filter_audiowave.yml
filter_autofade.yml
filter_box_blur.yml
filter_brightness.yml
filter_channelcopy.yml
Expand Down
12 changes: 12 additions & 0 deletions src/modules/core/factory.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ extern mlt_filter filter_audiowave_init(mlt_profile profile,
mlt_service_type type,
const char *id,
char *arg);
extern mlt_filter filter_autofade_init(mlt_profile profile,
mlt_service_type type,
const char *id,
char *arg);
extern mlt_filter filter_box_blur_init(mlt_profile profile,
mlt_service_type type,
const char *id,
Expand All @@ -65,6 +69,10 @@ extern mlt_filter filter_crop_init(mlt_profile profile,
mlt_service_type type,
const char *id,
char *arg);
extern mlt_filter filter_audioseam_init(mlt_profile profile,
mlt_service_type type,
const char *id,
char *arg);
extern mlt_filter filter_fieldorder_init(mlt_profile profile,
mlt_service_type type,
const char *id,
Expand Down Expand Up @@ -197,7 +205,9 @@ MLT_REPOSITORY
MLT_REGISTER(mlt_service_filter_type, "audiochannels", filter_audiochannels_init);
MLT_REGISTER(mlt_service_filter_type, "audioconvert", filter_audioconvert_init);
MLT_REGISTER(mlt_service_filter_type, "audiomap", filter_audiomap_init);
MLT_REGISTER(mlt_service_filter_type, "audioseam", filter_audioseam_init);
MLT_REGISTER(mlt_service_filter_type, "audiowave", filter_audiowave_init);
MLT_REGISTER(mlt_service_filter_type, "autofade", filter_autofade_init);
MLT_REGISTER(mlt_service_filter_type, "box_blur", filter_box_blur_init);
MLT_REGISTER(mlt_service_filter_type, "brightness", filter_brightness_init);
MLT_REGISTER(mlt_service_filter_type, "channelcopy", filter_channelcopy_init);
Expand Down Expand Up @@ -257,7 +267,9 @@ MLT_REPOSITORY
metadata,
"filter_audioconvert.yml");
MLT_REGISTER_METADATA(mlt_service_filter_type, "audiomap", metadata, "filter_audiomap.yml");
MLT_REGISTER_METADATA(mlt_service_filter_type, "audioseam", metadata, "filter_audioseam.yml");
MLT_REGISTER_METADATA(mlt_service_filter_type, "audiowave", metadata, "filter_audiowave.yml");
MLT_REGISTER_METADATA(mlt_service_filter_type, "autofade", metadata, "filter_autofade.yml");
MLT_REGISTER_METADATA(mlt_service_filter_type, "box_blur", metadata, "filter_box_blur.yml");
MLT_REGISTER_METADATA(mlt_service_filter_type, "brightness", metadata, "filter_brightness.yml");
MLT_REGISTER_METADATA(mlt_service_filter_type,
Expand Down
181 changes: 181 additions & 0 deletions src/modules/core/filter_audioseam.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
/*
* filter_audioseam.c -- smooth seams between clips in a playlist
* Copyright (C) 2023 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 <framework/mlt.h>

#include <math.h>
#include <stdlib.h>

typedef struct
{
struct mlt_audio_s prev_audio;
} private_data;

static float db_delta(float a, float b)
{
float dba = 0;
float dbb = 0;
const float essentially_zero = 0.001;
// Calculate db from zero
if (fabs(a) > essentially_zero) {
dba = log10(fabs(a)) * 20;
}
if (fabs(b) > essentially_zero) {
dbb = log10(fabs(b)) * 20;
}
// Apply sign
if (a < 0) {
dba *= -1.0;
}
if (b < 0) {
dba *= -1;
}
return dba - dbb;
}

static int filter_get_audio(mlt_frame frame,
void **buffer,
mlt_audio_format *format,
int *frequency,
int *channels,
int *samples)
{
mlt_filter filter = mlt_frame_pop_audio(frame);
mlt_properties filter_properties = MLT_FILTER_PROPERTIES(filter);
private_data *pdata = (private_data *) filter->child;
int clip_position = mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame),
"meta.playlist.clip_position");
int clip_length = mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame),
"meta.playlist.clip_length");

if (clip_length == 0 || (clip_position != 0 && clip_position != (clip_length - 1))) {
// Only operate on the first and last frame of every clip
return mlt_frame_get_audio(frame, buffer, format, frequency, channels, samples);
}

*format = mlt_audio_f32le;
int ret = mlt_frame_get_audio(frame, buffer, format, frequency, channels, samples);
if (ret != 0) {
return ret;
}

struct mlt_audio_s curr_audio;
mlt_audio_set_values(&curr_audio, *buffer, *frequency, *format, *samples, *channels);

if (clip_position == 0) {
if (!pdata->prev_audio.data) {
mlt_log_debug(MLT_FILTER_SERVICE(filter), "Missing previous audio\n");
} else {
float *prev_data = pdata->prev_audio.data;
float *curr_data = curr_audio.data;
float level_delta = db_delta(prev_data[pdata->prev_audio.samples - 1], curr_data[0]);
double discontinuity_threshold = mlt_properties_get_double(filter_properties,
"discontinuity_threshold");
if (fabs(level_delta) > discontinuity_threshold) {
// We have decided to create a transition with the previous frame.
// Reverse the prevous frame and use the reversed samples as faux
// data that is continuous from the prevous frame.
// Mix/fade the reversed previous samples with the new samples to create a transition.
mlt_audio_reverse(&pdata->prev_audio);
int fade_samples = 1000;
if (fade_samples > curr_audio.samples) {
fade_samples = curr_audio.samples;
}
if (fade_samples > pdata->prev_audio.samples) {
fade_samples = pdata->prev_audio.samples;
}
for (int c = 0; c < curr_audio.channels; c++) {
curr_data = (float *) curr_audio.data + c;
prev_data = (float *) pdata->prev_audio.data + c;
for (int i = 0; i < fade_samples; i++) {
float mix = (1.0 / fade_samples) * (float) (fade_samples - i);
*curr_data = (*prev_data * mix) + (*curr_data * (1.0 - mix));
curr_data += curr_audio.channels;
prev_data += curr_audio.channels;
}
}
// If this flag is set, it must be cleared so that other services will know it can't be ignored.
mlt_properties_clear(MLT_FRAME_PROPERTIES(frame), "test_audio");
// Increment the counter
mlt_properties_set_int(filter_properties,
"seam_count",
mlt_properties_get_int(filter_properties, "seam_count") + 1);
}
}
mlt_audio_free_data(&pdata->prev_audio);
} else if (clip_position == (clip_length - 1)) {
// Save the samples of the last frame to be used to mix with the first frame of the next clip.
mlt_audio_set_values(&pdata->prev_audio, NULL, *frequency, *format, *samples, *channels);
mlt_audio_alloc_data(&pdata->prev_audio);
mlt_audio_copy(&pdata->prev_audio, &curr_audio, *samples, 0, 0);
}

return 0;
}

static mlt_frame filter_process(mlt_filter filter, mlt_frame frame)
{
mlt_properties frame_properties = MLT_FRAME_PROPERTIES(frame);
int clip_position = mlt_properties_get_int(frame_properties, "meta.playlist.clip_position");
int clip_length = mlt_properties_get_int(frame_properties, "meta.playlist.clip_length");

// Only operate on the first and last frame of every clip
if (clip_length > 0 && (clip_position == 0 || clip_position == (clip_length - 1))) {
// Be sure to process blanks in a playlist
mlt_properties_clear(frame_properties, "test_audio");
mlt_frame_push_audio(frame, filter);
mlt_frame_push_audio(frame, filter_get_audio);
}
return frame;
}

static void filter_close(mlt_filter filter)
{
private_data *pdata = (private_data *) filter->child;

if (pdata) {
mlt_audio_free_data(&pdata->prev_audio);
}
free(pdata);
filter->child = NULL;
filter->close = NULL;
filter->parent.close = NULL;
mlt_service_close(&filter->parent);
}

mlt_filter filter_audioseam_init(mlt_profile profile,
mlt_service_type type,
const char *id,
char *arg)
{
mlt_filter filter = mlt_filter_new();
private_data *pdata = (private_data *) calloc(1, sizeof(private_data));

if (filter && pdata) {
filter->close = filter_close;
filter->process = filter_process;
filter->child = pdata;
} else {
mlt_filter_close(filter);
filter = NULL;
free(pdata);
}

return filter;
}
Loading

0 comments on commit 549ec53

Please sign in to comment.