Blame bat/alsa.c

Packit Service a9274b
/*
Packit Service a9274b
 * Copyright (C) 2013-2015 Intel Corporation
Packit Service a9274b
 *
Packit Service a9274b
 * This program is free software; you can redistribute it and/or modify
Packit Service a9274b
 * it under the terms of the GNU General Public License as published by
Packit Service a9274b
 * the Free Software Foundation; either version 2 of the License, or
Packit Service a9274b
 * (at your option) any later version.
Packit Service a9274b
 *
Packit Service a9274b
 * This program is distributed in the hope that it will be useful,
Packit Service a9274b
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit Service a9274b
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit Service a9274b
 * GNU General Public License for more details.
Packit Service a9274b
 *
Packit Service a9274b
 */
Packit Service a9274b
Packit Service a9274b
#include <stdio.h>
Packit Service a9274b
#include <string.h>
Packit Service a9274b
#include <stdbool.h>
Packit Service a9274b
#include <stdint.h>
Packit Service a9274b
#include <pthread.h>
Packit Service a9274b
#include <errno.h>
Packit Service a9274b
Packit Service a9274b
#include <alsa/asoundlib.h>
Packit Service a9274b
Packit Service a9274b
#include "aconfig.h"
Packit Service a9274b
#include "gettext.h"
Packit Service a9274b
Packit Service a9274b
#include "common.h"
Packit Service a9274b
#include "alsa.h"
Packit Service a9274b
#include "latencytest.h"
Packit Service a9274b
Packit Service a9274b
struct pcm_container {
Packit Service a9274b
	snd_pcm_t *handle;
Packit Service a9274b
	snd_pcm_uframes_t period_size;
Packit Service a9274b
	snd_pcm_uframes_t buffer_size;
Packit Service a9274b
	snd_pcm_format_t format;
Packit Service a9274b
	unsigned short channels;
Packit Service a9274b
	size_t period_bytes;
Packit Service a9274b
	size_t sample_bits;
Packit Service a9274b
	size_t frame_bits;
Packit Service a9274b
	char *buffer;
Packit Service a9274b
};
Packit Service a9274b
Packit Service a9274b
struct format_map_table {
Packit Service a9274b
	enum _bat_pcm_format format_bat;
Packit Service a9274b
	snd_pcm_format_t format_alsa;
Packit Service a9274b
};
Packit Service a9274b
Packit Service a9274b
static struct format_map_table map_tables[] = {
Packit Service a9274b
	{ BAT_PCM_FORMAT_UNKNOWN, SND_PCM_FORMAT_UNKNOWN },
Packit Service a9274b
	{ BAT_PCM_FORMAT_U8, SND_PCM_FORMAT_U8 },
Packit Service a9274b
	{ BAT_PCM_FORMAT_S16_LE, SND_PCM_FORMAT_S16_LE },
Packit Service a9274b
	{ BAT_PCM_FORMAT_S24_3LE, SND_PCM_FORMAT_S24_3LE },
Packit Service a9274b
	{ BAT_PCM_FORMAT_S32_LE, SND_PCM_FORMAT_S32_LE },
Packit Service a9274b
	{ BAT_PCM_FORMAT_MAX, },
Packit Service a9274b
};
Packit Service a9274b
Packit Service a9274b
static int format_convert(struct bat *bat, snd_pcm_format_t *fmt)
Packit Service a9274b
{
Packit Service a9274b
	struct format_map_table *t = map_tables;
Packit Service a9274b
Packit Service a9274b
	for (; t->format_bat != BAT_PCM_FORMAT_MAX; t++) {
Packit Service a9274b
		if (t->format_bat == bat->format) {
Packit Service a9274b
			*fmt = t->format_alsa;
Packit Service a9274b
			return 0;
Packit Service a9274b
		}
Packit Service a9274b
	}
Packit Service a9274b
	fprintf(bat->err, _("Invalid format!\n"));
Packit Service a9274b
	return -EINVAL;
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
static int set_snd_pcm_params(struct bat *bat, struct pcm_container *sndpcm)
Packit Service a9274b
{
Packit Service a9274b
	snd_pcm_hw_params_t *params;
Packit Service a9274b
	snd_pcm_format_t format;
Packit Service a9274b
	unsigned int buffer_time = 0;
Packit Service a9274b
	unsigned int period_time = 0;
Packit Service a9274b
	snd_pcm_uframes_t buffer_size = 0;
Packit Service a9274b
	snd_pcm_uframes_t period_size = 0;
Packit Service a9274b
	unsigned int rate;
Packit Service a9274b
	int err;
Packit Service a9274b
	const char *device_name = snd_pcm_name(sndpcm->handle);
Packit Service a9274b
Packit Service a9274b
	/* Convert common format to ALSA format */
Packit Service a9274b
	err = format_convert(bat, &format);
Packit Service a9274b
	if (err != 0)
Packit Service a9274b
		return err;
Packit Service a9274b
Packit Service a9274b
	/* Allocate a hardware parameters object. */
Packit Service a9274b
	snd_pcm_hw_params_alloca(&params);
Packit Service a9274b
Packit Service a9274b
	/* Fill it in with default values. */
Packit Service a9274b
	err = snd_pcm_hw_params_any(sndpcm->handle, params);
Packit Service a9274b
	if (err < 0) {
Packit Service a9274b
		fprintf(bat->err, _("Set parameter to device error: "));
Packit Service a9274b
		fprintf(bat->err, _("default params: %s: %s(%d)\n"),
Packit Service a9274b
				device_name, snd_strerror(err), err);
Packit Service a9274b
		return err;
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	/* Set access mode */
Packit Service a9274b
	err = snd_pcm_hw_params_set_access(sndpcm->handle, params,
Packit Service a9274b
			SND_PCM_ACCESS_RW_INTERLEAVED);
Packit Service a9274b
	if (err < 0) {
Packit Service a9274b
		fprintf(bat->err, _("Set parameter to device error: "));
Packit Service a9274b
		fprintf(bat->err, _("access type: %s: %s(%d)\n"),
Packit Service a9274b
				device_name, snd_strerror(err), err);
Packit Service a9274b
		return err;
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	/* Set format */
Packit Service a9274b
	err = snd_pcm_hw_params_set_format(sndpcm->handle, params, format);
Packit Service a9274b
	if (err < 0) {
Packit Service a9274b
		fprintf(bat->err, _("Set parameter to device error: "));
Packit Service a9274b
		fprintf(bat->err, _("PCM format: %d %s: %s(%d)\n"), format,
Packit Service a9274b
				device_name, snd_strerror(err), err);
Packit Service a9274b
		return err;
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	/* Set channels */
Packit Service a9274b
	err = snd_pcm_hw_params_set_channels(sndpcm->handle,
Packit Service a9274b
			params, bat->channels);
Packit Service a9274b
	if (err < 0) {
Packit Service a9274b
		fprintf(bat->err, _("Set parameter to device error: "));
Packit Service a9274b
		fprintf(bat->err, _("channel number: %d %s: %s(%d)\n"),
Packit Service a9274b
				bat->channels,
Packit Service a9274b
				device_name, snd_strerror(err), err);
Packit Service a9274b
		return err;
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	/* Set sampling rate */
Packit Service a9274b
	rate = bat->rate;
Packit Service a9274b
	err = snd_pcm_hw_params_set_rate_near(sndpcm->handle,
Packit Service a9274b
			params, &bat->rate,
Packit Service a9274b
			0);
Packit Service a9274b
	if (err < 0) {
Packit Service a9274b
		fprintf(bat->err, _("Set parameter to device error: "));
Packit Service a9274b
		fprintf(bat->err, _("sample rate: %d %s: %s(%d)\n"),
Packit Service a9274b
				bat->rate,
Packit Service a9274b
				device_name, snd_strerror(err), err);
Packit Service a9274b
		return err;
Packit Service a9274b
	}
Packit Service a9274b
	if ((float) rate * (1 + RATE_RANGE) < bat->rate
Packit Service a9274b
			|| (float) rate * (1 - RATE_RANGE) > bat->rate) {
Packit Service a9274b
		fprintf(bat->err, _("Invalid parameters: sample rate: "));
Packit Service a9274b
		fprintf(bat->err, _("requested %dHz, got %dHz\n"),
Packit Service a9274b
				rate, bat->rate);
Packit Service a9274b
		return -EINVAL;
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	if (bat->buffer_size > 0 && bat->period_size == 0)
Packit Service a9274b
		bat->period_size = bat->buffer_size / DIV_BUFFERSIZE;
Packit Service a9274b
Packit Service a9274b
	if (bat->roundtriplatency && bat->buffer_size == 0) {
Packit Service a9274b
		/* Set to minimum buffer size and period size
Packit Service a9274b
		   for latency test */
Packit Service a9274b
		if (snd_pcm_hw_params_get_buffer_size_min(params,
Packit Service a9274b
				&buffer_size) < 0) {
Packit Service a9274b
			fprintf(bat->err,
Packit Service a9274b
					_("Get parameter from device error: "));
Packit Service a9274b
			fprintf(bat->err, _("buffer size min: %d %s: %s(%d)\n"),
Packit Service a9274b
					(int) buffer_size,
Packit Service a9274b
					device_name, snd_strerror(err), err);
Packit Service a9274b
			return -EINVAL;
Packit Service a9274b
		}
Packit Service a9274b
Packit Service a9274b
		if (snd_pcm_hw_params_get_period_size_min(params,
Packit Service a9274b
				&period_size, 0) < 0) {
Packit Service a9274b
			fprintf(bat->err,
Packit Service a9274b
					_("Get parameter from device error: "));
Packit Service a9274b
			fprintf(bat->err, _("period size min: %d %s: %s(%d)\n"),
Packit Service a9274b
					(int) period_size,
Packit Service a9274b
					device_name, snd_strerror(err), err);
Packit Service a9274b
			return -EINVAL;
Packit Service a9274b
		}
Packit Service a9274b
		bat->buffer_size = (int) buffer_size;
Packit Service a9274b
		bat->period_size = (int) period_size;
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	if (bat->buffer_size > 0) {
Packit Service a9274b
		buffer_size = bat->buffer_size;
Packit Service a9274b
		period_size = bat->period_size;
Packit Service a9274b
Packit Service a9274b
		fprintf(bat->log, _("Set period size: %d  buffer size: %d\n"),
Packit Service a9274b
				(int) period_size, (int) buffer_size);
Packit Service a9274b
Packit Service a9274b
		err = snd_pcm_hw_params_set_buffer_size_near(sndpcm->handle,
Packit Service a9274b
				params, &buffer_size);
Packit Service a9274b
		if (err < 0) {
Packit Service a9274b
			fprintf(bat->err, _("Set parameter to device error: "));
Packit Service a9274b
			fprintf(bat->err, _("buffer size: %d %s: %s(%d)\n"),
Packit Service a9274b
					(int) buffer_size,
Packit Service a9274b
					device_name, snd_strerror(err), err);
Packit Service a9274b
			return err;
Packit Service a9274b
		}
Packit Service a9274b
Packit Service a9274b
		err = snd_pcm_hw_params_set_period_size_near(sndpcm->handle,
Packit Service a9274b
				params, &period_size, 0);
Packit Service a9274b
		if (err < 0) {
Packit Service a9274b
			fprintf(bat->err, _("Set parameter to device error: "));
Packit Service a9274b
			fprintf(bat->err, _("period size: %d %s: %s(%d)\n"),
Packit Service a9274b
					(int) period_size,
Packit Service a9274b
					device_name, snd_strerror(err), err);
Packit Service a9274b
			return err;
Packit Service a9274b
		}
Packit Service a9274b
	} else {
Packit Service a9274b
		if (snd_pcm_hw_params_get_buffer_time_max(params,
Packit Service a9274b
				&buffer_time, 0) < 0) {
Packit Service a9274b
			fprintf(bat->err,
Packit Service a9274b
					_("Get parameter from device error: "));
Packit Service a9274b
			fprintf(bat->err, _("buffer time: %d %s: %s(%d)\n"),
Packit Service a9274b
					buffer_time,
Packit Service a9274b
					device_name, snd_strerror(err), err);
Packit Service a9274b
			return -EINVAL;
Packit Service a9274b
		}
Packit Service a9274b
Packit Service a9274b
		if (buffer_time > MAX_BUFFERTIME)
Packit Service a9274b
			buffer_time = MAX_BUFFERTIME;
Packit Service a9274b
Packit Service a9274b
		period_time = buffer_time / DIV_BUFFERTIME;
Packit Service a9274b
Packit Service a9274b
		/* Set buffer time and period time */
Packit Service a9274b
		err = snd_pcm_hw_params_set_buffer_time_near(sndpcm->handle,
Packit Service a9274b
				params, &buffer_time, 0);
Packit Service a9274b
		if (err < 0) {
Packit Service a9274b
			fprintf(bat->err, _("Set parameter to device error: "));
Packit Service a9274b
			fprintf(bat->err, _("buffer time: %d %s: %s(%d)\n"),
Packit Service a9274b
					buffer_time,
Packit Service a9274b
					device_name, snd_strerror(err), err);
Packit Service a9274b
			return err;
Packit Service a9274b
		}
Packit Service a9274b
Packit Service a9274b
		err = snd_pcm_hw_params_set_period_time_near(sndpcm->handle,
Packit Service a9274b
				params, &period_time, 0);
Packit Service a9274b
		if (err < 0) {
Packit Service a9274b
			fprintf(bat->err, _("Set parameter to device error: "));
Packit Service a9274b
			fprintf(bat->err, _("period time: %d %s: %s(%d)\n"),
Packit Service a9274b
					period_time,
Packit Service a9274b
					device_name, snd_strerror(err), err);
Packit Service a9274b
			return err;
Packit Service a9274b
		}
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	/* Write the parameters to the driver */
Packit Service a9274b
	if (snd_pcm_hw_params(sndpcm->handle, params) < 0) {
Packit Service a9274b
		fprintf(bat->err, _("Set parameter to device error: "));
Packit Service a9274b
		fprintf(bat->err, _("hw params: %s: %s(%d)\n"),
Packit Service a9274b
				device_name, snd_strerror(err), err);
Packit Service a9274b
		return -EINVAL;
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	err = snd_pcm_hw_params_get_period_size(params,
Packit Service a9274b
			&sndpcm->period_size, 0);
Packit Service a9274b
	if (err < 0) {
Packit Service a9274b
		fprintf(bat->err, _("Get parameter from device error: "));
Packit Service a9274b
		fprintf(bat->err, _("period size: %zd %s: %s(%d)\n"),
Packit Service a9274b
				sndpcm->period_size,
Packit Service a9274b
				device_name, snd_strerror(err), err);
Packit Service a9274b
		return err;
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	err = snd_pcm_hw_params_get_buffer_size(params, &sndpcm->buffer_size);
Packit Service a9274b
	if (err < 0) {
Packit Service a9274b
		fprintf(bat->err, _("Get parameter from device error: "));
Packit Service a9274b
		fprintf(bat->err, _("buffer size: %zd %s: %s(%d)\n"),
Packit Service a9274b
				sndpcm->buffer_size,
Packit Service a9274b
				device_name, snd_strerror(err), err);
Packit Service a9274b
		return err;
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	if (sndpcm->period_size == sndpcm->buffer_size) {
Packit Service a9274b
		fprintf(bat->err, _("Invalid parameters: can't use period "));
Packit Service a9274b
		fprintf(bat->err, _("equal to buffer size (%zd)\n"),
Packit Service a9274b
				sndpcm->period_size);
Packit Service a9274b
		return -EINVAL;
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	fprintf(bat->log, _("Get period size: %d  buffer size: %d\n"),
Packit Service a9274b
			(int) sndpcm->period_size, (int) sndpcm->buffer_size);
Packit Service a9274b
Packit Service a9274b
	err = snd_pcm_format_physical_width(format);
Packit Service a9274b
	if (err < 0) {
Packit Service a9274b
		fprintf(bat->err, _("Invalid parameters: "));
Packit Service a9274b
		fprintf(bat->err, _("snd_pcm_format_physical_width: %d\n"),
Packit Service a9274b
				err);
Packit Service a9274b
		return err;
Packit Service a9274b
	}
Packit Service a9274b
	sndpcm->sample_bits = err;
Packit Service a9274b
Packit Service a9274b
	sndpcm->frame_bits = sndpcm->sample_bits * bat->channels;
Packit Service a9274b
Packit Service a9274b
	/* Calculate the period bytes */
Packit Service a9274b
	sndpcm->period_bytes = sndpcm->period_size * sndpcm->frame_bits / 8;
Packit Service a9274b
	sndpcm->buffer = (char *) malloc(sndpcm->period_bytes);
Packit Service a9274b
	if (sndpcm->buffer == NULL) {
Packit Service a9274b
		fprintf(bat->err, _("Not enough memory: size=%zd\n"),
Packit Service a9274b
				sndpcm->period_bytes);
Packit Service a9274b
		return -ENOMEM;
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	return 0;
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
static int write_to_pcm(const struct pcm_container *sndpcm,
Packit Service a9274b
		int frames, struct bat *bat)
Packit Service a9274b
{
Packit Service a9274b
	int err;
Packit Service a9274b
	int offset = 0;
Packit Service a9274b
	int remain = frames;
Packit Service a9274b
Packit Service a9274b
	while (remain > 0) {
Packit Service a9274b
		err = snd_pcm_writei(sndpcm->handle, sndpcm->buffer + offset,
Packit Service a9274b
				remain);
Packit Service a9274b
		if (err == -EAGAIN || (err >= 0 && err < frames)) {
Packit Service a9274b
			snd_pcm_wait(sndpcm->handle, 500);
Packit Service a9274b
		} else if (err == -EPIPE) {
Packit Service a9274b
			fprintf(bat->err, _("Underrun: %s(%d)\n"),
Packit Service a9274b
					snd_strerror(err), err);
Packit Service a9274b
			if (bat->roundtriplatency)
Packit Service a9274b
				bat->latency.xrun_error = true;
Packit Service a9274b
			snd_pcm_prepare(sndpcm->handle);
Packit Service a9274b
		} else if (err == -ESTRPIPE) {
Packit Service a9274b
			while ((err = snd_pcm_resume(sndpcm->handle)) == -EAGAIN)
Packit Service a9274b
				sleep(1);  /* wait until resume flag is released */
Packit Service a9274b
			if (err < 0)
Packit Service a9274b
				snd_pcm_prepare(sndpcm->handle);
Packit Service a9274b
		} else if (err < 0) {
Packit Service a9274b
			fprintf(bat->err, _("Write PCM device error: %s(%d)\n"),
Packit Service a9274b
					snd_strerror(err), err);
Packit Service a9274b
			return err;
Packit Service a9274b
		}
Packit Service a9274b
Packit Service a9274b
		if (err > 0) {
Packit Service a9274b
			remain -= err;
Packit Service a9274b
			offset += err * sndpcm->frame_bits / 8;
Packit Service a9274b
		}
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	return 0;
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
/**
Packit Service a9274b
 * Process output data for latency test
Packit Service a9274b
 */
Packit Service a9274b
static int latencytest_process_output(struct pcm_container *sndpcm,
Packit Service a9274b
		struct bat *bat)
Packit Service a9274b
{
Packit Service a9274b
	int err = 0;
Packit Service a9274b
	int bytes = sndpcm->period_bytes; /* playback buffer size */
Packit Service a9274b
	int frames = sndpcm->period_size; /* frame count */
Packit Service a9274b
Packit Service a9274b
	bat->latency.is_playing = true;
Packit Service a9274b
Packit Service a9274b
	while (1) {
Packit Service a9274b
		/* generate output data */
Packit Service a9274b
		err = handleoutput(bat, sndpcm->buffer, bytes, frames);
Packit Service a9274b
		if (err != 0)
Packit Service a9274b
			break;
Packit Service a9274b
Packit Service a9274b
		err = write_to_pcm(sndpcm, frames, bat);
Packit Service a9274b
		if (err != 0)
Packit Service a9274b
			break;
Packit Service a9274b
Packit Service a9274b
		/* Xrun error, terminate the playback thread*/
Packit Service a9274b
		if (bat->latency.xrun_error == true)
Packit Service a9274b
			break;
Packit Service a9274b
Packit Service a9274b
		if (bat->latency.state == LATENCY_STATE_COMPLETE_SUCCESS)
Packit Service a9274b
			break;
Packit Service a9274b
Packit Service a9274b
		bat->periods_played++;
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	bat->latency.is_playing = false;
Packit Service a9274b
Packit Service a9274b
	return err;
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
static int write_to_pcm_loop(struct pcm_container *sndpcm, struct bat *bat)
Packit Service a9274b
{
Packit Service a9274b
	int err = 0;
Packit Service a9274b
	int bytes = sndpcm->period_bytes; /* playback buffer size */
Packit Service a9274b
	int frames = bytes * 8 / sndpcm->frame_bits; /* frame count */
Packit Service a9274b
	FILE *fp = NULL;
Packit Service a9274b
	int bytes_total = 0;
Packit Service a9274b
Packit Service a9274b
	if (bat->debugplay) {
Packit Service a9274b
		fp = fopen(bat->debugplay, "wb");
Packit Service a9274b
		err = -errno;
Packit Service a9274b
		if (fp == NULL) {
Packit Service a9274b
			fprintf(bat->err, _("Cannot open file: %s %d\n"),
Packit Service a9274b
					bat->debugplay, err);
Packit Service a9274b
			return err;
Packit Service a9274b
		}
Packit Service a9274b
		/* leave space for wav header */
Packit Service a9274b
		if (fseek(fp, sizeof(struct wav_container), SEEK_SET) != 0) {
Packit Service a9274b
			err = -errno;
Packit Service a9274b
			fclose(fp);
Packit Service a9274b
			return err;
Packit Service a9274b
		}
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	while (1) {
Packit Service a9274b
		err = generate_input_data(bat, sndpcm->buffer, bytes, frames);
Packit Service a9274b
		if (err != 0)
Packit Service a9274b
			break;
Packit Service a9274b
Packit Service a9274b
		if (bat->debugplay) {
Packit Service a9274b
			if (fwrite(sndpcm->buffer, 1, bytes, fp) != bytes) {
Packit Service a9274b
				err = -EIO;
Packit Service a9274b
				break;
Packit Service a9274b
			}
Packit Service a9274b
			bytes_total += bytes;
Packit Service a9274b
		}
Packit Service a9274b
Packit Service a9274b
		bat->periods_played++;
Packit Service a9274b
		if (bat->period_is_limited
Packit Service a9274b
				&& bat->periods_played >= bat->periods_total)
Packit Service a9274b
			break;
Packit Service a9274b
Packit Service a9274b
		err = write_to_pcm(sndpcm, frames, bat);
Packit Service a9274b
		if (err != 0)
Packit Service a9274b
			break;
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	if (bat->debugplay) {
Packit Service a9274b
		update_wav_header(bat, fp, bytes_total);
Packit Service a9274b
		fclose(fp);
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	snd_pcm_drain(sndpcm->handle);
Packit Service a9274b
Packit Service a9274b
	return err;
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
/**
Packit Service a9274b
 * Play
Packit Service a9274b
 */
Packit Service a9274b
void *playback_alsa(struct bat *bat)
Packit Service a9274b
{
Packit Service a9274b
	int err = 0;
Packit Service a9274b
	struct pcm_container sndpcm;
Packit Service a9274b
Packit Service a9274b
	fprintf(bat->log, _("Entering playback thread (ALSA).\n"));
Packit Service a9274b
Packit Service a9274b
	retval_play = 0;
Packit Service a9274b
	memset(&sndpcm, 0, sizeof(sndpcm));
Packit Service a9274b
Packit Service a9274b
	err = snd_pcm_open(&sndpcm.handle, bat->playback.device,
Packit Service a9274b
			SND_PCM_STREAM_PLAYBACK, 0);
Packit Service a9274b
	if (err != 0) {
Packit Service a9274b
		fprintf(bat->err, _("Cannot open PCM playback device: "));
Packit Service a9274b
		fprintf(bat->err, _("%s(%d)\n"), snd_strerror(err), err);
Packit Service a9274b
		retval_play = err;
Packit Service a9274b
		goto exit1;
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	err = set_snd_pcm_params(bat, &sndpcm);
Packit Service a9274b
	if (err != 0) {
Packit Service a9274b
		retval_play = err;
Packit Service a9274b
		goto exit2;
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	if (bat->playback.file == NULL) {
Packit Service a9274b
		fprintf(bat->log, _("Playing generated audio sine wave"));
Packit Service a9274b
		bat->sinus_duration == 0 ?
Packit Service a9274b
			fprintf(bat->log, _(" endlessly\n")) :
Packit Service a9274b
			fprintf(bat->log, _("\n"));
Packit Service a9274b
	} else {
Packit Service a9274b
		fprintf(bat->log, _("Playing input audio file: %s\n"),
Packit Service a9274b
				bat->playback.file);
Packit Service a9274b
		bat->fp = fopen(bat->playback.file, "rb");
Packit Service a9274b
		err = -errno;
Packit Service a9274b
		if (bat->fp == NULL) {
Packit Service a9274b
			fprintf(bat->err, _("Cannot open file: %s %d\n"),
Packit Service a9274b
					bat->playback.file, err);
Packit Service a9274b
			retval_play = err;
Packit Service a9274b
			goto exit3;
Packit Service a9274b
		}
Packit Service a9274b
		/* Skip header */
Packit Service a9274b
		err = read_wav_header(bat, bat->playback.file, bat->fp, true);
Packit Service a9274b
		if (err != 0) {
Packit Service a9274b
			retval_play = err;
Packit Service a9274b
			goto exit4;
Packit Service a9274b
		}
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	if (bat->roundtriplatency)
Packit Service a9274b
		err = latencytest_process_output(&sndpcm, bat);
Packit Service a9274b
	else
Packit Service a9274b
		err = write_to_pcm_loop(&sndpcm, bat);
Packit Service a9274b
	if (err < 0) {
Packit Service a9274b
		retval_play = err;
Packit Service a9274b
		goto exit4;
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
exit4:
Packit Service a9274b
	if (bat->playback.file)
Packit Service a9274b
		fclose(bat->fp);
Packit Service a9274b
exit3:
Packit Service a9274b
	free(sndpcm.buffer);
Packit Service a9274b
exit2:
Packit Service a9274b
	snd_pcm_close(sndpcm.handle);
Packit Service a9274b
exit1:
Packit Service a9274b
	pthread_exit(&retval_play);
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
static int read_from_pcm(struct pcm_container *sndpcm,
Packit Service a9274b
		int frames, struct bat *bat)
Packit Service a9274b
{
Packit Service a9274b
	int err = 0;
Packit Service a9274b
	int offset = 0;
Packit Service a9274b
	int remain = frames;
Packit Service a9274b
Packit Service a9274b
	while (remain > 0) {
Packit Service a9274b
		err = snd_pcm_readi(sndpcm->handle,
Packit Service a9274b
				sndpcm->buffer + offset, remain);
Packit Service a9274b
		if (err == -EAGAIN || (err >= 0 && err < remain)) {
Packit Service a9274b
			snd_pcm_wait(sndpcm->handle, 500);
Packit Service a9274b
		} else if (err == -EPIPE) {
Packit Service a9274b
			snd_pcm_prepare(sndpcm->handle);
Packit Service a9274b
			fprintf(bat->err, _("Overrun: %s(%d)\n"),
Packit Service a9274b
					snd_strerror(err), err);
Packit Service a9274b
			if (bat->roundtriplatency)
Packit Service a9274b
				bat->latency.xrun_error = true;
Packit Service a9274b
		} else if (err == -ESTRPIPE) {
Packit Service a9274b
			while ((err = snd_pcm_resume(sndpcm->handle)) == -EAGAIN)
Packit Service a9274b
				sleep(1);  /* wait until resume flag is released */
Packit Service a9274b
			if (err < 0)
Packit Service a9274b
				snd_pcm_prepare(sndpcm->handle);
Packit Service a9274b
		} else if (err < 0) {
Packit Service a9274b
			fprintf(bat->err, _("Read PCM device error: %s(%d)\n"),
Packit Service a9274b
					snd_strerror(err), err);
Packit Service a9274b
			return err;
Packit Service a9274b
		}
Packit Service a9274b
Packit Service a9274b
		if (err > 0) {
Packit Service a9274b
			remain -= err;
Packit Service a9274b
			offset += err * sndpcm->frame_bits / 8;
Packit Service a9274b
		}
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	return 0;
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
static int read_from_pcm_loop(struct pcm_container *sndpcm, struct bat *bat)
Packit Service a9274b
{
Packit Service a9274b
	int err = 0;
Packit Service a9274b
	FILE *fp = NULL;
Packit Service a9274b
	int size, frames;
Packit Service a9274b
	int bytes_read = 0;
Packit Service a9274b
	int bytes_count = bat->frames * bat->frame_size;
Packit Service a9274b
	int remain = bytes_count;
Packit Service a9274b
Packit Service a9274b
	remove(bat->capture.file);
Packit Service a9274b
	fp = fopen(bat->capture.file, "wb");
Packit Service a9274b
	err = -errno;
Packit Service a9274b
	if (fp == NULL) {
Packit Service a9274b
		fprintf(bat->err, _("Cannot open file: %s %d\n"),
Packit Service a9274b
				bat->capture.file, err);
Packit Service a9274b
		return err;
Packit Service a9274b
	}
Packit Service a9274b
	/* leave space for file header */
Packit Service a9274b
	if (fseek(fp, sizeof(struct wav_container), SEEK_SET) != 0) {
Packit Service a9274b
		err = -errno;
Packit Service a9274b
		fclose(fp);
Packit Service a9274b
		return err;
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	while (remain > 0) {
Packit Service a9274b
		size = (remain <= sndpcm->period_bytes) ?
Packit Service a9274b
			remain : sndpcm->period_bytes;
Packit Service a9274b
		frames = size * 8 / sndpcm->frame_bits;
Packit Service a9274b
Packit Service a9274b
		/* read a chunk from pcm device */
Packit Service a9274b
		err = read_from_pcm(sndpcm, frames, bat);
Packit Service a9274b
		if (err != 0)
Packit Service a9274b
			break;
Packit Service a9274b
Packit Service a9274b
		/* write the chunk to file */
Packit Service a9274b
		if (fwrite(sndpcm->buffer, 1, size, fp) != size) {
Packit Service a9274b
			err = -EIO;
Packit Service a9274b
			break;
Packit Service a9274b
		}
Packit Service a9274b
Packit Service a9274b
		bytes_read += size;
Packit Service a9274b
		remain -= size;
Packit Service a9274b
		bat->periods_played++;
Packit Service a9274b
Packit Service a9274b
		if (bat->period_is_limited
Packit Service a9274b
				&& bat->periods_played >= bat->periods_total)
Packit Service a9274b
			break;
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	update_wav_header(bat, fp, bytes_read);
Packit Service a9274b
Packit Service a9274b
	fclose(fp);
Packit Service a9274b
	return err;
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
/**
Packit Service a9274b
 * Process input data for latency test
Packit Service a9274b
 */
Packit Service a9274b
static int latencytest_process_input(struct pcm_container *sndpcm,
Packit Service a9274b
		struct bat *bat)
Packit Service a9274b
{
Packit Service a9274b
	int err = 0;
Packit Service a9274b
	FILE *fp = NULL;
Packit Service a9274b
	int bytes_read = 0;
Packit Service a9274b
	int frames = sndpcm->period_size;
Packit Service a9274b
	int size = sndpcm->period_bytes;
Packit Service a9274b
	int bytes_count = bat->frames * bat->frame_size;
Packit Service a9274b
Packit Service a9274b
	remove(bat->capture.file);
Packit Service a9274b
	fp = fopen(bat->capture.file, "wb");
Packit Service a9274b
	err = -errno;
Packit Service a9274b
	if (fp == NULL) {
Packit Service a9274b
		fprintf(bat->err, _("Cannot open file: %s %d\n"),
Packit Service a9274b
				bat->capture.file, err);
Packit Service a9274b
		return err;
Packit Service a9274b
	}
Packit Service a9274b
	/* leave space for file header */
Packit Service a9274b
	if (fseek(fp, sizeof(struct wav_container), SEEK_SET) != 0) {
Packit Service a9274b
		fclose(fp);
Packit Service a9274b
		return err;
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	bat->latency.is_capturing = true;
Packit Service a9274b
Packit Service a9274b
	while (bytes_read < bytes_count) {
Packit Service a9274b
		/* read a chunk from pcm device */
Packit Service a9274b
		err = read_from_pcm(sndpcm, frames, bat);
Packit Service a9274b
		if (err != 0)
Packit Service a9274b
			break;
Packit Service a9274b
Packit Service a9274b
		/* Xrun error, terminate the capture thread*/
Packit Service a9274b
		if (bat->latency.xrun_error == true)
Packit Service a9274b
			break;
Packit Service a9274b
Packit Service a9274b
		err = handleinput(bat, sndpcm->buffer, frames);
Packit Service a9274b
		if (err != 0)
Packit Service a9274b
			break;
Packit Service a9274b
Packit Service a9274b
		if (bat->latency.is_playing == false)
Packit Service a9274b
			break;
Packit Service a9274b
Packit Service a9274b
		/* write the chunk to file */
Packit Service a9274b
		if (fwrite(sndpcm->buffer, 1, size, fp) != size) {
Packit Service a9274b
			err = -EIO;
Packit Service a9274b
			break;
Packit Service a9274b
		}
Packit Service a9274b
Packit Service a9274b
		bytes_read += size;
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	bat->latency.is_capturing = false;
Packit Service a9274b
Packit Service a9274b
	update_wav_header(bat, fp, bytes_read);
Packit Service a9274b
Packit Service a9274b
	fclose(fp);
Packit Service a9274b
	return err;
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
Packit Service a9274b
static void pcm_cleanup(void *p)
Packit Service a9274b
{
Packit Service a9274b
	snd_pcm_close(p);
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
/**
Packit Service a9274b
 * Record
Packit Service a9274b
 */
Packit Service a9274b
void *record_alsa(struct bat *bat)
Packit Service a9274b
{
Packit Service a9274b
	int err = 0;
Packit Service a9274b
	struct pcm_container sndpcm;
Packit Service a9274b
Packit Service a9274b
	pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
Packit Service a9274b
Packit Service a9274b
	fprintf(bat->log, _("Entering capture thread (ALSA).\n"));
Packit Service a9274b
Packit Service a9274b
	retval_record = 0;
Packit Service a9274b
	memset(&sndpcm, 0, sizeof(sndpcm));
Packit Service a9274b
Packit Service a9274b
	err = snd_pcm_open(&sndpcm.handle, bat->capture.device,
Packit Service a9274b
			SND_PCM_STREAM_CAPTURE, 0);
Packit Service a9274b
	if (err != 0) {
Packit Service a9274b
		fprintf(bat->err, _("Cannot open PCM capture device: "));
Packit Service a9274b
		fprintf(bat->err, _("%s(%d)\n"), snd_strerror(err), err);
Packit Service a9274b
		retval_record = err;
Packit Service a9274b
		goto exit1;
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	err = set_snd_pcm_params(bat, &sndpcm);
Packit Service a9274b
	if (err != 0) {
Packit Service a9274b
		retval_record = err;
Packit Service a9274b
		goto exit2;
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
Packit Service a9274b
	pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);
Packit Service a9274b
	pthread_cleanup_push(pcm_cleanup, sndpcm.handle);
Packit Service a9274b
	pthread_cleanup_push(free, sndpcm.buffer);
Packit Service a9274b
Packit Service a9274b
	fprintf(bat->log, _("Recording ...\n"));
Packit Service a9274b
	if (bat->roundtriplatency)
Packit Service a9274b
		err = latencytest_process_input(&sndpcm, bat);
Packit Service a9274b
	else
Packit Service a9274b
		err = read_from_pcm_loop(&sndpcm, bat);
Packit Service a9274b
Packit Service a9274b
	pthread_cleanup_pop(0);
Packit Service a9274b
	pthread_cleanup_pop(0);
Packit Service a9274b
Packit Service a9274b
	if (err != 0) {
Packit Service a9274b
		retval_record = err;
Packit Service a9274b
		goto exit3;
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	/* Normally we will never reach this part of code (unless error in
Packit Service a9274b
	 * previous call) (before exit3) as this thread will be cancelled
Packit Service a9274b
	 * by end of play thread. Except in single line mode. */
Packit Service a9274b
	snd_pcm_drain(sndpcm.handle);
Packit Service a9274b
	pthread_exit(&retval_record);
Packit Service a9274b
Packit Service a9274b
exit3:
Packit Service a9274b
	free(sndpcm.buffer);
Packit Service a9274b
exit2:
Packit Service a9274b
	snd_pcm_close(sndpcm.handle);
Packit Service a9274b
exit1:
Packit Service a9274b
	pthread_exit(&retval_record);
Packit Service a9274b
}