346 lines
10 KiB
C
346 lines
10 KiB
C
/*
|
|
* OSS sound handling functions
|
|
*
|
|
* This file is part of ANT (Ant is Not a Telephone)
|
|
*
|
|
* Copyright 2002, 2003 Roland Stigge
|
|
*
|
|
* ANT 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.
|
|
*
|
|
* ANT 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.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with ANT; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
/* GNU headers */
|
|
#include <stdio.h>
|
|
#ifdef HAVE_SYS_IOCTL_H
|
|
#include <sys/ioctl.h>
|
|
#endif
|
|
#ifdef HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif
|
|
#ifdef HAVE_FCNTL_H
|
|
#include <fcntl.h>
|
|
#endif
|
|
#include <math.h>
|
|
#include <errno.h>
|
|
#include <sys/soundcard.h>
|
|
#ifdef HAVE_STDLIB_H
|
|
#include <stdlib.h>
|
|
#endif
|
|
#include <string.h>
|
|
|
|
/* own header files */
|
|
#include "globals.h"
|
|
#include "sound.h"
|
|
|
|
/* try formats in this order */
|
|
int default_audio_priorities[] = {SND_PCM_FORMAT_S16_LE,
|
|
SND_PCM_FORMAT_S16_BE,
|
|
SND_PCM_FORMAT_U16_LE,
|
|
SND_PCM_FORMAT_U16_BE,
|
|
SND_PCM_FORMAT_U8,
|
|
SND_PCM_FORMAT_S8,
|
|
/* alaw/ulaw at last because no native soundcard support assumed */
|
|
SND_PCM_FORMAT_A_LAW,
|
|
SND_PCM_FORMAT_MU_LAW,
|
|
0}; /* end of list */
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
/*!
|
|
* @brief Common initialization for a specific audio device
|
|
*
|
|
* @param audio PCM descriptor of an audio device.
|
|
* @param format PCM format.
|
|
* @param channels number of PCM channels.
|
|
* @param speed requested/actual sampling rate (in/out).
|
|
* @param fragment_size requested/actual fragment size (in/out).
|
|
*
|
|
* @return 0 if successful, non-zero otherwise.
|
|
*/
|
|
static int init_audio_device(snd_pcm_t *audio,
|
|
int format,
|
|
int channels,
|
|
unsigned int *speed,
|
|
int *fragment_size)
|
|
{
|
|
int err;
|
|
unsigned int rspeed;
|
|
int dir;
|
|
unsigned int buffer_time, period_time;
|
|
snd_pcm_uframes_t period_size;
|
|
|
|
snd_pcm_hw_params_t *hwparams;
|
|
snd_pcm_sw_params_t *swparams;
|
|
snd_pcm_hw_params_alloca(&hwparams);
|
|
snd_pcm_sw_params_alloca(&swparams);
|
|
|
|
if ((err = snd_pcm_hw_params_any(audio, hwparams)) < 0) {
|
|
errprintf("AUDIO: AUDIO: No audio configurations available: %s\n",
|
|
snd_strerror(err));
|
|
return err;
|
|
}
|
|
|
|
if ((err = snd_pcm_hw_params_set_format(audio, hwparams, format)) < 0) {
|
|
errprintf("AUDIO: Audio sample format (%d) not available: %s\n",
|
|
format, snd_strerror(err));
|
|
return err;
|
|
}
|
|
|
|
if ((err = snd_pcm_hw_params_set_channels(audio, hwparams, channels)) < 0) {
|
|
errprintf("AUDIO: Audio channels count (%d) not available: %s\n",
|
|
channels, snd_strerror(err));
|
|
return err;
|
|
}
|
|
|
|
rspeed = *speed;
|
|
if ((err = snd_pcm_hw_params_set_rate_near(audio, hwparams, &rspeed, 0)) < 0) {
|
|
errprintf("AUDIO: Audio speed %dHz not available: %s\n",
|
|
*speed, snd_strerror(err));
|
|
return err;
|
|
}
|
|
if (rspeed != *speed) {
|
|
errprintf("AUDIO: Audio speed doesn't match (requested %dHz, got %dHz)\n",
|
|
*speed, rspeed);
|
|
return -EINVAL;
|
|
}
|
|
|
|
buffer_time = 150000;
|
|
if ((err = snd_pcm_hw_params_set_buffer_time_near(audio, hwparams, &buffer_time, &dir)) < 0) {
|
|
errprintf("AUDIO: Unable to set audio buffer time %d: %s\n",
|
|
buffer_time, snd_strerror(err));
|
|
return err;
|
|
}
|
|
|
|
period_time = 25000;
|
|
if ((err = snd_pcm_hw_params_set_period_time_near(audio, hwparams, &period_time, &dir)) < 0) {
|
|
errprintf("AUDIO: Unable to set audio period time %d: %s\n",
|
|
period_time, snd_strerror(err));
|
|
return err;
|
|
}
|
|
|
|
if ((err = snd_pcm_hw_params_get_period_size_min(hwparams, &period_size, &dir)) < 0) {
|
|
errprintf("AUDIO: Unable to get audio period time: %s\n",
|
|
snd_strerror(err));
|
|
return err;
|
|
}
|
|
*fragment_size = period_size;
|
|
|
|
if ((err = snd_pcm_hw_params_set_access(audio, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
|
|
errprintf("AUDIO: Unable to set audio access mode: %s\n",
|
|
snd_strerror(err));
|
|
return err;
|
|
}
|
|
|
|
if ((err = snd_pcm_hw_params(audio, hwparams)) < 0) {
|
|
errprintf("AUDIO: Unable to set hw params for audio: %s\n",
|
|
snd_strerror(err));
|
|
return err;
|
|
}
|
|
|
|
if ((err = snd_pcm_sw_params_current(audio, swparams)) < 0) {
|
|
errprintf("AUDIO: Unable to determine current swparams for audio: %s\n",
|
|
snd_strerror(err));
|
|
return err;
|
|
}
|
|
|
|
if ((err = snd_pcm_sw_params_set_start_threshold(audio, swparams, period_size)) < 0) {
|
|
errprintf("AUDIO: Unable to set start threshold: %s\n",
|
|
snd_strerror(err));
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
if ((err = snd_pcm_sw_params_set_sleep_min(audio, swparams, 0)) < 0) {
|
|
errprintf("AUDIO: Unable to set minimum sleep time: %s\n",
|
|
snd_strerror(err));
|
|
return err;
|
|
}
|
|
*/
|
|
|
|
if ((err = snd_pcm_sw_params_set_xfer_align(audio, swparams, 1)) < 0) {
|
|
errprintf("AUDIO: Unable to set transfer alignment: %s\n",
|
|
snd_strerror(err));
|
|
return err;
|
|
}
|
|
|
|
if ((err = snd_pcm_sw_params_set_silence_size(audio, swparams, period_size * 2)) < 0) {
|
|
errprintf("AUDIO: Unable to set silence threshold: %s\n",
|
|
snd_strerror(err));
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
if ((err = snd_pcm_sw_params_set_stop_threshold(audio, swparams, 0)) < 0) {
|
|
errprintf("AUDIO: Unable to set stop threshold: %s\n",
|
|
snd_strerror(err));
|
|
return err;
|
|
}
|
|
*/
|
|
|
|
//snd_pcm_sw_params_set_avail_min(Handle,swparams,Frames);
|
|
|
|
|
|
if ((err = snd_pcm_sw_params(audio, swparams)) < 0) {
|
|
printf("Unable to set sw params for audio: %s\n", snd_strerror(err));
|
|
return err;
|
|
}
|
|
|
|
#if 0
|
|
if ((err = snd_pcm_prepare(audio)) < 0) {
|
|
errprintf("AUDIO: Cannot prepare audio: %s\n", snd_strerror(err));
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
int open_audio_devices(char *in_audio_device_name,
|
|
char *out_audio_device_name,
|
|
int channels, int *format_priorities,
|
|
snd_pcm_t **audio_in, snd_pcm_t **audio_out,
|
|
int *fragment_size_in, int *fragment_size_out,
|
|
int *format_in, int *format_out,
|
|
unsigned int *speed_in, unsigned int *speed_out)
|
|
{
|
|
int err;
|
|
int initresult;
|
|
int *priority;
|
|
|
|
/* try to open the sound device */
|
|
if ((err = snd_pcm_open(audio_in, in_audio_device_name, SND_PCM_STREAM_CAPTURE, 0/*SND_PCM_NONBLOCK*/)) < 0) {
|
|
errprintf("AUDIO: Audio recording device '%s' open error: %s, trying default\n",
|
|
in_audio_device_name, snd_strerror(err));
|
|
if ((err = snd_pcm_open(audio_in, "default", SND_PCM_STREAM_CAPTURE, 0/*SND_PCM_NONBLOCK*/)) < 0) {
|
|
errprintf("AUDIO: Audio recording device 'default' open error: %s\n",
|
|
snd_strerror(err));
|
|
return -1;
|
|
}
|
|
}
|
|
if ((err = snd_pcm_open(audio_out, out_audio_device_name, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) {
|
|
errprintf("AUDIO: Audio playback device '%s' open error: %s, trying default\n",
|
|
out_audio_device_name, snd_strerror(err));
|
|
if ((err = snd_pcm_open(audio_out, "default", SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) {
|
|
errprintf("AUDIO: Audio playback device 'default' open error: %s\n",
|
|
snd_strerror(err));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* set format and sampling rate */
|
|
for (initresult = -1, priority = format_priorities;
|
|
initresult && priority; priority++) {
|
|
|
|
*format_in = *priority;
|
|
initresult = init_audio_device(*audio_in, *format_in, channels, speed_in, fragment_size_in);
|
|
}
|
|
|
|
if (initresult)
|
|
return initresult; /* no chance */
|
|
|
|
for (initresult = -1, priority = format_priorities;
|
|
initresult && priority; priority++) {
|
|
|
|
*format_out = *priority;
|
|
initresult = init_audio_device(*audio_out, *format_out, channels, speed_out, fragment_size_out);
|
|
}
|
|
|
|
#if 0
|
|
/* not implemented in ALSA */
|
|
if ((err = snd_pcm_link(*audio_out, *audio_in)) < 0) {
|
|
errprintf("AUDIO: Error linking in/out devices: %s\n",
|
|
snd_strerror(err));
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
if (debug > 2) {
|
|
/* TODO: redirect output to dbgprintf */
|
|
snd_output_t *output;
|
|
snd_output_stdio_attach(&output, stderr, 0);
|
|
dbgprintf(2, "AUDIO: Dump of IN PCM:");
|
|
snd_pcm_dump(*audio_in, output);
|
|
dbgprintf(2, "AUDIO: Dump of OUT PCM:");
|
|
snd_pcm_dump(*audio_out, output);
|
|
}
|
|
|
|
return initresult;
|
|
}
|
|
|
|
int audio_stop(snd_pcm_t *audio_in, snd_pcm_t *audio_out) {
|
|
|
|
int err, err0;
|
|
|
|
err = 0;
|
|
if ((err0 = snd_pcm_drop(audio_in)) < 0) {
|
|
errprintf("AUDIO: Unable to reset audio capture: %s\n", snd_strerror(err0));
|
|
err = -1;
|
|
}
|
|
if ((err0 = snd_pcm_drop(audio_out)) < 0) {
|
|
errprintf("AUDIO: Unable to reset audio playback: %s\n", snd_strerror(err0));
|
|
err = -1;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
int close_audio_devices(snd_pcm_t *audio_in, snd_pcm_t *audio_out)
|
|
{
|
|
|
|
int err, err0;
|
|
|
|
audio_stop(audio_in, audio_out);
|
|
|
|
err = 0;
|
|
if ((err0 = snd_pcm_close(audio_in)) < 0) {
|
|
errprintf("AUDIO: Unable to close audio capture: %s\n", snd_strerror(err0));
|
|
err = -1;
|
|
}
|
|
if ((err0 = snd_pcm_close(audio_out)) < 0) {
|
|
errprintf("AUDIO: Unable to close audio playback: %s\n", snd_strerror(err0));
|
|
err = -1;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
int sample_size_from_format(int format)
|
|
{
|
|
switch(format) {
|
|
case SND_PCM_FORMAT_S16_LE:
|
|
case SND_PCM_FORMAT_S16_BE:
|
|
case SND_PCM_FORMAT_U16_LE:
|
|
case SND_PCM_FORMAT_U16_BE:
|
|
return 2;
|
|
|
|
case SND_PCM_FORMAT_U8:
|
|
case SND_PCM_FORMAT_S8:
|
|
case SND_PCM_FORMAT_MU_LAW:
|
|
case SND_PCM_FORMAT_A_LAW:
|
|
return 1;
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/*--------------------------------------------------------------------------*/
|