|
| 1 | +#include "vocotype/desktop/audio_startup_diagnostics.hpp" |
| 2 | +#ifdef __APPLE__ |
| 3 | +#include <libproc.h> |
| 4 | +#endif |
1 | 5 | #include "vocotype/desktop/audio.hpp" |
2 | 6 | #include "vocotype/desktop/config.hpp" |
3 | 7 | #include "vocotype/desktop/ipc.hpp" |
@@ -69,6 +73,32 @@ Options parse(int argc, char **argv) { |
69 | 73 | return options; |
70 | 74 | } |
71 | 75 | } // namespace |
| 76 | +#ifdef __APPLE__ |
| 77 | +namespace { |
| 78 | +bool known_system_audio_capture_helper_present() noexcept { |
| 79 | + try { |
| 80 | + constexpr int kMaxProcessBytes = 1024 * 1024; |
| 81 | + const int bytes = proc_listpids(PROC_ALL_PIDS, 0, nullptr, 0); |
| 82 | + if (bytes <= 0 || bytes > kMaxProcessBytes) return false; |
| 83 | + std::vector<pid_t> pids(static_cast<std::size_t>(bytes) / sizeof(pid_t) + 64); |
| 84 | + const int received = proc_listpids(PROC_ALL_PIDS, 0, pids.data(), |
| 85 | + static_cast<int>(pids.size() * sizeof(pid_t))); |
| 86 | + if (received <= 0) return false; |
| 87 | + const auto count = std::min(pids.size(), static_cast<std::size_t>(received) / sizeof(pid_t)); |
| 88 | + for (std::size_t index = 0; index < count; ++index) { |
| 89 | + if (pids[index] <= 0) continue; |
| 90 | + char path[PROC_PIDPATHINFO_MAXSIZE]{}; |
| 91 | + if (proc_pidpath(pids[index], path, sizeof(path)) > 0 && |
| 92 | + vocotype::desktop::is_known_system_audio_capture_helper(path)) return true; |
| 93 | + } |
| 94 | + } catch (...) { |
| 95 | + // Diagnostics must never prevent the bounded recorder exit. |
| 96 | + } |
| 97 | + return false; |
| 98 | +} |
| 99 | +} // namespace |
| 100 | +#endif |
| 101 | + |
72 | 102 | int main(int argc, char **argv) { |
73 | 103 | try { |
74 | 104 | const Options options = parse(argc, argv); |
@@ -292,16 +322,18 @@ int main(int argc, char **argv) { |
292 | 322 | // not sufficient here: a timed recording may request stop while the |
293 | 323 | // capture thread is still blocked inside AudioDeviceStart. |
294 | 324 | std::thread([&first_audio_block, &capture_finished] { |
295 | | - // A built-in microphone that has been idle for a long time can take just |
296 | | - // over five seconds to leave its deep CoreAudio power state on macOS 26. |
297 | | - // Keep enough margin to avoid killing the recorder exactly as the HAL |
298 | | - // reports the device Running, while still bounding genuine startup hangs. |
| 325 | + // Bound a missing first PCM block. A later HAL "Running" log during |
| 326 | + // process cancellation is not evidence that audio capture succeeded, |
| 327 | + // nor proof of normal device wake-up latency. |
299 | 328 | constexpr auto kMicrophoneStartupTimeout = std::chrono::seconds(8); |
300 | 329 | std::this_thread::sleep_for(kMicrophoneStartupTimeout); |
301 | 330 | if (!first_audio_block.load(std::memory_order_acquire) && |
302 | 331 | !capture_finished.load(std::memory_order_acquire)) { |
| 332 | + const bool helper_detected = known_system_audio_capture_helper_present(); |
303 | 333 | emit({{"type", "error"}, |
304 | | - {"error", "麦克风启动超过 8 秒(CoreAudio 未返回音频);请重试,必要时重启系统音频服务"}}); |
| 334 | + {"error_code", "microphone_start_timeout"}, |
| 335 | + {"system_audio_capture_hint", helper_detected}, |
| 336 | + {"error", vocotype::desktop::microphone_startup_error(helper_detected)}}); |
305 | 337 | std::_Exit(2); |
306 | 338 | } |
307 | 339 | }).detach(); |
|
0 commit comments