143 lines
7.8 KiB
C
143 lines
7.8 KiB
C
/* Регрессии native bridge: владение config callbacks и границы батчей настоящего Opus-декодера. */
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "../lib/debug_config.h"
|
|
#include "../lib/mem.h"
|
|
#include "../src/media_async/voice_file.h"
|
|
#include "../tools/chatgui-android/jni_bridge/android_jni_bridge.h"
|
|
#include "../tools/chatgui-android/libutun_lite/utun_config_api.h"
|
|
#include "test_utils.h"
|
|
|
|
#define REQUIRE(value) do { if (!(value)) { \
|
|
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "Android bridge check failed line=%d: %s", __LINE__, #value); return 1; \
|
|
} } while (0)
|
|
|
|
static const char* config_string(const char* key, const char* def) {
|
|
return !strcmp(key, CFG_KEY_NODE_NAME) ? "Android config" : def;
|
|
}
|
|
|
|
static int64_t config_int64(const char* key, int64_t def) {
|
|
return !strcmp(key, "wide") ? INT64_C(0x123456789abcdef) : def;
|
|
}
|
|
|
|
static int config_int(const char* key, int def) { return !strcmp(key, CFG_KEY_CONTROL_PORT) ? 4321 : def; }
|
|
static int config_bool(const char* key, int def) { return !strcmp(key, "enabled") ? 3 : def; }
|
|
|
|
/* Таблица caller меняется после регистрации: сохранённые функции не должны зависеть от её памяти. */
|
|
static int check_provider_copy(void) {
|
|
utun_config_provider_t provider = {config_string, config_int64, config_int, config_bool};
|
|
utun_config_set_provider(&provider);
|
|
memset(&provider, 0, sizeof(provider));
|
|
REQUIRE(!strcmp(cfg_get_string(CFG_KEY_NODE_NAME, "fallback"), "Android config"));
|
|
REQUIRE(cfg_get_int64("wide", -1) == INT64_C(0x123456789abcdef));
|
|
REQUIRE(cfg_get_int(CFG_KEY_CONTROL_PORT, -1) == 4321);
|
|
REQUIRE(cfg_get_bool("enabled", -1) == 3);
|
|
REQUIRE(!strcmp(cfg_get_string("missing", "fallback"), "fallback"));
|
|
REQUIRE(cfg_get_int64("missing", 123) == 123 && cfg_get_int("missing", 456) == 456);
|
|
REQUIRE(cfg_get_bool("missing", 789) == 789);
|
|
provider.get_int = config_int;
|
|
utun_config_set_provider(&provider);
|
|
REQUIRE(cfg_get_int(CFG_KEY_CONTROL_PORT, -1) == 4321);
|
|
REQUIRE(!strcmp(cfg_get_string(CFG_KEY_NODE_NAME, "fallback"), "fallback"));
|
|
REQUIRE(cfg_get_int64("wide", 123) == 123 && cfg_get_bool("enabled", 456) == 456);
|
|
utun_config_set_provider(NULL);
|
|
REQUIRE(cfg_get_string(CFG_KEY_NODE_NAME, NULL) == NULL);
|
|
REQUIRE(cfg_get_int(CFG_KEY_CONTROL_PORT, 9999) == 9999);
|
|
printf("[PASS] config provider: owned table, optional callbacks and reset\n");
|
|
return 0;
|
|
}
|
|
|
|
static const char* bridge_string(const char* key) { return config_string(key, NULL); }
|
|
static int64_t bridge_int64(const char* key) { return config_int64(key, 0); }
|
|
static int bridge_int(const char* key) { return config_int(key, 0); }
|
|
|
|
/* Рабочая регистрация bridge переживает возврат init, повторная регистрация и destroy снимают прежние callbacks. */
|
|
static int check_bridge_config(void) {
|
|
struct utun_bridge_config config = {bridge_string, bridge_int64, bridge_int};
|
|
REQUIRE(utun_bridge_init(NULL, 0, NULL, NULL, &config));
|
|
memset(&config, 0, sizeof(config));
|
|
REQUIRE(!strcmp(cfg_get_string(CFG_KEY_NODE_NAME, NULL), "Android config"));
|
|
REQUIRE(!strcmp(cfg_get_string("missing", "fallback"), "fallback"));
|
|
REQUIRE(cfg_get_int64("wide", -1) == INT64_C(0x123456789abcdef));
|
|
REQUIRE(cfg_get_int(CFG_KEY_CONTROL_PORT, -1) == 4321 && cfg_get_bool("enabled", 99) == 99);
|
|
REQUIRE(utun_bridge_init(NULL, 0, NULL, NULL, NULL));
|
|
REQUIRE(!strcmp(cfg_get_string(CFG_KEY_NODE_NAME, "fallback"), "fallback"));
|
|
REQUIRE(cfg_get_int64("wide", 123) == 123 && cfg_get_int(CFG_KEY_CONTROL_PORT, 456) == 456);
|
|
config.get_int = bridge_int;
|
|
REQUIRE(utun_bridge_init(NULL, 0, NULL, NULL, &config));
|
|
REQUIRE(cfg_get_int(CFG_KEY_CONTROL_PORT, -1) == 4321);
|
|
REQUIRE(cfg_get_string(CFG_KEY_NODE_NAME, NULL) == NULL && cfg_get_int64("wide", 789) == 789);
|
|
utun_bridge_destroy();
|
|
REQUIRE(cfg_get_int(CFG_KEY_CONTROL_PORT, 9999) == 9999);
|
|
REQUIRE(utun_bridge_init(NULL, 0, NULL, NULL, NULL));
|
|
REQUIRE(cfg_get_int(CFG_KEY_CONTROL_PORT, 9998) == 9998);
|
|
utun_bridge_destroy();
|
|
printf("[PASS] bridge config: init, replacement, destroy and reinit\n");
|
|
return 0;
|
|
}
|
|
|
|
/* Один и тот же файл декодируется покадрово и батчами: PCM и количество отсчётов должны совпасть. */
|
|
static int check_voice_batches(const char* path, int rate, int channels) {
|
|
int frame = rate / 50 * channels, count = frame * 27;
|
|
int16_t* input = u_calloc((size_t)count, sizeof(*input));
|
|
int16_t* reference = u_calloc((size_t)count, sizeof(*reference));
|
|
int16_t* output = u_calloc((size_t)(count + 8 * frame), sizeof(*output));
|
|
REQUIRE(input && reference && output);
|
|
for (int i = 0; i < count; i++) input[i] = (int16_t)((i % 97 - 48) * 200);
|
|
struct attachment_info info = {0};
|
|
REQUIRE(!voice_file_encode(input, (size_t)count, rate, channels, 1, path, &info));
|
|
|
|
int decoded_rate, decoded_channels, total;
|
|
void* decoder = utun_bridge_voice_decode_open(path, &decoded_rate, &decoded_channels, &total);
|
|
REQUIRE(decoder && decoded_rate == rate && decoded_channels == channels && total == count);
|
|
for (int position = 0; position < count; position += frame)
|
|
REQUIRE(utun_bridge_voice_decode_read(decoder, reference + position, frame) == frame);
|
|
REQUIRE(!utun_bridge_voice_decode_read(decoder, output, frame));
|
|
utun_bridge_voice_decode_close(decoder);
|
|
|
|
decoder = utun_bridge_voice_decode_open(path, &decoded_rate, &decoded_channels, &total); REQUIRE(decoder);
|
|
REQUIRE(!utun_bridge_voice_decode_read(decoder, output, frame - 1));
|
|
int position = 0;
|
|
while (position < count) {
|
|
int expected = count - position < 7 * frame ? count - position : 7 * frame;
|
|
output[position + expected] = 12345;
|
|
int read = utun_bridge_voice_decode_read(decoder, output + position, 7 * frame + frame / 2);
|
|
REQUIRE(read == expected && output[position + expected] == 12345); position += read;
|
|
}
|
|
REQUIRE(position == count && !memcmp(output, reference, (size_t)count * sizeof(*output)));
|
|
REQUIRE(!utun_bridge_voice_decode_read(decoder, output, count + frame));
|
|
REQUIRE(!utun_bridge_voice_decode_read(decoder, output, count + frame));
|
|
utun_bridge_voice_decode_close(decoder);
|
|
|
|
decoder = utun_bridge_voice_decode_open(path, &decoded_rate, &decoded_channels, &total); REQUIRE(decoder);
|
|
output[count] = 12345;
|
|
REQUIRE(utun_bridge_voice_decode_read(decoder, output, count + frame) == count);
|
|
REQUIRE(output[count] == 12345 && !memcmp(output, reference, (size_t)count * sizeof(*output)));
|
|
REQUIRE(!utun_bridge_voice_decode_read(decoder, output, count + frame));
|
|
utun_bridge_voice_decode_close(decoder);
|
|
REQUIRE(!test_unlink(path));
|
|
u_free(input); u_free(reference); u_free(output);
|
|
printf("[PASS] voice decode: rate=%d channels=%d, partial batch, oversized read, exact PCM and EOF\n", rate, channels);
|
|
return 0;
|
|
}
|
|
|
|
int main(void) {
|
|
debug_config_init(); debug_set_level(DEBUG_LEVEL_DEBUG);
|
|
int failed = check_provider_copy();
|
|
/* Проверки bridge требуют исправного владения таблицей провайдера. */
|
|
if (!failed) failed += check_bridge_config();
|
|
utun_config_set_provider(NULL);
|
|
char directory[512] = "/tmp/utun_android_bridge_XXXXXX", path[1024];
|
|
REQUIRE(!test_mkdtemp(directory));
|
|
snprintf(path, sizeof(path), "%s/voice.opus", directory);
|
|
const int rates[] = {8000, 12000, 16000, 24000, 48000};
|
|
for (unsigned i = 0; i < sizeof(rates) / sizeof(rates[0]); i++)
|
|
for (int channels = 1; channels <= 2; channels++) failed += check_voice_batches(path, rates[i], channels);
|
|
if (failed) return 1;
|
|
REQUIRE(!test_rmdir(directory));
|
|
printf("test_android_bridge: PASS\n");
|
|
return 0;
|
|
}
|