Сохрани накопленные исправления аудио, async и диагностики
This commit is contained in:
1 parent
b1e3193899
commit
bdcf8b5634
59 files changed
+4611
-1441
No files matched your search
+103
-33
@@ -52,15 +52,23 @@ static void on_get_trampoline(void* arg);
|
||||
static void empty_trampoline(void* arg) {
|
||||
struct ll_queue* q = (struct ll_queue*)arg;
|
||||
q->empty_call_soon_id = NULL;
|
||||
if (q->count != 0) return;
|
||||
queue_callback_fn cb = q->empty_callback;
|
||||
void* cb_arg = q->empty_callback_arg;
|
||||
q->empty_callback = NULL;
|
||||
q->empty_callback_arg = NULL;
|
||||
if (q->count == 0 && cb) cb(q, cb_arg);
|
||||
if (cb) cb(q, cb_arg);
|
||||
}
|
||||
|
||||
static void on_get_trampoline(void* arg) {
|
||||
struct ll_queue* q = (struct ll_queue*)arg;
|
||||
q->on_get_call_soon_id = NULL;
|
||||
if (!q->on_get_pending || !q->on_get) return;
|
||||
q->on_get_pending--;
|
||||
if (q->on_get_pending) {
|
||||
q->on_get_call_soon_id = uasync_call_soon(q->ua, q, on_get_trampoline);
|
||||
if (!q->on_get_call_soon_id) DEBUG_ERROR(DEBUG_CATEGORY_SYS, "[%s] on_get scheduling failed: pending=%zu", q->name, q->on_get_pending);
|
||||
}
|
||||
if (q->on_get) q->on_get(q, q->on_get_arg);
|
||||
}
|
||||
|
||||
@@ -130,6 +138,7 @@ void queue_free(struct ll_queue* q) {
|
||||
// ВАЖНО: Не освобождаем элементы в очереди - они должны быть извлечены отдельно
|
||||
// Это упрощает архитектуру и предотвращает double-u_free
|
||||
|
||||
while (q->deferred_waiters) queue_waiter_cancel(q, q->deferred_waiters);
|
||||
// Освободить список waiters (сбросить handle->internal, отменить отложенные вызовы)
|
||||
struct queue_waiter* w = q->waiter_head;
|
||||
while (w) {
|
||||
@@ -159,6 +168,10 @@ void queue_free(struct ll_queue* q) {
|
||||
q->empty_callback_arg = NULL;
|
||||
q->on_get = NULL;
|
||||
q->on_get_arg = NULL;
|
||||
if (q->on_get_call_soon_id) uasync_call_soon_cancel(q->ua, q->on_get_call_soon_id);
|
||||
q->on_get_call_soon_id = NULL; q->on_get_pending = 0;
|
||||
if (q->head) DEBUG_WARN(DEBUG_CATEGORY_SYS, "[%s] freeing nonempty queue: count=%d; entries remain caller-owned", q->name, q->count);
|
||||
for (struct ll_entry* e = q->head; e; e = e->next) e->owner = NULL;
|
||||
|
||||
u_free(q);
|
||||
}
|
||||
@@ -316,22 +329,42 @@ static uint32_t make_hash(const void* data, uint16_t len) {// алгоритм F
|
||||
return hash;
|
||||
}
|
||||
|
||||
static void waiter_defer_cb(void* arg) {
|
||||
struct queue_waiter_handle* h = (struct queue_waiter_handle*)arg;
|
||||
h->call_soon_id = NULL;
|
||||
if (h->defer_cb) h->defer_cb(h->defer_q, h->defer_arg);
|
||||
else DEBUG_ERROR(DEBUG_CATEGORY_SYS, "waiter_defer_cb: NULL callback (stale?) q=%p", (void*)h->defer_q);
|
||||
static void unlink_deferred_waiter(struct ll_queue* q, struct queue_waiter_handle* h) {
|
||||
struct queue_waiter_handle** p = &q->deferred_waiters;
|
||||
while (*p && *p != h) p = &(*p)->defer_next;
|
||||
if (*p) *p = h->defer_next;
|
||||
else DEBUG_ERROR(DEBUG_CATEGORY_SYS, "[%s] deferred waiter not found: handle=%p", q->name, h);
|
||||
h->defer_next = NULL; h->defer_q = NULL;
|
||||
}
|
||||
|
||||
static inline void queue_waiter_call(struct ll_queue* q, queue_threshold_callback_fn cb, void* arg,
|
||||
static void waiter_defer_cb(void* arg) {
|
||||
struct queue_waiter_handle* h = (struct queue_waiter_handle*)arg;
|
||||
struct ll_queue* q = h->defer_q;
|
||||
queue_threshold_callback_fn cb = h->defer_cb;
|
||||
void* cb_arg = h->defer_arg;
|
||||
h->call_soon_id = NULL;
|
||||
unlink_deferred_waiter(q, h);
|
||||
h->defer_cb = NULL; h->defer_arg = NULL;
|
||||
if (cb) cb(q, cb_arg);
|
||||
else DEBUG_ERROR(DEBUG_CATEGORY_SYS, "[%s] deferred waiter has no callback", q->name);
|
||||
}
|
||||
|
||||
static inline int queue_waiter_call(struct ll_queue* q, queue_threshold_callback_fn cb, void* arg,
|
||||
struct queue_waiter_handle* h) {
|
||||
if (q->waiter_defer) {
|
||||
if (h->call_soon_id) { DEBUG_WARN(DEBUG_CATEGORY_SYS, "queue_waiter_call: overwriting existing call_soon_id=%p", h->call_soon_id); uasync_call_soon_cancel(q->ua, h->call_soon_id); }
|
||||
if (h->call_soon_id) queue_waiter_cancel(h->defer_q, h);
|
||||
h->defer_q = q; h->defer_cb = cb; h->defer_arg = arg;
|
||||
h->call_soon_id = uasync_call_soon(q->ua, h, waiter_defer_cb);
|
||||
if (h->call_soon_id) { h->defer_next = q->deferred_waiters; q->deferred_waiters = h; }
|
||||
else {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "[%s] deferred waiter scheduling failed: handle=%p", q->name, h);
|
||||
h->defer_q = NULL; h->defer_cb = NULL; h->defer_arg = NULL;
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
cb(q, arg);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Проверить и запустить первый ожидающий коллбэк (FIFO / round-robin)
|
||||
@@ -351,13 +384,18 @@ static void check_waiters(struct ll_queue* q) {
|
||||
|
||||
queue_threshold_callback_fn cb = waiter->callback;
|
||||
void* arg = waiter->callback_arg;
|
||||
u_free(waiter);
|
||||
|
||||
if (cb) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SYS, "check_waiters: waking head waiter, count=%d<=%d, bytes=%zu<=%zu",
|
||||
q->count, q->threshold_max_packets, q->total_bytes, q->threshold_max_bytes);
|
||||
queue_waiter_call(q, cb, arg, h);
|
||||
if (queue_waiter_call(q, cb, arg, h) < 0) {
|
||||
// Отказ планирования не теряет ожидание: следующий get/remove повторит попытку.
|
||||
waiter->next = q->waiter_head; q->waiter_head = waiter;
|
||||
if (!q->waiter_tail) q->waiter_tail = waiter;
|
||||
h->internal = waiter;
|
||||
return;
|
||||
}
|
||||
}
|
||||
u_free(waiter);
|
||||
}
|
||||
|
||||
int queue_data_put(struct ll_queue* q, struct ll_entry* entry) {
|
||||
@@ -366,7 +404,7 @@ int queue_data_put(struct ll_queue* q, struct ll_entry* entry) {
|
||||
queue_check_thread(q);
|
||||
#endif
|
||||
|
||||
if (entry->prev || entry->next || q->head == entry || q->tail == entry) {
|
||||
if (entry->owner || entry->prev || entry->next || q->head == entry || q->tail == entry) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "[%s] double put detected! entry=%p prev=%p next=%p len=%u",
|
||||
q->name, (void*)entry, (void*)entry->prev, (void*)entry->next, entry->len);
|
||||
abort();
|
||||
@@ -380,6 +418,7 @@ int queue_data_put(struct ll_queue* q, struct ll_entry* entry) {
|
||||
}
|
||||
|
||||
if (q->size_limit >= 0 && q->count >= q->size_limit) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_SYS, "[%s] queue full: count=%d limit=%d", q->name, q->count, q->size_limit);
|
||||
queue_dgram_free(entry);
|
||||
queue_entry_free(entry);
|
||||
return -1;
|
||||
@@ -391,17 +430,18 @@ int queue_data_put(struct ll_queue* q, struct ll_entry* entry) {
|
||||
if (q->tail) q->tail->next = entry; else q->head = entry;
|
||||
q->tail = entry;
|
||||
|
||||
entry->owner = q;
|
||||
q->count++;
|
||||
entry->int_len = entry->len;
|
||||
q->total_bytes += entry->int_len;
|
||||
|
||||
// НЕ вызываем add_to_hash
|
||||
|
||||
if (q->count == 1) queue_trigger_callback(q);
|
||||
|
||||
#ifdef QUEUE_DEBUG
|
||||
queue_check_consistency(q);
|
||||
#endif
|
||||
// Пользовательский callback может освободить очередь.
|
||||
if (q->count == 1) queue_trigger_callback(q);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -411,7 +451,7 @@ int queue_data_put_with_index(struct ll_queue* q, struct ll_entry* entry) {
|
||||
queue_check_thread(q);
|
||||
#endif
|
||||
|
||||
if (entry->prev || entry->next || q->head == entry || q->tail == entry) {
|
||||
if (entry->owner || entry->prev || entry->next || q->head == entry || q->tail == entry) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "[%s] double put_with_index detected! entry=%p prev=%p next=%p",
|
||||
q->name, (void*)entry, (void*)entry->prev, (void*)entry->next);
|
||||
abort();
|
||||
@@ -427,6 +467,7 @@ int queue_data_put_with_index(struct ll_queue* q, struct ll_entry* entry) {
|
||||
entry->index_hash = make_hash(entry->data + q->index_offset, q->index_size);
|
||||
|
||||
if (q->size_limit >= 0 && q->count >= q->size_limit) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_SYS, "[%s] queue full: count=%d limit=%d", q->name, q->count, q->size_limit);
|
||||
queue_dgram_free(entry);
|
||||
queue_entry_free(entry);
|
||||
return -1;
|
||||
@@ -438,17 +479,18 @@ int queue_data_put_with_index(struct ll_queue* q, struct ll_entry* entry) {
|
||||
if (q->tail) q->tail->next = entry; else q->head = entry;
|
||||
q->tail = entry;
|
||||
|
||||
entry->owner = q;
|
||||
q->count++;
|
||||
entry->int_len = entry->len;
|
||||
q->total_bytes += entry->int_len;
|
||||
|
||||
add_to_hash(q, entry);
|
||||
|
||||
if (q->count == 1) queue_trigger_callback(q);
|
||||
|
||||
#ifdef QUEUE_DEBUG
|
||||
queue_check_consistency(q);
|
||||
#endif
|
||||
// Пользовательский callback может освободить очередь.
|
||||
if (q->count == 1) queue_trigger_callback(q);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -458,7 +500,7 @@ int queue_data_put_first(struct ll_queue* q, struct ll_entry* entry) {
|
||||
queue_check_thread(q);
|
||||
#endif
|
||||
|
||||
if (entry->prev || entry->next || q->head == entry || q->tail == entry) {
|
||||
if (entry->owner || entry->prev || entry->next || q->head == entry || q->tail == entry) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "[%s] double put_first detected! entry=%p prev=%p next=%p",
|
||||
q->name, (void*)entry, (void*)entry->prev, (void*)entry->next);
|
||||
abort();
|
||||
@@ -471,20 +513,25 @@ int queue_data_put_first(struct ll_queue* q, struct ll_entry* entry) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (q->size_limit >= 0 && q->count >= q->size_limit) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_SYS, "[%s] queue full: count=%d limit=%d", q->name, q->count, q->size_limit);
|
||||
queue_dgram_free(entry); queue_entry_free(entry); return -1;
|
||||
}
|
||||
entry->next = q->head;
|
||||
entry->prev = NULL;
|
||||
if (q->head) q->head->prev = entry; else q->tail = entry;
|
||||
q->head = entry;
|
||||
|
||||
entry->owner = q;
|
||||
q->count++;
|
||||
entry->int_len = entry->len;
|
||||
q->total_bytes += entry->int_len;
|
||||
|
||||
if (q->count == 1) queue_trigger_callback(q);
|
||||
|
||||
#ifdef QUEUE_DEBUG
|
||||
queue_check_consistency(q);
|
||||
#endif
|
||||
// Пользовательский callback может освободить очередь.
|
||||
if (q->count == 1) queue_trigger_callback(q);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -495,7 +542,7 @@ int queue_data_put_first_with_index(struct ll_queue* q, struct ll_entry* entry)
|
||||
queue_check_thread(q);
|
||||
#endif
|
||||
|
||||
if (entry->prev || entry->next || q->head == entry || q->tail == entry) {
|
||||
if (entry->owner || entry->prev || entry->next || q->head == entry || q->tail == entry) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "[%s] double put_first_with_index detected! entry=%p prev=%p next=%p",
|
||||
q->name, (void*)entry, (void*)entry->prev, (void*)entry->next);
|
||||
abort();
|
||||
@@ -511,6 +558,7 @@ int queue_data_put_first_with_index(struct ll_queue* q, struct ll_entry* entry)
|
||||
entry->index_hash = make_hash(entry->data + q->index_offset, q->index_size);
|
||||
|
||||
if (q->size_limit >= 0 && q->count >= q->size_limit) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_SYS, "[%s] queue full: count=%d limit=%d", q->name, q->count, q->size_limit);
|
||||
queue_dgram_free(entry);
|
||||
queue_entry_free(entry);
|
||||
return -1;
|
||||
@@ -521,17 +569,18 @@ int queue_data_put_first_with_index(struct ll_queue* q, struct ll_entry* entry)
|
||||
if (q->head) q->head->prev = entry; else q->tail = entry;
|
||||
q->head = entry;
|
||||
|
||||
entry->owner = q;
|
||||
q->count++;
|
||||
entry->int_len = entry->len;
|
||||
q->total_bytes += entry->int_len;
|
||||
|
||||
add_to_hash(q, entry);
|
||||
|
||||
if (q->count == 1) queue_trigger_callback(q);
|
||||
|
||||
#ifdef QUEUE_DEBUG
|
||||
queue_check_consistency(q);
|
||||
#endif
|
||||
// Пользовательский callback может освободить очередь.
|
||||
if (q->count == 1) queue_trigger_callback(q);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -554,6 +603,7 @@ struct ll_entry* queue_data_get(struct ll_queue* q) {
|
||||
|
||||
entry->next = NULL;
|
||||
entry->prev = NULL;
|
||||
entry->owner = NULL;
|
||||
|
||||
remove_from_hash(q, entry);
|
||||
|
||||
@@ -562,18 +612,21 @@ struct ll_entry* queue_data_get(struct ll_queue* q) {
|
||||
// Приостановить коллбэки для предотвращения рекурсии
|
||||
q->callback_suspended = 1;
|
||||
|
||||
// Проверить ожидающие коллбэки
|
||||
check_waiters(q);
|
||||
|
||||
if (q->count == 0 && q->empty_callback && !q->empty_call_soon_id)
|
||||
q->empty_call_soon_id = uasync_call_soon(q->ua, q, (timeout_callback_t)empty_trampoline);
|
||||
|
||||
if (q->on_get)
|
||||
uasync_call_soon(q->ua, q, (timeout_callback_t)on_get_trampoline);
|
||||
if (q->on_get) {
|
||||
q->on_get_pending++;
|
||||
if (!q->on_get_call_soon_id) {
|
||||
q->on_get_call_soon_id = uasync_call_soon(q->ua, q, on_get_trampoline);
|
||||
if (!q->on_get_call_soon_id) DEBUG_ERROR(DEBUG_CATEGORY_SYS, "[%s] on_get scheduling failed: pending=%zu", q->name, q->on_get_pending);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef QUEUE_DEBUG
|
||||
queue_check_consistency(q);// !!!! for debug
|
||||
#endif
|
||||
check_waiters(q);
|
||||
return entry;
|
||||
}
|
||||
|
||||
@@ -672,18 +725,24 @@ void queue_set_on_get(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg) {
|
||||
if (!q) return;
|
||||
q->on_get = cbk_fn;
|
||||
q->on_get_arg = arg;
|
||||
if (!cbk_fn) {
|
||||
if (q->on_get_call_soon_id) uasync_call_soon_cancel(q->ua, q->on_get_call_soon_id);
|
||||
q->on_get_call_soon_id = NULL; q->on_get_pending = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int queue_waiter_wait(struct ll_queue* q, struct queue_waiter_handle* h,
|
||||
queue_threshold_callback_fn callback, void* arg) {
|
||||
if (!q || !h) return -1;
|
||||
if (!q || !h || !callback) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "queue_waiter_wait: invalid arguments q=%p handle=%p callback=%p", q, h, (void*)callback);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (h->internal) queue_waiter_cancel(q, h);
|
||||
if (h->internal || h->call_soon_id) queue_waiter_cancel(h->call_soon_id ? h->defer_q : q, h);
|
||||
|
||||
if (q->count <= q->threshold_max_packets
|
||||
&& (q->threshold_max_bytes == 0 || q->total_bytes <= q->threshold_max_bytes)) {
|
||||
queue_waiter_call(q, callback, arg, h);
|
||||
return 1;
|
||||
return queue_waiter_call(q, callback, arg, h) < 0 ? -1 : 1;
|
||||
}
|
||||
|
||||
struct queue_waiter* waiter = u_malloc(sizeof(struct queue_waiter));
|
||||
@@ -709,7 +768,11 @@ void queue_waiter_cancel(struct ll_queue* q, struct queue_waiter_handle* h) {
|
||||
* queue_waiter_wait по ветке «очередь свободна» планирует call_soon_id,
|
||||
* не регистрируя waiter. Ранний выход по !internal оставлял бы висячий
|
||||
* callback с arg=handle, который после teardown разыменовывал бы freed-память. */
|
||||
if (h->call_soon_id) { uasync_call_soon_cancel(q->ua, h->call_soon_id); h->call_soon_id = NULL; }
|
||||
if (h->call_soon_id) {
|
||||
struct ll_queue* deferred_q = h->defer_q;
|
||||
uasync_call_soon_cancel(deferred_q->ua, h->call_soon_id); h->call_soon_id = NULL;
|
||||
unlink_deferred_waiter(deferred_q, h); h->defer_cb = NULL; h->defer_arg = NULL;
|
||||
}
|
||||
if (!h->internal) return;
|
||||
|
||||
struct queue_waiter* waiter = h->internal;
|
||||
@@ -769,6 +832,10 @@ struct ll_entry* queue_find_next_by_index(struct ll_queue* q, const void* index_
|
||||
|
||||
int queue_remove_data(struct ll_queue* q, struct ll_entry* entry) {
|
||||
if (!q || !entry) return -1;
|
||||
if (entry->owner != q) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_SYS, "[%s] cannot remove foreign entry=%p owner=%p", q->name, entry, entry->owner);
|
||||
return -1;
|
||||
}
|
||||
|
||||
#ifdef QUEUE_THREAD_CHECK
|
||||
queue_check_thread(q);
|
||||
@@ -792,6 +859,7 @@ int queue_remove_data(struct ll_queue* q, struct ll_entry* entry) {
|
||||
|
||||
entry->next = NULL;
|
||||
entry->prev = NULL;
|
||||
entry->owner = NULL;
|
||||
|
||||
remove_from_hash(q, entry);
|
||||
|
||||
@@ -799,6 +867,8 @@ int queue_remove_data(struct ll_queue* q, struct ll_entry* entry) {
|
||||
#ifdef QUEUE_DEBUG
|
||||
queue_check_consistency(q);// !!!! for debug
|
||||
#endif
|
||||
if (q->count == 0 && q->empty_callback && !q->empty_call_soon_id)
|
||||
q->empty_call_soon_id = uasync_call_soon(q->ua, q, empty_trampoline);
|
||||
check_waiters(q);
|
||||
return 0;
|
||||
}
|
||||
+9
-3
@@ -61,6 +61,7 @@ struct queue_waiter_handle;
|
||||
*/
|
||||
struct ll_entry {
|
||||
char* name;
|
||||
struct ll_queue* owner; // Владелец элемента; NULL после извлечения
|
||||
struct ll_entry* next; // Следующий элемент в очереди
|
||||
struct ll_entry* prev; // Предыдущий элемент в очереди
|
||||
uint16_t size; // Размер пользовательского буфера data[] (байт)
|
||||
@@ -123,6 +124,7 @@ struct queue_waiter_handle {
|
||||
queue_threshold_callback_fn defer_cb;
|
||||
void* defer_arg;
|
||||
void* call_soon_id; ///< хендл отложенного вызова (для отмены)
|
||||
struct queue_waiter_handle* defer_next; ///< список отложенных уведомлений очереди
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -146,6 +148,7 @@ struct ll_queue {
|
||||
|
||||
struct queue_waiter* waiter_head; // Голова списка ожидающих
|
||||
struct queue_waiter* waiter_tail; // Хвост списка ожидающих
|
||||
struct queue_waiter_handle* deferred_waiters; // Уже запланированные уведомления
|
||||
int threshold_max_packets; // Общий порог: макс. кол-во элементов (по умолчанию 0)
|
||||
size_t threshold_max_bytes; // Общий порог: макс. объём данных (0 = не проверять)
|
||||
int waiter_defer; // 0=immediate callback, 1=defer via uasync_call_soon
|
||||
@@ -157,6 +160,8 @@ struct ll_queue {
|
||||
|
||||
queue_callback_fn on_get; // коллбэк после каждого queue_data_get (deferred)
|
||||
void* on_get_arg;
|
||||
void* on_get_call_soon_id;
|
||||
size_t on_get_pending;
|
||||
|
||||
struct ll_entry** hash_table; // Хеш-таблица для поиска по id (если hash_size > 0)
|
||||
size_t hash_size; // Размер хеш-таблицы
|
||||
@@ -275,12 +280,13 @@ void queue_set_on_get(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg);
|
||||
/**
|
||||
* @brief Регистрирует ожидание освобождения очереди до общего порога.
|
||||
* @param q очередь
|
||||
* @param h указатель на handle (создаётся как `struct queue_waiter_handle h = {0}` или `h.internal = NULL`)
|
||||
* @param h полностью обнулённый handle: `struct queue_waiter_handle h = {0}`
|
||||
* @param callback функция, вызываемая при достижении порога
|
||||
* @param arg аргумент коллбэка
|
||||
* @return 1 — условие уже выполнено (callback вызван немедленно), 0 — зарегистрирован в списке, -1 — ошибка
|
||||
* @return 1 — условие выполнено (callback вызван или отложен), 0 — ожидает порог, -1 — ошибка
|
||||
*
|
||||
* Если handle уже ожидает (h->internal != NULL) — старый waiter отменяется и создаётся новый.
|
||||
* Если handle уже ожидает или имеет отложенный callback — старое ожидание отменяется.
|
||||
* При queue_free отменяются также уже запланированные уведомления.
|
||||
*/
|
||||
int queue_waiter_wait(struct ll_queue* q, struct queue_waiter_handle* h,
|
||||
queue_threshold_callback_fn callback, void* arg);
|
||||
|
||||
+18
-8
@@ -5,11 +5,12 @@
|
||||
#include "memory_pool.h"
|
||||
#include "mem.h"
|
||||
|
||||
// Layout: [user_data: object_size] [canary: 4 bytes] [alloc_loc: pointer] [counter: 1 byte]
|
||||
// Layout: [user_data] [canary: 4] [alloc_loc: pointer] [free_loc: pointer] [counter: 1]
|
||||
#define POOL_CANARY_OFF (pool->object_size)
|
||||
#define POOL_LOC_OFF (pool->object_size + 4)
|
||||
#define POOL_COUNTER_OFF (pool->object_size + 4 + sizeof(const char*))
|
||||
#define POOL_ALLOC_SIZE (pool->object_size + 4 + sizeof(const char*) + 1)
|
||||
#define POOL_FREE_LOC_OFF (pool->object_size + 4 + sizeof(const char*))
|
||||
#define POOL_COUNTER_OFF (pool->object_size + 4 + 2 * sizeof(const char*))
|
||||
#define POOL_ALLOC_SIZE (pool->object_size + 4 + 2 * sizeof(const char*) + 1)
|
||||
#define POOL_CANARY_VAL 0xDEADBEAF
|
||||
|
||||
static void pool_init_tags(struct memory_pool* pool, void* obj, const char* location)
|
||||
@@ -19,35 +20,44 @@ static void pool_init_tags(struct memory_pool* pool, void* obj, const char* loca
|
||||
/* Метаданные следуют за payload произвольного размера и могут быть невыровненными. */
|
||||
memcpy((uint8_t*)obj + POOL_CANARY_OFF, &canary, sizeof(canary));
|
||||
memcpy((uint8_t*)obj + POOL_LOC_OFF, &location, sizeof(location));
|
||||
const char* free_loc = NULL;
|
||||
memcpy((uint8_t*)obj + POOL_FREE_LOC_OFF, &free_loc, sizeof(free_loc));
|
||||
*counter = 1;
|
||||
}
|
||||
|
||||
static void pool_check_and_clear_tags(struct memory_pool* pool, void* obj, const char* location)
|
||||
{
|
||||
uint32_t canary;
|
||||
const char* loc;
|
||||
const char *loc, *free_loc;
|
||||
memcpy(&canary, (uint8_t*)obj + POOL_CANARY_OFF, sizeof(canary));
|
||||
memcpy(&loc, (uint8_t*)obj + POOL_LOC_OFF, sizeof(loc));
|
||||
memcpy(&free_loc, (uint8_t*)obj + POOL_FREE_LOC_OFF, sizeof(free_loc));
|
||||
uint8_t* counter = (uint8_t*)obj + POOL_COUNTER_OFF;
|
||||
|
||||
if (canary != POOL_CANARY_VAL) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "pool_free BUFFER OVERFLOW pool=%p name=%s sz=%zu allocs=%zu reuse=%zu free=%d",
|
||||
pool, pool->name ? pool->name : "?", pool->object_size, pool->allocations, pool->reuse_count, pool->free_count);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SYS, " obj=%p alloc=%s free=%s canary=0x%08x expected=0x%08x",
|
||||
obj, loc ? loc : "(null)", location, canary, POOL_CANARY_VAL);
|
||||
/* Повреждённый хвост может содержать битые указатели; их нельзя читать как строки. */
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SYS, " obj=%p alloc_loc_ptr=%p free_loc_ptr=%p free_attempt=%s canary=0x%08x expected=0x%08x",
|
||||
obj, (void*)loc, (void*)free_loc, location, canary, POOL_CANARY_VAL);
|
||||
if (pool->object_size)
|
||||
log_dump(DEBUG_LEVEL_ERROR, DEBUG_CATEGORY_SYS, " user_data", obj, pool->object_size);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "pool_free: corruption logged, stopping thread permanently obj=%p", obj);
|
||||
volatile int _halt = 1;
|
||||
while (_halt) {}
|
||||
}
|
||||
if (*counter == 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "pool_free DOUBLE FREE pool=%p name=%s sz=%zu allocs=%zu reuse=%zu obj=%p alloc=%s free=%s",
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "pool_free DOUBLE FREE pool=%p name=%s sz=%zu allocs=%zu reuse=%zu obj=%p",
|
||||
pool, pool->name ? pool->name : "?", pool->object_size, pool->allocations, pool->reuse_count,
|
||||
obj, loc ? loc : "(null)", location);
|
||||
obj);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SYS, " alloc=%s first_free=%s repeated_free=%s",
|
||||
loc ? loc : "(null)", free_loc ? free_loc : "(null)", location);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "pool_free: corruption logged, stopping thread permanently obj=%p", obj);
|
||||
volatile int _halt = 1;
|
||||
while (_halt) {}
|
||||
}
|
||||
*counter = 0;
|
||||
memcpy((uint8_t*)obj + POOL_FREE_LOC_OFF, &location, sizeof(location));
|
||||
}
|
||||
|
||||
static size_t g_total_free = 0;
|
||||
|
||||
+170
-37
@@ -7543,11 +7543,13 @@ struct ma_context
|
||||
ma_proc pa_context_disconnect;
|
||||
ma_proc pa_context_set_state_callback;
|
||||
ma_proc pa_context_get_state;
|
||||
ma_proc pa_context_errno;
|
||||
ma_proc pa_context_get_sink_info_list;
|
||||
ma_proc pa_context_get_source_info_list;
|
||||
ma_proc pa_context_get_sink_info_by_name;
|
||||
ma_proc pa_context_get_source_info_by_name;
|
||||
ma_proc pa_operation_unref;
|
||||
ma_proc pa_operation_cancel;
|
||||
ma_proc pa_operation_get_state;
|
||||
ma_proc pa_channel_map_init_extend;
|
||||
ma_proc pa_channel_map_valid;
|
||||
@@ -7574,6 +7576,7 @@ struct ma_context
|
||||
ma_proc pa_stream_cork;
|
||||
ma_proc pa_stream_trigger;
|
||||
ma_proc pa_stream_begin_write;
|
||||
ma_proc pa_stream_cancel_write;
|
||||
ma_proc pa_stream_write;
|
||||
ma_proc pa_stream_peek;
|
||||
ma_proc pa_stream_drop;
|
||||
@@ -11553,6 +11556,17 @@ IMPLEMENTATION
|
||||
#ifndef miniaudio_c
|
||||
#define miniaudio_c
|
||||
|
||||
/* uTun: необязательные наблюдатели, без изменения ABI и поведения аудиотракта. */
|
||||
#ifndef MA_DIAGNOSTIC_EVENT
|
||||
#define MA_DIAGNOSTIC_EVENT(device, kind, stage, value, detail) ((void)0)
|
||||
#endif
|
||||
#ifndef MA_DIAGNOSTIC_DEVICE_INIT
|
||||
#define MA_DIAGNOSTIC_DEVICE_INIT(device) ((void)0)
|
||||
#endif
|
||||
#ifndef MA_DIAGNOSTIC_LOG
|
||||
#define MA_DIAGNOSTIC_LOG(log, level, message) ((void)0)
|
||||
#endif
|
||||
|
||||
#include <assert.h>
|
||||
#include <limits.h> /* For INT_MAX */
|
||||
#include <math.h> /* sin(), etc. */
|
||||
@@ -13752,6 +13766,7 @@ MA_API ma_result ma_log_post(ma_log* pLog, ma_uint32 level, const char* pMessage
|
||||
return MA_INVALID_ARGS;
|
||||
}
|
||||
|
||||
MA_DIAGNOSTIC_LOG(pLog, level, pMessage);
|
||||
ma_log_lock(pLog);
|
||||
{
|
||||
ma_uint32 iLog;
|
||||
@@ -20200,6 +20215,8 @@ static void ma_device__on_notification(ma_device_notification notification)
|
||||
{
|
||||
MA_ASSERT(notification.pDevice != NULL);
|
||||
|
||||
MA_DIAGNOSTIC_EVENT(notification.pDevice, AUDIO_DIAG_NOTIFY, -1, notification.type, ma_device_get_state(notification.pDevice));
|
||||
|
||||
if (notification.pDevice->onNotification != NULL) {
|
||||
notification.pDevice->onNotification(¬ification);
|
||||
}
|
||||
@@ -20360,6 +20377,7 @@ static void ma_device__handle_data_callback(ma_device* pDevice, void* pFramesOut
|
||||
{
|
||||
float masterVolumeFactor;
|
||||
|
||||
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_ENTER, AUDIO_DIAG_CALLBACK, frameCount, (pFramesIn != NULL) | ((pFramesOut != NULL) << 1));
|
||||
ma_device_get_master_volume(pDevice, &masterVolumeFactor); /* Use ma_device_get_master_volume() to ensure the volume is loaded atomically. */
|
||||
|
||||
if (pDevice->onData) {
|
||||
@@ -20407,6 +20425,7 @@ static void ma_device__handle_data_callback(ma_device* pDevice, void* pFramesOut
|
||||
ma_silence_pcm_frames(pFramesOut, frameCount, pDevice->playback.format, pDevice->playback.channels);
|
||||
}
|
||||
}
|
||||
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_LEAVE, AUDIO_DIAG_CALLBACK, frameCount, 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -20590,6 +20609,7 @@ static ma_result ma_device__handle_duplex_callback_capture(ma_device* pDevice, m
|
||||
|
||||
if (framesToProcessInClientFormat == 0) {
|
||||
if (ma_pcm_rb_pointer_distance(pRB) == (ma_int32)ma_pcm_rb_get_subbuffer_size(pRB)) {
|
||||
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_CAPTURE_DROP, AUDIO_DIAG_READ, framesToProcessInDeviceFormat, ma_pcm_rb_available_read(pRB));
|
||||
break; /* Overrun. Not enough room in the ring buffer for input frame. Excess frames are dropped. */
|
||||
}
|
||||
}
|
||||
@@ -20625,6 +20645,7 @@ static ma_result ma_device__handle_duplex_callback_playback(ma_device* pDevice,
|
||||
ma_result result;
|
||||
ma_uint8 silentInputFrames[MA_DATA_CONVERTER_STACK_BUFFER_SIZE];
|
||||
ma_uint32 totalFramesReadOut = 0;
|
||||
ma_silence_pcm_frames(pFramesInInternalFormat, frameCount, pDevice->playback.internalFormat, pDevice->playback.internalChannels);
|
||||
|
||||
MA_ASSERT(pDevice != NULL);
|
||||
MA_ASSERT(frameCount > 0);
|
||||
@@ -20636,6 +20657,7 @@ static ma_result ma_device__handle_duplex_callback_playback(ma_device* pDevice,
|
||||
Sitting in the ring buffer should be captured data from the capture callback in external format. If there's not enough data in there for
|
||||
the whole frameCount frames we just use silence instead for the input data.
|
||||
*/
|
||||
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_ENTER, AUDIO_DIAG_DUPLEX, frameCount, ma_pcm_rb_available_read(pRB));
|
||||
MA_ZERO_MEMORY(silentInputFrames, sizeof(silentInputFrames));
|
||||
|
||||
while (totalFramesReadOut < frameCount && ma_device_is_started(pDevice)) {
|
||||
@@ -20646,8 +20668,12 @@ static ma_result ma_device__handle_duplex_callback_playback(ma_device* pDevice,
|
||||
if (pDevice->playback.inputCacheRemaining > 0) {
|
||||
ma_uint64 framesConvertedIn = pDevice->playback.inputCacheRemaining;
|
||||
ma_uint64 framesConvertedOut = (frameCount - totalFramesReadOut);
|
||||
ma_data_converter_process_pcm_frames(&pDevice->playback.converter, ma_offset_pcm_frames_ptr(pDevice->playback.pInputCache, pDevice->playback.inputCacheConsumed, pDevice->playback.format, pDevice->playback.channels), &framesConvertedIn, pFramesInInternalFormat, &framesConvertedOut);
|
||||
result = ma_data_converter_process_pcm_frames(&pDevice->playback.converter, ma_offset_pcm_frames_ptr(pDevice->playback.pInputCache, pDevice->playback.inputCacheConsumed, pDevice->playback.format, pDevice->playback.channels), &framesConvertedIn, pFramesInInternalFormat, &framesConvertedOut);
|
||||
|
||||
if (result != MA_SUCCESS || (framesConvertedIn == 0 && framesConvertedOut == 0)) {
|
||||
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_ERROR, AUDIO_DIAG_DUPLEX, 1, result);
|
||||
break;
|
||||
}
|
||||
pDevice->playback.inputCacheConsumed += framesConvertedIn;
|
||||
pDevice->playback.inputCacheRemaining -= framesConvertedIn;
|
||||
|
||||
@@ -20662,30 +20688,34 @@ static ma_result ma_device__handle_duplex_callback_playback(ma_device* pDevice,
|
||||
|
||||
inputFrameCount = (ma_uint32)pDevice->playback.inputCacheCap;
|
||||
result = ma_pcm_rb_acquire_read(pRB, &inputFrameCount, &pInputFrames);
|
||||
if (result == MA_SUCCESS) {
|
||||
if (inputFrameCount > 0) {
|
||||
ma_device__handle_data_callback(pDevice, pDevice->playback.pInputCache, pInputFrames, inputFrameCount);
|
||||
} else {
|
||||
if (ma_pcm_rb_pointer_distance(pRB) == 0) {
|
||||
break; /* Underrun. */
|
||||
}
|
||||
if (result != MA_SUCCESS) {
|
||||
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_ERROR, AUDIO_DIAG_DUPLEX, 1, result);
|
||||
break;
|
||||
}
|
||||
if (inputFrameCount > 0) {
|
||||
ma_device__handle_data_callback(pDevice, pDevice->playback.pInputCache, pInputFrames, inputFrameCount);
|
||||
result = ma_pcm_rb_commit_read(pRB, inputFrameCount);
|
||||
if (result != MA_SUCCESS) {
|
||||
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_ERROR, AUDIO_DIAG_DUPLEX, 1, result);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
/* No capture data available. Feed in silence. */
|
||||
inputFrameCount = (ma_uint32)ma_min(pDevice->playback.inputCacheCap, sizeof(silentInputFrames) / ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels));
|
||||
/* Отсутствие микрофонных кадров не останавливает рендер; синтетический ввод не сдвигает capture-ring. */
|
||||
inputFrameCount = (ma_uint32)ma_min(pDevice->playback.inputCacheCap,
|
||||
sizeof(silentInputFrames) / ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels));
|
||||
ma_device__handle_data_callback(pDevice, pDevice->playback.pInputCache, silentInputFrames, inputFrameCount);
|
||||
}
|
||||
|
||||
pDevice->playback.inputCacheConsumed = 0;
|
||||
pDevice->playback.inputCacheRemaining = inputFrameCount;
|
||||
|
||||
result = ma_pcm_rb_commit_read(pRB, inputFrameCount);
|
||||
if (result != MA_SUCCESS) {
|
||||
return result; /* Should never happen. */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (totalFramesReadOut < frameCount) {
|
||||
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_UNFILLED, AUDIO_DIAG_DUPLEX, frameCount - totalFramesReadOut, ma_pcm_rb_available_read(pRB));
|
||||
}
|
||||
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_LEAVE, AUDIO_DIAG_DUPLEX, totalFramesReadOut, ma_pcm_rb_available_read(pRB));
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -30908,11 +30938,13 @@ typedef int (* ma_pa_context_connect_proc) (
|
||||
typedef void (* ma_pa_context_disconnect_proc) (ma_pa_context* c);
|
||||
typedef void (* ma_pa_context_set_state_callback_proc) (ma_pa_context* c, ma_pa_context_notify_cb_t cb, void* userdata);
|
||||
typedef ma_pa_context_state_t (* ma_pa_context_get_state_proc) (const ma_pa_context* c);
|
||||
typedef int (* ma_pa_context_errno_proc) (const ma_pa_context* c);
|
||||
typedef ma_pa_operation* (* ma_pa_context_get_sink_info_list_proc) (ma_pa_context* c, ma_pa_sink_info_cb_t cb, void* userdata);
|
||||
typedef ma_pa_operation* (* ma_pa_context_get_source_info_list_proc) (ma_pa_context* c, ma_pa_source_info_cb_t cb, void* userdata);
|
||||
typedef ma_pa_operation* (* ma_pa_context_get_sink_info_by_name_proc) (ma_pa_context* c, const char* name, ma_pa_sink_info_cb_t cb, void* userdata);
|
||||
typedef ma_pa_operation* (* ma_pa_context_get_source_info_by_name_proc)(ma_pa_context* c, const char* name, ma_pa_source_info_cb_t cb, void* userdata);
|
||||
typedef void (* ma_pa_operation_unref_proc) (ma_pa_operation* o);
|
||||
typedef void (* ma_pa_operation_cancel_proc) (ma_pa_operation* o);
|
||||
typedef ma_pa_operation_state_t (* ma_pa_operation_get_state_proc) (const ma_pa_operation* o);
|
||||
typedef ma_pa_channel_map* (* ma_pa_channel_map_init_extend_proc) (ma_pa_channel_map* m, unsigned channels, ma_pa_channel_map_def_t def);
|
||||
typedef int (* ma_pa_channel_map_valid_proc) (const ma_pa_channel_map* m);
|
||||
@@ -30939,6 +30971,7 @@ typedef int (* ma_pa_stream_is_corked_proc) (
|
||||
typedef ma_pa_operation* (* ma_pa_stream_cork_proc) (ma_pa_stream* s, int b, ma_pa_stream_success_cb_t cb, void* userdata);
|
||||
typedef ma_pa_operation* (* ma_pa_stream_trigger_proc) (ma_pa_stream* s, ma_pa_stream_success_cb_t cb, void* userdata);
|
||||
typedef int (* ma_pa_stream_begin_write_proc) (ma_pa_stream* s, void** data, size_t* nbytes);
|
||||
typedef int (* ma_pa_stream_cancel_write_proc) (ma_pa_stream* s);
|
||||
typedef int (* ma_pa_stream_write_proc) (ma_pa_stream* s, const void* data, size_t nbytes, ma_pa_free_cb_t free_cb, int64_t offset, ma_pa_seek_mode_t seek);
|
||||
typedef int (* ma_pa_stream_peek_proc) (ma_pa_stream* s, const void** data, size_t* nbytes);
|
||||
typedef int (* ma_pa_stream_drop_proc) (ma_pa_stream* s);
|
||||
@@ -31126,23 +31159,49 @@ static ma_pa_channel_position_t ma_channel_position_to_pulse(ma_channel position
|
||||
}
|
||||
#endif
|
||||
|
||||
static ma_result ma_wait_for_operation__pulse(ma_context* pContext, ma_ptr pMainLoop, ma_pa_operation* pOP)
|
||||
#ifndef MA_PULSE_WAIT_TIMEOUT_MS
|
||||
#define MA_PULSE_WAIT_TIMEOUT_MS 1000
|
||||
#endif
|
||||
|
||||
static ma_result ma_pulse_wait_step(ma_context* pContext, ma_ptr pMainLoop, ma_timer* pTimer, const char* operation)
|
||||
{
|
||||
int resultPA;
|
||||
if (ma_timer_get_time_in_seconds(pTimer) * 1000 >= MA_PULSE_WAIT_TIMEOUT_MS) {
|
||||
ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR,
|
||||
"[PulseAudio] %s timed out after %d ms.\n", operation, MA_PULSE_WAIT_TIMEOUT_MS);
|
||||
return MA_TIMEOUT;
|
||||
}
|
||||
resultPA = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pMainLoop, 0, NULL);
|
||||
if (resultPA < 0) {
|
||||
ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[PulseAudio] %s iterate failed: %d.\n", operation, resultPA);
|
||||
return ma_result_from_pulse(resultPA);
|
||||
}
|
||||
if (resultPA == 0) ma_sleep(1);
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
|
||||
static ma_result ma_wait_for_operation__pulse(ma_context* pContext, ma_ptr pMainLoop, ma_pa_operation* pOP)
|
||||
{
|
||||
ma_result result;
|
||||
ma_timer timer;
|
||||
ma_pa_operation_state_t state;
|
||||
|
||||
MA_ASSERT(pContext != NULL);
|
||||
MA_ASSERT(pOP != NULL);
|
||||
|
||||
ma_timer_init(&timer);
|
||||
for (;;) {
|
||||
state = ((ma_pa_operation_get_state_proc)pContext->pulse.pa_operation_get_state)(pOP);
|
||||
if (state == MA_PA_OPERATION_DONE) return MA_SUCCESS;
|
||||
if (state != MA_PA_OPERATION_RUNNING) {
|
||||
break; /* Done. */
|
||||
ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[PulseAudio] Operation cancelled.\n");
|
||||
return MA_ERROR;
|
||||
}
|
||||
|
||||
resultPA = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pMainLoop, 1, NULL);
|
||||
if (resultPA < 0) {
|
||||
return ma_result_from_pulse(resultPA);
|
||||
result = ma_pulse_wait_step(pContext, pMainLoop, &timer, "wait");
|
||||
if (result != MA_SUCCESS) {
|
||||
((ma_pa_operation_cancel_proc)pContext->pulse.pa_operation_cancel)(pOP);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31165,9 +31224,11 @@ static ma_result ma_wait_for_operation_and_unref__pulse(ma_context* pContext, ma
|
||||
|
||||
static ma_result ma_wait_for_pa_context_to_connect__pulse(ma_context* pContext, ma_ptr pMainLoop, ma_ptr pPulseContext)
|
||||
{
|
||||
int resultPA;
|
||||
ma_result result;
|
||||
ma_timer timer;
|
||||
ma_pa_context_state_t state;
|
||||
|
||||
ma_timer_init(&timer);
|
||||
for (;;) {
|
||||
state = ((ma_pa_context_get_state_proc)pContext->pulse.pa_context_get_state)((ma_pa_context*)pPulseContext);
|
||||
if (state == MA_PA_CONTEXT_READY) {
|
||||
@@ -31179,10 +31240,8 @@ static ma_result ma_wait_for_pa_context_to_connect__pulse(ma_context* pContext,
|
||||
return MA_ERROR;
|
||||
}
|
||||
|
||||
resultPA = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pMainLoop, 1, NULL);
|
||||
if (resultPA < 0) {
|
||||
return ma_result_from_pulse(resultPA);
|
||||
}
|
||||
result = ma_pulse_wait_step(pContext, pMainLoop, &timer, "context connect");
|
||||
if (result != MA_SUCCESS) return result;
|
||||
}
|
||||
|
||||
/* Should never get here. */
|
||||
@@ -31191,9 +31250,11 @@ static ma_result ma_wait_for_pa_context_to_connect__pulse(ma_context* pContext,
|
||||
|
||||
static ma_result ma_wait_for_pa_stream_to_connect__pulse(ma_context* pContext, ma_ptr pMainLoop, ma_ptr pStream)
|
||||
{
|
||||
int resultPA;
|
||||
ma_result result;
|
||||
ma_timer timer;
|
||||
ma_pa_stream_state_t state;
|
||||
|
||||
ma_timer_init(&timer);
|
||||
for (;;) {
|
||||
state = ((ma_pa_stream_get_state_proc)pContext->pulse.pa_stream_get_state)((ma_pa_stream*)pStream);
|
||||
if (state == MA_PA_STREAM_READY) {
|
||||
@@ -31205,10 +31266,8 @@ static ma_result ma_wait_for_pa_stream_to_connect__pulse(ma_context* pContext, m
|
||||
return MA_ERROR;
|
||||
}
|
||||
|
||||
resultPA = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pMainLoop, 1, NULL);
|
||||
if (resultPA < 0) {
|
||||
return ma_result_from_pulse(resultPA);
|
||||
}
|
||||
result = ma_pulse_wait_step(pContext, pMainLoop, &timer, "stream connect");
|
||||
if (result != MA_SUCCESS) return result;
|
||||
}
|
||||
|
||||
return MA_SUCCESS;
|
||||
@@ -31382,10 +31441,13 @@ static ma_result ma_context_get_default_device_index__pulse(ma_context* pContext
|
||||
|
||||
if (deviceType == ma_device_type_playback) {
|
||||
ma_pa_sink_info sinkInfo;
|
||||
MA_ZERO_OBJECT(&sinkInfo);
|
||||
sinkInfo.index = (ma_uint32)-1;
|
||||
result = ma_context_get_sink_info__pulse(pContext, NULL, &sinkInfo);
|
||||
if (result != MA_SUCCESS) {
|
||||
return result;
|
||||
}
|
||||
if (sinkInfo.index == (ma_uint32)-1) return MA_NO_DEVICE;
|
||||
|
||||
if (pIndex != NULL) {
|
||||
*pIndex = sinkInfo.index;
|
||||
@@ -31394,10 +31456,13 @@ static ma_result ma_context_get_default_device_index__pulse(ma_context* pContext
|
||||
|
||||
if (deviceType == ma_device_type_capture) {
|
||||
ma_pa_source_info sourceInfo;
|
||||
MA_ZERO_OBJECT(&sourceInfo);
|
||||
sourceInfo.index = (ma_uint32)-1;
|
||||
result = ma_context_get_source_info__pulse(pContext, NULL, &sourceInfo);
|
||||
if (result != MA_SUCCESS) {
|
||||
return result;
|
||||
}
|
||||
if (sourceInfo.index == (ma_uint32)-1) return MA_NO_DEVICE;
|
||||
|
||||
if (pIndex != NULL) {
|
||||
*pIndex = sourceInfo.index;
|
||||
@@ -31551,7 +31616,7 @@ static void ma_context_get_device_info_sink_callback__pulse(ma_pa_context* pPuls
|
||||
{
|
||||
ma_context_get_device_info_callback_data__pulse* pData = (ma_context_get_device_info_callback_data__pulse*)pUserData;
|
||||
|
||||
if (endOfList > 0) {
|
||||
if (endOfList != 0 || pInfo == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -31588,7 +31653,7 @@ static void ma_context_get_device_info_source_callback__pulse(ma_pa_context* pPu
|
||||
{
|
||||
ma_context_get_device_info_callback_data__pulse* pData = (ma_context_get_device_info_callback_data__pulse*)pUserData;
|
||||
|
||||
if (endOfList > 0) {
|
||||
if (endOfList != 0 || pInfo == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -31640,6 +31705,7 @@ static ma_result ma_context_get_device_info__pulse(ma_context* pContext, ma_devi
|
||||
}
|
||||
|
||||
result = ma_context_get_default_device_index__pulse(pContext, deviceType, &callbackData.defaultDeviceIndex);
|
||||
if (result != MA_SUCCESS) return result;
|
||||
|
||||
if (deviceType == ma_device_type_playback) {
|
||||
pOP = ((ma_pa_context_get_sink_info_by_name_proc)pContext->pulse.pa_context_get_sink_info_by_name)((ma_pa_context*)(pContext->pulse.pPulseContext), pDeviceName, ma_context_get_device_info_sink_callback__pulse, &callbackData);
|
||||
@@ -31648,7 +31714,8 @@ static ma_result ma_context_get_device_info__pulse(ma_context* pContext, ma_devi
|
||||
}
|
||||
|
||||
if (pOP != NULL) {
|
||||
ma_wait_for_operation_and_unref__pulse(pContext, pContext->pulse.pMainLoop, pOP);
|
||||
result = ma_wait_for_operation_and_unref__pulse(pContext, pContext->pulse.pMainLoop, pOP);
|
||||
if (result != MA_SUCCESS) goto done;
|
||||
} else {
|
||||
result = MA_ERROR;
|
||||
goto done;
|
||||
@@ -31747,6 +31814,7 @@ static void ma_device_on_read__pulse(ma_pa_stream* pStream, size_t byteCount, vo
|
||||
MA_ASSERT(bpf > 0);
|
||||
|
||||
frameCount = byteCount / bpf;
|
||||
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_ENTER, AUDIO_DIAG_READ, frameCount, deviceState);
|
||||
framesProcessed = 0;
|
||||
|
||||
while (ma_device_get_state(pDevice) == ma_device_state_started && framesProcessed < frameCount) {
|
||||
@@ -31756,6 +31824,7 @@ static void ma_device_on_read__pulse(ma_pa_stream* pStream, size_t byteCount, vo
|
||||
|
||||
int pulseResult = ((ma_pa_stream_peek_proc)pDevice->pContext->pulse.pa_stream_peek)(pStream, &pMappedPCMFrames, &bytesMapped);
|
||||
if (pulseResult < 0) {
|
||||
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_ERROR, AUDIO_DIAG_READ, 1, pulseResult);
|
||||
break; /* Failed to map. Abort. */
|
||||
}
|
||||
|
||||
@@ -31764,12 +31833,21 @@ static void ma_device_on_read__pulse(ma_pa_stream* pStream, size_t byteCount, vo
|
||||
if (pMappedPCMFrames != NULL) {
|
||||
ma_device_handle_backend_data_callback(pDevice, NULL, pMappedPCMFrames, framesMapped);
|
||||
} else {
|
||||
/* It's a hole. */
|
||||
ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[PulseAudio] ma_device_on_read__pulse: Hole.\n");
|
||||
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_HOLE, AUDIO_DIAG_READ, framesMapped, 0);
|
||||
ma_uint8 silence[MA_DATA_CONVERTER_STACK_BUFFER_SIZE];
|
||||
ma_uint64 remaining = framesMapped;
|
||||
ma_uint32 capacity = sizeof(silence) / bpf;
|
||||
ma_silence_pcm_frames(silence, capacity, pDevice->capture.internalFormat, pDevice->capture.internalChannels);
|
||||
while (remaining > 0 && ma_device_get_state(pDevice) == ma_device_state_started) {
|
||||
ma_uint32 n = (ma_uint32)ma_min(remaining, capacity);
|
||||
ma_device_handle_backend_data_callback(pDevice, NULL, silence, n);
|
||||
remaining -= n;
|
||||
}
|
||||
}
|
||||
|
||||
pulseResult = ((ma_pa_stream_drop_proc)pDevice->pContext->pulse.pa_stream_drop)(pStream);
|
||||
if (pulseResult < 0) {
|
||||
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_ERROR, AUDIO_DIAG_READ, 1, pulseResult);
|
||||
break; /* Failed to drop the buffer. */
|
||||
}
|
||||
|
||||
@@ -31780,6 +31858,7 @@ static void ma_device_on_read__pulse(ma_pa_stream* pStream, size_t byteCount, vo
|
||||
break;
|
||||
}
|
||||
}
|
||||
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_LEAVE, AUDIO_DIAG_READ, framesProcessed, 0);
|
||||
}
|
||||
|
||||
static ma_result ma_device_write_to_stream__pulse(ma_device* pDevice, ma_pa_stream* pStream, ma_uint64* pFramesProcessed)
|
||||
@@ -31798,6 +31877,7 @@ static ma_result ma_device_write_to_stream__pulse(ma_device* pDevice, ma_pa_stre
|
||||
|
||||
deviceState = ma_device_get_state(pDevice);
|
||||
|
||||
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_ENTER, AUDIO_DIAG_MAP, 0, deviceState);
|
||||
bytesMapped = ((ma_pa_stream_writable_size_proc)pDevice->pContext->pulse.pa_stream_writable_size)(pStream);
|
||||
if (bytesMapped != (size_t)-1) {
|
||||
if (bytesMapped > 0) {
|
||||
@@ -31810,15 +31890,23 @@ static ma_result ma_device_write_to_stream__pulse(ma_device* pDevice, ma_pa_stre
|
||||
}
|
||||
|
||||
framesMapped = bytesMapped / bpf;
|
||||
if (framesMapped == 0 || pMappedPCMFrames == NULL) {
|
||||
((ma_pa_stream_cancel_write_proc)pDevice->pContext->pulse.pa_stream_cancel_write)(pStream);
|
||||
result = MA_ERROR;
|
||||
goto done;
|
||||
}
|
||||
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_LEAVE, AUDIO_DIAG_MAP, framesMapped, 0);
|
||||
|
||||
if (deviceState == ma_device_state_started || deviceState == ma_device_state_starting) { /* Check for starting state just in case this is being used to do the initial fill. */
|
||||
ma_device_handle_backend_data_callback(pDevice, pMappedPCMFrames, NULL, framesMapped);
|
||||
} else {
|
||||
/* Device is not started. Write silence. */
|
||||
ma_silence_pcm_frames(pMappedPCMFrames, framesMapped, pDevice->playback.format, pDevice->playback.channels);
|
||||
ma_silence_pcm_frames(pMappedPCMFrames, framesMapped, pDevice->playback.internalFormat, pDevice->playback.internalChannels);
|
||||
}
|
||||
|
||||
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_ENTER, AUDIO_DIAG_SUBMIT, framesMapped, 0);
|
||||
pulseResult = ((ma_pa_stream_write_proc)pDevice->pContext->pulse.pa_stream_write)(pStream, pMappedPCMFrames, bytesMapped, NULL, 0, MA_PA_SEEK_RELATIVE);
|
||||
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_LEAVE, AUDIO_DIAG_SUBMIT, framesMapped, pulseResult);
|
||||
if (pulseResult < 0) {
|
||||
result = ma_result_from_pulse(pulseResult);
|
||||
goto done; /* Failed to write data to stream. */
|
||||
@@ -31835,6 +31923,10 @@ static ma_result ma_device_write_to_stream__pulse(ma_device* pDevice, ma_pa_stre
|
||||
}
|
||||
|
||||
done:
|
||||
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_LEAVE, AUDIO_DIAG_MAP, framesProcessed, result);
|
||||
if (result != MA_SUCCESS) {
|
||||
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_ERROR, AUDIO_DIAG_WRITE, 1, result);
|
||||
}
|
||||
if (pFramesProcessed != NULL) {
|
||||
*pFramesProcessed = framesProcessed;
|
||||
}
|
||||
@@ -31850,6 +31942,7 @@ static void ma_device_on_write__pulse(ma_pa_stream* pStream, size_t byteCount, v
|
||||
ma_uint64 framesProcessed;
|
||||
ma_uint32 deviceState;
|
||||
ma_result result;
|
||||
ma_bool32 diagnosticNoProgress = MA_FALSE;
|
||||
|
||||
MA_ASSERT(pDevice != NULL);
|
||||
|
||||
@@ -31866,6 +31959,7 @@ static void ma_device_on_write__pulse(ma_pa_stream* pStream, size_t byteCount, v
|
||||
MA_ASSERT(bpf > 0);
|
||||
|
||||
frameCount = byteCount / bpf;
|
||||
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_ENTER, AUDIO_DIAG_WRITE, frameCount, deviceState);
|
||||
framesProcessed = 0;
|
||||
|
||||
while (framesProcessed < frameCount) {
|
||||
@@ -31882,8 +31976,14 @@ static void ma_device_on_write__pulse(ma_pa_stream* pStream, size_t byteCount, v
|
||||
break;
|
||||
}
|
||||
|
||||
if (framesProcessedThisIteration == 0 && !diagnosticNoProgress) {
|
||||
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_NO_PROGRESS, AUDIO_DIAG_WRITE, 1, frameCount - framesProcessed);
|
||||
diagnosticNoProgress = MA_TRUE;
|
||||
}
|
||||
if (framesProcessedThisIteration == 0) break;
|
||||
framesProcessed += framesProcessedThisIteration;
|
||||
}
|
||||
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_LEAVE, AUDIO_DIAG_WRITE, framesProcessed, 0);
|
||||
}
|
||||
|
||||
static void ma_device_on_suspended__pulse(ma_pa_stream* pStream, void* pUserData)
|
||||
@@ -31902,10 +32002,10 @@ static void ma_device_on_suspended__pulse(ma_pa_stream* pStream, void* pUserData
|
||||
|
||||
if (suspended == 1) {
|
||||
ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[Pulse] Device suspended state changed. Suspended.\n");
|
||||
ma_device__on_notification_stopped(pDevice);
|
||||
ma_device__on_notification(ma_device_notification_init(pDevice, ma_device_notification_type_interruption_began));
|
||||
} else {
|
||||
ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[Pulse] Device suspended state changed. Resumed.\n");
|
||||
ma_device__on_notification_started(pDevice);
|
||||
ma_device__on_notification(ma_device_notification_init(pDevice, ma_device_notification_type_interruption_ended));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32375,6 +32475,10 @@ on_error1:
|
||||
((ma_pa_stream_unref_proc)pDevice->pContext->pulse.pa_stream_unref)((ma_pa_stream*)pDevice->pulse.pStreamCapture);
|
||||
}
|
||||
on_error0:
|
||||
((ma_pa_context_disconnect_proc)pDevice->pContext->pulse.pa_context_disconnect)((ma_pa_context*)pDevice->pulse.pPulseContext);
|
||||
((ma_pa_context_unref_proc)pDevice->pContext->pulse.pa_context_unref)((ma_pa_context*)pDevice->pulse.pPulseContext);
|
||||
((ma_pa_mainloop_free_proc)pDevice->pContext->pulse.pa_mainloop_free)((ma_pa_mainloop*)pDevice->pulse.pMainLoop);
|
||||
MA_ZERO_OBJECT(&pDevice->pulse);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -32504,8 +32608,24 @@ static ma_result ma_device_data_loop__pulse(ma_device* pDevice)
|
||||
the callbacks deal with it.
|
||||
*/
|
||||
while (ma_device_get_state(pDevice) == ma_device_state_started) {
|
||||
ma_pa_context_state_t contextState = ((ma_pa_context_get_state_proc)pDevice->pContext->pulse.pa_context_get_state)((ma_pa_context*)pDevice->pulse.pPulseContext);
|
||||
ma_pa_stream_state_t captureState = pDevice->pulse.pStreamCapture ?
|
||||
((ma_pa_stream_get_state_proc)pDevice->pContext->pulse.pa_stream_get_state)((ma_pa_stream*)pDevice->pulse.pStreamCapture) : MA_PA_STREAM_READY;
|
||||
ma_pa_stream_state_t playbackState = pDevice->pulse.pStreamPlayback ?
|
||||
((ma_pa_stream_get_state_proc)pDevice->pContext->pulse.pa_stream_get_state)((ma_pa_stream*)pDevice->pulse.pStreamPlayback) : MA_PA_STREAM_READY;
|
||||
if (contextState == MA_PA_CONTEXT_FAILED || contextState == MA_PA_CONTEXT_TERMINATED ||
|
||||
captureState == MA_PA_STREAM_FAILED || captureState == MA_PA_STREAM_TERMINATED ||
|
||||
playbackState == MA_PA_STREAM_FAILED || playbackState == MA_PA_STREAM_TERMINATED) {
|
||||
ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR,
|
||||
"[PulseAudio] Device failed context=%d capture=%d playback=%d errno=%d.\n", contextState, captureState,
|
||||
playbackState, ((ma_pa_context_errno_proc)pDevice->pContext->pulse.pa_context_errno)((ma_pa_context*)pDevice->pulse.pPulseContext));
|
||||
return MA_ERROR;
|
||||
}
|
||||
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_ENTER, AUDIO_DIAG_LOOP, 0, 0);
|
||||
resultPA = ((ma_pa_mainloop_iterate_proc)pDevice->pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pDevice->pulse.pMainLoop, 1, NULL);
|
||||
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_LEAVE, AUDIO_DIAG_LOOP, 0, resultPA);
|
||||
if (resultPA < 0) {
|
||||
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_ERROR, AUDIO_DIAG_LOOP, 1, resultPA);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -32588,11 +32708,13 @@ static ma_result ma_context_init__pulse(ma_context* pContext, const ma_context_c
|
||||
pContext->pulse.pa_context_disconnect = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_context_disconnect");
|
||||
pContext->pulse.pa_context_set_state_callback = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_context_set_state_callback");
|
||||
pContext->pulse.pa_context_get_state = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_context_get_state");
|
||||
pContext->pulse.pa_context_errno = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_context_errno");
|
||||
pContext->pulse.pa_context_get_sink_info_list = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_context_get_sink_info_list");
|
||||
pContext->pulse.pa_context_get_source_info_list = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_context_get_source_info_list");
|
||||
pContext->pulse.pa_context_get_sink_info_by_name = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_context_get_sink_info_by_name");
|
||||
pContext->pulse.pa_context_get_source_info_by_name = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_context_get_source_info_by_name");
|
||||
pContext->pulse.pa_operation_unref = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_operation_unref");
|
||||
pContext->pulse.pa_operation_cancel = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_operation_cancel");
|
||||
pContext->pulse.pa_operation_get_state = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_operation_get_state");
|
||||
pContext->pulse.pa_channel_map_init_extend = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_channel_map_init_extend");
|
||||
pContext->pulse.pa_channel_map_valid = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_channel_map_valid");
|
||||
@@ -32619,6 +32741,7 @@ static ma_result ma_context_init__pulse(ma_context* pContext, const ma_context_c
|
||||
pContext->pulse.pa_stream_cork = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_cork");
|
||||
pContext->pulse.pa_stream_trigger = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_trigger");
|
||||
pContext->pulse.pa_stream_begin_write = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_begin_write");
|
||||
pContext->pulse.pa_stream_cancel_write = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_cancel_write");
|
||||
pContext->pulse.pa_stream_write = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_write");
|
||||
pContext->pulse.pa_stream_peek = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_peek");
|
||||
pContext->pulse.pa_stream_drop = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_drop");
|
||||
@@ -32651,11 +32774,13 @@ static ma_result ma_context_init__pulse(ma_context* pContext, const ma_context_c
|
||||
ma_pa_context_disconnect_proc _pa_context_disconnect = pa_context_disconnect;
|
||||
ma_pa_context_set_state_callback_proc _pa_context_set_state_callback = pa_context_set_state_callback;
|
||||
ma_pa_context_get_state_proc _pa_context_get_state = pa_context_get_state;
|
||||
ma_pa_context_errno_proc _pa_context_errno = pa_context_errno;
|
||||
ma_pa_context_get_sink_info_list_proc _pa_context_get_sink_info_list = pa_context_get_sink_info_list;
|
||||
ma_pa_context_get_source_info_list_proc _pa_context_get_source_info_list = pa_context_get_source_info_list;
|
||||
ma_pa_context_get_sink_info_by_name_proc _pa_context_get_sink_info_by_name = pa_context_get_sink_info_by_name;
|
||||
ma_pa_context_get_source_info_by_name_proc _pa_context_get_source_info_by_name= pa_context_get_source_info_by_name;
|
||||
ma_pa_operation_unref_proc _pa_operation_unref = pa_operation_unref;
|
||||
ma_pa_operation_cancel_proc _pa_operation_cancel = pa_operation_cancel;
|
||||
ma_pa_operation_get_state_proc _pa_operation_get_state = pa_operation_get_state;
|
||||
ma_pa_channel_map_init_extend_proc _pa_channel_map_init_extend = pa_channel_map_init_extend;
|
||||
ma_pa_channel_map_valid_proc _pa_channel_map_valid = pa_channel_map_valid;
|
||||
@@ -32682,6 +32807,7 @@ static ma_result ma_context_init__pulse(ma_context* pContext, const ma_context_c
|
||||
ma_pa_stream_cork_proc _pa_stream_cork = pa_stream_cork;
|
||||
ma_pa_stream_trigger_proc _pa_stream_trigger = pa_stream_trigger;
|
||||
ma_pa_stream_begin_write_proc _pa_stream_begin_write = pa_stream_begin_write;
|
||||
ma_pa_stream_cancel_write_proc _pa_stream_cancel_write = pa_stream_cancel_write;
|
||||
ma_pa_stream_write_proc _pa_stream_write = pa_stream_write;
|
||||
ma_pa_stream_peek_proc _pa_stream_peek = pa_stream_peek;
|
||||
ma_pa_stream_drop_proc _pa_stream_drop = pa_stream_drop;
|
||||
@@ -32713,11 +32839,13 @@ static ma_result ma_context_init__pulse(ma_context* pContext, const ma_context_c
|
||||
pContext->pulse.pa_context_disconnect = (ma_proc)_pa_context_disconnect;
|
||||
pContext->pulse.pa_context_set_state_callback = (ma_proc)_pa_context_set_state_callback;
|
||||
pContext->pulse.pa_context_get_state = (ma_proc)_pa_context_get_state;
|
||||
pContext->pulse.pa_context_errno = (ma_proc)_pa_context_errno;
|
||||
pContext->pulse.pa_context_get_sink_info_list = (ma_proc)_pa_context_get_sink_info_list;
|
||||
pContext->pulse.pa_context_get_source_info_list = (ma_proc)_pa_context_get_source_info_list;
|
||||
pContext->pulse.pa_context_get_sink_info_by_name = (ma_proc)_pa_context_get_sink_info_by_name;
|
||||
pContext->pulse.pa_context_get_source_info_by_name = (ma_proc)_pa_context_get_source_info_by_name;
|
||||
pContext->pulse.pa_operation_unref = (ma_proc)_pa_operation_unref;
|
||||
pContext->pulse.pa_operation_cancel = (ma_proc)_pa_operation_cancel;
|
||||
pContext->pulse.pa_operation_get_state = (ma_proc)_pa_operation_get_state;
|
||||
pContext->pulse.pa_channel_map_init_extend = (ma_proc)_pa_channel_map_init_extend;
|
||||
pContext->pulse.pa_channel_map_valid = (ma_proc)_pa_channel_map_valid;
|
||||
@@ -32744,6 +32872,7 @@ static ma_result ma_context_init__pulse(ma_context* pContext, const ma_context_c
|
||||
pContext->pulse.pa_stream_cork = (ma_proc)_pa_stream_cork;
|
||||
pContext->pulse.pa_stream_trigger = (ma_proc)_pa_stream_trigger;
|
||||
pContext->pulse.pa_stream_begin_write = (ma_proc)_pa_stream_begin_write;
|
||||
pContext->pulse.pa_stream_cancel_write = (ma_proc)_pa_stream_cancel_write;
|
||||
pContext->pulse.pa_stream_write = (ma_proc)_pa_stream_write;
|
||||
pContext->pulse.pa_stream_peek = (ma_proc)_pa_stream_peek;
|
||||
pContext->pulse.pa_stream_drop = (ma_proc)_pa_stream_drop;
|
||||
@@ -43767,6 +43896,7 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC
|
||||
|
||||
result = pContext->callbacks.onDeviceInit(pDevice, pConfig, &descriptorPlayback, &descriptorCapture);
|
||||
if (result != MA_SUCCESS) {
|
||||
ma_event_uninit(&pDevice->stopEvent);
|
||||
ma_event_uninit(&pDevice->startEvent);
|
||||
ma_event_uninit(&pDevice->wakeupEvent);
|
||||
ma_mutex_uninit(&pDevice->startStopLock);
|
||||
@@ -44008,6 +44138,8 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC
|
||||
}
|
||||
}
|
||||
|
||||
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_OPEN, -1, pDevice->type, pDevice->sampleRate);
|
||||
MA_DIAGNOSTIC_DEVICE_INIT(pDevice);
|
||||
MA_ASSERT(ma_device_get_state(pDevice) == ma_device_state_stopped);
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
@@ -44175,6 +44307,7 @@ MA_API void ma_device_uninit(ma_device* pDevice)
|
||||
ma_free(pDevice->pContext, &allocationCallbacks);
|
||||
}
|
||||
|
||||
MA_DIAGNOSTIC_EVENT(pDevice, AUDIO_DIAG_CLOSE, -1, 0, 0);
|
||||
MA_ZERO_OBJECT(pDevice);
|
||||
}
|
||||
|
||||
|
||||
+35
-36
@@ -8,14 +8,15 @@
|
||||
// - дрейф независимых потоков: переполнение линии → отбрасываем старый кадр,
|
||||
// недозаполнение (старт/underflow) → passthrough без канселлера.
|
||||
//
|
||||
// Захват и рендер приходят чанками разного размера (480/960), поэтому обе
|
||||
// стороны накапливаются до полного кадра (frame_samples) внутри обёртки.
|
||||
// Рендер принимает чанки любого размера; захват — ровно один полный кадр.
|
||||
// Частые аномалии учитываются счётчиками; владелец публикует сводку вне audio callback.
|
||||
|
||||
#include "speex_aec.h"
|
||||
#include "debug_config.h"
|
||||
#include "mem.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include <speex/speex_echo.h>
|
||||
|
||||
@@ -30,8 +31,6 @@ struct speex_aec {
|
||||
int count; /* кадров в линии (0..depth) */
|
||||
int16_t* play_acc; /* аккумулятор рендера до кадра */
|
||||
int play_len;
|
||||
int16_t* cap_acc; /* аккумулятор захвата до кадра */
|
||||
int cap_len;
|
||||
uint32_t overruns; /* отброшено рендер-кадров (дрейф: рендер быстрее) */
|
||||
uint32_t underruns; /* passthrough-кадров (дрейф: рендер медленнее/старт) */
|
||||
};
|
||||
@@ -42,10 +41,6 @@ static void aec_enqueue(speex_aec_t* a, const int16_t* frame) {
|
||||
a->head = (a->head + 1) % a->depth;
|
||||
a->count--;
|
||||
a->overruns++;
|
||||
if (a->overruns == 1 || (a->overruns & 0xff) == 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_AEC,
|
||||
"%s: delay line overflow (render faster than capture) overruns=%u", AEC_ID, a->overruns);
|
||||
}
|
||||
}
|
||||
memcpy(a->line + (size_t)((a->head + a->count) % a->depth) * a->frame_samples,
|
||||
frame, (size_t)a->frame_samples * sizeof(int16_t));
|
||||
@@ -56,7 +51,8 @@ speex_aec_t* speex_aec_create(int sample_rate, int frame_samples, int filter_sam
|
||||
speex_aec_t* a;
|
||||
int rate;
|
||||
|
||||
if (sample_rate <= 0 || frame_samples <= 0 || filter_samples < frame_samples) {
|
||||
if (sample_rate <= 0 || frame_samples <= 0 || filter_samples < frame_samples ||
|
||||
(delay_frames > 0 && delay_frames > INT_MAX / frame_samples)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_AEC, "%s: bad args rate=%d frame=%d filter=%d delay=%d",
|
||||
AEC_ID, sample_rate, frame_samples, filter_samples, delay_frames);
|
||||
return NULL;
|
||||
@@ -69,6 +65,11 @@ speex_aec_t* speex_aec_create(int sample_rate, int frame_samples, int filter_sam
|
||||
}
|
||||
|
||||
a->st = speex_echo_state_init(frame_samples, filter_samples);
|
||||
if (!a->st) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_AEC, "%s: echo state allocation failed", AEC_ID);
|
||||
u_free(a);
|
||||
return NULL;
|
||||
}
|
||||
rate = sample_rate;
|
||||
if (speex_echo_ctl(a->st, SPEEX_ECHO_SET_SAMPLING_RATE, &rate) != 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_AEC, "%s: SET_SAMPLING_RATE(%d) failed", AEC_ID, sample_rate);
|
||||
@@ -81,12 +82,10 @@ speex_aec_t* speex_aec_create(int sample_rate, int frame_samples, int filter_sam
|
||||
a->depth = delay_frames > 0 ? delay_frames : 1;
|
||||
a->line = (int16_t*)u_calloc((uint32_t)(a->depth * frame_samples), sizeof(int16_t));
|
||||
a->play_acc = (int16_t*)u_calloc((uint32_t)frame_samples, sizeof(int16_t));
|
||||
a->cap_acc = (int16_t*)u_calloc((uint32_t)frame_samples, sizeof(int16_t));
|
||||
if (!a->line || !a->play_acc || !a->cap_acc) {
|
||||
if (!a->line || !a->play_acc) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_AEC, "%s: OOM for buffers", AEC_ID);
|
||||
if (a->line) u_free(a->line);
|
||||
if (a->play_acc) u_free(a->play_acc);
|
||||
if (a->cap_acc) u_free(a->cap_acc);
|
||||
speex_echo_state_destroy(a->st);
|
||||
u_free(a);
|
||||
return NULL;
|
||||
@@ -102,7 +101,6 @@ void speex_aec_destroy(speex_aec_t* a) {
|
||||
speex_echo_state_destroy(a->st);
|
||||
u_free(a->line);
|
||||
u_free(a->play_acc);
|
||||
u_free(a->cap_acc);
|
||||
u_free(a);
|
||||
}
|
||||
|
||||
@@ -112,7 +110,6 @@ void speex_aec_reset(speex_aec_t* a) {
|
||||
a->head = 0;
|
||||
a->count = 0;
|
||||
a->play_len = 0;
|
||||
a->cap_len = 0;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_AEC, "%s: reset (filter + delay line)", AEC_ID);
|
||||
}
|
||||
|
||||
@@ -122,48 +119,50 @@ void speex_aec_feed_playback(speex_aec_t* a, const int16_t* pcm, int count) {
|
||||
if (!a || !pcm || count <= 0) return;
|
||||
fs = a->frame_samples;
|
||||
|
||||
take = count;
|
||||
if (a->play_len + count > fs) take = fs - a->play_len;
|
||||
memcpy(a->play_acc + a->play_len, pcm, (size_t)take * sizeof(int16_t));
|
||||
a->play_len += take;
|
||||
if (a->play_len < fs) return;
|
||||
|
||||
aec_enqueue(a, a->play_acc);
|
||||
a->play_len = 0;
|
||||
while (count > 0) {
|
||||
take = fs - a->play_len;
|
||||
if (take > count) take = count;
|
||||
memcpy(a->play_acc + a->play_len, pcm, (size_t)take * sizeof(int16_t));
|
||||
a->play_len += take;
|
||||
pcm += take;
|
||||
count -= take;
|
||||
if (a->play_len == fs) {
|
||||
aec_enqueue(a, a->play_acc);
|
||||
a->play_len = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int speex_aec_process_capture(speex_aec_t* a, const int16_t* pcm, int count, int16_t* out) {
|
||||
int fs, take;
|
||||
int fs;
|
||||
|
||||
if (!a || !pcm || !out || count <= 0) return 0;
|
||||
fs = a->frame_samples;
|
||||
|
||||
take = count;
|
||||
if (a->cap_len + count > fs) take = fs - a->cap_len;
|
||||
memcpy(a->cap_acc + a->cap_len, pcm, (size_t)take * sizeof(int16_t));
|
||||
a->cap_len += take;
|
||||
if (a->cap_len < fs) return 0;
|
||||
if (count != fs) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_AEC, "%s: capture requires one frame samples=%d expected=%d", AEC_ID, count, fs);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (a->count == a->depth) {
|
||||
const int16_t* ref = a->line + (size_t)a->head * fs;
|
||||
speex_echo_cancellation(a->st, a->cap_acc, ref, out);
|
||||
speex_echo_cancellation(a->st, pcm, ref, out);
|
||||
a->head = (a->head + 1) % a->depth;
|
||||
a->count--;
|
||||
} else {
|
||||
/* линия не наполнена (старт или рендер отстаёт): passthrough без канселлера */
|
||||
if (a->underruns == 0 || (a->underruns & 0xff) == 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_AEC,
|
||||
"%s: delay line underflow (render slower than capture) underruns=%u fill=%d/%d",
|
||||
AEC_ID, a->underruns, a->count, a->depth);
|
||||
}
|
||||
memcpy(out, a->cap_acc, (size_t)fs * sizeof(int16_t));
|
||||
memcpy(out, pcm, (size_t)fs * sizeof(int16_t));
|
||||
a->underruns++;
|
||||
}
|
||||
|
||||
a->cap_len = 0;
|
||||
return fs;
|
||||
}
|
||||
|
||||
int speex_aec_delay_fill(const speex_aec_t* a) {
|
||||
return a ? a->count : 0;
|
||||
}
|
||||
|
||||
void speex_aec_get_stats(const speex_aec_t* a, uint32_t* overruns, uint32_t* underruns) {
|
||||
*overruns = a ? a->overruns : 0;
|
||||
*underruns = a ? a->underruns : 0;
|
||||
}
|
||||
+7
-6
@@ -16,7 +16,7 @@
|
||||
*
|
||||
* Дрейф двух независимых потоков (capture/play на Android) компенсируется
|
||||
* ограниченной глубиной линии: переполнение → отбрасываем старый рендер-кадр,
|
||||
* недозаполнение → passthrough без канселлера (с подробным логом категории "aec").
|
||||
* недозаполнение → passthrough без канселлера; счётчики публикует владелец вне callback.
|
||||
*
|
||||
* Зависимости: SpeexDSP (mdf.c/fftwrap.c/kiss_fft*) из lib/speexdsp, флаги
|
||||
* FLOATING_POINT + USE_KISS_FFT. Собирается всегда (без внешних зависимостей).
|
||||
@@ -39,7 +39,7 @@ typedef struct speex_aec speex_aec_t;
|
||||
* sample_rate — 48000 (также 8000/16000/32000);
|
||||
* frame_samples — сэмплов в кадре (960 = 20 мс @48 кГц);
|
||||
* filter_samples— длина эхо-хвоста в сэмплах (14400 = 300 мс, кратно кадру);
|
||||
* delay_frames — задержка рендер→захват в кадрах (>=0; 0 = без линии задержки).
|
||||
* delay_frames — глубина линии рендер→захват в кадрах (значения <=0 приводятся к 1).
|
||||
* Возвращает NULL при ошибке (лог категории "aec").
|
||||
*/
|
||||
speex_aec_t* speex_aec_create(int sample_rate, int frame_samples, int filter_samples, int delay_frames);
|
||||
@@ -57,15 +57,16 @@ void speex_aec_reset(speex_aec_t* aec);
|
||||
void speex_aec_feed_playback(speex_aec_t* aec, const int16_t* pcm, int count);
|
||||
|
||||
/**
|
||||
* Обработать захват (микрофон). Накопление до кадра внутри. Когда кадр собран —
|
||||
* подавляет эхо (с учётом линии задержки) и пишет результат в out.
|
||||
* Возвращает число сэмплов, записанных в out (frame_samples, если кадр готов;
|
||||
* 0 — кадр ещё накапливается). out не должен алиаситься с pcm.
|
||||
* Обработать ровно один кадр захвата (count == frame_samples).
|
||||
* Возвращает frame_samples при успехе, 0 при ошибке. out вмещает полный кадр
|
||||
* и не должен алиаситься с pcm. Накопитель аппаратных чанков принадлежит I/O-движку.
|
||||
*/
|
||||
int speex_aec_process_capture(speex_aec_t* aec, const int16_t* pcm, int count, int16_t* out);
|
||||
|
||||
/* Актуальная глубина линии задержки (кадров) — для диагностики дрейфа. */
|
||||
int speex_aec_delay_fill(const speex_aec_t* aec);
|
||||
/* Под той же блокировкой, что feed/process; выводить снимок вне callback. */
|
||||
void speex_aec_get_stats(const speex_aec_t* aec, uint32_t* overruns, uint32_t* underruns);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
+276
-437
File diff suppressed because it is too large.
Load diff
+3
-2
@@ -90,6 +90,7 @@ static inline int uasync_is_running(struct UASYNC* ua) {
|
||||
|
||||
// Instance API - основной API для работы с uasync
|
||||
struct UASYNC* uasync_create(void);
|
||||
// Уничтожение только вне callbacks, после возврата из poll/mainloop.
|
||||
void uasync_destroy(struct UASYNC* ua, int close_fds);
|
||||
void uasync_mark_running(struct UASYNC* ua);
|
||||
void uasync_mark_stopped(struct UASYNC* ua);
|
||||
@@ -127,14 +128,14 @@ err_t uasync_set_socket_write(struct UASYNC* ua, void* s_id, int enable);
|
||||
void uasync_poll(struct UASYNC* ua, int timeout_tb);
|
||||
void uasync_drain_immediate(struct UASYNC* ua);
|
||||
|
||||
// Mainloop (бесконечный цикл, __noreturn)
|
||||
// Цикл до uasync_stop (возвращается после остановки).
|
||||
void uasync_mainloop(struct UASYNC* ua);
|
||||
void uasync_stop(struct UASYNC* ua);
|
||||
|
||||
// Debug statistics
|
||||
void uasync_get_stats(struct UASYNC* ua, size_t* timer_alloc, size_t* timer_free, size_t* socket_alloc, size_t* socket_free);
|
||||
|
||||
// Lookup socket by file descriptor - returns current pointer even after realloc
|
||||
// Handle — кодированный индекс; сохраняется при расширении массива сокетов.
|
||||
int uasync_lookup_socket(struct UASYNC* ua, int fd, void** socket_id);
|
||||
|
||||
// Print all resources (timers, sockets) for debugging
|
||||
|
||||
+6
-2
@@ -17,7 +17,7 @@ u_async — центральный планировщик проекта uTun.
|
||||
|
||||
- **Один UASYNC на поток.** Нельзя создать несколько экземпляров и вызывать их из разных потоков.
|
||||
- **Никаких sleep/usleep.** Если поток заблокирован на sleep, цикл событий стоит. Все ожидания — только через таймеры `uasync_set_timeout`.
|
||||
- **Закрытие из callback.** Можно вызывать `uasync_destroy` или `uasync_remove_socket` прямо из callback сокета/таймера. Модуль использует локальные копии указателей перед вызовом callback и генерационные счётчики (gen) для защиты от stale epoll-событий.
|
||||
- **Закрытие из callback.** Можно удалять сокет и освобождать его пользовательский контекст из callback. Перед следующим callback модуль повторно проверяет регистрацию, поколение и разрешённые события. Для завершения цикла вызывайте `uasync_stop`; `uasync_destroy` выполняйте после возврата из `uasync_poll` / `uasync_mainloop`, вне callbacks.
|
||||
- **Указатель `user_arg`** передаётся во все callback и позволяет передать контекстную структуру (например, `UTUN_INSTANCE`).
|
||||
|
||||
### Типовой сценарий
|
||||
@@ -141,7 +141,7 @@ uint64_t now_us = get_time_us(); // микросекунды (для burst-и
|
||||
| `uasync_remove_socket_t(ua, sock)` | Удалить по значению `socket_t`. |
|
||||
| `uasync_set_socket_read(ua, s_id, enable)` | Динамически включить/отключить мониторинг чтения (EPOLL_CTL_MOD). |
|
||||
| `uasync_set_socket_write(ua, s_id, enable)` | Динамически включить/отключить мониторинг записи. |
|
||||
| `uasync_lookup_socket(ua, fd, &s_id)` | Найти дескриптор сокета по fd (возвращает актуальный указатель даже после realloc). |
|
||||
| `uasync_lookup_socket(ua, fd, &s_id)` | Найти handle сокета по fd (кодированный индекс, устойчивый к расширению массива). |
|
||||
|
||||
### Межпоточное взаимодействие
|
||||
|
||||
@@ -172,3 +172,7 @@ uint64_t now_us = get_time_us(); // микросекунды (для burst-и
|
||||
- **`struct socket_node`** — узел сокета: fd/sock, тип (FD/SOCK), колбэки, user_data, флаги active/enable_read/enable_write, gen (защита от stale epoll-событий после переиспользования fd).
|
||||
- **`struct socket_array`** — массив сокетов: O(1) доступ по fd через `fd_to_index[]`, обход активных через `active_indices[]`, динамическое расширение.
|
||||
- **`struct posted_task`** — задача из другого потока: callback + arg + next, защищена `posted_lock`.
|
||||
|
||||
Таймеры используют монотонное время: коррекция календарных часов не меняет срок срабатывания. Положительные интервалы округляются вверх до миллисекунд; нулевой интервал готов немедленно. Каждый проход обрабатывает исходную порцию `call_soon`, затем проверяет таймеры и сокеты; новые callbacks остаются на следующий проход. `uasync_drain_immediate` явно обрабатывает все порции до опустошения очереди.
|
||||
|
||||
Для fallback `poll` массив дескрипторов резервируется при создании экземпляра и до регистрации нового сокета. Отказ памяти возвращает `NULL` из `uasync_create` / `uasync_add_socket*`; существующие регистрации сохраняются. Перестроение массива в `uasync_poll` не выделяет память. Расширение из callback сохраняет события текущего прохода, новый сокет опрашивается со следующего прохода.
|
||||
Reference in new issue
Block a user