Reject malformed audio diagnostic snapshots
This commit is contained in:
1 parent
9fa58b4f83
commit
dafd3a2adc
3 files changed
+83
-14
No files matched your search
@@ -5,7 +5,7 @@
|
||||
#include <vector>
|
||||
|
||||
int main() {
|
||||
debug_config_init(); debug_enable_console(0);
|
||||
debug_config_init(); debug_enable_console(1); debug_set_category_level(DEBUG_CATEGORY_CALL, DEBUG_LEVEL_DEBUG);
|
||||
constexpr int producers = 4, perProducer = 20000;
|
||||
std::vector<bool> seen(producers * perProducer);
|
||||
std::atomic<int> done{0};
|
||||
@@ -19,6 +19,22 @@ int main() {
|
||||
assert(take(e)); assert(e.value == int64_t(i) && e.detail == 7);
|
||||
}
|
||||
assert(!take(e)); lost = 0;
|
||||
// Повреждённый вызов не должен выдавать прежний снимок слота за новые часы.
|
||||
audio_diag_capture_clock(nullptr, nullptr);
|
||||
audio_diag_playback_clock(nullptr, nullptr);
|
||||
audio_diag_wasapi_playback(nullptr, nullptr);
|
||||
audio_diag_event(nullptr, AUDIO_DIAG_CAPTURE_CLOCK, AUDIO_DIAG_READ, 1, 0);
|
||||
audio_diag_event(nullptr, AUDIO_DIAG_ENTER, AUDIO_DIAG_STAGE_COUNT, 1, 0);
|
||||
audio_diag_event(nullptr, -1, AUDIO_DIAG_READ, 1, 0);
|
||||
std::map<uintptr_t, Device> invalidDevices;
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
assert(take(e)); assert(e.kind == AUDIO_DIAG_ERROR && e.value == 1 && e.text[0]);
|
||||
consume(e, invalidDevices);
|
||||
}
|
||||
assert(!take(e));
|
||||
assert(invalidDevices[0].faults[AUDIO_DIAG_ERROR] == 6);
|
||||
assert(!invalidDevices[0].captureBlocks && !invalidDevices[0].playbackBlocks && !invalidDevices[0].wasapiSeen);
|
||||
report(invalidDevices, wallNow(), 1.0);
|
||||
// Граница секундного отчёта не обрывает карту времени; hole отделён от скачка оценки latency.
|
||||
std::map<uintptr_t, Device> devices;
|
||||
audio_capture_timing timing{1000000, 100000, 0, 480, 48000, 1, 0};
|
||||
@@ -56,6 +72,23 @@ int main() {
|
||||
assert(capture.playbackLatency.average() == 14500 && capture.playbackBlock.low == 20000);
|
||||
report(devices, wallNow(), 1.0);
|
||||
assert(capture.playbackBlocks == 0 && capture.playbackLatency.count == 0 && capture.playbackBlock.high == 0);
|
||||
// Снимок принадлежит producer только до возврата; exclusive padding не является измеренной очередью.
|
||||
audio_wasapi_timing wasapi{};
|
||||
wasapi.frames = 480; wasapi.rate = 48000; wasapi.device_rate = 96000;
|
||||
wasapi.shared = 1; wasapi.padding_frames = 960; wasapi.stream_latency_us = 5000; wasapi.converter_us = 1000;
|
||||
audio_diag_wasapi_playback(nullptr, &wasapi);
|
||||
wasapi.shared = 0; wasapi.padding_frames = 96000; wasapi.stream_latency_us = 8000;
|
||||
assert(take(e)); consume(e, devices);
|
||||
assert(capture.wasapiPadding.count == 1 && capture.wasapiPadding.high == 10000);
|
||||
assert(capture.wasapiLatency.high == 5000 && capture.wasapi.shared == 1);
|
||||
audio_diag_wasapi_playback(nullptr, &wasapi);
|
||||
wasapi.padding_result = -1; wasapi.latency_result = -1;
|
||||
audio_diag_wasapi_playback(nullptr, &wasapi);
|
||||
while (take(e)) consume(e, devices);
|
||||
assert(capture.wasapiBlocks == 3 && capture.wasapiFailures == 1 && capture.wasapiPadding.count == 1);
|
||||
assert(capture.wasapiLatency.count == 2 && capture.wasapiLatency.average() == 6500);
|
||||
report(devices, wallNow(), 1.0);
|
||||
assert(!capture.wasapiBlocks && !capture.wasapiFailures && !capture.wasapiPadding.count);
|
||||
std::vector<std::thread> workers;
|
||||
for (int p = 0; p < producers; ++p) workers.emplace_back([p, &done]() {
|
||||
for (int i = 0; i < perProducer; ++i)
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
#include <fstream>
|
||||
#include <time.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <unistd.h>
|
||||
#include "../../../lib/platform_compat.h"
|
||||
#endif
|
||||
|
||||
extern "C" {
|
||||
@@ -253,10 +253,11 @@ void consume(const Event& e, std::map<uintptr_t, Device>& devices) {
|
||||
// Первая аномалия за окно, остальные попадут в сводку.
|
||||
if (d.faults[e.kind] == uint64_t(std::max<int64_t>(e.value, 1)))
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CALL,
|
||||
"AudioDiag: fault dev=%p role=%s id=%016llx kind=%s stage=%s value=%lld detail=%lld tid=%ld mono_ms=%llu",
|
||||
"AudioDiag: fault dev=%p role=%s id=%016llx kind=%s stage=%s value=%lld detail=%lld tid=%ld mono_ms=%llu%s%s",
|
||||
(void*)e.device, d.label.c_str(), (unsigned long long)d.id, kFaults[e.kind - AUDIO_DIAG_HOLE],
|
||||
e.stage >= 0 && e.stage < AUDIO_DIAG_STAGE_COUNT ? kStages[e.stage] : "none",
|
||||
(long long)e.value, (long long)e.detail, e.tid, (unsigned long long)(e.wall / 1000000));
|
||||
(long long)e.value, (long long)e.detail, e.tid, (unsigned long long)(e.wall / 1000000),
|
||||
e.text[0] ? " reason=" : "", e.text);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -388,6 +389,14 @@ void audio_diag_stop(void) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CALL, "AudioDiag: stopped");
|
||||
}
|
||||
void audio_diag_event(const void* device, int kind, int stage, int64_t value, int64_t detail) {
|
||||
if (kind < AUDIO_DIAG_ENTER || kind > AUDIO_DIAG_ERROR) {
|
||||
publish(device, AUDIO_DIAG_ERROR, stage, 1, kind, "invalid generic event kind");
|
||||
return;
|
||||
}
|
||||
if ((kind == AUDIO_DIAG_ENTER || kind == AUDIO_DIAG_LEAVE) && (stage < 0 || stage >= AUDIO_DIAG_STAGE_COUNT)) {
|
||||
publish(device, AUDIO_DIAG_ERROR, -1, 1, stage, "invalid stage for enter/leave");
|
||||
return;
|
||||
}
|
||||
publish(device, kind, stage, value, detail, nullptr);
|
||||
}
|
||||
void audio_diag_label(const void* device, uint64_t id, const char* name) {
|
||||
@@ -397,12 +406,15 @@ void audio_diag_log(const void* log, unsigned int level, const char* message) {
|
||||
publish(log, AUDIO_DIAG_LOG, -1, level, 0, message);
|
||||
}
|
||||
void audio_diag_capture_clock(const void* device, const audio_capture_timing* timing) {
|
||||
if (!timing) { publish(device, AUDIO_DIAG_ERROR, AUDIO_DIAG_READ, 1, 0, "NULL capture clock snapshot"); return; }
|
||||
publish(device, AUDIO_DIAG_CAPTURE_CLOCK, AUDIO_DIAG_READ, 0, 0, nullptr, timing);
|
||||
}
|
||||
void audio_diag_playback_clock(const void* device, const audio_playback_timing* timing) {
|
||||
if (!timing) { publish(device, AUDIO_DIAG_ERROR, AUDIO_DIAG_WRITE, 1, 0, "NULL playback clock snapshot"); return; }
|
||||
publish(device, AUDIO_DIAG_PLAYBACK_CLOCK, AUDIO_DIAG_WRITE, 0, 0, nullptr, nullptr, timing);
|
||||
}
|
||||
void audio_diag_wasapi_playback(const void* device, const audio_wasapi_timing* timing) {
|
||||
if (!timing) { publish(device, AUDIO_DIAG_ERROR, AUDIO_DIAG_WRITE, 1, 0, "NULL WASAPI snapshot"); return; }
|
||||
publish(device, AUDIO_DIAG_WASAPI_PLAYBACK, AUDIO_DIAG_WRITE, 0, 0, nullptr, nullptr, nullptr, timing);
|
||||
}
|
||||
#else
|
||||
|
||||
@@ -2,8 +2,15 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* Desktop-аудиодиагностика: callbacks передают числовые снимки в ограниченную очередь.
|
||||
* Отдельный reporter пишет сводки в категорию call; start/stop принадлежат владельцу устройств. */
|
||||
/* Desktop-аудиодиагностика Linux/Windows; на других платформах API ничего не делает.
|
||||
* После start несколько producers передают метаданные в MPSC-кольцо на 8192 события.
|
||||
* Публикация без allocation/логирования/ожидания, не более четырёх попыток резервирования;
|
||||
* перегрузка/конкуренция дают потерю события, которую reporter отмечает в сводке.
|
||||
* Отдельный поток опрашивает очередь каждые 20 мс, пишет секундные сводки в call:
|
||||
* штатные стадии TRACE, часы DEBUG, аномалии WARN (первая каждого типа за интервал + счётчики).
|
||||
* Указатели device/log — только числовые ключи, могут быть NULL; reporter их не разыменовывает.
|
||||
* Строки копируются до 191 байта плюс NUL, timing копируется целиком до возврата вызова.
|
||||
* PCM и содержимое сообщений не принимаются. До start/после stop события игнорируются. */
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@@ -22,15 +29,18 @@ enum audio_diag_kind {
|
||||
};
|
||||
|
||||
struct audio_capture_timing {
|
||||
int64_t first_us, latency_us, converter_us;
|
||||
unsigned frames, rate;
|
||||
int measured, missing;
|
||||
int64_t first_us; // оценка monotonic-времени первого кадра блока, микросекунды
|
||||
int64_t latency_us; // знаковая backend latency, учитывается только при measured!=0
|
||||
int64_t converter_us; // задержка converter/cache, микросекунды
|
||||
unsigned frames, rate; // размер блока и его частота, Гц; rate=0 исключает расчёт длительности
|
||||
int measured; // backend-оценка доступна; 0 означает оценочный блок без измеренной latency
|
||||
int missing; // блок с hole: в missing_frames попадают все frames этого блока
|
||||
};
|
||||
|
||||
struct audio_playback_timing {
|
||||
int64_t latency_us, converter_us;
|
||||
unsigned frames, rate;
|
||||
int measured;
|
||||
int64_t latency_us, converter_us; // backend latency и задержка converter/cache, микросекунды
|
||||
unsigned frames, rate; // размер блока и частота, Гц; rate=0 исключает расчёт длительности
|
||||
int measured; // ненулевой: latency_us участвует в диапазоне; отрицательная latency допустима
|
||||
};
|
||||
|
||||
/* Снимок перед заполнением client PCM. Padding — текущая очередь, stream_latency — отдельное
|
||||
@@ -44,15 +54,29 @@ struct audio_wasapi_timing {
|
||||
int32_t padding_result, latency_result; // HRESULT каждого запроса; отрицательное значение — ошибка.
|
||||
};
|
||||
|
||||
/* start/stop — управляющий поток; перед stop все producers должны быть остановлены. */
|
||||
/* Один управляющий владелец сериализует start/stop. Повторный start активного reporter — no-op;
|
||||
* отказ создания потока логируется, диагностика остаётся выключенной (возврата статуса нет).
|
||||
* Перед stop остановить и дождаться всех producers, включая незавершённые публикации.
|
||||
* stop дренирует очередь, пишет последнюю сводку и join; повторный stop допустим.
|
||||
* Следующий start очищает статистику и очередь. Не вызывать stop из reporter. */
|
||||
void audio_diag_start(void);
|
||||
void audio_diag_stop(void);
|
||||
/* kind — ENTER..ERROR; LABEL/LOG/CLOCK передавать специализированными функциями.
|
||||
* ENTER/LEAVE требуют stage=0..STAGE_COUNT-1; остальные события могут использовать -1.
|
||||
* value/detail — backend-метрики: ENTER value=frames, OPEN value=тип/detail=частота;
|
||||
* для аномалий value даёт max(value,1) в счётчик. Неверные kind/stage дают ERROR-событие. */
|
||||
void audio_diag_event(const void* device, int kind, int stage, int64_t value, int64_t detail);
|
||||
/* Метка ресурса; id без интерпретации, NULL name означает пустую строку. */
|
||||
void audio_diag_label(const void* device, uint64_t id, const char* name);
|
||||
/* miniaudio level 1/2/3/остальные -> ERROR/WARN/DEBUG/TRACE; NULL message — пустая строка.
|
||||
* Строки с Hole. подавляются: число hole-кадров собирает отдельное событие. */
|
||||
void audio_diag_log(const void* log, unsigned int level, const char* message);
|
||||
/* Снимки из потока устройства; timing обязателен, NULL даёт ERROR-событие без снимка. */
|
||||
void audio_diag_capture_clock(const void* device, const struct audio_capture_timing* timing);
|
||||
void audio_diag_playback_clock(const void* device, const struct audio_playback_timing* timing);
|
||||
/* Вызывать из playback callback; снимок копируется, устройством reporter не пользуется. */
|
||||
/* Снимок перед заполнением playback buffer; timing обязателен, NULL даёт ERROR-событие.
|
||||
* Только shared padding с успешным HRESULT и device_rate>0 даёт оценку очереди;
|
||||
* stream latency учитывается отдельно, даже в exclusive mode. */
|
||||
void audio_diag_wasapi_playback(const void* device, const struct audio_wasapi_timing* timing);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
Reference in new issue
Block a user