Files
utun2/tests/test_video.c
T

207 lines
11 KiB
C

/* Реальные FFmpeg-контейнеры и независимое декодирование результата, без внешнего ffmpeg CLI. */
#ifdef HAVE_CONFIG_H
#include "../config.h"
#endif
#include "../lib/platform_compat.h"
#include "../lib/debug_config.h"
#include "../lib/u_async.h"
#include "../lib/mem.h"
#include "../src/video/video.h"
#include "../src/media_async/media_async.h"
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#ifdef HAVE_FFMPEG
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/opt.h>
#endif
static unsigned completed;
static int result_error;
static struct video_info result_info;
static void done(void* arg, int error, const struct video_info* info) {
(void)arg;
completed++; result_error = error;
if (info) result_info = *info;
DEBUG_INFO(DEBUG_CATEGORY_VIDEO, "test_video completion count=%u error=%d", completed, error);
}
#ifdef HAVE_FFMPEG
static char directory[256], source[512], destination[512];
/* Выпустить все доступные пакеты генератора; это отдельный путь, не использующий video.c. */
static void fixture_packets(AVFormatContext* mux, AVCodecContext* enc, AVStream* stream) {
AVPacket* packet = av_packet_alloc(); assert(packet);
int error;
while ((error = avcodec_receive_packet(enc, packet)) == 0) {
av_packet_rescale_ts(packet, enc->time_base, stream->time_base);
packet->stream_index = stream->index;
assert(av_interleaved_write_frame(mux, packet) >= 0);
}
assert(error == AVERROR(EAGAIN) || error == AVERROR_EOF);
av_packet_free(&packet);
}
/* Шесть кадров 64x64, B-кадры; опционально PCM/MP3/AAC с независимыми аудио-пакетами. */
static void fixture(const char* path, const char* format, enum AVCodecID video, enum AVCodecID audio) {
AVFormatContext* mux = NULL;
assert(avformat_alloc_output_context2(&mux, NULL, format, path) >= 0);
const AVCodec* codec = avcodec_find_encoder(video); assert(codec);
AVCodecContext* enc = avcodec_alloc_context3(codec); assert(enc);
enc->width = enc->height = 64; enc->pix_fmt = AV_PIX_FMT_YUV420P;
enc->time_base = (AVRational){1, 10}; enc->framerate = (AVRational){10, 1};
enc->bit_rate = 100000; enc->gop_size = 12; enc->max_b_frames = 2; enc->thread_count = 1;
if (mux->oformat->flags & AVFMT_GLOBALHEADER) enc->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
if (video == AV_CODEC_ID_H264) av_opt_set(enc->priv_data, "preset", "ultrafast", 0);
assert(avcodec_open2(enc, codec, NULL) >= 0);
AVStream* stream = avformat_new_stream(mux, NULL); assert(stream);
stream->time_base = enc->time_base;
assert(avcodec_parameters_from_context(stream->codecpar, enc) >= 0);
AVCodecContext* aenc = NULL; AVStream* astream = NULL;
if (audio != AV_CODEC_ID_NONE) {
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;
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);
astream = avformat_new_stream(mux, NULL); assert(astream);
astream->time_base = aenc->time_base;
assert(avcodec_parameters_from_context(astream->codecpar, aenc) >= 0);
}
assert(avio_open(&mux->pb, path, AVIO_FLAG_WRITE) >= 0);
assert(avformat_write_header(mux, NULL) >= 0);
AVFrame* frame = av_frame_alloc(); assert(frame);
frame->format = enc->pix_fmt; frame->width = enc->width; frame->height = enc->height;
assert(av_frame_get_buffer(frame, 0) >= 0);
for (int serial = 0; serial < 6; serial++) {
assert(av_frame_make_writable(frame) >= 0);
for (int y = 0; y < 64; y++) memset(frame->data[0] + y * frame->linesize[0], 40 + serial * 20, 64);
for (int plane = 1; plane < 3; plane++)
for (int y = 0; y < 32; y++) memset(frame->data[plane] + y * frame->linesize[plane], 128, 32);
frame->pts = serial;
assert(avcodec_send_frame(enc, frame) >= 0); fixture_packets(mux, enc, stream);
}
av_frame_free(&frame);
if (aenc) {
int block = aenc->frame_size ? aenc->frame_size : 1152;
for (int sample = 0; sample < 19200; sample += block) {
frame = av_frame_alloc(); assert(frame);
frame->format = aenc->sample_fmt; frame->sample_rate = aenc->sample_rate;
assert(av_channel_layout_copy(&frame->ch_layout, &aenc->ch_layout) >= 0);
frame->nb_samples = block; frame->pts = sample;
assert(av_frame_get_buffer(frame, 0) >= 0);
assert(av_samples_set_silence(frame->extended_data, 0, block, 1, aenc->sample_fmt) >= 0);
assert(avcodec_send_frame(aenc, frame) >= 0); fixture_packets(mux, aenc, astream);
av_frame_free(&frame);
}
assert(avcodec_send_frame(aenc, NULL) >= 0); fixture_packets(mux, aenc, astream);
}
assert(avcodec_send_frame(enc, NULL) >= 0); fixture_packets(mux, enc, stream);
assert(av_write_trailer(mux) >= 0); assert(avio_closep(&mux->pb) >= 0);
avcodec_free_context(&enc); avcodec_free_context(&aenc); avformat_free_context(mux);
}
static void write_sentinel(const char* path) {
FILE* file = fopen(path, "wb"); assert(file);
assert(fwrite("original", 1, 8, file) == 8); assert(!fclose(file));
}
static void check_sentinel(const char* path) {
FILE* file = fopen(path, "rb"); assert(file);
char bytes[9] = {0}; assert(fread(bytes, 1, sizeof(bytes), file) == 8);
assert(!memcmp(bytes, "original", 8)); assert(!fclose(file));
}
static void prepare(struct UASYNC* ua, struct media_async* owner, const char* src, const char* dst) {
completed = 0; result_error = 0; memset(&result_info, 0, sizeof(result_info));
assert(video_transcode_start(owner, ua, src, dst, done, NULL) == 0);
for (unsigned attempt = 0; attempt < 2000 && !completed; attempt++) uasync_poll(ua, 5);
assert(completed == 1 && !video_transcode_is_active() && video_transcode_get_progress() == -1);
}
static void same_file(struct UASYNC* ua, struct media_async* owner) {
uint8_t before[32], after[32]; assert(!ma_sha256_file(source, before));
completed = 0;
int started = video_transcode_start(owner, ua, source, source, done, NULL);
if (!started) {
for (unsigned attempt = 0; attempt < 2000 && !completed; attempt++) uasync_poll(ua, 5);
assert(completed == 1 && result_error);
} else assert(started == -1 && !completed);
assert(!ma_sha256_file(source, after) && !memcmp(before, after, 32));
DEBUG_INFO(DEBUG_CATEGORY_VIDEO, "PASS video same-file preservation");
}
/* Проверить число декодированных кадров и монотонные PTS независимо от подготовки. */
static void decode_result(const char* path, unsigned expected_frames, int expect_audio) {
AVFormatContext* mux = NULL; assert(avformat_open_input(&mux, path, NULL, NULL) >= 0);
assert(avformat_find_stream_info(mux, NULL) >= 0);
int index = av_find_best_stream(mux, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); assert(index >= 0);
AVStream* stream = mux->streams[index];
assert(stream->codecpar->codec_id == AV_CODEC_ID_H264 || stream->codecpar->codec_id == AV_CODEC_ID_MJPEG);
if (expect_audio) {
int audio = av_find_best_stream(mux, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0); assert(audio >= 0);
assert(mux->streams[audio]->codecpar->codec_id == AV_CODEC_ID_AAC);
}
AVCodecContext* dec = avcodec_alloc_context3(avcodec_find_decoder(stream->codecpar->codec_id)); assert(dec);
assert(avcodec_parameters_to_context(dec, stream->codecpar) >= 0);
assert(avcodec_open2(dec, dec->codec, NULL) >= 0);
AVFrame* frame = av_frame_alloc(); AVPacket* packet = av_packet_alloc(); assert(frame && packet);
unsigned frames = 0; int eof = 0; int64_t last = AV_NOPTS_VALUE;
while (!eof) {
int error = av_read_frame(mux, packet);
if (error == AVERROR_EOF) eof = 1;
else { assert(error >= 0); if (packet->stream_index != index) { av_packet_unref(packet); continue; } }
assert(avcodec_send_packet(dec, eof ? NULL : packet) >= 0);
av_packet_unref(packet);
while ((error = avcodec_receive_frame(dec, frame)) >= 0) {
assert(frame->width > 0 && frame->height > 0);
if (last != AV_NOPTS_VALUE && frame->best_effort_timestamp != AV_NOPTS_VALUE) assert(frame->best_effort_timestamp > last);
last = frame->best_effort_timestamp; frames++;
}
assert(error == AVERROR(EAGAIN) || error == AVERROR_EOF);
}
assert(frames == expected_frames);
av_frame_free(&frame); av_packet_free(&packet); avcodec_free_context(&dec); avformat_close_input(&mux);
}
#endif
int main(void) {
debug_config_init(); debug_set_level(DEBUG_LEVEL_INFO);
#ifdef HAVE_FFMPEG
snprintf(directory, sizeof(directory), "/tmp/utun-video-test-%llu", (unsigned long long)get_time_us());
assert(!utun_mkdir(directory, 0700));
snprintf(source, sizeof(source), "%s/source.mp4", directory);
snprintf(destination, sizeof(destination), "%s/result.mp4", directory);
struct UASYNC* ua = uasync_create(); struct media_async* owner = media_async_create(); assert(ua && owner);
fixture(source, "mp4", AV_CODEC_ID_H264, AV_CODEC_ID_NONE);
same_file(ua, owner);
assert(!video_extract_thumbnail(source, destination, 32)); decode_result(destination, 1, 0);
write_sentinel(destination);
assert(video_extract_thumbnail("/nonexistent/video", destination, 32) == -1); check_sentinel(destination);
#ifndef _WIN32
assert(video_extract_thumbnail(source, "/dev/full", 32) == -1);
#endif
prepare(ua, owner, source, destination); assert(!result_error); decode_result(destination, 6, 0);
fixture(source, "matroska", AV_CODEC_ID_MPEG4, AV_CODEC_ID_PCM_S16LE);
prepare(ua, owner, source, destination); assert(!result_error); decode_result(destination, 6, 1);
media_async_destroy(owner); uasync_destroy(ua, 0);
assert(!remove(source) && !remove(destination)); assert(!rmdir(directory));
#else
struct video_info info; memset(&info, 0x5a, sizeof(info));
assert(!video_probe("unused")); assert(video_get_info("unused", &info) == -1);
struct video_info zero = {0}; assert(!memcmp(&info, &zero, sizeof(info)));
assert(video_needs_transcode(NULL) == 1);
assert(video_transcode_start(NULL, NULL, "src", "dst", done, NULL) == -1 && !completed);
assert(video_extract_thumbnail("src", "dst", 320) == -1);
assert(!video_transcode_is_active() && video_transcode_get_progress() == -1);
#endif
DEBUG_INFO(DEBUG_CATEGORY_VIDEO, "PASS video regression suite");
return 0;
}