blob: ff33acaf7c6de4d52cc08b9aa4606b9689f0f089 [file] [log] [blame]
/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include <pthread.h>
#include <sys/param.h>
#include <syslog.h>
#include "byte_buffer.h"
#include "cras_audio_area.h"
#include "cras_config.h"
#include "cras_iodev.h"
#include "cras_iodev_list.h"
#include "cras_types.h"
#include "cras_util.h"
#include "sfh.h"
#include "utlist.h"
#define LOOPBACK_BUFFER_SIZE 8192
static const char *loopdev_names[LOOPBACK_NUM_TYPES] = {
"Post Mix Pre DSP Loopback",
"Post DSP Loopback",
};
static size_t loopback_supported_rates[] = {
48000, 0
};
static size_t loopback_supported_channel_counts[] = {
2, 0
};
static snd_pcm_format_t loopback_supported_formats[] = {
SND_PCM_FORMAT_S16_LE,
0
};
/* loopack iodev. Keep state of a loopback device.
* sample_buffer - Pointer to sample buffer.
*/
struct loopback_iodev {
struct cras_iodev base;
enum CRAS_LOOPBACK_TYPE loopback_type;
struct timespec last_filled;
struct byte_buffer *sample_buffer;
};
static int sample_hook(const uint8_t *frames, unsigned int nframes,
const struct cras_audio_format *fmt,
void *cb_data)
{
struct loopback_iodev *loopdev = (struct loopback_iodev *)cb_data;
struct byte_buffer *sbuf = loopdev->sample_buffer;
unsigned int frame_bytes = cras_get_format_bytes(fmt);
unsigned int frames_to_copy, bytes_to_copy;
struct cras_iodev *edev = cras_iodev_list_get_first_enabled_iodev(
CRAS_STREAM_OUTPUT);
/* If there's no active streams, the logic in frames_queued will fill
* zeros for loopback capture, do not accept zeros for draining device.
*/
if (!edev || !edev->streams)
return 0;
frames_to_copy = MIN(buf_writable_bytes(sbuf) / frame_bytes, nframes);
if (!frames_to_copy)
return 0;
bytes_to_copy = frames_to_copy * frame_bytes;
memcpy(buf_write_pointer(sbuf), frames, bytes_to_copy);
buf_increment_write(sbuf, bytes_to_copy);
clock_gettime(CLOCK_MONOTONIC_RAW, &loopdev->last_filled);
return frames_to_copy;
}
static void register_loopback_hook(enum CRAS_LOOPBACK_TYPE loopback_type,
struct cras_iodev *iodev,
loopback_hook_t hook, void *cb_data)
{
if (!iodev) {
syslog(LOG_ERR, "Failed to register loopback hook.");
return;
}
if (loopback_type == LOOPBACK_POST_MIX_PRE_DSP)
cras_iodev_register_pre_dsp_hook(iodev, hook, cb_data);
else if (loopback_type == LOOPBACK_POST_DSP)
cras_iodev_register_post_dsp_hook(iodev, hook, cb_data);
}
static void device_enabled_hook(struct cras_iodev *iodev, int enabled,
void *cb_data)
{
struct loopback_iodev *loopdev = (struct loopback_iodev *)cb_data;
struct cras_iodev *edev;
if (iodev->direction != CRAS_STREAM_OUTPUT)
return;
if (!enabled) {
/* Unregister loopback hook from disabled iodev. */
register_loopback_hook(loopdev->loopback_type, iodev, NULL,
NULL);
} else {
/* Register loopback hook onto first enabled iodev. */
edev = cras_iodev_list_get_first_enabled_iodev(
CRAS_STREAM_OUTPUT);
register_loopback_hook(loopdev->loopback_type, edev,
sample_hook, cb_data);
}
}
/*
* iodev callbacks.
*/
static int frames_queued(const struct cras_iodev *iodev,
struct timespec *hw_tstamp)
{
struct loopback_iodev *loopdev = (struct loopback_iodev *)iodev;
struct byte_buffer *sbuf = loopdev->sample_buffer;
unsigned int frame_bytes = cras_get_format_bytes(iodev->format);
struct cras_iodev *edev = cras_iodev_list_get_first_enabled_iodev(
CRAS_STREAM_OUTPUT);
if (!edev || !edev->streams) {
unsigned int frames_since_last, frames_to_fill, bytes_to_fill;
frames_since_last = cras_frames_since_time(
&loopdev->last_filled,
iodev->format->frame_rate);
frames_to_fill = MIN(buf_writable_bytes(sbuf) / frame_bytes,
frames_since_last);
if (frames_to_fill > 0) {
bytes_to_fill = frames_to_fill * frame_bytes;
memset(buf_write_pointer(sbuf), 0, bytes_to_fill);
buf_increment_write(sbuf, bytes_to_fill);
clock_gettime(CLOCK_MONOTONIC_RAW,
&loopdev->last_filled);
}
}
*hw_tstamp = loopdev->last_filled;
return buf_queued_bytes(sbuf) / frame_bytes;
}
static int delay_frames(const struct cras_iodev *iodev)
{
struct timespec tstamp;
return frames_queued(iodev, &tstamp);
}
static int close_record_dev(struct cras_iodev *iodev)
{
struct loopback_iodev *loopdev = (struct loopback_iodev *)iodev;
struct byte_buffer *sbuf = loopdev->sample_buffer;
struct cras_iodev *edev;
cras_iodev_free_format(iodev);
cras_iodev_free_audio_area(iodev);
buf_reset(sbuf);
edev = cras_iodev_list_get_first_enabled_iodev(CRAS_STREAM_OUTPUT);
register_loopback_hook(loopdev->loopback_type, edev, NULL, NULL);
cras_iodev_list_set_device_enabled_callback(NULL, NULL);
return 0;
}
static int open_record_dev(struct cras_iodev *iodev)
{
struct loopback_iodev *loopdev = (struct loopback_iodev *)iodev;
struct cras_iodev *edev;
cras_iodev_init_audio_area(iodev, iodev->format->num_channels);
clock_gettime(CLOCK_MONOTONIC_RAW, &loopdev->last_filled);
edev = cras_iodev_list_get_first_enabled_iodev(CRAS_STREAM_OUTPUT);
register_loopback_hook(loopdev->loopback_type, edev, sample_hook,
(void *)iodev);
cras_iodev_list_set_device_enabled_callback(device_enabled_hook,
(void *)iodev);
return 0;
}
static int get_record_buffer(struct cras_iodev *iodev,
struct cras_audio_area **area,
unsigned *frames)
{
struct loopback_iodev *loopdev = (struct loopback_iodev *)iodev;
struct byte_buffer *sbuf = loopdev->sample_buffer;
unsigned int frame_bytes = cras_get_format_bytes(iodev->format);
unsigned int avail_frames = buf_readable_bytes(sbuf) / frame_bytes;
*frames = MIN(avail_frames, *frames);
iodev->area->frames = *frames;
cras_audio_area_config_buf_pointers(iodev->area, iodev->format,
buf_read_pointer(sbuf));
*area = iodev->area;
return 0;
}
static int put_record_buffer(struct cras_iodev *iodev, unsigned nframes)
{
struct loopback_iodev *loopdev = (struct loopback_iodev *)iodev;
struct byte_buffer *sbuf = loopdev->sample_buffer;
unsigned int frame_bytes = cras_get_format_bytes(iodev->format);
buf_increment_read(sbuf, nframes * frame_bytes);
return 0;
}
static int flush_record_buffer(struct cras_iodev *iodev)
{
struct loopback_iodev *loopdev = (struct loopback_iodev *)iodev;
struct byte_buffer *sbuf = loopdev->sample_buffer;
unsigned int queued_bytes = buf_queued_bytes(sbuf);
buf_increment_read(sbuf, queued_bytes);
return 0;
}
static void update_active_node(struct cras_iodev *iodev, unsigned node_idx,
unsigned dev_enabled)
{
}
static struct cras_iodev *create_loopback_iodev(enum CRAS_LOOPBACK_TYPE type)
{
struct loopback_iodev *loopback_iodev;
struct cras_iodev *iodev;
loopback_iodev = calloc(1, sizeof(*loopback_iodev));
if (loopback_iodev == NULL)
return NULL;
loopback_iodev->sample_buffer = byte_buffer_create(1024*16*4);
if (loopback_iodev->sample_buffer == NULL) {
free(loopback_iodev);
return NULL;
}
loopback_iodev->loopback_type = type;
iodev = &loopback_iodev->base;
iodev->direction = CRAS_STREAM_INPUT;
snprintf(iodev->info.name, ARRAY_SIZE(iodev->info.name), "%s",
loopdev_names[type]);
iodev->info.name[ARRAY_SIZE(iodev->info.name) - 1] = '\0';
iodev->info.stable_id = SuperFastHash(iodev->info.name,
strlen(iodev->info.name),
strlen(iodev->info.name));
iodev->info.stable_id_new = iodev->info.stable_id;
iodev->supported_rates = loopback_supported_rates;
iodev->supported_channel_counts = loopback_supported_channel_counts;
iodev->supported_formats = loopback_supported_formats;
iodev->buffer_size = LOOPBACK_BUFFER_SIZE;
iodev->frames_queued = frames_queued;
iodev->delay_frames = delay_frames;
iodev->update_active_node = update_active_node;
iodev->open_dev = open_record_dev;
iodev->close_dev = close_record_dev;
iodev->get_buffer = get_record_buffer;
iodev->put_buffer = put_record_buffer;
iodev->flush_buffer = flush_record_buffer;
return iodev;
}
/*
* Exported Interface.
*/
struct cras_iodev *loopback_iodev_create(enum CRAS_LOOPBACK_TYPE type)
{
struct cras_iodev *iodev;
struct cras_ionode *node;
enum CRAS_NODE_TYPE node_type;
switch (type) {
case LOOPBACK_POST_MIX_PRE_DSP:
node_type = CRAS_NODE_TYPE_POST_MIX_PRE_DSP;
break;
case LOOPBACK_POST_DSP:
node_type = CRAS_NODE_TYPE_POST_DSP;
break;
default:
return NULL;
}
iodev = create_loopback_iodev(type);
if (iodev == NULL)
return NULL;
/* Create a dummy ionode */
node = (struct cras_ionode *)calloc(1, sizeof(*node));
node->dev = iodev;
node->type = node_type;
node->plugged = 1;
node->volume = 100;
node->stable_id = iodev->info.stable_id;
node->stable_id_new = iodev->info.stable_id_new;
node->software_volume_needed = 0;
node->max_software_gain = 0;
strcpy(node->name, loopdev_names[type]);
cras_iodev_add_node(iodev, node);
cras_iodev_set_active_node(iodev, node);
cras_iodev_list_add_input(iodev);
return iodev;
}
void loopback_iodev_destroy(struct cras_iodev *iodev)
{
struct loopback_iodev *loopdev = (struct loopback_iodev *)iodev;
struct byte_buffer *sbuf = loopdev->sample_buffer;
cras_iodev_list_rm_input(iodev);
byte_buffer_destroy(sbuf);
free(loopdev);
}