From c3330413a4a0194547a75b4571db90790c321a19 Mon Sep 17 00:00:00 2001 From: Josh Allmann Date: Wed, 31 Jul 2024 07:30:47 +0000 Subject: [PATCH] ffmpeg: Estimate duration for some audio formats in GetCodecInfoBytes --- data/audio.mp3 | Bin 0 -> 11853 bytes data/audio.ogg | Bin 0 -> 17783 bytes ffmpeg/extras.c | 4 +++ ffmpeg/extras.h | 2 ++ ffmpeg/ffmpeg.go | 22 +++++++++++++++ ffmpeg/ffmpeg_test.go | 62 ++++++++++++++++++++++++++++++++++-------- 6 files changed, 79 insertions(+), 11 deletions(-) create mode 100644 data/audio.mp3 create mode 100644 data/audio.ogg diff --git a/data/audio.mp3 b/data/audio.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..8fa9262745f11766eb9a46132f47063fac3b7009 GIT binary patch literal 11853 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 WD9<4+UQ|A>gsjB>OO5|`CI18J!gbaF literal 0 HcmV?d00001 diff --git a/data/audio.ogg b/data/audio.ogg new file mode 100644 index 0000000000000000000000000000000000000000..83e99628a45af1cf61393136334e7fa2ea452fae GIT binary patch literal 17783 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 OG`GN_jigrb_R>&GsBwe< literal 0 HcmV?d00001 diff --git a/ffmpeg/extras.c b/ffmpeg/extras.c index 82a7609e1e..3d93734058 100644 --- a/ffmpeg/extras.c +++ b/ffmpeg/extras.c @@ -168,6 +168,9 @@ int lpms_get_codec_info(char *fname, pcodec_info out) out->dur = ic->duration / AV_TIME_BASE; } // Return + if (ic->iformat && ic->iformat->name) { + strncpy(out->format_name, ic->iformat->name, MIN(strlen(out->format_name), strlen(ic->iformat->name)) + 1); + } if (video_present && vc->name) { strncpy(out->video_codec, vc->name, MIN(strlen(out->video_codec), strlen(vc->name))+1); // If video track is present extract pixel format info @@ -186,6 +189,7 @@ int lpms_get_codec_info(char *fname, pcodec_info out) } if (audio_present && ac->name) { strncpy(out->audio_codec, ac->name, MIN(strlen(out->audio_codec), strlen(ac->name))+1); + out->audio_bit_rate = ic->streams[astream]->codecpar->bit_rate; } else { // Indicate failure to extract audio codec from given container out->audio_codec[0] = 0; diff --git a/ffmpeg/extras.h b/ffmpeg/extras.h index b06b0903ac..3b7467fab7 100644 --- a/ffmpeg/extras.h +++ b/ffmpeg/extras.h @@ -2,8 +2,10 @@ #define _LPMS_EXTRAS_H_ typedef struct s_codec_info { + char * format_name; char * video_codec; char * audio_codec; + int audio_bit_rate; int pixel_format; int width; int height; diff --git a/ffmpeg/ffmpeg.go b/ffmpeg/ffmpeg.go index 94d9ff33d6..96eb9fa4e9 100755 --- a/ffmpeg/ffmpeg.go +++ b/ffmpeg/ffmpeg.go @@ -242,11 +242,13 @@ const ( ) type MediaFormatInfo struct { + Format string Acodec, Vcodec string PixFormat PixelFormat Width, Height int FPS float32 DurSecs int64 + AudioBitrate int } func (f *MediaFormatInfo) ScaledHeight(width int) int { @@ -261,15 +263,21 @@ func GetCodecInfo(fname string) (CodecStatus, MediaFormatInfo, error) { format := MediaFormatInfo{} cfname := C.CString(fname) defer C.free(unsafe.Pointer(cfname)) + fmtname := C.CString(strings.Repeat("0", 255)) acodec_c := C.CString(strings.Repeat("0", 255)) vcodec_c := C.CString(strings.Repeat("0", 255)) + defer C.free(unsafe.Pointer(fmtname)) defer C.free(unsafe.Pointer(acodec_c)) defer C.free(unsafe.Pointer(vcodec_c)) var params_c C.codec_info + params_c.format_name = fmtname params_c.video_codec = vcodec_c params_c.audio_codec = acodec_c params_c.pixel_format = C.AV_PIX_FMT_NONE status := CodecStatus(C.lpms_get_codec_info(cfname, ¶ms_c)) + if C.strlen(fmtname) < 255 { + format.Format = C.GoString(fmtname) + } if C.strlen(acodec_c) < 255 { format.Acodec = C.GoString(acodec_c) } @@ -281,6 +289,7 @@ func GetCodecInfo(fname string) (CodecStatus, MediaFormatInfo, error) { format.Height = int(params_c.height) format.FPS = float32(params_c.fps) format.DurSecs = int64(params_c.dur) + format.AudioBitrate = int(params_c.audio_bit_rate) return status, format, nil } @@ -300,6 +309,19 @@ func GetCodecInfoBytes(data []byte) (CodecStatus, MediaFormatInfo, error) { } fname := fmt.Sprintf("pipe:%d", or.Fd()) status, format, err = GetCodecInfo(fname) + + // estimate duration from bitrate and filesize for audio + // some formats do not have built-in track duration metadata, + // and pipes do not have a filesize on their own which breaks ffmpeg's own + // duration estimates. So do the estimation calculation ourselves + // NB : mpegts has the same problem but may contain video so let's not handle that + // some other formats, eg ogg, show zero bitrate + // + // ffmpeg estimation of duration from bitrate: + // https://github.com/FFmpeg/FFmpeg/blob/8280ec7a3213c9b7bad88aac3695be2dedd2c00b/libavformat/demux.c#L1798 + if format.DurSecs == 0 && format.AudioBitrate > 0 && (format.Format == "mp3" || format.Format == "wav" || format.Format == "aac") { + format.DurSecs = int64(len(data) * 8 / format.AudioBitrate) + } return status, format, err } diff --git a/ffmpeg/ffmpeg_test.go b/ffmpeg/ffmpeg_test.go index fa2f822523..3256d4c479 100644 --- a/ffmpeg/ffmpeg_test.go +++ b/ffmpeg/ffmpeg_test.go @@ -1987,28 +1987,68 @@ func TestDurationFPS_GetCodecInfo(t *testing.T) { ffprobe -loglevel warning -show_format test.m4a | grep duration=2.042993 ffmpeg -loglevel warning -i test-short.mp4 -vn -c:a flac test.flac ffprobe -loglevel warning -show_format test.flac | grep duration=2.043356 + + ffmpeg -loglevel warning -i test.mp4 -vn -c:a copy stereo-audio.aac + ffprobe -show_entries stream=channels,channel_layout -of csv stereo-audio.aac | grep stream,2,stereo + ffprobe -show_format stereo-audio.aac | grep duration=52.440083 + + ffmpeg -i test.mp4 -vn stereo-audio.wav + ffprobe -show_format stereo-audio.wav | grep duration=60.139683 + + cp $1/../data/audio.mp3 test.mp3 + ffprobe -show_format test.mp3 | grep duration=1.968000 + + cp $1/../data/audio.ogg test.ogg + ffprobe -show_format test.ogg | grep duration=1.974500 ` run(cmd) files := []struct { Filename string + Format string Duration int64 FPS float32 + + // skip check if bytes version is known to fail duration + BytesSkipDuration bool }{ - {Filename: "test-short.mp4", Duration: 2, FPS: 24}, - {Filename: "test.ts", Duration: 2, FPS: 30.0}, - {Filename: "test.flac", Duration: 2, FPS: 0.0}, - {Filename: "test.webm", Duration: 2, FPS: 24}, - {Filename: "test.m4a", Duration: 2, FPS: 0.0}, + {Filename: "test-short.mp4", Format: "mov,mp4,m4a,3gp,3g2,mj2", Duration: 2, FPS: 24}, + {Filename: "test.ts", Format: "mpegts", Duration: 2, FPS: 30.0, BytesSkipDuration: true}, + {Filename: "test.flac", Format: "flac", Duration: 2}, + {Filename: "test.webm", Format: "matroska,webm", Duration: 2, FPS: 24}, + {Filename: "test.m4a", Format: "mov,mp4,m4a,3gp,3g2,mj2", Duration: 2}, + {Filename: "stereo-audio.aac", Format: "aac", Duration: 52}, + {Filename: "stereo-audio.wav", Format: "wav", Duration: 60}, + {Filename: "test.mp3", Format: "mp3", Duration: 1}, + {Filename: "test.ogg", Format: "ogg", Duration: 1, BytesSkipDuration: true}, } for _, file := range files { t.Run(file.Filename, func(t *testing.T) { - assert := assert.New(t) - status, format, err := GetCodecInfo(path.Join(dir, file.Filename)) - assert.Nil(err, "getcodecinfo error") - assert.Equal(CodecStatusOk, status, "status not ok") - assert.Equal(file.Duration, format.DurSecs, "duration mismatch") - assert.Equal(file.FPS, format.FPS, "fps mismatch") + fname := path.Join(dir, file.Filename) + // use 'bytes' prefix to prevent test runner regex matching + for _, tt := range []string{"GetCodecInfo", "BytesGetCodecInfo"} { + t.Run(tt, func(t *testing.T) { + assert := assert.New(t) + f := func() (CodecStatus, MediaFormatInfo, error) { + if tt == "GetCodecInfo" { + return GetCodecInfo(fname) + } + d, err := os.ReadFile(fname) + assert.Nil(err, "reading file") + return GetCodecInfoBytes(d) + } + status, format, err := f() + assert.Nil(err, "getcodecinfo error") + assert.Equal(CodecStatusOk, status, "status not ok") + assert.Equal(file.Format, format.Format, "format mismatch") + if tt == "BytesGetCodecInfo" && file.BytesSkipDuration { + assert.Equal(int64(0), format.DurSecs, "special duration mismatch") + } else { + assert.Equal(file.Duration, format.DurSecs, "duration mismatch") + } + assert.Equal(file.FPS, format.FPS, "fps mismatch") + }) + } }) } }