All of lore.kernel.org
 help / color / mirror / Atom feed
From: han.lu@intel.com
To: tiwai@suse.de, liam.r.girdwood@intel.com,
	bernard.gautier@intel.com, caleb@crome.org,
	alsa-devel@alsa-project.org
Cc: "Lu, Han" <han.lu@intel.com>
Subject: [PATCH V2 3/7] BAT: Add playback and record functions
Date: Wed, 23 Sep 2015 15:48:51 +0800	[thread overview]
Message-ID: <00cc4402f76a0e6af0fa384422054cafc810a5e3.1442993440.git.han.lu@intel.com> (raw)
In-Reply-To: <cover.1442993440.git.han.lu@intel.com>
In-Reply-To: <cover.1442993440.git.han.lu@intel.com>

From: "Lu, Han" <han.lu@intel.com>

Add functions as main loop of playback thread and record thread.
The functions access pcm hardware through ALSA APIs.

Signed-off-by: Lu, Han <han.lu@intel.com>
Signed-off-by: Liam Girdwood <liam.r.girdwood@intel.com>
Signed-off-by: Bernard Gautier <bernard.gautier@intel.com>

diff --git a/bat/alsa.c b/bat/alsa.c
new file mode 100644
index 0000000..582c604
--- /dev/null
+++ b/bat/alsa.c
@@ -0,0 +1,594 @@
+/*
+ * Copyright (C) 2013-2015 Intel Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdbool.h>
+#include <math.h>
+#include <stdint.h>
+#include <pthread.h>
+
+#include <alsa/asoundlib.h>
+
+#include "aconfig.h"
+#include "gettext.h"
+
+#include "common.h"
+#include "alsa.h"
+#include "signal.h"
+
+struct pcm_container {
+	snd_pcm_t *handle;
+	snd_pcm_uframes_t period_size;
+	snd_pcm_uframes_t buffer_size;
+	snd_pcm_format_t format;
+	unsigned short channels;
+	size_t period_bytes;
+	size_t sample_bits;
+	size_t frame_bits;
+	char *buffer;
+};
+
+static int set_snd_pcm_params(struct bat *bat, struct pcm_container *sndpcm)
+{
+	snd_pcm_hw_params_t *params;
+	unsigned int buffer_time = 0;
+	unsigned int period_time = 0;
+	unsigned int rate;
+	int err;
+	const char *device_name = snd_pcm_name(sndpcm->handle);
+
+	/* Allocate a hardware parameters object. */
+	snd_pcm_hw_params_alloca(&params);
+
+	/* Fill it in with default values. */
+	err = snd_pcm_hw_params_any(sndpcm->handle, params);
+	if (err < 0) {
+		fprintf(bat->err, _("Set parameter to device error: "));
+		fprintf(bat->err, _("default params: %s: %s(%d)\n"),
+				device_name, snd_strerror(err), err);
+		return err;
+	}
+
+	/* Set access mode */
+	err = snd_pcm_hw_params_set_access(sndpcm->handle, params,
+			SND_PCM_ACCESS_RW_INTERLEAVED);
+	if (err < 0) {
+		fprintf(bat->err, _("Set parameter to device error: "));
+		fprintf(bat->err, _("access type: %s: %s(%d)\n"),
+				device_name, snd_strerror(err), err);
+		return err;
+	}
+
+	/* Set format */
+	err = snd_pcm_hw_params_set_format(sndpcm->handle, params, bat->format);
+	if (err < 0) {
+		fprintf(bat->err, _("Set parameter to device error: "));
+		fprintf(bat->err, _("PCM format: %d %s: %s(%d)\n"),
+				bat->format,
+				device_name, snd_strerror(err), err);
+		return err;
+	}
+
+	/* Set channels */
+	err = snd_pcm_hw_params_set_channels(sndpcm->handle,
+			params, bat->channels);
+	if (err < 0) {
+		fprintf(bat->err, _("Set parameter to device error: "));
+		fprintf(bat->err, _("channel number: %d %s: %s(%d)\n"),
+				bat->channels,
+				device_name, snd_strerror(err), err);
+		return err;
+	}
+
+	/* Set sampling rate */
+	rate = bat->rate;
+	err = snd_pcm_hw_params_set_rate_near(sndpcm->handle,
+			params, &bat->rate,
+			0);
+	if (err < 0) {
+		fprintf(bat->err, _("Set parameter to device error: "));
+		fprintf(bat->err, _("sample rate: %d %s: %s(%d)\n"),
+				bat->rate,
+				device_name, snd_strerror(err), err);
+		return err;
+	}
+	if ((float) rate * (1 + RATE_RANGE) < bat->rate
+			|| (float) rate * (1 - RATE_RANGE) > bat->rate) {
+		fprintf(bat->err, _("Invalid parameters: sample rate: "));
+		fprintf(bat->err, _("requested %dHz, got %dHz\n"),
+				rate, bat->rate);
+		return -EINVAL;
+	}
+
+	if (snd_pcm_hw_params_get_buffer_time_max(params,
+			&buffer_time, 0) < 0) {
+		fprintf(bat->err, _("Get parameter from device error: "));
+		fprintf(bat->err, _("buffer time: %d %s: %s(%d)\n"),
+				buffer_time,
+				device_name, snd_strerror(err), err);
+		return -EINVAL;
+	}
+
+	if (buffer_time > MAX_BUFFERTIME)
+		buffer_time = MAX_BUFFERTIME;
+
+	period_time = buffer_time / DIV_BUFFERTIME;
+
+	/* Set buffer time and period time */
+	err = snd_pcm_hw_params_set_buffer_time_near(sndpcm->handle, params,
+			&buffer_time, 0);
+	if (err < 0) {
+		fprintf(bat->err, _("Set parameter to device error: "));
+		fprintf(bat->err, _("buffer time: %d %s: %s(%d)\n"),
+				buffer_time,
+				device_name, snd_strerror(err), err);
+		return err;
+	}
+
+	err = snd_pcm_hw_params_set_period_time_near(sndpcm->handle, params,
+			&period_time, 0);
+	if (err < 0) {
+		fprintf(bat->err, _("Set parameter to device error: "));
+		fprintf(bat->err, _("period time: %d %s: %s(%d)\n"),
+				period_time,
+				device_name, snd_strerror(err), err);
+		return err;
+	}
+
+	/* Write the parameters to the driver */
+	if (snd_pcm_hw_params(sndpcm->handle, params) < 0) {
+		fprintf(bat->err, _("Set parameter to device error: "));
+		fprintf(bat->err, _("hw params: %s: %s(%d)\n"),
+				device_name, snd_strerror(err), err);
+		return -EINVAL;
+	}
+
+	err = snd_pcm_hw_params_get_period_size(params,
+			&sndpcm->period_size, 0);
+	if (err < 0) {
+		fprintf(bat->err, _("Get parameter from device error: "));
+		fprintf(bat->err, _("period size: %zd %s: %s(%d)\n"),
+				sndpcm->period_size,
+				device_name, snd_strerror(err), err);
+		return err;
+	}
+
+	err = snd_pcm_hw_params_get_buffer_size(params, &sndpcm->buffer_size);
+	if (err < 0) {
+		fprintf(bat->err, _("Get parameter from device error: "));
+		fprintf(bat->err, _("buffer size: %zd %s: %s(%d)\n"),
+				sndpcm->buffer_size,
+				device_name, snd_strerror(err), err);
+		return err;
+	}
+
+	if (sndpcm->period_size == sndpcm->buffer_size) {
+		fprintf(bat->err, _("Invalid parameters: can't use period "));
+		fprintf(bat->err, _("equal to buffer size (%zd)\n"),
+				sndpcm->period_size);
+		return -EINVAL;
+	}
+
+	err = snd_pcm_format_physical_width(bat->format);
+	if (err < 0) {
+		fprintf(bat->err, _("Invalid parameters: "));
+		fprintf(bat->err, _("snd_pcm_format_physical_width: %d\n"),
+				err);
+		return err;
+	}
+	sndpcm->sample_bits = err;
+
+	sndpcm->frame_bits = sndpcm->sample_bits * bat->channels;
+
+	/* Calculate the period bytes */
+	sndpcm->period_bytes = sndpcm->period_size * sndpcm->frame_bits / 8;
+	sndpcm->buffer = (char *) malloc(sndpcm->period_bytes);
+	if (sndpcm->buffer == NULL) {
+		fprintf(bat->err, _("Not enough memory: size=%zd\n"),
+				sndpcm->period_bytes);
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+/*
+ * Generate buffer to be played either from input file or from generated data
+ * Return value
+ * <0 error
+ * 0 ok
+ * >0 break
+ */
+static int generate_input_data(struct pcm_container *sndpcm, int bytes,
+		struct bat *bat)
+{
+	int err;
+	static int load;
+	int frames = bytes * 8 / sndpcm->frame_bits;
+
+	if (bat->playback.file != NULL) {
+		/* From input file */
+		load = 0;
+
+		while (1) {
+			err = fread(sndpcm->buffer + load, 1,
+					bytes - load, bat->fp);
+			if (0 == err) {
+				if (feof(bat->fp)) {
+					fprintf(bat->log,
+							_("End of playing.\n"));
+					return 1;
+				}
+			} else if (err < bytes - load) {
+				if (ferror(bat->fp)) {
+					fprintf(bat->err, _("Read file error"));
+					fprintf(bat->err, _(": %d\n"), err);
+					return -EIO;
+				}
+				load += err;
+			} else {
+				break;
+			}
+		}
+	} else {
+		/* Generate sine wave */
+		if ((bat->sinus_duration) && (load > bat->sinus_duration))
+			return 1;
+
+		err = generate_sine_wave(bat, frames, (void *)sndpcm->buffer);
+		if (err != 0)
+			return err;
+
+		load += frames;
+	}
+
+	return 0;
+}
+
+static int write_to_pcm(const struct pcm_container *sndpcm,
+		int frames, struct bat *bat)
+{
+	int err;
+	int offset = 0;
+	int remain = frames;
+
+	while (remain > 0) {
+		err = snd_pcm_writei(sndpcm->handle, sndpcm->buffer + offset,
+				remain);
+		if (err == -EAGAIN || (err >= 0 && err < frames)) {
+			snd_pcm_wait(sndpcm->handle, 500);
+		} else if (err == -EPIPE) {
+			fprintf(bat->err, _("Underrun: %s(%d)\n"),
+					snd_strerror(err), err);
+			snd_pcm_prepare(sndpcm->handle);
+		} else if (err < 0) {
+			fprintf(bat->err, _("Write PCM device error: %s(%d)\n"),
+					snd_strerror(err), err);
+			return err;
+		}
+
+		if (err > 0) {
+			remain -= err;
+			offset += err * sndpcm->frame_bits / 8;
+		}
+	}
+
+	return 0;
+}
+
+static int write_to_pcm_loop(struct pcm_container *sndpcm, struct bat *bat)
+{
+	int err;
+	int bytes = sndpcm->period_bytes; /* playback buffer size */
+	int frames = bytes * 8 / sndpcm->frame_bits; /* frame count */
+	FILE *fp = NULL;
+	struct wav_container wav;
+	int bytes_total = 0;
+
+	if (bat->debugplay) {
+		fp = fopen(bat->debugplay, "wb");
+		if (fp == NULL) {
+			fprintf(bat->err, _("Cannot open file for capture: "));
+			fprintf(bat->err, _("%s %d\n"), bat->debugplay, -errno);
+			return -errno;
+		}
+		/* leave space for wav header */
+		err = fseek(fp, sizeof(wav), SEEK_SET);
+		if (err != 0) {
+			fprintf(bat->err, _("Seek file error: %d %d\n"),
+					err, -errno);
+			return -errno;
+		}
+	}
+
+	while (1) {
+		err = generate_input_data(sndpcm, bytes, bat);
+		if (err < 0)
+			return err;
+		else if (err > 0)
+			break;
+
+		if (bat->debugplay) {
+			err = fwrite(sndpcm->buffer, 1, bytes, fp);
+			if (err != bytes) {
+				fprintf(bat->err, _("Write file error: "));
+				fprintf(bat->err, _("%s(%d)\n"),
+						snd_strerror(err), err);
+				return -EIO;
+			}
+			bytes_total += bytes;
+		}
+
+		bat->periods_played++;
+		if (bat->period_is_limited
+				&& bat->periods_played >= bat->periods_total)
+			break;
+
+		err = write_to_pcm(sndpcm, frames, bat);
+		if (err != 0)
+			return err;
+	}
+
+	if (bat->debugplay) {
+		/* update wav header */
+		prepare_wav_info(&wav, bat);
+		wav.chunk.length = bytes_total;
+		wav.header.length = (wav.chunk.length) + sizeof(wav.chunk)
+			+ sizeof(wav.format) + sizeof(wav.header) - 8;
+
+		rewind(fp);
+		err = write_wav_header(fp, &wav, bat);
+		if (err != 0) {
+			fprintf(bat->err, _("Write file error: %s %s(%d)\n"),
+					bat->debugplay, snd_strerror(err), err);
+			return err;
+		}
+		fclose(fp);
+	}
+
+	snd_pcm_drain(sndpcm->handle);
+
+	return 0;
+}
+
+/**
+ * Play
+ */
+void *playback_alsa(struct bat *bat)
+{
+	int err = 0;
+	struct pcm_container sndpcm;
+
+	fprintf(bat->log, _("Entering playback thread (ALSA).\n"));
+
+	retval_play = 0;
+	memset(&sndpcm, 0, sizeof(sndpcm));
+
+	if (bat->playback.device == NULL) {
+		fprintf(bat->err, _("No PCM device for playback: exit\n"));
+		retval_play = 1;
+		goto exit1;
+	}
+
+	err = snd_pcm_open(&sndpcm.handle, bat->playback.device,
+			SND_PCM_STREAM_PLAYBACK, 0);
+	if (err != 0) {
+		fprintf(bat->err, _("Cannot open PCM playback device: "));
+		fprintf(bat->err, _("%s(%d)\n"), snd_strerror(err), err);
+		retval_play = 1;
+		goto exit1;
+	}
+
+	err = set_snd_pcm_params(bat, &sndpcm);
+	if (err != 0) {
+		retval_play = 1;
+		goto exit2;
+	}
+
+	if (bat->playback.file == NULL) {
+		fprintf(bat->log, _("Playing generated audio sine wave"));
+		bat->sinus_duration == 0 ?
+			fprintf(bat->log, _(" endlessly\n")) :
+			fprintf(bat->log, _("\n"));
+	} else {
+		fprintf(bat->log, _("Playing input audio file: %s\n"),
+				bat->playback.file);
+		bat->fp = fopen(bat->playback.file, "rb");
+		if (bat->fp == NULL) {
+			fprintf(bat->err, _("Cannot open file for capture: "));
+			fprintf(bat->err, _("%s %d\n"),
+					bat->playback.file, -errno);
+			retval_play = 1;
+			goto exit3;
+		}
+		/* Skip header */
+		err = read_wav_header(bat, bat->playback.file, bat->fp, true);
+		if (err != 0) {
+			retval_play = 1;
+			goto exit4;
+		}
+	}
+
+	err = write_to_pcm_loop(&sndpcm, bat);
+	if (err != 0) {
+		retval_play = 1;
+		goto exit4;
+	}
+
+exit4:
+	if (bat->playback.file)
+		fclose(bat->fp);
+exit3:
+	free(sndpcm.buffer);
+exit2:
+	snd_pcm_close(sndpcm.handle);
+exit1:
+	pthread_exit(&retval_play);
+}
+
+static int read_from_pcm(struct pcm_container *sndpcm,
+		int frames, struct bat *bat)
+{
+	int err = 0;
+	int offset = 0;
+	int remain = frames;
+
+	while (remain > 0) {
+		err = snd_pcm_readi(sndpcm->handle,
+				sndpcm->buffer + offset, remain);
+		if (err == -EAGAIN || (err >= 0 && err < remain)) {
+			snd_pcm_wait(sndpcm->handle, 500);
+		} else if (err == -EPIPE) {
+			snd_pcm_prepare(sndpcm->handle);
+			fprintf(bat->err, _("Overrun: %s(%d)\n"),
+					snd_strerror(err), err);
+		} else if (err < 0) {
+			fprintf(bat->err, _("Read PCM device error: %s(%d)\n"),
+					snd_strerror(err), err);
+			return err;
+		}
+
+		if (err > 0) {
+			remain -= err;
+			offset += err * sndpcm->frame_bits / 8;
+		}
+	}
+
+	return 0;
+}
+
+static int read_from_pcm_loop(FILE *fp, int count,
+		struct pcm_container *sndpcm, struct bat *bat)
+{
+	int err = 0;
+	int size, frames;
+	int remain = count;
+
+	while (remain > 0) {
+		size = (remain <= sndpcm->period_bytes) ?
+			remain : sndpcm->period_bytes;
+		frames = size * 8 / sndpcm->frame_bits;
+
+		/* read a chunk from pcm device */
+		err = read_from_pcm(sndpcm, frames, bat);
+		if (err != 0)
+			return err;
+
+		/* write the chunk to file */
+		err = fwrite(sndpcm->buffer, 1, size, fp);
+		if (err != size) {
+			fprintf(bat->err, _("Write file error: %s(%d)\n"),
+					snd_strerror(err), err);
+			return -EIO;
+		}
+		remain -= size;
+		bat->periods_played++;
+
+		if (bat->period_is_limited
+				&& bat->periods_played >= bat->periods_total)
+			break;
+	}
+
+	return 0;
+}
+
+/**
+ * Record
+ */
+void *record_alsa(struct bat *bat)
+{
+	int err = 0;
+	FILE *fp = NULL;
+	struct pcm_container sndpcm;
+	struct wav_container wav;
+	int count;
+
+	pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
+
+	fprintf(bat->log, _("Entering capture thread (ALSA).\n"));
+
+	retval_record = 0;
+	memset(&sndpcm, 0, sizeof(sndpcm));
+
+	if (bat->capture.device == NULL) {
+		fprintf(bat->err, _("No PCM device for capture: exit\n"));
+		retval_record = 1;
+		goto exit1;
+	}
+
+	err = snd_pcm_open(&sndpcm.handle, bat->capture.device,
+			SND_PCM_STREAM_CAPTURE, 0);
+	if (err != 0) {
+		fprintf(bat->err, _("Cannot open PCM capture device: "));
+		fprintf(bat->err, _("%s(%d)\n"), snd_strerror(err), err);
+		retval_record = 1;
+		goto exit1;
+	}
+
+	err = set_snd_pcm_params(bat, &sndpcm);
+	if (err != 0) {
+		retval_record = 1;
+		goto exit2;
+	}
+
+	remove(bat->capture.file);
+	fp = fopen(bat->capture.file, "w+");
+	if (fp == NULL) {
+		fprintf(bat->err, _("Cannot open file for capture: %s %d\n"),
+				bat->capture.file, -errno);
+		retval_record = 1;
+		goto exit3;
+	}
+
+	prepare_wav_info(&wav, bat);
+
+	pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+	pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);
+	pthread_cleanup_push(snd_pcm_close, sndpcm.handle);
+	pthread_cleanup_push(free, sndpcm.buffer);
+	pthread_cleanup_push(fclose, fp);
+
+	err = write_wav_header(fp, &wav, bat);
+	if (err != 0) {
+		retval_record = 1;
+		goto exit4;
+	}
+
+	count = wav.chunk.length;
+	fprintf(bat->log, _("Recording ...\n"));
+	err = read_from_pcm_loop(fp, count, &sndpcm, bat);
+	if (err != 0) {
+		retval_record = 1;
+		goto exit4;
+	}
+
+	/* Normally we will never reach this part of code (before fail_exit) as
+	   this thread will be cancelled by end of play thread. */
+	pthread_cleanup_pop(0);
+	pthread_cleanup_pop(0);
+	pthread_cleanup_pop(0);
+
+	snd_pcm_drain(sndpcm.handle);
+
+exit4:
+	fclose(fp);
+exit3:
+	free(sndpcm.buffer);
+exit2:
+	snd_pcm_close(sndpcm.handle);
+exit1:
+	pthread_exit(&retval_record);
+}
diff --git a/bat/alsa.h b/bat/alsa.h
new file mode 100644
index 0000000..d5c9972
--- /dev/null
+++ b/bat/alsa.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2013-2015 Intel Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+extern int retval_play;
+extern int retval_record;
+
+void *playback_alsa(struct bat *);
+void *record_alsa(struct bat *);
-- 
1.9.1

  parent reply	other threads:[~2015-09-23  7:51 UTC|newest]

Thread overview: 13+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2015-09-23  7:48 [PATCH V2 0/7] BAT: Add Basic Audio Tester command line tool han.lu
2015-09-23  7:48 ` [PATCH V2 1/7] BAT: Add initial functions han.lu
2015-10-02 10:55   ` Takashi Iwai
2015-10-12  8:56     ` Lu, Han
2015-10-13  9:53       ` Liam Girdwood
2015-09-23  7:48 ` [PATCH V2 2/7] BAT: Add common definitions and functions han.lu
2015-10-02 10:57   ` Takashi Iwai
2015-09-23  7:48 ` han.lu [this message]
2015-09-23  7:48 ` [PATCH V2 4/7] BAT: Add signal generator han.lu
2015-09-23  7:48 ` [PATCH V2 5/7] BAT: Add converting functions han.lu
2015-09-23  7:48 ` [PATCH V2 6/7] BAT: Add spectrum analysis functions han.lu
2015-09-23  7:48 ` [PATCH V2 7/7] BAT: Add Makefile and configures han.lu
2015-10-02 10:49 ` [PATCH V2 0/7] BAT: Add Basic Audio Tester command line tool Takashi Iwai

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=00cc4402f76a0e6af0fa384422054cafc810a5e3.1442993440.git.han.lu@intel.com \
    --to=han.lu@intel.com \
    --cc=alsa-devel@alsa-project.org \
    --cc=bernard.gautier@intel.com \
    --cc=caleb@crome.org \
    --cc=liam.r.girdwood@intel.com \
    --cc=tiwai@suse.de \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.