diff --git a/ffmpeg/api_test.go b/ffmpeg/api_test.go index d61f52b21d..5aef81b01d 100755 --- a/ffmpeg/api_test.go +++ b/ffmpeg/api_test.go @@ -88,7 +88,7 @@ func TestAPI_SkippedSegment(t *testing.T) { } func TestTranscoderAPI_InvalidFile(t *testing.T) { - // Test the following file open results on input: fail, success, fail, success + // Test the following file open results on input: success, fail, success tc := NewTranscoder() defer tc.StopTranscoder() @@ -100,18 +100,9 @@ func TestTranscoderAPI_InvalidFile(t *testing.T) { Muxer: ComponentOptions{Name: "null"}, }} - // fail # 1 - in.Fname = "none" - _, err := tc.Transcode(in, out) - if err == nil || err.Error() != "TranscoderInvalidVideo" { - // Early codec check didn't find video in missing input file so we get `TranscoderInvalidVideo` - // instead of `No such file or directory` - t.Error("Expected 'TranscoderInvalidVideo', got ", err) - } - // success # 1 in.Fname = "../transcoder/test.ts" - _, err = tc.Transcode(in, out) + _, err := tc.Transcode(in, out) if err != nil { t.Error(err) } @@ -1217,27 +1208,6 @@ func audioOnlySegment(t *testing.T, accel Acceleration) { } } tc.StopTranscoder() - - // Test encoding with audio-only segment in start of stream - tc = NewTranscoder() - defer tc.StopTranscoder() - for i := 2; i < 4; i++ { - in := &TranscodeOptionsIn{ - Fname: fmt.Sprintf("%s/test%d.ts", dir, i), - Accel: accel, - } - out := []TranscodeOptions{{ - Oname: fmt.Sprintf("%s/out2_%d.ts", dir, i), - Profile: prof, - Accel: accel, - }} - _, err := tc.Transcode(in, out) - if i == 2 && (err == nil || err.Error() != "TranscoderInvalidVideo") { - t.Errorf("Expected to fail for audio-only segment but did not, instead got err=%v", err) - } else if i != 2 && err != nil { - t.Error(err) - } - } } func TestTranscoder_AudioOnly(t *testing.T) { diff --git a/ffmpeg/decoder.c b/ffmpeg/decoder.c index 9f830d99e0..f1b90123e2 100755 --- a/ffmpeg/decoder.c +++ b/ffmpeg/decoder.c @@ -128,21 +128,22 @@ static enum AVPixelFormat get_hw_pixfmt(AVCodecContext *vc, const enum AVPixelFo } -int open_audio_decoder(struct input_ctx *ctx, AVCodec *codec) +static int open_audio_decoder(struct input_ctx *ctx, AVCodec *codec) { int ret = 0; AVFormatContext *ic = ctx->ic; // open audio decoder - AVCodecContext * ac = avcodec_alloc_context3(codec); - if (!ac) LPMS_ERR(open_audio_err, "Unable to alloc audio codec"); - if (ctx->ac) LPMS_WARN("An audio context was already open!"); - ctx->ac = ac; - ret = avcodec_parameters_to_context(ac, ic->streams[ctx->ai]->codecpar); - if (ret < 0) LPMS_ERR(open_audio_err, "Unable to assign audio params"); - ret = avcodec_open2(ac, codec, NULL); - if (ret < 0) LPMS_ERR(open_audio_err, "Unable to open audio decoder"); - + AVCodecContext * ac = avcodec_alloc_context3(codec); + if (!ac) LPMS_ERR(open_audio_err, "Unable to alloc audio codec"); + if (ctx->ac) LPMS_WARN("An audio context was already open!"); + ctx->ac = ac; + ret = avcodec_parameters_to_context(ac, ic->streams[ctx->ai]->codecpar); + if (ret < 0) LPMS_ERR(open_audio_err, "Unable to assign audio params"); + ret = avcodec_open2(ac, codec, NULL); + if (ret < 0) LPMS_ERR(open_audio_err, "Unable to open audio decoder"); + ctx->last_frame_a = av_frame_alloc(); + if (!ctx->last_frame_a) LPMS_ERR(open_audio_err, "Unable to alloc last_frame_a"); return 0; open_audio_err: @@ -150,7 +151,13 @@ int open_audio_decoder(struct input_ctx *ctx, AVCodec *codec) return ret; } -char* get_hw_decoder(int ff_codec_id, int hw_type) +static void close_audio_decoder(struct input_ctx *ictx) +{ + if (ictx->ac) avcodec_free_context(&ictx->ac); + if (ictx->last_frame_a) av_frame_free(&ictx->last_frame_a); +} + +static char* get_hw_decoder(int ff_codec_id, int hw_type) { switch (hw_type) { case AV_HWDEVICE_TYPE_CUDA: @@ -184,47 +191,51 @@ char* get_hw_decoder(int ff_codec_id, int hw_type) } } -int open_video_decoder(struct input_ctx *ctx, AVCodec *codec) +static int open_video_decoder(struct input_ctx *ctx, AVCodec *codec) { int ret = 0; AVDictionary **opts = NULL; AVFormatContext *ic = ctx->ic; // open video decoder - if (ctx->hw_type > AV_HWDEVICE_TYPE_NONE) { - char* decoder_name = get_hw_decoder(codec->id, ctx->hw_type); - if (!*decoder_name) { - ret = lpms_ERR_INPUT_CODEC; - LPMS_ERR(open_decoder_err, "Input codec does not support hardware acceleration"); - } - AVCodec *c = avcodec_find_decoder_by_name(decoder_name); - if (c) codec = c; - else LPMS_WARN("Nvidia decoder not found; defaulting to software"); - if (AV_PIX_FMT_YUV420P != ic->streams[ctx->vi]->codecpar->format && - AV_PIX_FMT_YUVJ420P != ic->streams[ctx->vi]->codecpar->format) { - // TODO check whether the color range is truncated if yuvj420p is used - ret = lpms_ERR_INPUT_PIXFMT; - LPMS_ERR(open_decoder_err, "Non 4:2:0 pixel format detected in input"); - } + if (ctx->hw_type > AV_HWDEVICE_TYPE_NONE) { + char* decoder_name = get_hw_decoder(codec->id, ctx->hw_type); + if (!*decoder_name) { + ret = lpms_ERR_INPUT_CODEC; + LPMS_ERR(open_decoder_err, "Input codec does not support hardware acceleration"); } - AVCodecContext *vc = avcodec_alloc_context3(codec); - if (!vc) LPMS_ERR(open_decoder_err, "Unable to alloc video codec"); - ctx->vc = vc; - ret = avcodec_parameters_to_context(vc, ic->streams[ctx->vi]->codecpar); - if (ret < 0) LPMS_ERR(open_decoder_err, "Unable to assign video params"); - vc->opaque = (void*)ctx; - // XXX Could this break if the original device falls out of scope in golang? - if (ctx->hw_type == AV_HWDEVICE_TYPE_CUDA) { - // First set the hw device then set the hw frame - ret = av_hwdevice_ctx_create(&ctx->hw_device_ctx, ctx->hw_type, ctx->device, NULL, 0); - if (ret < 0) LPMS_ERR(open_decoder_err, "Unable to open hardware context for decoding") - vc->hw_device_ctx = av_buffer_ref(ctx->hw_device_ctx); - vc->get_format = get_hw_pixfmt; + AVCodec *c = avcodec_find_decoder_by_name(decoder_name); + if (c) codec = c; + else LPMS_WARN("Nvidia decoder not found; defaulting to software"); + // It is safe to use ctx->vi here, because open_video_decoder won't be + // called if vi < 0 + if (AV_PIX_FMT_YUV420P != ic->streams[ctx->vi]->codecpar->format && + AV_PIX_FMT_YUVJ420P != ic->streams[ctx->vi]->codecpar->format) { + // TODO check whether the color range is truncated if yuvj420p is used + ret = lpms_ERR_INPUT_PIXFMT; + LPMS_ERR(open_decoder_err, "Non 4:2:0 pixel format detected in input"); } - vc->pkt_timebase = ic->streams[ctx->vi]->time_base; - av_opt_set(vc->priv_data, "xcoder-params", ctx->xcoderParams, 0); - ret = avcodec_open2(vc, codec, opts); - if (ret < 0) LPMS_ERR(open_decoder_err, "Unable to open video decoder"); + } + AVCodecContext *vc = avcodec_alloc_context3(codec); + if (!vc) LPMS_ERR(open_decoder_err, "Unable to alloc video codec"); + ctx->vc = vc; + ret = avcodec_parameters_to_context(vc, ic->streams[ctx->vi]->codecpar); + if (ret < 0) LPMS_ERR(open_decoder_err, "Unable to assign video params"); + vc->opaque = (void*)ctx; + // XXX Could this break if the original device falls out of scope in golang? + if (ctx->hw_type == AV_HWDEVICE_TYPE_CUDA) { + // First set the hw device then set the hw frame + ret = av_hwdevice_ctx_create(&ctx->hw_device_ctx, ctx->hw_type, ctx->device, NULL, 0); + if (ret < 0) LPMS_ERR(open_decoder_err, "Unable to open hardware context for decoding") + vc->hw_device_ctx = av_buffer_ref(ctx->hw_device_ctx); + vc->get_format = get_hw_pixfmt; + } + vc->pkt_timebase = ic->streams[ctx->vi]->time_base; + av_opt_set(vc->priv_data, "xcoder-params", ctx->xcoderParams, 0); + ret = avcodec_open2(vc, codec, opts); + if (ret < 0) LPMS_ERR(open_decoder_err, "Unable to open video decoder"); + ctx->last_frame_v = av_frame_alloc(); + if (!ctx->last_frame_v) LPMS_ERR(open_decoder_err, "Unable to alloc last_frame_v"); return 0; open_decoder_err: @@ -233,6 +244,16 @@ int open_video_decoder(struct input_ctx *ctx, AVCodec *codec) return ret; } +static void close_video_decoder(struct input_ctx *ictx) +{ + if (ictx->vc) { + if (ictx->vc->hw_device_ctx) av_buffer_unref(&ictx->vc->hw_device_ctx); + avcodec_free_context(&ictx->vc); + } + if (ictx->hw_device_ctx) av_buffer_unref(&ictx->hw_device_ctx); + if (ictx->last_frame_v) av_frame_free(&ictx->last_frame_v); +} + int open_input(input_params *params, struct input_ctx *ctx) { char *inp = params->fname; @@ -247,52 +268,51 @@ int open_input(input_params *params, struct input_ctx *ctx) ctx->device = params->device; // open demuxer - if (!ctx->ic) { - ret = avformat_open_input(&ctx->ic, inp, NULL, NULL); - if (ret < 0) LPMS_ERR(open_input_err, "demuxer: Unable to open input"); - ret = avformat_find_stream_info(ctx->ic, NULL); - if (ret < 0) LPMS_ERR(open_input_err, "Unable to find input info"); - } else if (!ctx->ic->pb) { - // reopen input segment file IO context if needed - ret = avio_open(&ctx->ic->pb, inp, AVIO_FLAG_READ); - if (ret < 0) LPMS_ERR(open_input_err, "Unable to reopen file"); - } else reopen_decoders = 0; + ret = avformat_open_input(&ctx->ic, inp, NULL, NULL); + if (ret < 0) LPMS_ERR(open_input_err, "demuxer: Unable to open input"); + ret = avformat_find_stream_info(ctx->ic, NULL); + if (ret < 0) LPMS_ERR(open_input_err, "Unable to find input info"); AVCodec *video_codec = NULL; AVCodec *audio_codec = NULL; ctx->vi = av_find_best_stream(ctx->ic, AVMEDIA_TYPE_VIDEO, -1, -1, &video_codec, 0); ctx->ai = av_find_best_stream(ctx->ic, AVMEDIA_TYPE_AUDIO, -1, -1, &audio_codec, 0); - if (AV_HWDEVICE_TYPE_CUDA == ctx->hw_type && ctx->vi >= 0) { - if (ctx->last_format == AV_PIX_FMT_NONE) ctx->last_format = ctx->ic->streams[ctx->vi]->codecpar->format; - else if (ctx->ic->streams[ctx->vi]->codecpar->format != ctx->last_format) { + // Now be careful here. It appears that in certain situation (such as .ts + // stream without video stream) ctx->vi will be set to 0, but the format will + // be set to AV_PIX_FMT_NONE and both width and height will be zero, etc + // This is normally fine, but when re-using video decoder we have to be + // extra careful, and handle both situations: one with negative vi, and one + // with positive vi but AV_PIX_FMT_NONE in stream format + enum AVPixelFormat format = + (ctx->vi >= 0) ? ctx->ic->streams[ctx->vi]->codecpar->format : AV_PIX_FMT_NONE; + if ((AV_HWDEVICE_TYPE_CUDA == ctx->hw_type) && (ctx->vi >= 0) + && (AV_PIX_FMT_NONE != format)) { + if (ctx->last_format == AV_PIX_FMT_NONE) ctx->last_format = format; + else if (format != ctx->last_format) { LPMS_WARN("Input pixel format has been changed in the middle."); - ctx->last_format = ctx->ic->streams[ctx->vi]->codecpar->format; + ctx->last_format = format; // if the decoder is not re-opened when the video pixel format is changed, // the decoder tries HW decoding with the video context initialized to a pixel format different from the input one. // to handle a change in the input pixel format, - // we close the demuxer and re-open the decoder by calling open_input(). - free_input(ctx, FORCE_CLOSE_HW_DECODER); - ret = open_input(params, ctx); - if (ret < 0) LPMS_ERR(open_input_err, "Unable to reopen video demuxer for HW decoding"); - reopen_decoders = 0; + // we close the decoder so it will get reopened later + close_video_decoder(ctx); } } if (reopen_decoders) { - if (!ctx->dv && (ctx->vi >= 0) && - (!ctx->vc || (ctx->hw_type == AV_HWDEVICE_TYPE_NONE))) { - ret = open_video_decoder(ctx, video_codec); - if (ret < 0) LPMS_ERR(open_input_err, "Unable to open video decoder") - ctx->last_frame_v = av_frame_alloc(); - if (!ctx->last_frame_v) LPMS_ERR(open_input_err, "Unable to alloc last_frame_v"); + if (!ctx->dv && (ctx->vi >= 0) && (AV_PIX_FMT_NONE != format)) { + // yes, we have video stream to decode, but check if we should reopen + // decoder + if (!ctx->vc || (ctx->hw_type == AV_HWDEVICE_TYPE_NONE)) { + ret = open_video_decoder(ctx, video_codec); + if (ret < 0) LPMS_ERR(open_input_err, "Unable to open video decoder") + } } else LPMS_WARN("No video stream found in input"); if (!ctx->da && (ctx->ai >= 0)) { ret = open_audio_decoder(ctx, audio_codec); if (ret < 0) LPMS_ERR(open_input_err, "Unable to open audio decoder") - ctx->last_frame_a = av_frame_alloc(); - if (!ctx->last_frame_a) LPMS_ERR(open_input_err, "Unable to alloc last_frame_a"); } else LPMS_WARN("No audio stream found in input"); } @@ -306,44 +326,19 @@ int open_input(input_params *params, struct input_ctx *ctx) void free_input(struct input_ctx *ictx, enum FreeInputPolicy policy) { - if (FORCE_CLOSE_HW_DECODER == policy) { - // This means we are closing everything, so we also want to - // remove demuxer - if (ictx->ic) avformat_close_input(&ictx->ic); - } else { - // Otherwise we may want to retain demuxer in certain cases. Note that - // this is a lot of effort for very little gain, because demuxer is very - // cheap to create and destroy (being software component) - if (ictx->ic) { - // Only mpegts reuse the demuxer for subsequent segments. - // Close the demuxer for everything else. - // TODO might be reusable with fmp4 ; check! - if (!is_mpegts(ictx->ic)) avformat_close_input(&ictx->ic); - else if (ictx->ic->pb) { - // Reset leftovers from demuxer internals to prepare for next segment - avio_flush(ictx->ic->pb); - avformat_flush(ictx->ic); - avio_closep(&ictx->ic->pb); - } - } - } + if (ictx->ic) avformat_close_input(&ictx->ic); ictx->flushed = 0; ictx->flushing = 0; ictx->pkt_diff = 0; ictx->sentinel_count = 0; + // this is allocated elsewhere on first video packet if (ictx->first_pkt) av_packet_free(&ictx->first_pkt); - if (ictx->ac) avcodec_free_context(&ictx->ac); // video decoder is always closed when it is a SW decoder // otherwise only when forced - int close_vc = ictx->vc && - ((AV_HWDEVICE_TYPE_NONE == ictx->hw_type) || (FORCE_CLOSE_HW_DECODER == policy)); - if (close_vc) { - if (ictx->vc->hw_device_ctx) av_buffer_unref(&ictx->vc->hw_device_ctx); - avcodec_free_context(&ictx->vc); - if (ictx->hw_device_ctx) av_buffer_unref(&ictx->hw_device_ctx); - if (ictx->last_frame_v) av_frame_free(&ictx->last_frame_v); + if ((AV_HWDEVICE_TYPE_NONE == ictx->hw_type) || (FORCE_CLOSE_HW_DECODER == policy)) { + close_video_decoder(ictx); } - if (ictx->last_frame_a) av_frame_free(&ictx->last_frame_a); - + // audio decoder is always closed + close_audio_decoder(ictx); } diff --git a/ffmpeg/decoder.h b/ffmpeg/decoder.h index 600680a4ca..77a3e60360 100755 --- a/ffmpeg/decoder.h +++ b/ffmpeg/decoder.h @@ -10,6 +10,9 @@ struct input_ctx { AVFormatContext *ic; // demuxer required AVCodecContext *vc; // video decoder optional AVCodecContext *ac; // audo decoder optional + // TODO: perhaps get rid of indices and introduce pointers same way as on + // the encoder side, easier to check and easier to dereference without + // pointer to demuxer int vi, ai; // video and audio stream indices int dv, da; // flags whether to drop video or audio diff --git a/ffmpeg/encoder.c b/ffmpeg/encoder.c index c57a98bec4..f951766f03 100755 --- a/ffmpeg/encoder.c +++ b/ffmpeg/encoder.c @@ -42,22 +42,19 @@ static int add_video_stream(struct output_ctx *octx, struct input_ctx *ictx) { // video stream to muxer int ret = 0; - AVStream *st = NULL; if (is_copy(octx->video->name)) { // create stream as a copy of existing one if (ictx->vi < 0) LPMS_ERR(add_video_err, "Input video stream does not exist"); - st = add_stream_copy(octx, ictx->ic->streams[ictx->vi]); - if (!st) LPMS_ERR(add_video_err, "Error adding video copy stream"); - octx->vi = st->index; - if (octx->fps.den) st->avg_frame_rate = octx->fps; - else st->avg_frame_rate = ictx->ic->streams[ictx->vi]->r_frame_rate; + octx->video_stream = add_stream_copy(octx, ictx->ic->streams[ictx->vi]); + if (!octx->video_stream) LPMS_ERR(add_video_err, "Error adding video copy stream"); + if (octx->fps.den) octx->video_stream->avg_frame_rate = octx->fps; + else octx->video_stream->avg_frame_rate = ictx->ic->streams[ictx->vi]->r_frame_rate; } else if (octx->vc) { // create stream from encoder - st = add_stream_for_encoder(octx, octx->vc); - if (!st) LPMS_ERR(add_video_err, "Error adding video encoder stream"); - octx->vi = st->index; - if (octx->fps.den) st->avg_frame_rate = octx->fps; - else st->avg_frame_rate = ictx->ic->streams[ictx->vi]->r_frame_rate; + octx->video_stream = add_stream_for_encoder(octx, octx->vc); + if (!octx->video_stream) LPMS_ERR(add_video_err, "Error adding video encoder stream"); + if (octx->fps.den) octx->video_stream->avg_frame_rate = octx->fps; + else octx->video_stream->avg_frame_rate = ictx->ic->streams[ictx->vi]->r_frame_rate; // Video has rescale here. Audio is slightly different // Rescale the gop/clip time to the expected timebase after filtering. // The FPS filter outputs pts incrementing by 1 at a rate of 1/framerate @@ -65,7 +62,7 @@ static int add_video_stream(struct output_ctx *octx, struct input_ctx *ictx) AVRational ms_tb = {1, 1000}; AVRational dest_tb; if (octx->fps.den) dest_tb = av_inv_q(octx->fps); - else dest_tb = ictx->ic->streams[ictx->vi]->time_base; + else dest_tb = ictx->ic->streams[ictx->vi]->time_base; // should be safe to use vi if (octx->gop_time) { octx->gop_pts_len = av_rescale_q(octx->gop_time, ms_tb, dest_tb); octx->next_kf_pts = 0; // force for first frame @@ -77,8 +74,14 @@ static int add_video_stream(struct output_ctx *octx, struct input_ctx *ictx) octx->clip_to_pts = av_rescale_q(octx->clip_to, ms_tb, dest_tb); } } else if (is_drop(octx->video->name)) { + octx->video_stream = NULL; LPMS_ERR(add_video_err, "add_video_stream called for dropped video!"); - } else LPMS_ERR(add_video_err, "No video encoder, not a copy; what is this?"); + } else { + // this can actually happen if the transcoder configured for video + // gets segment without actual video stream + octx->video_stream = NULL; + LPMS_WARN("No video encoder, not a copy; missing video input perhaps?"); + } octx->last_video_dts = AV_NOPTS_VALUE; return 0; @@ -99,26 +102,26 @@ static int add_audio_stream(struct input_ctx *ictx, struct output_ctx *octx) // audio stream to muxer int ret = 0; - AVStream *st = NULL; if (is_copy(octx->audio->name)) { // create stream as a copy of existing one if (ictx->ai < 0) LPMS_ERR(add_audio_err, "Input audio stream does not exist"); - st = add_stream_copy(octx, ictx->ic->streams[ictx->ai]); - octx->ai = st->index; + octx->audio_stream = add_stream_copy(octx, ictx->ic->streams[ictx->ai]); } else if (octx->ac) { // create stream from encoder - st = add_stream_for_encoder(octx, octx->ac); - octx->ai = st->index; + octx->audio_stream = add_stream_for_encoder(octx, octx->ac); // Video has rescale here } else if (is_drop(octx->audio->name)) { // Supposed to exit this function early if there's a drop + octx->audio_stream = NULL; LPMS_ERR(add_audio_err, "add_audio_stream called for dropped audio!"); } else { - LPMS_ERR(add_audio_err, "No audio encoder; not a copy; what is this?"); + // see comment in add_video_stream above + octx->audio_stream = NULL; + LPMS_WARN("No audio encoder; not a copy; missing audio input perhaps?"); + return 0; } - if (!st) LPMS_ERR(add_audio_err, "Error adding video copy stream"); - octx->ai = st->index; + if (!octx->audio_stream) LPMS_ERR(add_audio_err, "Error adding audio stream");; // Audio has rescale here. Video version is slightly different AVRational ms_tb = {1, 1000}; @@ -131,7 +134,7 @@ static int add_audio_stream(struct input_ctx *ictx, struct output_ctx *octx) } // signal whether to drop preroll audio - if (st->codecpar->initial_padding) octx->drop_ts = AV_NOPTS_VALUE; + if (octx->audio_stream->codecpar->initial_padding) octx->drop_ts = AV_NOPTS_VALUE; octx->last_audio_dts = AV_NOPTS_VALUE; @@ -217,10 +220,10 @@ static int open_video_encoder(struct input_ctx *ictx, struct output_ctx *octx, vc->height = av_buffersink_get_h(octx->vf.sink_ctx); if (octx->fps.den) vc->framerate = av_buffersink_get_frame_rate(octx->vf.sink_ctx); else if (ictx->vc->framerate.num && ictx->vc->framerate.den) vc->framerate = ictx->vc->framerate; - else vc->framerate = ictx->ic->streams[ictx->vi]->r_frame_rate; + else vc->framerate = ictx->ic->streams[ictx->vi]->r_frame_rate; // vi should be safe if (octx->fps.den) vc->time_base = av_buffersink_get_time_base(octx->vf.sink_ctx); else if (ictx->vc->time_base.num && ictx->vc->time_base.den) vc->time_base = ictx->vc->time_base; - else vc->time_base = ictx->ic->streams[ictx->vi]->time_base; + else vc->time_base = ictx->ic->streams[ictx->vi]->time_base; // vi should be safe if (octx->bitrate) vc->rc_min_rate = vc->bit_rate = vc->rc_max_rate = vc->rc_buffer_size = octx->bitrate; if (av_buffersink_get_hw_frames_ctx(octx->vf.sink_ctx)) { vc->hw_frames_ctx = diff --git a/ffmpeg/ffmpeg.go b/ffmpeg/ffmpeg.go index b22a3239da..6a947135a5 100755 --- a/ffmpeg/ffmpeg.go +++ b/ffmpeg/ffmpeg.go @@ -88,7 +88,6 @@ type Transcoder struct { handle *C.struct_transcode_thread stopped bool started bool - lastacodec string mu *sync.Mutex } @@ -857,8 +856,6 @@ func (t *Transcoder) Transcode(input *TranscodeOptionsIn, ps []TranscodeOptions) if input == nil { return nil, ErrTranscoderInp } - var reopendemux bool - reopendemux = false // don't read metadata for pipe input, because it can't seek back and av_find_input_format in the decoder will fail if !strings.HasPrefix(strings.ToLower(input.Fname), "pipe:") { status, format, err := GetCodecInfo(input.Fname) @@ -875,30 +872,8 @@ func (t *Transcoder) Transcode(input *TranscodeOptionsIn, ps []TranscodeOptions) } } if !t.started { - // NeedsBypass is state where video is present in container & without any frames - videoMissing := status == CodecStatusNeedsBypass || format.Vcodec == "" - if videoMissing { - // Audio-only segment, fail fast right here as we cannot handle them nicely - return nil, ErrTranscoderVid - } - // keep last audio codec - t.lastacodec = format.Acodec // Stream is either OK or completely broken, let the transcoder handle it t.started = true - } else { - // check if we need to reopen demuxer because added audio in video - // TODO: fixes like that are needed because handling of cfg change in - // LPMS is a joke. We need to decide whether LPMS should support full - // dynamic config one day and either implement it there, or implement - // some generic workaround for the problem in Go code, such as marking - // config changes as significant/insignificant and re-creating the instance - // if the former type change happens - if format.Acodec != "" && !isAudioAllDrop(ps) { - if (t.lastacodec == "") || (t.lastacodec != "" && t.lastacodec != format.Acodec) { - reopendemux = true - t.lastacodec = format.Acodec - } - } } } hw_type, err := accelDeviceType(input.Accel) @@ -954,16 +929,6 @@ func (t *Transcoder) Transcode(input *TranscodeOptionsIn, ps []TranscodeOptions) paramsPointer = (*C.output_params)(¶ms[0]) resultsPointer = (*C.output_results)(&results[0]) } - if reopendemux { - // forcefully close and open demuxer - ret := int(C.lpms_transcode_reopen_demux(inp)) - if ret != 0 { - if LogTranscodeErrors { - glog.Error("Reopen demux returned : ", ErrorMap[ret]) - } - return nil, ErrorMap[ret] - } - } ret := int(C.lpms_transcode(inp, paramsPointer, resultsPointer, C.int(len(params)), decoded)) if ret != 0 { if LogTranscodeErrors { diff --git a/ffmpeg/ffmpeg_test.go b/ffmpeg/ffmpeg_test.go index 098e81d15a..19232b877b 100644 --- a/ffmpeg/ffmpeg_test.go +++ b/ffmpeg/ffmpeg_test.go @@ -871,17 +871,6 @@ func TestTranscoder_StreamCopy(t *testing.T) { if res.Decoded.Frames != 0 || res.Encoded[0].Frames != 0 { t.Error("Unexpected count of decoded/encoded frames") } - in = &TranscodeOptionsIn{Fname: dir + "/audioonly.ts"} - out = []TranscodeOptions{ - { - Oname: dir + "/noaudio.ts", - Profile: P144p30fps16x9, - AudioEncoder: ComponentOptions{Name: "copy"}, - }, - } - // Audio only segments are not supported - _, err = Transcode3(in, out) - assert.EqualError(t, err, "TranscoderInvalidVideo") } func TestTranscoder_StreamCopy_Validate_B_Frames(t *testing.T) { @@ -1010,11 +999,6 @@ func TestTranscoder_Drop(t *testing.T) { if res.Decoded.Frames != 30 || res.Encoded[0].Frames != 30 { t.Error("Unexpected encoded/decoded frame counts ", res.Decoded.Frames, res.Encoded[0].Frames) } - in.Fname = dir + "/novideo.ts" - out = []TranscodeOptions{{Oname: dir + "/encoded-audio.mp4", Profile: P144p30fps16x9}} - _, err = Transcode3(in, out) - // Audio only segments are not supported - assert.EqualError(t, err, "TranscoderInvalidVideo") } func TestTranscoder_StreamCopyAndDrop(t *testing.T) { diff --git a/ffmpeg/filter.c b/ffmpeg/filter.c index 3865b9d23e..7bcabd8377 100644 --- a/ffmpeg/filter.c +++ b/ffmpeg/filter.c @@ -53,6 +53,8 @@ int init_video_filters(struct input_ctx *ictx, struct output_ctx *octx) const AVFilter *buffersink = avfilter_get_by_name("buffersink"); AVFilterInOut *outputs = NULL; AVFilterInOut *inputs = NULL; + // vi should be safe here because this function gets called only if video + // is available AVRational time_base = ictx->ic->streams[ictx->vi]->time_base; enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_YUV420P, AV_PIX_FMT_CUDA, AV_PIX_FMT_NONE }; // XXX ensure the encoder allows this struct filter_ctx *vf = &octx->vf; @@ -302,7 +304,7 @@ int filtergraph_write(AVFrame *inf, struct input_ctx *ictx, struct output_ctx *o } // Timestamp handling code - AVStream *vst = ictx->ic->streams[ictx->vi]; + AVStream *vst = (ictx->vi >= 0) ? ictx->ic->streams[ictx->vi] : NULL; if (inf) { // Non-Flush Frame inf->opaque = (void *) inf->pts; // Store original PTS for calc later if (is_video && octx->fps.den) { @@ -363,6 +365,7 @@ int filtergraph_read(struct input_ctx *ictx, struct output_ctx *octx, struct fil // re-calculate our output PTS before passing it on to the encoder if (filter->pts_diff == INT64_MIN) { int64_t pts = (int64_t)frame->opaque; // original input PTS + // safe to use ictx->vi because we know this is video frame pts = av_rescale_q_rnd(pts, ictx->ic->streams[ictx->vi]->time_base, av_buffersink_get_time_base(filter->sink_ctx), AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX); // difference between rescaled input PTS and the segment's first frame PTS of the filtergraph output filter->pts_diff = pts - frame->pts; diff --git a/ffmpeg/filter.h b/ffmpeg/filter.h index 2e556b63cd..5e6995c493 100755 --- a/ffmpeg/filter.h +++ b/ffmpeg/filter.h @@ -45,7 +45,8 @@ struct output_ctx { AVFormatContext *oc; // muxer required AVCodecContext *vc; // video decoder optional AVCodecContext *ac; // audo decoder optional - int vi, ai; // video and audio stream indices + AVStream *audio_stream; + AVStream *video_stream; int dv, da; // flags whether to drop video or audio struct filter_ctx vf, af, sf; diff --git a/ffmpeg/transcoder.c b/ffmpeg/transcoder.c index 59b60b83ec..245581ec1e 100755 --- a/ffmpeg/transcoder.c +++ b/ffmpeg/transcoder.c @@ -36,10 +36,9 @@ const int lpms_ERR_UNRECOVERABLE = FFERRTAG('U', 'N', 'R', 'V'); // short of re-initializing the component. This is addressed for each component // as follows: // -// Demuxer: For resumable / header-less formats such as mpegts, the demuxer -// is reused across segments. This gives a small speed boost. For -// all other formats, the demuxer is closed and reopened at the next -// segment. +// Demuxer: Used to be reused, but it was found very problematic, as reused +// muxer retained information from previous segments. It caused all +// kind of subtle problems and was removed // // MOVED TO decoder.[ch] @@ -133,13 +132,13 @@ static int flush_output(struct input_ctx *ictx, struct output_ctx *octx) int ret = 0; if (octx->vc) { // flush video while (!ret || ret == AVERROR(EAGAIN)) { - ret = process_out(ictx, octx, octx->vc, octx->oc->streams[0], &octx->vf, NULL); + ret = process_out(ictx, octx, octx->vc, octx->video_stream, &octx->vf, NULL); } } ret = 0; if (octx->ac) { // flush audio while (!ret || ret == AVERROR(EAGAIN)) { - ret = process_out(ictx, octx, octx->ac, octx->oc->streams[octx->dv ? 0 : 1], &octx->af, NULL); + ret = process_out(ictx, octx, octx->ac, octx->audio_stream, &octx->af, NULL); } } // send EOF signal to signature filter @@ -214,7 +213,6 @@ static int handle_audio_frame(struct transcode_thread *h, AVStream *ist, output_results *decoded_results, AVFrame *dframe) { struct input_ctx *ictx = &h->ictx; - ++decoded_results->audio_frames; // frame duration update int64_t dur = 0; @@ -237,7 +235,7 @@ static int handle_audio_frame(struct transcode_thread *h, AVStream *ist, if (octx->ac) { int ret = process_out(ictx, octx, octx->ac, - octx->oc->streams[octx->dv ? 0 : 1], &octx->af, dframe); + octx->audio_stream, &octx->af, dframe); if (AVERROR(EAGAIN) == ret || AVERROR_EOF == ret) continue; // this is ok if (ret < 0) LPMS_ERR_RETURN("Error encoding audio"); } @@ -320,7 +318,7 @@ static int handle_audio_packet(struct transcode_thread *h, output_results *decod if (octx->da) continue; // drop audio // If there is no encoder, then we are copying. Also the index of // audio stream is 0 when we are dropping video and 1 otherwise - if (!octx->ac) ost = octx->oc->streams[octx->dv ? 0 : 1]; + if (!octx->ac) ost = octx->audio_stream; } if (ost) { @@ -418,7 +416,7 @@ static int handle_video_packet(struct transcode_thread *h, output_results *decod // This is video stream for this output, but do we need packet? if (octx->dv) continue; // drop video // If there is no encoder, then we are copying - if (!octx->vc) ost = octx->oc->streams[0]; + if (!octx->vc) ost = octx->video_stream; } if (ost) { @@ -714,12 +712,6 @@ int lpms_transcode(input_params *inp, output_params *params, return ret; } -int lpms_transcode_reopen_demux(input_params *inp) -{ - free_input(&inp->handle->ictx, FORCE_CLOSE_HW_DECODER); - return open_input(inp, &inp->handle->ictx); -} - // TODO: name - this is called _stop, but it is more like stop & destroy void lpms_transcode_stop(struct transcode_thread *handle) { diff --git a/ffmpeg/transcoder.h b/ffmpeg/transcoder.h index 8047550bc7..e53a7635c0 100755 --- a/ffmpeg/transcoder.h +++ b/ffmpeg/transcoder.h @@ -98,7 +98,6 @@ enum LPMSLogLevel { void lpms_init(enum LPMSLogLevel max_level); int lpms_transcode(input_params *inp, output_params *params, output_results *results, int nb_outputs, output_results *decoded_results); -int lpms_transcode_reopen_demux(input_params *inp); struct transcode_thread* lpms_transcode_new(lvpdnn_opts *dnn_opts); void lpms_transcode_stop(struct transcode_thread* handle); void lpms_transcode_discontinuity(struct transcode_thread *handle);