kopia lustrzana https://github.com/raspberrypi/pico-extras
255 wiersze
8.9 KiB
C++
255 wiersze
8.9 KiB
C++
/*
|
|
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
|
*
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*/
|
|
|
|
#include <cstring>
|
|
#include "pico/audio.h"
|
|
#include "pico/sample_conversion.h"
|
|
|
|
// ======================
|
|
// == DEBUGGING =========
|
|
|
|
#define ENABLE_AUDIO_ASSERTIONS
|
|
|
|
#ifdef ENABLE_AUDIO_ASSERTIONS
|
|
#define audio_assert(x) assert(x)
|
|
#else
|
|
#define audio_assert(x) (void)0
|
|
#endif
|
|
|
|
inline static audio_buffer_t *list_remove_head(audio_buffer_t **phead) {
|
|
audio_buffer_t *ab = *phead;
|
|
|
|
if (ab) {
|
|
*phead = ab->next;
|
|
ab->next = NULL;
|
|
}
|
|
|
|
return ab;
|
|
}
|
|
|
|
inline static audio_buffer_t *list_remove_head_with_tail(audio_buffer_t **phead,
|
|
audio_buffer_t **ptail) {
|
|
audio_buffer_t *ab = *phead;
|
|
|
|
if (ab) {
|
|
*phead = ab->next;
|
|
|
|
if (!ab->next) {
|
|
audio_assert(*ptail == ab);
|
|
*ptail = NULL;
|
|
} else {
|
|
ab->next = NULL;
|
|
}
|
|
}
|
|
|
|
return ab;
|
|
}
|
|
|
|
inline static void list_prepend(audio_buffer_t **phead, audio_buffer_t *ab) {
|
|
audio_assert(ab->next == NULL);
|
|
audio_assert(ab != *phead);
|
|
ab->next = *phead;
|
|
*phead = ab;
|
|
}
|
|
|
|
// todo add a tail for these already sorted lists as we generally insert on the end
|
|
inline static void list_append_with_tail(audio_buffer_t **phead, audio_buffer_t **ptail,
|
|
audio_buffer_t *ab) {
|
|
audio_assert(ab->next == NULL);
|
|
audio_assert(ab != *phead);
|
|
audio_assert(ab != *ptail);
|
|
|
|
if (!*phead) {
|
|
audio_assert(!*ptail);
|
|
*ptail = ab;
|
|
// insert at the beginning
|
|
list_prepend(phead, ab);
|
|
} else {
|
|
// insert at end
|
|
(*ptail)->next = ab;
|
|
*ptail = ab;
|
|
}
|
|
}
|
|
|
|
audio_buffer_t *get_free_audio_buffer(audio_buffer_pool_t *context, bool block) {
|
|
audio_buffer_t *ab;
|
|
|
|
do {
|
|
uint32_t save = spin_lock_blocking(context->free_list_spin_lock);
|
|
ab = list_remove_head(&context->free_list);
|
|
spin_unlock(context->free_list_spin_lock, save);
|
|
if (ab || !block) break;
|
|
__wfe();
|
|
} while (true);
|
|
return ab;
|
|
}
|
|
|
|
void queue_free_audio_buffer(audio_buffer_pool_t *context, audio_buffer_t *ab) {
|
|
assert(!ab->next);
|
|
uint32_t save = spin_lock_blocking(context->free_list_spin_lock);
|
|
list_prepend(&context->free_list, ab);
|
|
spin_unlock(context->free_list_spin_lock, save);
|
|
__sev();
|
|
}
|
|
|
|
audio_buffer_t *get_full_audio_buffer(audio_buffer_pool_t *context, bool block) {
|
|
audio_buffer_t *ab;
|
|
|
|
do {
|
|
uint32_t save = spin_lock_blocking(context->prepared_list_spin_lock);
|
|
ab = list_remove_head_with_tail(&context->prepared_list, &context->prepared_list_tail);
|
|
spin_unlock(context->prepared_list_spin_lock, save);
|
|
if (ab || !block) break;
|
|
__wfe();
|
|
} while (true);
|
|
return ab;
|
|
}
|
|
|
|
void queue_full_audio_buffer(audio_buffer_pool_t *context, audio_buffer_t *ab) {
|
|
assert(!ab->next);
|
|
uint32_t save = spin_lock_blocking(context->prepared_list_spin_lock);
|
|
list_append_with_tail(&context->prepared_list, &context->prepared_list_tail, ab);
|
|
spin_unlock(context->prepared_list_spin_lock, save);
|
|
__sev();
|
|
}
|
|
|
|
void producer_pool_give_buffer_default(audio_connection_t *connection, audio_buffer_t *buffer) {
|
|
queue_full_audio_buffer(connection->producer_pool, buffer);
|
|
}
|
|
|
|
audio_buffer_t *producer_pool_take_buffer_default(audio_connection_t *connection, bool block) {
|
|
return get_free_audio_buffer(connection->producer_pool, block);
|
|
}
|
|
|
|
void consumer_pool_give_buffer_default(audio_connection_t *connection, audio_buffer_t *buffer) {
|
|
queue_free_audio_buffer(connection->consumer_pool, buffer);
|
|
}
|
|
|
|
audio_buffer_t *consumer_pool_take_buffer_default(audio_connection_t *connection, bool block) {
|
|
return get_full_audio_buffer(connection->consumer_pool, block);
|
|
}
|
|
|
|
static audio_connection_t connection_default = {
|
|
.producer_pool_take = producer_pool_take_buffer_default,
|
|
.producer_pool_give = producer_pool_give_buffer_default,
|
|
.consumer_pool_take = consumer_pool_take_buffer_default,
|
|
.consumer_pool_give = consumer_pool_give_buffer_default,
|
|
};
|
|
|
|
audio_buffer_t *audio_new_buffer(audio_buffer_format_t *format, int buffer_sample_count) {
|
|
audio_buffer_t *buffer = (audio_buffer_t *) calloc(1, sizeof(audio_buffer_t));
|
|
audio_init_buffer(buffer, format, buffer_sample_count);
|
|
return buffer;
|
|
}
|
|
|
|
void audio_init_buffer(audio_buffer_t *audio_buffer, audio_buffer_format_t *format, int buffer_sample_count) {
|
|
audio_buffer->format = format;
|
|
audio_buffer->buffer = pico_buffer_alloc(buffer_sample_count * format->sample_stride);
|
|
audio_buffer->max_sample_count = buffer_sample_count;
|
|
audio_buffer->sample_count = 0;
|
|
}
|
|
|
|
audio_buffer_pool_t *
|
|
audio_new_buffer_pool(audio_buffer_format_t *format, int buffer_count, int buffer_sample_count) {
|
|
audio_buffer_pool_t *ac = (audio_buffer_pool_t *) calloc(1, sizeof(audio_buffer_pool_t));
|
|
audio_buffer_t *audio_buffers = buffer_count ? (audio_buffer_t *) calloc(buffer_count,
|
|
sizeof(audio_buffer_t)) : 0;
|
|
ac->format = format->format;
|
|
for (int i = 0; i < buffer_count; i++) {
|
|
audio_init_buffer(audio_buffers + i, format, buffer_sample_count);
|
|
audio_buffers[i].next = i != buffer_count - 1 ? &audio_buffers[i + 1] : NULL;
|
|
}
|
|
// todo one per channel?
|
|
ac->free_list_spin_lock = spin_lock_init(SPINLOCK_ID_AUDIO_FREE_LIST_LOCK);
|
|
ac->free_list = audio_buffers;
|
|
ac->prepared_list_spin_lock = spin_lock_init(SPINLOCK_ID_AUDIO_PREPARED_LISTS_LOCK);
|
|
ac->prepared_list = NULL;
|
|
ac->prepared_list_tail = NULL;
|
|
ac->connection = &connection_default;
|
|
return ac;
|
|
}
|
|
|
|
audio_buffer_t *audio_new_wrapping_buffer(audio_buffer_format_t *format, mem_buffer_t *buffer) {
|
|
audio_buffer_t *audio_buffer = (audio_buffer_t *) calloc(1, sizeof(audio_buffer_t));
|
|
if (audio_buffer) {
|
|
audio_buffer->format = format;
|
|
audio_buffer->buffer = buffer;
|
|
audio_buffer->max_sample_count = buffer->size / format->sample_stride;
|
|
audio_buffer->sample_count = 0;
|
|
audio_buffer->next = 0;
|
|
}
|
|
return audio_buffer;
|
|
|
|
}
|
|
|
|
audio_buffer_pool_t *
|
|
audio_new_producer_pool(audio_buffer_format_t *format, int buffer_count, int buffer_sample_count) {
|
|
audio_buffer_pool_t *ac = audio_new_buffer_pool(format, buffer_count, buffer_sample_count);
|
|
ac->type = audio_buffer_pool::ac_producer;
|
|
return ac;
|
|
}
|
|
|
|
audio_buffer_pool_t *
|
|
audio_new_consumer_pool(audio_buffer_format_t *format, int buffer_count, int buffer_sample_count) {
|
|
audio_buffer_pool_t *ac = audio_new_buffer_pool(format, buffer_count, buffer_sample_count);
|
|
ac->type = audio_buffer_pool::ac_consumer;
|
|
return ac;
|
|
}
|
|
|
|
void audio_complete_connection(audio_connection_t *connection, audio_buffer_pool_t *producer_pool,
|
|
audio_buffer_pool_t *consumer_pool) {
|
|
assert(producer_pool->type == audio_buffer_pool::ac_producer);
|
|
assert(consumer_pool->type == audio_buffer_pool::ac_consumer);
|
|
producer_pool->connection = connection;
|
|
consumer_pool->connection = connection;
|
|
connection->producer_pool = producer_pool;
|
|
connection->consumer_pool = consumer_pool;
|
|
}
|
|
|
|
void give_audio_buffer(audio_buffer_pool_t *ac, audio_buffer_t *buffer) {
|
|
buffer->user_data = 0;
|
|
assert(ac->connection);
|
|
if (ac->type == audio_buffer_pool::ac_producer)
|
|
ac->connection->producer_pool_give(ac->connection, buffer);
|
|
else
|
|
ac->connection->consumer_pool_give(ac->connection, buffer);
|
|
}
|
|
|
|
audio_buffer_t *take_audio_buffer(audio_buffer_pool_t *ac, bool block) {
|
|
assert(ac->connection);
|
|
if (ac->type == audio_buffer_pool::ac_producer)
|
|
return ac->connection->producer_pool_take(ac->connection, block);
|
|
else
|
|
return ac->connection->consumer_pool_take(ac->connection, block);
|
|
}
|
|
|
|
// todo rename this - this is s16 to s16
|
|
audio_buffer_t *mono_to_mono_consumer_take(audio_connection_t *connection, bool block) {
|
|
return consumer_pool_take<Mono<FmtS16>, Mono<FmtS16>>(connection, block);
|
|
}
|
|
|
|
// todo rename this - this is s16 to s16
|
|
audio_buffer_t *stereo_to_stereo_consumer_take(audio_connection_t *connection, bool block) {
|
|
return consumer_pool_take<Stereo<FmtS16>, Stereo<FmtS16>>(connection, block);
|
|
}
|
|
|
|
// todo rename this - this is s16 to s16
|
|
audio_buffer_t *mono_to_stereo_consumer_take(audio_connection_t *connection, bool block) {
|
|
return consumer_pool_take<Stereo<FmtS16>, Mono<FmtS16>>(connection, block);
|
|
}
|
|
|
|
audio_buffer_t *mono_s8_to_mono_consumer_take(audio_connection_t *connection, bool block) {
|
|
return consumer_pool_take<Mono<FmtS16>, Mono<FmtS8>>(connection, block);
|
|
}
|
|
|
|
audio_buffer_t *mono_s8_to_stereo_consumer_take(audio_connection_t *connection, bool block) {
|
|
return consumer_pool_take<Stereo<FmtS16>, Mono<FmtS8>>(connection, block);
|
|
}
|
|
|
|
void stereo_to_stereo_producer_give(audio_connection_t *connection, audio_buffer_t *buffer) {
|
|
return producer_pool_blocking_give<Stereo<FmtS16>, Stereo<FmtS16>>(connection, buffer);
|
|
}
|