| /* Copyright (c) 2012 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 "cras_audio_area.h" |
| #include "cras_config.h" |
| #include "cras_iodev.h" |
| #include "cras_iodev_list.h" |
| #include "cras_rstream.h" |
| #include "cras_types.h" |
| #include "utlist.h" |
| |
| #define EMPTY_BUFFER_SIZE (16 * 1024) |
| #define EMPTY_FRAME_SIZE 4 |
| #define EMPTY_FRAMES (EMPTY_BUFFER_SIZE / EMPTY_FRAME_SIZE) |
| |
| static size_t empty_supported_rates[] = { |
| 44100, 48000, 0 |
| }; |
| |
| static size_t empty_supported_channel_counts[] = { |
| 1, 2, 0 |
| }; |
| |
| static snd_pcm_format_t empty_supported_formats[] = { |
| SND_PCM_FORMAT_S16_LE, |
| SND_PCM_FORMAT_S24_LE, |
| SND_PCM_FORMAT_S32_LE, |
| SND_PCM_FORMAT_S24_3LE, |
| 0 |
| }; |
| |
| struct empty_iodev { |
| struct cras_iodev base; |
| int open; |
| uint8_t *audio_buffer; |
| unsigned int buffer_level; |
| struct timespec last_buffer_access; |
| }; |
| |
| /* Current level of the audio buffer. This is made up based on what has been |
| * read/written and how long it has been since then. Simulates audio hardware |
| * running at the given sample rate. |
| */ |
| static unsigned int current_level(const struct cras_iodev *iodev) |
| { |
| struct empty_iodev *empty_iodev = (struct empty_iodev *)iodev; |
| unsigned int frames, frames_since_last; |
| |
| frames = empty_iodev->buffer_level; |
| frames_since_last = cras_frames_since_time( |
| &empty_iodev->last_buffer_access, |
| iodev->format->frame_rate); |
| |
| if (iodev->direction == CRAS_STREAM_INPUT) |
| return (frames + frames_since_last) % EMPTY_FRAMES; |
| |
| /* output */ |
| if (frames <= frames_since_last) |
| return 0; |
| return frames - frames_since_last; |
| } |
| |
| /* |
| * iodev callbacks. |
| */ |
| |
| static int is_open(const struct cras_iodev *iodev) |
| { |
| struct empty_iodev *empty_iodev = (struct empty_iodev *)iodev; |
| |
| return empty_iodev->open; |
| } |
| |
| static int dev_running(const struct cras_iodev *iodev) |
| { |
| return 1; |
| } |
| |
| static int frames_queued(const struct cras_iodev *iodev) |
| { |
| return current_level(iodev); |
| } |
| |
| static int delay_frames(const struct cras_iodev *iodev) |
| { |
| return 0; |
| } |
| |
| static int close_dev(struct cras_iodev *iodev) |
| { |
| struct empty_iodev *empty_iodev = (struct empty_iodev *)iodev; |
| |
| empty_iodev->open = 0; |
| free(empty_iodev->audio_buffer); |
| empty_iodev->audio_buffer = NULL; |
| cras_iodev_free_audio_area(iodev); |
| return 0; |
| } |
| |
| static int open_dev(struct cras_iodev *iodev) |
| { |
| struct empty_iodev *empty_iodev = (struct empty_iodev *)iodev; |
| |
| if (iodev->format == NULL) |
| return -EINVAL; |
| |
| cras_iodev_init_audio_area(iodev, iodev->format->num_channels); |
| empty_iodev->open = 1; |
| empty_iodev->audio_buffer = calloc(1, EMPTY_BUFFER_SIZE); |
| empty_iodev->buffer_level = 0; |
| |
| clock_gettime(CLOCK_MONOTONIC_RAW, &empty_iodev->last_buffer_access); |
| |
| return 0; |
| } |
| |
| static int get_buffer(struct cras_iodev *iodev, |
| struct cras_audio_area **area, |
| unsigned *frames) |
| { |
| struct empty_iodev *empty_iodev = (struct empty_iodev *)iodev; |
| |
| if (iodev->direction == CRAS_STREAM_OUTPUT) |
| *frames = MIN(*frames, EMPTY_FRAMES - current_level(iodev)); |
| else |
| *frames = MIN(*frames, current_level(iodev)); |
| |
| iodev->area->frames = *frames; |
| cras_audio_area_config_buf_pointers(iodev->area, iodev->format, |
| empty_iodev->audio_buffer); |
| *area = iodev->area; |
| return 0; |
| } |
| |
| static int put_buffer(struct cras_iodev *iodev, unsigned frames) |
| { |
| struct empty_iodev *empty_iodev = (struct empty_iodev *)iodev; |
| |
| empty_iodev->buffer_level = current_level(iodev); |
| |
| clock_gettime(CLOCK_MONOTONIC_RAW, &empty_iodev->last_buffer_access); |
| |
| if (iodev->direction == CRAS_STREAM_OUTPUT) { |
| empty_iodev->buffer_level += frames; |
| empty_iodev->buffer_level %= EMPTY_FRAMES; |
| } else { |
| /* Input */ |
| if (empty_iodev->buffer_level > frames) |
| empty_iodev->buffer_level -= frames; |
| else |
| empty_iodev->buffer_level = 0; |
| } |
| |
| return 0; |
| } |
| |
| static int flush_buffer(struct cras_iodev *iodev) |
| { |
| struct empty_iodev *empty_iodev = (struct empty_iodev *)iodev; |
| empty_iodev->buffer_level = current_level(iodev); |
| if (iodev->direction == CRAS_STREAM_INPUT) |
| empty_iodev->buffer_level = 0; |
| return 0; |
| } |
| |
| static void update_active_node(struct cras_iodev *iodev, unsigned node_idx) |
| { |
| } |
| |
| /* |
| * Exported Interface. |
| */ |
| |
| struct cras_iodev *empty_iodev_create(enum CRAS_STREAM_DIRECTION direction) |
| { |
| struct empty_iodev *empty_iodev; |
| struct cras_iodev *iodev; |
| struct cras_ionode *node; |
| |
| if (direction != CRAS_STREAM_INPUT && direction != CRAS_STREAM_OUTPUT) |
| return NULL; |
| |
| empty_iodev = calloc(1, sizeof(*empty_iodev)); |
| if (empty_iodev == NULL) |
| return NULL; |
| iodev = &empty_iodev->base; |
| iodev->direction = direction; |
| |
| iodev->supported_rates = empty_supported_rates; |
| iodev->supported_channel_counts = empty_supported_channel_counts; |
| iodev->supported_formats = empty_supported_formats; |
| iodev->buffer_size = EMPTY_BUFFER_SIZE; |
| |
| iodev->open_dev = open_dev; |
| iodev->close_dev = close_dev; |
| iodev->is_open = is_open; |
| iodev->frames_queued = frames_queued; |
| iodev->delay_frames = delay_frames; |
| iodev->get_buffer = get_buffer; |
| iodev->put_buffer = put_buffer; |
| iodev->flush_buffer = flush_buffer; |
| iodev->dev_running = dev_running; |
| iodev->update_active_node = update_active_node; |
| |
| /* Create a dummy ionode */ |
| node = (struct cras_ionode *)calloc(1, sizeof(*node)); |
| node->dev = iodev; |
| node->type = CRAS_NODE_TYPE_UNKNOWN; |
| node->volume = 100; |
| strcpy(node->name, "(default)"); |
| cras_iodev_add_node(iodev, node); |
| cras_iodev_set_active_node(iodev, node); |
| |
| /* Finally add it to the appropriate iodev list. */ |
| if (direction == CRAS_STREAM_INPUT) { |
| snprintf(iodev->info.name, |
| ARRAY_SIZE(iodev->info.name), |
| "Silent record device."); |
| iodev->info.name[ARRAY_SIZE(iodev->info.name) - 1] = '\0'; |
| iodev->info.idx = SILENT_RECORD_DEVICE; |
| } else { |
| snprintf(iodev->info.name, |
| ARRAY_SIZE(iodev->info.name), |
| "Silent playback device."); |
| iodev->info.name[ARRAY_SIZE(iodev->info.name) - 1] = '\0'; |
| iodev->info.idx = SILENT_PLAYBACK_DEVICE; |
| } |
| |
| return iodev; |
| } |
| |
| void empty_iodev_destroy(struct cras_iodev *iodev) |
| { |
| struct empty_iodev *empty_iodev = (struct empty_iodev *)iodev; |
| |
| if (iodev->direction == CRAS_STREAM_INPUT) |
| cras_iodev_list_rm_input(iodev); |
| else |
| cras_iodev_list_rm_output(iodev); |
| free(iodev->active_node); |
| cras_iodev_free_resources(iodev); |
| free(empty_iodev); |
| } |