Commit f8cdc96f authored by yanmorin's avatar yanmorin

Audio refactoring to use float or integer with portaudio.

Samplerate is now a dependancy.
parent 10461835
2006-08-30 Yan Morin
* Set libsamplerate as a dependency
* Refactoring samplerate conversion, -DDATAFORMAT_IS_FLOAT allow the user to use float instead of int
2006-08-02 Yan Morin
* Add IAX quelch/unquelch
......
......@@ -80,11 +80,14 @@ Building the dependencies
-------------------------
If you do not use either the development packages of your distribution or the source packages made by the upstream authors of dependencies, you may want to try our custom dependencies building script in tools/ directory:
Note that commoncpp, ccrtp, libosip and libexosip, samplerate are in debian and fedora.
1. cd tools/
2. edit config.sh to change the default prefix (/usr/local)
3. ./download.sh
4. ./install.sh
5. ./portaudio.sh <-- compile portaudio
2. ./portaudio.sh <-- compile portaudio
3. if you want to install other software, check inside the file config.sh
edit config.sh to change the default prefix (/usr/local)
./download.sh
./install.sh
You can also compile each dependency, one by one:
......@@ -108,7 +111,7 @@ You can also compile each dependency, one by one:
make
make install
Note: if you install portaudio in /usr/local, don't forget to set pkg-config path with:
Note: if you install any package in /usr/local, don't forget to set pkg-config path with:
export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig/
......@@ -175,8 +178,16 @@ How to enable IAX support?
--------------------------
Go inside libs directory and execute ./libiax2.sh script.
Run ./configure with --enable-iax2 option.
Then, run ./configure with --enable-iax2 option.
Debugging SFLPhone
------------------
You can use the --with-debug option with configure
./configure --with-debug
make
cd src
PATH=. ./gui/qt/sflphone-qt
Run-time troubleshooting
......@@ -219,7 +230,7 @@ Short description of content of source tree
audiodriver, rtp layer, audio codec ulaw, alaw and gsm.
- src/audio/gsm/ contains the implementation of gsm audiocodec library.
- src/audio/pacpp/ implements PortAudioCpp, a native C++ binding of
PortAudio V19.
PortAudio V19. (remove in sflphone 0.7)
- src/gui/ is the old directory that contains all about different user
interface.
- src/gui/server is the directory that talk (tcp socket on port 3999) to
......
......@@ -7,6 +7,7 @@ Management of account (add, remove, ...)
Management of exceptions
Remove all warnings in compilation
Better handling for an reINVITE request. (done?)
Mono channel, float for portaudio
For project dependencies:
------------------------
......
......@@ -129,16 +129,22 @@ dnl Check for exosip2
LP_CHECK_EXOSIP2
SFLPHONE_LIBS="$SFLPHONE_LIBS $EXOSIP_LIBS"
dnl Check for samplerate
AC_CHECK_HEADER([samplerate.h], [
AC_CHECK_LIB(samplerate, src_simple, [with_samplerate=yes], [with_samplerate=no])
], [ with_samplerate=no ]
)
AM_CONDITIONAL(USE_SAMPLERATE, test x$with_samplerate = xyes)
dnl AC_CHECK_HEADER([samplerate.h], [
dnl AC_CHECK_LIB(samplerate, src_simple, [with_samplerate=yes], [with_samplerate=no])
dnl ], [ with_samplerate=no ]
dnl )
dnl AM_CONDITIONAL(USE_SAMPLERATE, test x$with_samplerate = xyes)
dnl Check for GNU ccRTP
PKG_PROG_PKG_CONFIG
LIBSAMPLERATE_MIN_VERSION=0.1.2
PKG_CHECK_MODULES(samplerate, samplerate >= ${LIBSAMPLERATE_MIN_VERSION})
SFLPHONE_CFLAGS="$SFLPHONE_CFLAGS $samplerate_CFLAGS"
SFLPHONE_LIBS="$SFLPHONE_LIBS $samplerate_LIBS"
LIBCCGNU2_MIN_VERSION=1.3.1
PKG_CHECK_MODULES(libccgnu2, libccgnu2 >= ${LIBCCGNU2_MIN_VERSION})
SFLPHONE_CFLAGS="$SFLPHONE_CFLAGS $libccgnu2_CFLAGS"
......
......@@ -25,14 +25,6 @@ IAXSOURCES =
IAXHEADERS =
endif
if USE_SAMPLERATE
SAMPLERATE_FLAG=-DUSE_SAMPLERATE
SAMPLERATE_LIB =-lsamplerate
else
SAMPLERATE_FLAG=
SAMPLERATE_LIB=
endif
SUBDIRS = audio config gui $(ZEROCONFDIR)
sflphoned_SOURCES = eventthread.cpp main.cpp voIPLink.cpp \
......
......@@ -12,14 +12,6 @@ SPEEX_FLAG=
SPEEX_LIB=
endif
if USE_SAMPLERATE
SAMPLERATE_FLAG=-DUSE_SAMPLERATE
SAMPLERATE_LIB =-lsamplerate
else
SAMPLERATE_FLAG=
SAMPLERATE_LIB=
endif
libaudio_la_SOURCES = alaw.cpp audiofile.cpp g711.cpp tonelist.cpp \
audiortp.cpp dtmf.cpp tone.cpp audiocodec.cpp audiolayer.cpp audiodevice.cpp dtmfgenerator.cpp gsmcodec.cpp \
tonegenerator.cpp ulaw.cpp codecDescriptor.cpp \
......
......@@ -33,6 +33,9 @@ public:
AudioCodec(int payload, const std::string &codecName);
virtual ~AudioCodec(void);
/**
* @return the number of bytes decoded
*/
virtual int codecDecode(short *, unsigned char *, unsigned int) = 0;
virtual int codecEncode(unsigned char *, short *, unsigned int) = 0;
......
......@@ -23,6 +23,9 @@
#include "audiofile.h"
#include "codecDescriptor.h"
#include <fstream>
#include <math.h>
#include <samplerate.h>
AudioFile::AudioFile()
: AudioLoop()
......@@ -38,12 +41,13 @@ AudioFile::~AudioFile()
delete _ulaw;
}
// load file in mono format
bool
AudioFile::loadFile(const std::string& filename, unsigned int nbChannel=2)
AudioFile::loadFile(const std::string& filename, unsigned int sampleRate=8000)
{
_nbChannel = (nbChannel==2) ? 2 : 1;
if (_filename == filename) {
// if the filename was already load, with the same samplerate
// we do nothing
if (_filename == filename && _sampleRate == sampleRate) {
return true;
} else {
// reset to 0
......@@ -54,6 +58,7 @@ AudioFile::loadFile(const std::string& filename, unsigned int nbChannel=2)
// no filename to load
if (filename.empty()) {
_debug("Unable to open audio file: filename is empty\n");
return false;
}
......@@ -61,6 +66,7 @@ AudioFile::loadFile(const std::string& filename, unsigned int nbChannel=2)
file.open(filename.c_str(), std::fstream::in);
if (!file.is_open()) {
// unable to load the file
_debug("Unable to open audio file %s\n", filename.c_str());
return false;
}
......@@ -71,6 +77,7 @@ AudioFile::loadFile(const std::string& filename, unsigned int nbChannel=2)
// allocate memory:
char fileBuffer[length];
// read data as a block:
file.read (fileBuffer,length);
file.close();
......@@ -80,29 +87,61 @@ AudioFile::loadFile(const std::string& filename, unsigned int nbChannel=2)
// expandedsize should be exactly two time more, else failed
int16 monoBuffer[length];
unsigned int expandedsize = _ulaw->codecDecode (monoBuffer, (unsigned char *) fileBuffer, length);
if (expandedsize != length*sizeof(int16)) {
if (expandedsize != length*2) {
_debug("Audio file error on loading audio file!");
return false;
}
unsigned int nbSampling = expandedsize/sizeof(int16);
// we need to change the sample rating here:
// case 1: we don't have to resample : only do splitting and convert
if ( sampleRate == 8000 ) {
// just s
_size = nbSampling;
_buffer = new SFLDataFormat[_size];
// src to dest
for(unsigned int i=0; i<nbSampling; i++) {
_buffer[i] = SFLConvertInt16(monoBuffer[i]);
}
} else {
// case 2: we need to convert it and split it
// convert here
double factord = (double)sampleRate / 8000;
float* floatBufferIn = new float[nbSampling];
int sizeOut = (int)(ceil(factord*nbSampling));
src_short_to_float_array(monoBuffer, floatBufferIn, nbSampling);
SFLDataFormat* bufferTmp = new SFLDataFormat[sizeOut];
unsigned int int16size = expandedsize/sizeof(int16);
SRC_DATA src_data;
src_data.data_in = floatBufferIn;
src_data.input_frames = nbSampling;
src_data.output_frames = sizeOut;
src_data.src_ratio = factord;
if (_nbChannel == 2) {
_size = int16size<<1; // multiply by two
_buffer = new int16[_size];
#ifdef DATAFORMAT_IS_FLOAT
// case number 2.1: the output is float32 : convert directly in _bufferTmp
src_data.data_out = bufferTmp;
src_simple (&src_data, SRC_SINC_BEST_QUALITY, 1);
#else
// case number 2.2: the output is int16 : convert and change to int16
float* floatBufferOut = new float[sizeOut];
src_data.data_out = floatBufferOut;
for(unsigned int i=0, k=0; i<int16size; i++) {
_buffer[k] = _buffer[k+1] = monoBuffer[i];
k+=2;
}
} else {
// copy the mono buffer to a mono buffer
_size = int16size;
_buffer = new int16[_size];
// src to dest
bcopy(monoBuffer, _buffer, expandedsize);
src_simple (&src_data, SRC_SINC_BEST_QUALITY, 1);
src_float_to_short_array(floatBufferOut, bufferTmp, src_data.output_frames_gen);
delete [] floatBufferOut;
#endif
delete [] floatBufferIn;
nbSampling = src_data.output_frames_gen;
// if we are in mono, we send the bufferTmp location and don't delete it
// else we split the audio in 2 and put it into buffer
_size = nbSampling;
_buffer = bufferTmp; // just send the buffer pointer;
bufferTmp = 0;
}
return true;
}
......@@ -35,7 +35,7 @@ public:
AudioFile();
~AudioFile();
bool loadFile(const std::string& filename, unsigned int nbChannel/*=2*/);
bool loadFile(const std::string& filename, unsigned int sampleRate/*=8000*/);
void start() { _start = true; }
void stop() { _start = false; }
bool isStarted() { return _start; }
......
......@@ -25,29 +25,67 @@
#include "../global.h"
#include "../manager.h"
AudioLayer::AudioLayer()
//#define SFL_TEST
//#define SFL_TEST_SINE
#ifdef SFL_TEST_SINE
#include <cmath>
#endif
AudioLayer::AudioLayer(ManagerImpl* manager)
: _urgentRingBuffer(SIZEBUF)
, _mainSndRingBuffer(SIZEBUF)
, _micRingBuffer(SIZEBUF)
, _stream(NULL)
, _errorMessage("")
, _manager(manager)
{
_sampleRate = 8000;
_inChannel = 1;
_outChannel = 1;
portaudio::System::initialize();
_inChannel = 1; // don't put in stereo
_outChannel = 1; // don't put in stereo
try {
portaudio::AutoSystem autoSys;
portaudio::System::initialize();
}
catch (const portaudio::PaException &e) {
setErrorMessage(e.paErrorText());
}
catch (const portaudio::PaCppException &e) {
setErrorMessage(e.what());
} // std::runtime_error &e (e.what())
catch (...) {
setErrorMessage("Unknown type error in portaudio initialization");
}
#ifdef SFL_TEST_SINE
leftPhase_ = 0;
tableSize_ = 200;
const double PI = 3.14159265;
table_ = new float[tableSize_];
for (int i = 0; i < tableSize_; ++i)
{
table_[i] = 0.125f * (float)sin(((double)i/(double)tableSize_)*PI*2.);
_debug("%9.8f\n", table_[i]);
}
#endif
}
// Destructor
AudioLayer::~AudioLayer (void)
{
{
stopStream();
closeStream();
try {
portaudio::System::terminate();
} catch (const portaudio::PaException &e) {
_debug("! AL: Catch an exception when portaudio tried to terminate\n");
}
closeStream();
#ifdef SFL_TEST_SINE
delete [] table_;
#endif
}
void
......@@ -72,40 +110,117 @@ AudioLayer::openDevice (int indexIn, int indexOut, int sampleRate)
{
closeStream();
_sampleRate = sampleRate;
int portaudioFramePerBuffer = FRAME_PER_BUFFER; //=FRAME_PER_BUFFER; //= paFramesPerBufferUnspecified;
int nbDevice = getDeviceCount();
if (nbDevice == 0) {
_debug("Portaudio detect no sound card.");
return;
} else {
if (indexIn >= nbDevice) {
_debug(" Portaudio auto-select device #0 for input because device #%02d is not found\n", indexIn);
indexIn = 0;
}
if (indexOut >= nbDevice) {
_debug(" Portaudio auto-select device #0 for output because device #%02d is not found\n", indexOut);
indexOut = 0;
}
_debug(" Setting audiolayer: device in=%2d, out=%2d\n", indexIn, indexOut);
_debug(" : nb channel in=%2d, out=%2d\n", _inChannel, _outChannel);
_debug(" : sample rate=%5d, format=%s\n", _sampleRate, SFLPortaudioFormatString);
_debug(" : frame per buffer=%d\n", portaudioFramePerBuffer);
}
try {
// Set up the parameters required to open a (Callback)Stream:
portaudio::DirectionSpecificStreamParameters
outParams(portaudio::System::instance().deviceByIndex(indexOut),
_outChannel, portaudio::INT16, true,
portaudio::System::instance().deviceByIndex(indexOut).defaultLowOutputLatency(),
NULL);
portaudio::DirectionSpecificStreamParameters
inParams(portaudio::System::instance().deviceByIndex(indexIn),
_inChannel, portaudio::INT16, true,
// Set up the parameters required to open a (Callback)Stream:
portaudio::DirectionSpecificStreamParameters
inParams(portaudio::System::instance().deviceByIndex(indexIn),
_inChannel, SFLPortaudioFormat, true,
portaudio::System::instance().deviceByIndex(indexIn).defaultLowInputLatency(),
NULL);
#ifdef USE_SAMPLERATE
_sampleRate = sampleRate;
#else
_sampleRate = 8000;
#endif
// we could put paFramesPerBufferUnspecified instead of FRAME_PER_BUFFER to be variable
portaudio::StreamParameters const params(inParams, outParams,
_sampleRate, FRAME_PER_BUFFER /*paFramesPerBufferUnspecified*/, paNoFlag /*paPrimeOutputBuffersUsingStreamCallback | paNeverDropInput*/);
// Create (and open) a new Stream, using the AudioLayer::audioCallback
ost::MutexLock guard(_mutex);
_stream = new portaudio::MemFunCallbackStream<AudioLayer>(params,
*this,
&AudioLayer::audioCallback);
} catch(...) {
throw;
portaudio::DirectionSpecificStreamParameters outParams(
portaudio::System::instance().deviceByIndex(indexOut),
_outChannel, SFLPortaudioFormat, true,
portaudio::System::instance().deviceByIndex(indexOut).defaultLowOutputLatency(),
NULL);
// like audacity
// DON'T USE paFramesPerBufferUnspecified, it's 32, instead of 160 for FRAME_PER_BUFFER
// DON'T USE paDitherOff or paClipOff,
// FRAME_PER_BUFFER | paFramesPerBufferUnspecified
// paNoFlag | paClipOff || paDitherOff | paPrimeOutputBuffersUsingStreamCallback | paNeverDropInput
portaudio::StreamParameters const params(
inParams,
outParams,
_sampleRate, portaudioFramePerBuffer, paClipOff);
// Create (and open) a new Stream, using the AudioLayer::audioCallback
ost::MutexLock guard(_mutex);
#ifdef SFL_TEST
_stream = new portaudio::MemFunCallbackStream<AudioLayer>(params, *this, &AudioLayer::miniAudioCallback);
#else
_stream = new portaudio::MemFunCallbackStream<AudioLayer>(params,*this, &AudioLayer::audioCallback);
#endif
}
catch (const portaudio::PaException &e) {
setErrorMessage(e.paErrorText());
_debug("Portaudio openDevice error: %s\n", e.paErrorText());
}
catch (const portaudio::PaCppException &e) {
setErrorMessage(e.what());
_debug("Portaudio openDevice error: %s\n", e.what());
} // std::runtime_error &e (e.what())
catch (...) {
setErrorMessage("Unknown type error in portaudio openDevice");
_debug("Portaudio openDevice: unknown error\n");
}
}
int
AudioLayer::getDeviceCount()
{
return portaudio::System::instance().deviceCount();
}
AudioDevice*
AudioLayer::getAudioDeviceInfo(int index, int ioDeviceMask)
{
try {
portaudio::System& sys = portaudio::System::instance();
portaudio::Device& device = sys.deviceByIndex(index);
int deviceIsSupported = false;
if (ioDeviceMask == InputDevice && !device.isOutputOnlyDevice()) {
deviceIsSupported = true;
} else if (ioDeviceMask == OutputDevice && !device.isInputOnlyDevice()) {
deviceIsSupported = true;
} else if (device.isFullDuplexDevice()) {
deviceIsSupported = true;
}
if (deviceIsSupported) {
AudioDevice* audiodevice = new AudioDevice(index, device.hostApi().name(), device.name());
if (audiodevice) {
audiodevice->setRate(device.defaultSampleRate());
}
return audiodevice;
}
} catch (...) {
return 0;
}
return 0;
}
void
AudioLayer::startStream(void)
{
......@@ -115,7 +230,7 @@ AudioLayer::startStream(void)
_debug("- AL Action: Starting sound stream\n");
_stream->start();
} else {
_debug ("* AL Info: Stream doesn't exist or is already active\n");
_debug ("* AL Info: Stream doesn't exist or is already active\n");
}
} catch (const portaudio::PaException &e) {
_debugException("! AL: Portaudio error: error on starting audiolayer stream");
......@@ -129,6 +244,7 @@ AudioLayer::startStream(void)
void
AudioLayer::stopStream(void)
{
_debug("- AL Action: Stopping sound stream\n");
try {
ost::MutexLock guard(_mutex);
if (_stream && !_stream->isStopped()) {
......@@ -254,76 +370,80 @@ AudioLayer::audioCallback (const void *inputBuffer, void *outputBuffer,
(void) timeInfo;
(void) statusFlags;
int16 *in = (int16 *) inputBuffer;
int16 *out = (int16 *) outputBuffer;
SFLDataFormat *in = (SFLDataFormat *) inputBuffer;
SFLDataFormat *out = (SFLDataFormat *) outputBuffer;
int toGet;
int toPut;
int urgentAvail; // number of int16 right and int16 left
int normalAvail; // number of int16 right and int16 left
int urgentAvail; // number of data right and data left
int normalAvail; // number of data right and data left
int micAvailPut;
unsigned short spkrVolume = Manager::instance().getSpkrVolume();
unsigned short micVolume = Manager::instance().getMicVolume();
unsigned short spkrVolume = _manager->getSpkrVolume();
unsigned short micVolume = _manager->getMicVolume();
// AvailForGet tell the number of chars inside the buffer
// framePerBuffer are the number of int16 for one channel (left)
// framePerBuffer are the number of data for one channel (left)
urgentAvail = _urgentRingBuffer.AvailForGet();
if (urgentAvail > 0) {
// Urgent data (dtmf, incoming call signal) come first.
toGet = (urgentAvail < (int)(framesPerBuffer * sizeof(int16) * _outChannel)) ? urgentAvail : framesPerBuffer * sizeof(int16) * _outChannel;
toGet = (urgentAvail < (int)(framesPerBuffer * sizeof(SFLDataFormat))) ? urgentAvail : framesPerBuffer * sizeof(SFLDataFormat);
_urgentRingBuffer.Get(out, toGet, spkrVolume);
// Consume the regular one as well (same amount of bytes)
_mainSndRingBuffer.Discard(toGet);
} else {
AudioLoop* tone = Manager::instance().getTelephoneTone();
} else {
AudioLoop* tone = _manager->getTelephoneTone();
if ( tone != 0) {
tone->getNext(out, framesPerBuffer*_outChannel, spkrVolume);
} else if ( (tone=Manager::instance().getTelephoneFile()) != 0 ) {
tone->getNext(out, framesPerBuffer*_outChannel, spkrVolume);
tone->getNext(out, framesPerBuffer, spkrVolume);
} else if ( (tone=_manager->getTelephoneFile()) != 0 ) {
tone->getNext(out, framesPerBuffer, spkrVolume);
} else {
// If nothing urgent, play the regular sound samples
normalAvail = _mainSndRingBuffer.AvailForGet();
toGet = (normalAvail < (int)(framesPerBuffer * sizeof(int16) * _outChannel)) ? normalAvail : framesPerBuffer * sizeof(int16) * (int)_outChannel;
toGet = (normalAvail < (int)(framesPerBuffer * sizeof(SFLDataFormat))) ? normalAvail : framesPerBuffer * sizeof(SFLDataFormat);
if (toGet) {
_mainSndRingBuffer.Get(out, toGet, spkrVolume);
} else {
bzero(out, framesPerBuffer * sizeof(int16) * _outChannel);
bzero(out, framesPerBuffer * sizeof(SFLDataFormat));
}
}
}
// Additionally handle the mic's audio stream
micAvailPut = _micRingBuffer.AvailForPut();
toPut = (micAvailPut <= (int)(framesPerBuffer * sizeof(int16) * _inChannel)) ? micAvailPut : framesPerBuffer * sizeof(int16) * _inChannel;
_micRingBuffer.Put(in, toPut, micVolume );
toPut = (micAvailPut <= (int)(framesPerBuffer * sizeof(SFLDataFormat))) ? micAvailPut : framesPerBuffer * sizeof(SFLDataFormat);
_micRingBuffer.Put(in, toPut, micVolume);
return paContinue;
}
unsigned int
AudioLayer::convert(int16* fromBuffer, int fromChannel, unsigned int fromSize, int16** toBuffer, int toChannel) {
if (fromChannel == toChannel ) { // 1=>1 or 2=>2
// src, dest, size in bytes
*toBuffer = fromBuffer;
//bcopy(fromBuffer, *toBuffer, fromSize*sizeof(int16));
return fromSize;
} else if (fromChannel > toChannel ) { // 2 => 1
unsigned int toSize = fromSize>>1; // divise by 2
for(unsigned int m=0, s=0; m<toSize; m++) {
s = m<<1;
(*toBuffer)[m] = (int16)(0.5f*(fromBuffer[s] + fromBuffer[s+1]));
}
return toSize;
} else { // (fromChannel > toChannel ) { // 1 => 2
for(unsigned int m=0, s=0; m<fromSize; m++) {
s = m<<1;
(*toBuffer)[s] = (*toBuffer)[s+1] = fromBuffer[m];
int
AudioLayer::miniAudioCallback (const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo *timeInfo,
PaStreamCallbackFlags statusFlags) {