mirror of https://github.com/HandBrake/HandBrake
3383 lines
104 KiB
C
3383 lines
104 KiB
C
/* sync.c
|
|
|
|
Copyright (c) 2003-2025 HandBrake Team
|
|
This file is part of the HandBrake source code
|
|
Homepage: <http://handbrake.fr/>.
|
|
It may be used under the terms of the GNU General Public License v2.
|
|
For full terms see the file COPYING file or visit http://www.gnu.org/licenses/gpl-2.0.html
|
|
*/
|
|
|
|
#include "handbrake/handbrake.h"
|
|
#include "handbrake/hbffmpeg.h"
|
|
#include <stdio.h>
|
|
#include "handbrake/audio_resample.h"
|
|
#include "handbrake/hwaccel.h"
|
|
|
|
#if HB_PROJECT_FEATURE_QSV
|
|
#include "handbrake/qsv_common.h"
|
|
#endif
|
|
|
|
#define SYNC_MAX_VIDEO_QUEUE_LEN 40
|
|
#define SYNC_MIN_VIDEO_QUEUE_LEN 20
|
|
|
|
// Audio is small, buffer a lot. It helps to ensure that we see
|
|
// the initial PTS from all input streams before setting the 'zero' point.
|
|
#define SYNC_MAX_AUDIO_QUEUE_LEN 200
|
|
#define SYNC_MIN_AUDIO_QUEUE_LEN 30
|
|
|
|
// We do not place a limit on the number of subtitle frames
|
|
// that are buffered (max_len == INT_MAX) because there are
|
|
// cases where we will receive all the subtitles for a file
|
|
// all at once (SSA subs).
|
|
//
|
|
// If we did not buffer these subs here, the following deadlock
|
|
// condition would occur:
|
|
// 1. Subtitle decoder blocks trying to generate more subtitle
|
|
// lines than will fit in sync input buffers.
|
|
// 2. This blocks the reader. Reader doesn't read any more
|
|
// audio or video, so sync won't receive buffers it needs
|
|
// to unblock subtitles.
|
|
#define SYNC_MAX_SUBTITLE_QUEUE_LEN INT_MAX
|
|
#define SYNC_MIN_SUBTITLE_QUEUE_LEN 0
|
|
|
|
typedef enum
|
|
{
|
|
SYNC_TYPE_VIDEO,
|
|
SYNC_TYPE_AUDIO,
|
|
SYNC_TYPE_SUBTITLE,
|
|
} sync_type_t;
|
|
|
|
typedef struct
|
|
{
|
|
int64_t pts;
|
|
int64_t delta;
|
|
} sync_delta_t;
|
|
|
|
typedef struct
|
|
{
|
|
int link;
|
|
int merge;
|
|
hb_buffer_list_t list_current;
|
|
} subtitle_sanitizer_t;
|
|
|
|
typedef struct sync_common_s sync_common_t;
|
|
|
|
#define SCR_HASH_SZ (2 << 3)
|
|
#define SCR_HASH_MASK (SCR_HASH_SZ - 1)
|
|
|
|
typedef struct
|
|
{
|
|
int scr_sequence;
|
|
int64_t scr_offset;
|
|
} scr_t;
|
|
|
|
typedef struct
|
|
{
|
|
sync_common_t * common;
|
|
|
|
// Stream I/O control
|
|
int done;
|
|
int flush;
|
|
hb_list_t * in_queue;
|
|
hb_list_t * scr_delay_queue;
|
|
int max_len;
|
|
int min_len;
|
|
hb_fifo_t * fifo_in;
|
|
hb_fifo_t * fifo_out;
|
|
|
|
// PTS synchronization
|
|
hb_list_t * delta_list;
|
|
int64_t pts_slip;
|
|
double next_pts;
|
|
double last_pts;
|
|
|
|
// SCR recovery
|
|
int last_scr_sequence;
|
|
double last_scr_pts;
|
|
double last_duration;
|
|
|
|
// frame statistics
|
|
int64_t first_pts;
|
|
double min_frame_duration;
|
|
double max_frame_duration;
|
|
int64_t current_duration;
|
|
int frame_count;
|
|
|
|
// Error reporting stats
|
|
int64_t drop_duration;
|
|
int drop;
|
|
int64_t drop_pts;
|
|
int64_t gap_duration;
|
|
int64_t gap_pts;
|
|
|
|
int first_frame;
|
|
|
|
// stream type specific context
|
|
sync_type_t type;
|
|
union
|
|
{
|
|
struct
|
|
{
|
|
int id;
|
|
int cadence[12];
|
|
int new_chap;
|
|
} video;
|
|
|
|
// Audio stream context
|
|
struct
|
|
{
|
|
hb_audio_t * audio;
|
|
|
|
// Audio filter settings
|
|
// Samplerate conversion
|
|
hb_audio_resample_t * resample;
|
|
double gain_factor;
|
|
} audio;
|
|
|
|
// Subtitle stream context
|
|
struct
|
|
{
|
|
hb_subtitle_t * subtitle;
|
|
subtitle_sanitizer_t sanitizer;
|
|
} subtitle;
|
|
};
|
|
} sync_stream_t;
|
|
|
|
struct sync_common_s
|
|
{
|
|
// Audio/Video sync thread synchronization
|
|
hb_job_t * job;
|
|
hb_lock_t * mutex;
|
|
int stream_count;
|
|
sync_stream_t * streams;
|
|
int found_first_pts;
|
|
int flush;
|
|
|
|
// SCR adjustments
|
|
scr_t scr[SCR_HASH_SZ];
|
|
int first_scr;
|
|
|
|
// point-to-point support
|
|
int start_found;
|
|
int64_t pts_to_start;
|
|
int64_t start_pts;
|
|
int64_t stop_pts;
|
|
int wait_for_frame;
|
|
int wait_for_pts;
|
|
|
|
// sync audio work objects
|
|
hb_list_t * list_work;
|
|
|
|
// UpdateState Statistics
|
|
int est_frame_count;
|
|
uint64_t st_counts[4];
|
|
uint64_t st_dates[4];
|
|
uint64_t st_first;
|
|
|
|
int chapter;
|
|
};
|
|
|
|
struct hb_work_private_s
|
|
{
|
|
sync_common_t * common;
|
|
sync_stream_t * stream;
|
|
};
|
|
|
|
/***********************************************************************
|
|
* Local prototypes
|
|
**********************************************************************/
|
|
static void UpdateState( sync_common_t * common, int frame_count );
|
|
static void UpdateSearchState( sync_common_t * common, int64_t start,
|
|
int frame_count );
|
|
static int UpdateSCR( sync_stream_t * stream, hb_buffer_t * buf );
|
|
static hb_buffer_t * FilterAudioFrame( sync_stream_t * stream,
|
|
hb_buffer_t *buf );
|
|
static void SortedQueueBuffer( sync_stream_t * stream, hb_buffer_t * buf );
|
|
static hb_buffer_t * sanitizeSubtitle(sync_stream_t * stream,
|
|
hb_buffer_t * sub);
|
|
static int OutputBuffer( sync_common_t * common );
|
|
|
|
static void saveChap( sync_stream_t * stream, hb_buffer_t * buf )
|
|
{
|
|
if (stream->type != SYNC_TYPE_VIDEO || buf == NULL)
|
|
{
|
|
return;
|
|
}
|
|
if (buf->s.new_chap > 0)
|
|
{
|
|
stream->video.new_chap = buf->s.new_chap;
|
|
}
|
|
}
|
|
|
|
static void restoreChap( sync_stream_t * stream, hb_buffer_t * buf )
|
|
{
|
|
if (stream->type != SYNC_TYPE_VIDEO || buf == NULL)
|
|
{
|
|
return;
|
|
}
|
|
if (stream->video.new_chap > 0 && buf->s.new_chap <= 0)
|
|
{
|
|
buf->s.new_chap = stream->video.new_chap;
|
|
stream->video.new_chap = 0;
|
|
}
|
|
}
|
|
|
|
static int fillQueues( sync_common_t * common )
|
|
{
|
|
int ii, wait = 0, abort = 0;
|
|
|
|
for (ii = 0; ii < common->stream_count; ii++)
|
|
{
|
|
sync_stream_t *stream = &common->streams[ii];
|
|
|
|
// Don't let the queues grow indefinitely
|
|
// abort when too large
|
|
if (hb_list_count(stream->in_queue) > stream->max_len)
|
|
{
|
|
abort = 1;
|
|
}
|
|
if (hb_list_count(stream->in_queue) <= stream->min_len)
|
|
{
|
|
wait = 1;
|
|
}
|
|
}
|
|
return !wait || abort;
|
|
}
|
|
|
|
static void scrSlip( sync_common_t * common, int64_t delta )
|
|
{
|
|
int ii;
|
|
for (ii = 0; ii < SCR_HASH_SZ; ii++)
|
|
{
|
|
common->scr[ii].scr_offset += delta;
|
|
}
|
|
}
|
|
|
|
static void shiftTS( sync_common_t * common, int64_t delta )
|
|
{
|
|
int ii, jj;
|
|
|
|
scrSlip(common, delta);
|
|
for (ii = 0; ii < common->stream_count; ii++)
|
|
{
|
|
hb_buffer_t * buf = NULL;
|
|
sync_stream_t * stream = &common->streams[ii];
|
|
int count = hb_list_count(stream->in_queue);
|
|
|
|
for (jj = 0; jj < count; jj++)
|
|
{
|
|
buf = hb_list_item(stream->in_queue, jj);
|
|
if (buf->s.start != AV_NOPTS_VALUE)
|
|
{
|
|
buf->s.start -= delta;
|
|
}
|
|
if (buf->s.stop != AV_NOPTS_VALUE)
|
|
{
|
|
buf->s.stop -= delta;
|
|
}
|
|
}
|
|
if (buf != NULL && buf->s.start != AV_NOPTS_VALUE)
|
|
{
|
|
stream->last_scr_pts = buf->s.start + buf->s.duration;
|
|
}
|
|
else
|
|
{
|
|
stream->last_scr_pts = (int64_t)AV_NOPTS_VALUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
static hb_buffer_t * CreateSilenceBuf( sync_stream_t * stream,
|
|
int64_t dur, int64_t pts )
|
|
{
|
|
double frame_dur, next_pts, duration;
|
|
int size;
|
|
hb_buffer_list_t list;
|
|
hb_buffer_t * buf;
|
|
|
|
if (stream->audio.audio->config.out.codec & HB_ACODEC_PASS_FLAG)
|
|
{
|
|
return NULL;
|
|
}
|
|
duration = dur;
|
|
// Although frame size isn't technically important any more, we
|
|
// keep audio buffer durations <= input audio buffer durations.
|
|
frame_dur = (90000. * stream->audio.audio->config.in.samples_per_frame) /
|
|
stream->audio.audio->config.in.samplerate;
|
|
// Audio mixdown occurs in decoders before sync.
|
|
// So number of channels here is output channel count.
|
|
// But audio samplerate conversion happens in later here in sync.c
|
|
// FilterAudioFrame, so samples_per_frame is still the input sample count.
|
|
size = sizeof(float) * stream->audio.audio->config.in.samples_per_frame *
|
|
hb_mixdown_get_discrete_channel_count(
|
|
stream->audio.audio->config.out.mixdown);
|
|
|
|
hb_buffer_list_clear(&list);
|
|
next_pts = pts;
|
|
while (duration >= frame_dur)
|
|
{
|
|
buf = hb_buffer_init(size);
|
|
memset(buf->data, 0, buf->size);
|
|
buf->s.start = next_pts;
|
|
next_pts += frame_dur;
|
|
buf->s.stop = next_pts;
|
|
buf->s.duration = frame_dur;
|
|
duration -= frame_dur;
|
|
hb_buffer_list_append(&list, buf);
|
|
}
|
|
if (duration > 0)
|
|
{
|
|
// Make certain size is even multiple of sample size * num channels
|
|
size = (int)(duration * stream->audio.audio->config.in.samplerate /
|
|
90000) * sizeof(float) *
|
|
hb_mixdown_get_discrete_channel_count(
|
|
stream->audio.audio->config.out.mixdown);
|
|
if (size > 0)
|
|
{
|
|
buf = hb_buffer_init(size);
|
|
memset(buf->data, 0, buf->size);
|
|
buf->s.start = next_pts;
|
|
next_pts += duration;
|
|
buf->s.stop = next_pts;
|
|
buf->s.duration = duration;
|
|
hb_buffer_list_append(&list, buf);
|
|
}
|
|
}
|
|
return hb_buffer_list_clear(&list);
|
|
}
|
|
|
|
static hb_buffer_t * CreateBlackBuf( sync_stream_t * stream,
|
|
int64_t dur, int64_t pts )
|
|
{
|
|
// I would like to just allocate one black frame here and give
|
|
// it the full duration. But encoders that use B-Frames compute
|
|
// the dts delay based on the pts of the 2nd or 3rd frame which
|
|
// can be a very large value in this case. This large dts delay
|
|
// causes problems with computing the dts of the frames (which is
|
|
// extrapolated from the pts and the computed dts delay). And the
|
|
// dts problems lead to problems with frame duration.
|
|
double frame_dur, next_pts, duration;
|
|
hb_buffer_list_t list;
|
|
hb_buffer_t *buf = NULL;
|
|
hb_job_t *job = stream->common->job;
|
|
|
|
hb_buffer_list_clear(&list);
|
|
duration = dur;
|
|
next_pts = pts;
|
|
|
|
frame_dur = 90000. * job->title->vrate.den / job->title->vrate.num;
|
|
|
|
// Only create black buffers of frame_dur or longer
|
|
while (duration >= frame_dur)
|
|
{
|
|
if (buf == NULL)
|
|
{
|
|
buf = hb_frame_buffer_init(job->input_pix_fmt,
|
|
job->title->geometry.width,
|
|
job->title->geometry.height);
|
|
uint8_t *planes[4];
|
|
ptrdiff_t linesizes[4];
|
|
for (int i = 0; i <= buf->f.max_plane; ++i)
|
|
{
|
|
planes[i] = buf->plane[i].data;
|
|
linesizes[i] = buf->plane[i].stride;
|
|
}
|
|
av_image_fill_black(planes, linesizes,
|
|
job->input_pix_fmt, job->color_range,
|
|
buf->f.width, buf->f.height);
|
|
buf->f.color_prim = job->title->color_prim;
|
|
buf->f.color_transfer = job->title->color_transfer;
|
|
buf->f.color_matrix = job->title->color_matrix;
|
|
buf->f.color_range = job->color_range;
|
|
buf->f.chroma_location = job->chroma_location;
|
|
|
|
// Dolby Vision requires a RPU on every buffer, attach the first
|
|
// found during scan in the absence of something better
|
|
if (job->title->initial_rpu)
|
|
{
|
|
hb_data_t *rpu = job->title->initial_rpu;
|
|
AVBufferRef *ref = av_buffer_alloc(rpu->size);
|
|
memcpy(ref->data, rpu->bytes, rpu->size);
|
|
|
|
AVFrameSideData *sd_dst = NULL;
|
|
sd_dst = hb_buffer_new_side_data_from_buf(buf, job->title->initial_rpu_type, ref);
|
|
|
|
if (!sd_dst)
|
|
{
|
|
av_buffer_unref(&ref);
|
|
}
|
|
}
|
|
|
|
if (job->hw_pix_fmt != AV_PIX_FMT_NONE)
|
|
{
|
|
AVBufferRef *hw_frames_ctx = hb_hwaccel_init_hw_frames_ctx(job->hw_device_ctx,
|
|
job->input_pix_fmt, job->hw_pix_fmt,
|
|
job->width, job->height, 0);
|
|
buf = job->hw_accel->upload(hw_frames_ctx, &buf);
|
|
av_buffer_unref(&hw_frames_ctx);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
buf = hb_buffer_shallow_dup(buf);
|
|
}
|
|
buf->s.start = next_pts;
|
|
next_pts += frame_dur;
|
|
buf->s.stop = next_pts;
|
|
buf->s.duration = frame_dur;
|
|
duration -= frame_dur;
|
|
hb_buffer_list_append(&list, buf);
|
|
}
|
|
|
|
if (buf != NULL)
|
|
{
|
|
if (buf->s.stop < pts + dur)
|
|
{
|
|
// Extend the duration of the last black buffer to fill
|
|
// the remaining gap.
|
|
buf->s.duration += pts + dur - buf->s.stop;
|
|
buf->s.stop = pts + dur;
|
|
}
|
|
}
|
|
|
|
return hb_buffer_list_clear(&list);
|
|
}
|
|
|
|
static void setNextPts( sync_common_t * common )
|
|
{
|
|
int ii;
|
|
|
|
for (ii = 0; ii < common->stream_count; ii++)
|
|
{
|
|
sync_stream_t * stream = &common->streams[ii];
|
|
hb_buffer_t * buf = hb_list_item(stream->in_queue, 0);
|
|
if (buf != NULL)
|
|
{
|
|
stream->next_pts = buf->s.start;
|
|
}
|
|
else
|
|
{
|
|
stream->next_pts = (int64_t)AV_NOPTS_VALUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void alignStream( sync_common_t * common, sync_stream_t * stream,
|
|
int64_t pts )
|
|
{
|
|
if (hb_list_count(stream->in_queue) <= 0 ||
|
|
stream->type == SYNC_TYPE_SUBTITLE)
|
|
{
|
|
return;
|
|
}
|
|
|
|
hb_buffer_t * buf = hb_list_item(stream->in_queue, 0);
|
|
int64_t gap = buf->s.start - pts;
|
|
|
|
if (gap == 0)
|
|
{
|
|
return;
|
|
}
|
|
if (gap < 0)
|
|
{
|
|
int ii;
|
|
|
|
// Drop frames from other streams
|
|
for (ii = 0; ii < common->stream_count; ii++)
|
|
{
|
|
sync_stream_t * other_stream = &common->streams[ii];
|
|
if (stream == other_stream)
|
|
{
|
|
continue;
|
|
}
|
|
while (hb_list_count(other_stream->in_queue) > 0)
|
|
{
|
|
buf = hb_list_item(other_stream->in_queue, 0);
|
|
if (buf->s.start < pts)
|
|
{
|
|
if (other_stream->type == SYNC_TYPE_SUBTITLE &&
|
|
buf->s.stop > pts)
|
|
{
|
|
// Subtitle ends after start time, keep sub and
|
|
// adjust it's start time
|
|
buf->s.start = pts;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
hb_list_rem(other_stream->in_queue, buf);
|
|
hb_buffer_close(&buf);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Fill the partial frame gap left after dropping frames
|
|
alignStream(common, other_stream, pts);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hb_buffer_t * blank_buf = NULL;
|
|
|
|
// Insert a blank frame to fill the gap
|
|
if (stream->type == SYNC_TYPE_AUDIO)
|
|
{
|
|
// Can't add silence padding to passthru streams
|
|
if (!(stream->audio.audio->config.out.codec & HB_ACODEC_PASS_FLAG))
|
|
{
|
|
blank_buf = CreateSilenceBuf(stream, gap, pts);
|
|
}
|
|
}
|
|
else if (stream->type == SYNC_TYPE_VIDEO)
|
|
{
|
|
blank_buf = CreateBlackBuf(stream, gap, pts);
|
|
}
|
|
|
|
int64_t last_stop = pts;
|
|
hb_buffer_t * next;
|
|
int pos;
|
|
for (pos = 0; blank_buf != NULL; blank_buf = next, pos++)
|
|
{
|
|
last_stop = blank_buf->s.stop;
|
|
next = blank_buf->next;
|
|
blank_buf->next = NULL;
|
|
hb_list_insert(stream->in_queue, pos, blank_buf);
|
|
}
|
|
if (stream->type == SYNC_TYPE_VIDEO && last_stop < buf->s.start)
|
|
{
|
|
// Extend the duration of the first frame to fill the remaining gap.
|
|
buf->s.duration += buf->s.start - last_stop;
|
|
buf->s.start = last_stop;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void alignStreams( sync_common_t * common, int64_t pts )
|
|
{
|
|
int ii;
|
|
hb_buffer_t * buf;
|
|
|
|
if (common->job->align_av_start)
|
|
{
|
|
int64_t first_pts = AV_NOPTS_VALUE;
|
|
int audio_passthru = 0;
|
|
|
|
for (ii = 0; ii < common->stream_count; ii++)
|
|
{
|
|
sync_stream_t * stream = &common->streams[ii];
|
|
|
|
buf = hb_list_item(stream->in_queue, 0);
|
|
|
|
// P-to-P encoding will pass the start point in pts.
|
|
// Drop any buffers that are before the start point.
|
|
while (buf != NULL && buf->s.start < pts)
|
|
{
|
|
hb_list_rem(stream->in_queue, buf);
|
|
hb_buffer_close(&buf);
|
|
buf = hb_list_item(stream->in_queue, 0);
|
|
}
|
|
if (buf == NULL)
|
|
{
|
|
continue;
|
|
}
|
|
if (stream->type == SYNC_TYPE_AUDIO &&
|
|
stream->audio.audio->config.out.codec & HB_ACODEC_PASS_FLAG)
|
|
{
|
|
// Find the largest initial pts of all passthru audio streams.
|
|
// We can not add silence to passthru audio streams.
|
|
// To align with a passthru audio stream, we must drop
|
|
// buffers from all other streams that are before
|
|
// the first buffer in the passthru audio stream.
|
|
audio_passthru = 1;
|
|
if (first_pts < buf->s.start)
|
|
{
|
|
first_pts = buf->s.start;
|
|
}
|
|
}
|
|
else if (!audio_passthru)
|
|
{
|
|
// Find the smallest initial pts of all streams when
|
|
// there is *no* passthru audio.
|
|
// When there is no passthru audio stream, we insert
|
|
// silence or black buffers to fill any gaps between
|
|
// the start of any stream and the start of the stream
|
|
// with the smallest pts.
|
|
if (first_pts == AV_NOPTS_VALUE || first_pts > buf->s.start)
|
|
{
|
|
first_pts = buf->s.start;
|
|
}
|
|
}
|
|
}
|
|
if (first_pts != AV_NOPTS_VALUE)
|
|
{
|
|
for (ii = 0; ii < common->stream_count; ii++)
|
|
{
|
|
// Fill the gap (or drop frames for passthru audio)
|
|
alignStream(common, &common->streams[ii], first_pts);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void computeInitialTS( sync_common_t * common,
|
|
sync_stream_t * first_stream )
|
|
{
|
|
int ii;
|
|
hb_buffer_t * prev, * buf;
|
|
|
|
// Process first_stream first since it has the initial PTS
|
|
prev = NULL;
|
|
for (ii = 0; ii < hb_list_count(first_stream->in_queue);)
|
|
{
|
|
buf = hb_list_item(first_stream->in_queue, ii);
|
|
|
|
if (!UpdateSCR(first_stream, buf))
|
|
{
|
|
hb_list_rem(first_stream->in_queue, buf);
|
|
}
|
|
else
|
|
{
|
|
ii++;
|
|
if (first_stream->type == SYNC_TYPE_VIDEO && prev != NULL)
|
|
{
|
|
double duration = buf->s.start - prev->s.start;
|
|
if (duration > 0)
|
|
{
|
|
prev->s.duration = duration;
|
|
prev->s.stop = buf->s.start;
|
|
}
|
|
}
|
|
prev = buf;
|
|
}
|
|
}
|
|
|
|
for (ii = 0; ii < common->stream_count; ii++)
|
|
{
|
|
sync_stream_t * stream = &common->streams[ii];
|
|
|
|
if (stream == first_stream)
|
|
{
|
|
// skip first_stream, already done
|
|
continue;
|
|
}
|
|
|
|
int jj;
|
|
prev = NULL;
|
|
for (jj = 0; jj < hb_list_count(stream->in_queue);)
|
|
{
|
|
buf = hb_list_item(stream->in_queue, jj);
|
|
if (!UpdateSCR(stream, buf))
|
|
{
|
|
// Subtitle put into delay queue, remove it from in_queue
|
|
hb_list_rem(stream->in_queue, buf);
|
|
}
|
|
else
|
|
{
|
|
jj++;
|
|
if (stream->type == SYNC_TYPE_VIDEO && prev != NULL)
|
|
{
|
|
double duration = buf->s.start - prev->s.start;
|
|
if (duration > 0)
|
|
{
|
|
prev->s.duration = duration;
|
|
prev->s.stop = buf->s.start;
|
|
}
|
|
}
|
|
prev = buf;
|
|
}
|
|
}
|
|
}
|
|
alignStreams(common, AV_NOPTS_VALUE);
|
|
}
|
|
|
|
static void checkFirstPts( sync_common_t * common )
|
|
{
|
|
int ii;
|
|
int64_t first_pts = INT64_MAX;
|
|
sync_stream_t * first_stream = NULL;
|
|
|
|
for (ii = 0; ii < common->stream_count; ii++)
|
|
{
|
|
sync_stream_t *stream = &common->streams[ii];
|
|
if (stream->type == SYNC_TYPE_SUBTITLE)
|
|
{
|
|
// only wait for audio and video
|
|
continue;
|
|
}
|
|
|
|
// If buffers are queued, find the lowest initial PTS
|
|
while (hb_list_count(stream->in_queue) > 0)
|
|
{
|
|
hb_buffer_t * buf = hb_list_item(stream->in_queue, 0);
|
|
if (buf->s.start != AV_NOPTS_VALUE)
|
|
{
|
|
// We require an initial pts for every stream
|
|
if (buf->s.start < first_pts)
|
|
{
|
|
first_pts = buf->s.start;
|
|
first_stream = stream;
|
|
}
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
hb_list_rem(stream->in_queue, buf);
|
|
hb_buffer_close(&buf);
|
|
}
|
|
}
|
|
}
|
|
// We should *always* find a first pts because we let the queues
|
|
// fill before performing this test.
|
|
if (first_pts != INT64_MAX)
|
|
{
|
|
common->found_first_pts = 1;
|
|
// We may have buffers that have no timestamps (i.e. AV_NOPTS_VALUE).
|
|
// Compute these timestamps based on previous buffer's timestamp
|
|
// and duration.
|
|
computeInitialTS(common, first_stream);
|
|
// After this initialization, all AV_NOPTS_VALUE timestamps
|
|
// will be filled in by UpdateSCR()
|
|
}
|
|
else
|
|
{
|
|
// This should never happen
|
|
hb_error("checkFirstPts: No initial PTS found!\n");
|
|
}
|
|
}
|
|
|
|
static void addDelta( sync_common_t * common, int64_t start, int64_t delta)
|
|
{
|
|
int ii;
|
|
|
|
for (ii = 0; ii < common->stream_count; ii++)
|
|
{
|
|
sync_delta_t * delta_item = malloc(sizeof(sync_delta_t));
|
|
delta_item->pts = start;
|
|
delta_item->delta = delta;
|
|
hb_list_add(common->streams[ii].delta_list, delta_item);
|
|
}
|
|
}
|
|
|
|
static void applyDeltas( sync_common_t * common )
|
|
{
|
|
int ii;
|
|
|
|
// Apply delta to any applicable buffers in the queue
|
|
for (ii = 0; ii < common->stream_count; ii++)
|
|
{
|
|
sync_stream_t * stream = &common->streams[ii];
|
|
|
|
// Make adjustments for deltas found in other streams
|
|
sync_delta_t * delta = hb_list_item(stream->delta_list, 0);
|
|
if (delta != NULL)
|
|
{
|
|
int jj, index = -1;
|
|
int64_t prev_start, max = 0;
|
|
hb_buffer_t * buf;
|
|
|
|
prev_start = stream->next_pts;
|
|
for (jj = 0; jj < hb_list_count(stream->in_queue); jj++)
|
|
{
|
|
buf = hb_list_item(stream->in_queue, jj);
|
|
if (stream->type == SYNC_TYPE_SUBTITLE)
|
|
{
|
|
if (buf->s.start > delta->pts)
|
|
{
|
|
// absorb gaps in subtitles as soon as possible
|
|
index = jj;
|
|
break;
|
|
}
|
|
}
|
|
else if (buf->s.start > delta->pts)
|
|
{
|
|
// absorb gap in largest gap found in this stream.
|
|
if (buf->s.start - prev_start > max)
|
|
{
|
|
max = buf->s.start - prev_start;
|
|
index = jj;
|
|
}
|
|
if (stream->type == SYNC_TYPE_AUDIO && max >= delta->delta)
|
|
{
|
|
// absorb gaps in audio as soon as possible
|
|
// when there is a gap that will absorb it.
|
|
break;
|
|
}
|
|
}
|
|
prev_start = buf->s.start;
|
|
}
|
|
|
|
if (index >= 0)
|
|
{
|
|
for (jj = index; jj < hb_list_count(stream->in_queue); jj++)
|
|
{
|
|
buf = hb_list_item(stream->in_queue, jj);
|
|
buf->s.start -= delta->delta;
|
|
if (buf->s.stop != AV_NOPTS_VALUE)
|
|
{
|
|
buf->s.stop -= delta->delta;
|
|
}
|
|
}
|
|
// Correct the duration of the video buffer before
|
|
// the affected timestamp correction.
|
|
if (stream->type == SYNC_TYPE_VIDEO && index > 0)
|
|
{
|
|
buf = hb_list_item(stream->in_queue, index - 1);
|
|
if (buf->s.duration > delta->delta)
|
|
{
|
|
buf->s.duration -= delta->delta;
|
|
buf->s.stop -= delta->delta;
|
|
}
|
|
else
|
|
{
|
|
buf->s.duration = 0;
|
|
buf->s.stop = buf->s.start;
|
|
}
|
|
}
|
|
stream->pts_slip += delta->delta;
|
|
hb_list_rem(stream->delta_list, delta);
|
|
free(delta);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void removeVideoJitter( sync_stream_t * stream, int stop )
|
|
{
|
|
int ii;
|
|
hb_buffer_t * buf;
|
|
double frame_duration, next_pts;
|
|
|
|
frame_duration = 90000. * stream->common->job->title->vrate.den /
|
|
stream->common->job->title->vrate.num;
|
|
|
|
buf = hb_list_item(stream->in_queue, 0);
|
|
buf->s.start = stream->next_pts;
|
|
next_pts = stream->next_pts + frame_duration;
|
|
for (ii = 1; ii <= stop; ii++)
|
|
{
|
|
buf->s.duration = frame_duration;
|
|
buf->s.stop = next_pts;
|
|
buf = hb_list_item(stream->in_queue, ii);
|
|
buf->s.start = next_pts;
|
|
next_pts += frame_duration;
|
|
}
|
|
}
|
|
|
|
// Look for a sequence of packets whose duration as measure by
|
|
// vrate closely matches the duration as measured by timestamp
|
|
// differences. When close matches are found, smooth the timestamps.
|
|
//
|
|
// Most often, video dejitter is applied when there is jitter due to
|
|
// soft telecine. I also have a couple sample files that have very
|
|
// bad video jitter that this corrects.
|
|
static void dejitterVideo( sync_stream_t * stream )
|
|
{
|
|
int ii, count, jitter_stop;
|
|
double frame_duration, duration;
|
|
hb_buffer_t * buf;
|
|
|
|
count = hb_list_count(stream->in_queue);
|
|
if (count < 2)
|
|
{
|
|
return;
|
|
}
|
|
|
|
frame_duration = 90000. * stream->common->job->title->vrate.den /
|
|
stream->common->job->title->vrate.num;
|
|
|
|
// Look for start of jittered sequence
|
|
buf = hb_list_item(stream->in_queue, 1);
|
|
duration = buf->s.start - stream->next_pts;
|
|
if (ABS(duration - frame_duration) < 1.1)
|
|
{
|
|
// Ignore small jitter
|
|
buf->s.start = stream->next_pts + frame_duration;
|
|
buf = hb_list_item(stream->in_queue, 0);
|
|
buf->s.start = stream->next_pts;
|
|
buf->s.duration = frame_duration;
|
|
buf->s.stop = stream->next_pts + frame_duration;
|
|
return;
|
|
}
|
|
|
|
// Look for end of jittered sequence
|
|
jitter_stop = 0;
|
|
for (ii = 1; ii < count; ii++)
|
|
{
|
|
buf = hb_list_item(stream->in_queue, ii);
|
|
duration = buf->s.start - stream->next_pts;
|
|
|
|
// Only dejitter video that aligns periodically
|
|
// with the frame durations.
|
|
if (ABS(duration - ii * frame_duration) < frame_duration / 3)
|
|
{
|
|
jitter_stop = ii;
|
|
}
|
|
}
|
|
|
|
if (jitter_stop > 0)
|
|
{
|
|
removeVideoJitter(stream, jitter_stop);
|
|
}
|
|
}
|
|
|
|
// Fix video overlaps that could not be corrected by dejitter
|
|
static void fixVideoOverlap( sync_stream_t * stream )
|
|
{
|
|
int drop = 0;
|
|
int64_t overlap;
|
|
hb_buffer_t * buf;
|
|
|
|
// If time goes backwards drop the frame.
|
|
// Check if subsequent buffers also overlap.
|
|
while ((buf = hb_list_item(stream->in_queue, 0)) != NULL)
|
|
{
|
|
// For video, an overlap is where the entire frame is
|
|
// in the past.
|
|
overlap = stream->next_pts - buf->s.stop;
|
|
if (overlap >= 0)
|
|
{
|
|
if (stream->drop == 0)
|
|
{
|
|
stream->drop_pts = buf->s.start;
|
|
}
|
|
hb_list_rem(stream->in_queue, buf);
|
|
// Video frame durations are assumed to be variable and are
|
|
// adjusted based on the start time of the next frame before
|
|
// we get to this point.
|
|
//
|
|
// Estimate duration dropped based on average framerate
|
|
stream->drop_duration +=
|
|
90000. * stream->common->job->title->vrate.den /
|
|
stream->common->job->title->vrate.num;
|
|
stream->drop++;
|
|
drop++;
|
|
saveChap(stream, buf);
|
|
hb_buffer_close(&buf);
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (drop <= 0 && stream->drop > 0)
|
|
{
|
|
hb_log("sync: video time went backwards %d ms, dropped %d frames. "
|
|
"PTS %"PRId64"",
|
|
(int)stream->drop_duration / 90, stream->drop, stream->drop_pts);
|
|
stream->drop_duration = 0;
|
|
stream->drop = 0;
|
|
}
|
|
}
|
|
|
|
static void removeAudioJitter(sync_stream_t * stream, int stop)
|
|
{
|
|
int ii;
|
|
hb_buffer_t * buf;
|
|
double next_pts;
|
|
|
|
// If duration of sum of packet durations is close to duration
|
|
// as measured by timestamps, align timestamps to packet durations.
|
|
// The packet durations are computed based on samplerate and
|
|
// number of samples and are therefore a reliable measure
|
|
// of the actual duration of an audio frame.
|
|
buf = hb_list_item(stream->in_queue, 0);
|
|
buf->s.start = stream->next_pts;
|
|
next_pts = stream->next_pts + buf->s.duration;
|
|
for (ii = 1; ii <= stop; ii++)
|
|
{
|
|
// Duration can be fractional, so track fractional PTS
|
|
buf->s.stop = next_pts;
|
|
buf = hb_list_item(stream->in_queue, ii);
|
|
buf->s.start = next_pts;
|
|
next_pts += buf->s.duration;
|
|
}
|
|
}
|
|
|
|
// Look for a sequence of packets whose duration as measure by packet
|
|
// durations closely matches the duration as measured by timestamp
|
|
// differences. When close matches are found, smooth the timestamps.
|
|
//
|
|
// This fixes issues where there are false overlaps and gaps in audio
|
|
// timestamps. libav creates this type of problem sometimes with it's
|
|
// timestamp guessing code.
|
|
static void dejitterAudio( sync_stream_t * stream )
|
|
{
|
|
int ii, count, jitter_stop;
|
|
double duration;
|
|
hb_buffer_t * buf, * buf0, * buf1;
|
|
|
|
count = hb_list_count(stream->in_queue);
|
|
if (count < 4)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Look for start of jitter sequence
|
|
jitter_stop = 0;
|
|
buf0 = hb_list_item(stream->in_queue, 0);
|
|
buf1 = hb_list_item(stream->in_queue, 1);
|
|
if (ABS(buf0->s.duration - (buf1->s.start - stream->next_pts)) < 1.1)
|
|
{
|
|
// Ignore very small jitter
|
|
return;
|
|
}
|
|
buf = hb_list_item(stream->in_queue, 0);
|
|
duration = buf->s.duration;
|
|
|
|
// Look for end of jitter sequence
|
|
for (ii = 1; ii < count; ii++)
|
|
{
|
|
buf = hb_list_item(stream->in_queue, ii);
|
|
if (ABS(duration - (buf->s.start - stream->next_pts)) < (90 * 40))
|
|
{
|
|
// Finds the largest span that has low jitter
|
|
jitter_stop = ii;
|
|
}
|
|
duration += buf->s.duration;
|
|
}
|
|
if (jitter_stop >= 4)
|
|
{
|
|
removeAudioJitter(stream, jitter_stop);
|
|
}
|
|
}
|
|
|
|
// Fix audio gaps that could not be corrected with dejitter
|
|
static void fixAudioGap( sync_stream_t * stream )
|
|
{
|
|
int64_t gap;
|
|
hb_buffer_t * buf;
|
|
|
|
if (hb_list_count(stream->in_queue) < 1 || !stream->first_frame)
|
|
{
|
|
// Can't find gaps with < 1 buffers
|
|
return;
|
|
}
|
|
|
|
buf = hb_list_item(stream->in_queue, 0);
|
|
gap = buf->s.start - stream->next_pts;
|
|
|
|
// If there's a gap of more than a minute between the last
|
|
// frame and this, assume we got a corrupted timestamp.
|
|
if (gap > 90 * 20 && gap < 90000LL * 60)
|
|
{
|
|
if (stream->gap_duration <= 0)
|
|
{
|
|
stream->gap_pts = buf->s.start;
|
|
}
|
|
buf = CreateSilenceBuf(stream, gap, stream->next_pts);
|
|
if (buf != NULL)
|
|
{
|
|
hb_buffer_t * next;
|
|
int pos;
|
|
for (pos = 0; buf != NULL; buf = next, pos++)
|
|
{
|
|
next = buf->next;
|
|
buf->next = NULL;
|
|
hb_list_insert(stream->in_queue, pos, buf);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
addDelta(stream->common, stream->next_pts, gap);
|
|
applyDeltas(stream->common);
|
|
}
|
|
stream->gap_duration += gap;
|
|
}
|
|
else
|
|
{
|
|
// If not fixing the gap, carry to the next frame
|
|
// so they do not accumulate. Do not carry negative gaps.
|
|
// Those are overlaps and are handled by fixAudioOverlap.
|
|
if (gap >= 90000LL * 60)
|
|
{
|
|
// Fix "corrupted" timestamp
|
|
buf->s.start = stream->next_pts;
|
|
}
|
|
if (stream->gap_duration > 0)
|
|
{
|
|
hb_deep_log(3, "sync: audio 0x%x time gap %d ms. PTS %"PRId64"",
|
|
stream->audio.audio->id, (int)stream->gap_duration / 90,
|
|
stream->gap_pts);
|
|
stream->gap_duration = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fix audio overlaps that could not be corrected with dejitter
|
|
static void fixAudioOverlap( sync_stream_t * stream )
|
|
{
|
|
int drop = 0;
|
|
int64_t overlap;
|
|
hb_buffer_t * buf;
|
|
|
|
if (!stream->first_frame)
|
|
{
|
|
// There are no overlaps if we haven't seen the first frame yet.
|
|
return;
|
|
}
|
|
|
|
// If time goes backwards drop the frame.
|
|
// Check if subsequent buffers also overlap.
|
|
while ((buf = hb_list_item(stream->in_queue, 0)) != NULL)
|
|
{
|
|
overlap = stream->next_pts - buf->s.start;
|
|
if (overlap > 90 * 20)
|
|
{
|
|
if (stream->drop == 0)
|
|
{
|
|
stream->drop_pts = buf->s.start;
|
|
}
|
|
// This is likely to generate a gap in audio timestamps
|
|
// This will be subsequently handled by the call to
|
|
// fix AudioGap in Synchronize(). Small gaps will be handled
|
|
// by just shifting the timestamps and carrying the gap
|
|
// along.
|
|
hb_list_rem(stream->in_queue, buf);
|
|
stream->drop_duration += buf->s.duration;
|
|
stream->drop++;
|
|
drop++;
|
|
hb_buffer_close(&buf);
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (drop <= 0 && stream->drop > 0)
|
|
{
|
|
hb_log("sync: audio 0x%x time went backwards %d ms, dropped %d frames. "
|
|
"PTS %"PRId64"",
|
|
stream->audio.audio->id, (int)stream->drop_duration / 90,
|
|
stream->drop, stream->drop_pts);
|
|
stream->drop_duration = 0;
|
|
stream->drop = 0;
|
|
}
|
|
}
|
|
|
|
static void fixSubtitleOverlap( sync_stream_t * stream )
|
|
{
|
|
hb_buffer_t * buf;
|
|
|
|
buf = hb_list_item(stream->in_queue, 0);
|
|
if (buf == NULL || (buf->s.flags & HB_BUF_FLAG_EOS) ||
|
|
(buf->s.flags & HB_BUF_FLAG_EOF))
|
|
{
|
|
// marker to indicate the end of a subtitle
|
|
return;
|
|
}
|
|
// Theoretically only SSA subs can overlap,
|
|
// but there are some SRT subs out there with
|
|
// overlapping samples, so let's try to preserve them too
|
|
if (stream->subtitle.subtitle->source != SSASUB &&
|
|
stream->subtitle.subtitle->source != IMPORTSSA &&
|
|
stream->subtitle.subtitle->source != IMPORTSRT &&
|
|
stream->subtitle.subtitle->config.dest == PASSTHRUSUB &&
|
|
buf->s.start <= stream->last_pts)
|
|
{
|
|
int64_t overlap;
|
|
overlap = stream->last_pts - buf->s.start;
|
|
hb_log("sync: subtitle 0x%x time went backwards %d ms, PTS %"PRId64"",
|
|
stream->subtitle.subtitle->id, (int)overlap / 90,
|
|
buf->s.start);
|
|
hb_list_rem(stream->in_queue, buf);
|
|
hb_buffer_close(&buf);
|
|
}
|
|
}
|
|
|
|
static void fixStreamTimestamps( sync_stream_t * stream )
|
|
{
|
|
// Fix gaps and overlaps in queue
|
|
if (stream->type == SYNC_TYPE_AUDIO)
|
|
{
|
|
dejitterAudio(stream);
|
|
fixAudioOverlap(stream);
|
|
fixAudioGap(stream);
|
|
}
|
|
else if (stream->type == SYNC_TYPE_VIDEO)
|
|
{
|
|
dejitterVideo(stream);
|
|
fixVideoOverlap(stream);
|
|
}
|
|
else if (stream->type == SYNC_TYPE_SUBTITLE)
|
|
{
|
|
fixSubtitleOverlap(stream);
|
|
}
|
|
}
|
|
|
|
static void fifo_push( hb_fifo_t * fifo, hb_buffer_t * buf )
|
|
{
|
|
if (fifo != NULL)
|
|
{
|
|
hb_fifo_push(fifo, buf);
|
|
}
|
|
else
|
|
{
|
|
hb_buffer_close(&buf);
|
|
}
|
|
}
|
|
|
|
static void streamFlush( sync_stream_t * stream )
|
|
{
|
|
while (hb_list_count(stream->in_queue) > 0)
|
|
{
|
|
hb_buffer_t * buf;
|
|
|
|
buf = hb_list_item(stream->in_queue, 0);
|
|
hb_list_rem(stream->in_queue, buf);
|
|
hb_buffer_close(&buf);
|
|
}
|
|
fifo_push(stream->fifo_out, hb_buffer_eof_init());
|
|
}
|
|
|
|
static void flushStreams( sync_common_t * common )
|
|
{
|
|
int ii;
|
|
|
|
// Make sure all streams are complete
|
|
for (ii = 0; ii < common->stream_count; ii++)
|
|
{
|
|
sync_stream_t * stream = &common->streams[ii];
|
|
if (!stream->done && !stream->flush)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!common->found_first_pts)
|
|
{
|
|
checkFirstPts(common);
|
|
}
|
|
|
|
common->flush = 1;
|
|
while (OutputBuffer(common));
|
|
|
|
// Flush all streams
|
|
for (ii = 0; ii < common->stream_count; ii++)
|
|
{
|
|
sync_stream_t * stream = &common->streams[ii];
|
|
streamFlush(stream);
|
|
}
|
|
}
|
|
|
|
static void flushStreamsLock( sync_common_t * common )
|
|
{
|
|
hb_lock(common->mutex);
|
|
flushStreams(common);
|
|
hb_unlock(common->mutex);
|
|
}
|
|
|
|
static void log_chapter( sync_common_t *common, int chap_num,
|
|
int nframes, int64_t pts )
|
|
{
|
|
hb_chapter_t *c;
|
|
|
|
if ( !common->job )
|
|
return;
|
|
|
|
c = hb_list_item( common->job->list_chapter, chap_num - 1 );
|
|
if ( c && c->title )
|
|
{
|
|
hb_log("sync: \"%s\" (%d) at frame %d time %"PRId64,
|
|
c->title, chap_num, nframes, pts);
|
|
}
|
|
else
|
|
{
|
|
hb_log("sync: Chapter %d at frame %d time %"PRId64,
|
|
chap_num, nframes, pts );
|
|
}
|
|
}
|
|
|
|
#define TOP_FIRST PIC_FLAG_TOP_FIELD_FIRST
|
|
#define PROGRESSIVE PIC_FLAG_PROGRESSIVE_FRAME
|
|
#define REPEAT_FIRST PIC_FLAG_REPEAT_FIRST_FIELD
|
|
#define TB 8
|
|
#define BT 16
|
|
#define BT_PROG 32
|
|
#define BTB_PROG 64
|
|
#define TB_PROG 128
|
|
#define TBT_PROG 256
|
|
|
|
static void checkCadence( int * cadence, hb_buffer_t * buf )
|
|
{
|
|
/* Rotate the cadence tracking. */
|
|
int i = 0;
|
|
for (i = 11; i > 0; i--)
|
|
{
|
|
cadence[i] = cadence[i-1];
|
|
}
|
|
|
|
if (!(buf->s.flags & PROGRESSIVE) && !(buf->s.flags & TOP_FIRST))
|
|
{
|
|
/* Not progressive, not top first...
|
|
That means it's probably bottom
|
|
first, 2 fields displayed.
|
|
*/
|
|
//hb_log("MPEG2 Flag: Bottom field first, 2 fields displayed.");
|
|
cadence[0] = BT;
|
|
}
|
|
else if (!(buf->s.flags & PROGRESSIVE) && (buf->s.flags & TOP_FIRST))
|
|
{
|
|
/* Not progressive, top is first,
|
|
Two fields displayed.
|
|
*/
|
|
//hb_log("MPEG2 Flag: Top field first, 2 fields displayed.");
|
|
cadence[0] = TB;
|
|
}
|
|
else if ((buf->s.flags & PROGRESSIVE) &&
|
|
!(buf->s.flags & TOP_FIRST) && !(buf->s.flags & REPEAT_FIRST))
|
|
{
|
|
/* Progressive, but noting else.
|
|
That means Bottom first,
|
|
2 fields displayed.
|
|
*/
|
|
//hb_log("MPEG2 Flag: Progressive. Bottom field first, 2 fields displayed.");
|
|
cadence[0] = BT_PROG;
|
|
}
|
|
else if ((buf->s.flags & PROGRESSIVE) &&
|
|
!(buf->s.flags & TOP_FIRST) && (buf->s.flags & REPEAT_FIRST))
|
|
{
|
|
/* Progressive, and repeat. .
|
|
That means Bottom first,
|
|
3 fields displayed.
|
|
*/
|
|
//hb_log("MPEG2 Flag: Progressive repeat. Bottom field first, 3 fields displayed.");
|
|
cadence[0] = BTB_PROG;
|
|
}
|
|
else if ((buf->s.flags & PROGRESSIVE) &&
|
|
(buf->s.flags & TOP_FIRST) && !(buf->s.flags & REPEAT_FIRST))
|
|
{
|
|
/* Progressive, top first.
|
|
That means top first,
|
|
2 fields displayed.
|
|
*/
|
|
//hb_log("MPEG2 Flag: Progressive. Top field first, 2 fields displayed.");
|
|
cadence[0] = TB_PROG;
|
|
}
|
|
else if ((buf->s.flags & PROGRESSIVE) &&
|
|
(buf->s.flags & TOP_FIRST) && (buf->s.flags & REPEAT_FIRST))
|
|
{
|
|
/* Progressive, top, repeat.
|
|
That means top first,
|
|
3 fields displayed.
|
|
*/
|
|
//hb_log("MPEG2 Flag: Progressive repeat. Top field first, 3 fields displayed.");
|
|
cadence[0] = TBT_PROG;
|
|
}
|
|
|
|
if ((cadence[2] <= TB) && (cadence[1] <= TB) &&
|
|
(cadence[0] > TB) && (cadence[11]))
|
|
{
|
|
hb_log("%fs: Video -> Film", (float)buf->s.start / 90000);
|
|
}
|
|
if ((cadence[2] > TB) && (cadence[1] <= TB) &&
|
|
(cadence[0] <= TB) && (cadence[11]))
|
|
{
|
|
hb_log("%fs: Film -> Video", (float)buf->s.start / 90000);
|
|
}
|
|
}
|
|
|
|
// When doing point-to-point encoding, subtitles can cause a long
|
|
// delay in finishing the job when the stop point is reached. This
|
|
// is due to the sparse nature of subtitles. We may not even see
|
|
// any additional subtitle till we reach the end of the file.
|
|
//
|
|
// So when all audio and video streams have reached the end point,
|
|
// force the termination of all subtitle streams by pushing an
|
|
// end-of-stream buffer into each subtitles input fifo.
|
|
// This will cause each subtitle sync worker to wake and return
|
|
// HB_WORK_DONE.
|
|
static void terminateSubtitleStreams( sync_common_t * common )
|
|
{
|
|
int ii;
|
|
|
|
// Make sure all streams are complete
|
|
for (ii = 0; ii < common->stream_count; ii++)
|
|
{
|
|
sync_stream_t * stream = &common->streams[ii];
|
|
if (stream->type == SYNC_TYPE_SUBTITLE)
|
|
{
|
|
continue;
|
|
}
|
|
if (!stream->done)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Terminate all subtitle streams
|
|
for (ii = 0; ii < common->stream_count; ii++)
|
|
{
|
|
sync_stream_t * stream = &common->streams[ii];
|
|
if (stream->done || stream->type != SYNC_TYPE_SUBTITLE)
|
|
{
|
|
continue;
|
|
}
|
|
fifo_push(stream->fifo_in, hb_buffer_eof_init());
|
|
stream->done = 1;
|
|
}
|
|
}
|
|
|
|
// OutputBuffer pulls buffers from the internal sync buffer queues in
|
|
// lowest PTS first order. It then processes the queue the buffer is
|
|
// pulled from for frame overlaps and gaps.
|
|
//
|
|
// If a queue reaches MAX depth, it is possible another queue is too low
|
|
// to achieve both goals of pulling lowest PTS first *and* perform
|
|
// timestamp correction. In this scenario, we forego lowest PTS and pull
|
|
// the next lowest PTS that has enough buffers in the queue to perform
|
|
// timestamp correction.
|
|
static int OutputBuffer( sync_common_t * common )
|
|
{
|
|
int ii, more;
|
|
int64_t pts;
|
|
sync_stream_t * out_stream;
|
|
hb_buffer_t * buf;
|
|
int out_count = 0;
|
|
|
|
do
|
|
{
|
|
more = 0;
|
|
out_stream = NULL;
|
|
pts = INT64_MAX;
|
|
|
|
// Find lowest PTS and output that buffer
|
|
for (ii = 0; ii < common->stream_count; ii++)
|
|
{
|
|
sync_stream_t * stream = &common->streams[ii];
|
|
int min = stream->min_len;
|
|
|
|
// Ignore minimum buffer requirements for video if we have
|
|
// not yet found the PtoP start frame.
|
|
if (!common->start_found && common->wait_for_frame &&
|
|
stream->type == SYNC_TYPE_VIDEO)
|
|
{
|
|
min = 0;
|
|
}
|
|
// We need at least 2 buffers in the queue in order to fix
|
|
// frame overlaps and inter-frame gaps. So if a queue is
|
|
// low, do not do normal PTS interleaving with this queue.
|
|
// Except for subtitles which are not processed for gaps
|
|
// and overlaps.
|
|
if ((common->flush && hb_list_count(stream->in_queue) > 0) ||
|
|
hb_list_count(stream->in_queue) > min)
|
|
{
|
|
buf = hb_list_item(stream->in_queue, 0);
|
|
if (buf->s.start < pts)
|
|
{
|
|
pts = buf->s.start;
|
|
out_stream = stream;
|
|
}
|
|
}
|
|
// But continue output of buffers as long as one of the queues
|
|
// is above the maximum queue level.
|
|
if ((common->flush && hb_list_count(stream->in_queue) > 0) ||
|
|
hb_list_count(stream->in_queue) > stream->max_len)
|
|
{
|
|
more = 1;
|
|
}
|
|
}
|
|
if (out_stream == NULL)
|
|
{
|
|
// This should only happen if all queues are below the
|
|
// minimum queue level
|
|
break;
|
|
}
|
|
if (out_stream->done)
|
|
{
|
|
buf = hb_list_item(out_stream->in_queue, 0);
|
|
hb_list_rem(out_stream->in_queue, buf);
|
|
hb_buffer_close(&buf);
|
|
continue;
|
|
}
|
|
|
|
if (out_stream->next_pts == (int64_t)AV_NOPTS_VALUE)
|
|
{
|
|
// Initialize next_pts, it is used to make timestamp corrections
|
|
// If doing p-to-p encoding, it will get reinitialized when
|
|
// we find the start point.
|
|
buf = hb_list_item(out_stream->in_queue, 0);
|
|
out_stream->next_pts = buf->s.start;
|
|
}
|
|
|
|
// Make timestamp adjustments to eliminate jitter, gaps, and overlaps
|
|
fixStreamTimestamps(out_stream);
|
|
|
|
buf = hb_list_item(out_stream->in_queue, 0);
|
|
if (buf == NULL)
|
|
{
|
|
// In case some timestamp sanitization causes the one and
|
|
// only buffer in the queue to be deleted...
|
|
// This really shouldn't happen.
|
|
continue;
|
|
}
|
|
if (!common->start_found)
|
|
{
|
|
// pts_to_start or frame_to_start were specified.
|
|
// Wait for the appropriate start point.
|
|
if (common->wait_for_frame)
|
|
{
|
|
if (out_stream->type != SYNC_TYPE_VIDEO)
|
|
{
|
|
// We haven't found the PtoP start frame yet and
|
|
// this buffer is either before the start frame or
|
|
// the video queue was empty.
|
|
out_stream->next_pts = buf->s.start + buf->s.duration;
|
|
hb_list_rem(out_stream->in_queue, buf);
|
|
hb_buffer_close(&buf);
|
|
continue;
|
|
}
|
|
common->start_pts = buf->s.start + 1;
|
|
if (out_stream->frame_count >= common->job->frame_to_start)
|
|
{
|
|
common->start_found = 1;
|
|
out_stream->frame_count = 0;
|
|
}
|
|
}
|
|
else if (common->wait_for_pts)
|
|
{
|
|
if (buf->s.start >= common->pts_to_start)
|
|
{
|
|
common->start_found = 1;
|
|
common->streams[0].frame_count = 0;
|
|
}
|
|
}
|
|
if (!common->start_found)
|
|
{
|
|
if (out_stream->type == SYNC_TYPE_VIDEO)
|
|
{
|
|
UpdateSearchState(common, buf->s.start,
|
|
out_stream->frame_count);
|
|
out_stream->frame_count++;
|
|
}
|
|
if (out_stream->type == SYNC_TYPE_SUBTITLE &&
|
|
buf->s.stop > common->start_pts)
|
|
{
|
|
// Subtitle ends after start time, keep sub and
|
|
// adjust it's start time
|
|
buf->s.start = common->start_pts;
|
|
}
|
|
else if (buf->s.start < common->start_pts)
|
|
{
|
|
out_stream->next_pts = buf->s.start + buf->s.duration;
|
|
hb_list_rem(out_stream->in_queue, buf);
|
|
hb_buffer_close(&buf);
|
|
}
|
|
continue;
|
|
}
|
|
// reset frame count to track number of frames after
|
|
// the start position till the end of encode.
|
|
out_stream->frame_count = 0;
|
|
|
|
shiftTS(common, buf->s.start);
|
|
alignStreams(common, buf->s.start);
|
|
setNextPts(common);
|
|
|
|
buf = hb_list_item(out_stream->in_queue, 0);
|
|
if (buf == NULL)
|
|
{
|
|
// In case aligning timestamps causes all buffers in
|
|
// out_stream to be deleted...
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// If pts_to_stop or frame_to_stop were specified, stop output
|
|
if (common->stop_pts &&
|
|
buf->s.start >= common->stop_pts )
|
|
{
|
|
switch (out_stream->type)
|
|
{
|
|
case SYNC_TYPE_VIDEO:
|
|
hb_log("sync: reached video pts %"PRId64", exiting early",
|
|
buf->s.start);
|
|
break;
|
|
case SYNC_TYPE_AUDIO:
|
|
hb_log("sync: reached audio 0x%x pts %"PRId64
|
|
", exiting early",
|
|
out_stream->audio.audio->id, buf->s.start);
|
|
break;
|
|
case SYNC_TYPE_SUBTITLE:
|
|
hb_log("sync: reached subtitle 0x%x pts %"PRId64
|
|
", exiting early",
|
|
out_stream->subtitle.subtitle->id, buf->s.start);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
out_stream->done = 1;
|
|
terminateSubtitleStreams(common);
|
|
flushStreams(common);
|
|
continue;
|
|
}
|
|
if (out_stream->type == SYNC_TYPE_VIDEO &&
|
|
common->job->frame_to_stop &&
|
|
out_stream->frame_count >= common->job->frame_to_stop)
|
|
{
|
|
hb_log("sync: reached video frame %d, exiting early",
|
|
out_stream->frame_count);
|
|
common->stop_pts = buf->s.start;
|
|
out_stream->done = 1;
|
|
terminateSubtitleStreams(common);
|
|
flushStreams(common);
|
|
continue;
|
|
}
|
|
|
|
if (out_stream->type == SYNC_TYPE_VIDEO)
|
|
{
|
|
checkCadence(out_stream->video.cadence, buf);
|
|
}
|
|
|
|
// Out the buffer goes...
|
|
hb_list_rem(out_stream->in_queue, buf);
|
|
if (out_stream->type == SYNC_TYPE_VIDEO)
|
|
{
|
|
UpdateState(common, out_stream->frame_count);
|
|
}
|
|
if (!out_stream->first_frame)
|
|
{
|
|
if (buf->s.start >= 0)
|
|
{
|
|
switch (out_stream->type)
|
|
{
|
|
case SYNC_TYPE_VIDEO:
|
|
hb_log("sync: first pts video is %"PRId64,
|
|
buf->s.start);
|
|
break;
|
|
case SYNC_TYPE_AUDIO:
|
|
hb_log("sync: first pts audio 0x%x is %"PRId64,
|
|
out_stream->audio.audio->id, buf->s.start);
|
|
break;
|
|
case SYNC_TYPE_SUBTITLE:
|
|
hb_log("sync: first pts subtitle 0x%x is %"PRId64,
|
|
out_stream->subtitle.subtitle->id, buf->s.start);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
out_stream->first_frame = 1;
|
|
out_stream->first_pts = buf->s.start;
|
|
out_stream->min_frame_duration = buf->s.duration;
|
|
}
|
|
out_stream->next_pts = buf->s.start;
|
|
}
|
|
|
|
if (out_stream->type == SYNC_TYPE_AUDIO)
|
|
{
|
|
buf = FilterAudioFrame(out_stream, buf);
|
|
if (buf == NULL)
|
|
{
|
|
// FilterAudioFrame can consume the buffer with no output
|
|
continue;
|
|
}
|
|
}
|
|
int64_t subtitle_last_pts = AV_NOPTS_VALUE;
|
|
if (out_stream->type == SYNC_TYPE_SUBTITLE)
|
|
{
|
|
buf = sanitizeSubtitle(out_stream, buf);
|
|
if (buf == NULL)
|
|
{
|
|
// sanitizeSubtitle can consume the buffer with no output
|
|
continue;
|
|
}
|
|
// sanitizeSubtitle can return a list of subtitles.
|
|
// Find the pts of the last subtitle in the list
|
|
hb_buffer_t * sub = buf;
|
|
while (sub != NULL)
|
|
{
|
|
subtitle_last_pts = sub->s.start;
|
|
sub = sub->next;
|
|
}
|
|
}
|
|
if (out_stream->type == SYNC_TYPE_AUDIO ||
|
|
out_stream->type == SYNC_TYPE_VIDEO)
|
|
{
|
|
buf->s.start = out_stream->next_pts;
|
|
buf->s.stop = out_stream->next_pts + buf->s.duration;
|
|
out_stream->last_pts = out_stream->next_pts;
|
|
out_stream->next_pts += buf->s.duration;
|
|
}
|
|
else
|
|
{
|
|
out_stream->next_pts =
|
|
out_stream->last_pts = subtitle_last_pts;
|
|
}
|
|
|
|
if (buf->s.stop > 0)
|
|
{
|
|
out_stream->current_duration = buf->s.stop -
|
|
out_stream->first_pts;
|
|
}
|
|
out_stream->frame_count++;
|
|
if (buf->s.duration > 0 &&
|
|
out_stream->min_frame_duration > buf->s.duration)
|
|
{
|
|
out_stream->min_frame_duration = buf->s.duration;
|
|
}
|
|
if (out_stream->max_frame_duration < buf->s.duration)
|
|
{
|
|
out_stream->max_frame_duration = buf->s.duration;
|
|
}
|
|
if (out_stream->type == SYNC_TYPE_VIDEO &&
|
|
buf->s.new_chap > common->chapter)
|
|
{
|
|
common->chapter = buf->s.new_chap;
|
|
log_chapter(common, buf->s.new_chap, out_stream->frame_count,
|
|
buf->s.start);
|
|
}
|
|
if (buf->s.start < 0)
|
|
{
|
|
// The pipeline can't handle negative timestamps
|
|
// and it is sometimes not possible to avoid one
|
|
// at the start of the video. There can be a
|
|
// significant delay before we see the first buffers
|
|
// from all streams. We can't buffer indefinitely
|
|
// until we have seen the first PTS for all streams
|
|
// so sometimes we may start before we have seen
|
|
// the earliest PTS
|
|
saveChap(out_stream, buf);
|
|
hb_buffer_close(&buf);
|
|
}
|
|
restoreChap(out_stream, buf);
|
|
fifo_push(out_stream->fifo_out, buf);
|
|
out_count++;
|
|
} while (more);
|
|
|
|
return out_count;
|
|
}
|
|
|
|
static void Synchronize( sync_stream_t * stream )
|
|
{
|
|
sync_common_t * common = stream->common;
|
|
|
|
// Sync deposits output directly into fifos, so work_loop is not
|
|
// blocking when output fifos become full. Wait here before
|
|
// performing any output when the output fifo for the input stream
|
|
// is full
|
|
if (stream->fifo_out != NULL && common->start_found)
|
|
{
|
|
while (!common->job->done && !*common->job->die)
|
|
{
|
|
if (hb_fifo_full_wait(stream->fifo_out))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
hb_lock(common->mutex);
|
|
|
|
if (!fillQueues(common))
|
|
{
|
|
hb_unlock(common->mutex);
|
|
return;
|
|
}
|
|
if (!common->found_first_pts)
|
|
{
|
|
checkFirstPts(common);
|
|
}
|
|
OutputBuffer(common);
|
|
|
|
hb_unlock(common->mutex);
|
|
}
|
|
|
|
static void updateDuration( sync_stream_t * stream )
|
|
{
|
|
// The video decoder sets a nominal duration for frames. But the
|
|
// actual duration needs to be computed from timestamps.
|
|
if (stream->type == SYNC_TYPE_VIDEO)
|
|
{
|
|
int count = hb_list_count(stream->in_queue);
|
|
if (count >= 2)
|
|
{
|
|
hb_buffer_t * buf1 = hb_list_item(stream->in_queue, count - 1);
|
|
hb_buffer_t * buf2 = hb_list_item(stream->in_queue, count - 2);
|
|
double duration = buf1->s.start - buf2->s.start;
|
|
if (duration > 0)
|
|
{
|
|
buf2->s.duration = duration;
|
|
buf2->s.stop = buf1->s.start;
|
|
}
|
|
else
|
|
{
|
|
buf2->s.duration = 0.;
|
|
buf2->s.start = buf2->s.stop = buf1->s.start;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void ProcessSCRDelayQueue( sync_common_t * common )
|
|
{
|
|
int ii, jj;
|
|
|
|
for (ii = 0; ii < common->stream_count; ii++)
|
|
{
|
|
sync_stream_t * stream = &common->streams[ii];
|
|
for (jj = 0; jj < hb_list_count(stream->scr_delay_queue);)
|
|
{
|
|
hb_buffer_t * buf = hb_list_item(stream->scr_delay_queue, jj);
|
|
int hash = buf->s.scr_sequence & SCR_HASH_MASK;
|
|
if (buf->s.scr_sequence < 0)
|
|
{
|
|
// Unset scr_sequence indicates an external stream
|
|
// (e.g. SRT subtitle) that is not on the same timebase
|
|
// as the source tracks. Do not adjust timestamps for
|
|
// scr_offset in this case.
|
|
hb_list_rem(stream->scr_delay_queue, buf);
|
|
SortedQueueBuffer(stream, buf);
|
|
}
|
|
else if (buf->s.scr_sequence == common->scr[hash].scr_sequence)
|
|
{
|
|
if (buf->s.start != AV_NOPTS_VALUE)
|
|
{
|
|
buf->s.start -= common->scr[hash].scr_offset;
|
|
buf->s.start -= stream->pts_slip;
|
|
}
|
|
if (buf->s.stop != AV_NOPTS_VALUE)
|
|
{
|
|
buf->s.stop -= common->scr[hash].scr_offset;
|
|
buf->s.stop -= stream->pts_slip;
|
|
}
|
|
hb_list_rem(stream->scr_delay_queue, buf);
|
|
SortedQueueBuffer(stream, buf);
|
|
}
|
|
else
|
|
{
|
|
jj++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static const char * getStreamType( sync_stream_t * stream )
|
|
{
|
|
switch (stream->type)
|
|
{
|
|
case SYNC_TYPE_VIDEO:
|
|
return "Video";
|
|
case SYNC_TYPE_AUDIO:
|
|
return "Audio";
|
|
case SYNC_TYPE_SUBTITLE:
|
|
return "Subtitle";
|
|
default:
|
|
return "Unknown";
|
|
}
|
|
}
|
|
|
|
static int getStreamId( sync_stream_t * stream )
|
|
{
|
|
switch (stream->type)
|
|
{
|
|
case SYNC_TYPE_VIDEO:
|
|
return stream->video.id;
|
|
case SYNC_TYPE_AUDIO:
|
|
return stream->audio.audio->id;
|
|
case SYNC_TYPE_SUBTITLE:
|
|
return stream->subtitle.subtitle->id;
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
static int UpdateSCR( sync_stream_t * stream, hb_buffer_t * buf )
|
|
{
|
|
int hash = buf->s.scr_sequence & SCR_HASH_MASK;
|
|
sync_common_t * common = stream->common;
|
|
double last_scr_pts, last_duration;
|
|
int64_t scr_offset = 0;
|
|
|
|
if (buf->s.scr_sequence < stream->last_scr_sequence)
|
|
{
|
|
// In decoder error conditions, the decoder can send us out of
|
|
// order frames. Often the stream error will also trigger a
|
|
// discontinuity detection. An out of order frame will
|
|
// cause an incorrect SCR offset, so drop such frames.
|
|
hb_deep_log(3, "SCR sequence went backwards %d -> %d",
|
|
stream->last_scr_sequence, buf->s.scr_sequence);
|
|
hb_buffer_close(&buf);
|
|
return 0;
|
|
}
|
|
if (buf->s.scr_sequence >= 0)
|
|
{
|
|
if (buf->s.scr_sequence != common->scr[hash].scr_sequence)
|
|
{
|
|
if (stream->type == SYNC_TYPE_SUBTITLE ||
|
|
(stream->last_scr_pts == (int64_t)AV_NOPTS_VALUE &&
|
|
common->first_scr))
|
|
{
|
|
// We got a new scr, but we have no last_scr_pts to base it
|
|
// off of. Delay till we can compute the scr offset from a
|
|
// different stream.
|
|
hb_list_add(stream->scr_delay_queue, buf);
|
|
return 0;
|
|
}
|
|
if (buf->s.start != AV_NOPTS_VALUE)
|
|
{
|
|
last_scr_pts = stream->last_scr_pts;
|
|
last_duration = stream->last_duration;
|
|
if (last_scr_pts == (int64_t)AV_NOPTS_VALUE)
|
|
{
|
|
last_scr_pts = 0.;
|
|
last_duration = 0.;
|
|
common->first_scr = 1;
|
|
}
|
|
// New SCR. Compute SCR offset
|
|
common->scr[hash].scr_sequence = buf->s.scr_sequence;
|
|
common->scr[hash].scr_offset = buf->s.start -
|
|
(last_scr_pts + last_duration);
|
|
hb_deep_log(4,
|
|
"New SCR: type %8s id %x scr seq %d scr offset %"PRId64" "
|
|
"start %"PRId64" last %f dur %f",
|
|
getStreamType(stream), getStreamId(stream),
|
|
buf->s.scr_sequence, common->scr[hash].scr_offset,
|
|
buf->s.start, last_scr_pts, last_duration);
|
|
ProcessSCRDelayQueue(common);
|
|
}
|
|
}
|
|
scr_offset = common->scr[hash].scr_offset;
|
|
}
|
|
|
|
// Adjust buffer timestamps for SCR offset
|
|
if (buf->s.start != AV_NOPTS_VALUE)
|
|
{
|
|
buf->s.start -= scr_offset;
|
|
last_scr_pts = buf->s.start;
|
|
}
|
|
else if (stream->last_scr_pts != (int64_t)AV_NOPTS_VALUE)
|
|
{
|
|
last_scr_pts = stream->last_scr_pts + stream->last_duration;
|
|
buf->s.start = last_scr_pts;
|
|
}
|
|
else
|
|
{
|
|
// This should happen extremely rarely if ever.
|
|
// But if we get here, this is the first buffer received for
|
|
// this stream. Normally, some buffers will be queued for
|
|
// every stream before we ever call UpdateSCR.
|
|
// We don't really know what it's timestamp should be.
|
|
// So drop the buffer.
|
|
hb_buffer_close(&buf);
|
|
return 0;
|
|
}
|
|
if (buf->s.stop != AV_NOPTS_VALUE)
|
|
{
|
|
buf->s.stop -= scr_offset;
|
|
}
|
|
if (last_scr_pts > stream->last_scr_pts)
|
|
{
|
|
stream->last_scr_pts = last_scr_pts;
|
|
}
|
|
if (buf->s.scr_sequence > stream->last_scr_sequence)
|
|
{
|
|
stream->last_scr_sequence = buf->s.scr_sequence;
|
|
}
|
|
stream->last_duration = buf->s.duration;
|
|
|
|
return 1;
|
|
}
|
|
|
|
// Handle broken timestamps that are out of order
|
|
// These are usually due to a broken decoder (e.g. QSV and libav AVI packed
|
|
// b-frame support). But sometimes can come from a severely broken or
|
|
// corrupted source file.
|
|
//
|
|
// We can pretty reliably fix out of order timestamps *if* every frame
|
|
// has a timestamp. But some container formats allow frames with no
|
|
// timestamp (e.g. TS and PS). When there is no timestamp, we will
|
|
// compute one based on the last frames timestamp. If the missing
|
|
// timestamp is out of order and really belonged on an earlier frame (A),
|
|
// this will result in the frame before (A) being long and the frame
|
|
// after the current will overlap current.
|
|
//
|
|
// The condition above of one long frame and one overlap will most likely
|
|
// get fixed by dejitterVideo. dejitterVideo finds sequences where the
|
|
// sum of the durations of frames 1..N == (1/fps) * N. When it finds such
|
|
// a sequence, it adjusts the frame durations to all be 1/fps. Since the
|
|
// vast majority of video is constant framerate, this will fix the above
|
|
// problem most of the time.
|
|
static void SortedQueueBuffer( sync_stream_t * stream, hb_buffer_t * buf )
|
|
{
|
|
int64_t start;
|
|
int ii, count;
|
|
|
|
start = buf->s.start;
|
|
hb_list_add(stream->in_queue, buf);
|
|
|
|
// Search for the first earlier timestamp that is < this one.
|
|
// Under normal circumstances where the timestamps are not broken,
|
|
// this will only check the next to last buffer in the queue
|
|
// before aborting.
|
|
count = hb_list_count(stream->in_queue);
|
|
for (ii = count - 2; ii >= 0; ii--)
|
|
{
|
|
buf = hb_list_item(stream->in_queue, ii);
|
|
if (buf->s.start < start || start == AV_NOPTS_VALUE)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
if (ii < count - 2)
|
|
{
|
|
hb_buffer_t * prev = NULL;
|
|
int jj;
|
|
|
|
// The timestamp was out of order.
|
|
// The timestamp belongs at position ii + 1
|
|
// Every timestamp from ii + 2 to count - 1 needs to be shifted up.
|
|
if (ii >= 0)
|
|
{
|
|
prev = hb_list_item(stream->in_queue, ii);
|
|
}
|
|
for (jj = ii + 1; jj < count; jj++)
|
|
{
|
|
int64_t tmp_start;
|
|
|
|
buf = hb_list_item(stream->in_queue, jj);
|
|
tmp_start = buf->s.start;
|
|
buf->s.start = start;
|
|
start = tmp_start;
|
|
if (stream->type == SYNC_TYPE_VIDEO && prev != NULL)
|
|
{
|
|
// recompute video buffer duration
|
|
prev->s.duration = buf->s.start - prev->s.start;
|
|
prev->s.stop = buf->s.start;
|
|
}
|
|
prev = buf;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void QueueBuffer( sync_stream_t * stream, hb_buffer_t * buf )
|
|
{
|
|
hb_lock(stream->common->mutex);
|
|
|
|
while (hb_list_count(stream->in_queue) > stream->max_len &&
|
|
!stream->done && !stream->common->job->done &&
|
|
!*stream->common->job->die)
|
|
{
|
|
// If the in_queue is full, we have to force some output to
|
|
// unblock it. Blocking here would back up the pipeline and
|
|
// stall out reader eventually.
|
|
hb_unlock(stream->common->mutex);
|
|
Synchronize(stream);
|
|
hb_lock(stream->common->mutex);
|
|
}
|
|
|
|
// Reader can change job->reader_pts_offset after initialization
|
|
// and before we receive the first buffer here. Calculate
|
|
// common->pts_to_start here since this is the first opportunity where
|
|
// all the necessary information exists.
|
|
if (stream->common->pts_to_start == AV_NOPTS_VALUE)
|
|
{
|
|
hb_job_t * job = stream->common->job;
|
|
if (job->pts_to_start > 0)
|
|
{
|
|
stream->common->start_pts =
|
|
stream->common->pts_to_start =
|
|
MAX(0, job->pts_to_start - job->reader_pts_offset);
|
|
}
|
|
}
|
|
|
|
// Render offset is only useful for decoders, which are all
|
|
// upstream of sync. Squash it.
|
|
buf->s.renderOffset = AV_NOPTS_VALUE;
|
|
|
|
hb_deep_log(11,
|
|
"type %8s id %x scr seq %d start %"PRId64" stop %"PRId64" dur %f",
|
|
getStreamType(stream), getStreamId(stream), buf->s.scr_sequence,
|
|
buf->s.start, buf->s.stop, buf->s.duration);
|
|
|
|
if (stream->common->found_first_pts)
|
|
{
|
|
if (UpdateSCR(stream, buf) > 0)
|
|
{
|
|
// Apply any stream slips.
|
|
// Stream slips will only temporarily differ between
|
|
// the streams. The slips get updated in applyDeltas. When
|
|
// all the deltas are absorbed, the stream slips will all
|
|
// be equal.
|
|
buf->s.start -= stream->pts_slip;
|
|
if (buf->s.stop != AV_NOPTS_VALUE)
|
|
{
|
|
buf->s.stop -= stream->pts_slip;
|
|
}
|
|
|
|
SortedQueueBuffer(stream, buf);
|
|
updateDuration(stream);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (buf->s.start == AV_NOPTS_VALUE &&
|
|
hb_list_count(stream->in_queue) == 0)
|
|
{
|
|
// We require an initial pts to start synchronization
|
|
saveChap(stream, buf);
|
|
hb_buffer_close(&buf);
|
|
hb_unlock(stream->common->mutex);
|
|
return;
|
|
}
|
|
SortedQueueBuffer(stream, buf);
|
|
}
|
|
|
|
// Make adjustments for gaps found in other streams
|
|
applyDeltas(stream->common);
|
|
|
|
hb_unlock(stream->common->mutex);
|
|
}
|
|
|
|
static int InitAudio( sync_common_t * common, int index )
|
|
{
|
|
hb_work_object_t * w = NULL;
|
|
hb_work_private_t * pv;
|
|
hb_audio_t * audio;
|
|
|
|
audio = hb_list_item(common->job->list_audio, index);
|
|
if (audio->priv.fifo_raw == NULL)
|
|
{
|
|
// No input fifo, not configured
|
|
return 0;
|
|
}
|
|
pv = calloc(1, sizeof(hb_work_private_t));
|
|
if (pv == NULL) goto fail;
|
|
|
|
w = hb_get_work(common->job->h, WORK_SYNC_AUDIO);
|
|
w->private_data = pv;
|
|
w->audio = audio;
|
|
w->fifo_in = audio->priv.fifo_raw;
|
|
w->fifo_out = audio->priv.fifo_sync;
|
|
|
|
pv->common = common;
|
|
pv->stream = &common->streams[1 + index];
|
|
pv->stream->common = common;
|
|
pv->stream->in_queue = hb_list_init();
|
|
pv->stream->scr_delay_queue = hb_list_init();
|
|
pv->stream->max_len = SYNC_MAX_AUDIO_QUEUE_LEN;
|
|
pv->stream->min_len = SYNC_MIN_AUDIO_QUEUE_LEN;
|
|
if (pv->stream->in_queue == NULL) goto fail;
|
|
pv->stream->delta_list = hb_list_init();
|
|
if (pv->stream->delta_list == NULL) goto fail;
|
|
pv->stream->type = SYNC_TYPE_AUDIO;
|
|
pv->stream->first_pts = AV_NOPTS_VALUE;
|
|
pv->stream->next_pts = (int64_t)AV_NOPTS_VALUE;
|
|
pv->stream->last_pts = (int64_t)AV_NOPTS_VALUE;
|
|
pv->stream->last_scr_pts = (int64_t)AV_NOPTS_VALUE;
|
|
pv->stream->last_scr_sequence = -1;
|
|
pv->stream->last_duration = (int64_t)AV_NOPTS_VALUE;
|
|
pv->stream->audio.audio = audio;
|
|
pv->stream->fifo_out = w->fifo_out;
|
|
|
|
if (!(audio->config.out.codec & HB_ACODEC_PASS_FLAG) &&
|
|
audio->config.in.samplerate != audio->config.out.samplerate)
|
|
{
|
|
/* Initialize samplerate conversion */
|
|
pv->stream->audio.resample =
|
|
hb_audio_resample_init(AV_SAMPLE_FMT_FLT,
|
|
audio->config.out.samplerate,
|
|
audio->config.out.mixdown,
|
|
audio->config.out.normalize_mix_level);
|
|
if (pv->stream->audio.resample == NULL)
|
|
{
|
|
hb_error("sync: audio 0x%x resample init failed", audio->id);
|
|
goto fail;
|
|
}
|
|
hb_audio_resample_set_sample_rate(pv->stream->audio.resample,
|
|
audio->config.in.samplerate);
|
|
if (hb_audio_resample_update(pv->stream->audio.resample))
|
|
{
|
|
hb_error("sync: audio 0x%x resample update failed", audio->id);
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
pv->stream->audio.gain_factor = pow(10, audio->config.out.gain / 20);
|
|
|
|
hb_list_add(common->list_work, w);
|
|
|
|
return 0;
|
|
|
|
fail:
|
|
if (pv != NULL)
|
|
{
|
|
if (pv->stream != NULL)
|
|
{
|
|
if (pv->stream->audio.resample)
|
|
{
|
|
hb_audio_resample_free(pv->stream->audio.resample);
|
|
}
|
|
hb_list_close(&pv->stream->delta_list);
|
|
hb_list_close(&pv->stream->in_queue);
|
|
}
|
|
}
|
|
free(pv);
|
|
free(w);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int InitSubtitle( sync_common_t * common, int index )
|
|
{
|
|
hb_work_object_t * w = NULL;
|
|
hb_work_private_t * pv;
|
|
hb_subtitle_t * subtitle;
|
|
|
|
subtitle = hb_list_item(common->job->list_subtitle, index);
|
|
if (subtitle->fifo_raw == NULL)
|
|
{
|
|
// No input fifo, not configured
|
|
return 0;
|
|
}
|
|
pv = calloc(1, sizeof(hb_work_private_t));
|
|
if (pv == NULL) goto fail;
|
|
|
|
pv->common = common;
|
|
pv->stream =
|
|
&common->streams[1 + hb_list_count(common->job->list_audio) + index];
|
|
pv->stream->common = common;
|
|
pv->stream->in_queue = hb_list_init();
|
|
pv->stream->scr_delay_queue = hb_list_init();
|
|
pv->stream->max_len = SYNC_MAX_SUBTITLE_QUEUE_LEN;
|
|
pv->stream->min_len = SYNC_MIN_SUBTITLE_QUEUE_LEN;
|
|
if (pv->stream->in_queue == NULL) goto fail;
|
|
pv->stream->delta_list = hb_list_init();
|
|
if (pv->stream->delta_list == NULL) goto fail;
|
|
pv->stream->type = SYNC_TYPE_SUBTITLE;
|
|
pv->stream->first_pts = AV_NOPTS_VALUE;
|
|
pv->stream->next_pts = (int64_t)AV_NOPTS_VALUE;
|
|
pv->stream->last_pts = (int64_t)AV_NOPTS_VALUE;
|
|
pv->stream->last_scr_pts = (int64_t)AV_NOPTS_VALUE;
|
|
pv->stream->last_scr_sequence = -1;
|
|
pv->stream->last_duration = (int64_t)AV_NOPTS_VALUE;
|
|
pv->stream->subtitle.subtitle = subtitle;
|
|
pv->stream->fifo_out = subtitle->fifo_sync;
|
|
pv->stream->fifo_in = subtitle->fifo_in;
|
|
|
|
w = hb_get_work(common->job->h, WORK_SYNC_SUBTITLE);
|
|
w->private_data = pv;
|
|
w->subtitle = subtitle;
|
|
w->fifo_in = subtitle->fifo_raw;
|
|
w->fifo_out = subtitle->fifo_sync;
|
|
|
|
memset(&pv->stream->subtitle.sanitizer, 0,
|
|
sizeof(pv->stream->subtitle.sanitizer));
|
|
if (subtitle->format == TEXTSUB && subtitle->config.dest == PASSTHRUSUB &&
|
|
(common->job->mux & HB_MUX_MASK_MP4))
|
|
{
|
|
// Merge overlapping subtitles since mpv tx3g does not support them
|
|
pv->stream->subtitle.sanitizer.merge = 1;
|
|
}
|
|
// PGS & DVB subtitles don't need to be linked because there are explicit
|
|
// "clear" subtitle packets that indicate the end time of the
|
|
// previous subtitle
|
|
if (subtitle->config.dest == PASSTHRUSUB &&
|
|
subtitle->source != PGSSUB &&
|
|
subtitle->source != DVBSUB)
|
|
{
|
|
// Fill in stop time when it is missing
|
|
pv->stream->subtitle.sanitizer.link = 1;
|
|
}
|
|
hb_buffer_list_clear(&pv->stream->subtitle.sanitizer.list_current);
|
|
|
|
hb_list_add(common->list_work, w);
|
|
|
|
return 0;
|
|
|
|
fail:
|
|
if (pv != NULL)
|
|
{
|
|
if (pv->stream != NULL)
|
|
{
|
|
hb_list_close(&pv->stream->delta_list);
|
|
hb_list_close(&pv->stream->in_queue);
|
|
}
|
|
}
|
|
free(pv);
|
|
free(w);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/***********************************************************************
|
|
* Initialize the work object
|
|
**********************************************************************/
|
|
static int syncVideoInit( hb_work_object_t * w, hb_job_t * job)
|
|
{
|
|
hb_work_private_t * pv;
|
|
int ii;
|
|
|
|
pv = calloc(1, sizeof(hb_work_private_t));
|
|
if (pv == NULL) goto fail;
|
|
w->private_data = pv;
|
|
pv->common = calloc(1, sizeof(sync_common_t));
|
|
if (pv->common == NULL) goto fail;
|
|
pv->common->job = job;
|
|
|
|
// count number of streams we need
|
|
pv->common->stream_count = 1;
|
|
pv->common->stream_count += hb_list_count(job->list_audio);
|
|
pv->common->stream_count += hb_list_count(job->list_subtitle);
|
|
pv->common->streams = calloc(pv->common->stream_count,
|
|
sizeof(sync_stream_t));
|
|
|
|
// Allocate streams
|
|
if (pv->common->streams == NULL) goto fail;
|
|
|
|
// create audio and subtitle work list
|
|
pv->common->list_work = hb_list_init();
|
|
if (pv->common->list_work == NULL) goto fail;
|
|
|
|
// mutex to mediate access to pv->common
|
|
pv->common->mutex = hb_lock_init();
|
|
if (pv->common->mutex == NULL) goto fail;
|
|
|
|
// Set up video sync work object
|
|
pv->stream = &pv->common->streams[0];
|
|
pv->stream->common = pv->common;
|
|
pv->stream->in_queue = hb_list_init();
|
|
pv->stream->scr_delay_queue = hb_list_init();
|
|
pv->stream->max_len = SYNC_MAX_VIDEO_QUEUE_LEN;
|
|
pv->stream->min_len = SYNC_MIN_VIDEO_QUEUE_LEN;
|
|
if (pv->stream->in_queue == NULL) goto fail;
|
|
pv->stream->delta_list = hb_list_init();
|
|
if (pv->stream->delta_list == NULL) goto fail;
|
|
pv->stream->type = SYNC_TYPE_VIDEO;
|
|
pv->stream->first_pts = AV_NOPTS_VALUE;
|
|
pv->stream->next_pts = (int64_t)AV_NOPTS_VALUE;
|
|
pv->stream->last_pts = (int64_t)AV_NOPTS_VALUE;
|
|
pv->stream->last_scr_pts = (int64_t)AV_NOPTS_VALUE;
|
|
pv->stream->last_scr_sequence = -1;
|
|
pv->stream->last_duration = (int64_t)AV_NOPTS_VALUE;
|
|
pv->stream->fifo_out = job->fifo_sync;
|
|
pv->stream->video.id = job->title->video_id;
|
|
|
|
w->fifo_in = job->fifo_raw;
|
|
w->fifo_out = job->fifo_sync;
|
|
|
|
if (job->pass_id == HB_PASS_ENCODE_FINAL)
|
|
{
|
|
/* We already have an accurate frame count from pass 1 */
|
|
hb_interjob_t * interjob = hb_interjob_get(job->h);
|
|
pv->common->est_frame_count = interjob->frame_count;
|
|
}
|
|
else
|
|
{
|
|
/* Calculate how many video frames we are expecting */
|
|
if (job->frame_to_stop)
|
|
{
|
|
pv->common->est_frame_count = job->frame_to_stop;
|
|
}
|
|
else
|
|
{
|
|
int64_t duration, total_duration, extra_duration = 0;
|
|
if (job->pts_to_stop)
|
|
{
|
|
duration = job->pts_to_stop + 90000;
|
|
}
|
|
else
|
|
{
|
|
total_duration = 0;
|
|
for (ii = 0; ii <= hb_list_count(job->list_chapter); ii++)
|
|
{
|
|
hb_chapter_t * chapter;
|
|
chapter = hb_list_item(job->list_chapter, ii - 1);
|
|
if (chapter != NULL)
|
|
{
|
|
total_duration += chapter->duration;
|
|
}
|
|
}
|
|
// Some titles are longer than the sum duration of their
|
|
// chapters. Account for this extra duration.
|
|
if (job->title->duration > total_duration)
|
|
{
|
|
extra_duration = job->title->duration - total_duration;
|
|
}
|
|
duration = 0;
|
|
for (ii = job->chapter_start; ii <= job->chapter_end; ii++)
|
|
{
|
|
hb_chapter_t * chapter;
|
|
chapter = hb_list_item(job->list_chapter, ii - 1);
|
|
if (chapter != NULL)
|
|
{
|
|
duration += chapter->duration;
|
|
}
|
|
}
|
|
if (job->chapter_end == hb_list_count(job->list_chapter))
|
|
{
|
|
duration += extra_duration;
|
|
}
|
|
}
|
|
pv->common->est_frame_count = duration * job->title->vrate.num /
|
|
job->title->vrate.den / 90000;
|
|
}
|
|
}
|
|
hb_log("sync: expecting %d video frames", pv->common->est_frame_count);
|
|
|
|
// Initialize audio sync work objects
|
|
for (ii = 0; ii < hb_list_count(job->list_audio); ii++ )
|
|
{
|
|
if (InitAudio(pv->common, ii)) goto fail;
|
|
}
|
|
|
|
// Initialize subtitle sync work objects
|
|
for (ii = 0; ii < hb_list_count(job->list_subtitle); ii++ )
|
|
{
|
|
if (InitSubtitle(pv->common, ii)) goto fail;
|
|
}
|
|
|
|
/* Launch work processing threads */
|
|
for (ii = 0; ii < hb_list_count(pv->common->list_work); ii++)
|
|
{
|
|
hb_work_object_t * work;
|
|
work = hb_list_item(pv->common->list_work, ii);
|
|
work->done = w->done;
|
|
work->thread = hb_thread_init(work->name, hb_work_loop,
|
|
work, HB_LOW_PRIORITY);
|
|
}
|
|
|
|
if (job->frame_to_start || job->pts_to_start)
|
|
{
|
|
pv->common->start_found = 0;
|
|
pv->common->start_pts = job->pts_to_start;
|
|
pv->common->wait_for_frame = !!job->frame_to_start;
|
|
pv->common->wait_for_pts = !!job->pts_to_start;
|
|
if (job->pts_to_start)
|
|
{
|
|
pv->common->pts_to_start = AV_NOPTS_VALUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pv->common->start_found = 1;
|
|
}
|
|
if (job->pts_to_stop)
|
|
{
|
|
pv->common->stop_pts = job->pts_to_stop;
|
|
}
|
|
|
|
return 0;
|
|
|
|
fail:
|
|
if (pv != NULL)
|
|
{
|
|
if (pv->common != NULL)
|
|
{
|
|
for (ii = 0; ii < hb_list_count(pv->common->list_work); ii++)
|
|
{
|
|
hb_work_object_t * work;
|
|
work = hb_list_item(pv->common->list_work, ii);
|
|
if (work->close) work->close(work);
|
|
}
|
|
hb_list_close(&pv->common->list_work);
|
|
hb_lock_close(&pv->common->mutex);
|
|
if (pv->stream != NULL)
|
|
{
|
|
hb_list_close(&pv->stream->delta_list);
|
|
hb_list_close(&pv->stream->in_queue);
|
|
}
|
|
free(pv->common->streams);
|
|
free(pv->common);
|
|
}
|
|
}
|
|
free(pv);
|
|
w->private_data = NULL;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/***********************************************************************
|
|
* Close Video
|
|
***********************************************************************
|
|
*
|
|
**********************************************************************/
|
|
static void syncVideoClose( hb_work_object_t * w )
|
|
{
|
|
hb_work_private_t * pv = w->private_data;
|
|
hb_job_t * job;
|
|
|
|
if (pv == NULL)
|
|
{
|
|
return;
|
|
}
|
|
job = pv->common->job;
|
|
|
|
hb_log("sync: got %d frames, %d expected",
|
|
pv->stream->frame_count, pv->common->est_frame_count );
|
|
if (pv->stream->min_frame_duration > 0 &&
|
|
pv->stream->max_frame_duration > 0 &&
|
|
pv->stream->current_duration > 0)
|
|
{
|
|
hb_log("sync: framerate min %.3f fps, max %.3f fps, avg %.3f fps",
|
|
90000. / pv->stream->max_frame_duration,
|
|
90000. / pv->stream->min_frame_duration,
|
|
(pv->stream->frame_count * 90000.) /
|
|
pv->stream->current_duration);
|
|
}
|
|
|
|
/* save data for second pass */
|
|
if( job->pass_id == HB_PASS_ENCODE_ANALYSIS )
|
|
{
|
|
/* Preserve frame count for better accuracy in pass 2 */
|
|
hb_interjob_t * interjob = hb_interjob_get( job->h );
|
|
interjob->frame_count = pv->stream->frame_count;
|
|
}
|
|
sync_delta_t * delta;
|
|
while ((delta = hb_list_item(pv->stream->delta_list, 0)) != NULL)
|
|
{
|
|
hb_list_rem(pv->stream->delta_list, delta);
|
|
free(delta);
|
|
}
|
|
hb_list_close(&pv->stream->delta_list);
|
|
hb_list_empty(&pv->stream->in_queue);
|
|
hb_list_empty(&pv->stream->scr_delay_queue);
|
|
|
|
// Close work threads
|
|
hb_work_object_t * work;
|
|
while ((work = hb_list_item(pv->common->list_work, 0)))
|
|
{
|
|
hb_list_rem(pv->common->list_work, work);
|
|
if (work->thread != NULL)
|
|
{
|
|
hb_thread_close(&work->thread);
|
|
}
|
|
if (work->close) work->close(work);
|
|
free(work);
|
|
}
|
|
hb_list_close(&pv->common->list_work);
|
|
|
|
hb_lock_close(&pv->common->mutex);
|
|
free(pv->common->streams);
|
|
free(pv->common);
|
|
free(pv);
|
|
w->private_data = NULL;
|
|
}
|
|
|
|
static hb_buffer_t * merge_ssa(hb_buffer_t *a, hb_buffer_t *b)
|
|
{
|
|
int len, ii;
|
|
char *text;
|
|
hb_buffer_t *buf;
|
|
|
|
if (a == NULL && b == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
if (a == NULL)
|
|
{
|
|
return hb_buffer_dup(b);
|
|
}
|
|
if (b == NULL)
|
|
{
|
|
return hb_buffer_dup(a);
|
|
}
|
|
|
|
buf = hb_buffer_init(a->size + b->size);
|
|
buf->s = a->s;
|
|
|
|
// Find the text in the second SSA sub
|
|
text = (char*)b->data;
|
|
for (ii = 0; ii < 8; ii++)
|
|
{
|
|
text = strchr(text, ',');
|
|
if (text == NULL)
|
|
break;
|
|
text++;
|
|
}
|
|
if (text != NULL)
|
|
{
|
|
// Strip trailing CR and/or LF
|
|
len = strlen((char*)a->data);
|
|
if (len > 0 && a->data[len - 1] == '\n')
|
|
{
|
|
a->data[len - 1] = 0;
|
|
len--;
|
|
if (len > 0 && a->data[len - 1] == '\r')
|
|
{
|
|
a->data[len - 1] = 0;
|
|
}
|
|
}
|
|
// Text subtitles are SSA internally. Use SSA newline code
|
|
// and force style reset at beginning of new line.
|
|
len = snprintf((char*)buf->data, buf->size, "%s\\N{\\r}%s", a->data, text);
|
|
if (len >= 0)
|
|
buf->size = len + 1;
|
|
}
|
|
else
|
|
{
|
|
memcpy(buf->data, a->data, a->size);
|
|
buf->size = a->size;
|
|
}
|
|
|
|
return buf;
|
|
}
|
|
|
|
static hb_buffer_t * setSubDuration(sync_stream_t * stream, hb_buffer_t * sub)
|
|
{
|
|
if (sub->s.flags & HB_BUF_FLAG_EOS)
|
|
{
|
|
return sub;
|
|
}
|
|
if (sub->s.stop != AV_NOPTS_VALUE)
|
|
{
|
|
sub->s.duration = sub->s.stop - sub->s.start;
|
|
if (sub->s.duration <= 0)
|
|
{
|
|
hb_log("sync: subtitle 0x%x duration <= 0, PTS %"PRId64"",
|
|
stream->subtitle.subtitle->id, sub->s.start);
|
|
hb_buffer_close(&sub);
|
|
}
|
|
}
|
|
else if (stream->subtitle.sanitizer.link)
|
|
{
|
|
hb_log("sync: subtitle 0x%x duration not set, PTS %"PRId64"",
|
|
stream->subtitle.subtitle->id, sub->s.start);
|
|
sub->s.duration = (int64_t)AV_NOPTS_VALUE;
|
|
}
|
|
return sub;
|
|
}
|
|
|
|
// Create a list of buffers that overlap the given start time.
|
|
// Returns a list of buffers and the smallest stop time of those
|
|
// buffers.
|
|
static hb_buffer_t * findOverlap(subtitle_sanitizer_t *sanitizer,
|
|
int64_t start, int64_t *stop_out)
|
|
{
|
|
hb_buffer_list_t list;
|
|
hb_buffer_t * buf;
|
|
int64_t stop;
|
|
|
|
stop = INT64_MAX;
|
|
hb_buffer_list_clear(&list);
|
|
buf = hb_buffer_list_head(&sanitizer->list_current);
|
|
while (buf != NULL)
|
|
{
|
|
if (buf->s.flags & HB_BUF_FLAG_EOF)
|
|
{
|
|
break;
|
|
}
|
|
if (buf->s.start > start)
|
|
{
|
|
if (stop > buf->s.start)
|
|
{
|
|
*stop_out = buf->s.start;
|
|
}
|
|
break;
|
|
}
|
|
if (buf->s.start <= start && start < buf->s.stop)
|
|
{
|
|
hb_buffer_t * tmp = hb_buffer_dup(buf);
|
|
tmp->s.start = start;
|
|
hb_buffer_list_append(&list, tmp);
|
|
if (stop > buf->s.stop)
|
|
{
|
|
stop = buf->s.stop;
|
|
*stop_out = stop;
|
|
}
|
|
}
|
|
buf = buf->next;
|
|
}
|
|
|
|
return hb_buffer_list_clear(&list);
|
|
}
|
|
|
|
// Find all subtitles in the list that start "now" and overlap for
|
|
// some period of time. Create a new subtitle buffer that is the
|
|
// merged results of the overlapping parts and update start times
|
|
// of non-overlapping parts.
|
|
static int mergeSubtitleOverlaps(subtitle_sanitizer_t *sanitizer)
|
|
{
|
|
hb_buffer_t * merged_buf = NULL;
|
|
hb_buffer_t * a, * b;
|
|
|
|
a = hb_buffer_list_head(&sanitizer->list_current);
|
|
if (a != NULL && (a->s.flags & HB_BUF_FLAG_EOF))
|
|
{
|
|
// EOF
|
|
return 0;
|
|
}
|
|
if (a == NULL ||
|
|
a->s.start == AV_NOPTS_VALUE || a->s.stop == AV_NOPTS_VALUE)
|
|
{
|
|
// Not enough information to resolve an overlap
|
|
return -1;
|
|
}
|
|
b = a->next;
|
|
if (b != NULL && a->s.stop <= b->s.start)
|
|
{
|
|
// No overlap
|
|
return 0;
|
|
}
|
|
|
|
// Check that we have 2 non-overlapping buffers in the list
|
|
// and that all timestamps are valid up to the non-overlap.
|
|
// This ensures that multiple overlapping subtitles have been
|
|
// completely merged.
|
|
while (b != NULL &&
|
|
b->s.start < a->s.stop && !(b->s.flags & HB_BUF_FLAG_EOF))
|
|
{
|
|
if (b->s.start == AV_NOPTS_VALUE || b->s.stop == AV_NOPTS_VALUE)
|
|
{
|
|
// Not enough information to resolve an overlap
|
|
return -1;
|
|
}
|
|
b = b->next;
|
|
}
|
|
if (b == NULL)
|
|
{
|
|
// Not enough information to resolve an overlap
|
|
return -1;
|
|
}
|
|
|
|
hb_buffer_list_t merged_list;
|
|
int64_t start, stop, last;
|
|
|
|
if (b->s.flags & HB_BUF_FLAG_EOF)
|
|
{
|
|
last = INT64_MAX;
|
|
}
|
|
else
|
|
{
|
|
last = b->s.start;
|
|
}
|
|
|
|
hb_buffer_list_clear(&merged_list);
|
|
a = hb_buffer_list_head(&sanitizer->list_current);
|
|
stop = start = a->s.start;
|
|
while (start < last)
|
|
{
|
|
hb_buffer_t * merge = findOverlap(sanitizer, start, &stop);
|
|
if (merge == NULL)
|
|
{
|
|
break;
|
|
}
|
|
a = merge;
|
|
merged_buf = NULL;
|
|
while (a != NULL)
|
|
{
|
|
hb_buffer_t * tmp;
|
|
|
|
tmp = merge_ssa(merged_buf, a);
|
|
hb_buffer_close(&merged_buf);
|
|
merged_buf = tmp;
|
|
|
|
a = a->next;
|
|
}
|
|
merged_buf->s.stop = stop;
|
|
hb_buffer_close(&merge);
|
|
hb_buffer_list_append(&merged_list, merged_buf);
|
|
start = stop;
|
|
|
|
// Remove merged buffers
|
|
a = hb_buffer_list_head(&sanitizer->list_current);
|
|
while (a != NULL && a->s.start < stop &&
|
|
!(a->s.flags & HB_BUF_FLAG_EOF))
|
|
{
|
|
hb_buffer_t * next = a->next;
|
|
if (a->s.stop <= stop)
|
|
{
|
|
// Buffer consumed
|
|
hb_buffer_list_rem(&sanitizer->list_current, a);
|
|
hb_buffer_close(&a);
|
|
}
|
|
else
|
|
{
|
|
a->s.start = stop;
|
|
}
|
|
a = next;
|
|
}
|
|
}
|
|
merged_buf = hb_buffer_list_clear(&merged_list);
|
|
hb_buffer_list_prepend(&sanitizer->list_current, merged_buf);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static hb_buffer_t * mergeSubtitles(sync_stream_t * stream)
|
|
{
|
|
hb_buffer_t * buf;
|
|
hb_buffer_list_t list;
|
|
subtitle_sanitizer_t * sanitizer = &stream->subtitle.sanitizer;
|
|
|
|
hb_buffer_list_clear(&list);
|
|
|
|
if (!sanitizer->merge)
|
|
{
|
|
// Handle all but the last buffer
|
|
// The last buffer may not have been "linked" yet
|
|
while (hb_buffer_list_count(&sanitizer->list_current) > 0)
|
|
{
|
|
buf = hb_buffer_list_head(&sanitizer->list_current);
|
|
if (!(buf->s.flags & HB_BUF_FLAG_EOF) &&
|
|
buf->s.stop != AV_NOPTS_VALUE)
|
|
{
|
|
buf = hb_buffer_list_rem_head(&sanitizer->list_current);
|
|
buf = setSubDuration(stream, buf);
|
|
hb_buffer_list_append(&list, buf);
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
return hb_buffer_list_clear(&list);
|
|
}
|
|
|
|
// We only reach here if we are merging subtitles
|
|
while (hb_buffer_list_count(&sanitizer->list_current) > 0)
|
|
{
|
|
buf = hb_buffer_list_head(&sanitizer->list_current);
|
|
if (buf->s.flags & HB_BUF_FLAG_EOF)
|
|
{
|
|
// remove EOF from list, add to output
|
|
buf = hb_buffer_list_rem_head(&sanitizer->list_current);
|
|
hb_buffer_list_append(&list, buf);
|
|
break;
|
|
}
|
|
|
|
int result = mergeSubtitleOverlaps(sanitizer);
|
|
if (result < 0)
|
|
{
|
|
// not enough information available yet to resolve the overlap
|
|
break;
|
|
}
|
|
|
|
// Overlap resolved, output a buffer
|
|
buf = hb_buffer_list_rem_head(&sanitizer->list_current);
|
|
if (buf != NULL && !(buf->s.flags & HB_BUF_FLAG_EOF))
|
|
{
|
|
buf = setSubDuration(stream, buf);
|
|
hb_buffer_list_append(&list, buf);
|
|
}
|
|
}
|
|
|
|
return hb_buffer_list_clear(&list);
|
|
}
|
|
|
|
static hb_buffer_t * sanitizeSubtitle(
|
|
sync_stream_t * stream,
|
|
hb_buffer_t * sub)
|
|
{
|
|
hb_buffer_list_t list;
|
|
subtitle_sanitizer_t * sanitizer = &stream->subtitle.sanitizer;
|
|
|
|
if (sub == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
hb_buffer_list_set(&list, sub);
|
|
if (!sanitizer->link && !sanitizer->merge)
|
|
{
|
|
hb_buffer_list_t out_list;
|
|
hb_buffer_list_clear(&out_list);
|
|
|
|
sub = hb_buffer_list_rem_head(&list);
|
|
while (sub != NULL && !(sub->s.flags & HB_BUF_FLAG_EOF))
|
|
{
|
|
sub = setSubDuration(stream, sub);
|
|
hb_buffer_list_append(&out_list, sub);
|
|
sub = hb_buffer_list_rem_head(&list);
|
|
}
|
|
if (sub != NULL)
|
|
{
|
|
hb_buffer_close(&sub);
|
|
}
|
|
return hb_buffer_list_clear(&out_list);
|
|
}
|
|
|
|
sub = hb_buffer_list_rem_head(&list);
|
|
while (sub != NULL)
|
|
{
|
|
if (sub->s.flags & HB_BUF_FLAG_EOF)
|
|
{
|
|
hb_buffer_list_append(&sanitizer->list_current, sub);
|
|
break;
|
|
}
|
|
|
|
hb_buffer_t *last = hb_buffer_list_tail(&sanitizer->list_current);
|
|
if (last != NULL && last->s.stop == AV_NOPTS_VALUE)
|
|
{
|
|
last->s.stop = sub->s.start;
|
|
if (last->s.stop <= last->s.start)
|
|
{
|
|
// Subtitle duration <= 0. Drop it.
|
|
hb_log("sync: subtitle 0x%x has no duration, PTS %"PRId64"",
|
|
stream->subtitle.subtitle->id, sub->s.start);
|
|
hb_buffer_list_rem_tail(&sanitizer->list_current);
|
|
hb_buffer_close(&last);
|
|
}
|
|
}
|
|
|
|
if (sub->s.flags & HB_BUF_FLAG_EOS)
|
|
{
|
|
// Used to indicate "clear" subtitles when the duration
|
|
// of subtitles is not encoded in the stream
|
|
hb_buffer_close(&sub);
|
|
}
|
|
hb_buffer_list_append(&sanitizer->list_current, sub);
|
|
sub = hb_buffer_list_rem_head(&list);
|
|
}
|
|
|
|
return mergeSubtitles(stream);
|
|
}
|
|
|
|
/***********************************************************************
|
|
* syncVideoWork
|
|
***********************************************************************
|
|
*
|
|
**********************************************************************/
|
|
static int syncVideoWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
|
|
hb_buffer_t ** buf_out )
|
|
{
|
|
hb_work_private_t * pv = w->private_data;
|
|
hb_buffer_t * in = *buf_in;
|
|
|
|
#if HB_PROJECT_FEATURE_QSV
|
|
// Fix of LA case allowing use of LA up to 40 in full encode path,
|
|
// as currently for such support we cannot allocate >64 slices per texture
|
|
// due to MSFT limitation, not impacting other cases
|
|
hb_job_t *job = pv->common->job;
|
|
if (job->hw_pix_fmt == AV_PIX_FMT_QSV &&
|
|
job->qsv_ctx->la_is_enabled == 1)
|
|
{
|
|
pv->stream->max_len = SYNC_MIN_VIDEO_QUEUE_LEN;
|
|
pv->common->job->qsv_ctx->la_is_enabled++;
|
|
}
|
|
#endif
|
|
|
|
if (pv->stream->done)
|
|
{
|
|
flushStreamsLock(pv->common);
|
|
return HB_WORK_DONE;
|
|
}
|
|
if (in->s.flags & HB_BUF_FLAG_EOF)
|
|
{
|
|
pv->stream->flush = 1;
|
|
flushStreamsLock(pv->common);
|
|
// Ideally, we would only do this subtitle scan check in
|
|
// syncSubtitleWork, but someone might try to do a subtitle
|
|
// scan on a source that has no subtitles :-(
|
|
if (pv->common->job->indepth_scan)
|
|
{
|
|
// When doing subtitle indepth scan, the pipeline ends at sync.
|
|
// Terminate job when EOF reached.
|
|
*w->done = 1;
|
|
}
|
|
return HB_WORK_DONE;
|
|
}
|
|
|
|
*buf_in = NULL;
|
|
QueueBuffer(pv->stream, in);
|
|
Synchronize(pv->stream);
|
|
|
|
if (pv->stream->done)
|
|
{
|
|
flushStreamsLock(pv->common);
|
|
return HB_WORK_DONE;
|
|
}
|
|
return HB_WORK_OK;
|
|
}
|
|
|
|
hb_work_object_t hb_sync_video =
|
|
{
|
|
WORK_SYNC_VIDEO,
|
|
"Video Synchronization",
|
|
syncVideoInit,
|
|
syncVideoWork,
|
|
syncVideoClose
|
|
};
|
|
|
|
/***********************************************************************
|
|
* Close Audio
|
|
***********************************************************************
|
|
*
|
|
**********************************************************************/
|
|
static void syncAudioClose( hb_work_object_t * w )
|
|
{
|
|
hb_work_private_t * pv = w->private_data;
|
|
|
|
if (pv == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Free samplerate conversion context
|
|
if (pv->stream->audio.resample)
|
|
{
|
|
hb_audio_resample_free(pv->stream->audio.resample);
|
|
}
|
|
|
|
sync_delta_t * delta;
|
|
while ((delta = hb_list_item(pv->stream->delta_list, 0)) != NULL)
|
|
{
|
|
hb_list_rem(pv->stream->delta_list, delta);
|
|
free(delta);
|
|
}
|
|
hb_list_close(&pv->stream->delta_list);
|
|
hb_list_empty(&pv->stream->in_queue);
|
|
hb_list_empty(&pv->stream->scr_delay_queue);
|
|
free(pv);
|
|
w->private_data = NULL;
|
|
}
|
|
|
|
static int syncAudioInit( hb_work_object_t * w, hb_job_t * job)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/***********************************************************************
|
|
* SyncAudio
|
|
***********************************************************************
|
|
*
|
|
**********************************************************************/
|
|
static int syncAudioWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
|
|
hb_buffer_t ** buf_out )
|
|
{
|
|
hb_work_private_t * pv = w->private_data;
|
|
hb_buffer_t * in = *buf_in;
|
|
|
|
if (pv->stream->done)
|
|
{
|
|
flushStreamsLock(pv->common);
|
|
return HB_WORK_DONE;
|
|
}
|
|
if (in->s.flags & HB_BUF_FLAG_EOF)
|
|
{
|
|
pv->stream->flush = 1;
|
|
flushStreamsLock(pv->common);
|
|
return HB_WORK_DONE;
|
|
}
|
|
|
|
*buf_in = NULL;
|
|
QueueBuffer(pv->stream, in);
|
|
Synchronize(pv->stream);
|
|
|
|
if (pv->stream->done)
|
|
{
|
|
flushStreamsLock(pv->common);
|
|
return HB_WORK_DONE;
|
|
}
|
|
return HB_WORK_OK;
|
|
}
|
|
|
|
hb_work_object_t hb_sync_audio =
|
|
{
|
|
WORK_SYNC_AUDIO,
|
|
"Audio Synchronization",
|
|
syncAudioInit,
|
|
syncAudioWork,
|
|
syncAudioClose
|
|
};
|
|
|
|
// FilterAudioFrame is called after audio timestamp discontinuities
|
|
// have all been corrected. So we expect smooth continuous audio
|
|
// here.
|
|
static hb_buffer_t * FilterAudioFrame( sync_stream_t * stream,
|
|
hb_buffer_t *buf )
|
|
{
|
|
hb_audio_t * audio = stream->audio.audio;
|
|
|
|
// Can't count of buf->s.stop - buf->s.start for accurate duration
|
|
// due to integer rounding, so use buf->s.duration when it is set
|
|
// (which should be always if I didn't miss anything)
|
|
if (buf->s.duration <= 0)
|
|
{
|
|
buf->s.duration = buf->s.stop - buf->s.start;
|
|
}
|
|
|
|
if (!(audio->config.out.codec & HB_ACODEC_PASS_FLAG))
|
|
{
|
|
// TODO: this should all be replaced by an audio filter chain.
|
|
|
|
// Audio is not passthru. Check if we need to modify the audio
|
|
// in any way.
|
|
if (stream->audio.resample != NULL)
|
|
{
|
|
/* do sample rate conversion */
|
|
hb_buffer_t * out;
|
|
int nsamples, sample_size;
|
|
|
|
sample_size = hb_mixdown_get_discrete_channel_count(
|
|
audio->config.out.mixdown ) * sizeof( float );
|
|
|
|
nsamples = buf->size / sample_size;
|
|
out = hb_audio_resample(stream->audio.resample,
|
|
(const uint8_t **)&buf->data,
|
|
nsamples);
|
|
hb_buffer_close(&buf);
|
|
if (out == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
out->s.start = stream->next_pts;
|
|
out->s.stop = stream->next_pts + out->s.duration;
|
|
buf = out;
|
|
}
|
|
if (audio->config.out.gain > 0.0)
|
|
{
|
|
int count, ii;
|
|
|
|
count = buf->size / sizeof(float);
|
|
for ( ii = 0; ii < count; ii++ )
|
|
{
|
|
double sample;
|
|
|
|
sample = (double)*(((float*)buf->data)+ii);
|
|
sample *= stream->audio.gain_factor;
|
|
if (sample > 0)
|
|
sample = MIN(sample, 1.0);
|
|
else
|
|
sample = MAX(sample, -1.0);
|
|
*(((float*)buf->data)+ii) = sample;
|
|
}
|
|
}
|
|
else if( audio->config.out.gain < 0.0 )
|
|
{
|
|
int count, ii;
|
|
|
|
count = buf->size / sizeof(float);
|
|
for ( ii = 0; ii < count; ii++ )
|
|
{
|
|
double sample;
|
|
|
|
sample = (double)*(((float*)buf->data)+ii);
|
|
sample *= stream->audio.gain_factor;
|
|
*(((float*)buf->data)+ii) = sample;
|
|
}
|
|
}
|
|
}
|
|
|
|
buf->s.type = AUDIO_BUF;
|
|
buf->s.frametype = HB_FRAME_AUDIO;
|
|
|
|
return buf;
|
|
}
|
|
|
|
static void UpdateState( sync_common_t * common, int frame_count )
|
|
{
|
|
hb_job_t * job = common->job;
|
|
hb_state_t state;
|
|
|
|
if (job->indepth_scan)
|
|
{
|
|
// Progress for indepth scan is handled by reader
|
|
// frame_count is used during indepth_scan
|
|
// to find start & end points.
|
|
return;
|
|
}
|
|
|
|
if (frame_count == 0)
|
|
{
|
|
common->st_first = hb_get_date();
|
|
}
|
|
|
|
if (hb_get_date() > common->st_dates[3] + 1000)
|
|
{
|
|
memmove( &common->st_dates[0], &common->st_dates[1],
|
|
3 * sizeof( uint64_t ) );
|
|
memmove( &common->st_counts[0], &common->st_counts[1],
|
|
3 * sizeof( uint64_t ) );
|
|
common->st_dates[3] = hb_get_date();
|
|
common->st_counts[3] = frame_count;
|
|
}
|
|
|
|
hb_get_state2(job->h, &state);
|
|
state.state = HB_STATE_WORKING;
|
|
|
|
#define p state.param.working
|
|
p.progress = (float)frame_count / common->est_frame_count;
|
|
if (p.progress > 1.0)
|
|
{
|
|
p.progress = 1.0;
|
|
}
|
|
p.rate_cur = 1000.0 * (common->st_counts[3] - common->st_counts[0]) /
|
|
(common->st_dates[3] - common->st_dates[0]);
|
|
if (hb_get_date() > common->st_first + 4000)
|
|
{
|
|
p.rate_avg = 1000.0 * common->st_counts[3] /
|
|
(common->st_dates[3] - common->st_first - job->st_paused);
|
|
if (common->est_frame_count >= common->st_counts[3])
|
|
{
|
|
int eta = (common->est_frame_count - common->st_counts[3]) / p.rate_avg;
|
|
p.eta_seconds = eta;
|
|
p.hours = eta / 3600;
|
|
p.minutes = (eta % 3600) / 60;
|
|
p.seconds = eta % 60;
|
|
}
|
|
else
|
|
{
|
|
p.eta_seconds = 0;
|
|
p.hours = -1;
|
|
p.minutes = -1;
|
|
p.seconds = -1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
p.rate_avg = 0.0;
|
|
p.hours = -1;
|
|
p.minutes = -1;
|
|
p.seconds = -1;
|
|
}
|
|
#undef p
|
|
|
|
hb_set_state(job->h, &state);
|
|
}
|
|
|
|
static void UpdateSearchState( sync_common_t * common, int64_t start,
|
|
int frame_count )
|
|
{
|
|
hb_job_t * job = common->job;
|
|
hb_state_t state;
|
|
uint64_t now;
|
|
double avg;
|
|
|
|
if (job->indepth_scan)
|
|
{
|
|
// Progress for indepth scan is handled by reader
|
|
// frame_count is used during indepth_scan
|
|
// to find start & end points.
|
|
return;
|
|
}
|
|
|
|
now = hb_get_date();
|
|
if (frame_count == 0)
|
|
{
|
|
common->st_first = now;
|
|
}
|
|
|
|
hb_get_state2(job->h, &state);
|
|
state.state = HB_STATE_SEARCHING;
|
|
|
|
#define p state.param.working
|
|
if (common->wait_for_frame)
|
|
p.progress = (float)frame_count / job->frame_to_start;
|
|
else if (common->wait_for_pts)
|
|
p.progress = (float) start / common->pts_to_start;
|
|
else
|
|
p.progress = 0;
|
|
if (p.progress > 1.0)
|
|
{
|
|
p.progress = 1.0;
|
|
}
|
|
if (now > common->st_first)
|
|
{
|
|
int eta = 0;
|
|
|
|
if (common->wait_for_frame)
|
|
{
|
|
avg = 1000.0 * frame_count / (now - common->st_first);
|
|
eta = (job->frame_to_start - frame_count ) / avg;
|
|
}
|
|
else if (common->wait_for_pts)
|
|
{
|
|
avg = 1000.0 * start / (now - common->st_first);
|
|
eta = (common->pts_to_start - start) / avg;
|
|
}
|
|
p.eta_seconds = eta;
|
|
p.hours = eta / 3600;
|
|
p.minutes = (eta % 3600) / 60;
|
|
p.seconds = eta % 60;
|
|
}
|
|
else
|
|
{
|
|
p.rate_avg = 0.0;
|
|
p.hours = -1;
|
|
p.minutes = -1;
|
|
p.seconds = -1;
|
|
}
|
|
#undef p
|
|
|
|
hb_set_state(job->h, &state);
|
|
}
|
|
|
|
static int syncSubtitleInit( hb_work_object_t * w, hb_job_t * job )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static void syncSubtitleClose( hb_work_object_t * w )
|
|
{
|
|
hb_work_private_t * pv = w->private_data;
|
|
|
|
if (pv == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
sync_delta_t * delta;
|
|
while ((delta = hb_list_item(pv->stream->delta_list, 0)) != NULL)
|
|
{
|
|
hb_list_rem(pv->stream->delta_list, delta);
|
|
free(delta);
|
|
}
|
|
hb_list_close(&pv->stream->delta_list);
|
|
hb_list_empty(&pv->stream->in_queue);
|
|
hb_list_empty(&pv->stream->scr_delay_queue);
|
|
hb_buffer_list_close(&pv->stream->subtitle.sanitizer.list_current);
|
|
free(pv);
|
|
w->private_data = NULL;
|
|
}
|
|
|
|
static int syncSubtitleWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
|
|
hb_buffer_t ** buf_out )
|
|
{
|
|
hb_work_private_t * pv = w->private_data;
|
|
hb_buffer_t * in = *buf_in;
|
|
|
|
if (pv->stream->done)
|
|
{
|
|
flushStreamsLock(pv->common);
|
|
return HB_WORK_DONE;
|
|
}
|
|
if (in->s.flags & HB_BUF_FLAG_EOF)
|
|
{
|
|
pv->stream->flush = 1;
|
|
// sanitizeSubtitle requires EOF buffer to recognize that
|
|
// it needs to flush all subtitles.
|
|
hb_list_add(pv->stream->in_queue, hb_buffer_eof_init());
|
|
flushStreamsLock(pv->common);
|
|
if (pv->common->job->indepth_scan)
|
|
{
|
|
// When doing subtitle indepth scan, the pipeline ends at sync.
|
|
// Terminate job when EOF reached.
|
|
*w->done = 1;
|
|
}
|
|
return HB_WORK_DONE;
|
|
}
|
|
|
|
if (pv->common->job->indepth_scan)
|
|
{
|
|
// When doing subtitle indepth scan, the pipeline ends at sync,
|
|
// do not add the subtitles to the queue
|
|
return HB_WORK_OK;
|
|
}
|
|
else
|
|
{
|
|
*buf_in = NULL;
|
|
}
|
|
|
|
QueueBuffer(pv->stream, in);
|
|
Synchronize(pv->stream);
|
|
|
|
if (pv->stream->done)
|
|
{
|
|
flushStreamsLock(pv->common);
|
|
return HB_WORK_DONE;
|
|
}
|
|
return HB_WORK_OK;
|
|
}
|
|
|
|
hb_work_object_t hb_sync_subtitle =
|
|
{
|
|
WORK_SYNC_SUBTITLE,
|
|
"Subtitle Synchronization",
|
|
syncSubtitleInit,
|
|
syncSubtitleWork,
|
|
syncSubtitleClose
|
|
};
|