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.
 
 
 
 
 
 

230 lines
12 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);
}
static double audio_buffer_ms(ma_uint32 bytes, ma_uint32 bytes_per_second) {
return bytes == (ma_uint32)-1 ? -1.0 : (double)bytes * 1000 / bytes_per_second;
}
/* После init, до запуска worker: размеры PA относятся к внутреннему PCM, не к client PCM. */
static void audio_diag_pulse_buffer(ma_device* device, int capture) {
ma_pa_stream* stream = capture ? device->pulse.pStreamCapture : device->pulse.pStreamPlayback;
if (!stream) return;
const ma_pa_buffer_attr* attr = ((ma_pa_stream_get_buffer_attr_proc)device->pContext->pulse.pa_stream_get_buffer_attr)(stream);
ma_format format = capture ? device->capture.internalFormat : device->playback.internalFormat;
ma_uint32 channels = capture ? device->capture.internalChannels : device->playback.internalChannels;
ma_uint32 rate = capture ? device->capture.internalSampleRate : device->playback.internalSampleRate;
ma_uint32 bpf = ma_get_bytes_per_frame(format, channels);
if (!attr || !rate || !bpf) {
audio_diag_log(ma_device_get_log(device), MA_LOG_LEVEL_WARNING, "PulseAudio buffer snapshot unavailable");
return;
}
ma_log_postf(ma_device_get_log(device), MA_LOG_LEVEL_INFO,
"[PulseAudio] dev=%p %s PCM format=%s channels=%u rate=%u bpf=%u\n",
(void*)device, capture ? "capture" : "playback", ma_get_format_name(format), channels, rate, bpf);
ma_log_postf(ma_device_get_log(device), MA_LOG_LEVEL_INFO,
"[PulseAudio] dev=%p %s buffer_ms tlength=%.3f fragsize=%.3f minreq=%.3f maxlength=%.3f prebuf=%.3f (-1=default)\n",
(void*)device, capture ? "capture" : "playback", audio_buffer_ms(attr->tlength, rate * bpf),
audio_buffer_ms(attr->fragsize, rate * bpf), audio_buffer_ms(attr->minreq, rate * bpf),
audio_buffer_ms(attr->maxlength, rate * bpf), audio_buffer_ms(attr->prebuf, rate * bpf));
}
#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) return;
audio_diag_pulse_buffer(dev, 1);
audio_diag_pulse_buffer(dev, 0);
if (!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;
if (capture) {
struct audio_capture_timing timing = {time, latency, (int64_t)converter_delay * 1000000 / device->sampleRate,
frames, rate, audio_timing.valid, audio_timing.missing};
audio_diag_capture_clock(device, &timing);
} else {
struct audio_playback_timing timing = {latency,
(int64_t)(converter_delay + device->playback.inputCacheRemaining) * 1000000 / device->sampleRate,
frames, rate, audio_timing.valid};
audio_diag_playback_clock(device, &timing);
}
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
}