/* Регрессии native bridge: владение config callbacks и границы батчей настоящего Opus-декодера. */ #include #include #include #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; }