You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

190 lines
9.0 KiB

#include "audio_diagnostics.h"
#ifdef __linux__
#include <dlfcn.h>
#include <time.h>
static void audio_diag_pulse_init(void* device);
static void audio_timing_begin(void* device, unsigned int frames, int capture);
static void audio_timing_end(void);
static void audio_capture_hole(uint64_t offset);
#define MA_AUDIO_TIMING_BEGIN(device, frames, capture) audio_timing_begin(device, frames, capture)
#define MA_AUDIO_TIMING_END() audio_timing_end()
#define MA_AUDIO_CAPTURE_HOLE(offset) audio_capture_hole(offset)
#define MA_DIAGNOSTIC_EVENT(device, kind, stage, value, detail) audio_diag_event(device, kind, stage, value, detail)
#define MA_DIAGNOSTIC_LOG(log, level, message) audio_diag_log(log, level, message)
#define MA_DIAGNOSTIC_DEVICE_INIT(device) audio_diag_pulse_init(device)
#endif
#define MINIAUDIO_IMPLEMENTATION
#include "../../../lib/miniaudio.h"
#ifdef __linux__
#ifdef MA_SUPPORT_PULSEAUDIO
static void audio_diag_pulse_underflow(ma_pa_stream* stream, void* userdata) {
(void)stream;
audio_diag_event(userdata, AUDIO_DIAG_BACKEND_UNDERRUN, AUDIO_DIAG_WRITE, 1, 0);
}
static void audio_diag_pulse_overflow(ma_pa_stream* stream, void* userdata) {
(void)stream;
audio_diag_event(userdata, AUDIO_DIAG_BACKEND_OVERFLOW, AUDIO_DIAG_WRITE, 1, 0);
}
static void audio_diag_pulse_started(ma_pa_stream* stream, void* userdata) {
(void)stream;
audio_diag_event(userdata, AUDIO_DIAG_BACKEND_STARTED, AUDIO_DIAG_WRITE, 1, 0);
}
#endif
/* Только init-поток: backend-callbacks остаются короткими наблюдателями. */
static void audio_diag_pulse_init(void* device) {
#ifdef MA_SUPPORT_PULSEAUDIO
ma_device* dev = (ma_device*)device;
ma_pa_stream_set_suspended_callback_proc underflow, overflow, started;
if (dev->pContext->backend != ma_backend_pulseaudio || !dev->pulse.pStreamPlayback) return;
#ifndef MA_NO_RUNTIME_LINKING
underflow = (ma_pa_stream_set_suspended_callback_proc)dlsym(dev->pContext->pulse.pulseSO, "pa_stream_set_underflow_callback");
overflow = (ma_pa_stream_set_suspended_callback_proc)dlsym(dev->pContext->pulse.pulseSO, "pa_stream_set_overflow_callback");
started = (ma_pa_stream_set_suspended_callback_proc)dlsym(dev->pContext->pulse.pulseSO, "pa_stream_set_started_callback");
#else
underflow = pa_stream_set_underflow_callback;
overflow = pa_stream_set_overflow_callback;
started = pa_stream_set_started_callback;
#endif
if (!underflow || !overflow || !started) {
audio_diag_log(ma_device_get_log(dev), MA_LOG_LEVEL_WARNING, "PulseAudio xrun/started callbacks unavailable");
return;
}
underflow((ma_pa_stream*)dev->pulse.pStreamPlayback, audio_diag_pulse_underflow, dev);
overflow((ma_pa_stream*)dev->pulse.pStreamPlayback, audio_diag_pulse_overflow, dev);
started((ma_pa_stream*)dev->pulse.pStreamPlayback, audio_diag_pulse_started, dev);
audio_diag_log(ma_device_get_log(dev), MA_LOG_LEVEL_INFO, "PulseAudio xrun/started observers registered");
#else
(void)device;
#endif
}
#endif
/* Вызывается только владельцем общего контекста (GUI), вне device callbacks. */
int audio_context_failed(ma_context* context) {
#ifdef MA_SUPPORT_PULSEAUDIO
if (context && context->backend == ma_backend_pulseaudio) {
int result = ((ma_pa_mainloop_iterate_proc)context->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)context->pulse.pMainLoop, 0, NULL);
ma_pa_context_state_t state = ((ma_pa_context_get_state_proc)context->pulse.pa_context_get_state)((ma_pa_context*)context->pulse.pPulseContext);
if (result < 0 || state == MA_PA_CONTEXT_FAILED || state == MA_PA_CONTEXT_TERMINATED) {
ma_log_postf(ma_context_get_log(context), MA_LOG_LEVEL_ERROR, "[PulseAudio] Shared context failed iterate=%d state=%d errno=%d.\n",
result, state, ((ma_pa_context_errno_proc)context->pulse.pa_context_errno)((ma_pa_context*)context->pulse.pPulseContext));
return 1;
}
}
#endif
return 0;
}
/* Только device callback того же PA mainloop: запрос использует готовый timing snapshot без roundtrip. */
int64_t audio_stream_latency_us(ma_device* device, int capture, int* valid) {
*valid = 0;
#ifdef MA_SUPPORT_PULSEAUDIO
if (device && device->pContext->backend == ma_backend_pulseaudio && device->pContext->pulse.pa_stream_get_latency) {
ma_pa_stream* stream = capture ? device->pulse.pStreamCapture : device->pulse.pStreamPlayback;
ma_uint64 latency = 0; int negative = 0;
if (stream && ((ma_pa_stream_get_latency_proc)device->pContext->pulse.pa_stream_get_latency)(stream, &latency, &negative) == 0) {
*valid = 1;
return negative ? -(int64_t)latency : (int64_t)latency;
}
}
#endif
return 0;
}
/* PA device name читается в его mainloop; GUI получает только непрозрачный идентификатор маршрута. */
uint64_t audio_stream_route(ma_device* device, int capture) {
#ifdef MA_SUPPORT_PULSEAUDIO
if (device && device->pContext->backend == ma_backend_pulseaudio) {
ma_pa_stream* stream = capture ? device->pulse.pStreamCapture : device->pulse.pStreamPlayback;
const char* name = stream ? ((ma_pa_stream_get_device_name_proc)device->pContext->pulse.pa_stream_get_device_name)(stream) : NULL;
if (name && *name) {
uint64_t hash = UINT64_C(14695981039346656037);
for (; *name; ++name) { hash ^= (unsigned char)*name; hash *= UINT64_C(1099511628211); }
return hash;
}
}
#else
(void)device; (void)capture;
#endif
return 0;
}
/* Reporter копирует сообщение в bounded очередь; DEBUG/file I/O в аппаратном потоке не выполняются. */
void audio_stream_log_route(ma_device* device, int capture) {
#ifdef MA_SUPPORT_PULSEAUDIO
if (device && device->pContext->backend == ma_backend_pulseaudio) {
ma_pa_stream* stream = capture ? device->pulse.pStreamCapture : device->pulse.pStreamPlayback;
const char* name = stream ? ((ma_pa_stream_get_device_name_proc)device->pContext->pulse.pa_stream_get_device_name)(stream) : NULL;
if (name) {
char message[320];
snprintf(message, sizeof(message), "PulseAudio physical %s route: %.255s", capture ? "capture" : "playback", name);
audio_diag_log(ma_device_get_log(device), MA_LOG_LEVEL_INFO, message);
}
}
#else
(void)device; (void)capture;
#endif
}
#ifdef __linux__
/* Один backend dispatch может породить несколько client callbacks при конвертации.
* Начало snapshot фиксируется один раз; offset растёт по фактически выданному client PCM. */
static _Thread_local struct {
ma_device* device;
int valid;
int64_t first_us;
uint64_t frames;
uint64_t hole_offset;
int missing, hole_pending;
} audio_timing;
static void audio_timing_begin(void* opaque, unsigned int frames, int capture) {
ma_device* device = opaque;
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
int valid = 0;
int64_t latency = audio_stream_latency_us(device, capture, &valid);
ma_uint32 rate = capture ? device->capture.internalSampleRate : device->playback.internalSampleRate;
ma_uint64 converter_delay = capture ? ma_data_converter_get_output_latency(&device->capture.converter)
: ma_data_converter_get_input_latency(&device->playback.converter);
audio_timing.device = device;
audio_timing.missing = audio_timing.hole_pending;
audio_timing.valid = valid && device->noFixedSizedCallback;
int64_t time = now.tv_sec * INT64_C(1000000) + now.tv_nsec / 1000;
if (capture) {
time -= valid ? latency : (int64_t)frames * 1000000 / rate;
time -= (int64_t)converter_delay * 1000000 / device->sampleRate;
if (audio_timing.missing) time += (int64_t)audio_timing.hole_offset * 1000000 / rate;
} else {
time += latency + (int64_t)(converter_delay + device->playback.inputCacheRemaining) * 1000000 / device->sampleRate;
}
audio_timing.first_us = time; audio_timing.frames = 0;
audio_timing.hole_pending = 0;
}
static void audio_timing_end(void) { audio_timing.device = NULL; }
static void audio_capture_hole(uint64_t offset) { audio_timing.hole_pending = 1; audio_timing.hole_offset = offset; }
#endif
int64_t audio_stream_frame_time(ma_device* device, unsigned int frames, int* valid) {
*valid = 0;
#ifdef __linux__
if (audio_timing.device == device) {
int64_t time = audio_timing.first_us + (int64_t)audio_timing.frames * 1000000 / device->sampleRate;
audio_timing.frames += frames; *valid = audio_timing.valid;
return time;
}
#else
(void)device; (void)frames;
#endif
return 0;
}
int audio_stream_capture_missing(ma_device* device) {
#ifdef __linux__
return audio_timing.device == device && audio_timing.missing;
#else
(void)device; return 0;
#endif
}