From 9f1825dbf8d9d3c9c91834ebb564983c6c5099bd Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 22 Oct 2026 00:08:22 +0200 Subject: [PATCH] Verify decoded video content, AAC signal timing and FFmpeg-free stubs --- tests/Makefile.am | 3 +++ tests/test_video.c | 38 ++++++++++++++++++++++++++++++++---- tests/test_video_no_ffmpeg.c | 27 +++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 4 deletions(-) create mode 100644 tests/test_video_no_ffmpeg.c diff --git a/tests/Makefile.am b/tests/Makefile.am index a13781a3..ea3b3001 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -10,6 +10,7 @@ EXTRA_DIST = fixtures/reality_openssl_hello.hex data/silero_speech.pcm data/sile # Быстрые проверки (до 100мс). check_PROGRAMS = \ test_video \ + test_video_no_ffmpeg \ test_video_plane \ test_route_tx_stats \ test_call_video_rate \ @@ -786,6 +787,8 @@ test_call_video_LDADD = $(LIBUTUN) $(COMMON_LIBS) test_video_SOURCES = test_video.c test_video_LDADD = $(LIBUTUN) $(COMMON_LIBS) +test_video_no_ffmpeg_SOURCES = test_video_no_ffmpeg.c +test_video_no_ffmpeg_LDADD = $(COMMON_LIBS) if HAVE_FFMPEG if OS_LINUX test_video_LDFLAGS = -Wl,--wrap=fdopen -Wl,--wrap=fwrite -Wl,--wrap=fflush -Wl,--wrap=fclose -Wl,--wrap=rename \ diff --git a/tests/test_video.c b/tests/test_video.c index 8cb4130d..b01fdbb7 100644 --- a/tests/test_video.c +++ b/tests/test_video.c @@ -134,6 +134,8 @@ static void fixture_packets(AVFormatContext* mux, AVCodecContext* enc, AVStream* int error; while ((error = avcodec_receive_packet(enc, packet)) == 0) { av_packet_rescale_ts(packet, enc->time_base, stream->time_base); + if (enc->codec_type == AVMEDIA_TYPE_VIDEO && packet->duration <= 0) + packet->duration = av_rescale_q(1, enc->time_base, stream->time_base); packet->stream_index = stream->index; assert(av_interleaved_write_frame(mux, packet) >= 0); } @@ -161,7 +163,14 @@ static void fixture(const char* path, const char* format, enum AVCodecID video, codec = avcodec_find_encoder(audio); assert(codec); aenc = avcodec_alloc_context3(codec); assert(aenc); aenc->sample_rate = 32000; aenc->time_base = (AVRational){1, 32000}; - aenc->sample_fmt = codec->sample_fmts[0]; aenc->bit_rate = 64000; aenc->thread_count = 1; +#if LIBAVCODEC_VERSION_MAJOR >= 61 + const void* formats; + assert(avcodec_get_supported_config(aenc, codec, AV_CODEC_CONFIG_SAMPLE_FORMAT, 0, &formats, NULL) >= 0 && formats); + aenc->sample_fmt = ((const enum AVSampleFormat*)formats)[0]; +#else + aenc->sample_fmt = codec->sample_fmts[0]; +#endif + aenc->bit_rate = 64000; aenc->thread_count = 1; av_channel_layout_default(&aenc->ch_layout, 1); if (mux->oformat->flags & AVFMT_GLOBALHEADER) aenc->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; assert(avcodec_open2(aenc, codec, NULL) >= 0); @@ -192,6 +201,14 @@ static void fixture(const char* path, const char* format, enum AVCodecID video, frame->nb_samples = block; frame->pts = sample + audio_offset_ms * aenc->sample_rate / 1000; assert(av_frame_get_buffer(frame, 0) >= 0); assert(av_samples_set_silence(frame->extended_data, 0, block, 1, aenc->sample_fmt) >= 0); + enum AVSampleFormat packed = av_get_packed_sample_fmt(aenc->sample_fmt); + for (int i = 0; i < block; i++) { + float value = (sample + i) % 64 < 32 ? 0.25f : -0.25f; + if (packed == AV_SAMPLE_FMT_FLT) ((float*)frame->data[0])[i] = value; + else if (packed == AV_SAMPLE_FMT_S16) ((int16_t*)frame->data[0])[i] = (int16_t)(value * 32767); + else if (packed == AV_SAMPLE_FMT_S32) ((int32_t*)frame->data[0])[i] = (int32_t)(value * 2147483647.0); + else assert(!"unsupported fixture sample format"); + } assert(avcodec_send_frame(aenc, frame) >= 0); fixture_packets(mux, aenc, astream); av_frame_free(&frame); } @@ -256,6 +273,7 @@ static void decode_result(const char* path, unsigned expected_frames, int expect av_packet_unref(packet); while ((error = avcodec_receive_frame(dec, frame)) >= 0) { assert(frame->width > 0 && frame->height > 0); + if (expected_frames == 6) assert(abs(frame->data[0][0] - (40 + (int)frames * 20)) <= 6); if (last != AV_NOPTS_VALUE && frame->best_effort_timestamp != AV_NOPTS_VALUE) assert(frame->best_effort_timestamp > last); last = frame->best_effort_timestamp; frames++; } @@ -274,7 +292,7 @@ static void decode_audio(const char* path, int minimum_samples, int offset_ms) { AVCodecContext* decoder = avcodec_alloc_context3(avcodec_find_decoder(stream->codecpar->codec_id)); assert(decoder); assert(avcodec_parameters_to_context(decoder, stream->codecpar) >= 0 && avcodec_open2(decoder, decoder->codec, NULL) >= 0); AVPacket* packet = av_packet_alloc(); AVFrame* frame = av_frame_alloc(); assert(packet && frame); - int samples = 0, eof = 0; int64_t first_us = AV_NOPTS_VALUE; + int samples = 0, eof = 0; int64_t first_us = AV_NOPTS_VALUE, signal_us = AV_NOPTS_VALUE; while (!eof) { int error = av_read_frame(format, packet); if (error == AVERROR_EOF) eof = 1; @@ -282,14 +300,26 @@ static void decode_audio(const char* path, int minimum_samples, int offset_ms) { assert(avcodec_send_packet(decoder, eof ? NULL : packet) >= 0); av_packet_unref(packet); while ((error = avcodec_receive_frame(decoder, frame)) >= 0) { if (first_us == AV_NOPTS_VALUE) first_us = av_rescale_q(frame->best_effort_timestamp, stream->time_base, AV_TIME_BASE_Q); + assert(frame->format == AV_SAMPLE_FMT_FLTP); + if (signal_us == AV_NOPTS_VALUE) { + for (int sample = 0; sample < frame->nb_samples; sample++) { + float value = ((const float*)frame->extended_data[0])[sample]; + if (value > 0.05f || value < -0.05f) { + signal_us = av_rescale_q(frame->best_effort_timestamp, stream->time_base, AV_TIME_BASE_Q) + + av_rescale(sample, 1000000, decoder->sample_rate); + break; + } + } + } samples += frame->nb_samples; } assert(error == AVERROR_EOF || error == AVERROR(EAGAIN)); } - DEBUG_INFO(DEBUG_CATEGORY_VIDEO, "test_video decoded audio samples=%d first_us=%lld expected_offset_ms=%d", - samples, (long long)first_us, offset_ms); + DEBUG_INFO(DEBUG_CATEGORY_VIDEO, "test_video decoded audio samples=%d first_us=%lld signal_us=%lld expected_offset_ms=%d", + samples, (long long)first_us, (long long)signal_us, offset_ms); assert(samples >= minimum_samples && samples <= minimum_samples + 2048); assert(first_us != AV_NOPTS_VALUE && llabs(first_us - (int64_t)offset_ms * 1000) <= 33000); + assert(signal_us != AV_NOPTS_VALUE && llabs(signal_us - (int64_t)offset_ms * 1000) <= 10000); av_packet_free(&packet); av_frame_free(&frame); avcodec_free_context(&decoder); avformat_close_input(&format); } diff --git a/tests/test_video_no_ffmpeg.c b/tests/test_video_no_ffmpeg.c new file mode 100644 index 00000000..9e29832c --- /dev/null +++ b/tests/test_video_no_ffmpeg.c @@ -0,0 +1,27 @@ +/* Компилировать сам модуль без FFmpeg независимо от конфигурации основной сборки. */ +#undef HAVE_FFMPEG +#include "../src/video/video.c" +#include + +static unsigned callbacks; +static void unexpected_done(void* arg, int error, const struct video_info* info) { + (void)arg; (void)error; (void)info; callbacks++; +} + +int main(void) { + debug_config_init(); debug_set_level(DEBUG_LEVEL_INFO); + struct video_info actual, zero = {0}; memset(&actual, 0x5a, sizeof(actual)); + video_ffmpeg_init(); + assert(video_probe("unused") == 0); + assert(video_get_info("unused", &actual) == -1 && !memcmp(&actual, &zero, sizeof(actual))); + assert(video_get_info(NULL, NULL) == -1 && video_needs_transcode(NULL) == 1); + assert(video_transcode_start(NULL, NULL, "src", "dst", unexpected_done, NULL) == -1 && !callbacks); + assert(video_extract_thumbnail("src", "dst", 320) == -1); + assert(video_transcode_get_progress() == -1 && !video_transcode_is_active()); + video_codec_probe_begin(); video_codec_probe_begin(); + assert(video_codec_log_level(DEBUG_LEVEL_ERROR) == DEBUG_LEVEL_DEBUG); + video_codec_probe_end(); assert(video_codec_log_level(DEBUG_LEVEL_WARN) == DEBUG_LEVEL_DEBUG); + video_codec_probe_end(); assert(video_codec_log_level(DEBUG_LEVEL_ERROR) == DEBUG_LEVEL_ERROR); + DEBUG_INFO(DEBUG_CATEGORY_VIDEO, "PASS video build without FFmpeg"); + return 0; +}