From 7b49a69f4348d079c1837401d4084b49fd643748 Mon Sep 17 00:00:00 2001 From: Sankalpa Sarkar Date: Sun, 5 Apr 2026 12:24:27 +0530 Subject: [PATCH] fate: add unit tests for libavutil/timecode functions --- libavutil/Makefile | 1 + libavutil/tests/timecode.c | 278 +++++++++++++++++++++++++++++++++++++ tests/fate/libavutil.mak | 4 + tests/ref/fate/timecode | 51 +++++++ 4 files changed, 334 insertions(+) create mode 100644 libavutil/tests/timecode.c create mode 100644 tests/ref/fate/timecode diff --git a/libavutil/Makefile b/libavutil/Makefile index c46b4f19a3..38e08a3d5d 100644 --- a/libavutil/Makefile +++ b/libavutil/Makefile @@ -307,6 +307,7 @@ TESTPROGS = adler32 \ softfloat \ spherical \ stereo3d \ + timecode \ tree \ twofish \ utf8 \ diff --git a/libavutil/tests/timecode.c b/libavutil/tests/timecode.c new file mode 100644 index 0000000000..56b488798d --- /dev/null +++ b/libavutil/tests/timecode.c @@ -0,0 +1,278 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * Unit tests for libavutil/timecode.c: + * av_timecode_init, av_timecode_init_from_components, + * av_timecode_init_from_string, av_timecode_make_string, + * av_timecode_get_smpte_from_framenum, av_timecode_get_smpte, + * av_timecode_make_smpte_tc_string, av_timecode_make_smpte_tc_string2, + * av_timecode_make_mpeg_tc_string, av_timecode_adjust_ntsc_framenum2, + * av_timecode_check_frame_rate + */ + +#include +#include + +#include "libavutil/macros.h" +#include "libavutil/rational.h" +#include "libavutil/timecode.h" + +static void test_check_frame_rate(void) +{ + static const AVRational rates[] = { + {24, 1}, {25, 1}, {30, 1}, {48, 1}, {50, 1}, {60, 1}, + {100, 1}, {120, 1}, {150, 1}, + {30000, 1001}, {15, 1}, {12, 1}, + {0, 0}, {30, 0}, + }; + for (int i = 0; i < FF_ARRAY_ELEMS(rates); i++) + printf("check_frame_rate %d/%d: %d\n", + rates[i].num, rates[i].den, + av_timecode_check_frame_rate(rates[i])); +} + +static void test_init(void) +{ + AVTimecode tc; + static const struct { + AVRational rate; + int flags; + int start; + } cases[] = { + { {25, 1}, 0, 0 }, + { {30, 1}, AV_TIMECODE_FLAG_DROPFRAME, 0 }, + { {24, 1}, 0, 100 }, + { {0, 1}, 0, 0 }, + { {25, 1}, AV_TIMECODE_FLAG_DROPFRAME, 0 }, + { {30000, 1001}, AV_TIMECODE_FLAG_DROPFRAME, 0 }, + { {25, 1}, AV_TIMECODE_FLAG_ALLOWNEGATIVE, -100 }, + }; + + for (int i = 0; i < FF_ARRAY_ELEMS(cases); i++) { + int ret = av_timecode_init(&tc, cases[i].rate, cases[i].flags, cases[i].start, NULL); + printf("init %d/%d flags:%d start:%d: ", + cases[i].rate.num, cases[i].rate.den, cases[i].flags, cases[i].start); + if (ret < 0) { + printf("error\n"); + } else { + printf("ok fps=%d start=%d rate=%d/%d\n", + tc.fps, tc.start, tc.rate.num, tc.rate.den); + } + } +} + +static void test_init_from_components(void) +{ + AVTimecode tc; + int ret; + + ret = av_timecode_init_from_components(&tc, (AVRational){25, 1}, + 0, 0, 0, 0, 0, NULL); + printf("from_components 25/1 00:00:00:00: %d start=%d\n", ret, tc.start); + + ret = av_timecode_init_from_components(&tc, (AVRational){25, 1}, + 0, 1, 0, 0, 0, NULL); + printf("from_components 25/1 01:00:00:00: %d start=%d\n", ret, tc.start); + + ret = av_timecode_init_from_components(&tc, (AVRational){30, 1}, + 0, 1, 2, 3, 4, NULL); + printf("from_components 30/1 01:02:03:04: %d start=%d\n", ret, tc.start); + + ret = av_timecode_init_from_components(&tc, (AVRational){30000, 1001}, + AV_TIMECODE_FLAG_DROPFRAME, + 1, 0, 0, 0, NULL); + printf("from_components 30000/1001 drop 01:00:00;00: %d start=%d\n", + ret, tc.start); +} + +static void test_init_from_string(void) +{ + AVTimecode tc; + int ret; + + ret = av_timecode_init_from_string(&tc, (AVRational){30, 1}, + "00:01:02:03", NULL); + printf("from_string 30/1 00:01:02:03: %d drop=%d start=%d\n", + ret, !!(tc.flags & AV_TIMECODE_FLAG_DROPFRAME), tc.start); + + ret = av_timecode_init_from_string(&tc, (AVRational){30000, 1001}, + "00:01:00;02", NULL); + printf("from_string 30000/1001 00:01:00;02: %d drop=%d\n", + ret, !!(tc.flags & AV_TIMECODE_FLAG_DROPFRAME)); + + ret = av_timecode_init_from_string(&tc, (AVRational){30000, 1001}, + "01:00:00.00", NULL); + printf("from_string 30000/1001 01:00:00.00: %d drop=%d\n", + ret, !!(tc.flags & AV_TIMECODE_FLAG_DROPFRAME)); + + ret = av_timecode_init_from_string(&tc, (AVRational){25, 1}, + "notvalid", NULL); + printf("from_string 25/1 notvalid: %s\n", ret < 0 ? "error" : "ok"); +} + +static void test_make_string(void) +{ + AVTimecode tc; + char buf[AV_TIMECODE_STR_SIZE]; + + av_timecode_init(&tc, (AVRational){25, 1}, 0, 0, NULL); + printf("make_string 25/1 35: %s\n", av_timecode_make_string(&tc, buf, 35)); + printf("make_string 25/1 0: %s\n", av_timecode_make_string(&tc, buf, 0)); + + av_timecode_init(&tc, (AVRational){30, 1}, 0, 0, NULL); + printf("make_string 30/1 30: %s\n", av_timecode_make_string(&tc, buf, 30)); + + av_timecode_init(&tc, (AVRational){25, 1}, + AV_TIMECODE_FLAG_24HOURSMAX, 0, NULL); + printf("make_string 25/1 24hwrap %d: %s\n", + 25 * 3600 * 25, av_timecode_make_string(&tc, buf, 25 * 3600 * 25)); + + av_timecode_init(&tc, (AVRational){30000, 1001}, + AV_TIMECODE_FLAG_DROPFRAME, 0, NULL); + printf("make_string 30000/1001 drop 0: %s\n", + av_timecode_make_string(&tc, buf, 0)); + + av_timecode_init(&tc, (AVRational){25, 1}, + AV_TIMECODE_FLAG_ALLOWNEGATIVE, -100, NULL); + printf("make_string 25/1 negative start -100 frame 0: %s\n", + av_timecode_make_string(&tc, buf, 0)); +} + +static void test_make_smpte_tc_string(void) +{ + char buf[AV_TIMECODE_STR_SIZE]; + AVTimecode tc; + uint32_t smpte; + + smpte = av_timecode_get_smpte((AVRational){30, 1}, 0, 1, 2, 3, 4); + printf("smpte_tc 30/1 01:02:03:04: %s\n", + av_timecode_make_smpte_tc_string(buf, smpte, 1)); + + av_timecode_init(&tc, (AVRational){25, 1}, 0, 0, NULL); + smpte = av_timecode_get_smpte_from_framenum(&tc, + 25 * 3600 + 25 * 60 + 25 + 5); + printf("smpte_from_framenum 25/1 91530: %s\n", + av_timecode_make_smpte_tc_string(buf, smpte, 1)); +} + +static void test_make_smpte_tc_string2(void) +{ + char buf[AV_TIMECODE_STR_SIZE]; + uint32_t smpte; + + smpte = av_timecode_get_smpte((AVRational){50, 1}, 0, 0, 0, 0, 0); + printf("smpte_tc2 50/1 00:00:00:00: %s\n", + av_timecode_make_smpte_tc_string2(buf, (AVRational){50, 1}, + smpte, 1, 0)); + + smpte = av_timecode_get_smpte((AVRational){60, 1}, 0, 1, 0, 0, 0); + printf("smpte_tc2 60/1 01:00:00:00: %s\n", + av_timecode_make_smpte_tc_string2(buf, (AVRational){60, 1}, + smpte, 1, 0)); +} + +static void test_make_mpeg_tc_string(void) +{ + char buf[AV_TIMECODE_STR_SIZE]; + + uint32_t tc25 = (1u << 19) | (2u << 13) | (3u << 6) | 4u; + printf("mpeg_tc 01:02:03:04: %s\n", + av_timecode_make_mpeg_tc_string(buf, tc25)); + + uint32_t tc25_drop = tc25 | (1u << 24); + printf("mpeg_tc drop 01:02:03:04: %s\n", + av_timecode_make_mpeg_tc_string(buf, tc25_drop)); +} + +static void test_adjust_ntsc(void) +{ + static const struct { + int framenum; + int fps; + } cases[] = { + { 0, 30 }, + { 1800, 30 }, + { 1000, 25 }, + { 1000, 0 }, + { 3600, 60 }, + }; + + for (int i = 0; i < FF_ARRAY_ELEMS(cases); i++) { + printf("adjust_ntsc %d %d: %d\n", + cases[i].framenum, cases[i].fps, + av_timecode_adjust_ntsc_framenum2(cases[i].framenum, + cases[i].fps)); + } +} + +static void test_get_smpte_roundtrip(void) +{ + char buf[AV_TIMECODE_STR_SIZE]; + char buf2[AV_TIMECODE_STR_SIZE]; + uint32_t smpte; + + smpte = av_timecode_get_smpte((AVRational){30, 1}, 0, 12, 34, 56, 7); + printf("smpte_roundtrip 30/1 12:34:56:07: %s / %s\n", + av_timecode_make_smpte_tc_string(buf, smpte, 1), + av_timecode_make_smpte_tc_string2(buf2, (AVRational){30, 1}, + smpte, 1, 0)); + + smpte = av_timecode_get_smpte((AVRational){30, 1}, 0, 0, 0, 0, 0); + printf("smpte_roundtrip 30/1 00:00:00:00: %s / %s\n", + av_timecode_make_smpte_tc_string(buf, smpte, 1), + av_timecode_make_smpte_tc_string2(buf2, (AVRational){30, 1}, + smpte, 1, 0)); + + smpte = av_timecode_get_smpte((AVRational){30000, 1001}, 1, 0, 1, 0, 2); + printf("smpte_roundtrip 30000/1001 drop bit30=%d: %s / %s\n", + !!(smpte & (1u << 30)), + av_timecode_make_smpte_tc_string(buf, smpte, 0), + av_timecode_make_smpte_tc_string2(buf2, (AVRational){30000, 1001}, + smpte, 0, 0)); + + /* >30 fps SMPTE field bit handling test */ + smpte = av_timecode_get_smpte((AVRational){50, 1}, 0, 0, 0, 0, 49); + printf("smpte_roundtrip 50/1 field bit7=%d: %s / %s\n", + !!(smpte & (1u << 7)), + av_timecode_make_smpte_tc_string(buf, smpte, 0), + av_timecode_make_smpte_tc_string2(buf2, (AVRational){50, 1}, + smpte, 0, 0)); + + smpte = av_timecode_get_smpte((AVRational){60000, 1001}, 0, 0, 0, 0, 59); + printf("smpte_roundtrip 60000/1001 field bit23=%d: %s / %s\n", + !!(smpte & (1u << 23)), + av_timecode_make_smpte_tc_string(buf, smpte, 0), + av_timecode_make_smpte_tc_string2(buf2, (AVRational){60000, 1001}, + smpte, 0, 0)); +} + +int main(void) +{ + test_check_frame_rate(); + test_init(); + test_init_from_components(); + test_init_from_string(); + test_make_string(); + test_make_smpte_tc_string(); + test_make_smpte_tc_string2(); + test_make_mpeg_tc_string(); + test_adjust_ntsc(); + test_get_smpte_roundtrip(); + return 0; +} diff --git a/tests/fate/libavutil.mak b/tests/fate/libavutil.mak index 857bac7641..12143bc248 100644 --- a/tests/fate/libavutil.mak +++ b/tests/fate/libavutil.mak @@ -216,6 +216,10 @@ fate-file: libavutil/tests/file$(EXESUF) fate-file: CMD = run libavutil/tests/file$(EXESUF) $(SRC_PATH)/libavutil/tests/file.c fate-file: CMP = null +FATE_LIBAVUTIL += fate-timecode +fate-timecode: libavutil/tests/timecode$(EXESUF) +fate-timecode: CMD = run libavutil/tests/timecode$(EXESUF) + FATE_LIBAVUTIL += $(FATE_LIBAVUTIL-yes) FATE-$(CONFIG_AVUTIL) += $(FATE_LIBAVUTIL) fate-libavutil: $(FATE_LIBAVUTIL) diff --git a/tests/ref/fate/timecode b/tests/ref/fate/timecode new file mode 100644 index 0000000000..6992eaab7a --- /dev/null +++ b/tests/ref/fate/timecode @@ -0,0 +1,51 @@ +check_frame_rate 24/1: 0 +check_frame_rate 25/1: 0 +check_frame_rate 30/1: 0 +check_frame_rate 48/1: 0 +check_frame_rate 50/1: 0 +check_frame_rate 60/1: 0 +check_frame_rate 100/1: 0 +check_frame_rate 120/1: 0 +check_frame_rate 150/1: 0 +check_frame_rate 30000/1001: 0 +check_frame_rate 15/1: -1 +check_frame_rate 12/1: -1 +check_frame_rate 0/0: -1 +check_frame_rate 30/0: -1 +init 25/1 flags:0 start:0: ok fps=25 start=0 rate=25/1 +init 30/1 flags:1 start:0: ok fps=30 start=0 rate=30/1 +init 24/1 flags:0 start:100: ok fps=24 start=100 rate=24/1 +init 0/1 flags:0 start:0: error +init 25/1 flags:1 start:0: error +init 30000/1001 flags:1 start:0: ok fps=30 start=0 rate=30000/1001 +init 25/1 flags:4 start:-100: ok fps=25 start=-100 rate=25/1 +from_components 25/1 00:00:00:00: 0 start=0 +from_components 25/1 01:00:00:00: 0 start=90000 +from_components 30/1 01:02:03:04: 0 start=111694 +from_components 30000/1001 drop 01:00:00;00: 0 start=107892 +from_string 30/1 00:01:02:03: 0 drop=0 start=1863 +from_string 30000/1001 00:01:00;02: 0 drop=1 +from_string 30000/1001 01:00:00.00: 0 drop=1 +from_string 25/1 notvalid: error +make_string 25/1 35: 00:00:01:10 +make_string 25/1 0: 00:00:00:00 +make_string 30/1 30: 00:00:01:00 +make_string 25/1 24hwrap 2250000: 01:00:00:00 +make_string 30000/1001 drop 0: 00:00:00;00 +make_string 25/1 negative start -100 frame 0: -00:00:04:00 +smpte_tc 30/1 01:02:03:04: 01:02:03:04 +smpte_from_framenum 25/1 91530: 01:01:01:05 +smpte_tc2 50/1 00:00:00:00: 00:00:00:00 +smpte_tc2 60/1 01:00:00:00: 01:00:00:00 +mpeg_tc 01:02:03:04: 01:02:03:04 +mpeg_tc drop 01:02:03:04: 01:02:03;04 +adjust_ntsc 0 30: 0 +adjust_ntsc 1800 30: 1802 +adjust_ntsc 1000 25: 1000 +adjust_ntsc 1000 0: 1000 +adjust_ntsc 3600 60: 3604 +smpte_roundtrip 30/1 12:34:56:07: 12:34:56:07 / 12:34:56:07 +smpte_roundtrip 30/1 00:00:00:00: 00:00:00:00 / 00:00:00:00 +smpte_roundtrip 30000/1001 drop bit30=1: 00:01:00;02 / 00:01:00;02 +smpte_roundtrip 50/1 field bit7=1: 00:00:00:24 / 00:00:00:49 +smpte_roundtrip 60000/1001 field bit23=1: 00:00:00:29 / 00:00:00:59