Files
utun2/tests/test_attachment.c
T

142 lines
8.3 KiB
C

/* Строгие метаданные и реальный Opus-файл: границы, повреждения, длительность. */
#include <stdio.h>
#include <string.h>
#include "../lib/mem.h"
#include "../lib/debug_config.h"
#include "../lib/opus_codec.h"
#include "../src/media_async/attachment.h"
#include "../src/media_async/voice_file.h"
#ifdef __linux__
#include <sys/mman.h>
#endif
#define REQUIRE(value) do { if (!(value)) { fprintf(stderr, "attachment check failed line=%d\n", __LINE__); return 1; } } while (0)
/* Проверяем настоящий контейнер и число декодируемых кадров, включая отбрасывание хвоста. */
static int check_voice_roundtrip(const int16_t* pcm, size_t count, int rate, int channels, int preset, uint32_t duration_ms) {
struct attachment_info info = { .name = "voice.opus" };
const char* path = "test_attachment_voice.opus";
REQUIRE(!voice_file_encode(pcm, count, rate, channels, preset, path, &info));
REQUIRE(info.duration_ms == duration_ms && strlen(info.waveform) == 100 && !attachment_validate(&info));
FILE* file = fopen(path, "rb"); REQUIRE(file != NULL);
uint8_t header[12]; REQUIRE(fread(header, 1, 12, file) == 12);
uint32_t magic, saved_rate; uint16_t saved_channels, frame;
memcpy(&magic, header, 4); memcpy(&saved_rate, header + 4, 4);
memcpy(&saved_channels, header + 8, 2); memcpy(&frame, header + 10, 2);
REQUIRE(magic == 0x5355504f && saved_rate == (uint32_t)rate && saved_channels == channels && frame == rate / 50);
opus_codec_decoder_t* decoder = opus_codec_decoder_create(rate, channels); REQUIRE(decoder != NULL);
uint8_t packet[4000]; int16_t decoded[1920]; unsigned frames = 0;
for (;;) {
uint16_t n; REQUIRE(fread(&n, 2, 1, file) == 1);
if (!n) break;
REQUIRE(n <= sizeof(packet) && fread(packet, 1, n, file) == n);
REQUIRE(opus_codec_decode(decoder, packet, n, decoded, frame) == frame); frames++;
}
REQUIRE(frames == duration_ms / 20 && fgetc(file) == EOF);
REQUIRE(!fclose(file) && !remove(path)); opus_codec_decoder_destroy(decoder);
return 0;
}
#ifdef __linux__
static int encoder_probe;
static unsigned encoder_calls;
opus_codec_encoder_t* __real_opus_codec_encoder_create(int rate, int channels);
opus_codec_encoder_t* __wrap_opus_codec_encoder_create(int rate, int channels) {
if (!encoder_probe) return __real_opus_codec_encoder_create(rate, channels);
encoder_calls++;
return NULL; /* Останавливаем только probe: PCM ещё не прочитан, файл/метаданные не изменены. */
}
#if SIZE_MAX > UINT32_MAX
/* Виртуальный PCM позволяет проверить суточную границу без выделения физической памяти/суточного Opus. */
static int check_voice_duration(void) {
static const int rates[] = {8000, 12000, 16000, 24000, 48000};
static const uint32_t durations[] = {0, 479, 480, 499, 500, 519, 86399980, 86400000, 86400019, 86400020, 86400980, 86401000};
const char* path = "test_attachment_duration.opus";
static const char marker[] = "existing voice output";
size_t bytes = (size_t)48000 * 2 * 86401 * sizeof(int16_t);
int16_t* pcm = mmap(NULL, bytes, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0);
REQUIRE(pcm != MAP_FAILED);
FILE* file = fopen(path, "wb"); REQUIRE(file != NULL);
REQUIRE(fwrite(marker, 1, sizeof(marker), file) == sizeof(marker) && !fclose(file));
encoder_probe = 1;
unsigned cases = 0, rejected = 0;
debug_set_category_level(DEBUG_CATEGORY_MEDIA, DEBUG_LEVEL_DEBUG);
for (size_t r = 0; r < sizeof(rates) / sizeof(rates[0]); r++) {
for (int channels = 1; channels <= 2; channels++) {
for (size_t d = 0; d < sizeof(durations) / sizeof(durations[0]); d++) {
uint32_t duration = durations[d];
size_t count = (size_t)rates[r] * channels * duration / 1000;
int allowed = duration >= 500 && duration < 86400020;
struct attachment_info info = { .kind = ATTACHMENT_FILE, .name = "unchanged.bin" };
unsigned char before[sizeof(info)]; memcpy(before, &info, sizeof(info));
encoder_calls = 0;
REQUIRE(voice_file_encode(pcm, count, rates[r], channels, 0, path, &info) == -1);
REQUIRE(encoder_calls == (unsigned)allowed && !memcmp(&info, before, sizeof(info)));
file = fopen(path, "rb"); REQUIRE(file != NULL);
char saved[sizeof(marker)]; REQUIRE(fread(saved, 1, sizeof(saved), file) == sizeof(saved));
REQUIRE(!memcmp(saved, marker, sizeof(saved)) && fgetc(file) == EOF && !fclose(file));
cases++; rejected += !allowed;
}
}
}
encoder_probe = 0;
REQUIRE(!munmap(pcm, bytes) && !remove(path));
DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "voice duration: cases=%u rejected_before_encoder=%u file/metadata preserved", cases, rejected);
debug_set_category_level(DEBUG_CATEGORY_MEDIA, DEBUG_LEVEL_ERROR);
REQUIRE(cases == 120 && rejected == 70);
return 0;
}
#endif
#endif
int main(void) {
debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR);
struct attachment_info in = { .kind = ATTACHMENT_VOICE, .name = "голос.opus", .duration_ms = 1200 }, out;
memset(in.waveform, 'A', 100);
uint8_t wire[ATTACHMENT_WIRE_MAX + 1];
int length = attachment_encode(&in, wire, sizeof(wire));
REQUIRE(length > 0 && !attachment_decode(wire, length, &out));
REQUIRE(out.kind == in.kind && out.duration_ms == 1200 && !strcmp(in.name, out.name) && !strcmp(in.waveform, out.waveform));
for (int i = 0; i < length; i++) REQUIRE(attachment_decode(wire, i, &out) < 0);
REQUIRE(attachment_decode(wire, length + 1, &out) < 0);
wire[0] = 99; REQUIRE(attachment_decode(wire, length, &out) < 0);
wire[0] = ATTACHMENT_VOICE; wire[length - 1] = '?'; REQUIRE(attachment_decode(wire, length, &out) < 0);
in.kind = ATTACHMENT_VIDEO; in.waveform[0] = 0; in.width = 640; in.height = 480;
length = attachment_encode(&in, wire, sizeof(wire)); REQUIRE(length > 0 && !attachment_decode(wire, length, &out));
REQUIRE(out.width == 640 && out.height == 480);
in.width = 0; REQUIRE(attachment_validate(&in) < 0);
REQUIRE(ATTACHMENT_DURATION_MAX_MS == 86400000);
for (int kind = ATTACHMENT_VOICE; kind <= ATTACHMENT_VIDEO; kind++) {
in = (struct attachment_info){ .kind = kind, .name = "duration.bin", .duration_ms = 86400000 };
if (kind == ATTACHMENT_VOICE) memset(in.waveform, 'A', 100);
else { in.width = 640; in.height = 480; }
length = attachment_encode(&in, wire, sizeof(wire)); REQUIRE(length > 0 && !attachment_decode(wire, length, &out));
in.duration_ms++; REQUIRE(attachment_validate(&in) < 0 && attachment_encode(&in, wire, sizeof(wire)) < 0);
wire[6]++; REQUIRE(attachment_decode(wire, length, &out) < 0);
}
in = (struct attachment_info){ .kind = ATTACHMENT_FILE, .name = "../unsafe" }; REQUIRE(attachment_validate(&in) < 0);
strcpy(in.name, "\xc0\xaf"); REQUIRE(attachment_validate(&in) < 0);
memset(in.name, 'n', 255); in.name[255] = 0;
REQUIRE(attachment_encode(&in, wire, 265) < 0); REQUIRE(attachment_encode(&in, wire, 266) == 266);
int16_t* pcm = u_calloc(49920, sizeof(*pcm)); REQUIRE(pcm != NULL);
for (unsigned i = 0; i < 49920; i++) pcm[i] = (int16_t)((i % 200) * 100 - 10000);
REQUIRE(!check_voice_roundtrip(pcm, 48001, 48000, 1, 1, 1000));
static const int rates[] = {8000, 12000, 16000, 24000, 48000};
for (size_t r = 0; r < sizeof(rates) / sizeof(rates[0]); r++) {
for (int channels = 1; channels <= 2; channels++) {
size_t frame = (size_t)(rates[r] / 50) * channels;
REQUIRE(!check_voice_roundtrip(pcm, frame * 26 - 1, rates[r], channels, (r + channels) % 3, 500));
}
}
const char* path = "test_attachment_voice.opus";
REQUIRE(voice_file_encode(pcm, 23999, 48000, 1, 1, path, &in) < 0);
REQUIRE(voice_file_encode(pcm, 48000, 48000, 1, 3, path, &in) < 0);
REQUIRE(voice_file_encode(pcm, 48000, 44100, 1, 1, path, &in) < 0);
u_free(pcm);
#if defined(__linux__) && SIZE_MAX > UINT32_MAX
REQUIRE(!check_voice_duration());
#endif
printf("test_attachment: strict metadata and shared Opus encode/decode PASS\n");
return 0;
}