ffmpeg: add video heartbeat capability to fix_sub_duration

Splits the currently handled subtitle at random access point
packets that can be configured to follow a specific output stream.
Currently only subtitle streams which are directly mapped into the
same output in which the heartbeat stream resides are affected.

This way the subtitle - which is known to be shown at this time
can be split and passed to muxer before its full duration is
yet known. This is also a drawback, as this essentially outputs
multiple subtitles from a single input subtitle that continues
over multiple random access points. Thus this feature should not
be utilized in cases where subtitle output latency does not matter.

Co-authored-by: Andrzej Nadachowski <andrzej.nadachowski@24i.com>
Co-authored-by: Bernard Boulay <bernard.boulay@24i.com>

Signed-off-by: Jan Ekström <jan.ekstrom@24i.com>
This commit is contained in:
Jan Ekström
2022-07-22 13:57:54 +03:00
committed by Jan Ekström
parent 746d27455b
commit 9a820ec8b1
8 changed files with 245 additions and 0 deletions

View File

@@ -126,6 +126,7 @@ typedef struct BenchmarkTimeStamps {
int64_t sys_usec;
} BenchmarkTimeStamps;
static int trigger_fix_sub_duration_heartbeat(OutputStream *ost, const AVPacket *pkt);
static BenchmarkTimeStamps get_benchmark_time_stamps(void);
static int64_t getmaxrss(void);
static int ifilter_has_all_input_formats(FilterGraph *fg);
@@ -953,6 +954,13 @@ static int encode_frame(OutputFile *of, OutputStream *ost, AVFrame *frame)
av_ts2str(pkt->duration), av_ts2timestr(pkt->duration, &enc->time_base));
}
if ((ret = trigger_fix_sub_duration_heartbeat(ost, pkt)) < 0) {
av_log(NULL, AV_LOG_ERROR,
"Subtitle heartbeat logic failed in %s! (%s)\n",
__func__, av_err2str(ret));
exit_program(1);
}
ost->data_size_enc += pkt->size;
ost->packets_encoded++;
@@ -1912,6 +1920,16 @@ static void do_streamcopy(InputStream *ist, OutputStream *ost, const AVPacket *p
opkt->duration = av_rescale_q(pkt->duration, ist->st->time_base, ost->mux_timebase);
{
int ret = trigger_fix_sub_duration_heartbeat(ost, pkt);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR,
"Subtitle heartbeat logic failed in %s! (%s)\n",
__func__, av_err2str(ret));
exit_program(1);
}
}
of_output_packet(of, opkt, ost, 0);
ost->streamcopy_started = 1;
@@ -2355,6 +2373,136 @@ out:
return ret;
}
static int copy_av_subtitle(AVSubtitle *dst, AVSubtitle *src)
{
int ret = AVERROR_BUG;
AVSubtitle tmp = {
.format = src->format,
.start_display_time = src->start_display_time,
.end_display_time = src->end_display_time,
.num_rects = 0,
.rects = NULL,
.pts = src->pts
};
if (!src->num_rects)
goto success;
if (!(tmp.rects = av_calloc(src->num_rects, sizeof(*tmp.rects))))
return AVERROR(ENOMEM);
for (int i = 0; i < src->num_rects; i++) {
AVSubtitleRect *src_rect = src->rects[i];
AVSubtitleRect *dst_rect;
if (!(dst_rect = tmp.rects[i] = av_mallocz(sizeof(*tmp.rects[0])))) {
ret = AVERROR(ENOMEM);
goto cleanup;
}
tmp.num_rects++;
dst_rect->type = src_rect->type;
dst_rect->flags = src_rect->flags;
dst_rect->x = src_rect->x;
dst_rect->y = src_rect->y;
dst_rect->w = src_rect->w;
dst_rect->h = src_rect->h;
dst_rect->nb_colors = src_rect->nb_colors;
if (src_rect->text)
if (!(dst_rect->text = av_strdup(src_rect->text))) {
ret = AVERROR(ENOMEM);
goto cleanup;
}
if (src_rect->ass)
if (!(dst_rect->ass = av_strdup(src_rect->ass))) {
ret = AVERROR(ENOMEM);
goto cleanup;
}
for (int j = 0; j < 4; j++) {
// SUBTITLE_BITMAP images are special in the sense that they
// are like PAL8 images. first pointer to data, second to
// palette. This makes the size calculation match this.
size_t buf_size = src_rect->type == SUBTITLE_BITMAP && j == 1 ?
AVPALETTE_SIZE :
src_rect->h * src_rect->linesize[j];
if (!src_rect->data[j])
continue;
if (!(dst_rect->data[j] = av_memdup(src_rect->data[j], buf_size))) {
ret = AVERROR(ENOMEM);
goto cleanup;
}
dst_rect->linesize[j] = src_rect->linesize[j];
}
}
success:
*dst = tmp;
return 0;
cleanup:
avsubtitle_free(&tmp);
return ret;
}
static int fix_sub_duration_heartbeat(InputStream *ist, int64_t signal_pts)
{
int ret = AVERROR_BUG;
int got_output = 1;
AVSubtitle *prev_subtitle = &ist->prev_sub.subtitle;
AVSubtitle subtitle;
if (!ist->fix_sub_duration || !prev_subtitle->num_rects ||
signal_pts <= prev_subtitle->pts)
return 0;
if ((ret = copy_av_subtitle(&subtitle, prev_subtitle)) < 0)
return ret;
subtitle.pts = signal_pts;
return process_subtitle(ist, &subtitle, &got_output);
}
static int trigger_fix_sub_duration_heartbeat(OutputStream *ost, const AVPacket *pkt)
{
OutputFile *of = output_files[ost->file_index];
int64_t signal_pts = av_rescale_q(pkt->pts, ost->mux_timebase,
AV_TIME_BASE_Q);
if (!ost->fix_sub_duration_heartbeat || !(pkt->flags & AV_PKT_FLAG_KEY))
// we are only interested in heartbeats on streams configured, and
// only on random access points.
return 0;
for (int i = 0; i < of->nb_streams; i++) {
OutputStream *iter_ost = of->streams[i];
InputStream *ist = iter_ost->ist;
int ret = AVERROR_BUG;
if (iter_ost == ost || !ist || !ist->decoding_needed ||
ist->dec_ctx->codec_type != AVMEDIA_TYPE_SUBTITLE)
// We wish to skip the stream that causes the heartbeat,
// output streams without an input stream, streams not decoded
// (as fix_sub_duration is only done for decoded subtitles) as
// well as non-subtitle streams.
continue;
if ((ret = fix_sub_duration_heartbeat(ist, signal_pts)) < 0)
return ret;
}
return 0;
}
static int transcode_subtitles(InputStream *ist, AVPacket *pkt, int *got_output,
int *decode_failed)
{