| /* 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); |
| } |