From aa14cc5a661022cbe52c4a3bfdd85e64748a17c6 Mon Sep 17 00:00:00 2001 From: Josh Allmann Date: Thu, 5 Sep 2024 00:54:49 +0000 Subject: [PATCH 1/5] ffmpeg: Add a metadata option to each output This will allow us to identify the software version and who transcoded a given segment. --- ffmpeg/encoder.c | 2 ++ ffmpeg/ffmpeg.go | 7 ++++++- ffmpeg/ffmpeg_test.go | 9 ++++++++- ffmpeg/filter.h | 1 + ffmpeg/transcoder.c | 1 + ffmpeg/transcoder.h | 1 + 6 files changed, 19 insertions(+), 2 deletions(-) diff --git a/ffmpeg/encoder.c b/ffmpeg/encoder.c index 3ce1ab4527..c930b1362b 100755 --- a/ffmpeg/encoder.c +++ b/ffmpeg/encoder.c @@ -286,6 +286,8 @@ int open_output(struct output_ctx *octx, struct input_ctx *ictx) if (ret < 0) LPMS_ERR(open_output_err, "Error opening output file"); } + if (octx->metadata) av_dict_copy(&oc->metadata, octx->metadata, 0); + ret = avformat_write_header(oc, &octx->muxer->opts); if (ret < 0) LPMS_ERR(open_output_err, "Error writing header"); diff --git a/ffmpeg/ffmpeg.go b/ffmpeg/ffmpeg.go index 76c743fdb0..201f6aa161 100755 --- a/ffmpeg/ffmpeg.go +++ b/ffmpeg/ffmpeg.go @@ -112,6 +112,7 @@ type TranscodeOptions struct { Muxer ComponentOptions VideoEncoder ComponentOptions AudioEncoder ComponentOptions + Metadata map[string]string } type MediaInfo struct { @@ -778,6 +779,7 @@ func createCOutputParams(input *TranscodeOptionsIn, ps []TranscodeOptions) ([]C. name: C.CString(audioEncoder), opts: newAVOpts(p.AudioEncoder.Opts), } + metadata := newAVOpts(p.Metadata) fromMs := int(p.From.Milliseconds()) toMs := int(p.To.Milliseconds()) vfilt := C.CString(filters) @@ -786,7 +788,7 @@ func createCOutputParams(input *TranscodeOptionsIn, ps []TranscodeOptions) ([]C. params[i] = C.output_params{fname: oname, fps: fps, w: C.int(w), h: C.int(h), bitrate: C.int(bitrate), gop_time: C.int(gopMs), from: C.int(fromMs), to: C.int(toMs), - muxer: muxOpts, audio: audioOpts, video: vidOpts, + muxer: muxOpts, audio: audioOpts, video: vidOpts, metadata: metadata, vfilters: vfilt, sfilters: nil, xcoderParams: xcoderOutParams} if p.CalcSign { //signfilter string @@ -841,6 +843,9 @@ func destroyCOutputParams(params []C.output_params) { if p.video.opts != nil { C.av_dict_free(&p.video.opts) } + if p.metadata != nil { + C.av_dict_free(&p.metadata) + } } } diff --git a/ffmpeg/ffmpeg_test.go b/ffmpeg/ffmpeg_test.go index 93e184b9cc..14bdd67da9 100644 --- a/ffmpeg/ffmpeg_test.go +++ b/ffmpeg/ffmpeg_test.go @@ -1628,6 +1628,9 @@ func TestTranscoder_FormatOptions(t *testing.T) { Oname: dir + "/test.flv", VideoEncoder: ComponentOptions{Name: "copy"}, AudioEncoder: ComponentOptions{Name: "copy"}, + Metadata: map[string]string{ + "encoded_by": "Livepeer Media Server", + }, }} if out[0].Profile.Format != FormatNone { t.Error("Expected empty profile for output option") @@ -1637,7 +1640,7 @@ func TestTranscoder_FormatOptions(t *testing.T) { t.Error(err) } cmd = ` - ffprobe -loglevel warning -show_format test.flv | grep format_name=flv + ffprobe -loglevel warning -show_format test.flv | grep 'format_name=flv\|encoded_by=Livepeer Media Server' ` run(cmd) @@ -1646,6 +1649,9 @@ func TestTranscoder_FormatOptions(t *testing.T) { out[0].Muxer = ComponentOptions{Name: "hls", Opts: map[string]string{ "hls_segment_filename": dir + "/test_segment_%d.ts", }} + out[0].Metadata = map[string]string{ + "service_provider": "Livepeer Media Server", + } _, err = Transcode3(in, out) if err != nil { t.Error(err) @@ -1660,6 +1666,7 @@ func TestTranscoder_FormatOptions(t *testing.T) { ffprobe -loglevel warning -show_entries format=format_name,duration test.ts > test.out diff -u segment.out test.out wc -l test.out | grep 4 # sanity check output file length + ffprobe segment.ts 2>&1 | grep 'service_provider: Livepeer Media Server' ` run(cmd) diff --git a/ffmpeg/filter.h b/ffmpeg/filter.h index 59d9e8c945..412f185d0f 100755 --- a/ffmpeg/filter.h +++ b/ffmpeg/filter.h @@ -65,6 +65,7 @@ struct output_ctx { component_opts *muxer; component_opts *video; component_opts *audio; + AVDictionary *metadata; int64_t drop_ts; // preroll audio ts to drop diff --git a/ffmpeg/transcoder.c b/ffmpeg/transcoder.c index dfc0807aa5..b27a7e02e5 100755 --- a/ffmpeg/transcoder.c +++ b/ffmpeg/transcoder.c @@ -216,6 +216,7 @@ int transcode_init(struct transcode_thread *h, input_params *inp, octx->muxer = ¶ms[i].muxer; octx->audio = ¶ms[i].audio; octx->video = ¶ms[i].video; + octx->metadata = params[i].metadata; octx->vfilters = params[i].vfilters; octx->sfilters = params[i].sfilters; octx->xcoderParams = params[i].xcoderParams; diff --git a/ffmpeg/transcoder.h b/ffmpeg/transcoder.h index 8c1c4888b4..e0cca743b4 100755 --- a/ffmpeg/transcoder.h +++ b/ffmpeg/transcoder.h @@ -35,6 +35,7 @@ typedef struct { component_opts muxer; component_opts audio; component_opts video; + AVDictionary *metadata; } output_params; typedef struct { From beda797c2cbd393eb65d81e503419835da06d5a4 Mon Sep 17 00:00:00 2001 From: Josh Allmann Date: Fri, 6 Sep 2024 03:55:51 +0000 Subject: [PATCH 2/5] Also set metadata when reopening only the muxer under nvenc. --- ffmpeg/encoder.c | 2 ++ ffmpeg/ffmpeg_test.go | 44 +++++++++++++++++++++++++++++++++++++++++++ ffmpeg/nvidia_test.go | 5 +++++ 3 files changed, 51 insertions(+) diff --git a/ffmpeg/encoder.c b/ffmpeg/encoder.c index c930b1362b..0620ff02e2 100755 --- a/ffmpeg/encoder.c +++ b/ffmpeg/encoder.c @@ -326,6 +326,8 @@ int reopen_output(struct output_ctx *octx, struct input_ctx *ictx) ret = avio_open(&octx->oc->pb, octx->fname, AVIO_FLAG_WRITE); if (ret < 0) LPMS_ERR(reopen_out_err, "Error re-opening output file"); } + + if (octx->metadata) av_dict_copy(&octx->oc->metadata, octx->metadata, 0); ret = avformat_write_header(octx->oc, &octx->muxer->opts); if (ret < 0) LPMS_ERR(reopen_out_err, "Error re-writing header"); diff --git a/ffmpeg/ffmpeg_test.go b/ffmpeg/ffmpeg_test.go index 14bdd67da9..1b3f1e6280 100644 --- a/ffmpeg/ffmpeg_test.go +++ b/ffmpeg/ffmpeg_test.go @@ -1759,6 +1759,50 @@ func TestTranscoder_FormatOptions(t *testing.T) { } } +func TestTranscoder_Metadata(t *testing.T) { + runTestTranscoder_Metadata(t, Software) +} + +func runTestTranscoder_Metadata(t *testing.T, accel Acceleration) { + // check that metadata is there in all segments + run, dir := setupTest(t) + defer os.RemoveAll(dir) + + err := RTMPToHLS("../transcoder/test.ts", dir+"/in.m3u8", dir+"/in_%d.ts", "2", 0) + if err != nil { + t.Error(err) + } + tc := NewTranscoder() + defer tc.StopTranscoder() + for i := 0; i < 4; i++ { + in := &TranscodeOptionsIn{ + Fname: fmt.Sprintf("%s/in_%d.ts", dir, i), + Accel: accel, + } + out := []TranscodeOptions{{ + Accel: accel, + Oname: fmt.Sprintf("%s/out_%d.ts", dir, i), + Profile: P144p30fps16x9, + Metadata: map[string]string{ + "service_name": fmt.Sprintf("lpms-test-%d", i), + }, + }} + _, err := tc.Transcode(in, out) + if err != nil { + t.Error(err) + } + } + + cmd := ` + ffprobe -hide_banner -i out_1.ts + ffprobe -i out_0.ts 2>&1 | grep 'service_name : lpms-test-0' + ffprobe -i out_1.ts 2>&1 | grep 'service_name : lpms-test-1' + ffprobe -i out_2.ts 2>&1 | grep 'service_name : lpms-test-2' + ffprobe -i out_3.ts 2>&1 | grep 'service_name : lpms-test-3' + ` + run(cmd) +} + func TestTranscoder_IgnoreUnknown(t *testing.T) { run, dir := setupTest(t) defer os.RemoveAll(dir) diff --git a/ffmpeg/nvidia_test.go b/ffmpeg/nvidia_test.go index 86c0f76a88..0206c98285 100755 --- a/ffmpeg/nvidia_test.go +++ b/ffmpeg/nvidia_test.go @@ -801,3 +801,8 @@ func TestNvidia_DiscontinuityAudioSegment(t *testing.T) { func TestNvidia_Rotation(t *testing.T) { runRotationTests(t, Nvidia) } + +func TestNvidia_Metadata(t *testing.T) { + // with nvenc we reopen the outputs so exercise that + runTestTranscoder_Metadata(t, Nvidia) +} From f27bb55944e21777321e13a82b5686c75f8907fa Mon Sep 17 00:00:00 2001 From: Josh Allmann Date: Fri, 6 Sep 2024 20:59:46 +0000 Subject: [PATCH 3/5] DEBUGGING --- .github/workflows/build.yaml | 2 +- ffmpeg/encoder.c | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index e82aebb1fe..787dc956f2 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -95,7 +95,7 @@ jobs: - name: Test shell: bash - run: PATH="/github/home/compiled/bin:$PATH" go test -coverprofile cover.out ./... + run: PATH="/github/home/compiled/bin:$PATH" go test -v -coverprofile cover.out ./... - name: Upload coverage reports uses: codecov/codecov-action@v4 diff --git a/ffmpeg/encoder.c b/ffmpeg/encoder.c index 0620ff02e2..8e4b37d9ce 100755 --- a/ffmpeg/encoder.c +++ b/ffmpeg/encoder.c @@ -216,6 +216,7 @@ int open_output(struct output_ctx *octx, struct input_ctx *ictx) AVCodecContext *vc = NULL; // open muxer + fprintf(stderr, "JOSH muxer name %s fname %s\n", octx->muxer->name, octx->fname); fmt = av_guess_format(octx->muxer->name, octx->fname, NULL); if (!fmt) LPMS_ERR(open_output_err, "Unable to guess output format"); ret = avformat_alloc_output_context2(&oc, fmt, NULL, octx->fname); From 22c6d7a9364a14bab2a752116963eeb355295fa5 Mon Sep 17 00:00:00 2001 From: Josh Allmann Date: Fri, 6 Sep 2024 21:25:28 +0000 Subject: [PATCH 4/5] bump golang version for hopefully better backtraces https://github.com/golang/go/issues/63277 --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 787dc956f2..59387283fc 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -42,7 +42,7 @@ jobs: id: go uses: actions/setup-go@v5 with: - go-version: 1.20.4 + go-version: 1.23.1 cache: true cache-dependency-path: go.sum From 99bcd989fc3129f6eb067ee4756cb97923048ec7 Mon Sep 17 00:00:00 2001 From: Josh Allmann Date: Fri, 20 Sep 2024 22:41:27 +0000 Subject: [PATCH 5/5] adjust path --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 59387283fc..b4be7fc9c3 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -57,7 +57,7 @@ jobs: run: | echo "PKG_CONFIG_PATH=/github/home/compiled/lib/pkgconfig" >> $GITHUB_ENV echo "LD_LIBRARY_PATH=/github/home/compiled/lib:/usr/local/lib:/usr/local/cuda-11.2/lib64:/usr/lib/x86_64-linux-gnu" >> $GITHUB_ENV - echo "PATH=$PATH:/github/home/compiled/bin:/github/home/ffmpeg:/usr/local/go/bin" >> $GITHUB_ENV + echo "PATH=/github/home/compiled/bin:/github/home/ffmpeg:/usr/local/go/bin:$PATH" >> $GITHUB_ENV - name: Install dependencies run: |