| /* 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. |
| */ |
| |
| #ifndef _GNU_SOURCE |
| #define _GNU_SOURCE /* for ppoll */ |
| #endif |
| |
| #include <pthread.h> |
| #include <poll.h> |
| #include <sys/param.h> |
| #include <syslog.h> |
| |
| #include "cras_audio_area.h" |
| #include "audio_thread_log.h" |
| #include "cras_config.h" |
| #include "cras_fmt_conv.h" |
| #include "cras_iodev.h" |
| #include "cras_rstream.h" |
| #include "cras_server_metrics.h" |
| #include "cras_system_state.h" |
| #include "cras_types.h" |
| #include "cras_util.h" |
| #include "dev_stream.h" |
| #include "audio_thread.h" |
| #include "utlist.h" |
| |
| #define MIN_PROCESS_TIME_US 500 /* 0.5ms - min amount of time to mix/src. */ |
| #define SLEEP_FUZZ_FRAMES 10 /* # to consider "close enough" to sleep frames. */ |
| #define MIN_READ_WAIT_US 2000 /* 2ms */ |
| static const struct timespec playback_wake_fuzz_ts = { |
| 0, 500 * 1000 /* 500 usec. */ |
| }; |
| |
| /* Messages that can be sent from the main context to the audio thread. */ |
| enum AUDIO_THREAD_COMMAND { |
| AUDIO_THREAD_ADD_OPEN_DEV, |
| AUDIO_THREAD_RM_OPEN_DEV, |
| AUDIO_THREAD_ADD_STREAM, |
| AUDIO_THREAD_DISCONNECT_STREAM, |
| AUDIO_THREAD_STOP, |
| AUDIO_THREAD_DUMP_THREAD_INFO, |
| AUDIO_THREAD_DRAIN_STREAM, |
| AUDIO_THREAD_CONFIG_GLOBAL_REMIX, |
| AUDIO_THREAD_DEV_START_RAMP, |
| AUDIO_THREAD_REMOVE_CALLBACK, |
| }; |
| |
| struct audio_thread_msg { |
| size_t length; |
| enum AUDIO_THREAD_COMMAND id; |
| }; |
| |
| struct audio_thread_config_global_remix { |
| struct audio_thread_msg header; |
| struct cras_fmt_conv *fmt_conv; |
| }; |
| |
| struct audio_thread_open_device_msg { |
| struct audio_thread_msg header; |
| struct cras_iodev *dev; |
| }; |
| |
| struct audio_thread_rm_callback_msg { |
| struct audio_thread_msg header; |
| int fd; |
| }; |
| |
| struct audio_thread_add_rm_stream_msg { |
| struct audio_thread_msg header; |
| struct cras_rstream *stream; |
| struct cras_iodev **devs; |
| unsigned int num_devs; |
| }; |
| |
| struct audio_thread_dump_debug_info_msg { |
| struct audio_thread_msg header; |
| struct audio_debug_info *info; |
| }; |
| |
| struct audio_thread_dev_start_ramp_msg { |
| struct audio_thread_msg header; |
| struct cras_iodev *dev; |
| enum CRAS_IODEV_RAMP_REQUEST request; |
| }; |
| |
| /* Audio thread logging. */ |
| struct audio_thread_event_log *atlog; |
| /* Global fmt converter used to remix output channels. */ |
| static struct cras_fmt_conv *remix_converter = NULL; |
| |
| static struct iodev_callback_list *iodev_callbacks; |
| static struct timespec longest_wake; |
| |
| struct iodev_callback_list { |
| int fd; |
| int is_write; |
| int enabled; |
| thread_callback cb; |
| void *cb_data; |
| struct pollfd *pollfd; |
| struct iodev_callback_list *prev, *next; |
| }; |
| |
| static void _audio_thread_add_callback(int fd, thread_callback cb, |
| void *data, int is_write) |
| { |
| struct iodev_callback_list *iodev_cb; |
| |
| /* Don't add iodev_cb twice */ |
| DL_FOREACH(iodev_callbacks, iodev_cb) |
| if (iodev_cb->fd == fd && iodev_cb->cb_data == data) |
| return; |
| |
| iodev_cb = (struct iodev_callback_list *)calloc(1, sizeof(*iodev_cb)); |
| iodev_cb->fd = fd; |
| iodev_cb->cb = cb; |
| iodev_cb->cb_data = data; |
| iodev_cb->enabled = 1; |
| iodev_cb->is_write = is_write; |
| |
| DL_APPEND(iodev_callbacks, iodev_cb); |
| } |
| |
| void audio_thread_add_callback(int fd, thread_callback cb, |
| void *data) |
| { |
| _audio_thread_add_callback(fd, cb, data, 0); |
| } |
| |
| void audio_thread_add_write_callback(int fd, thread_callback cb, |
| void *data) |
| { |
| _audio_thread_add_callback(fd, cb, data, 1); |
| } |
| |
| void audio_thread_rm_callback(int fd) |
| { |
| struct iodev_callback_list *iodev_cb; |
| |
| DL_FOREACH(iodev_callbacks, iodev_cb) { |
| if (iodev_cb->fd == fd) { |
| DL_DELETE(iodev_callbacks, iodev_cb); |
| free(iodev_cb); |
| return; |
| } |
| } |
| } |
| |
| void audio_thread_enable_callback(int fd, int enabled) |
| { |
| struct iodev_callback_list *iodev_cb; |
| |
| DL_FOREACH(iodev_callbacks, iodev_cb) { |
| if (iodev_cb->fd == fd) { |
| iodev_cb->enabled = !!enabled; |
| return; |
| } |
| } |
| } |
| |
| /* Sends a response (error code) from the audio thread to the main thread. |
| * Indicates that the last message sent to the audio thread has been handled |
| * with an error code of rc. |
| * Args: |
| * thread - thread responding to command. |
| * rc - Result code to send back to the main thread. |
| * Returns: |
| * The number of bytes written to the main thread. |
| */ |
| static int audio_thread_send_response(struct audio_thread *thread, int rc) |
| { |
| return write(thread->to_main_fds[1], &rc, sizeof(rc)); |
| } |
| |
| /* Reads a command from the main thread. Called from the playback/capture |
| * thread. This will read the next available command from the main thread and |
| * put it in buf. |
| * Args: |
| * thread - thread reading the command. |
| * buf - Message is stored here on return. |
| * max_len - maximum length of message to put into buf. |
| * Returns: |
| * 0 on success, negative error code on failure. |
| */ |
| static int audio_thread_read_command(struct audio_thread *thread, |
| uint8_t *buf, |
| size_t max_len) |
| { |
| int to_read, nread, rc; |
| struct audio_thread_msg *msg = (struct audio_thread_msg *)buf; |
| |
| /* Get the length of the message first */ |
| nread = read(thread->to_thread_fds[0], buf, sizeof(msg->length)); |
| if (nread < 0) |
| return nread; |
| if (msg->length > max_len) |
| return -ENOMEM; |
| |
| to_read = msg->length - nread; |
| rc = read(thread->to_thread_fds[0], &buf[0] + nread, to_read); |
| if (rc < 0) |
| return rc; |
| return 0; |
| } |
| |
| /* Builds an initial buffer to avoid an underrun. Adds min_level of latency. */ |
| static void fill_odevs_zeros_min_level(struct cras_iodev *odev) |
| { |
| cras_iodev_fill_odev_zeros(odev, odev->min_buffer_level); |
| } |
| |
| static void thread_rm_open_adev(struct audio_thread *thread, |
| struct open_dev *adev); |
| |
| static void delete_stream_from_dev(struct cras_iodev *dev, |
| struct cras_rstream *stream) |
| { |
| struct dev_stream *out; |
| |
| out = cras_iodev_rm_stream(dev, stream); |
| if (out) |
| dev_stream_destroy(out); |
| } |
| |
| /* Append a new stream to a specified set of iodevs. */ |
| static int append_stream(struct audio_thread *thread, |
| struct cras_rstream *stream, |
| struct cras_iodev **iodevs, |
| unsigned int num_iodevs) |
| { |
| struct open_dev *open_dev; |
| struct cras_iodev *dev; |
| struct dev_stream *out; |
| struct timespec init_cb_ts; |
| const struct timespec *stream_ts; |
| unsigned int i; |
| int rc = 0; |
| |
| for (i = 0; i < num_iodevs; i++) { |
| DL_SEARCH_SCALAR(thread->open_devs[stream->direction], open_dev, |
| dev, iodevs[i]); |
| if (!open_dev) |
| continue; |
| |
| dev = iodevs[i]; |
| DL_SEARCH_SCALAR(dev->streams, out, stream, stream); |
| if (out) |
| continue; |
| |
| /* If open device already has stream, get the first stream |
| * and use its next callback time to align with. Otherwise |
| * use the timestamp now as the initial callback time for |
| * new stream. |
| */ |
| if (dev->streams && |
| (stream_ts = dev_stream_next_cb_ts(dev->streams))) |
| init_cb_ts = *stream_ts; |
| else |
| clock_gettime(CLOCK_MONOTONIC_RAW, &init_cb_ts); |
| |
| out = dev_stream_create(stream, dev->info.idx, |
| dev->ext_format, dev, &init_cb_ts); |
| if (!out) { |
| rc = -EINVAL; |
| break; |
| } |
| |
| /* When the first input stream is added, flush the input buffer |
| * so that we can read from multiple input devices of the same |
| * buffer level. |
| */ |
| if ((stream->direction == CRAS_STREAM_INPUT) && !dev->streams) { |
| int num_flushed = dev->flush_buffer(dev); |
| if (num_flushed < 0) { |
| rc = num_flushed; |
| break; |
| } |
| } |
| |
| cras_iodev_add_stream(dev, out); |
| |
| /* For multiple inputs case, if the new stream is not the first |
| * one to append, copy the 1st stream's offset to it so that |
| * future read offsets can be aligned across all input streams |
| * to avoid the deadlock scenario when multiple streams reading |
| * from multiple devices. |
| */ |
| if ((stream->direction == CRAS_STREAM_INPUT) && |
| (dev->streams != out)) { |
| unsigned int offset = |
| cras_iodev_stream_offset(dev, dev->streams); |
| if (offset > stream->cb_threshold) |
| offset = stream->cb_threshold; |
| cras_iodev_stream_written(dev, out, offset); |
| |
| offset = cras_rstream_dev_offset(dev->streams->stream, |
| dev->info.idx); |
| if (offset > stream->cb_threshold) |
| offset = stream->cb_threshold; |
| cras_rstream_dev_offset_update(stream, offset, |
| dev->info.idx); |
| } |
| } |
| |
| if (rc) { |
| DL_FOREACH(thread->open_devs[stream->direction], open_dev) { |
| dev = open_dev->dev; |
| DL_SEARCH_SCALAR(dev->streams, out, stream, stream); |
| if (!out) |
| continue; |
| |
| cras_iodev_rm_stream(dev, stream); |
| dev_stream_destroy(out); |
| } |
| } |
| |
| return rc; |
| } |
| |
| /* Handles messages from main thread to add a new active device. */ |
| static int thread_add_open_dev(struct audio_thread *thread, |
| struct cras_iodev *iodev) |
| { |
| struct open_dev *adev; |
| |
| DL_SEARCH_SCALAR(thread->open_devs[iodev->direction], |
| adev, dev, iodev); |
| if (adev) |
| return -EEXIST; |
| |
| adev = (struct open_dev *)calloc(1, sizeof(*adev)); |
| adev->dev = iodev; |
| |
| /* |
| * Start output devices by padding the output. This avoids a burst of |
| * audio callbacks when the stream starts |
| */ |
| if (iodev->direction == CRAS_STREAM_OUTPUT) |
| fill_odevs_zeros_min_level(iodev); |
| else |
| adev->input_streaming = 0; |
| |
| ATLOG(atlog, |
| AUDIO_THREAD_DEV_ADDED, |
| iodev->info.idx, 0, 0); |
| |
| DL_APPEND(thread->open_devs[iodev->direction], adev); |
| |
| return 0; |
| } |
| |
| static struct open_dev *find_adev(struct open_dev *adev_list, |
| struct cras_iodev *dev) |
| { |
| struct open_dev *adev; |
| DL_FOREACH(adev_list, adev) |
| if (adev->dev == dev) |
| return adev; |
| return NULL; |
| } |
| |
| static void thread_rm_open_adev(struct audio_thread *thread, |
| struct open_dev *dev_to_rm) |
| { |
| enum CRAS_STREAM_DIRECTION dir = dev_to_rm->dev->direction; |
| struct open_dev *adev; |
| struct dev_stream *dev_stream; |
| |
| /* Do nothing if dev_to_rm wasn't already in the active dev list. */ |
| adev = find_adev(thread->open_devs[dir], dev_to_rm->dev); |
| if (!adev) |
| return; |
| |
| DL_DELETE(thread->open_devs[dir], dev_to_rm); |
| |
| ATLOG(atlog, |
| AUDIO_THREAD_DEV_REMOVED, |
| dev_to_rm->dev->info.idx, 0, 0); |
| |
| DL_FOREACH(dev_to_rm->dev->streams, dev_stream) { |
| cras_iodev_rm_stream(dev_to_rm->dev, dev_stream->stream); |
| dev_stream_destroy(dev_stream); |
| } |
| |
| free(dev_to_rm); |
| } |
| |
| /* Handles messages from the main thread to remove an active device. */ |
| static int thread_rm_open_dev(struct audio_thread *thread, |
| struct cras_iodev *iodev) |
| { |
| struct open_dev *adev = find_adev( |
| thread->open_devs[iodev->direction], iodev); |
| if (!adev) |
| return -EINVAL; |
| |
| thread_rm_open_adev(thread, adev); |
| return 0; |
| } |
| |
| /* Handles messages from the main thread to start ramping on a device. */ |
| static int thread_dev_start_ramp(struct audio_thread *thread, |
| struct cras_iodev *iodev, |
| enum CRAS_IODEV_RAMP_REQUEST request) |
| { |
| /* Do nothing if device wasn't already in the active dev list. */ |
| struct open_dev *adev = find_adev( |
| thread->open_devs[iodev->direction], iodev); |
| if (!adev) |
| return -EINVAL; |
| return cras_iodev_start_ramp(iodev, request); |
| } |
| |
| |
| /* Return non-zero if the stream is attached to any device. */ |
| static int thread_find_stream(struct audio_thread *thread, |
| struct cras_rstream *rstream) |
| { |
| struct open_dev *open_dev; |
| struct dev_stream *s; |
| |
| DL_FOREACH(thread->open_devs[rstream->direction], open_dev) { |
| DL_FOREACH(open_dev->dev->streams, s) { |
| if (s->stream == rstream) |
| return 1; |
| } |
| } |
| return 0; |
| } |
| |
| /* Remove stream from the audio thread. If this is the last stream to be |
| * removed close the device. |
| */ |
| static int thread_remove_stream(struct audio_thread *thread, |
| struct cras_rstream *stream, |
| struct cras_iodev *dev) |
| { |
| struct open_dev *open_dev; |
| struct timespec delay; |
| unsigned fetch_delay_msec; |
| |
| /* Metrics log the longest fetch delay of this stream. */ |
| if (timespec_after(&stream->longest_fetch_interval, |
| &stream->sleep_interval_ts)) { |
| subtract_timespecs(&stream->longest_fetch_interval, |
| &stream->sleep_interval_ts, |
| &delay); |
| fetch_delay_msec = delay.tv_sec * 1000 + |
| delay.tv_nsec / 1000000; |
| if (fetch_delay_msec) |
| cras_server_metrics_longest_fetch_delay( |
| fetch_delay_msec); |
| } |
| |
| ATLOG(atlog, |
| AUDIO_THREAD_STREAM_REMOVED, |
| stream->stream_id, 0, 0); |
| |
| if (dev == NULL) { |
| DL_FOREACH(thread->open_devs[stream->direction], open_dev) { |
| delete_stream_from_dev(open_dev->dev, stream); |
| } |
| } else { |
| delete_stream_from_dev(dev, stream); |
| } |
| |
| return 0; |
| } |
| |
| /* Handles the disconnect_stream message from the main thread. */ |
| static int thread_disconnect_stream(struct audio_thread* thread, |
| struct cras_rstream* stream, |
| struct cras_iodev *dev) |
| { |
| int rc; |
| |
| if (!thread_find_stream(thread, stream)) |
| return 0; |
| |
| rc = thread_remove_stream(thread, stream, dev); |
| |
| return rc; |
| } |
| |
| /* Initiates draining of a stream or returns the status of a draining stream. |
| * If the stream has completed draining the thread forfeits ownership and must |
| * never reference it again. Returns the number of milliseconds it will take to |
| * finish draining, a minimum of one ms if any samples remain. |
| */ |
| static int thread_drain_stream_ms_remaining(struct audio_thread *thread, |
| struct cras_rstream *rstream) |
| { |
| int fr_in_buff; |
| struct cras_audio_shm *shm; |
| |
| if (rstream->direction != CRAS_STREAM_OUTPUT) |
| return 0; |
| |
| shm = cras_rstream_output_shm(rstream); |
| fr_in_buff = cras_shm_get_frames(shm); |
| |
| if (fr_in_buff <= 0) |
| return 0; |
| |
| cras_rstream_set_is_draining(rstream, 1); |
| |
| return 1 + cras_frames_to_ms(fr_in_buff, rstream->format.frame_rate); |
| } |
| |
| /* Handles a request to begin draining and return the amount of time left to |
| * draing a stream. |
| */ |
| static int thread_drain_stream(struct audio_thread *thread, |
| struct cras_rstream *rstream) |
| { |
| int ms_left; |
| |
| if (!thread_find_stream(thread, rstream)) |
| return 0; |
| |
| ms_left = thread_drain_stream_ms_remaining(thread, rstream); |
| if (ms_left == 0) |
| thread_remove_stream(thread, rstream, NULL); |
| |
| return ms_left; |
| } |
| |
| /* Handles the add_stream message from the main thread. */ |
| static int thread_add_stream(struct audio_thread *thread, |
| struct cras_rstream *stream, |
| struct cras_iodev **iodevs, |
| unsigned int num_iodevs) |
| { |
| int rc; |
| |
| rc = append_stream(thread, stream, iodevs, num_iodevs); |
| if (rc < 0) |
| return rc; |
| |
| ATLOG(atlog, |
| AUDIO_THREAD_STREAM_ADDED, |
| stream->stream_id, |
| num_iodevs ? iodevs[0]->info.idx : 0, |
| num_iodevs); |
| return 0; |
| } |
| |
| /* Reads any pending audio message from the socket. */ |
| static void flush_old_aud_messages(struct cras_audio_shm *shm, int fd) |
| { |
| struct audio_message msg; |
| struct pollfd pollfd; |
| int err; |
| |
| pollfd.fd = fd; |
| pollfd.events = POLLIN; |
| |
| do { |
| err = poll(&pollfd, 1, 0); |
| if (pollfd.revents & POLLIN) { |
| err = read(fd, &msg, sizeof(msg)); |
| cras_shm_set_callback_pending(shm, 0); |
| } |
| } while (err > 0); |
| } |
| |
| /* Asks any stream with room for more data. Sets the time stamp for all streams. |
| * Args: |
| * thread - The thread to fetch samples for. |
| * adev - The output device streams are attached to. |
| * Returns: |
| * 0 on success, negative error on failure. If failed, can assume that all |
| * streams have been removed from the device. |
| */ |
| static int fetch_streams(struct audio_thread *thread, |
| struct open_dev *adev) |
| { |
| struct dev_stream *dev_stream; |
| struct cras_iodev *odev = adev->dev; |
| int rc; |
| int delay; |
| |
| delay = cras_iodev_delay_frames(odev); |
| if (delay < 0) |
| return delay; |
| |
| DL_FOREACH(adev->dev->streams, dev_stream) { |
| struct cras_rstream *rstream = dev_stream->stream; |
| struct cras_audio_shm *shm = |
| cras_rstream_output_shm(rstream); |
| int fd = cras_rstream_get_audio_fd(rstream); |
| const struct timespec *next_cb_ts; |
| struct timespec now; |
| |
| clock_gettime(CLOCK_MONOTONIC_RAW, &now); |
| |
| if (cras_shm_callback_pending(shm) && fd >= 0) { |
| flush_old_aud_messages(shm, fd); |
| cras_rstream_record_fetch_interval(dev_stream->stream, |
| &now); |
| } |
| |
| if (cras_shm_get_frames(shm) < 0) |
| cras_rstream_set_is_draining(rstream, 1); |
| |
| if (cras_rstream_get_is_draining(dev_stream->stream)) |
| continue; |
| |
| next_cb_ts = dev_stream_next_cb_ts(dev_stream); |
| if (!next_cb_ts) |
| continue; |
| |
| /* Check if it's time to get more data from this stream. |
| * Allowing for waking up half a little early. */ |
| add_timespecs(&now, &playback_wake_fuzz_ts); |
| if (!timespec_after(&now, next_cb_ts)) |
| continue; |
| |
| if (!dev_stream_can_fetch(dev_stream)) { |
| ATLOG( |
| atlog, AUDIO_THREAD_STREAM_SKIP_CB, |
| rstream->stream_id, |
| shm->area->write_offset[0], |
| shm->area->write_offset[1]); |
| continue; |
| } |
| |
| dev_stream_set_delay(dev_stream, delay); |
| |
| ATLOG( |
| atlog, |
| AUDIO_THREAD_FETCH_STREAM, |
| rstream->stream_id, |
| cras_rstream_get_cb_threshold(rstream), delay); |
| |
| rc = dev_stream_request_playback_samples(dev_stream, &now); |
| if (rc < 0) { |
| syslog(LOG_ERR, "fetch err: %d for %x", |
| rc, rstream->stream_id); |
| cras_rstream_set_is_draining(rstream, 1); |
| } |
| } |
| |
| return 0; |
| } |
| |
| /* Fill the buffer with samples from the attached streams. |
| * Args: |
| * thread - The thread object the device is attached to. |
| * adev - The device to write to. |
| * dst - The buffer to put the samples in (returned from snd_pcm_mmap_begin) |
| * write_limit - The maximum number of frames to write to dst. |
| * |
| * Returns: |
| * The number of frames rendered on success, a negative error code otherwise. |
| * This number of frames is the minimum of the amount of frames each stream |
| * could provide which is the maximum that can currently be rendered. |
| */ |
| static int write_streams(struct audio_thread *thread, |
| struct open_dev *adev, |
| uint8_t *dst, |
| size_t write_limit) |
| { |
| struct cras_iodev *odev = adev->dev; |
| struct dev_stream *curr; |
| unsigned int max_offset = 0; |
| unsigned int frame_bytes = cras_get_format_bytes(odev->ext_format); |
| unsigned int num_playing = 0; |
| unsigned int drain_limit = write_limit; |
| |
| /* Mix as much as we can, the minimum fill level of any stream. */ |
| max_offset = cras_iodev_max_stream_offset(odev); |
| |
| /* Mix as much as we can, the minimum fill level of any stream. */ |
| DL_FOREACH(adev->dev->streams, curr) { |
| int dev_frames; |
| |
| /* If this is a single output dev stream, updates the latest |
| * number of frames for playback. */ |
| if (dev_stream_attached_devs(curr) == 1) |
| dev_stream_update_frames(curr); |
| |
| dev_frames = dev_stream_playback_frames(curr); |
| if (dev_frames < 0) { |
| thread_remove_stream(thread, curr->stream, NULL); |
| continue; |
| } |
| ATLOG(atlog, |
| AUDIO_THREAD_WRITE_STREAMS_STREAM, |
| curr->stream->stream_id, |
| dev_frames, |
| cras_shm_callback_pending(cras_rstream_output_shm(curr->stream))); |
| if (cras_rstream_get_is_draining(curr->stream)) { |
| drain_limit = MIN((size_t)dev_frames, drain_limit); |
| if (!dev_frames) |
| thread_remove_stream(thread, curr->stream, |
| NULL); |
| } else { |
| write_limit = MIN((size_t)dev_frames, write_limit); |
| num_playing++; |
| } |
| } |
| |
| if (!num_playing) |
| write_limit = drain_limit; |
| |
| if (write_limit > max_offset) |
| memset(dst + max_offset * frame_bytes, 0, |
| (write_limit - max_offset) * frame_bytes); |
| |
| ATLOG(atlog, AUDIO_THREAD_WRITE_STREAMS_MIX, |
| write_limit, max_offset, 0); |
| |
| DL_FOREACH(adev->dev->streams, curr) { |
| unsigned int offset; |
| int nwritten; |
| |
| offset = cras_iodev_stream_offset(odev, curr); |
| if (offset >= write_limit) |
| continue; |
| nwritten = dev_stream_mix(curr, odev->ext_format, |
| dst + frame_bytes * offset, |
| write_limit - offset); |
| |
| if (nwritten < 0) { |
| thread_remove_stream(thread, curr->stream, NULL); |
| continue; |
| } |
| |
| cras_iodev_stream_written(odev, curr, nwritten); |
| } |
| |
| write_limit = cras_iodev_all_streams_written(odev); |
| |
| ATLOG(atlog, AUDIO_THREAD_WRITE_STREAMS_MIXED, |
| write_limit, 0, 0); |
| |
| return write_limit; |
| } |
| |
| /* Gets the max delay frames of open input devices. */ |
| static int input_delay_frames(struct open_dev *adevs) |
| { |
| struct open_dev *adev; |
| int delay; |
| int max_delay = 0; |
| |
| DL_FOREACH(adevs, adev) { |
| if (!cras_iodev_is_open(adev->dev)) |
| continue; |
| delay = cras_iodev_delay_frames(adev->dev); |
| if (delay < 0) |
| return delay; |
| if (delay > max_delay) |
| max_delay = delay; |
| } |
| return max_delay; |
| } |
| |
| /* Stop the playback thread */ |
| static void terminate_pb_thread() |
| { |
| pthread_exit(0); |
| } |
| |
| static void append_dev_dump_info(struct audio_dev_debug_info *di, |
| struct open_dev *adev) |
| { |
| struct cras_audio_format *fmt = adev->dev->ext_format; |
| strncpy(di->dev_name, adev->dev->info.name, sizeof(di->dev_name)); |
| di->buffer_size = adev->dev->buffer_size; |
| di->min_buffer_level = adev->dev->min_buffer_level; |
| di->min_cb_level = adev->dev->min_cb_level; |
| di->max_cb_level = adev->dev->max_cb_level; |
| di->direction = adev->dev->direction; |
| di->num_underruns = cras_iodev_get_num_underruns(adev->dev); |
| di->num_severe_underruns = cras_iodev_get_num_severe_underruns( |
| adev->dev); |
| if (fmt) { |
| di->frame_rate = fmt->frame_rate; |
| di->num_channels = fmt->num_channels; |
| di->est_rate_ratio = cras_iodev_get_est_rate_ratio(adev->dev); |
| } else { |
| di->frame_rate = 0; |
| di->num_channels = 0; |
| di->est_rate_ratio = 0; |
| } |
| } |
| |
| /* Put stream info for the given stream into the info struct. */ |
| static void append_stream_dump_info(struct audio_debug_info *info, |
| struct dev_stream *stream, |
| unsigned int dev_idx, |
| int index) |
| { |
| struct audio_stream_debug_info *si; |
| |
| si = &info->streams[index]; |
| |
| si->stream_id = stream->stream->stream_id; |
| si->dev_idx = dev_idx; |
| si->direction = stream->stream->direction; |
| si->stream_type = stream->stream->stream_type; |
| si->buffer_frames = stream->stream->buffer_frames; |
| si->cb_threshold = stream->stream->cb_threshold; |
| si->frame_rate = stream->stream->format.frame_rate; |
| si->num_channels = stream->stream->format.num_channels; |
| memcpy(si->channel_layout, stream->stream->format.channel_layout, |
| sizeof(si->channel_layout)); |
| si->longest_fetch_sec = stream->stream->longest_fetch_interval.tv_sec; |
| si->longest_fetch_nsec = stream->stream->longest_fetch_interval.tv_nsec; |
| si->num_overruns = cras_shm_num_overruns(&stream->stream->shm); |
| |
| longest_wake.tv_sec = 0; |
| longest_wake.tv_nsec = 0; |
| } |
| |
| /* Handle a message sent to the playback thread */ |
| static int handle_playback_thread_message(struct audio_thread *thread) |
| { |
| uint8_t buf[256]; |
| struct audio_thread_msg *msg = (struct audio_thread_msg *)buf; |
| int ret = 0; |
| int err; |
| |
| err = audio_thread_read_command(thread, buf, 256); |
| if (err < 0) |
| return err; |
| |
| ATLOG(atlog, AUDIO_THREAD_PB_MSG, msg->id, 0, 0); |
| |
| switch (msg->id) { |
| case AUDIO_THREAD_ADD_STREAM: { |
| struct audio_thread_add_rm_stream_msg *amsg; |
| amsg = (struct audio_thread_add_rm_stream_msg *)msg; |
| ATLOG( |
| atlog, |
| AUDIO_THREAD_WRITE_STREAMS_WAIT, |
| amsg->stream->stream_id, 0, 0); |
| ret = thread_add_stream(thread, amsg->stream, amsg->devs, |
| amsg->num_devs); |
| break; |
| } |
| case AUDIO_THREAD_DISCONNECT_STREAM: { |
| struct audio_thread_add_rm_stream_msg *rmsg; |
| |
| rmsg = (struct audio_thread_add_rm_stream_msg *)msg; |
| |
| ret = thread_disconnect_stream(thread, rmsg->stream, |
| rmsg->devs[0]); |
| break; |
| } |
| case AUDIO_THREAD_ADD_OPEN_DEV: { |
| struct audio_thread_open_device_msg *rmsg; |
| |
| rmsg = (struct audio_thread_open_device_msg *)msg; |
| ret = thread_add_open_dev(thread, rmsg->dev); |
| break; |
| } |
| case AUDIO_THREAD_RM_OPEN_DEV: { |
| struct audio_thread_open_device_msg *rmsg; |
| |
| rmsg = (struct audio_thread_open_device_msg *)msg; |
| ret = thread_rm_open_dev(thread, rmsg->dev); |
| break; |
| } |
| case AUDIO_THREAD_STOP: |
| ret = 0; |
| err = audio_thread_send_response(thread, ret); |
| if (err < 0) |
| return err; |
| terminate_pb_thread(); |
| break; |
| case AUDIO_THREAD_DUMP_THREAD_INFO: { |
| struct dev_stream *curr; |
| struct open_dev *adev; |
| struct audio_thread_dump_debug_info_msg *dmsg; |
| struct audio_debug_info *info; |
| unsigned int num_streams = 0; |
| unsigned int num_devs = 0; |
| |
| ret = 0; |
| dmsg = (struct audio_thread_dump_debug_info_msg *)msg; |
| info = dmsg->info; |
| |
| /* Go through all open devices. */ |
| DL_FOREACH(thread->open_devs[CRAS_STREAM_OUTPUT], adev) { |
| append_dev_dump_info(&info->devs[num_devs], adev); |
| if (++num_devs == MAX_DEBUG_DEVS) |
| break; |
| DL_FOREACH(adev->dev->streams, curr) { |
| if (num_streams == MAX_DEBUG_STREAMS) |
| break; |
| append_stream_dump_info(info, curr, |
| adev->dev->info.idx, |
| num_streams++); |
| } |
| } |
| DL_FOREACH(thread->open_devs[CRAS_STREAM_INPUT], adev) { |
| if (num_devs == MAX_DEBUG_DEVS) |
| break; |
| append_dev_dump_info(&info->devs[num_devs], adev); |
| DL_FOREACH(adev->dev->streams, curr) { |
| if (num_streams == MAX_DEBUG_STREAMS) |
| break; |
| append_stream_dump_info(info, curr, |
| adev->dev->info.idx, |
| num_streams++); |
| } |
| ++num_devs; |
| } |
| info->num_devs = num_devs; |
| |
| info->num_streams = num_streams; |
| |
| memcpy(&info->log, atlog, sizeof(info->log)); |
| break; |
| } |
| case AUDIO_THREAD_DRAIN_STREAM: { |
| struct audio_thread_add_rm_stream_msg *rmsg; |
| |
| rmsg = (struct audio_thread_add_rm_stream_msg *)msg; |
| ret = thread_drain_stream(thread, rmsg->stream); |
| break; |
| } |
| case AUDIO_THREAD_REMOVE_CALLBACK: { |
| struct audio_thread_rm_callback_msg *rmsg; |
| |
| rmsg = (struct audio_thread_rm_callback_msg *)msg; |
| audio_thread_rm_callback(rmsg->fd); |
| break; |
| } |
| case AUDIO_THREAD_CONFIG_GLOBAL_REMIX: { |
| struct audio_thread_config_global_remix *rmsg; |
| void *rsp; |
| |
| /* Respond the pointer to the old remix converter, so it can be |
| * freed later in main thread. */ |
| rsp = (void *)remix_converter; |
| |
| rmsg = (struct audio_thread_config_global_remix *)msg; |
| remix_converter = rmsg->fmt_conv; |
| |
| return write(thread->to_main_fds[1], &rsp, sizeof(rsp)); |
| } |
| case AUDIO_THREAD_DEV_START_RAMP: { |
| struct audio_thread_dev_start_ramp_msg *rmsg; |
| |
| rmsg = (struct audio_thread_dev_start_ramp_msg*)msg; |
| ret = thread_dev_start_ramp(thread, rmsg->dev, rmsg->request); |
| break; |
| } |
| default: |
| ret = -EINVAL; |
| break; |
| } |
| |
| err = audio_thread_send_response(thread, ret); |
| if (err < 0) |
| return err; |
| return ret; |
| } |
| |
| /* Fills the time that the next stream needs to be serviced. */ |
| static int get_next_stream_wake_from_list(struct dev_stream *streams, |
| struct timespec *min_ts) |
| { |
| struct dev_stream *dev_stream; |
| int ret = 0; /* The total number of streams to wait on. */ |
| |
| DL_FOREACH(streams, dev_stream) { |
| const struct timespec *next_cb_ts; |
| |
| if (cras_rstream_get_is_draining(dev_stream->stream) && |
| dev_stream_playback_frames(dev_stream) <= 0) |
| continue; |
| if (!dev_stream_can_fetch(dev_stream)) |
| continue; |
| |
| next_cb_ts = dev_stream_next_cb_ts(dev_stream); |
| if (!next_cb_ts) |
| continue; |
| |
| ATLOG(atlog, |
| AUDIO_THREAD_STREAM_SLEEP_TIME, |
| dev_stream->stream->stream_id, |
| next_cb_ts->tv_sec, |
| next_cb_ts->tv_nsec); |
| if (timespec_after(min_ts, next_cb_ts)) |
| *min_ts = *next_cb_ts; |
| ret++; |
| } |
| |
| return ret; |
| } |
| |
| static int get_next_output_wake(struct audio_thread *thread, |
| struct timespec *min_ts, |
| const struct timespec *now) |
| { |
| struct open_dev *adev; |
| struct timespec sleep_time; |
| double est_rate; |
| int ret = 0; |
| unsigned int frames_to_play_in_sleep; |
| unsigned int hw_level = 0; |
| |
| DL_FOREACH(thread->open_devs[CRAS_STREAM_OUTPUT], adev) |
| ret += get_next_stream_wake_from_list( |
| adev->dev->streams, |
| min_ts); |
| |
| DL_FOREACH(thread->open_devs[CRAS_STREAM_OUTPUT], adev) { |
| if (!cras_iodev_odev_should_wake(adev->dev)) |
| continue; |
| |
| frames_to_play_in_sleep = cras_iodev_frames_to_play_in_sleep( |
| adev->dev, &hw_level, &adev->wake_ts); |
| if (!timespec_is_nonzero(&adev->wake_ts)) |
| adev->wake_ts = *now; |
| |
| est_rate = adev->dev->ext_format->frame_rate * |
| cras_iodev_get_est_rate_ratio(adev->dev); |
| |
| ATLOG(atlog, |
| AUDIO_THREAD_SET_DEV_WAKE, |
| adev->dev->info.idx, |
| hw_level, |
| frames_to_play_in_sleep); |
| |
| cras_frames_to_time_precise( |
| frames_to_play_in_sleep, |
| est_rate, |
| &sleep_time); |
| |
| add_timespecs(&adev->wake_ts, &sleep_time); |
| |
| ret++; |
| ATLOG(atlog, |
| AUDIO_THREAD_DEV_SLEEP_TIME, |
| adev->dev->info.idx, |
| adev->wake_ts.tv_sec, |
| adev->wake_ts.tv_nsec); |
| if (timespec_after(min_ts, &adev->wake_ts)) |
| *min_ts = adev->wake_ts; |
| } |
| |
| return ret; |
| } |
| |
| static int input_adev_ignore_wake(const struct open_dev *adev) |
| { |
| if (!cras_iodev_is_open(adev->dev)) |
| return 1; |
| |
| if (!adev->dev->active_node) |
| return 1; |
| |
| if (adev->dev->active_node->type == CRAS_NODE_TYPE_HOTWORD && |
| !adev->input_streaming) |
| return 1; |
| |
| return 0; |
| } |
| |
| static int get_next_input_wake(struct audio_thread *thread, |
| struct timespec *min_ts, |
| const struct timespec *now) |
| { |
| struct open_dev *adev; |
| int ret = 0; /* The total number of devices to wait on. */ |
| |
| DL_FOREACH(thread->open_devs[CRAS_STREAM_INPUT], adev) { |
| if (input_adev_ignore_wake(adev)) |
| continue; |
| ret++; |
| ATLOG(atlog, |
| AUDIO_THREAD_DEV_SLEEP_TIME, |
| adev->dev->info.idx, |
| adev->wake_ts.tv_sec, |
| adev->wake_ts.tv_nsec); |
| if (timespec_after(min_ts, &adev->wake_ts)) |
| *min_ts = adev->wake_ts; |
| } |
| |
| return ret; |
| } |
| |
| static int output_stream_fetch(struct audio_thread *thread) |
| { |
| struct open_dev *odev_list = thread->open_devs[CRAS_STREAM_OUTPUT]; |
| struct open_dev *adev; |
| |
| DL_FOREACH(odev_list, adev) { |
| if (!cras_iodev_is_open(adev->dev)) |
| continue; |
| fetch_streams(thread, adev); |
| } |
| |
| return 0; |
| } |
| |
| static int wait_pending_output_streams(struct audio_thread *thread) |
| { |
| /* TODO(dgreid) - is this needed? */ |
| return 0; |
| } |
| |
| /* Gets the master device which the stream is attached to. */ |
| static inline |
| struct cras_iodev *get_master_dev(const struct dev_stream *stream) |
| { |
| return (struct cras_iodev *)stream->stream->master_dev.dev_ptr; |
| } |
| |
| /* Updates the estimated sample rate of open device to all attached |
| * streams. |
| */ |
| static void update_estimated_rate(struct audio_thread *thread, |
| struct open_dev *adev) |
| { |
| struct cras_iodev *master_dev; |
| struct cras_iodev *dev = adev->dev; |
| struct dev_stream *dev_stream; |
| |
| DL_FOREACH(dev->streams, dev_stream) { |
| master_dev = get_master_dev(dev_stream); |
| if (master_dev == NULL) { |
| syslog(LOG_ERR, "Fail to find master open dev."); |
| continue; |
| } |
| |
| dev_stream_set_dev_rate(dev_stream, |
| dev->ext_format->frame_rate, |
| cras_iodev_get_est_rate_ratio(dev), |
| cras_iodev_get_est_rate_ratio(master_dev), |
| adev->coarse_rate_adjust); |
| } |
| } |
| |
| /* Returns 0 on success negative error on device failure. */ |
| static int write_output_samples(struct audio_thread *thread, |
| struct open_dev *adev) |
| { |
| struct cras_iodev *odev = adev->dev; |
| unsigned int hw_level; |
| struct timespec hw_tstamp; |
| unsigned int frames, fr_to_req; |
| snd_pcm_sframes_t written; |
| snd_pcm_uframes_t total_written = 0; |
| int rc; |
| uint8_t *dst = NULL; |
| struct cras_audio_area *area = NULL; |
| |
| /* Possibly fill zeros for no_stream state and possibly transit state. |
| */ |
| rc = cras_iodev_prepare_output_before_write_samples(odev); |
| if (rc < 0) { |
| syslog(LOG_ERR, "Failed to prepare output dev for write"); |
| return rc; |
| } |
| |
| if (cras_iodev_state(odev) != CRAS_IODEV_STATE_NORMAL_RUN) |
| return 0; |
| |
| rc = cras_iodev_frames_queued(odev, &hw_tstamp); |
| if (rc < 0) |
| return rc; |
| hw_level = rc; |
| |
| ATLOG(atlog, AUDIO_THREAD_FILL_AUDIO_TSTAMP, adev->dev->info.idx, |
| hw_tstamp.tv_sec, hw_tstamp.tv_nsec); |
| if (timespec_is_nonzero(&hw_tstamp)) { |
| if (hw_level < odev->min_cb_level / 2) |
| adev->coarse_rate_adjust = 1; |
| else if (hw_level > odev->max_cb_level * 2) |
| adev->coarse_rate_adjust = -1; |
| else |
| adev->coarse_rate_adjust = 0; |
| |
| if (cras_iodev_update_rate(odev, hw_level, &hw_tstamp)) |
| update_estimated_rate(thread, adev); |
| } |
| ATLOG(atlog, AUDIO_THREAD_FILL_AUDIO, |
| adev->dev->info.idx, hw_level, 0); |
| |
| /* Don't request more than hardware can hold. Note that min_buffer_level |
| * has been subtracted from the actual hw_level so we need to take it |
| * into account here. */ |
| fr_to_req = cras_iodev_buffer_avail(odev, hw_level); |
| |
| /* Have to loop writing to the device, will be at most 2 loops, this |
| * only happens when the circular buffer is at the end and returns us a |
| * partial area to write to from mmap_begin */ |
| while (total_written < fr_to_req) { |
| frames = fr_to_req - total_written; |
| rc = cras_iodev_get_output_buffer(odev, &area, &frames); |
| if (rc < 0) |
| return rc; |
| |
| /* TODO(dgreid) - This assumes interleaved audio. */ |
| dst = area->channels[0].buf; |
| written = write_streams(thread, adev, dst, frames); |
| if (written < 0) /* pcm has been closed */ |
| return (int)written; |
| |
| if (written < (snd_pcm_sframes_t)frames) |
| /* Got all the samples from client that we can, but it |
| * won't fill the request. */ |
| fr_to_req = 0; /* break out after committing samples */ |
| |
| rc = cras_iodev_put_output_buffer(odev, dst, written); |
| if (rc < 0) |
| return rc; |
| total_written += written; |
| } |
| |
| /* Empty hardware and nothing written, zero fill it if it is running. */ |
| if (!hw_level && !total_written && |
| odev->min_cb_level < odev->buffer_size) |
| cras_iodev_output_underrun(odev); |
| |
| ATLOG(atlog, AUDIO_THREAD_FILL_AUDIO_DONE, |
| hw_level, total_written, odev->min_cb_level); |
| return 0; |
| } |
| |
| static int do_playback(struct audio_thread *thread) |
| { |
| struct open_dev *adev; |
| struct dev_stream *curr; |
| int rc; |
| |
| /* For multiple output case, update the number of queued frames in shm |
| * of all streams before starting write output samples. */ |
| adev = thread->open_devs[CRAS_STREAM_OUTPUT]; |
| if (adev && adev->next) { |
| DL_FOREACH(thread->open_devs[CRAS_STREAM_OUTPUT], adev) { |
| DL_FOREACH(adev->dev->streams, curr) |
| dev_stream_update_frames(curr); |
| } |
| } |
| |
| DL_FOREACH(thread->open_devs[CRAS_STREAM_OUTPUT], adev) { |
| if (!cras_iodev_is_open(adev->dev)) |
| continue; |
| |
| rc = write_output_samples(thread, adev); |
| if (rc < 0) { |
| if (rc == -EPIPE) { |
| /* Handle severe underrun. */ |
| ATLOG(atlog, AUDIO_THREAD_SEVERE_UNDERRUN, |
| adev->dev->info.idx, 0, 0); |
| cras_iodev_reset_request(adev->dev); |
| } else { |
| /* Device error, close it. */ |
| thread_rm_open_adev(thread, adev); |
| } |
| } |
| } |
| |
| /* TODO(dgreid) - once per rstream, not once per dev_stream. */ |
| DL_FOREACH(thread->open_devs[CRAS_STREAM_OUTPUT], adev) { |
| struct dev_stream *stream; |
| if (!cras_iodev_is_open(adev->dev)) |
| continue; |
| DL_FOREACH(adev->dev->streams, stream) { |
| dev_stream_playback_update_rstream(stream); |
| } |
| } |
| |
| return 0; |
| } |
| |
| /* Gets the minimum amount of space available for writing across all streams. |
| * Args: |
| * adev - The device to capture from. |
| * write_limit - Initial limit to number of frames to capture. |
| */ |
| static unsigned int get_stream_limit_set_delay(struct open_dev *adev, |
| unsigned int write_limit) |
| { |
| struct cras_rstream *rstream; |
| struct cras_audio_shm *shm; |
| struct dev_stream *stream; |
| int delay; |
| unsigned int avail; |
| |
| /* TODO(dgreid) - Setting delay from last dev only. */ |
| delay = input_delay_frames(adev); |
| |
| DL_FOREACH(adev->dev->streams, stream) { |
| rstream = stream->stream; |
| |
| shm = cras_rstream_input_shm(rstream); |
| if (cras_shm_check_write_overrun(shm)) |
| ATLOG(atlog, AUDIO_THREAD_READ_OVERRUN, |
| adev->dev->info.idx, rstream->stream_id, |
| shm->area->num_overruns); |
| dev_stream_set_delay(stream, delay); |
| avail = dev_stream_capture_avail(stream); |
| write_limit = MIN(write_limit, avail); |
| } |
| |
| return write_limit; |
| } |
| |
| /* Read samples from an input device to the specified stream. |
| * Args: |
| * adev - The device to capture samples from. |
| * Returns 0 on success. |
| */ |
| static int capture_to_streams(struct audio_thread *thread, |
| struct open_dev *adev) |
| { |
| struct cras_iodev *idev = adev->dev; |
| snd_pcm_uframes_t remainder, hw_level, cap_limit; |
| struct timespec hw_tstamp; |
| int rc; |
| |
| rc = cras_iodev_frames_queued(idev, &hw_tstamp); |
| if (rc < 0) |
| return rc; |
| hw_level = rc; |
| |
| ATLOG(atlog, AUDIO_THREAD_READ_AUDIO_TSTAMP, idev->info.idx, |
| hw_tstamp.tv_sec, hw_tstamp.tv_nsec); |
| if (timespec_is_nonzero(&hw_tstamp)) { |
| if (hw_level) |
| adev->input_streaming = 1; |
| |
| if (hw_level < idev->min_cb_level / 2) |
| adev->coarse_rate_adjust = 1; |
| else if (hw_level > idev->max_cb_level * 2) |
| adev->coarse_rate_adjust = -1; |
| else |
| adev->coarse_rate_adjust = 0; |
| if (cras_iodev_update_rate(idev, hw_level, &hw_tstamp)) |
| update_estimated_rate(thread, adev); |
| } |
| |
| cap_limit = get_stream_limit_set_delay(adev, hw_level); |
| remainder = MIN(hw_level, cap_limit); |
| |
| ATLOG(atlog, AUDIO_THREAD_READ_AUDIO, |
| idev->info.idx, hw_level, remainder); |
| |
| if (cras_iodev_state(idev) != CRAS_IODEV_STATE_NORMAL_RUN) |
| return 0; |
| |
| while (remainder > 0) { |
| struct cras_audio_area *area = NULL; |
| struct dev_stream *stream; |
| unsigned int nread, total_read; |
| |
| nread = remainder; |
| |
| rc = cras_iodev_get_input_buffer(idev, &area, &nread); |
| if (rc < 0 || nread == 0) |
| return rc; |
| |
| DL_FOREACH(adev->dev->streams, stream) { |
| unsigned int this_read; |
| unsigned int area_offset; |
| |
| area_offset = cras_iodev_stream_offset(idev, stream); |
| this_read = dev_stream_capture( |
| stream, area, area_offset, |
| cras_iodev_get_software_gain_scaler(idev)); |
| |
| cras_iodev_stream_written(idev, stream, this_read); |
| } |
| if (adev->dev->streams) |
| total_read = cras_iodev_all_streams_written(idev); |
| else |
| total_read = nread; /* No streams, drop. */ |
| |
| rc = cras_iodev_put_input_buffer(idev, total_read); |
| if (rc < 0) |
| return rc; |
| remainder -= nread; |
| |
| if (total_read < nread) |
| break; |
| } |
| |
| ATLOG(atlog, AUDIO_THREAD_READ_AUDIO_DONE, |
| remainder, 0, 0); |
| |
| return 0; |
| } |
| |
| static int do_capture(struct audio_thread *thread) |
| { |
| struct open_dev *idev_list = thread->open_devs[CRAS_STREAM_INPUT]; |
| struct open_dev *adev; |
| |
| DL_FOREACH(idev_list, adev) { |
| if (!cras_iodev_is_open(adev->dev)) |
| continue; |
| if (capture_to_streams(thread, adev) < 0) |
| thread_rm_open_adev(thread, adev); |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * Set wake_ts for this device to be the earliest wake up time for |
| * dev_streams. |
| */ |
| static int set_input_dev_wake_ts(struct open_dev *adev) |
| { |
| int rc; |
| struct timespec level_tstamp, wake_time_out, min_ts, now; |
| unsigned int curr_level; |
| struct dev_stream *stream; |
| |
| /* Limit the sleep time to 20 seconds. */ |
| min_ts.tv_sec = 20; |
| min_ts.tv_nsec = 0; |
| clock_gettime(CLOCK_MONOTONIC_RAW, &now); |
| add_timespecs(&min_ts, &now); |
| |
| curr_level = cras_iodev_frames_queued(adev->dev, &level_tstamp); |
| if (!timespec_is_nonzero(&level_tstamp)) |
| clock_gettime(CLOCK_MONOTONIC_RAW, &level_tstamp); |
| |
| /* |
| * Loop through streams to find the earliest time audio thread |
| * should wake up. |
| */ |
| DL_FOREACH(adev->dev->streams, stream) { |
| rc = dev_stream_wake_time( |
| stream, |
| curr_level, |
| &level_tstamp, |
| &wake_time_out); |
| |
| if (rc < 0) |
| return rc; |
| |
| if (timespec_after(&min_ts, &wake_time_out)) { |
| min_ts = wake_time_out; |
| } |
| } |
| adev->wake_ts = min_ts; |
| return 0; |
| } |
| |
| static int send_captured_samples(struct audio_thread *thread) |
| { |
| struct open_dev *idev_list = thread->open_devs[CRAS_STREAM_INPUT]; |
| struct open_dev *adev; |
| int rc; |
| |
| // TODO(dgreid) - once per rstream, not once per dev_stream. |
| DL_FOREACH(idev_list, adev) { |
| struct dev_stream *stream; |
| |
| if (!cras_iodev_is_open(adev->dev)) |
| continue; |
| |
| /* Post samples to rstream if there are enough samples. */ |
| DL_FOREACH(adev->dev->streams, stream) { |
| dev_stream_capture_update_rstream(stream); |
| } |
| |
| /* Set wake_ts for this device. */ |
| rc = set_input_dev_wake_ts(adev); |
| if (rc < 0) |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| /* Reads and/or writes audio sampels from/to the devices. */ |
| static int stream_dev_io(struct audio_thread *thread) |
| { |
| output_stream_fetch(thread); |
| do_capture(thread); |
| send_captured_samples(thread); |
| wait_pending_output_streams(thread); |
| do_playback(thread); |
| |
| return 0; |
| } |
| |
| int fill_next_sleep_interval(struct audio_thread *thread, struct timespec *ts) |
| { |
| struct timespec min_ts; |
| struct timespec now; |
| int ret; /* The sum of active streams and devices. */ |
| |
| ts->tv_sec = 0; |
| ts->tv_nsec = 0; |
| /* Limit the sleep time to 20 seconds. */ |
| min_ts.tv_sec = 20; |
| min_ts.tv_nsec = 0; |
| clock_gettime(CLOCK_MONOTONIC_RAW, &now); |
| add_timespecs(&min_ts, &now); |
| ret = get_next_output_wake(thread, &min_ts, &now); |
| ret += get_next_input_wake(thread, &min_ts, &now); |
| if (timespec_after(&min_ts, &now)) |
| subtract_timespecs(&min_ts, &now, ts); |
| |
| return ret; |
| } |
| |
| /* For playback, fill the audio buffer when needed, for capture, pull out |
| * samples when they are ready. |
| * This thread will attempt to run at a high priority to allow for low latency |
| * streams. This thread sleeps while the device plays back or captures audio, |
| * it will wake up as little as it can while avoiding xruns. It can also be |
| * woken by sending it a message using the "audio_thread_post_message" function. |
| */ |
| static void *audio_io_thread(void *arg) |
| { |
| struct audio_thread *thread = (struct audio_thread *)arg; |
| struct open_dev *adev; |
| struct dev_stream *curr; |
| struct timespec ts, now, last_wake; |
| struct pollfd *pollfds; |
| unsigned int num_pollfds; |
| unsigned int pollfds_size = 32; |
| int msg_fd; |
| int rc; |
| |
| msg_fd = thread->to_thread_fds[0]; |
| |
| /* Attempt to get realtime scheduling */ |
| if (cras_set_rt_scheduling(CRAS_SERVER_RT_THREAD_PRIORITY) == 0) |
| cras_set_thread_priority(CRAS_SERVER_RT_THREAD_PRIORITY); |
| |
| last_wake.tv_sec = 0; |
| longest_wake.tv_sec = 0; |
| longest_wake.tv_nsec = 0; |
| |
| pollfds = (struct pollfd *)malloc(sizeof(*pollfds) * pollfds_size); |
| pollfds[0].fd = msg_fd; |
| pollfds[0].events = POLLIN; |
| |
| while (1) { |
| struct timespec *wait_ts; |
| struct iodev_callback_list *iodev_cb; |
| |
| wait_ts = NULL; |
| num_pollfds = 1; |
| |
| /* device opened */ |
| rc = stream_dev_io(thread); |
| if (rc < 0) |
| syslog(LOG_ERR, "audio cb error %d", rc); |
| |
| if (fill_next_sleep_interval(thread, &ts)) |
| wait_ts = &ts; |
| |
| restart_poll_loop: |
| num_pollfds = 1; |
| |
| DL_FOREACH(iodev_callbacks, iodev_cb) { |
| if (!iodev_cb->enabled) |
| continue; |
| pollfds[num_pollfds].fd = iodev_cb->fd; |
| iodev_cb->pollfd = &pollfds[num_pollfds]; |
| if (iodev_cb->is_write) |
| pollfds[num_pollfds].events = POLLOUT; |
| else |
| pollfds[num_pollfds].events = POLLIN; |
| num_pollfds++; |
| if (num_pollfds >= pollfds_size) { |
| pollfds_size *= 2; |
| pollfds = (struct pollfd *)realloc(pollfds, |
| sizeof(*pollfds) * pollfds_size); |
| goto restart_poll_loop; |
| } |
| } |
| |
| /* TODO(dgreid) - once per rstream not per dev_stream */ |
| DL_FOREACH(thread->open_devs[CRAS_STREAM_OUTPUT], adev) { |
| DL_FOREACH(adev->dev->streams, curr) { |
| int fd = dev_stream_poll_stream_fd(curr); |
| if (fd < 0) |
| continue; |
| pollfds[num_pollfds].fd = fd; |
| pollfds[num_pollfds].events = POLLIN; |
| num_pollfds++; |
| if (num_pollfds >= pollfds_size) { |
| pollfds_size *= 2; |
| pollfds = (struct pollfd *)realloc( |
| pollfds, |
| sizeof(*pollfds) * |
| pollfds_size); |
| goto restart_poll_loop; |
| } |
| } |
| } |
| |
| if (last_wake.tv_sec) { |
| struct timespec this_wake; |
| clock_gettime(CLOCK_MONOTONIC_RAW, &now); |
| subtract_timespecs(&now, &last_wake, &this_wake); |
| if (timespec_after(&this_wake, &longest_wake)) |
| longest_wake = this_wake; |
| } |
| |
| ATLOG(atlog, AUDIO_THREAD_SLEEP, |
| wait_ts ? wait_ts->tv_sec : 0, |
| wait_ts ? wait_ts->tv_nsec : 0, |
| longest_wake.tv_nsec); |
| rc = ppoll(pollfds, num_pollfds, wait_ts, NULL); |
| clock_gettime(CLOCK_MONOTONIC_RAW, &last_wake); |
| ATLOG(atlog, AUDIO_THREAD_WAKE, rc, 0, 0); |
| if (rc <= 0) |
| continue; |
| |
| if (pollfds[0].revents & POLLIN) { |
| rc = handle_playback_thread_message(thread); |
| if (rc < 0) |
| syslog(LOG_INFO, "handle message %d", rc); |
| } |
| |
| DL_FOREACH(iodev_callbacks, iodev_cb) { |
| if (iodev_cb->pollfd && |
| iodev_cb->pollfd->revents & (POLLIN | POLLOUT)) { |
| ATLOG( |
| atlog, AUDIO_THREAD_IODEV_CB, |
| iodev_cb->is_write, 0, 0); |
| iodev_cb->cb(iodev_cb->cb_data); |
| } |
| } |
| } |
| |
| return NULL; |
| } |
| |
| /* Write a message to the playback thread and wait for an ack, This keeps these |
| * operations synchronous for the main server thread. For instance when the |
| * RM_STREAM message is sent, the stream can be deleted after the function |
| * returns. Making this synchronous also allows the thread to return an error |
| * code that can be handled by the caller. |
| * Args: |
| * thread - thread to receive message. |
| * msg - The message to send. |
| * Returns: |
| * A return code from the message handler in the thread. |
| */ |
| static int audio_thread_post_message(struct audio_thread *thread, |
| struct audio_thread_msg *msg) |
| { |
| int err; |
| void *rsp; |
| |
| err = write(thread->to_thread_fds[1], msg, msg->length); |
| if (err < 0) { |
| syslog(LOG_ERR, "Failed to post message to thread."); |
| return err; |
| } |
| /* Synchronous action, wait for response. */ |
| err = read(thread->to_main_fds[0], &rsp, sizeof(rsp)); |
| if (err < 0) { |
| syslog(LOG_ERR, "Failed to read reply from thread."); |
| return err; |
| } |
| |
| return (intptr_t)rsp; |
| } |
| |
| static void init_open_device_msg(struct audio_thread_open_device_msg *msg, |
| enum AUDIO_THREAD_COMMAND id, |
| struct cras_iodev *dev) |
| { |
| memset(msg, 0, sizeof(*msg)); |
| msg->header.id = id; |
| msg->header.length = sizeof(*msg); |
| msg->dev = dev; |
| } |
| |
| static void init_add_rm_stream_msg(struct audio_thread_add_rm_stream_msg *msg, |
| enum AUDIO_THREAD_COMMAND id, |
| struct cras_rstream *stream, |
| struct cras_iodev **devs, |
| unsigned int num_devs) |
| { |
| memset(msg, 0, sizeof(*msg)); |
| msg->header.id = id; |
| msg->header.length = sizeof(*msg); |
| msg->stream = stream; |
| msg->devs = devs; |
| msg->num_devs = num_devs; |
| } |
| |
| static void init_dump_debug_info_msg( |
| struct audio_thread_dump_debug_info_msg *msg, |
| struct audio_debug_info *info) |
| { |
| memset(msg, 0, sizeof(*msg)); |
| msg->header.id = AUDIO_THREAD_DUMP_THREAD_INFO; |
| msg->header.length = sizeof(*msg); |
| msg->info = info; |
| } |
| |
| static void init_config_global_remix_msg( |
| struct audio_thread_config_global_remix *msg) |
| { |
| memset(msg, 0, sizeof(*msg)); |
| msg->header.id = AUDIO_THREAD_CONFIG_GLOBAL_REMIX; |
| msg->header.length = sizeof(*msg); |
| } |
| |
| static void init_device_start_ramp_msg( |
| struct audio_thread_dev_start_ramp_msg *msg, |
| enum AUDIO_THREAD_COMMAND id, |
| struct cras_iodev *dev, |
| enum CRAS_IODEV_RAMP_REQUEST request) |
| { |
| memset(msg, 0, sizeof(*msg)); |
| msg->header.id = id; |
| msg->header.length = sizeof(*msg); |
| msg->dev = dev; |
| msg->request = request; |
| } |
| |
| /* Exported Interface */ |
| |
| int audio_thread_add_stream(struct audio_thread *thread, |
| struct cras_rstream *stream, |
| struct cras_iodev **devs, |
| unsigned int num_devs) |
| { |
| struct audio_thread_add_rm_stream_msg msg; |
| |
| assert(thread && stream); |
| |
| if (!thread->started) |
| return -EINVAL; |
| |
| init_add_rm_stream_msg(&msg, AUDIO_THREAD_ADD_STREAM, stream, |
| devs, num_devs); |
| return audio_thread_post_message(thread, &msg.header); |
| } |
| |
| int audio_thread_disconnect_stream(struct audio_thread *thread, |
| struct cras_rstream *stream, |
| struct cras_iodev *dev) |
| { |
| struct audio_thread_add_rm_stream_msg msg; |
| |
| assert(thread && stream); |
| |
| init_add_rm_stream_msg(&msg, AUDIO_THREAD_DISCONNECT_STREAM, stream, |
| &dev, 0); |
| return audio_thread_post_message(thread, &msg.header); |
| } |
| |
| int audio_thread_drain_stream(struct audio_thread *thread, |
| struct cras_rstream *stream) |
| { |
| struct audio_thread_add_rm_stream_msg msg; |
| |
| assert(thread && stream); |
| |
| init_add_rm_stream_msg(&msg, AUDIO_THREAD_DRAIN_STREAM, stream, |
| NULL, 0); |
| return audio_thread_post_message(thread, &msg.header); |
| } |
| |
| int audio_thread_dump_thread_info(struct audio_thread *thread, |
| struct audio_debug_info *info) |
| { |
| struct audio_thread_dump_debug_info_msg msg; |
| |
| init_dump_debug_info_msg(&msg, info); |
| return audio_thread_post_message(thread, &msg.header); |
| } |
| |
| int audio_thread_rm_callback_sync(struct audio_thread *thread, int fd) { |
| struct audio_thread_rm_callback_msg msg; |
| |
| memset(&msg, 0, sizeof(msg)); |
| msg.header.id = AUDIO_THREAD_REMOVE_CALLBACK; |
| msg.header.length = sizeof(msg); |
| msg.fd = fd; |
| |
| return audio_thread_post_message(thread, &msg.header); |
| } |
| |
| int audio_thread_config_global_remix(struct audio_thread *thread, |
| unsigned int num_channels, |
| const float *coefficient) |
| { |
| int err; |
| int identity_remix = 1; |
| unsigned int i, j; |
| struct audio_thread_config_global_remix msg; |
| void *rsp; |
| |
| init_config_global_remix_msg(&msg); |
| |
| /* Check if the coefficients represent an identity matrix for remix |
| * conversion, which means no remix at all. If so then leave the |
| * converter as NULL. */ |
| for (i = 0; i < num_channels; i++) { |
| if (coefficient[i * num_channels + i] != 1.0f) { |
| identity_remix = 0; |
| break; |
| } |
| for (j = i + 1; j < num_channels; j++) { |
| if (coefficient[i * num_channels + j] != 0 || |
| coefficient[j * num_channels + i] != 0) |
| identity_remix = 0; |
| break; |
| } |
| } |
| |
| if (!identity_remix) { |
| msg.fmt_conv = cras_channel_remix_conv_create(num_channels, |
| coefficient); |
| if (NULL == msg.fmt_conv) |
| return -ENOMEM; |
| } |
| |
| err = write(thread->to_thread_fds[1], &msg, msg.header.length); |
| if (err < 0) { |
| syslog(LOG_ERR, "Failed to post message to thread."); |
| return err; |
| } |
| /* Synchronous action, wait for response. */ |
| err = read(thread->to_main_fds[0], &rsp, sizeof(rsp)); |
| if (err < 0) { |
| syslog(LOG_ERR, "Failed to read reply from thread."); |
| return err; |
| } |
| |
| if (rsp) |
| cras_fmt_conv_destroy((struct cras_fmt_conv *)rsp); |
| return 0; |
| } |
| |
| struct cras_fmt_conv *audio_thread_get_global_remix_converter() |
| { |
| return remix_converter; |
| } |
| |
| struct audio_thread *audio_thread_create() |
| { |
| int rc; |
| struct audio_thread *thread; |
| |
| thread = (struct audio_thread *)calloc(1, sizeof(*thread)); |
| if (!thread) |
| return NULL; |
| |
| thread->to_thread_fds[0] = -1; |
| thread->to_thread_fds[1] = -1; |
| thread->to_main_fds[0] = -1; |
| thread->to_main_fds[1] = -1; |
| |
| /* Two way pipes for communication with the device's audio thread. */ |
| rc = pipe(thread->to_thread_fds); |
| if (rc < 0) { |
| syslog(LOG_ERR, "Failed to pipe"); |
| free(thread); |
| return NULL; |
| } |
| rc = pipe(thread->to_main_fds); |
| if (rc < 0) { |
| syslog(LOG_ERR, "Failed to pipe"); |
| free(thread); |
| return NULL; |
| } |
| |
| atlog = audio_thread_event_log_init(); |
| |
| return thread; |
| } |
| |
| int audio_thread_add_open_dev(struct audio_thread *thread, |
| struct cras_iodev *dev) |
| { |
| struct audio_thread_open_device_msg msg; |
| |
| assert(thread && dev); |
| |
| if (!thread->started) |
| return -EINVAL; |
| |
| init_open_device_msg(&msg, AUDIO_THREAD_ADD_OPEN_DEV, dev); |
| return audio_thread_post_message(thread, &msg.header); |
| } |
| |
| int audio_thread_rm_open_dev(struct audio_thread *thread, |
| struct cras_iodev *dev) |
| { |
| struct audio_thread_open_device_msg msg; |
| |
| assert(thread && dev); |
| if (!thread->started) |
| return -EINVAL; |
| |
| init_open_device_msg(&msg, AUDIO_THREAD_RM_OPEN_DEV, dev); |
| return audio_thread_post_message(thread, &msg.header); |
| } |
| |
| int audio_thread_dev_start_ramp(struct audio_thread *thread, |
| struct cras_iodev *dev, |
| enum CRAS_IODEV_RAMP_REQUEST request) |
| { |
| struct audio_thread_dev_start_ramp_msg msg; |
| |
| assert(thread && dev); |
| |
| if (!thread->started) |
| return -EINVAL; |
| |
| init_device_start_ramp_msg(&msg, AUDIO_THREAD_DEV_START_RAMP, |
| dev, request); |
| return audio_thread_post_message(thread, &msg.header); |
| } |
| |
| int audio_thread_start(struct audio_thread *thread) |
| { |
| int rc; |
| |
| rc = pthread_create(&thread->tid, NULL, audio_io_thread, thread); |
| if (rc) { |
| syslog(LOG_ERR, "Failed pthread_create"); |
| return rc; |
| } |
| |
| thread->started = 1; |
| |
| return 0; |
| } |
| |
| void audio_thread_destroy(struct audio_thread *thread) |
| { |
| audio_thread_event_log_deinit(atlog); |
| |
| if (thread->started) { |
| struct audio_thread_msg msg; |
| |
| msg.id = AUDIO_THREAD_STOP; |
| msg.length = sizeof(msg); |
| audio_thread_post_message(thread, &msg); |
| pthread_join(thread->tid, NULL); |
| } |
| |
| if (thread->to_thread_fds[0] != -1) { |
| close(thread->to_thread_fds[0]); |
| close(thread->to_thread_fds[1]); |
| } |
| if (thread->to_main_fds[0] != -1) { |
| close(thread->to_main_fds[0]); |
| close(thread->to_main_fds[1]); |
| } |
| |
| if (remix_converter) |
| cras_fmt_conv_destroy(remix_converter); |
| |
| free(thread); |
| } |