Blame axfer/xfer-libasound-irq-rw.c

Packit Service a9274b
// SPDX-License-Identifier: GPL-2.0
Packit Service a9274b
//
Packit Service a9274b
// xfer-libasound-irq-rw.c - IRQ-based scheduling model for read/write operation.
Packit Service a9274b
//
Packit Service a9274b
// Copyright (c) 2018 Takashi Sakamoto <o-takashi@sakamocchi.jp>
Packit Service a9274b
//
Packit Service a9274b
// Licensed under the terms of the GNU General Public License, version 2.
Packit Service a9274b
Packit Service a9274b
#include "xfer-libasound.h"
Packit Service a9274b
#include "misc.h"
Packit Service a9274b
#include "frame-cache.h"
Packit Service a9274b
Packit Service a9274b
struct rw_closure {
Packit Service a9274b
	snd_pcm_access_t access;
Packit Service a9274b
	int (*process_frames)(struct libasound_state *state,
Packit Service a9274b
			      snd_pcm_state_t status, unsigned int *frame_count,
Packit Service a9274b
			      struct mapper_context *mapper,
Packit Service a9274b
		      struct container_context *cntrs);
Packit Service a9274b
	struct frame_cache cache;
Packit Service a9274b
};
Packit Service a9274b
Packit Service a9274b
static int wait_for_avail(struct libasound_state *state)
Packit Service a9274b
{
Packit Service a9274b
	unsigned int msec_per_buffer;
Packit Service a9274b
	unsigned short revents;
Packit Service a9274b
	unsigned short event;
Packit Service a9274b
	int err;
Packit Service a9274b
Packit Service a9274b
	// Wait during msec equivalent to all audio data frames in buffer
Packit Service a9274b
	// instead of period, for safe.
Packit Service a9274b
	err = snd_pcm_hw_params_get_buffer_time(state->hw_params,
Packit Service a9274b
						&msec_per_buffer, NULL);
Packit Service a9274b
	if (err < 0)
Packit Service a9274b
		return err;
Packit Service a9274b
	msec_per_buffer /= 1000;
Packit Service a9274b
Packit Service a9274b
	// Wait for hardware IRQ when no available space.
Packit Service a9274b
	err = xfer_libasound_wait_event(state, msec_per_buffer, &revents);
Packit Service a9274b
	if (err < 0)
Packit Service a9274b
		return err;
Packit Service a9274b
Packit Service a9274b
	// TODO: error reporting.
Packit Service a9274b
	if (revents & POLLERR)
Packit Service a9274b
		return -EIO;
Packit Service a9274b
Packit Service a9274b
	if (snd_pcm_stream(state->handle) == SND_PCM_STREAM_CAPTURE)
Packit Service a9274b
		event = POLLIN;
Packit Service a9274b
	else
Packit Service a9274b
		event = POLLOUT;
Packit Service a9274b
Packit Service a9274b
	if (!(revents & event))
Packit Service a9274b
		return -EAGAIN;
Packit Service a9274b
Packit Service a9274b
	return 0;
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
static int read_frames(struct libasound_state *state, unsigned int *frame_count,
Packit Service a9274b
		       unsigned int avail_count, struct mapper_context *mapper,
Packit Service a9274b
		       struct container_context *cntrs)
Packit Service a9274b
{
Packit Service a9274b
	struct rw_closure *closure = state->private_data;
Packit Service a9274b
	snd_pcm_sframes_t handled_frame_count;
Packit Service a9274b
	unsigned int consumed_count;
Packit Service a9274b
	int err;
Packit Service a9274b
Packit Service a9274b
	// Trim according up to expected frame count.
Packit Service a9274b
	if (*frame_count < avail_count)
Packit Service a9274b
		avail_count = *frame_count;
Packit Service a9274b
Packit Service a9274b
	// Cache required amount of frames.
Packit Service a9274b
	if (avail_count > frame_cache_get_count(&closure->cache)) {
Packit Service a9274b
		avail_count -= frame_cache_get_count(&closure->cache);
Packit Service a9274b
Packit Service a9274b
		// Execute write operation according to the shape of buffer.
Packit Service a9274b
		// These operations automatically start the substream.
Packit Service a9274b
		if (closure->access == SND_PCM_ACCESS_RW_INTERLEAVED) {
Packit Service a9274b
			handled_frame_count = snd_pcm_readi(state->handle,
Packit Service a9274b
							closure->cache.buf_ptr,
Packit Service a9274b
							avail_count);
Packit Service a9274b
		} else {
Packit Service a9274b
			handled_frame_count = snd_pcm_readn(state->handle,
Packit Service a9274b
							closure->cache.buf_ptr,
Packit Service a9274b
							avail_count);
Packit Service a9274b
		}
Packit Service a9274b
		if (handled_frame_count < 0) {
Packit Service a9274b
			err = handled_frame_count;
Packit Service a9274b
			return err;
Packit Service a9274b
		}
Packit Service a9274b
		frame_cache_increase_count(&closure->cache, handled_frame_count);
Packit Service a9274b
		avail_count = frame_cache_get_count(&closure->cache);
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	// Write out to file descriptors.
Packit Service a9274b
	consumed_count = avail_count;
Packit Service a9274b
	err = mapper_context_process_frames(mapper, closure->cache.buf,
Packit Service a9274b
					    &consumed_count, cntrs);
Packit Service a9274b
	if (err < 0)
Packit Service a9274b
		return err;
Packit Service a9274b
Packit Service a9274b
	frame_cache_reduce(&closure->cache, consumed_count);
Packit Service a9274b
Packit Service a9274b
	*frame_count = consumed_count;
Packit Service a9274b
Packit Service a9274b
	return 0;
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
static int r_process_frames_blocking(struct libasound_state *state,
Packit Service a9274b
				     snd_pcm_state_t status,
Packit Service a9274b
				     unsigned int *frame_count,
Packit Service a9274b
				     struct mapper_context *mapper,
Packit Service a9274b
				     struct container_context *cntrs)
Packit Service a9274b
{
Packit Service a9274b
	snd_pcm_sframes_t avail;
Packit Service a9274b
	snd_pcm_uframes_t avail_count;
Packit Service a9274b
	int err = 0;
Packit Service a9274b
Packit Service a9274b
	if (status == SND_PCM_STATE_RUNNING) {
Packit Service a9274b
		// Check available space on the buffer.
Packit Service a9274b
		avail = snd_pcm_avail(state->handle);
Packit Service a9274b
		if (avail < 0) {
Packit Service a9274b
			err = avail;
Packit Service a9274b
			goto error;
Packit Service a9274b
		}
Packit Service a9274b
		avail_count = (snd_pcm_uframes_t)avail;
Packit Service a9274b
Packit Service a9274b
		if (avail_count == 0) {
Packit Service a9274b
			// Request data frames so that blocking is just
Packit Service a9274b
			// released.
Packit Service a9274b
			err = snd_pcm_sw_params_get_avail_min(state->sw_params,
Packit Service a9274b
							      &avail_count);
Packit Service a9274b
			if (err < 0)
Packit Service a9274b
				goto error;
Packit Service a9274b
		}
Packit Service a9274b
	} else {
Packit Service a9274b
		// Request data frames so that the PCM substream starts.
Packit Service a9274b
		snd_pcm_uframes_t frame_count;
Packit Service a9274b
		err = snd_pcm_sw_params_get_start_threshold(state->sw_params,
Packit Service a9274b
							    &frame_count);
Packit Service a9274b
		if (err < 0)
Packit Service a9274b
			goto error;
Packit Service a9274b
Packit Service a9274b
		avail_count = (unsigned int)frame_count;
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	err = read_frames(state, frame_count, avail_count, mapper, cntrs);
Packit Service a9274b
	if (err < 0)
Packit Service a9274b
		goto error;
Packit Service a9274b
Packit Service a9274b
	return 0;
Packit Service a9274b
error:
Packit Service a9274b
	*frame_count = 0;
Packit Service a9274b
	return err;
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
static int r_process_frames_nonblocking(struct libasound_state *state,
Packit Service a9274b
					snd_pcm_state_t status,
Packit Service a9274b
					unsigned int *frame_count,
Packit Service a9274b
					struct mapper_context *mapper,
Packit Service a9274b
					struct container_context *cntrs)
Packit Service a9274b
{
Packit Service a9274b
	snd_pcm_sframes_t avail;
Packit Service a9274b
	snd_pcm_uframes_t avail_count;
Packit Service a9274b
	int err = 0;
Packit Service a9274b
Packit Service a9274b
	if (status != SND_PCM_STATE_RUNNING) {
Packit Service a9274b
		err = snd_pcm_start(state->handle);
Packit Service a9274b
		if (err < 0)
Packit Service a9274b
			goto error;
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	if (state->use_waiter) {
Packit Service a9274b
		err = wait_for_avail(state);
Packit Service a9274b
		if (err < 0)
Packit Service a9274b
			goto error;
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	// Check available space on the buffer.
Packit Service a9274b
	avail = snd_pcm_avail(state->handle);
Packit Service a9274b
	if (avail < 0) {
Packit Service a9274b
		err = avail;
Packit Service a9274b
		goto error;
Packit Service a9274b
	}
Packit Service a9274b
	avail_count = (snd_pcm_uframes_t)avail;
Packit Service a9274b
Packit Service a9274b
	if (avail_count == 0) {
Packit Service a9274b
		// Let's go to a next iteration.
Packit Service a9274b
		err = 0;
Packit Service a9274b
		goto error;
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	err = read_frames(state, frame_count, avail_count, mapper, cntrs);
Packit Service a9274b
	if (err < 0)
Packit Service a9274b
		goto error;
Packit Service a9274b
Packit Service a9274b
	return 0;
Packit Service a9274b
error:
Packit Service a9274b
	*frame_count = 0;
Packit Service a9274b
	return err;
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
static int write_frames(struct libasound_state *state,
Packit Service a9274b
			unsigned int *frame_count, unsigned int avail_count,
Packit Service a9274b
			struct mapper_context *mapper,
Packit Service a9274b
			struct container_context *cntrs)
Packit Service a9274b
{
Packit Service a9274b
	struct rw_closure *closure = state->private_data;
Packit Service a9274b
	snd_pcm_uframes_t consumed_count;
Packit Service a9274b
	snd_pcm_sframes_t handled_frame_count;
Packit Service a9274b
	int err;
Packit Service a9274b
Packit Service a9274b
	// Trim according up to expected frame count.
Packit Service a9274b
	if (*frame_count < avail_count)
Packit Service a9274b
		avail_count = *frame_count;
Packit Service a9274b
Packit Service a9274b
	// Cache required amount of frames.
Packit Service a9274b
	if (avail_count > frame_cache_get_count(&closure->cache)) {
Packit Service a9274b
		avail_count -= frame_cache_get_count(&closure->cache);
Packit Service a9274b
Packit Service a9274b
		// Read frames to transfer.
Packit Service a9274b
		err = mapper_context_process_frames(mapper,
Packit Service a9274b
				closure->cache.buf_ptr, &avail_count, cntrs);
Packit Service a9274b
		if (err < 0)
Packit Service a9274b
			return err;
Packit Service a9274b
		frame_cache_increase_count(&closure->cache, avail_count);
Packit Service a9274b
		avail_count = frame_cache_get_count(&closure->cache);
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	// Execute write operation according to the shape of buffer. These
Packit Service a9274b
	// operations automatically start the stream.
Packit Service a9274b
	consumed_count = avail_count;
Packit Service a9274b
	if (closure->access == SND_PCM_ACCESS_RW_INTERLEAVED) {
Packit Service a9274b
		handled_frame_count = snd_pcm_writei(state->handle,
Packit Service a9274b
					closure->cache.buf, consumed_count);
Packit Service a9274b
	} else {
Packit Service a9274b
		handled_frame_count = snd_pcm_writen(state->handle,
Packit Service a9274b
					closure->cache.buf, consumed_count);
Packit Service a9274b
	}
Packit Service a9274b
	if (handled_frame_count < 0) {
Packit Service a9274b
		err = handled_frame_count;
Packit Service a9274b
		return err;
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	consumed_count = handled_frame_count;
Packit Service a9274b
	frame_cache_reduce(&closure->cache, consumed_count);
Packit Service a9274b
Packit Service a9274b
	*frame_count = consumed_count;
Packit Service a9274b
Packit Service a9274b
	return 0;
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
static int w_process_frames_blocking(struct libasound_state *state,
Packit Service a9274b
				     snd_pcm_state_t status,
Packit Service a9274b
				     unsigned int *frame_count,
Packit Service a9274b
				     struct mapper_context *mapper,
Packit Service a9274b
				     struct container_context *cntrs)
Packit Service a9274b
{
Packit Service a9274b
	snd_pcm_sframes_t avail;
Packit Service a9274b
	unsigned int avail_count;
Packit Service a9274b
	int err;
Packit Service a9274b
Packit Service a9274b
	if (status == SND_PCM_STATE_RUNNING) {
Packit Service a9274b
		// Check available space on the buffer.
Packit Service a9274b
		avail = snd_pcm_avail(state->handle);
Packit Service a9274b
		if (avail < 0) {
Packit Service a9274b
			err = avail;
Packit Service a9274b
			goto error;
Packit Service a9274b
		}
Packit Service a9274b
		avail_count = (unsigned int)avail;
Packit Service a9274b
Packit Service a9274b
		if (avail_count == 0) {
Packit Service a9274b
			// Fill with data frames so that blocking is just
Packit Service a9274b
			// released.
Packit Service a9274b
			snd_pcm_uframes_t avail_min;
Packit Service a9274b
			err = snd_pcm_sw_params_get_avail_min(state->sw_params,
Packit Service a9274b
							      &avail_min);
Packit Service a9274b
			if (err < 0)
Packit Service a9274b
				goto error;
Packit Service a9274b
			avail_count = (unsigned int)avail_min;
Packit Service a9274b
		}
Packit Service a9274b
	} else {
Packit Service a9274b
		snd_pcm_uframes_t frames_for_start_threshold;
Packit Service a9274b
		snd_pcm_uframes_t frames_per_period;
Packit Service a9274b
Packit Service a9274b
		// Fill with data frames so that the PCM substream starts.
Packit Service a9274b
		err = snd_pcm_sw_params_get_start_threshold(state->sw_params,
Packit Service a9274b
						&frames_for_start_threshold);
Packit Service a9274b
		if (err < 0)
Packit Service a9274b
			goto error;
Packit Service a9274b
Packit Service a9274b
		// But the above number can be too small and cause XRUN because
Packit Service a9274b
		// I/O operation is done per period.
Packit Service a9274b
		err = snd_pcm_hw_params_get_period_size(state->hw_params,
Packit Service a9274b
						&frames_per_period, NULL);
Packit Service a9274b
		if (err < 0)
Packit Service a9274b
			goto error;
Packit Service a9274b
Packit Service a9274b
		// Use larger one to prevent from both of XRUN and successive
Packit Service a9274b
		// blocking.
Packit Service a9274b
		if (frames_for_start_threshold > frames_per_period)
Packit Service a9274b
			avail_count = (unsigned int)frames_for_start_threshold;
Packit Service a9274b
		else
Packit Service a9274b
			avail_count = (unsigned int)frames_per_period;
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	err = write_frames(state, frame_count, avail_count, mapper, cntrs);
Packit Service a9274b
	if (err < 0)
Packit Service a9274b
		goto error;
Packit Service a9274b
Packit Service a9274b
	return 0;
Packit Service a9274b
error:
Packit Service a9274b
	*frame_count = 0;
Packit Service a9274b
	return err;
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
static int w_process_frames_nonblocking(struct libasound_state *state,
Packit Service a9274b
					snd_pcm_state_t status,
Packit Service a9274b
					unsigned int *frame_count,
Packit Service a9274b
					struct mapper_context *mapper,
Packit Service a9274b
					struct container_context *cntrs)
Packit Service a9274b
{
Packit Service a9274b
	snd_pcm_sframes_t avail;
Packit Service a9274b
	unsigned int avail_count;
Packit Service a9274b
	int err;
Packit Service a9274b
Packit Service a9274b
	if (state->use_waiter) {
Packit Service a9274b
		err = wait_for_avail(state);
Packit Service a9274b
		if (err < 0)
Packit Service a9274b
			goto error;
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	// Check available space on the buffer.
Packit Service a9274b
	avail = snd_pcm_avail(state->handle);
Packit Service a9274b
	if (avail < 0) {
Packit Service a9274b
		err = avail;
Packit Service a9274b
		goto error;
Packit Service a9274b
	}
Packit Service a9274b
	avail_count = (unsigned int)avail;
Packit Service a9274b
Packit Service a9274b
	if (avail_count == 0) {
Packit Service a9274b
		// Let's go to a next iteration.
Packit Service a9274b
		err = 0;
Packit Service a9274b
		goto error;
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	err = write_frames(state, frame_count, avail_count, mapper, cntrs);
Packit Service a9274b
	if (err < 0)
Packit Service a9274b
		goto error;
Packit Service a9274b
Packit Service a9274b
	// NOTE: The substream starts automatically when the accumulated number
Packit Service a9274b
	// of queued data frame exceeds start_threshold.
Packit Service a9274b
Packit Service a9274b
	return 0;
Packit Service a9274b
error:
Packit Service a9274b
	*frame_count = 0;
Packit Service a9274b
	return err;
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
static int irq_rw_pre_process(struct libasound_state *state)
Packit Service a9274b
{
Packit Service a9274b
	struct rw_closure *closure = state->private_data;
Packit Service a9274b
	snd_pcm_format_t format;
Packit Service a9274b
	snd_pcm_uframes_t frames_per_buffer;
Packit Service a9274b
	int bytes_per_sample;
Packit Service a9274b
	unsigned int samples_per_frame;
Packit Service a9274b
	int err;
Packit Service a9274b
Packit Service a9274b
	err = snd_pcm_hw_params_get_format(state->hw_params, &format);
Packit Service a9274b
	if (err < 0)
Packit Service a9274b
		return err;
Packit Service a9274b
	bytes_per_sample = snd_pcm_format_physical_width(format) / 8;
Packit Service a9274b
	if (bytes_per_sample <= 0)
Packit Service a9274b
		return -ENXIO;
Packit Service a9274b
Packit Service a9274b
	err = snd_pcm_hw_params_get_channels(state->hw_params,
Packit Service a9274b
					     &samples_per_frame);
Packit Service a9274b
	if (err < 0)
Packit Service a9274b
		return err;
Packit Service a9274b
Packit Service a9274b
	err = snd_pcm_hw_params_get_buffer_size(state->hw_params,
Packit Service a9274b
						&frames_per_buffer);
Packit Service a9274b
	if (err < 0)
Packit Service a9274b
		return err;
Packit Service a9274b
Packit Service a9274b
	err = snd_pcm_hw_params_get_access(state->hw_params, &closure->access);
Packit Service a9274b
	if (err < 0)
Packit Service a9274b
		return err;
Packit Service a9274b
Packit Service a9274b
	err = frame_cache_init(&closure->cache, closure->access,
Packit Service a9274b
			       bytes_per_sample, samples_per_frame,
Packit Service a9274b
			       frames_per_buffer);
Packit Service a9274b
	if (err < 0)
Packit Service a9274b
		return err;
Packit Service a9274b
Packit Service a9274b
	if (snd_pcm_stream(state->handle) == SND_PCM_STREAM_CAPTURE) {
Packit Service a9274b
		if (state->nonblock)
Packit Service a9274b
			closure->process_frames = r_process_frames_nonblocking;
Packit Service a9274b
		else
Packit Service a9274b
			closure->process_frames = r_process_frames_blocking;
Packit Service a9274b
	} else {
Packit Service a9274b
		if (state->nonblock)
Packit Service a9274b
			closure->process_frames = w_process_frames_nonblocking;
Packit Service a9274b
		else
Packit Service a9274b
			closure->process_frames = w_process_frames_blocking;
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	return 0;
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
static int irq_rw_process_frames(struct libasound_state *state,
Packit Service a9274b
				unsigned int *frame_count,
Packit Service a9274b
				struct mapper_context *mapper,
Packit Service a9274b
				struct container_context *cntrs)
Packit Service a9274b
{
Packit Service a9274b
	struct rw_closure *closure = state->private_data;
Packit Service a9274b
	snd_pcm_state_t status;
Packit Service a9274b
Packit Service a9274b
	// Need to recover the stream.
Packit Service a9274b
	status = snd_pcm_state(state->handle);
Packit Service a9274b
	if (status != SND_PCM_STATE_RUNNING && status != SND_PCM_STATE_PREPARED)
Packit Service a9274b
		return -EPIPE;
Packit Service a9274b
Packit Service a9274b
	// NOTE: Actually, status can be shift always.
Packit Service a9274b
	return closure->process_frames(state, status, frame_count, mapper, cntrs);
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
static void irq_rw_post_process(struct libasound_state *state)
Packit Service a9274b
{
Packit Service a9274b
	struct rw_closure *closure = state->private_data;
Packit Service a9274b
Packit Service a9274b
	frame_cache_destroy(&closure->cache);
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
const struct xfer_libasound_ops xfer_libasound_irq_rw_ops = {
Packit Service a9274b
	.pre_process	= irq_rw_pre_process,
Packit Service a9274b
	.process_frames	= irq_rw_process_frames,
Packit Service a9274b
	.post_process	= irq_rw_post_process,
Packit Service a9274b
	.private_size	= sizeof(struct rw_closure),
Packit Service a9274b
};