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(¶ms); + + /* 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 *);