Skip to content

Commit cea0459

Browse files
MikeIndiaAlphaMichal Adamczak
authored andcommitted
Demuxer reopening fixes
This is quite deep and important change. Thing is, old code had "demuxer reuse" attempt. And unfortunately it neither made sense (next to nothing performance improvement, complex code, limited to MPEGTS streams), nor was working fine. During Low Latency work I tried to eliminate demuxer reusing because I wanted single demuxer logic to make input I/O plug-in easier. It turned out that so modified code failed certain tests such as Nvidia_AudioOnly. It took me a great deal of debugging, but I found out that there is bug in demuxer reuse procedure: namely only first muxer open attempt _really_ checks what is in the segment provided, the rest just assumes that same streams are there. So basically, in the series of "audio/video"/"audio without video" /"audio/video" segments all will be perceived as containing video! This "patched over" *AudioOnly tests, and let them run, but only because Transcoder "though" that it has video all the time. And this simply wasn't true. When I removed "muxer reusing" code, the Nvidia-dedicated "preserve video decoder" tactics was falling over the fact that pixel format changed (because it changed from AV_PIX_FMT_SOMETHING into AV_PIX_FMT_NONE) and tried to create hardware video decoder for AV_PIX_FMT_NONE, and failed. Fixing all that finally allowed me to get rid of recursive call to open_input when HW decoder needs to be reset. Which is good. During the work on this commit I also noticed that various pieces of code here and there assume that video stream is always there. Which is not true, and never was (for example, some tests were using segments without video in the middle of transcode sequence, and it worked only because Transcoder "failed to notice" there is no video anymore). So the whole code was carefully tested and examined against this dangerous assumption and checks were added. These changes, in turn, allowed to remove the limitations that allowed the Transcoder to start only with segments containing video. Transcoder will now happily process audio-only segments.
1 parent 730cbec commit cea0459

File tree

10 files changed

+141
-221
lines changed

10 files changed

+141
-221
lines changed

ffmpeg/api_test.go

Lines changed: 2 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ func TestAPI_SkippedSegment(t *testing.T) {
8888
}
8989

9090
func TestTranscoderAPI_InvalidFile(t *testing.T) {
91-
// Test the following file open results on input: fail, success, fail, success
91+
// Test the following file open results on input: success, fail, success
9292

9393
tc := NewTranscoder()
9494
defer tc.StopTranscoder()
@@ -100,18 +100,9 @@ func TestTranscoderAPI_InvalidFile(t *testing.T) {
100100
Muxer: ComponentOptions{Name: "null"},
101101
}}
102102

103-
// fail # 1
104-
in.Fname = "none"
105-
_, err := tc.Transcode(in, out)
106-
if err == nil || err.Error() != "TranscoderInvalidVideo" {
107-
// Early codec check didn't find video in missing input file so we get `TranscoderInvalidVideo`
108-
// instead of `No such file or directory`
109-
t.Error("Expected 'TranscoderInvalidVideo', got ", err)
110-
}
111-
112103
// success # 1
113104
in.Fname = "../transcoder/test.ts"
114-
_, err = tc.Transcode(in, out)
105+
_, err := tc.Transcode(in, out)
115106
if err != nil {
116107
t.Error(err)
117108
}
@@ -1217,27 +1208,6 @@ func audioOnlySegment(t *testing.T, accel Acceleration) {
12171208
}
12181209
}
12191210
tc.StopTranscoder()
1220-
1221-
// Test encoding with audio-only segment in start of stream
1222-
tc = NewTranscoder()
1223-
defer tc.StopTranscoder()
1224-
for i := 2; i < 4; i++ {
1225-
in := &TranscodeOptionsIn{
1226-
Fname: fmt.Sprintf("%s/test%d.ts", dir, i),
1227-
Accel: accel,
1228-
}
1229-
out := []TranscodeOptions{{
1230-
Oname: fmt.Sprintf("%s/out2_%d.ts", dir, i),
1231-
Profile: prof,
1232-
Accel: accel,
1233-
}}
1234-
_, err := tc.Transcode(in, out)
1235-
if i == 2 && (err == nil || err.Error() != "TranscoderInvalidVideo") {
1236-
t.Errorf("Expected to fail for audio-only segment but did not, instead got err=%v", err)
1237-
} else if i != 2 && err != nil {
1238-
t.Error(err)
1239-
}
1240-
}
12411211
}
12421212

12431213
func TestTranscoder_AudioOnly(t *testing.T) {

ffmpeg/decoder.c

Lines changed: 97 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -128,29 +128,36 @@ static enum AVPixelFormat get_hw_pixfmt(AVCodecContext *vc, const enum AVPixelFo
128128
}
129129

130130

131-
int open_audio_decoder(struct input_ctx *ctx, AVCodec *codec)
131+
static int open_audio_decoder(struct input_ctx *ctx, AVCodec *codec)
132132
{
133133
int ret = 0;
134134
AVFormatContext *ic = ctx->ic;
135135

136136
// open audio decoder
137-
AVCodecContext * ac = avcodec_alloc_context3(codec);
138-
if (!ac) LPMS_ERR(open_audio_err, "Unable to alloc audio codec");
139-
if (ctx->ac) LPMS_WARN("An audio context was already open!");
140-
ctx->ac = ac;
141-
ret = avcodec_parameters_to_context(ac, ic->streams[ctx->ai]->codecpar);
142-
if (ret < 0) LPMS_ERR(open_audio_err, "Unable to assign audio params");
143-
ret = avcodec_open2(ac, codec, NULL);
144-
if (ret < 0) LPMS_ERR(open_audio_err, "Unable to open audio decoder");
145-
137+
AVCodecContext * ac = avcodec_alloc_context3(codec);
138+
if (!ac) LPMS_ERR(open_audio_err, "Unable to alloc audio codec");
139+
if (ctx->ac) LPMS_WARN("An audio context was already open!");
140+
ctx->ac = ac;
141+
ret = avcodec_parameters_to_context(ac, ic->streams[ctx->ai]->codecpar);
142+
if (ret < 0) LPMS_ERR(open_audio_err, "Unable to assign audio params");
143+
ret = avcodec_open2(ac, codec, NULL);
144+
if (ret < 0) LPMS_ERR(open_audio_err, "Unable to open audio decoder");
145+
ctx->last_frame_a = av_frame_alloc();
146+
if (!ctx->last_frame_a) LPMS_ERR(open_audio_err, "Unable to alloc last_frame_a");
146147
return 0;
147148

148149
open_audio_err:
149150
free_input(ctx, FORCE_CLOSE_HW_DECODER);
150151
return ret;
151152
}
152153

153-
char* get_hw_decoder(int ff_codec_id, int hw_type)
154+
static void close_audio_decoder(struct input_ctx *ictx)
155+
{
156+
if (ictx->ac) avcodec_free_context(&ictx->ac);
157+
if (ictx->last_frame_a) av_frame_free(&ictx->last_frame_a);
158+
}
159+
160+
static char* get_hw_decoder(int ff_codec_id, int hw_type)
154161
{
155162
switch (hw_type) {
156163
case AV_HWDEVICE_TYPE_CUDA:
@@ -184,47 +191,51 @@ char* get_hw_decoder(int ff_codec_id, int hw_type)
184191
}
185192
}
186193

187-
int open_video_decoder(struct input_ctx *ctx, AVCodec *codec)
194+
static int open_video_decoder(struct input_ctx *ctx, AVCodec *codec)
188195
{
189196
int ret = 0;
190197
AVDictionary **opts = NULL;
191198
AVFormatContext *ic = ctx->ic;
192199
// open video decoder
193-
if (ctx->hw_type > AV_HWDEVICE_TYPE_NONE) {
194-
char* decoder_name = get_hw_decoder(codec->id, ctx->hw_type);
195-
if (!*decoder_name) {
196-
ret = lpms_ERR_INPUT_CODEC;
197-
LPMS_ERR(open_decoder_err, "Input codec does not support hardware acceleration");
198-
}
199-
AVCodec *c = avcodec_find_decoder_by_name(decoder_name);
200-
if (c) codec = c;
201-
else LPMS_WARN("Nvidia decoder not found; defaulting to software");
202-
if (AV_PIX_FMT_YUV420P != ic->streams[ctx->vi]->codecpar->format &&
203-
AV_PIX_FMT_YUVJ420P != ic->streams[ctx->vi]->codecpar->format) {
204-
// TODO check whether the color range is truncated if yuvj420p is used
205-
ret = lpms_ERR_INPUT_PIXFMT;
206-
LPMS_ERR(open_decoder_err, "Non 4:2:0 pixel format detected in input");
207-
}
200+
if (ctx->hw_type > AV_HWDEVICE_TYPE_NONE) {
201+
char* decoder_name = get_hw_decoder(codec->id, ctx->hw_type);
202+
if (!*decoder_name) {
203+
ret = lpms_ERR_INPUT_CODEC;
204+
LPMS_ERR(open_decoder_err, "Input codec does not support hardware acceleration");
208205
}
209-
AVCodecContext *vc = avcodec_alloc_context3(codec);
210-
if (!vc) LPMS_ERR(open_decoder_err, "Unable to alloc video codec");
211-
ctx->vc = vc;
212-
ret = avcodec_parameters_to_context(vc, ic->streams[ctx->vi]->codecpar);
213-
if (ret < 0) LPMS_ERR(open_decoder_err, "Unable to assign video params");
214-
vc->opaque = (void*)ctx;
215-
// XXX Could this break if the original device falls out of scope in golang?
216-
if (ctx->hw_type == AV_HWDEVICE_TYPE_CUDA) {
217-
// First set the hw device then set the hw frame
218-
ret = av_hwdevice_ctx_create(&ctx->hw_device_ctx, ctx->hw_type, ctx->device, NULL, 0);
219-
if (ret < 0) LPMS_ERR(open_decoder_err, "Unable to open hardware context for decoding")
220-
vc->hw_device_ctx = av_buffer_ref(ctx->hw_device_ctx);
221-
vc->get_format = get_hw_pixfmt;
206+
AVCodec *c = avcodec_find_decoder_by_name(decoder_name);
207+
if (c) codec = c;
208+
else LPMS_WARN("Nvidia decoder not found; defaulting to software");
209+
// It is safe to use ctx->vi here, because open_video_decoder won't be
210+
// called if vi < 0
211+
if (AV_PIX_FMT_YUV420P != ic->streams[ctx->vi]->codecpar->format &&
212+
AV_PIX_FMT_YUVJ420P != ic->streams[ctx->vi]->codecpar->format) {
213+
// TODO check whether the color range is truncated if yuvj420p is used
214+
ret = lpms_ERR_INPUT_PIXFMT;
215+
LPMS_ERR(open_decoder_err, "Non 4:2:0 pixel format detected in input");
222216
}
223-
vc->pkt_timebase = ic->streams[ctx->vi]->time_base;
224-
av_opt_set(vc->priv_data, "xcoder-params", ctx->xcoderParams, 0);
225-
ret = avcodec_open2(vc, codec, opts);
226-
if (ret < 0) LPMS_ERR(open_decoder_err, "Unable to open video decoder");
217+
}
227218

219+
AVCodecContext *vc = avcodec_alloc_context3(codec);
220+
if (!vc) LPMS_ERR(open_decoder_err, "Unable to alloc video codec");
221+
ctx->vc = vc;
222+
ret = avcodec_parameters_to_context(vc, ic->streams[ctx->vi]->codecpar);
223+
if (ret < 0) LPMS_ERR(open_decoder_err, "Unable to assign video params");
224+
vc->opaque = (void*)ctx;
225+
// XXX Could this break if the original device falls out of scope in golang?
226+
if (ctx->hw_type == AV_HWDEVICE_TYPE_CUDA) {
227+
// First set the hw device then set the hw frame
228+
ret = av_hwdevice_ctx_create(&ctx->hw_device_ctx, ctx->hw_type, ctx->device, NULL, 0);
229+
if (ret < 0) LPMS_ERR(open_decoder_err, "Unable to open hardware context for decoding")
230+
vc->hw_device_ctx = av_buffer_ref(ctx->hw_device_ctx);
231+
vc->get_format = get_hw_pixfmt;
232+
}
233+
vc->pkt_timebase = ic->streams[ctx->vi]->time_base;
234+
av_opt_set(vc->priv_data, "xcoder-params", ctx->xcoderParams, 0);
235+
ret = avcodec_open2(vc, codec, opts);
236+
if (ret < 0) LPMS_ERR(open_decoder_err, "Unable to open video decoder");
237+
ctx->last_frame_v = av_frame_alloc();
238+
if (!ctx->last_frame_v) LPMS_ERR(open_decoder_err, "Unable to alloc last_frame_v");
228239
return 0;
229240

230241
open_decoder_err:
@@ -233,6 +244,16 @@ int open_video_decoder(struct input_ctx *ctx, AVCodec *codec)
233244
return ret;
234245
}
235246

247+
static void close_video_decoder(struct input_ctx *ictx)
248+
{
249+
if (ictx->vc) {
250+
if (ictx->vc->hw_device_ctx) av_buffer_unref(&ictx->vc->hw_device_ctx);
251+
avcodec_free_context(&ictx->vc);
252+
}
253+
if (ictx->hw_device_ctx) av_buffer_unref(&ictx->hw_device_ctx);
254+
if (ictx->last_frame_v) av_frame_free(&ictx->last_frame_v);
255+
}
256+
236257
int open_input(input_params *params, struct input_ctx *ctx)
237258
{
238259
char *inp = params->fname;
@@ -247,52 +268,51 @@ int open_input(input_params *params, struct input_ctx *ctx)
247268
ctx->device = params->device;
248269

249270
// open demuxer
250-
if (!ctx->ic) {
251-
ret = avformat_open_input(&ctx->ic, inp, NULL, NULL);
252-
if (ret < 0) LPMS_ERR(open_input_err, "demuxer: Unable to open input");
253-
ret = avformat_find_stream_info(ctx->ic, NULL);
254-
if (ret < 0) LPMS_ERR(open_input_err, "Unable to find input info");
255-
} else if (!ctx->ic->pb) {
256-
// reopen input segment file IO context if needed
257-
ret = avio_open(&ctx->ic->pb, inp, AVIO_FLAG_READ);
258-
if (ret < 0) LPMS_ERR(open_input_err, "Unable to reopen file");
259-
} else reopen_decoders = 0;
271+
ret = avformat_open_input(&ctx->ic, inp, NULL, NULL);
272+
if (ret < 0) LPMS_ERR(open_input_err, "demuxer: Unable to open input");
273+
ret = avformat_find_stream_info(ctx->ic, NULL);
274+
if (ret < 0) LPMS_ERR(open_input_err, "Unable to find input info");
260275

261276
AVCodec *video_codec = NULL;
262277
AVCodec *audio_codec = NULL;
263278
ctx->vi = av_find_best_stream(ctx->ic, AVMEDIA_TYPE_VIDEO, -1, -1, &video_codec, 0);
264279
ctx->ai = av_find_best_stream(ctx->ic, AVMEDIA_TYPE_AUDIO, -1, -1, &audio_codec, 0);
265280

266-
if (AV_HWDEVICE_TYPE_CUDA == ctx->hw_type && ctx->vi >= 0) {
267-
if (ctx->last_format == AV_PIX_FMT_NONE) ctx->last_format = ctx->ic->streams[ctx->vi]->codecpar->format;
268-
else if (ctx->ic->streams[ctx->vi]->codecpar->format != ctx->last_format) {
281+
// Now be careful here. It appears that in certain situation (such as .ts
282+
// stream without video stream) ctx->vi will be set to 0, but the format will
283+
// be set to AV_PIX_FMT_NONE and both width and height will be zero, etc
284+
// This is normally fine, but when re-using video decoder we have to be
285+
// extra careful, and handle both situations: one with negative vi, and one
286+
// with positive vi but AV_PIX_FMT_NONE in stream format
287+
enum AVPixelFormat format =
288+
(ctx->vi >= 0) ? ctx->ic->streams[ctx->vi]->codecpar->format : AV_PIX_FMT_NONE;
289+
if ((AV_HWDEVICE_TYPE_CUDA == ctx->hw_type) && (ctx->vi >= 0)
290+
&& (AV_PIX_FMT_NONE != format)) {
291+
if (ctx->last_format == AV_PIX_FMT_NONE) ctx->last_format = format;
292+
else if (format != ctx->last_format) {
269293
LPMS_WARN("Input pixel format has been changed in the middle.");
270-
ctx->last_format = ctx->ic->streams[ctx->vi]->codecpar->format;
294+
ctx->last_format = format;
271295
// if the decoder is not re-opened when the video pixel format is changed,
272296
// the decoder tries HW decoding with the video context initialized to a pixel format different from the input one.
273297
// to handle a change in the input pixel format,
274-
// we close the demuxer and re-open the decoder by calling open_input().
275-
free_input(ctx, FORCE_CLOSE_HW_DECODER);
276-
ret = open_input(params, ctx);
277-
if (ret < 0) LPMS_ERR(open_input_err, "Unable to reopen video demuxer for HW decoding");
278-
reopen_decoders = 0;
298+
// we close the decoder so it will get reopened later
299+
close_video_decoder(ctx);
279300
}
280301
}
281302

282303
if (reopen_decoders) {
283-
if (!ctx->dv && (ctx->vi >= 0) &&
284-
(!ctx->vc || (ctx->hw_type == AV_HWDEVICE_TYPE_NONE))) {
285-
ret = open_video_decoder(ctx, video_codec);
286-
if (ret < 0) LPMS_ERR(open_input_err, "Unable to open video decoder")
287-
ctx->last_frame_v = av_frame_alloc();
288-
if (!ctx->last_frame_v) LPMS_ERR(open_input_err, "Unable to alloc last_frame_v");
304+
if (!ctx->dv && (ctx->vi >= 0) && (AV_PIX_FMT_NONE != format)) {
305+
// yes, we have video stream to decode, but check if we should reopen
306+
// decoder
307+
if (!ctx->vc || (ctx->hw_type == AV_HWDEVICE_TYPE_NONE)) {
308+
ret = open_video_decoder(ctx, video_codec);
309+
if (ret < 0) LPMS_ERR(open_input_err, "Unable to open video decoder")
310+
}
289311
} else LPMS_WARN("No video stream found in input");
290312

291313
if (!ctx->da && (ctx->ai >= 0)) {
292314
ret = open_audio_decoder(ctx, audio_codec);
293315
if (ret < 0) LPMS_ERR(open_input_err, "Unable to open audio decoder")
294-
ctx->last_frame_a = av_frame_alloc();
295-
if (!ctx->last_frame_a) LPMS_ERR(open_input_err, "Unable to alloc last_frame_a");
296316
} else LPMS_WARN("No audio stream found in input");
297317
}
298318

@@ -306,44 +326,19 @@ int open_input(input_params *params, struct input_ctx *ctx)
306326

307327
void free_input(struct input_ctx *ictx, enum FreeInputPolicy policy)
308328
{
309-
if (FORCE_CLOSE_HW_DECODER == policy) {
310-
// This means we are closing everything, so we also want to
311-
// remove demuxer
312-
if (ictx->ic) avformat_close_input(&ictx->ic);
313-
} else {
314-
// Otherwise we may want to retain demuxer in certain cases. Note that
315-
// this is a lot of effort for very little gain, because demuxer is very
316-
// cheap to create and destroy (being software component)
317-
if (ictx->ic) {
318-
// Only mpegts reuse the demuxer for subsequent segments.
319-
// Close the demuxer for everything else.
320-
// TODO might be reusable with fmp4 ; check!
321-
if (!is_mpegts(ictx->ic)) avformat_close_input(&ictx->ic);
322-
else if (ictx->ic->pb) {
323-
// Reset leftovers from demuxer internals to prepare for next segment
324-
avio_flush(ictx->ic->pb);
325-
avformat_flush(ictx->ic);
326-
avio_closep(&ictx->ic->pb);
327-
}
328-
}
329-
}
329+
if (ictx->ic) avformat_close_input(&ictx->ic);
330330
ictx->flushed = 0;
331331
ictx->flushing = 0;
332332
ictx->pkt_diff = 0;
333333
ictx->sentinel_count = 0;
334+
// this is allocated elsewhere on first video packet
334335
if (ictx->first_pkt) av_packet_free(&ictx->first_pkt);
335-
if (ictx->ac) avcodec_free_context(&ictx->ac);
336336
// video decoder is always closed when it is a SW decoder
337337
// otherwise only when forced
338-
int close_vc = ictx->vc &&
339-
((AV_HWDEVICE_TYPE_NONE == ictx->hw_type) || (FORCE_CLOSE_HW_DECODER == policy));
340-
if (close_vc) {
341-
if (ictx->vc->hw_device_ctx) av_buffer_unref(&ictx->vc->hw_device_ctx);
342-
avcodec_free_context(&ictx->vc);
343-
if (ictx->hw_device_ctx) av_buffer_unref(&ictx->hw_device_ctx);
344-
if (ictx->last_frame_v) av_frame_free(&ictx->last_frame_v);
338+
if ((AV_HWDEVICE_TYPE_NONE == ictx->hw_type) || (FORCE_CLOSE_HW_DECODER == policy)) {
339+
close_video_decoder(ictx);
345340
}
346-
if (ictx->last_frame_a) av_frame_free(&ictx->last_frame_a);
347-
341+
// audio decoder is always closed
342+
close_audio_decoder(ictx);
348343
}
349344

ffmpeg/decoder.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ struct input_ctx {
1010
AVFormatContext *ic; // demuxer required
1111
AVCodecContext *vc; // video decoder optional
1212
AVCodecContext *ac; // audo decoder optional
13+
// TODO: perhaps get rid of indices and introduce pointers same way as on
14+
// the encoder side, easier to check and easier to dereference without
15+
// pointer to demuxer
1316
int vi, ai; // video and audio stream indices
1417
int dv, da; // flags whether to drop video or audio
1518

0 commit comments

Comments
 (0)