From b8d80355911d2a9fd53312e8446f25c0b7bf49b2 Mon Sep 17 00:00:00 2001 From: Josh Allmann Date: Mon, 18 Nov 2024 00:17:11 +0000 Subject: [PATCH] ffmpeg: Add demuxer options. --- ffmpeg/decoder.c | 13 +++++++-- ffmpeg/ffmpeg.go | 20 ++++++++++---- ffmpeg/ffmpeg_test.go | 62 +++++++++++++++++++++++++++++++++++++++++++ ffmpeg/transcoder.c | 10 ++++++- 4 files changed, 97 insertions(+), 8 deletions(-) diff --git a/ffmpeg/decoder.c b/ffmpeg/decoder.c index c19ff7f446..0a73b99b32 100755 --- a/ffmpeg/decoder.c +++ b/ffmpeg/decoder.c @@ -355,10 +355,19 @@ int open_input(input_params *params, struct input_ctx *ctx) ctx->transmuxing = params->transmuxing; - // open demuxer/ open demuxer + const AVInputFormat *fmt = NULL; + if (params->demuxer.name) { + fmt = av_find_input_format(params->demuxer.name); + if (!fmt) { + ret = AVERROR_DEMUXER_NOT_FOUND; + LPMS_ERR(open_input_err, "Invalid demuxer name") + } + } + + // open demuxer AVDictionary **demuxer_opts = NULL; if (params->demuxer.opts) demuxer_opts = ¶ms->demuxer.opts; - ret = avformat_open_input(&ic, inp, NULL, demuxer_opts); + ret = avformat_open_input(&ic, inp, fmt, demuxer_opts); if (ret < 0) LPMS_ERR(open_input_err, "demuxer: Unable to open input"); // If avformat_open_input replaced the options AVDictionary with options that were not found free it if (demuxer_opts) av_dict_free(demuxer_opts); diff --git a/ffmpeg/ffmpeg.go b/ffmpeg/ffmpeg.go index 201f6aa161..4cdeed4902 100755 --- a/ffmpeg/ffmpeg.go +++ b/ffmpeg/ffmpeg.go @@ -98,6 +98,7 @@ type TranscodeOptionsIn struct { Device string Transmuxing bool Profile VideoProfile + Demuxer ComponentOptions } type TranscodeOptions struct { @@ -948,6 +949,12 @@ func (t *Transcoder) Transcode(input *TranscodeOptionsIn, ps []TranscodeOptions) var demuxerOpts C.component_opts + if input.Demuxer.Name != "" { + demuxerName := C.CString(input.Demuxer.Name) + defer C.free(unsafe.Pointer(demuxerName)) + demuxerOpts.name = demuxerName + } + ext := filepath.Ext(input.Fname) // If the input has an image file extension setup the image2 demuxer if ext == ".png" { @@ -963,14 +970,17 @@ func (t *Transcoder) Transcode(input *TranscodeOptionsIn, ps []TranscodeOptions) input.Profile.FramerateDen = 1 } - // Do not try to free in this function because in the C code avformat_open_input() - // will destroy this - demuxerOpts.opts = newAVOpts(map[string]string{ - "framerate": fmt.Sprintf("%d/%d", input.Profile.Framerate, input.Profile.FramerateDen), - }) + // changing the input map here is maybe not great + input.Demuxer.Opts["framerate"] = fmt.Sprintf("%d/%d", input.Profile.Framerate, input.Profile.FramerateDen) } } + if len(input.Demuxer.Opts) > 0 { + // Do not free in this function because avformat_open_input() + // in the C code will destroy this + demuxerOpts.opts = newAVOpts(input.Demuxer.Opts) + } + inp := &C.input_params{fname: fname, hw_type: hw_type, device: device, xcoderParams: xcoderParams, handle: t.handle, demuxer: demuxerOpts} if input.Transmuxing { diff --git a/ffmpeg/ffmpeg_test.go b/ffmpeg/ffmpeg_test.go index 1b3f1e6280..09611d8d62 100644 --- a/ffmpeg/ffmpeg_test.go +++ b/ffmpeg/ffmpeg_test.go @@ -2377,3 +2377,65 @@ func runRotationTests(t *testing.T, accel Acceleration) { ` run(cmd) } + +func TestTranscoder_DemuxerOpts(t *testing.T) { + // generate test files: a few frames of raw video + run, dir := setupTest(t) + defer os.RemoveAll(dir) + + cmd := ` + # use an unusual pixel format + ffmpeg -i "$1/../transcoder/test.ts" -an -c:v rawvideo -pix_fmt gbrp12be -s 320x240 -r 1 -frames:v 3 -f rawvideo test.raw + ffprobe -show_streams -count_frames -pixel_format gbrp12be -video_size 320x240 -f rawvideo test.raw 2>&1 | grep nb_read_frames=3 + ` + run(cmd) + res, err := Transcode3( + &TranscodeOptionsIn{ + Fname: dir + "/test.raw", + Demuxer: ComponentOptions{ + Name: "rawvideo", + Opts: map[string]string{ + "fflags": "+discardcorrupt+nobuffer", + "pixel_format": "gbrp12be", + "video_size": "320x240", + }, + }, + }, + []TranscodeOptions{{ + Oname: dir + "/out-%d.png", + Profile: VideoProfile{ + Name: "-", + Resolution: "200x150", + Bitrate: "10k", + }, + }}) + assert.Nil(t, err, "transcoder returned error") + assert := assert.New(t) + // we transcode 3 but decode/encode 2 due to nobuffer + assert.Equal(2, res.Decoded.Frames, "decoded frame count did not match") + assert.Equal(2, res.Encoded[0].Frames, "encoded frame count did not match") + assert.Equal(int64(2*320*240), res.Decoded.Pixels, "decoded pixel count did not match") + assert.Equal(int64(2*200*150), res.Encoded[0].Pixels, "encoded frame count did not match") +} + +func TestTranscoder_DemuxerOptsError(t *testing.T) { + + // nonexistent demuxer + _, err := Transcode3(&TranscodeOptionsIn{ + Fname: "../transcoder/test.ts", + Demuxer: ComponentOptions{ + Name: "nonexistent", + }, + }, nil) + assert.Equal(t, "Demuxer not found", err.Error()) + + // wrong demuxer + _, err = Transcode3(&TranscodeOptionsIn{ + Fname: "../transcoder/test.ts", + Demuxer: ComponentOptions{ + Name: "mp4", + }, + }, nil) + assert.Equal(t, "Invalid data found when processing input", err.Error()) + +} diff --git a/ffmpeg/transcoder.c b/ffmpeg/transcoder.c index b27a7e02e5..f83a6b1692 100755 --- a/ffmpeg/transcoder.c +++ b/ffmpeg/transcoder.c @@ -168,7 +168,15 @@ int transcode_init(struct transcode_thread *h, input_params *inp, if (!ictx->ic) { // reopen demuxer for the input segment if needed // XXX could open_input() be re-used here? - ret = avformat_open_input(&ictx->ic, inp->fname, NULL, demuxer_opts); + const AVInputFormat *fmt = NULL; + if (inp->demuxer.name) { + fmt = av_find_input_format(inp->demuxer.name); + if (!fmt) { + ret = AVERROR_DEMUXER_NOT_FOUND; + LPMS_ERR(transcode_cleanup, "Invalid demuxer name") + } + } + ret = avformat_open_input(&ictx->ic, inp->fname, fmt, demuxer_opts); if (ret < 0) LPMS_ERR(transcode_cleanup, "Unable to reopen demuxer"); // If avformat_open_input replaced the options AVDictionary with options that were not found free it if (demuxer_opts) av_dict_free(demuxer_opts);