HelenOS sources
This source file includes following definitions.
- is_running
- audio_device_init
- audio_device_fini
- audio_device_get_source
- audio_device_get_sink
- device_sink_connection_callback
- device_source_connection_callback
- device_event_callback
- device_check_format
- get_buffer
- release_buffer
- advance_buffer
#include <assert.h>
#include <async.h>
#include <errno.h>
#include <inttypes.h>
#include <loc.h>
#include <stdbool.h>
#include <str.h>
#include <str_error.h>
#include <as.h>
#include "audio_device.h"
#include "log.h"
#define BUFFER_PARTS 16
static errno_t device_sink_connection_callback(audio_sink_t *sink, bool new);
static errno_t device_source_connection_callback(audio_source_t *source, bool new);
static void device_event_callback(ipc_call_t *icall, void *arg);
static errno_t device_check_format(audio_sink_t *sink);
static errno_t get_buffer(audio_device_t *dev);
static errno_t release_buffer(audio_device_t *dev);
static void advance_buffer(audio_device_t *dev, size_t size);
static inline bool is_running(audio_device_t *dev)
{
assert(dev);
return dev->buffer.base != NULL;
}
errno_t audio_device_init(audio_device_t *dev, service_id_t id, const char *name)
{
assert(dev);
link_initialize(&dev->link);
dev->id = id;
dev->name = str_dup(name);
dev->sess = audio_pcm_open_service(id);
if (!dev->sess) {
log_debug("Failed to connect to device \"%s\"", name);
return ENOMEM;
}
audio_sink_init(&dev->sink, name, dev, device_sink_connection_callback,
device_check_format, NULL, &AUDIO_FORMAT_ANY);
audio_source_init(&dev->source, name, dev,
device_source_connection_callback, NULL, &AUDIO_FORMAT_ANY);
dev->buffer.base = NULL;
dev->buffer.position = NULL;
dev->buffer.size = 0;
dev->buffer.fragment_size = 0;
log_verbose("Initialized device (%p) '%s' with id %" PRIun ".",
dev, dev->name, dev->id);
return EOK;
}
void audio_device_fini(audio_device_t *dev)
{
}
audio_source_t *audio_device_get_source(audio_device_t *dev)
{
assert(dev);
sysarg_t val;
errno_t rc = audio_pcm_query_cap(dev->sess, AUDIO_CAP_CAPTURE, &val);
if (rc == EOK && val)
return &dev->source;
return NULL;
}
audio_sink_t *audio_device_get_sink(audio_device_t *dev)
{
assert(dev);
sysarg_t val;
errno_t rc = audio_pcm_query_cap(dev->sess, AUDIO_CAP_PLAYBACK, &val);
if (rc == EOK && val)
return &dev->sink;
return NULL;
}
static errno_t device_sink_connection_callback(audio_sink_t *sink, bool new)
{
assert(sink);
audio_device_t *dev = sink->private_data;
if (new && list_count(&sink->connections) == 1) {
log_verbose("First connection on device sink '%s'", sink->name);
errno_t ret = get_buffer(dev);
if (ret != EOK) {
log_error("Failed to get device buffer: %s",
str_error(ret));
return ret;
}
audio_pcm_register_event_callback(dev->sess,
device_event_callback, dev);
pcm_format_silence(dev->buffer.base, dev->buffer.size,
&dev->sink.format);
const size_t size = dev->buffer.fragment_size * 2;
audio_sink_mix_inputs(&dev->sink, dev->buffer.position, size);
advance_buffer(dev, size);
const unsigned frames = dev->buffer.fragment_size /
pcm_format_frame_size(&dev->sink.format);
log_verbose("Fragment frame count %u", frames);
ret = audio_pcm_start_playback_fragment(dev->sess, frames,
dev->sink.format.channels, dev->sink.format.sampling_rate,
dev->sink.format.sample_format);
if (ret != EOK) {
log_error("Failed to start playback: %s",
str_error(ret));
release_buffer(dev);
return ret;
}
}
if (list_count(&sink->connections) == 0) {
assert(!new);
log_verbose("Removed last connection on device sink '%s'",
sink->name);
errno_t ret = audio_pcm_stop_playback(dev->sess);
if (ret != EOK) {
log_error("Failed to stop playback: %s",
str_error(ret));
return ret;
}
}
return EOK;
}
static errno_t device_source_connection_callback(audio_source_t *source, bool new)
{
assert(source);
audio_device_t *dev = source->private_data;
if (new && list_count(&source->connections) == 1) {
errno_t ret = get_buffer(dev);
if (ret != EOK) {
log_error("Failed to get device buffer: %s",
str_error(ret));
return ret;
}
const unsigned frames = dev->buffer.fragment_size /
pcm_format_frame_size(&dev->sink.format);
ret = audio_pcm_start_capture_fragment(dev->sess, frames,
dev->source.format.channels,
dev->source.format.sampling_rate,
dev->source.format.sample_format);
if (ret != EOK) {
log_error("Failed to start recording: %s",
str_error(ret));
release_buffer(dev);
return ret;
}
}
if (list_count(&source->connections) == 0) {
assert(!new);
errno_t ret = audio_pcm_stop_capture_immediate(dev->sess);
if (ret != EOK) {
log_error("Failed to start recording: %s",
str_error(ret));
return ret;
}
}
return EOK;
}
static void device_event_callback(ipc_call_t *icall, void *arg)
{
struct timespec time1;
errno_t ret;
audio_device_t *dev = arg;
assert(dev);
while (true) {
ipc_call_t call;
async_get_call(&call);
async_answer_0(&call, EOK);
switch (ipc_get_imethod(&call)) {
case PCM_EVENT_FRAMES_PLAYED:
getuptime(&time1);
audio_sink_mix_inputs(&dev->sink, dev->buffer.position,
dev->buffer.fragment_size);
advance_buffer(dev, dev->buffer.fragment_size);
struct timespec time2;
getuptime(&time2);
log_verbose("Time to mix sources: %lld\n",
NSEC2USEC(ts_sub_diff(&time2, &time1)));
break;
case PCM_EVENT_CAPTURE_TERMINATED:
log_verbose("Capture terminated");
dev->source.format = AUDIO_FORMAT_ANY;
ret = release_buffer(dev);
if (ret != EOK) {
log_error("Failed to release buffer: %s",
str_error(ret));
}
audio_pcm_unregister_event_callback(dev->sess);
break;
case PCM_EVENT_PLAYBACK_TERMINATED:
log_verbose("Playback Terminated");
dev->sink.format = AUDIO_FORMAT_ANY;
ret = release_buffer(dev);
if (ret != EOK) {
log_error("Failed to release buffer: %s",
str_error(ret));
}
audio_pcm_unregister_event_callback(dev->sess);
break;
case PCM_EVENT_FRAMES_CAPTURED:
ret = audio_source_push_data(&dev->source,
dev->buffer.position, dev->buffer.fragment_size);
advance_buffer(dev, dev->buffer.fragment_size);
if (ret != EOK)
log_warning("Failed to push recorded data");
break;
case 0:
log_info("Device event callback hangup");
return;
}
}
}
static errno_t device_check_format(audio_sink_t *sink)
{
assert(sink);
audio_device_t *dev = sink->private_data;
assert(dev);
if (is_running(dev))
return EBUSY;
log_verbose("Checking format on sink %s", sink->name);
return audio_pcm_test_format(dev->sess, &sink->format.channels,
&sink->format.sampling_rate, &sink->format.sample_format);
}
static errno_t get_buffer(audio_device_t *dev)
{
assert(dev);
if (!dev->sess) {
log_debug("No connection to device");
return EIO;
}
if (dev->buffer.base) {
log_debug("We already have a buffer");
return EBUSY;
}
size_t preferred_size = 0;
const errno_t ret = audio_pcm_get_buffer(dev->sess, &dev->buffer.base,
&preferred_size);
if (ret == EOK) {
dev->buffer.size = preferred_size;
dev->buffer.fragment_size = dev->buffer.size / BUFFER_PARTS;
dev->buffer.position = dev->buffer.base;
}
return ret;
}
static errno_t release_buffer(audio_device_t *dev)
{
assert(dev);
assert(dev->buffer.base);
const errno_t ret = audio_pcm_release_buffer(dev->sess);
if (ret == EOK) {
as_area_destroy(dev->buffer.base);
dev->buffer.base = NULL;
dev->buffer.size = 0;
dev->buffer.position = NULL;
} else {
log_warning("Failed to release buffer: %s", str_error(ret));
}
return ret;
}
static void advance_buffer(audio_device_t *dev, size_t size)
{
assert(dev);
assert(dev->buffer.position >= dev->buffer.base);
assert(dev->buffer.position < (dev->buffer.base + dev->buffer.size));
dev->buffer.position += size;
if (dev->buffer.position == (dev->buffer.base + dev->buffer.size))
dev->buffer.position = dev->buffer.base;
}
HelenOS homepage, sources at GitHub