Browse Source

chatgui: диагностика воспроизведения голосовых сообщений (Windows)

- sound_manager: ma_engine_init/playRawPcm логируют через DEBUG_* (qWarning/qDebug терялись в GUI-приложении без консоли); детальный результат старта PCM (rc/is_playing/cursor)
- main: ранняя инициализация лога в GUI-потоке до SoundManager::init, чтобы диагностика инициализации попала в файл
- messagedelegate: лог клика по Play/timeline (filePath/dlState/существует ли файл)
- voiceplayback: лог заголовка opus (sr/ch/frameSamples)

Попутные правки:
- u_async: фикс двойного close wakeup_pipe (eventfd/Windows)
- chat_core: NULL-guard в get_db/is_initialized
- android: sqlite открывается в utun_instance_init, cleanup stale-потока без утечки fd
- android SettingsScreen: компактная раскладка имени интерфейса
proxy
evgeny 2 weeks ago
parent
commit
9793733cd5
  1. 7
      lib/u_async.c
  2. 4
      src/chat/chat_core.c
  3. 4
      tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/SettingsScreen.kt
  4. 63
      tools/chatgui-android/libutun_lite/instance_lite.c
  5. 10
      tools/chatgui/src/main.cpp
  6. 4
      tools/chatgui/src/messagedelegate.cpp
  7. 32
      tools/chatgui/src/sound_manager.cpp
  8. 3
      tools/chatgui/src/voiceplayback.cpp

7
lib/u_async.c

@ -1783,10 +1783,13 @@ void uasync_destroy(struct UASYNC* ua, int close_fds) {
if (ua->wakeup_initialized) { if (ua->wakeup_initialized) {
#ifdef _WIN32 #ifdef _WIN32
closesocket((SOCKET)(intptr_t)ua->wakeup_pipe[0]); closesocket((SOCKET)(intptr_t)ua->wakeup_pipe[0]);
closesocket((SOCKET)(intptr_t)ua->wakeup_pipe[1]); if (ua->wakeup_pipe[1] != ua->wakeup_pipe[0])
closesocket((SOCKET)(intptr_t)ua->wakeup_pipe[1]);
#else #else
close(ua->wakeup_pipe[0]); close(ua->wakeup_pipe[0]);
close(ua->wakeup_pipe[1]); // eventfd на Linux: wakeup_pipe[0] == wakeup_pipe[1] — избегаем двойного close
if (ua->wakeup_pipe[1] != ua->wakeup_pipe[0])
close(ua->wakeup_pipe[1]);
#endif #endif
} }

4
src/chat/chat_core.c

@ -241,8 +241,8 @@ void chat_core_destroy(struct UTUN_INSTANCE* inst) {
DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "%s: destroyed", CC_ID); DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "%s: destroyed", CC_ID);
} }
sqlite3* chat_core_get_db(struct UTUN_INSTANCE* inst) { return CC(inst) ? CC(inst)->db : NULL; } sqlite3* chat_core_get_db(struct UTUN_INSTANCE* inst) { return (inst && CC(inst)) ? CC(inst)->db : NULL; }
int chat_core_is_initialized(struct UTUN_INSTANCE* inst) { return CC(inst) ? CC(inst)->initialized : 0; } int chat_core_is_initialized(struct UTUN_INSTANCE* inst) { return (inst && CC(inst)) ? CC(inst)->initialized : 0; }
/* ─── chat settings (GUI → uasync) ─── */ /* ─── chat settings (GUI → uasync) ─── */

4
tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/SettingsScreen.kt

@ -249,8 +249,6 @@ private fun GeneralSettingsTab(vm: ChatViewModel, dirty: Boolean, onDirty: (Bool
val stateColor = when (s.state) { 2 -> Color(0xFF27AE60); 1 -> Color(0xFFF1C40F); else -> Color(0xFFE53935) } val stateColor = when (s.state) { 2 -> Color(0xFF27AE60); 1 -> Color(0xFFF1C40F); else -> Color(0xFFE53935) }
Column(modifier = Modifier.fillMaxWidth().padding(vertical = 2.dp)) { Column(modifier = Modifier.fillMaxWidth().padding(vertical = 2.dp)) {
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
Text(s.ifName, style = MaterialTheme.typography.bodyMedium, modifier = Modifier.weight(1f))
Spacer(Modifier.width(4.dp))
Text("${s.bindIp}:${s.port}", color = MaterialTheme.colorScheme.onSurfaceVariant, style = MaterialTheme.typography.bodySmall) Text("${s.bindIp}:${s.port}", color = MaterialTheme.colorScheme.onSurfaceVariant, style = MaterialTheme.typography.bodySmall)
Spacer(Modifier.width(6.dp)) Spacer(Modifier.width(6.dp))
Surface(shape = MaterialTheme.shapes.extraSmall, color = stateColor, Surface(shape = MaterialTheme.shapes.extraSmall, color = stateColor,
@ -265,6 +263,8 @@ private fun GeneralSettingsTab(vm: ChatViewModel, dirty: Boolean, onDirty: (Bool
color = MaterialTheme.colorScheme.onSurfaceVariant) color = MaterialTheme.colorScheme.onSurfaceVariant)
} }
} }
Text(s.ifName, style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.fillMaxWidth())
} }
} }
} }

63
tools/chatgui-android/libutun_lite/instance_lite.c

@ -261,6 +261,8 @@ static void install_crash_handlers(void) {
static void* instance_thread(void* arg) { static void* instance_thread(void* arg) {
char* config_text = (char*)arg; char* config_text = (char*)arg;
int my_gen = g_generation; int my_gen = g_generation;
struct UASYNC* my_ua = NULL; /* поток-локальная копия — для cleanup stale-потока */
struct UTUN_INSTANCE* my_inst = NULL;
struct utun_config* config = parse_config_from_buf(config_text, strlen(config_text), "android"); struct utun_config* config = parse_config_from_buf(config_text, strlen(config_text), "android");
u_free(config_text); u_free(config_text);
@ -269,6 +271,7 @@ static void* instance_thread(void* arg) {
install_crash_handlers(); install_crash_handlers();
g_ua = uasync_create(); g_ua = uasync_create();
my_ua = g_ua;
if (!g_ua) { IL_LOGE("uasync_create failed"); free_config(config); __atomic_store_n(&g_thread_running, 0, __ATOMIC_RELEASE); pthread_detach(pthread_self()); return NULL; } if (!g_ua) { IL_LOGE("uasync_create failed"); free_config(config); __atomic_store_n(&g_thread_running, 0, __ATOMIC_RELEASE); pthread_detach(pthread_self()); return NULL; }
standby_init(g_ua); standby_init(g_ua);
@ -280,6 +283,7 @@ static void* instance_thread(void* arg) {
ensure_keys(config); ensure_keys(config);
g_inst = utun_instance_create_from_config(g_ua, config); g_inst = utun_instance_create_from_config(g_ua, config);
my_inst = g_inst;
if (!g_inst) { if (!g_inst) {
IL_LOGE("utun_instance_create_from_config failed"); IL_LOGE("utun_instance_create_from_config failed");
standby_deinit(); standby_deinit();
@ -294,25 +298,8 @@ static void* instance_thread(void* arg) {
IL_LOGI("instance created, node_id=0x%016llx", (unsigned long long)g_inst->node_id); IL_LOGI("instance created, node_id=0x%016llx", (unsigned long long)g_inst->node_id);
/* Open shared SQLite DB */ /* Shared SQLite DB открывается внутри utun_instance_init() → topo_groups_init()
if (g_db_path[0] && !g_inst->topo_sqlite_db) { (topo_group.c). Двойное открытие здесь давало утечку соединения на каждый рестарт. */
char db_file[512];
snprintf(db_file, sizeof(db_file), "%s/chats.db", g_db_path);
int rc = sqlite3_open_v2(db_file, &g_inst->topo_sqlite_db,
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, NULL);
if (rc == SQLITE_OK && g_inst->topo_sqlite_db) {
sqlite3_exec(g_inst->topo_sqlite_db, "PRAGMA journal_mode=WAL", NULL, NULL, NULL);
sqlite3_exec(g_inst->topo_sqlite_db, "PRAGMA foreign_keys=ON", NULL, NULL, NULL);
sqlite3_exec(g_inst->topo_sqlite_db, "PRAGMA synchronous=NORMAL", NULL, NULL, NULL);
sqlite3_exec(g_inst->topo_sqlite_db, "PRAGMA wal_autocheckpoint=10000", NULL, NULL, NULL);
topo_node_sqlite_init(g_inst->topo_sqlite_db);
IL_LOGI("SQLite opened %s", db_file);
} else {
IL_LOGE("SQLite open failed rc=%d", rc);
if (g_inst->topo_sqlite_db) { sqlite3_close(g_inst->topo_sqlite_db); g_inst->topo_sqlite_db = NULL; }
}
}
/* Full init: db_sync, chat_core, chat_sync, init_connections */ /* Full init: db_sync, chat_core, chat_sync, init_connections */
if (utun_instance_init(g_inst) != 0) { if (utun_instance_init(g_inst) != 0) {
IL_LOGE("utun_instance_init failed"); IL_LOGE("utun_instance_init failed");
@ -369,13 +356,17 @@ static void* instance_thread(void* arg) {
g_inst ? g_inst->connections : NULL); g_inst ? g_inst->connections : NULL);
uasync_mark_stopped(g_ua); uasync_mark_stopped(g_ua);
__atomic_store_n(&g_running, 0, __ATOMIC_RELEASE); __atomic_store_n(&g_running, 0, __ATOMIC_RELEASE);
utun_instance_destroy(g_inst); struct UTUN_INSTANCE* old_inst = g_inst;
g_inst = NULL; g_inst = NULL;
utun_instance_destroy(old_inst);
my_inst = NULL;
uasync_print_resources(g_ua, "AFTER_DESTROY"); uasync_print_resources(g_ua, "AFTER_DESTROY");
u_report_unfreed_blocks(); u_report_unfreed_blocks();
standby_deinit(); standby_deinit();
uasync_destroy(g_ua, 0); uasync_destroy(g_ua, 0);
my_ua = NULL;
g_ua = uasync_create(); g_ua = uasync_create();
my_ua = g_ua;
if (!g_ua) { IL_LOGE("poll exit: uasync_create failed"); break; } if (!g_ua) { IL_LOGE("poll exit: uasync_create failed"); break; }
standby_init(g_ua); standby_init(g_ua);
@ -389,20 +380,11 @@ static void* instance_thread(void* arg) {
if (!config) { IL_LOGE("poll exit: parse_config failed"); break; } if (!config) { IL_LOGE("poll exit: parse_config failed"); break; }
g_inst = utun_instance_create_from_config(g_ua, config); g_inst = utun_instance_create_from_config(g_ua, config);
my_inst = g_inst;
if (!g_inst) { IL_LOGE("poll exit: create_from_config failed"); break; } if (!g_inst) { IL_LOGE("poll exit: create_from_config failed"); break; }
chat_event_set_handler(g_inst, chat_event_forward); chat_event_set_handler(g_inst, chat_event_forward);
char db_file[512]; /* Shared SQLite DB открывается внутри utun_instance_init() (topo_group.c). */
snprintf(db_file, sizeof(db_file), "%s/chats.db", g_db_path);
int rc = sqlite3_open_v2(db_file, &g_inst->topo_sqlite_db,
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, NULL);
if (rc == SQLITE_OK && g_inst->topo_sqlite_db) {
sqlite3_exec(g_inst->topo_sqlite_db, "PRAGMA journal_mode=WAL", NULL, NULL, NULL);
sqlite3_exec(g_inst->topo_sqlite_db, "PRAGMA foreign_keys=ON", NULL, NULL, NULL);
sqlite3_exec(g_inst->topo_sqlite_db, "PRAGMA synchronous=NORMAL", NULL, NULL, NULL);
topo_node_sqlite_init(g_inst->topo_sqlite_db);
} else { IL_LOGE("poll exit: SQLite open failed rc=%d", rc); break; }
if (utun_instance_init(g_inst) != 0) { IL_LOGE("poll exit: utun_instance_init failed"); break; } if (utun_instance_init(g_inst) != 0) { IL_LOGE("poll exit: utun_instance_init failed"); break; }
chat_core_sync_my_addresses(g_inst); chat_core_sync_my_addresses(g_inst);
fire_local_sockets_event(); fire_local_sockets_event();
@ -424,22 +406,29 @@ static void* instance_thread(void* arg) {
if (my_gen == g_generation) { if (my_gen == g_generation) {
__atomic_store_n(&g_running, 0, __ATOMIC_RELEASE); __atomic_store_n(&g_running, 0, __ATOMIC_RELEASE);
if (g_inst) { if (my_inst) {
chat_event_post(g_inst, CHAT_EVT_SERVICE_STOPPED, NULL, 0); chat_event_post(my_inst, CHAT_EVT_SERVICE_STOPPED, NULL, 0);
utun_instance_destroy(g_inst);
g_inst = NULL; g_inst = NULL;
utun_instance_destroy(my_inst);
my_inst = NULL;
} }
if (g_ua) { if (my_ua) {
standby_deinit(); standby_deinit();
uasync_destroy(g_ua, 0); uasync_destroy(my_ua, 0);
g_ua = NULL; g_ua = NULL;
my_ua = NULL;
} }
char* stale_cfg = (char*)__atomic_exchange_n(&g_restart_config, NULL, __ATOMIC_ACQUIRE); char* stale_cfg = (char*)__atomic_exchange_n(&g_restart_config, NULL, __ATOMIC_ACQUIRE);
u_free(stale_cfg); u_free(stale_cfg);
u_report_unfreed_blocks(); u_report_unfreed_blocks();
IL_LOGI("cleanup complete (gen=%d)", my_gen); IL_LOGI("cleanup complete (gen=%d)", my_gen);
} else { } else {
IL_LOGI("cleanup skipped — thread gen=%d but current gen=%d (stale thread)", my_gen, g_generation); /* stale-поток: глобальные g_inst/g_ua уже принадлежат новому потоку,
но собственные объекты этого потока надо закрыть, иначе течёт fd. */
IL_LOGI("cleanup (stale thread gen=%d vs %d): destroying own inst=%p ua=%p to avoid fd leak",
my_gen, g_generation, (void*)my_inst, (void*)my_ua);
if (my_inst) { utun_instance_destroy(my_inst); my_inst = NULL; }
if (my_ua) { uasync_destroy(my_ua, 0); my_ua = NULL; }
} }
__atomic_store_n(&g_thread_running, 0, __ATOMIC_RELEASE); __atomic_store_n(&g_thread_running, 0, __ATOMIC_RELEASE);
pthread_mutex_lock(&g_stop_mutex); pthread_mutex_lock(&g_stop_mutex);

10
tools/chatgui/src/main.cpp

@ -148,6 +148,16 @@ int main(int argc, char *argv[]) {
if (!QDir::isAbsolutePath(debugFile)) if (!QDir::isAbsolutePath(debugFile))
debugFile = QCoreApplication::applicationDirPath() + "/" + debugFile; debugFile = QCoreApplication::applicationDirPath() + "/" + debugFile;
/* ранняя инициализация лога в GUI-потоке: SoundManager::init() вызывается в
* конструкторе MainWindow ДО старта utun-потока, поэтому без этого его диагностика
* (ma_engine_init и т.п.) в лог-файл не попадёт. */
debug_config_init();
debug_enable_function_name(0);
debug_enable_file_output(debugFile.toUtf8().constData(), 0);
QString lvl = cfg.debugLevel().trimmed();
if (lvl.isEmpty()) lvl = QStringLiteral("info");
debug_apply_global_level(lvl.toUtf8().constData());
DbManager db; DbManager db;
MainWindow w(nullptr, &db, cfgPath, dbPath, debugFile, MainWindow w(nullptr, &db, cfgPath, dbPath, debugFile,
cfg.debugLevel(), cfg.debugCategories()); cfg.debugLevel(), cfg.debugCategories());

4
tools/chatgui/src/messagedelegate.cpp

@ -174,6 +174,10 @@ bool MessageDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
qint64 msgId = index.data(MsgIdRole).toLongLong(); qint64 msgId = index.data(MsgIdRole).toLongLong();
QString chId = index.data(MsgChannelIdRole).toString(); QString chId = index.data(MsgChannelIdRole).toString();
GUI_DEBUG("editorEvent voice: playBtn=%d wave=%d file='%s' exists=%d dlState=%d msgId=%lld",
(int)onPlayBtn, (int)onWaveform, qPrintable(filePath), QFile::exists(filePath),
dlState, (long long)msgId);
/* not downloaded: start download (play will start on completion) */ /* not downloaded: start download (play will start on completion) */
if (filePath.isEmpty() && dlState != 1) { if (filePath.isEmpty() && dlState != 1) {
if (chId.isEmpty() || msgId <= 0) return false; if (chId.isEmpty() || msgId <= 0) return false;

32
tools/chatgui/src/sound_manager.cpp

@ -21,14 +21,21 @@ bool SoundManager::init() {
ma_engine* engine = new ma_engine; ma_engine* engine = new ma_engine;
ma_result result = ma_engine_init(NULL, engine); ma_result result = ma_engine_init(NULL, engine);
if (result != MA_SUCCESS) { if (result != MA_SUCCESS) {
qWarning("SoundManager: ma_engine_init failed (%d)", result); DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "SoundManager: ma_engine_init failed (%d)", result);
delete engine; delete engine;
return false; return false;
} }
m_engine = engine; m_engine = engine;
m_context = ma_device_get_context(ma_engine_get_device(engine)); m_context = ma_device_get_context(ma_engine_get_device(engine));
m_initialized = true; m_initialized = true;
qDebug("SoundManager: initialized (engine=%p, context=%p)", (void*)engine, (void*)m_context);
ma_device* dev = ma_engine_get_device(engine);
DEBUG_INFO(DEBUG_CATEGORY_GENERAL,
"SoundManager: initialized engine=%p ctx=%p dev='%s' fmt=%d ch=%u sr=%u internalSr=%u",
(void*)engine, (void*)m_context,
dev ? dev->playback.name : "?", dev ? (int)dev->playback.format : -1,
dev ? dev->playback.channels : 0, dev ? dev->sampleRate : 0,
dev ? dev->playback.internalSampleRate : 0);
return true; return true;
} }
@ -78,7 +85,8 @@ void SoundManager::setEvent(const QString& name, bool enabled, float volume) {
es.enabled = enabled; es.enabled = enabled;
es.volume = volume; es.volume = volume;
m_eventSettings[name] = es; m_eventSettings[name] = es;
qDebug("SoundManager: event '%s' enabled=%d vol=%.2f", qPrintable(name), enabled, volume); DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "SoundManager: event '%s' enabled=%d vol=%.2f",
qPrintable(name), enabled, volume);
} }
SoundManager::EventSettings SoundManager::eventSettings(const QString& name) const { SoundManager::EventSettings SoundManager::eventSettings(const QString& name) const {
@ -191,12 +199,14 @@ ma_context* SoundManager::context() {
void SoundManager::playRawPcm(const int16_t* pcm, int sampleCount, int sampleRate, int channels) { void SoundManager::playRawPcm(const int16_t* pcm, int sampleCount, int sampleRate, int channels) {
if (!m_engine || !m_initialized) { if (!m_engine || !m_initialized) {
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "SoundManager::playRawPcm: not initialized"); DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "SoundManager::playRawPcm: not initialized (engine=%p init=%d)",
(void*)m_engine, (int)m_initialized);
return; return;
} }
clearCurrentPcm(); clearCurrentPcm();
m_currentPcmSampleRate = sampleRate; m_currentPcmSampleRate = sampleRate;
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "SoundManager::playRawPcm: %d samples %dHz %dch", sampleCount, sampleRate, channels); DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "SoundManager::playRawPcm: %d samples %dHz %dch volume=%.2f",
sampleCount, sampleRate, channels, m_volume);
ma_audio_buffer_config bufCfg = ma_audio_buffer_config_init( ma_audio_buffer_config bufCfg = ma_audio_buffer_config_init(
ma_format_s16, (ma_uint32)channels, (ma_uint64)sampleCount, pcm, NULL); ma_format_s16, (ma_uint32)channels, (ma_uint64)sampleCount, pcm, NULL);
@ -204,7 +214,7 @@ void SoundManager::playRawPcm(const int16_t* pcm, int sampleCount, int sampleRat
m_currentPcmBuffer = buf; m_currentPcmBuffer = buf;
ma_result r = ma_audio_buffer_init_copy(&bufCfg, buf); ma_result r = ma_audio_buffer_init_copy(&bufCfg, buf);
if (r != MA_SUCCESS) { if (r != MA_SUCCESS) {
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "SoundManager: audio_buffer_init_copy failed (%d)", r); DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "SoundManager: audio_buffer_init_copy failed (%d)", r);
delete buf; m_currentPcmBuffer = nullptr; delete buf; m_currentPcmBuffer = nullptr;
return; return;
} }
@ -212,14 +222,20 @@ void SoundManager::playRawPcm(const int16_t* pcm, int sampleCount, int sampleRat
m_currentPcmSound = new ma_sound; m_currentPcmSound = new ma_sound;
r = ma_sound_init_from_data_source(m_engine, (ma_data_source*)buf, 0, nullptr, m_currentPcmSound); r = ma_sound_init_from_data_source(m_engine, (ma_data_source*)buf, 0, nullptr, m_currentPcmSound);
if (r != MA_SUCCESS) { if (r != MA_SUCCESS) {
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "SoundManager: PCM sound init failed (%d)", r); DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "SoundManager: PCM sound init failed (%d)", r);
ma_audio_buffer_uninit(buf); delete buf; m_currentPcmBuffer = nullptr; ma_audio_buffer_uninit(buf); delete buf; m_currentPcmBuffer = nullptr;
delete m_currentPcmSound; m_currentPcmSound = nullptr; delete m_currentPcmSound; m_currentPcmSound = nullptr;
return; return;
} }
ma_sound_set_volume(m_currentPcmSound, m_volume); ma_sound_set_volume(m_currentPcmSound, m_volume);
ma_sound_start(m_currentPcmSound); r = ma_sound_start(m_currentPcmSound);
ma_uint64 cursor = 0;
ma_sound_get_cursor_in_pcm_frames(m_currentPcmSound, &cursor);
DEBUG_INFO(DEBUG_CATEGORY_GENERAL,
"SoundManager::playRawPcm: started rc=%d is_playing=%d cursor=%llu/%d",
(int)r, (int)ma_sound_is_playing(m_currentPcmSound),
(unsigned long long)cursor, sampleCount);
} }
void SoundManager::pauseCurrentPcm() { void SoundManager::pauseCurrentPcm() {

3
tools/chatgui/src/voiceplayback.cpp

@ -41,6 +41,9 @@ int VoicePlayer::decodeOpusFile(const QString& filePath, std::vector<int16_t>& o
outSampleRate = sampleRate; outSampleRate = sampleRate;
outChannels = channels; outChannels = channels;
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "VoicePlayer: header sr=%d ch=%d frameSamples=%u file=%s",
sampleRate, channels, (unsigned)frameSamples, qPrintable(filePath));
opus_codec_decoder_t* dec = opus_codec_decoder_create(sampleRate, channels); opus_codec_decoder_t* dec = opus_codec_decoder_create(sampleRate, channels);
if (!dec) { if (!dec) {
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "VoicePlayer: decoder create failed"); DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "VoicePlayer: decoder create failed");

Loading…
Cancel
Save