/* sync.c
Copyright (c) 2003-2016 HandBrake Team
This file is part of the HandBrake source code
Homepage: .
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 "hb.h"
#include "hbffmpeg.h"
#include
#include "samplerate.h"
#define SYNC_MAX_VIDEO_QUEUE_LEN 20
#define SYNC_MIN_VIDEO_QUEUE_LEN 12
// 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 60
#define SYNC_MIN_AUDIO_QUEUE_LEN 30
#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;
typedef struct
{
sync_common_t * common;
// Stream I/O control
hb_list_t * in_queue;
int max_len;
int min_len;
hb_cond_t * cond_full;
hb_buffer_list_t out_queue;
int eof;
// PTS synchronization
hb_list_t * delta_list;
int64_t pts_slip;
double next_pts;
// frame statistics
int64_t first_pts;
int64_t min_frame_duration;
int64_t 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
{
double dejitter_pts;
} video;
// Audio stream context
struct
{
hb_audio_t * audio;
// Audio filter settings
// Samplerate conversion
struct
{
SRC_STATE * ctx;
SRC_DATA pkt;
} src;
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 done;
// point-to-point support
int start_found;
// 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;
};
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 hb_buffer_t * FilterAudioFrame( sync_stream_t * stream,
hb_buffer_t *buf );
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 signalBuffer( sync_stream_t * stream )
{
if (hb_list_count(stream->in_queue) < stream->max_len)
{
hb_cond_signal(stream->cond_full);
}
}
static void allSlip( sync_common_t * common, int64_t delta )
{
int ii;
for (ii = 0; ii < common->stream_count; ii++)
{
common->streams[ii].pts_slip += delta;
if (common->streams[ii].next_pts != AV_NOPTS_VALUE)
{
common->streams[ii].next_pts -= delta;
}
}
}
static void shiftTS( sync_common_t * common, int64_t delta )
{
int ii, jj;
allSlip(common, delta);
for (ii = 0; ii < common->stream_count; ii++)
{
sync_stream_t * stream = &common->streams[ii];
int count = hb_list_count(stream->in_queue);
for (jj = 0; jj < count; jj++)
{
hb_buffer_t * buf = hb_list_item(stream->in_queue, jj);
buf->s.start -= delta;
if (buf->s.stop != AV_NOPTS_VALUE)
{
buf->s.stop -= delta;
}
}
}
}
static void checkFirstPts( sync_common_t * common )
{
int ii;
int64_t first_pts = INT64_MAX;
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
if (hb_list_count(stream->in_queue) > 0)
{
hb_buffer_t * buf = hb_list_item(stream->in_queue, 0);
if (first_pts > buf->s.start)
{
first_pts = buf->s.start;
}
}
}
if (first_pts != INT64_MAX)
{
// Add a fudge factor to first pts to prevent negative
// timestamps from leaking through. The pipeline can
// handle a positive offset, but some things choke on
// negative offsets
//first_pts -= 500000;
shiftTS(common, first_pts);
}
common->found_first_pts = 1;
}
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
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) < 0.1)
{
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, new_chap = 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)
{
// 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;
}
// Preserve chapter marks
if (buf->s.new_chap > 0)
{
new_chap = buf->s.new_chap;
}
hb_list_rem(stream->in_queue, buf);
signalBuffer(stream);
stream->drop_duration += buf->s.duration;
stream->drop++;
drop++;
hb_buffer_close(&buf);
}
else
{
if (new_chap > 0)
{
buf->s.new_chap = new_chap;
}
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;
// 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;
}
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);
signalBuffer(stream);
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 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);
}
}
static void sendEof( sync_common_t * common )
{
int ii;
for (ii = 0; ii < common->stream_count; ii++)
{
hb_buffer_list_append(&common->streams[ii].out_queue,
hb_buffer_eof_init());
}
}
static void streamFlush( sync_stream_t * stream )
{
while (hb_list_count(stream->in_queue) > 0)
{
fixStreamTimestamps(stream);
hb_buffer_t * buf = hb_list_item(stream->in_queue, 0);
if (buf != NULL)
{
hb_list_rem(stream->in_queue, buf);
if ((buf->s.start < 0) ||
(stream->type == SYNC_TYPE_VIDEO && buf->s.duration < 256))
{
// 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
//
// Also, encx264.c can't handle timestamps that are spaced
// less than 256 ticks apart.
hb_buffer_close(&buf);
continue;
}
if (!stream->first_frame)
{
switch (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,
stream->audio.audio->id, buf->s.start);
break;
case SYNC_TYPE_SUBTITLE:
hb_log("sync: first pts subtitle 0x%x is %"PRId64,
stream->subtitle.subtitle->id, buf->s.start);
break;
default:
break;
}
stream->first_frame = 1;
stream->first_pts = buf->s.start;
stream->next_pts = buf->s.start;
stream->min_frame_duration = buf->s.duration;
}
if (stream->type == SYNC_TYPE_AUDIO)
{
buf = FilterAudioFrame(stream, buf);
}
if (stream->type == SYNC_TYPE_AUDIO ||
stream->type == SYNC_TYPE_VIDEO)
{
buf->s.start = stream->next_pts;
buf->s.stop = stream->next_pts + buf->s.duration;
stream->next_pts += buf->s.duration;
}
else
{
// Last ditch effort to prevent timestamps from going backwards
// This can (and should only) affect subtitle streams.
if (buf->s.start < stream->next_pts)
{
buf->s.start = stream->next_pts;
}
stream->next_pts = buf->s.start;
}
if (buf->s.stop > 0)
{
stream->current_duration = buf->s.stop - stream->first_pts;
}
stream->frame_count++;
if (buf->s.duration > 0 &&
stream->min_frame_duration > buf->s.duration)
{
stream->min_frame_duration = buf->s.duration;
}
if (stream->max_frame_duration < buf->s.duration)
{
stream->max_frame_duration = buf->s.duration;
}
hb_buffer_list_append(&stream->out_queue, buf);
}
}
hb_buffer_list_append(&stream->out_queue, hb_buffer_eof_init());
}
// 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 void OutputBuffer( sync_common_t * common )
{
int ii, full;
int64_t pts;
sync_stream_t * out_stream;
hb_buffer_t * buf;
if (common->done)
{
// It is possible to get here when one stream triggers
// end of output (i.e. pts_to_stop or frame_to_stop) while
// another stream is waiting on the mutex.
return;
}
do
{
full = 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];
// 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 (hb_list_count(stream->in_queue) > stream->min_len)
{
buf = hb_list_item(stream->in_queue, 0);
if (stream->type == SYNC_TYPE_SUBTITLE)
{
// Forward subtitles immediately instead of interleaving.
//
// Normally, we interleave output by PTS in order to
// optimize sync recovery. This results in queueing
// stream data that may not get delivered to it's
// respecitive output fifo until the next input data
// is received for that stream. This isn't a problem
// for continuous streams like audio and video, but
// it delays subtitles unacceptably.
out_stream = stream;
break;
}
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(hb_list_count(stream->in_queue) > stream->max_len)
{
full = 1;
}
}
if (out_stream == NULL)
{
// This should only happen if all queues are below the
// minimum queue level
break;
}
if (out_stream->next_pts == AV_NOPTS_VALUE)
{
// Initialize next_pts, it is used to make timestamp corrections
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 (out_stream->type == SYNC_TYPE_VIDEO &&
common->job->frame_to_start > 0 &&
out_stream->frame_count >= common->job->frame_to_start)
{
common->start_found = 1;
out_stream->frame_count = 0;
}
else if (common->job->pts_to_start > 0 &&
buf->s.start >= common->job->pts_to_start)
{
common->start_found = 1;
common->streams[0].frame_count = 0;
}
else
{
if (out_stream->type == SYNC_TYPE_VIDEO)
{
UpdateSearchState(common, buf->s.start,
out_stream->frame_count);
out_stream->frame_count++;
}
hb_list_rem(out_stream->in_queue, buf);
signalBuffer(out_stream);
hb_buffer_close(&buf);
continue;
}
// reset frame count to track number of frames after
// the start position till the end of encode.
shiftTS(common, buf->s.start);
}
// If pts_to_stop or frame_to_stop were specified, stop output
if (common->job->pts_to_stop &&
buf->s.start >= common->job->pts_to_stop )
{
hb_log("sync: reached pts %"PRId64", exiting early", buf->s.start);
common->done = 1;
sendEof(common);
return;
}
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 %d frames, exiting early",
out_stream->frame_count);
common->done = 1;
sendEof(common);
return;
}
// Out the buffer goes...
hb_list_rem(out_stream->in_queue, buf);
signalBuffer(out_stream);
if ((buf->s.start < 0) ||
(out_stream->type == SYNC_TYPE_VIDEO && buf->s.duration < 256))
{
// 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
//
// Also, encx264.c can't handle timestamps that are spaced
// less than 256 ticks apart.
hb_buffer_close(&buf);
}
else
{
if (out_stream->type == SYNC_TYPE_VIDEO)
{
UpdateState(common, out_stream->frame_count);
}
if (!out_stream->first_frame)
{
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->next_pts = buf->s.start;
out_stream->min_frame_duration = buf->s.duration;
}
if (out_stream->type == SYNC_TYPE_AUDIO)
{
buf = FilterAudioFrame(out_stream, buf);
}
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->next_pts += buf->s.duration;
}
else
{
// Last ditch effort to prevent timestamps from going backwards
// This can (and should only) affect subtitle streams.
if (buf->s.start < out_stream->next_pts)
{
buf->s.start = out_stream->next_pts;
}
out_stream->next_pts = buf->s.start;
}
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;
}
hb_buffer_list_append(&out_stream->out_queue, buf);
}
} while (full);
}
static void Synchronize( sync_common_t * common )
{
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, int64_t start )
{
// The video decoder does not set an initial duration for frames.
// So set it here.
if (stream->type == SYNC_TYPE_VIDEO)
{
int count = hb_list_count(stream->in_queue);
if (count > 0)
{
hb_buffer_t * buf = hb_list_item(stream->in_queue, count - 1);
double duration = start - buf->s.start;
if (duration > 0)
{
buf->s.duration = duration;
buf->s.stop = start;
}
else
{
buf->s.duration = 0.;
buf->s.stop = buf->s.start;
}
}
}
}
static void QueueBuffer( sync_stream_t * stream, hb_buffer_t * buf )
{
hb_lock(stream->common->mutex);
// We do not place a limit on the number of subtitle frames
// that are buffered becuase 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.
while (hb_list_count(stream->in_queue) > stream->max_len)
{
hb_cond_wait(stream->cond_full, stream->common->mutex);
}
buf->s.start -= stream->pts_slip;
if (buf->s.stop != AV_NOPTS_VALUE)
{
buf->s.stop -= stream->pts_slip;
}
// Render offset is only useful for decoders, which are all
// upstream of sync. Squash it.
buf->s.renderOffset = AV_NOPTS_VALUE;
updateDuration(stream, buf->s.start);
hb_list_add(stream->in_queue, 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;
pv->common = common;
pv->stream = &common->streams[1 + index];
pv->stream->common = common;
pv->stream->cond_full = hb_cond_init();
if (pv->stream->cond_full == NULL) goto fail;
pv->stream->in_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 = AV_NOPTS_VALUE;
pv->stream->audio.audio = audio;
w = hb_get_work(common->job->h, WORK_SYNC_AUDIO);
w->private_data = pv;
w->audio = audio;
w->fifo_in = audio->priv.fifo_raw;
if (audio->config.out.codec & HB_ACODEC_PASS_FLAG)
{
w->fifo_out = audio->priv.fifo_out;
}
else
{
w->fifo_out = audio->priv.fifo_sync;
}
if (!(audio->config.out.codec & HB_ACODEC_PASS_FLAG) &&
audio->config.in.samplerate != audio->config.out.samplerate)
{
/* Initialize libsamplerate */
int error;
pv->stream->audio.src.ctx = src_new(SRC_SINC_MEDIUM_QUALITY,
hb_mixdown_get_discrete_channel_count(audio->config.out.mixdown),
&error);
if (pv->stream->audio.src.ctx == NULL) goto fail;
pv->stream->audio.src.pkt.end_of_input = 0;
}
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.src.ctx)
{
src_delete(pv->stream->audio.src.ctx);
}
hb_list_close(&pv->stream->delta_list);
hb_list_close(&pv->stream->in_queue);
hb_cond_close(&pv->stream->cond_full);
}
}
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->cond_full = hb_cond_init();
if (pv->stream->cond_full == NULL) goto fail;
pv->stream->in_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 = AV_NOPTS_VALUE;
pv->stream->subtitle.subtitle = subtitle;
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_out;
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 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)
{
// 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);
hb_cond_close(&pv->stream->cond_full);
}
}
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->cond_full = hb_cond_init();
if (pv->stream->cond_full == NULL) goto fail;
pv->stream->in_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 = AV_NOPTS_VALUE;
w->fifo_in = job->fifo_raw;
// sync performs direct output to fifos
w->fifo_out = job->fifo_sync;
if (job->indepth_scan)
{
// When doing subtitle indepth scan, the pipeline ends at sync
w->fifo_out = NULL;
}
if (job->pass_id == HB_PASS_ENCODE_2ND)
{
/* 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;
if (job->pts_to_stop)
{
duration = job->pts_to_stop + 90000;
}
else
{
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;
}
}
}
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;
}
else
{
pv->common->start_found = 1;
}
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);
hb_cond_close(&pv->stream->cond_full);
}
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_1ST )
{
/* 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_buffer_list_close(&pv->stream->out_queue);
hb_list_empty(&pv->stream->in_queue);
hb_cond_close(&pv->stream->cond_full);
// 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 = 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)
{
len = sprintf((char*)buf->data, "%s\n%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 void setSubDuration(hb_buffer_t * buf)
{
if (buf->s.stop != AV_NOPTS_VALUE)
buf->s.duration = buf->s.stop - buf->s.start;
else
buf->s.duration = AV_NOPTS_VALUE;
}
static hb_buffer_t * mergeSubtitles(subtitle_sanitizer_t *sanitizer)
{
hb_buffer_t *a, *b, *buf;
hb_buffer_list_t list;
hb_buffer_list_clear(&list);
if (!sanitizer->merge)
{
// Handle all but the last buffer
while (hb_buffer_list_count(&sanitizer->list_current) > 1)
{
buf = hb_buffer_list_rem_head(&sanitizer->list_current);
setSubDuration(buf);
hb_buffer_list_append(&list, buf);
}
// Check last buffer
buf = hb_buffer_list_head(&sanitizer->list_current);
if (buf != NULL)
{
if (buf->s.flags & HB_BUF_FLAG_EOF)
{
buf = hb_buffer_list_rem_head(&sanitizer->list_current);
hb_buffer_list_append(&list, buf);
}
else if (buf->s.stop != AV_NOPTS_VALUE)
{
buf = hb_buffer_list_rem_head(&sanitizer->list_current);
setSubDuration(buf);
hb_buffer_list_append(&list, buf);
}
}
return hb_buffer_list_clear(&list);
}
// We only reach here if we are merging subtitles
while (hb_buffer_list_count(&sanitizer->list_current) > 0)
{
a = hb_buffer_list_head(&sanitizer->list_current);
if (a->s.flags & HB_BUF_FLAG_EOF)
{
buf = hb_buffer_list_rem_head(&sanitizer->list_current);
hb_buffer_list_append(&list, buf);
break;
}
b = a->next;
if (b == NULL)
{
break;
}
if (b->s.flags & HB_BUF_FLAG_EOF)
{
buf = hb_buffer_list_rem_head(&sanitizer->list_current);
setSubDuration(buf);
hb_buffer_list_append(&list, buf);
buf = hb_buffer_list_rem_head(&sanitizer->list_current);
hb_buffer_list_append(&list, buf);
break;
}
if (a->s.stop == AV_NOPTS_VALUE)
{
// To evaluate overlaps, we need the stop times
// This should really never happen...
break;
}
buf = NULL;
if (a->s.stop > b->s.start)
{
// Overlap
if (ABS(a->s.start - b->s.start) <= 18000)
{
if (b->s.stop == AV_NOPTS_VALUE)
{
// To evaluate overlaps, we need the stop times
// for a and b
break;
}
a = hb_buffer_list_rem_head(&sanitizer->list_current);
// subtitles start within 1/5 second of eachother, merge
if (a->s.stop > b->s.stop && b->s.stop != AV_NOPTS_VALUE)
{
// a continues after b, reorder the list and swap
b = a;
a = hb_buffer_list_rem_head(&sanitizer->list_current);
hb_buffer_list_prepend(&sanitizer->list_current, b);
}
b->s.start = a->s.stop;
buf = merge_ssa(a, b);
hb_buffer_close(&a);
a = buf;
if (b->s.stop != AV_NOPTS_VALUE &&
ABS(b->s.stop - b->s.start) <= 18000)
{
// b and a completely overlap, remove b
b = hb_buffer_list_rem_head(&sanitizer->list_current);
hb_buffer_close(&b);
}
}
else
{
// a starts before b, output copy of a and update a start
buf = hb_buffer_dup(a);
buf->s.stop = b->s.start;
a->s.start = b->s.start;
}
}
else if (a->s.stop <= b->s.start)
{
// a starts and ends before b
buf = hb_buffer_list_rem_head(&sanitizer->list_current);
}
if (buf != NULL)
{
setSubDuration(buf);
hb_buffer_list_append(&list, buf);
}
}
return hb_buffer_list_clear(&list);
}
static hb_buffer_t * sanitizeSubtitle(
subtitle_sanitizer_t * sanitizer,
hb_buffer_t * sub)
{
hb_buffer_list_t list;
if (sub == NULL)
{
return NULL;
}
hb_buffer_list_set(&list, sub);
if (!sanitizer->link && !sanitizer->merge)
{
sub = hb_buffer_list_head(&list);
while (sub != NULL)
{
if (sub->s.stop != AV_NOPTS_VALUE)
sub->s.duration = sub->s.stop - sub->s.start;
else
sub->s.duration = AV_NOPTS_VALUE;
sub = sub->next;
}
return hb_buffer_list_clear(&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 (sub->s.start == sub->s.stop)
{
// Used to indicate "clear" subtitles when the duration
// of subtitles is not encoded in the stream
hb_buffer_close(&sub);
}
else
{
hb_buffer_list_append(&sanitizer->list_current, sub);
}
sub = hb_buffer_list_rem_head(&list);
}
return mergeSubtitles(sanitizer);
}
/***********************************************************************
* 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 (pv->common->done)
{
*buf_out = hb_buffer_list_clear(&pv->stream->out_queue);
return HB_WORK_DONE;
}
if (in->s.flags & HB_BUF_FLAG_EOF)
{
streamFlush(pv->stream);
*buf_out = hb_buffer_list_clear(&pv->stream->out_queue);
// 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->common);
*buf_out = hb_buffer_list_clear(&pv->stream->out_queue);
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.src.ctx)
{
src_delete(pv->stream->audio.src.ctx);
}
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_buffer_list_close(&pv->stream->out_queue);
hb_list_empty(&pv->stream->in_queue);
hb_cond_close(&pv->stream->cond_full);
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->common->done)
{
*buf_out = hb_buffer_list_clear(&pv->stream->out_queue);
return HB_WORK_DONE;
}
if (in->s.flags & HB_BUF_FLAG_EOF)
{
streamFlush(pv->stream);
*buf_out = hb_buffer_list_clear(&pv->stream->out_queue);
return HB_WORK_DONE;
}
*buf_in = NULL;
QueueBuffer(pv->stream, in);
Synchronize(pv->common);
*buf_out = hb_buffer_list_clear(&pv->stream->out_queue);
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.src.ctx != NULL)
{
/* do sample rate conversion */
int count_in, count_out;
hb_buffer_t * buf_raw = buf;
int sample_size = hb_mixdown_get_discrete_channel_count(
audio->config.out.mixdown ) * sizeof( float );
count_in = buf_raw->size / sample_size;
/*
* When using stupid rates like 44.1 there will always be some
* truncation error. E.g., a 1536 sample AC3 frame will turn into a
* 1536*44.1/48.0 = 1411.2 sample frame. If we just truncate the .2
* the error will build up over time and eventually the audio will
* substantially lag the video. libsamplerate will keep track of the
* fractional sample & give it to us when appropriate if we give it
* an extra sample of space in the output buffer.
*/
count_out = (buf->s.duration * audio->config.out.samplerate) /
90000 + 1;
stream->audio.src.pkt.input_frames = count_in;
stream->audio.src.pkt.output_frames = count_out;
stream->audio.src.pkt.src_ratio =
(double)audio->config.out.samplerate /
audio->config.in.samplerate;
buf = hb_buffer_init( count_out * sample_size );
buf->s = buf_raw->s;
stream->audio.src.pkt.data_in = (float *) buf_raw->data;
stream->audio.src.pkt.data_out = (float *) buf->data;
if (src_process(stream->audio.src.ctx, &stream->audio.src.pkt))
{
/* XXX If this happens, we're screwed */
hb_error("sync: audio 0x%x src_process failed", audio->id);
}
hb_buffer_close(&buf_raw);
if (stream->audio.src.pkt.output_frames_gen <= 0)
{
hb_buffer_close(&buf);
return NULL;
}
buf->s.duration = 90000. * stream->audio.src.pkt.output_frames_gen /
audio->config.out.samplerate;
}
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;
hb_get_state2(job->h, &state);
if (frame_count == 0)
{
common->st_first = hb_get_date();
job->st_pause_date = -1;
job->st_paused = 0;
}
if (job->indepth_scan)
{
// Progress for indept scan is handled by reader
// frame_count is used during indepth_scan
// to find start & end points.
return;
}
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;
}
#define p state.param.working
state.state = HB_STATE_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)
{
int eta;
p.rate_avg = 1000.0 * common->st_counts[3] /
(common->st_dates[3] - common->st_first - job->st_paused);
eta = (common->est_frame_count - common->st_counts[3]) / p.rate_avg;
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 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;
now = hb_get_date();
if (frame_count == 0)
{
common->st_first = now;
job->st_pause_date = -1;
job->st_paused = 0;
}
if (job->indepth_scan)
{
// Progress for indept scan is handled by reader
// frame_count is used during indepth_scan
// to find start & end points.
return;
}
hb_get_state2(job->h, &state);
#define p state.param.working
state.state = HB_STATE_SEARCHING;
if (job->frame_to_start)
p.progress = (float)frame_count / job->frame_to_start;
else if (job->pts_to_start)
p.progress = (float) start / job->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 (job->frame_to_start)
{
avg = 1000.0 * frame_count / (now - common->st_first);
eta = (job->frame_to_start - frame_count ) / avg;
}
else if (job->pts_to_start)
{
avg = 1000.0 * start / (now - common->st_first);
eta = (job->pts_to_start - start) / avg;
}
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_buffer_list_close(&pv->stream->out_queue);
hb_list_empty(&pv->stream->in_queue);
hb_cond_close(&pv->stream->cond_full);
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->common->done)
{
hb_buffer_list_append(&pv->stream->out_queue, hb_buffer_eof_init());
in = hb_buffer_list_clear(&pv->stream->out_queue);
*buf_out = sanitizeSubtitle(&pv->stream->subtitle.sanitizer, in);
return HB_WORK_DONE;
}
if (in->s.flags & HB_BUF_FLAG_EOF)
{
streamFlush(pv->stream);
in = hb_buffer_list_clear(&pv->stream->out_queue);
*buf_out = sanitizeSubtitle(&pv->stream->subtitle.sanitizer, in);
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->common);
in = hb_buffer_list_clear(&pv->stream->out_queue);
if (in != NULL)
{
*buf_out = sanitizeSubtitle(&pv->stream->subtitle.sanitizer, in);
}
return HB_WORK_OK;
}
hb_work_object_t hb_sync_subtitle =
{
WORK_SYNC_SUBTITLE,
"Subitle Synchronization",
syncSubtitleInit,
syncSubtitleWork,
syncSubtitleClose
};