// Copyright (C) 1999-2006 Jean-Marc Valin & Dominic Letourneau
#include "Node.h"
#include "Vector.h"
#include "ObjectParser.h"
#include <iostream>
#include <stdio.h>
#include "stream_wrap.h"
#include "Stream.h"
#include <alsa/asoundlib.h>
#include <sched.h>
#include <sys/mman.h>
#include <bits/mman.h>
namespace FD {
class ALSASound;
DECLARE_NODE(ALSASound)
/*Node
*
* @name ALSASound
* @category DSP:Audio
* @description Opens a sound device
*
* @output_name OUTPUT
* @output_description A file descriptor to the sound device
*
* @parameter_name DEVICE
* @parameter_type string
* @parameter_value plughw:0,0
* @parameter_description ALSA sound device use
*
* @parameter_name RATE
* @parameter_type int
* @parameter_value 48000
* @parameter_description Sampling rate
*
* @parameter_name CHANNELS
* @parameter_type int
* @parameter_description 1 for stereo, 0 for mono
*
* @parameter_name MODE
* @parameter_type string
* @parameter_value W
* @parameter_description R for sound input, W for sound output, RW for full-duplex mode
*
* @parameter_name PERIOD
* @parameter_type int
* @parameter_value 256
* @parameter_description Length of the audio periods to allocate (not reliable)
*
* @parameter_name NB_PERIODS
* @parameter_type int
* @parameter_value 2
* @parameter_description Number of audio periods to allocate
*
END*/
class alsa_streambuf : public std::streambuf {
protected:
virtual int overflow(int = EOF) {std::cerr << "Don't call alsa_streambuf::overflow()" << std::endl;return 1;}
virtual std::streamsize xsputn(const char *s, std::streamsize n);
virtual int uflow() {std::cerr << "Don't call alsa_streambuf::uflow()" << std::endl;return 1;}
virtual int underflow() {std::cerr << "Don't call alsa_streambuf::underflow()" << std::endl;return 1;}
virtual std::streamsize xsgetn(char *s, std::streamsize n);
virtual int pbackfail(int c) {std::cerr << "Don't call alsa_streambuf::pbackfail()" << std::endl;return 1;}
public:
alsa_streambuf(std::string dev, int _mode, unsigned int _rate, int _channels, int _period);
virtual ~alsa_streambuf();
protected:
std::string device_name;
unsigned int rate;
int channels;
int mode;
int period;
snd_pcm_t *capture_handle;
snd_pcm_t *playback_handle;
//int readN, writeN;
//struct pollfd *read_fd, *write_fd;
};
class alsa_ostream : public std::ostream {
alsa_streambuf _streambuffer;
public:
alsa_ostream(std::string dev, int _mode, unsigned int _rate, int _channels, int _period)
: std::ostream(&_streambuffer)
, _streambuffer (dev, _mode, _rate, _channels, _period)
{clear();}
};
class alsa_istream : public std::istream {
alsa_streambuf _streambuffer;
public:
alsa_istream(std::string dev, int _mode, unsigned int _rate, int _channels, int _period)
: std::istream(&_streambuffer)
, _streambuffer (dev, _mode, _rate, _channels, _period)
{clear();}
};
class alsa_iostream : public std::iostream {
alsa_streambuf _streambuffer;
public:
alsa_iostream(std::string dev, int _mode, unsigned int _rate, int _channels, int _period)
: std::iostream(&_streambuffer)
, _streambuffer (dev, _mode, _rate, _channels, _period)
{clear();}
};
alsa_streambuf::alsa_streambuf(std::string dev, int _mode, unsigned int _rate, int _channels, int _period)
: device_name(dev)
, mode (_mode)
, rate (_rate)
, channels (_channels)
, period (_period)
{
int dir;
int err;
snd_pcm_hw_params_t *hw_params;
snd_pcm_sw_params_t *sw_params;
snd_pcm_uframes_t period_size = period;
snd_pcm_uframes_t buffer_size = 2*period;
static snd_output_t *jcd_out;
int nb_periods=2;
std::cerr << "Initialising ALSA" << std::endl;
err = snd_output_stdio_attach(&jcd_out, stdout, 0);
if ((err = snd_pcm_open (&capture_handle, device_name.c_str(), SND_PCM_STREAM_CAPTURE, 0)) < 0) {
fprintf (stderr, "cannot open capture audio device %s (%s)\n",
device_name.c_str(),
snd_strerror (err));
throw new GeneralException("Cannot setup capture", __FILE__, __LINE__);
}
if ((err = snd_pcm_hw_params_malloc (&hw_params)) < 0) {
fprintf (stderr, "cannot allocate capture hardware parameter structure (%s)\n",
snd_strerror (err));
throw new GeneralException("Cannot setup capture", __FILE__, __LINE__);
}
if ((err = snd_pcm_hw_params_any (capture_handle, hw_params)) < 0) {
fprintf (stderr, "cannot initialize capture hardware parameter structure (%s)\n",
snd_strerror (err));
throw new GeneralException("Cannot setup capture", __FILE__, __LINE__);
}
if ((err = snd_pcm_hw_params_set_access (capture_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
fprintf (stderr, "cannot set capture access type (%s)\n",
snd_strerror (err));
throw new GeneralException("Cannot setup capture", __FILE__, __LINE__);
}
if ((err = snd_pcm_hw_params_set_format (capture_handle, hw_params, SND_PCM_FORMAT_S16_LE)) < 0) {
fprintf (stderr, "cannot set capture sample format (%s)\n",
snd_strerror (err));
throw new GeneralException("Cannot setup capture", __FILE__, __LINE__);
}
if ((err = snd_pcm_hw_params_set_rate_near (capture_handle, hw_params, &rate, 0)) < 0) {
fprintf (stderr, "cannot set capture sample rate (%s)\n",
snd_strerror (err));
throw new GeneralException("Cannot setup capture", __FILE__, __LINE__);
}
/*fprintf (stderr, "rate = %d\n", rate);*/
if ((err = snd_pcm_hw_params_set_channels (capture_handle, hw_params, channels)) < 0) {
fprintf (stderr, "cannot set capture channel count (%s)\n",
snd_strerror (err));
throw new GeneralException("Cannot setup capture", __FILE__, __LINE__);
}
period_size = period;
dir = 0;
if ((err = snd_pcm_hw_params_set_period_size_near (capture_handle, hw_params, &period_size, &dir)) < 0) {
fprintf (stderr, "cannot set capture period size (%s)\n",
snd_strerror (err));
throw new GeneralException("Cannot setup capture", __FILE__, __LINE__);
}
dir = 0;
if ((err = snd_pcm_hw_params_set_periods (capture_handle, hw_params, nb_periods, 0)) < 0) {
fprintf (stderr, "cannot set capture number of periods (%s)\n",
snd_strerror (err));
throw new GeneralException("Cannot setup capture", __FILE__, __LINE__);
}
buffer_size = period_size * nb_periods;
dir=0;
if ((err = snd_pcm_hw_params_set_buffer_size_near (capture_handle, hw_params, &buffer_size)) < 0) {
fprintf (stderr, "cannot set capture buffer size (%s)\n",
snd_strerror (err));
throw new GeneralException("Cannot setup capture", __FILE__, __LINE__);
}
if ((err = snd_pcm_hw_params (capture_handle, hw_params)) < 0) {
fprintf (stderr, "cannot set capture parameters (%s)\n",
snd_strerror (err));
throw new GeneralException("Cannot setup capture", __FILE__, __LINE__);
}
snd_pcm_dump_setup(capture_handle, jcd_out);
snd_pcm_hw_params_free (hw_params);
if ((err = snd_pcm_sw_params_malloc (&sw_params)) < 0) {
fprintf (stderr, "cannot allocate capture software parameters structure (%s)\n",
snd_strerror (err));
throw new GeneralException("Cannot setup capture", __FILE__, __LINE__);
}
if ((err = snd_pcm_sw_params_current (capture_handle, sw_params)) < 0) {
fprintf (stderr, "cannot initialize capture software parameters structure (%s)\n",
snd_strerror (err));
throw new GeneralException("Cannot setup capture", __FILE__, __LINE__);
}
if ((err = snd_pcm_sw_params_set_avail_min (capture_handle, sw_params, period)) < 0) {
fprintf (stderr, "cannot set capture minimum available count (%s)\n",
snd_strerror (err));
throw new GeneralException("Cannot setup capture", __FILE__, __LINE__);
}
if ((err = snd_pcm_sw_params (capture_handle, sw_params)) < 0) {
fprintf (stderr, "cannot set capture software parameters (%s)\n",
snd_strerror (err));
throw new GeneralException("Cannot setup capture", __FILE__, __LINE__);
}
if ((err = snd_pcm_open (&playback_handle, device_name.c_str(), SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
fprintf (stderr, "cannot open playback audio device %s (%s)\n",
device_name.c_str(),
snd_strerror (err));
throw new GeneralException("Cannot setup playback", __FILE__, __LINE__);
}
if ((err = snd_pcm_hw_params_malloc (&hw_params)) < 0) {
fprintf (stderr, "cannot allocate playback hardware parameter structure (%s)\n",
snd_strerror (err));
throw new GeneralException("Cannot setup playback", __FILE__, __LINE__);
}
if ((err = snd_pcm_hw_params_any (playback_handle, hw_params)) < 0) {
fprintf (stderr, "cannot initialize playback hardware parameter structure (%s)\n",
snd_strerror (err));
throw new GeneralException("Cannot setup playback", __FILE__, __LINE__);
}
if ((err = snd_pcm_hw_params_set_access (playback_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
fprintf (stderr, "cannot set playback access type (%s)\n",
snd_strerror (err));
throw new GeneralException("Cannot setup playback", __FILE__, __LINE__);
}
if ((err = snd_pcm_hw_params_set_format (playback_handle, hw_params, SND_PCM_FORMAT_S16_LE)) < 0) {
fprintf (stderr, "cannot set playback sample format (%s)\n",
snd_strerror (err));
throw new GeneralException("Cannot setup playback", __FILE__, __LINE__);
}
if ((err = snd_pcm_hw_params_set_rate_near (playback_handle, hw_params, &rate, 0)) < 0) {
fprintf (stderr, "cannot set playback sample rate (%s)\n",
snd_strerror (err));
throw new GeneralException("Cannot setup playback", __FILE__, __LINE__);
}
/*fprintf (stderr, "rate = %d\n", rate);*/
if ((err = snd_pcm_hw_params_set_channels (playback_handle, hw_params, channels)) < 0) {
fprintf (stderr, "cannot set playback channel count (%s)\n",
snd_strerror (err));
throw new GeneralException("Cannot setup playback", __FILE__, __LINE__);
}
period_size = period;
dir = 0;
if ((err = snd_pcm_hw_params_set_period_size_near (playback_handle, hw_params, &period_size, &dir)) < 0) {
fprintf (stderr, "cannot set playback period size (%s)\n",
snd_strerror (err));
throw new GeneralException("Cannot setup playback", __FILE__, __LINE__);
}
dir = 0;
if ((err = snd_pcm_hw_params_set_periods (playback_handle, hw_params, nb_periods, 0)) < 0) {
fprintf (stderr, "cannot set capture number of periods (%s)\n",
snd_strerror (err));
throw new GeneralException("Cannot setup capture", __FILE__, __LINE__);
}
buffer_size = period_size * nb_periods;
dir=0;
if ((err = snd_pcm_hw_params_set_buffer_size_near (playback_handle, hw_params, &buffer_size)) < 0) {
fprintf (stderr, "cannot set playback buffer size (%s)\n",
snd_strerror (err));
throw new GeneralException("Cannot setup playback", __FILE__, __LINE__);
}
if ((err = snd_pcm_hw_params (playback_handle, hw_params)) < 0) {
fprintf (stderr, "cannot set playback parameters (%s)\n",
snd_strerror (err));
throw new GeneralException("Cannot setup playback", __FILE__, __LINE__);
}
snd_pcm_dump_setup(playback_handle, jcd_out);
snd_pcm_hw_params_free (hw_params);
if ((err = snd_pcm_sw_params_malloc (&sw_params)) < 0) {
fprintf (stderr, "cannot allocate playback software parameters structure (%s)\n",
snd_strerror (err));
throw new GeneralException("Cannot setup playback", __FILE__, __LINE__);
}
if ((err = snd_pcm_sw_params_current (playback_handle, sw_params)) < 0) {
fprintf (stderr, "cannot initialize playback software parameters structure (%s)\n",
snd_strerror (err));
throw new GeneralException("Cannot setup playback", __FILE__, __LINE__);
}
if ((err = snd_pcm_sw_params_set_avail_min (playback_handle, sw_params, period)) < 0) {
fprintf (stderr, "cannot set playback minimum available count (%s)\n",
snd_strerror (err));
throw new GeneralException("Cannot setup playback", __FILE__, __LINE__);
}
if ((err = snd_pcm_sw_params_set_start_threshold (playback_handle, sw_params, period)) < 0) {
fprintf (stderr, "cannot set playback start mode (%s)\n",
snd_strerror (err));
throw new GeneralException("Cannot setup playback", __FILE__, __LINE__);
}
if ((err = snd_pcm_sw_params (playback_handle, sw_params)) < 0) {
fprintf (stderr, "cannot set playback software parameters (%s)\n",
snd_strerror (err));
throw new GeneralException("Cannot setup playback", __FILE__, __LINE__);
}
snd_pcm_link(capture_handle, playback_handle);
perror("snd_pcm_link");
if ((err = snd_pcm_prepare (capture_handle)) < 0) {
fprintf (stderr, "cannot prepare capture audio interface for use (%s)\n",
snd_strerror (err));
throw new GeneralException("Cannot setup capture", __FILE__, __LINE__);
}
if ((err = snd_pcm_prepare (playback_handle)) < 0) {
fprintf (stderr, "cannot prepare playback audio interface for use (%s)\n",
snd_strerror (err));
throw new GeneralException("Cannot setup playback", __FILE__, __LINE__);
}
#if 0
readN = snd_pcm_poll_descriptors_count(capture_handle);
writeN = snd_pcm_poll_descriptors_count(playback_handle);
read_fd = malloc(readN*sizeof(*read_fd));
/*printf ("descriptors: %d %d\n", dev->readN, dev->writeN);*/
if (snd_pcm_poll_descriptors(capture_handle, read_fd, readN) != readN)
{
fprintf (stderr, "cannot obtain capture file descriptors (%s)\n",
snd_strerror (err));
assert(0);
}
write_fd = malloc(writeN*sizeof(*read_fd));
if (snd_pcm_poll_descriptors(playback_handle, write_fd, writeN) != writeN)
{
fprintf (stderr, "cannot obtain playback file descriptors (%s)\n",
snd_strerror (err));
assert(0);
}
#endif
struct sched_param param;
param.sched_priority = sched_get_priority_max(SCHED_FIFO);
//param.sched_priority = 40;
sched_setscheduler(0,SCHED_FIFO,¶m);
perror("sched_setscheduler");
if (mlockall(MCL_FUTURE)!=0)
perror("mlockall");
{
short zeros[channels*period];
for (int i=0;i<channels*period;i++)
zeros[i] = 0;
xsputn((char*)zeros, 2*channels*period);
xsputn((char*)zeros, 2*channels*period);
}
}
alsa_streambuf::~alsa_streambuf()
{
snd_pcm_close(playback_handle);
snd_pcm_close(capture_handle);
}
//#include <sys/time.h>
//#include <time.h>
std::streamsize alsa_streambuf::xsputn(const char *s, std::streamsize n)
{
int err;
#if 0
struct timeval tv;
gettimeofday(&tv,NULL);
snd_pcm_sframes_t del1, del2;
snd_pcm_delay(playback_handle, &del1);
snd_pcm_delay(capture_handle, &del2);
fprintf (stderr, "+%d\t%d\t%d\n", tv.tv_usec, del1, del2);
#endif
//fprintf (stderr, "+");
int nsamples = n/2/channels;
if ((err = snd_pcm_writei (playback_handle, s, nsamples)) != nsamples)
{
if (err<0)
{
//fprintf(stderr, "error %d, EPIPE = %d\n", err, EPIPE);
if (err == -EPIPE)
{
fprintf (stderr, "An underrun has occured, reseting playback, len=%d\n", nsamples);
} else
{
fprintf (stderr, "write to audio interface failed (%s)\n",
snd_strerror (err));
}
if ((err = snd_pcm_prepare (playback_handle)) < 0)
{
fprintf (stderr, "cannot prepare audio interface for use (%s)\n",
snd_strerror (err));
}
} else {
fprintf (stderr, "Couldn't write as many samples as I wanted (%d instead of %d)\n", err, nsamples);
}
//return 0;
}
//std::cerr << "wrote " << err << std::endl;
//std::cerr << "+";
return n;
}
std::streamsize alsa_streambuf::xsgetn(char *s, std::streamsize n)
{
int err;
#if 0
struct timeval tv;
gettimeofday(&tv,NULL);
snd_pcm_sframes_t del1, del2;
snd_pcm_delay(playback_handle, &del1);
snd_pcm_delay(capture_handle, &del2);
fprintf (stderr, "-%d\t%d\t%d\n", tv.tv_usec, del1, del2);
#endif
//fprintf (stderr, "-");
int nsamples = n/2/channels;
//fprintf (stderr, "reading...\n");
if ((err = snd_pcm_readi (capture_handle, s, nsamples)) != nsamples)
{
//fprintf (stderr, "error\n");
if (err<0)
{
//fprintf(stderr, "error %d, EPIPE = %d\n", err, EPIPE);
if (err == -EPIPE)
{
fprintf (stderr, "An overrun has occured, reseting capture\n");
} else
{
fprintf (stderr, "read from audio interface failed (%s)\n",
snd_strerror (err));
}
if ((err = snd_pcm_prepare (capture_handle)) < 0)
{
fprintf (stderr, "cannot prepare audio interface for use (%s)\n",
snd_strerror (err));
}
if ((err = snd_pcm_start (capture_handle)) < 0)
{
fprintf (stderr, "cannot prepare audio interface for use (%s)\n",
snd_strerror (err));
}
} else {
fprintf (stderr, "Couldn't read as many samples as I wanted (%d instead of %d)\n", err, nsamples);
}
//return 0;
}
//fprintf (stderr, "done reading\n");
return n;
}
/** A constant node contains a value that will never changes. */
class ALSASound : public Node
{
protected:
/**The value of the constant*/
ObjectRef value;
/*the file descriptor*/
int audio_fd;
/**The ID of the 'value' output*/
int outputID;
String device;
int speed;
int channels;
int period;
int nb_periods;
String mode;
bool devOpen;
public:
/**Constructor, takes the name of the node and a set of parameters*/
ALSASound(std::string nodeName, ParameterSet params)
: Node(nodeName, params)
{
outputID = addOutput("OUTPUT");
}
void initialize()
{
Node::initialize();
device = object_cast <String> (parameters.get("DEVICE"));
speed = dereference_cast<int> (parameters.get("RATE"));
channels = dereference_cast<int> (parameters.get("CHANNELS"));
period = dereference_cast<int> (parameters.get("PERIOD"));
nb_periods = dereference_cast<int> (parameters.get("NB_PERIODS"));
mode = object_cast <String> (parameters.get("MODE"));
devOpen = false;
}
virtual ~ALSASound()
{
//cerr << "Sound destructor\n";
//close(audio_fd);
}
/**Ask for the node's output which ID (number) is output_id
and for the 'count' iteration */
virtual ObjectRef getOutput(int output_id, int count)
{
if (!devOpen)
{
std::cerr << "Opening audio device " << device << std::endl;
value = ObjectRef(new IOStream(new alsa_iostream(device, 0, speed, channels, period)));
std::cerr << "done" << std::endl;
devOpen = true;
}
if (output_id==outputID) return value;
else throw new NodeException (this, "Sound: Unknown output id", __FILE__, __LINE__);
}
protected:
/**Default constructor, should not be used*/
ALSASound() {throw new GeneralException("Sound copy constructor should not be called",__FILE__,__LINE__);}
};
}//namespace FD
syntax highlighted by Code2HTML, v. 0.9.1