tahoma2d/toonz/sources/common/tsound/tsound_l.cpp
Shinya Kitaoka d1f6c4e95b REFACTORING: Add final specifiers (#537)
* add final specifiers

* apply clang-format

* fix for macOS
2016-06-29 15:17:12 +09:00

2218 lines
77 KiB
C++

#include "tfilepath.h"
#include "tsound.h"
#include "tsound_io.h"
#include "tsop.h"
#include "tthread.h"
#include "texception.h"
#include "tsystem.h"
#include <iostream>
#include <linux/soundcard.h>
#include <set>
#include <sys/time.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
// using namespace std;
// forward declaration
namespace {
int openMixer();
int getCurrentRecordSource(int mixer);
bool writeVolume(int volume, int mixer, int indexDev);
bool selectInputDevice(TSoundInputDevice::Source dev);
string parseError(int error);
}
// bisogna interagire con /dev/dsp per il settaggio delle
// caratteristiche della traccia tipo bit, rate, canali e
// per effettuare lettura/scrittura ossia record/play
//
// mentre bisogna interagire con /dev/mixer per modificare
// i valori del volume e per selezionare il dispositivo da
// cui registrare e ascoltare
class SmartWatch {
struct timeval m_start_tv;
TINT32 m_totalus;
bool m_stopped;
public:
SmartWatch() : m_totalus(0), m_stopped(true) { timerclear(&m_start_tv); }
void start() {
m_stopped = false;
gettimeofday(&m_start_tv, 0);
}
void stop() {
m_stopped = true;
struct timeval tv;
gettimeofday(&tv, 0);
m_totalus = (tv.tv_sec - m_start_tv.tv_sec) * 1000000 +
(tv.tv_usec - m_start_tv.tv_usec);
}
double getTotalTime() {
if (!m_stopped) // questa e' una porcata!
{
stop();
m_stopped = false;
}
return m_totalus / 1000.;
}
void addDelay(double ms) { m_start_tv.tv_usec += (long)(ms * 1000.); }
};
//======================================================================
//======================================================================
// CLASSI PER IL PLAYBACK
//======================================================================
//======================================================================
class TSoundOutputDeviceImp {
private:
static int m_count;
public:
int m_dev;
bool m_stopped;
bool m_isPlaying;
bool m_looped;
TSoundTrackFormat m_currentFormat;
std::set<int> m_supportedRate;
static std::multimap<TUINT32, TSoundTrackFormat> m_supportFormats;
typedef pair<TSoundTrackP, bool> WaitPair;
vector<WaitPair> m_waitingTracks;
std::set<TSoundOutputDeviceListener *> m_listeners;
TThread::Executor m_executor;
TThread::Mutex m_mutex;
TSoundOutputDeviceImp()
: m_dev(-1)
, m_stopped(false)
, m_isPlaying(false)
, m_looped(false)
, m_supportedRate() {
/*
if (m_count != 0)
throw TException("unable to create second instance of TSoundOutputDeviceImp");
*/
++m_count;
checkSupportedFormat();
};
~TSoundOutputDeviceImp() { --m_count; };
bool doOpenDevice();
bool doCloseDevice();
bool verifyRate();
void insertAllRate();
void checkSupportedFormat();
bool isSupportFormat(const TSoundTrackFormat &fmt);
void setFormat(const TSoundTrackFormat &fmt);
};
int TSoundOutputDeviceImp::m_count = 0;
std::multimap<TUINT32, TSoundTrackFormat>
TSoundOutputDeviceImp::m_supportFormats;
//----------------------------------------------------------------------------
bool TSoundOutputDeviceImp::doOpenDevice() {
if (m_dev >= 0) return true;
TThread::ScopedLock sl(m_mutex);
m_dev = open("/dev/dsp", O_WRONLY, 0);
if (m_dev < 0) {
string errMsg = strerror(errno);
throw TSoundDeviceException(
TSoundDeviceException::UnableOpenDevice, errMsg + " /dev/dsp"
/*"Unable to open device /dev/dsp; check permissions"*/);
}
// le chiamate a questa ioctl sono state commentate perche' pag 36 doc OSS
//"this ioctl stop the device immadiately and returns it to a state where
// it
// can accept new parameters. It Should not be called after opening the device
// as it may cause unwanted side effect in his situation. require to abort
// play
// or secord.Generally recommended to open and close device after using the
// RESET"
// ioctl(m_dev, SNDCTL_DSP_RESET,0);
// N.B. e' bene che la dimensione sia piccola cosi' l'attesa, in seguito
// alla richiesta di stop,
// e' minore in questo caso vogliamo 32 frammenti ognuno di 256 byte
// Se non chiamata questa ioctl il device la calcola per conto suo
// ma alcune volte la dimensione dei frammenti potrebbe essere eccessiva e
// creare dei click e dei silenzi in attesi ad esempio nel playback dentro
// zcomp, quindi e' meglio settarla. 32 rappresenta il numero di frammenti
// di solito e' documentato che 2 siano sufficienti ma potrebbero essere pochi
// soprattutto se si interagisce con altre console
// int fraginfo = ( 32<<16)|8;
int fraginfo = 0xffffffff;
if (ioctl(m_dev, SNDCTL_DSP_SETFRAGMENT, &fraginfo) == -1)
perror("SETFRAGMENT");
// if(fraginfo != ((32<<16)|8))
// std::cout << std::hex << fraginfo<<std::endl;
// exit(0);
audio_buf_info info;
if (ioctl(m_dev, SNDCTL_DSP_GETOSPACE, &info) == -1) perror("GETOSPACE");
// std::cout << info.fragments << " frammenti " << " da " << info.fragsize <<
// " = " << info.fragsize*info.fragments << " bytes" <<std::endl;
return true;
}
//----------------------------------------------------------------------------
bool TSoundOutputDeviceImp::doCloseDevice() {
if (m_dev < 0) return true;
TThread::ScopedLock sl(m_mutex);
ioctl(m_dev, SNDCTL_DSP_POST, 0);
ioctl(m_dev, SNDCTL_DSP_RESET, 0);
int tmpDev = m_dev;
bool not_closed = (close(m_dev) < 0);
if (not_closed) {
while (true) {
m_dev = tmpDev;
perror("non chiude il device :");
not_closed = (close(m_dev) < 0);
if (!not_closed) break;
}
// return false;
}
ioctl(m_dev, SNDCTL_DSP_RESET, 0);
m_dev = -1;
return true;
}
//----------------------------------------------------------------------------
void TSoundOutputDeviceImp::insertAllRate() {
m_supportedRate.insert(8000);
m_supportedRate.insert(11025);
m_supportedRate.insert(16000);
m_supportedRate.insert(22050);
m_supportedRate.insert(32000);
m_supportedRate.insert(44100);
m_supportedRate.insert(48000);
}
//----------------------------------------------------------------------------
bool TSoundOutputDeviceImp::verifyRate() {
std::set<int>::iterator it;
for (it = m_supportedRate.begin(); it != m_supportedRate.end(); ++it) {
int sampleRate = *it;
if (ioctl(m_dev, SNDCTL_DSP_SPEED, &sampleRate) == -1)
throw TSoundDeviceException(
TSoundDeviceException::UnablePrepare,
"Failed setting the specified sample rate aaaa");
if (sampleRate != *it) m_supportedRate.erase(*it);
}
if (m_supportedRate.end() == m_supportedRate.begin()) return false;
return true;
}
//------------------------------------------------------------------------------
void TSoundOutputDeviceImp::checkSupportedFormat() {
if (!m_supportFormats.empty()) return;
int test_formats[] = {
AFMT_U8, AFMT_S8, AFMT_S16_LE, AFMT_S16_BE,
/* // servono per supportare traccie a 24 e 32 bit ma non compaiono in
// nessun file linux/soundcard.h in distribuzione sulle macchine che abbiamo
// il che fa pensare che non sono supportati ancora
AFMT_S32_LE,
AFMT_S32_BE,
*/
0};
int test_channels[] = {1, 2, 0};
TUINT32 test_sample_rates[] = {8000, 11025, 16000, 22050,
32000, 44100, 48000, 0};
// Open the device nonblocking, so the open call returns immediately.
if (m_dev)
if ((m_dev = open("/dev/dsp", O_WRONLY | O_NONBLOCK)) == -1) {
/*
m_dev = -1;
string errMsg = strerror(errno);
throw TSoundDeviceException(
TSoundDeviceException::UnableOpenDevice, errMsg + " /dev/dsp\n:
impossible check supported formats");
*/
return;
}
int mask;
// Querying hardware supported formats.
if (ioctl(m_dev, SNDCTL_DSP_GETFMTS, &mask) == -1) {
/*
throw TSoundDeviceException(
TSoundDeviceException::UnsupportedFormat,
"Getting supported formats failed.");
*/
return;
} else {
for (int i = 0; test_formats[i] != 0; i++) {
// Test all the formats in test_formats which are supported by the
// hardware.
if (mask & test_formats[i]) {
// Test the format only if it is supported by the hardware.
// Note: We also could test formats that are not supported by the
// hardware.
// In some cases there exist OSS software converter, so that some
// formats
// work but are not reported by the above SNDCTL_DSP_GETFMTS.
int fmt = test_formats[i];
// Try to set the format...
if (ioctl(m_dev, SNDCTL_DSP_SETFMT, &fmt) == -1)
continue; // gli altri formati potrebbero essere supportati
else {
// and always check the variable after doing an ioctl!
if (fmt == test_formats[i]) {
// Test the supported channel numbers for this format.
// Note: If you need a channel that is not tested here, simply add
// it to
// the definition of the array test_channels in this file.
for (int j = 0; test_channels[j] != 0; j++) {
int test_channel = test_channels[j];
// Try to set the channel number.
if (ioctl(m_dev, SNDCTL_DSP_CHANNELS, &test_channel) == -1)
continue; // altri canali potrebbero essere supportati
else {
if (test_channel == test_channels[j]) {
// Last step: Test the supported sample rates for the current
// channel number
// and format.
// Note: If you need a sample rate that is not tested here,
// simply add it to
// the definition of the array test_sample_rates in this
// file.
for (int k = 0; test_sample_rates[k] != 0; k++) {
TUINT32 test_rate = test_sample_rates[k];
if (ioctl(m_dev, SNDCTL_DSP_SPEED, &test_rate) == -1)
continue; // altri rates ppotrebbero essere supportati
else {
bool sign = true;
int bits;
if (fmt == AFMT_U8 || fmt == AFMT_S8) {
bits = 8;
if (fmt == AFMT_U8) sign = false;
} else if (fmt == AFMT_S16_LE || fmt == AFMT_S16_BE)
bits = 16;
/*// vedi commento alla variabile test_formats
else if(fmt == AFMT_S32_LE || fmt == AFMT_S32_BE)
bits = 24;
*/
// Add it to the format in the input property.
// std::cout << test_rate << " " <<bits<<"
// "<<test_channel<<std::endl;
m_supportFormats.insert(std::make_pair(
test_rate, TSoundTrackFormat(test_rate, bits,
test_channel, sign)));
}
}
} // loop over the test_sample_rates
} // channel set correctly ?
} // ioctl for sample rate worked ?
} // loop over the test_channels
} // channel set correctly ?
} // ioctl for channel worked ?
// After testing all configurations for one format, the device is closed
// and reopened.
// This is necessary to configure it to another format in a secure way.
if (close(m_dev) == -1) {
/*
throw TSoundDeviceException(
TSoundDeviceException::UnableCloseDevice,
"Problem to close the output device checking formats");
*/
continue;
} else if ((m_dev = open("/dev/dsp", O_WRONLY | O_NONBLOCK)) == -1) {
/*
m_dev = -1;
string errMsg = strerror(errno);
throw TSoundDeviceException(
TSoundDeviceException::UnableOpenDevice, errMsg + " /dev/dsp\n:
impossible check supported formats");
*/
return;
}
} // format set correctly ?
} // loop over the test_formats
// Close the device to make it forget the last format configurations.
if (close(m_dev) == -1) {
/*
throw TSoundDeviceException(
TSoundDeviceException::UnableCloseDevice,
"Problem to close the output device checking formats");
*/
return;
} else
m_dev = -1;
// std::cout << "FINITO " << m_supportFormats.size()<< std::endl;
}
//------------------------------------------------------------------------------
bool TSoundOutputDeviceImp::isSupportFormat(const TSoundTrackFormat &fmt) {
try {
if (m_supportFormats.empty()) checkSupportedFormat();
} catch (TSoundDeviceException &e) {
return false;
}
std::multimap<TUINT32, TSoundTrackFormat>::iterator it;
pair<std::multimap<TUINT32, TSoundTrackFormat>::iterator,
std::multimap<TUINT32, TSoundTrackFormat>::iterator>
findRange;
findRange = m_supportFormats.equal_range(fmt.m_sampleRate);
it = findRange.first;
for (; it != findRange.second; ++it) {
assert(it->first == fmt.m_sampleRate);
if (it->second == fmt) {
return true;
}
}
return false;
}
//------------------------------------------------------------------------------
void TSoundOutputDeviceImp::setFormat(const TSoundTrackFormat &fmt) {
int bps, ch, status;
TUINT32 sampleRate;
ch = fmt.m_channelCount;
sampleRate = fmt.m_sampleRate;
if (m_dev == -1)
if (!doOpenDevice()) return;
if (fmt.m_bitPerSample == 8) {
if (fmt.m_signedSample)
bps = AFMT_S8;
else
bps = AFMT_U8;
} else if (fmt.m_bitPerSample == 16) {
bps = AFMT_S16_NE;
}
int bitPerSample = bps;
status = ioctl(m_dev, SNDCTL_DSP_SETFMT, &bps);
if (status == -1) {
throw TSoundDeviceException(TSoundDeviceException::UnablePrepare,
"Failed setting the specified number of bits");
}
status = ioctl(m_dev, SNDCTL_DSP_CHANNELS, &ch);
if (status == -1)
throw TSoundDeviceException(
TSoundDeviceException::UnablePrepare,
"Failed setting the specified number of channel");
if (ioctl(m_dev, SNDCTL_DSP_SPEED, &sampleRate) == -1)
throw TSoundDeviceException(TSoundDeviceException::UnablePrepare,
"Failed setting the specified sample rate");
if (ch != fmt.m_channelCount || bps != bitPerSample ||
sampleRate != fmt.m_sampleRate) {
doCloseDevice();
m_currentFormat = TSoundTrackFormat();
return;
}
m_currentFormat = fmt;
}
//==============================================================================
class TPlayTask : public TThread::Runnable {
SmartWatch *m_stopWatch;
public:
TSoundOutputDeviceImp *m_devImp;
TSoundTrackP m_sndtrack;
static int m_skipBytes;
TPlayTask(TSoundOutputDeviceImp *devImp, const TSoundTrackP &st);
~TPlayTask() { delete m_stopWatch; }
void run();
void run2();
};
//-------------------------------------------------------------------------------
TPlayTask::TPlayTask(TSoundOutputDeviceImp *devImp, const TSoundTrackP &st)
: Runnable()
, m_stopWatch(new SmartWatch)
, m_devImp(devImp)
, m_sndtrack(st) {
if (st->getFormat() != m_devImp->m_currentFormat)
if (m_devImp->doCloseDevice()) m_devImp->setFormat(st->getFormat());
m_stopWatch->start();
};
//-------------------------------------------------------------------------------
void TPlayTask::run() {
int bytesLeft = m_sndtrack->getSampleCount() * m_sndtrack->getSampleSize();
char *buf = (char *)m_sndtrack->getRawData();
int done = 0;
int written = 0;
TINT32 sampleSize = (TINT32)m_sndtrack->getSampleSize();
const double msToBytes = sampleSize * m_sndtrack->getSampleRate() / 1000.;
TThread::milestone();
double startupDelay = m_stopWatch->getTotalTime();
// std::cout << "ritardo iniziale " << startupDelay << std::endl;
int miss = 0;
m_stopWatch->start(); // e' meglio ignorare il ritardo iniziale
if (done > 0) {
m_stopWatch->addDelay(((done / sampleSize) * 1000) /
double(m_sndtrack->getSampleRate()));
}
int auxbuffersize = 0;
int preWrittenBytes = 0;
int bytesToSkipNext;
TSoundTrackP src = TSoundTrack::create(m_sndtrack->getFormat(), 1);
TSoundTrackP dst = src;
TSoundTrackP newSrc = src;
try {
do // gia' tracce accodate
{
bool changeSnd = false;
do // c'e' il flag loop settato
{
while ((bytesLeft > 0)) {
TThread::milestone();
changeSnd = false;
audio_buf_info info;
TINT32 bytesToWrite = 0;
TINT32 bytesToWriteNext = 0;
double samplesDone = done / (double)sampleSize;
double trackTime =
(samplesDone * 1000.) / m_sndtrack->getSampleRate();
double curTime = m_stopWatch->getTotalTime();
double delta = curTime - trackTime;
/*
delta
== 0 sync
< 0 audio piu' veloce del tempo
di playback --> simuliamo un ritardo con un continue;
> 0 audio piu' lento del playback ->
skip una porzione di audio
*/
const double minDelay = -10;
const double maxDelay = 0;
// if (delta>maxDelay)
// std::cout << "buffer underrun:" << delta << std::endl;
// std::cout << "buffer " <<
// (delta<minDelay?"overrun":delta>maxDelay?"underrun":"sync") << " "
// << delta<< std::endl;
if (delta < minDelay) // overrun
{
// std::cout << "out of sync -> audio troppo veloce" << std::endl;
continue;
}
if (ioctl(m_devImp->m_dev, SNDCTL_DSP_GETOSPACE, &info) == -1) {
miss++;
break;
}
int fragmentsFree_bytes = info.fragsize * info.fragments;
if (fragmentsFree_bytes == 0) {
// std::cout << "no bytes left on device" << std::endl;
continue;
}
int bytesToSkip = 0;
bytesToSkipNext = 0;
if (delta > maxDelay) // underrun
{
// std::cout << "out of sync -> audio troppo lento"<<std::endl;
// skip di una porzione... delta in ms
bytesToSkip =
tceil(delta * msToBytes); // numero di bytes da skippare
// lo arrotondo al sample size
bytesToSkip += sampleSize - (bytesToSkip % sampleSize);
// std::cout << "bytes skippati "<< bytesToSkip << std::endl;
} else { // sto fra minDelay e maxDelay
bytesToSkip = 0;
}
bytesToSkipNext = bytesToSkip;
bytesToSkip = tmin(bytesLeft, bytesToSkip);
bytesToSkipNext -= bytesToSkip; // se !=0 => la corrente traccia non
// basta per avere il sync
bytesLeft -= bytesToSkip;
done += bytesToSkip;
bytesToWrite = tmin(bytesLeft, fragmentsFree_bytes);
bytesToWriteNext = fragmentsFree_bytes - bytesToWrite;
assert(bytesToWrite >= 0);
assert(bytesToWriteNext >= 0);
if (bytesToWrite % info.fragsize !=
0) { // cerco di gestire il write di un frammento non intero
auxbuffersize =
((bytesToWrite / info.fragsize) + 1) * info.fragsize;
} else
auxbuffersize = 0;
//--------------write
if (bytesToSkipNext == 0) // la corrente traccia basta per lo skip
{
std::cout << " QUI 0 " << std::endl;
dst = m_sndtrack->extract(done / sampleSize,
(done + bytesToWrite) / sampleSize);
if (bytesToSkip != 0) {
// costruisco traccia su cui fare il crossfade
// utilizzo il contenuto della traccia di crossfade
dst = TSop::crossFade(0.2, src, dst);
}
char *auxbuf = new char[fragmentsFree_bytes];
memcpy(auxbuf, (char *)dst->getRawData(), bytesToWrite);
if (bytesToWriteNext != 0) {
int offset = bytesToWrite;
if (m_devImp->m_looped) {
offset += bytesToWriteNext;
preWrittenBytes = bytesToWriteNext;
memcpy(auxbuf + offset, buf, preWrittenBytes);
newSrc = m_sndtrack->extract(preWrittenBytes / sampleSize,
preWrittenBytes / sampleSize);
std::cout << " QUI 1" << std::endl;
} else {
while (!m_devImp->m_waitingTracks.empty()) {
TSoundTrackP st = m_devImp->m_waitingTracks[0].first;
int count = st->getSampleCount() * sampleSize;
if (bytesToWriteNext >= count) {
char *buffer = (char *)st->getRawData();
memcpy(auxbuf + offset, buffer, count);
bytesToWriteNext -= count;
offset += count;
std::cout << " QUI 2" << std::endl;
if (m_devImp->m_waitingTracks[0].second) {
m_devImp->m_looped = m_devImp->m_waitingTracks[0].second;
m_devImp->m_waitingTracks.erase(
m_devImp->m_waitingTracks.begin());
newSrc = m_sndtrack->extract(count / sampleSize,
count / sampleSize);
m_sndtrack = st;
preWrittenBytes = 0;
std::cout << " QUI 3" << std::endl;
break;
}
m_devImp->m_waitingTracks.erase(
m_devImp->m_waitingTracks.begin());
newSrc = m_sndtrack->extract(count / sampleSize,
count / sampleSize);
} else {
m_sndtrack = st;
m_devImp->m_looped = m_devImp->m_waitingTracks[0].second;
preWrittenBytes = bytesToWriteNext;
buf = (char *)m_sndtrack->getRawData();
memcpy(auxbuf + offset, buf, bytesToWriteNext);
m_devImp->m_waitingTracks.erase(
m_devImp->m_waitingTracks.begin());
newSrc =
m_sndtrack->extract((bytesToWriteNext) / sampleSize,
(bytesToWriteNext) / sampleSize);
std::cout << " QUI 4" << std::endl;
break;
}
} // end while
}
if (fragmentsFree_bytes > offset) {
std::cout << " QUI 5" << std::endl;
int val = m_sndtrack->isSampleSigned() ? 0 : 127;
memset(auxbuf + offset, val,
fragmentsFree_bytes - offset); // ci metto silenzio
newSrc = TSoundTrack::create(m_sndtrack->getFormat(), 1);
}
}
written = write(m_devImp->m_dev, auxbuf, fragmentsFree_bytes);
delete[] auxbuf;
} else // devo skippare anche parte di una delle seguenti
{
std::cout << " QUI 6a" << std::endl;
assert(bytesToWriteNext > 0);
assert(bytesToWriteNext == fragmentsFree_bytes - bytesToWrite);
assert(bytesToWrite == 0);
assert(bytesToSkip != 0);
char *auxbuf = new char[fragmentsFree_bytes];
// memcpy(auxbuf, buf+done, bytesToWrite);
// TSoundTrackP subCross =
// m_sndtrack->extract((done+bytesToWrite)/sampleSize,
// (done+bytesToWrite)/sampleSize);
// togli quelle da skippare
int backupSkipNext = bytesToSkipNext;
while (!m_devImp->m_waitingTracks.empty()) {
TSoundTrackP st = m_devImp->m_waitingTracks[0].first;
int count = st->getSampleCount() * sampleSize;
if (bytesToSkipNext >= count) {
std::cout << " QUI 6b" << std::endl;
m_devImp->m_waitingTracks.erase(
m_devImp->m_waitingTracks.begin());
bytesToSkipNext -= count;
} else {
std::cout << " QUI 7" << std::endl;
m_devImp->m_waitingTracks.erase(
m_devImp->m_waitingTracks.begin());
m_devImp->m_looped = m_devImp->m_waitingTracks[0].second;
m_sndtrack = st;
buf = (char *)st->getRawData();
break;
}
}
// scrivi byteWriteNext fai crossfade e cerca quella successiva
// con cui riempire
TINT32 displacement =
0; // deve essere in munero di campioni non bytes
dst = TSoundTrack::create(
m_sndtrack->getFormat(),
(fragmentsFree_bytes - bytesToWrite) / sampleSize);
int count = m_sndtrack->getSampleCount() * sampleSize;
if (count >= bytesToSkipNext + bytesToWriteNext) // la traccia
// trovata e' suff
// sia per
// skippare che
// per scrivere
{
preWrittenBytes = bytesToSkipNext + bytesToWriteNext;
dst = m_sndtrack->extract(bytesToSkipNext / sampleSize,
preWrittenBytes / sampleSize);
newSrc = m_sndtrack->extract(preWrittenBytes / sampleSize,
preWrittenBytes / sampleSize);
} else // non e' suff per scrivere
{
dst->copy(m_sndtrack->extract(bytesToSkipNext / sampleSize,
m_sndtrack->getSampleCount() - 1),
0);
displacement =
m_sndtrack->getSampleCount() - bytesToSkipNext / sampleSize;
bytesToWriteNext -= displacement * sampleSize;
while (!m_devImp->m_waitingTracks.empty()) {
TSoundTrackP st = m_devImp->m_waitingTracks[0].first;
int count = st->getSampleCount() * sampleSize;
if (bytesToWriteNext >= count) {
std::cout << " QUI 8" << std::endl;
dst->copy(st, displacement);
bytesToWriteNext -= count;
displacement += count;
if (m_devImp->m_waitingTracks[0].second) {
std::cout << " QUI 9" << std::endl;
m_devImp->m_looped = m_devImp->m_waitingTracks[0].second;
m_devImp->m_waitingTracks.erase(
m_devImp->m_waitingTracks.begin());
newSrc = m_sndtrack->extract(count / sampleSize,
count / sampleSize);
m_sndtrack = st;
break;
}
m_devImp->m_waitingTracks.erase(
m_devImp->m_waitingTracks.begin());
newSrc = m_sndtrack->extract(count / sampleSize,
count / sampleSize);
} else {
std::cout << " QUI 10" << std::endl;
dst->copy(st->extract(0L, bytesToWriteNext / sampleSize),
displacement);
m_sndtrack = st;
m_devImp->m_looped = m_devImp->m_waitingTracks[0].second;
preWrittenBytes = bytesToWriteNext;
done = preWrittenBytes;
bytesLeft = m_sndtrack->getSampleCount() * sampleSize - done;
buf = (char *)m_sndtrack->getRawData();
m_devImp->m_waitingTracks.erase(
m_devImp->m_waitingTracks.begin());
newSrc = m_sndtrack->extract(preWrittenBytes / sampleSize,
preWrittenBytes / sampleSize);
break;
}
}
bytesToSkipNext = backupSkipNext;
}
TSoundTrackP st = TSop::crossFade(0.2, src, dst);
memcpy(auxbuf + bytesToWrite, (char *)st->getRawData(),
fragmentsFree_bytes - bytesToWrite);
// devo ricercare quella giusta che non deve essere skippata
// ma sostitutita come traccia corrente
// devo fare un cross fade
written = write(m_devImp->m_dev, auxbuf, fragmentsFree_bytes);
delete[] auxbuf;
}
//----------- end write
src = newSrc;
if (written == -1) break;
std::cout << written << " " << (bytesToWrite + preWrittenBytes)
<< std::endl;
if (written != bytesToWrite + preWrittenBytes) break;
std::cout << " update done 2" << std::endl;
bytesLeft -= written;
done += written;
} // chiudo il while((bytesLeft > 0))
std::cout << " QUI 11" << std::endl;
done = preWrittenBytes + bytesToSkipNext;
written = 0;
bytesLeft = m_sndtrack->getSampleCount() * sampleSize - done;
m_stopWatch->start();
if (done > 0) {
m_stopWatch->addDelay(((done / m_sndtrack->getSampleSize()) * 1000) /
double(m_sndtrack->getSampleRate()));
}
preWrittenBytes = 0;
} while (m_devImp->m_looped || changeSnd);
if (m_devImp->m_waitingTracks.empty()) break;
m_sndtrack = m_devImp->m_waitingTracks[0].first;
m_devImp->m_looped = m_devImp->m_waitingTracks[0].second;
m_devImp->m_waitingTracks.erase(m_devImp->m_waitingTracks.begin());
bytesLeft = m_sndtrack->getSampleCount() * m_sndtrack->getSampleSize();
buf = (char *)m_sndtrack->getRawData();
done = 0;
written = 0;
m_stopWatch->start(); // ignoro il ritardo iniziale
if (done > 0) {
m_stopWatch->addDelay(((done / m_sndtrack->getSampleSize()) * 1000) /
double(m_sndtrack->getSampleRate()));
}
} while (true); // ci sono gia' tracce accodate
if (!m_devImp->m_waitingTracks.empty()) {
m_devImp->m_looped = m_devImp->m_waitingTracks[0].second;
m_devImp->m_executor.addTask(
new TPlayTask(m_devImp, m_devImp->m_waitingTracks[0].first));
m_devImp->m_waitingTracks.erase(m_devImp->m_waitingTracks.begin());
// std::cout<<"OPS ..... erase 4"<<std::endl;
} else if (m_devImp->m_dev != -1) {
if (ioctl(m_devImp->m_dev, SNDCTL_DSP_SYNC) == -1) {
std::cout << "unable to sync! " << std::endl;
throw TException("unable to sync!");
}
m_devImp->m_isPlaying = false;
m_devImp->m_stopped = true;
m_devImp->m_looped = false;
// std::cout << "miss = " << miss << std::endl;
}
} catch (TThread::Interrupt &e) {
std::cout << "Play interrupted " << e.getMessage() << std::endl;
m_devImp->m_isPlaying = false;
m_devImp->m_stopped = true;
m_devImp->m_looped = false;
} catch (TException &e) {
std::cout << "esco dal play " << e.getMessage() << std::endl;
m_devImp->m_isPlaying = false;
m_devImp->m_stopped = true;
m_devImp->m_looped = false;
}
}
//-------------------------------------------------------------------------------
void TPlayTask::run2() {
int bytesLeft = m_sndtrack->getSampleCount() * m_sndtrack->getSampleSize();
char *buf = (char *)m_sndtrack->getRawData();
int done = 0;
int written = 0;
TINT32 sampleSize = (TINT32)m_sndtrack->getSampleSize();
const double msToBytes = sampleSize * m_sndtrack->getSampleRate() / 1000.;
TThread::milestone();
double startupDelay = m_stopWatch->getTotalTime();
// std::cout << "ritardo iniziale " << startupDelay << std::endl;
int miss = 0;
m_stopWatch->start(); // e' meglio ignorare il ritardo iniziale
if (done > 0) {
m_stopWatch->addDelay(((done / sampleSize) * 1000) /
double(m_sndtrack->getSampleRate()));
}
int auxbuffersize = 0;
int preWrittenBytes = 0;
TSoundTrackP src = TSoundTrack::create(m_sndtrack->getFormat(), 1);
TSoundTrackP dst = src;
try {
do // gia' tracce accodate
{
bool changeSnd = false;
do // c'e' il flag loop settato
{
while ((bytesLeft > 0)) {
changeSnd = false;
TThread::milestone();
audio_buf_info info;
TINT32 bytesToWrite;
double samplesDone = done / (double)sampleSize;
double trackTime =
(samplesDone * 1000.) / m_sndtrack->getSampleRate();
double curTime = m_stopWatch->getTotalTime();
double delta = curTime - trackTime;
/*
delta
== 0 sync
< 0 audio piu' veloce del tempo
di playback --> simuliamo un ritardo con un continue;
> 0 audio piu' lento del playback ->
skip una porzione di audio
*/
const double minDelay = -10;
const double maxDelay = 0;
// if (delta>maxDelay)
// std::cout << "buffer underrun:" << delta << std::endl;
// std::cout << "buffer " <<
// (delta<minDelay?"overrun":delta>maxDelay?"underrun":"sync") << " "
// << delta<< std::endl;
if (delta < minDelay) // overrun
{
// std::cout << "out of sync -> audio troppo veloce" << std::endl;
continue;
}
if (ioctl(m_devImp->m_dev, SNDCTL_DSP_GETOSPACE, &info) == -1) {
miss++;
break;
}
int fragmentsFree_bytes = info.fragsize * info.fragments;
if (fragmentsFree_bytes == 0) {
// std::cout << "no bytes left on device" << std::endl;
continue;
}
int bytesToSkip = 0;
int bigSkip = 0;
if (delta > maxDelay) // underrun
{
// std::cout << "out of sync -> audio troppo lento"<<std::endl;
// skip di una porzione... delta in ms
bytesToSkip =
tceil(delta * msToBytes); // numero di bytes da skippare
// lo arrotondo al sample size
bytesToSkip += sampleSize - (bytesToSkip % sampleSize);
// std::cout << "bytes skippati "<< bytesToSkip << std::endl;
bigSkip = bytesToSkip;
} else { // sto fra minDelay e maxDelay
bytesToSkip = 0;
}
bytesToSkip = tmin(bytesLeft, bytesToSkip);
bigSkip -= bytesToSkip;
bytesLeft -= bytesToSkip;
done += bytesToSkip;
bytesToWrite = tmin(bytesLeft, fragmentsFree_bytes);
if (bytesToWrite % info.fragsize !=
0) { // cerco di gestire il write di un frammento non intero
auxbuffersize =
((bytesToWrite / info.fragsize) + 1) * info.fragsize;
}
if (bigSkip)
while (!m_devImp->m_waitingTracks.empty()) {
TSoundTrackP st = m_devImp->m_waitingTracks[0].first;
int count = st->getSampleCount() * sampleSize;
if (bigSkip >= count) {
m_devImp->m_waitingTracks.erase(
m_devImp->m_waitingTracks.begin());
bigSkip -= count;
} else
break;
}
preWrittenBytes = 0;
if (auxbuffersize == 0) {
if (bytesToSkip != 0) {
// costruisco traccia su cui fare il crossfade
// utilizzo il contenuto della traccia di crossfade
dst = m_sndtrack->extract(done / sampleSize,
(done + bytesToWrite) / sampleSize);
TSoundTrackP st = TSop::crossFade(0.2, src, dst);
char *buffer = (char *)st->getRawData();
written = write(m_devImp->m_dev, buffer, bytesToWrite);
} else
written = write(m_devImp->m_dev, buf + done, bytesToWrite);
src = m_sndtrack->extract((done + bytesToWrite) / sampleSize,
(done + bytesToWrite) / sampleSize);
} else { // auxbuffersize != 0 sse il numero di bytes residui nella
// traccia e' inferiore alla dimensione del frammento
char *auxbuf = new char[auxbuffersize];
TSoundTrackP newSrc;
dst = TSoundTrack::create(m_sndtrack->getFormat(),
auxbuffersize / sampleSize);
memcpy(auxbuf, buf + done, bytesToWrite);
dst->copy(m_sndtrack->extract(done / sampleSize,
(done + bytesToWrite) / sampleSize),
0);
preWrittenBytes = auxbuffersize - bytesToWrite;
if (m_devImp->m_looped) {
memcpy(auxbuf + bytesToWrite, buf, preWrittenBytes);
dst->copy(m_sndtrack->extract(0, preWrittenBytes / sampleSize),
bytesToWrite / sampleSize);
newSrc = m_sndtrack->extract(preWrittenBytes / sampleSize,
preWrittenBytes / sampleSize);
} else {
newSrc = TSoundTrack::create(m_sndtrack->getFormat(), 1);
static int added = 0;
// se non c'e' alcuna altra traccia o e di diverso format
// riempo il frammento con del silenzio
if (m_devImp->m_waitingTracks.empty() ||
(m_sndtrack->getFormat() !=
m_devImp->m_waitingTracks[0].first->getFormat())) {
int val = m_sndtrack->isSampleSigned() ? 0 : 127;
memset(auxbuf + bytesToWrite, val,
preWrittenBytes); // ci metto silenzio
} else
while (true) // ci sono altre tracce accodate
{
TSoundTrackP st = m_devImp->m_waitingTracks[0].first;
int sampleBytes = st->getSampleCount() * st->getSampleSize();
if (sampleBytes >= preWrittenBytes - added) {
// La traccia ha abbastanza campioni per riempire il
// frammento
// quindi la sostituisco alla corrente del runnable e
// continuo
buf = (char *)st->getRawData();
memcpy(auxbuf + bytesToWrite, buf, preWrittenBytes - added);
m_sndtrack = st;
m_devImp->m_looped = m_devImp->m_waitingTracks[0].second;
m_devImp->m_waitingTracks.erase(
m_devImp->m_waitingTracks.begin());
changeSnd = true;
dst->copy(m_sndtrack->extract(
0, (preWrittenBytes - added) / sampleSize),
bytesToWrite / sampleSize + added);
newSrc = m_sndtrack->extract(
(preWrittenBytes - added) / sampleSize,
(preWrittenBytes - added) / sampleSize);
break;
} else { // occhio al loop
// La traccia successiva e' piu corta del frammento da
// riempire quindi
// ce la metto tutta e se non ha il flag di loop settato
// cerco di aggiungere
// i byte della successiva
memcpy(auxbuf + bytesToWrite, st->getRawData(),
sampleBytes);
dst->copy(st->extract(0, st->getSampleCount() - 1),
bytesToWrite / sampleSize);
added += st->getSampleCount();
if (m_devImp->m_waitingTracks[0]
.second) // e' quella che deve essere in loop
{
buf = (char *)st->getRawData();
m_sndtrack = st;
m_devImp->m_looped = m_devImp->m_waitingTracks[0].second;
preWrittenBytes = 0;
bytesLeft = 0;
m_devImp->m_waitingTracks.erase(
m_devImp->m_waitingTracks.begin());
changeSnd = true;
break;
}
// la elimino e vedo se esiste la successiva altrimenti
// metto campioni "zero"
m_devImp->m_waitingTracks.erase(
m_devImp->m_waitingTracks.begin());
if (!m_devImp->m_waitingTracks.empty()) {
st = m_devImp->m_waitingTracks[0].first;
std::cout
<< " Traccia con meno campioni cerco la successiva"
<< std::endl;
} else {
int val = m_sndtrack->isSampleSigned() ? 0 : 127;
memset(
auxbuf + bytesToWrite, val,
preWrittenBytes - sampleBytes); // ci metto silenzio
std::cout << "OPS ..... silence" << std::endl;
break;
}
}
} // end while(true)
}
// qui andrebbe fatto un cross-fade se c'erano da skippare campioni
// => bytesToSkip != 0
if (bytesToSkip != 0) {
TSoundTrackP st = TSop::crossFade(0.2, src, dst);
char *buffer = (char *)st->getRawData();
written = write(m_devImp->m_dev, buffer, bytesToWrite);
} else
written = write(m_devImp->m_dev, auxbuf, auxbuffersize);
src = newSrc;
auxbuffersize = 0;
delete[] auxbuf;
}
if (written == -1) break;
if (written != bytesToWrite + preWrittenBytes) break;
bytesLeft -= written;
done += written;
} // chiudo il while((bytesLeft > 0))
done = preWrittenBytes;
written = 0;
bytesLeft =
m_sndtrack->getSampleCount() * m_sndtrack->getSampleSize() - done;
m_stopWatch->start();
if (done > 0) {
m_stopWatch->addDelay(((done / m_sndtrack->getSampleSize()) * 1000) /
double(m_sndtrack->getSampleRate()));
}
} while (m_devImp->m_looped || changeSnd);
if (m_devImp->m_waitingTracks.empty()) {
// std::cout<<"OPS ..... non accodato"<<std::endl;
break;
}
// std::cout<<"OPS ..... accodato"<<std::endl;
m_sndtrack = m_devImp->m_waitingTracks[0].first;
m_devImp->m_looped = m_devImp->m_waitingTracks[0].second;
m_devImp->m_waitingTracks.erase(m_devImp->m_waitingTracks.begin());
bytesLeft = m_sndtrack->getSampleCount() * m_sndtrack->getSampleSize();
buf = (char *)m_sndtrack->getRawData();
done = 0;
written = 0;
m_stopWatch->start(); // ignoro il ritardo iniziale
if (done > 0) {
m_stopWatch->addDelay(((done / m_sndtrack->getSampleSize()) * 1000) /
double(m_sndtrack->getSampleRate()));
}
} while (true); // ci sono gia' tracce accodate
if (!m_devImp->m_waitingTracks.empty()) {
m_devImp->m_looped = m_devImp->m_waitingTracks[0].second;
m_devImp->m_executor.addTask(
new TPlayTask(m_devImp, m_devImp->m_waitingTracks[0].first));
m_devImp->m_waitingTracks.erase(m_devImp->m_waitingTracks.begin());
// std::cout<<"OPS ..... erase 4"<<std::endl;
} else if (m_devImp->m_dev != -1) {
if (ioctl(m_devImp->m_dev, SNDCTL_DSP_SYNC) == -1) {
std::cout << "unable to sync! " << std::endl;
throw TException("unable to sync!");
}
m_devImp->m_isPlaying = false;
m_devImp->m_stopped = true;
m_devImp->m_looped = false;
// std::cout << "miss = " << miss << std::endl;
}
} catch (TThread::Interrupt &e) {
std::cout << "Play interrupted " << e.getMessage() << std::endl;
m_devImp->m_isPlaying = false;
m_devImp->m_stopped = true;
m_devImp->m_looped = false;
} catch (TException &e) {
std::cout << "esco dal play " << e.getMessage() << std::endl;
m_devImp->m_isPlaying = false;
m_devImp->m_stopped = true;
m_devImp->m_looped = false;
}
}
//==============================================================================
TSoundOutputDevice::TSoundOutputDevice() : m_imp(new TSoundOutputDeviceImp) {
if (m_imp->doOpenDevice()) {
m_imp->insertAllRate();
try {
if (!m_imp->verifyRate())
throw TSoundDeviceException(TSoundDeviceException::UnablePrepare,
"No default samplerate are supported");
} catch (TSoundDeviceException &e) {
throw TSoundDeviceException(e.getType(), e.getMessage());
}
m_imp->doCloseDevice();
}
}
//------------------------------------------------------------------------------
TSoundOutputDevice::~TSoundOutputDevice() { close(); }
//------------------------------------------------------------------------------
bool TSoundOutputDevice::installed() {
bool ret = false;
int dev = ::open("/dev/dsp", O_WRONLY, 0);
if (dev >= 0) {
ret = true;
::close(dev);
}
return ret;
}
//------------------------------------------------------------------------------
bool TSoundOutputDevice::open(const TSoundTrackP &st) {
m_imp->m_currentFormat = st->getFormat();
try {
m_imp->doOpenDevice();
} catch (TSoundDeviceException &e) {
throw TSoundDeviceException(e.getType(), e.getMessage());
}
return true;
}
//------------------------------------------------------------------------------
bool TSoundOutputDevice::close() {
stop();
if (m_imp->m_dev != -1) {
bool closed = m_imp->doCloseDevice();
if (!closed)
throw TSoundDeviceException(
TSoundDeviceException::UnableCloseDevice,
"Error during the closing of the output device");
}
return true;
}
//------------------------------------------------------------------------------
void TSoundOutputDevice::attach(TSoundOutputDeviceListener *listener) {
m_imp->m_listeners.insert(listener);
}
//------------------------------------------------------------------------------
void TSoundOutputDevice::detach(TSoundOutputDeviceListener *listener) {
m_imp->m_listeners.erase(listener);
}
//------------------------------------------------------------------------------
void TSoundOutputDevice::play(const TSoundTrackP &st, TINT32 s0, TINT32 s1,
bool loop, bool scrubbing) {
assert((scrubbing && !loop) || !scrubbing);
if (!st->getSampleCount()) return;
TThread::ScopedLock sl(m_imp->m_mutex);
if (m_imp->m_looped)
throw TSoundDeviceException(
TSoundDeviceException::Busy,
"Unable to queue another playback when the sound player is looping");
TSoundTrackFormat format = st->getFormat();
if (!m_imp->isSupportFormat(format)) {
throw TSoundDeviceException(TSoundDeviceException::UnsupportedFormat,
"Unsupported format for playback");
}
if (m_imp->m_isPlaying) {
assert(s1 >= s0);
TSoundTrackP subTrack = st->extract(s0, s1);
m_imp->m_waitingTracks.push_back(std::make_pair(subTrack, loop));
// std::cout<<"Sono in pushback"<<std::endl;
return;
}
if ((m_imp->m_dev == -1)) try {
if (m_imp->doOpenDevice()) m_imp->setFormat(format);
} catch (TSoundDeviceException &e) {
m_imp->doCloseDevice();
throw TSoundDeviceException(e.getType(), e.getMessage());
}
m_imp->m_isPlaying = true;
m_imp->m_stopped = false;
m_imp->m_looped = loop;
// m_imp->m_currentFormat = st->getFormat();
assert(s1 >= s0);
TSoundTrackP subTrack = st->extract(s0, s1);
m_imp->m_executor.addTask(new TPlayTask(m_imp, subTrack));
}
//------------------------------------------------------------------------------
void TSoundOutputDevice::stop() {
TThread::ScopedLock sl(m_imp->m_mutex);
if (!m_imp->m_isPlaying) return;
m_imp->m_executor.cancel();
ioctl(m_imp->m_dev, SNDCTL_DSP_POST, 0);
m_imp->m_isPlaying = false;
m_imp->m_stopped = true;
m_imp->m_looped = false;
m_imp->m_waitingTracks.clear();
}
//------------------------------------------------------------------------------
double TSoundOutputDevice::getVolume() {
int mixer;
if ((mixer = openMixer()) < 0)
throw TSoundDeviceException(TSoundDeviceException::NoMixer,
"Can't open the mixer device");
int devmask;
if (ioctl(mixer, SOUND_MIXER_READ_DEVMASK, &devmask) == -1) {
::close(mixer);
throw TSoundDeviceException(TSoundDeviceException::NoMixer,
"Error in ioctl with mixer device");
}
int recmask;
if (ioctl(mixer, SOUND_MIXER_READ_RECMASK, &recmask) == -1) {
::close(mixer);
throw TSoundDeviceException(TSoundDeviceException::NoMixer,
"Error in ioctl with mixer device");
}
int stereo;
if (ioctl(mixer, SOUND_MIXER_READ_STEREODEVS, &stereo) == -1) {
::close(mixer);
throw TSoundDeviceException(TSoundDeviceException::NoMixer,
"Error in ioctl with mixer device");
}
int outmask = devmask | ~recmask;
int index;
if (outmask & (1 << SOUND_MIXER_ALTPCM))
index = SOUND_MIXER_ALTPCM;
else if (outmask & (1 << SOUND_MIXER_PCM))
index = SOUND_MIXER_PCM;
int level;
if (ioctl(mixer, MIXER_READ(index), &level) == -1) {
::close(mixer);
throw TSoundDeviceException(TSoundDeviceException::UnableVolume,
"Error to read the volume");
}
if ((1 << index) & stereo) {
int left = level & 0xff;
int right = ((level & 0xff00) >> 8);
::close(mixer);
return (left + right) / 20.0;
} else {
::close(mixer);
return (level & 0xff) / 10.0;
}
}
//------------------------------------------------------------------------------
bool TSoundOutputDevice::setVolume(double volume) {
int mixer;
if ((mixer = openMixer()) < 0)
throw TSoundDeviceException(TSoundDeviceException::NoMixer,
"Can't open the mixer device");
int devmask;
if (ioctl(mixer, SOUND_MIXER_READ_DEVMASK, &devmask) == -1) {
::close(mixer);
throw TSoundDeviceException(TSoundDeviceException::NoMixer,
"Error in ioctl with mixer device");
}
int recmask;
if (ioctl(mixer, SOUND_MIXER_READ_DEVMASK, &recmask) == -1) {
::close(mixer);
throw TSoundDeviceException(TSoundDeviceException::NoMixer,
"Error in ioctl with mixer device");
}
int stereo;
if (ioctl(mixer, SOUND_MIXER_READ_STEREODEVS, &stereo) == -1) {
::close(mixer);
throw TSoundDeviceException(TSoundDeviceException::NoMixer,
"Error in ioctl with mixer device");
}
int outmask = devmask | ~recmask;
if (outmask & (1 << SOUND_MIXER_ALTPCM)) {
int vol, index = SOUND_MIXER_ALTPCM;
if ((1 << index) & stereo) {
volume *= 10.0;
vol = (int)volume + ((int)(volume * 256.0));
} else
vol = (int)(volume * 10.0);
if (!writeVolume(vol, mixer, index)) {
::close(mixer);
throw TSoundDeviceException(TSoundDeviceException::UnableVolume,
"Can't write the volume");
}
// metto anche l'altro ad un livello di sensibilita' adeguata
if (outmask & (1 << SOUND_MIXER_PCM)) {
int vol, index = SOUND_MIXER_PCM;
double volDefault = 6.7;
if ((1 << index) & stereo) {
volDefault *= 10.0;
vol = (int)volDefault + ((int)(volDefault * 256.0));
} else
vol = (int)(volDefault * 10.0);
if (!writeVolume(vol, mixer, index)) {
::close(mixer);
throw TSoundDeviceException(TSoundDeviceException::UnableVolume,
"Can't write the volume");
}
}
} else if (outmask & (1 << SOUND_MIXER_PCM)) {
int vol, index = SOUND_MIXER_PCM;
if ((1 << index) & stereo) {
volume *= 10.0;
vol = (int)volume + ((int)(volume * 256.0));
} else
vol = (int)(volume * 10.0);
if (!writeVolume(vol, mixer, index)) {
::close(mixer);
throw TSoundDeviceException(TSoundDeviceException::NoMixer,
"Can't write the volume");
}
}
::close(mixer);
return true;
}
//------------------------------------------------------------------------------
bool TSoundOutputDevice::isPlaying() const {
TThread::ScopedLock sl(m_imp->m_mutex);
return m_imp->m_isPlaying;
}
//------------------------------------------------------------------------------
bool TSoundOutputDevice::isLooping() {
TThread::ScopedLock sl(m_imp->m_mutex);
return m_imp->m_looped;
}
//------------------------------------------------------------------------------
void TSoundOutputDevice::setLooping(bool loop) {
TThread::ScopedLock sl(m_imp->m_mutex);
m_imp->m_looped = loop;
}
//------------------------------------------------------------------------------
bool TSoundOutputDevice::supportsVolume() {
int mixer;
if ((mixer = openMixer()) < 0)
throw TSoundDeviceException(TSoundDeviceException::NoMixer,
"Can't open the mixer device");
int devmask;
if (ioctl(mixer, SOUND_MIXER_READ_DEVMASK, &devmask) == -1) {
::close(mixer);
throw TSoundDeviceException(TSoundDeviceException::NoMixer,
"Error in ioctl with mixer device");
}
int recmask;
if (ioctl(mixer, SOUND_MIXER_READ_DEVMASK, &recmask) == -1) {
::close(mixer);
throw TSoundDeviceException(TSoundDeviceException::NoMixer,
"Error in ioctl with mixer device");
}
int stereo;
if (ioctl(mixer, SOUND_MIXER_READ_STEREODEVS, &stereo) == -1) {
::close(mixer);
throw TSoundDeviceException(TSoundDeviceException::NoMixer,
"Error in ioctl with mixer device");
}
int outmask = devmask | ~recmask;
if ((outmask & (1 << SOUND_MIXER_ALTPCM)) ||
(outmask & (1 << SOUND_MIXER_PCM))) {
::close(mixer);
return true;
}
return false;
}
//------------------------------------------------------------------------------
TSoundTrackFormat TSoundOutputDevice::getPreferredFormat(TUINT32 sampleRate,
int channelCount,
int bitPerSample) {
TSoundTrackFormat fmt;
if (bitPerSample == 8)
fmt = TSoundTrackFormat(sampleRate, channelCount, bitPerSample, false);
else
fmt = TSoundTrackFormat(sampleRate, channelCount, bitPerSample);
if (m_imp->isSupportFormat(fmt)) return fmt;
int bps, ch, status;
bps = bitPerSample;
ch = channelCount;
if (m_imp->m_dev == -1) m_imp->doOpenDevice();
if (bitPerSample <= 8) {
bitPerSample = AFMT_U8;
fmt.m_signedSample = false;
} else if ((bitPerSample > 8 && bitPerSample < 16) || bitPerSample >= 16) {
bitPerSample = AFMT_S16_NE;
fmt.m_signedSample = true;
}
status = ioctl(m_imp->m_dev, SNDCTL_DSP_SETFMT, &bitPerSample);
if (status == -1) {
perror("CHE palle ");
throw TSoundDeviceException(TSoundDeviceException::UnablePrepare,
"Failed setting the specified number of bits");
}
fmt.m_bitPerSample = bitPerSample;
status = ioctl(m_imp->m_dev, SNDCTL_DSP_CHANNELS, &channelCount);
if (status == -1)
throw TSoundDeviceException(
TSoundDeviceException::UnablePrepare,
"Failed setting the specified number of channel");
fmt.m_channelCount = channelCount;
if (m_imp->m_supportedRate.find((int)sampleRate) ==
m_imp->m_supportedRate.end()) {
std::set<int>::iterator it =
m_imp->m_supportedRate.lower_bound((int)sampleRate);
if (it == m_imp->m_supportedRate.end()) {
it = std::max_element(m_imp->m_supportedRate.begin(),
m_imp->m_supportedRate.end());
if (it != m_imp->m_supportedRate.end())
sampleRate = *(m_imp->m_supportedRate.rbegin());
else
throw TSoundDeviceException(TSoundDeviceException::UnsupportedFormat,
"There isn't a supported rate");
} else
sampleRate = *it;
}
if (ioctl(m_imp->m_dev, SNDCTL_DSP_SPEED, &sampleRate) == -1)
throw TSoundDeviceException(TSoundDeviceException::UnablePrepare,
"Failed setting the specified sample rate");
fmt.m_sampleRate = sampleRate;
if (ch != channelCount || bps != bitPerSample) {
m_imp->doCloseDevice();
}
return fmt;
}
//------------------------------------------------------------------------------
TSoundTrackFormat TSoundOutputDevice::getPreferredFormat(
const TSoundTrackFormat &format) {
try {
return getPreferredFormat(format.m_sampleRate, format.m_channelCount,
format.m_bitPerSample);
} catch (TSoundDeviceException &e) {
throw TSoundDeviceException(e.getType(), e.getMessage());
}
}
//======================================================================
//======================================================================
// CLASSI PER IL RECORD
//======================================================================
//======================================================================
class TSoundInputDeviceImp {
public:
int m_dev;
bool m_stopped;
bool m_isRecording;
TSoundTrackFormat m_currentFormat;
TSoundTrackP m_st;
std::set<int> m_supportedRate;
TINT32 m_recordedSampleCount;
vector<char *> m_recordedBlocks;
vector<int> m_samplePerBlocks;
bool m_oneShotRecording;
TThread::Executor m_executor;
TSoundInputDeviceImp()
: m_dev(-1)
, m_stopped(false)
, m_isRecording(false)
, m_st(0)
, m_supportedRate()
, m_recordedBlocks()
, m_samplePerBlocks()
, m_oneShotRecording(false) {}
~TSoundInputDeviceImp() {}
bool doOpenDevice(const TSoundTrackFormat &format,
TSoundInputDevice::Source devType);
bool doCloseDevice();
void insertAllRate();
bool verifyRate();
};
//------------------------------------------------------------------------------
bool TSoundInputDeviceImp::doOpenDevice(const TSoundTrackFormat &format,
TSoundInputDevice::Source devType) {
m_dev = open("/dev/dsp", O_RDONLY);
if (m_dev < 0)
throw TSoundDeviceException(TSoundDeviceException::UnableOpenDevice,
"Cannot open the dsp device");
// ioctl(m_dev, SNDCTL_DSP_RESET,0);
return true;
}
//------------------------------------------------------------------------------
bool TSoundInputDeviceImp::doCloseDevice() {
if (close(m_dev) < 0)
throw TSoundDeviceException(TSoundDeviceException::UnableCloseDevice,
"Cannot close the dsp device");
m_dev = -1;
return true;
}
//----------------------------------------------------------------------------
void TSoundInputDeviceImp::insertAllRate() {
m_supportedRate.insert(8000);
m_supportedRate.insert(11025);
m_supportedRate.insert(16000);
m_supportedRate.insert(22050);
m_supportedRate.insert(32000);
m_supportedRate.insert(44100);
m_supportedRate.insert(48000);
}
//----------------------------------------------------------------------------
bool TSoundInputDeviceImp::verifyRate() {
std::set<int>::iterator it;
for (it = m_supportedRate.begin(); it != m_supportedRate.end(); ++it) {
int sampleRate = *it;
if (ioctl(m_dev, SNDCTL_DSP_SPEED, &sampleRate) == -1)
throw TSoundDeviceException(TSoundDeviceException::UnablePrepare,
"Failed setting the specified sample rate");
if (sampleRate != *it) m_supportedRate.erase(*it);
}
if (m_supportedRate.end() == m_supportedRate.begin()) return false;
return true;
}
//==============================================================================
class TRecordTask : public TThread::Runnable {
public:
TSoundInputDeviceImp *m_devImp;
TRecordTask(TSoundInputDeviceImp *devImp) : Runnable(), m_devImp(devImp){};
~TRecordTask(){};
void run();
};
//------------------------------------------------------------------------------
void TRecordTask::run() {
// N.B. e' bene che la dimensione sia piccola cosi' l'attesa perche' termini
// e' minore in questo caso vogliamo 16 frammenti ognuno di 4096 byte
int fraginfo = (16 << 16) | 12;
if (ioctl(m_devImp->m_dev, SNDCTL_DSP_SETFRAGMENT, &fraginfo) == -1)
perror("SETFRAGMENT");
int fragsize = 0;
if (ioctl(m_devImp->m_dev, SNDCTL_DSP_GETBLKSIZE, &fragsize) == -1)
perror("GETFRAGMENT");
TINT32 byteRecordedSample = 0;
if (m_devImp->m_oneShotRecording) {
TINT32 byteToSample =
m_devImp->m_st->getSampleSize() * m_devImp->m_st->getSampleCount();
char *buf = (char *)m_devImp->m_st->getRawData();
while ((byteRecordedSample < byteToSample) && m_devImp->m_isRecording) {
int sample;
if (fragsize > (byteToSample - byteRecordedSample))
sample = byteToSample - byteRecordedSample;
else
sample = fragsize;
int nread = read(m_devImp->m_dev, buf + byteRecordedSample, sample);
if (nread == -1) break;
if (nread != sample) break;
byteRecordedSample += nread;
}
} else {
int bytePerSample = m_devImp->m_currentFormat.m_bitPerSample >> 3;
switch (bytePerSample) {
case 3:
bytePerSample++;
break;
default:
break;
}
bytePerSample *= m_devImp->m_currentFormat.m_channelCount;
while (m_devImp->m_isRecording) {
char *dataBuffer = new char[fragsize];
m_devImp->m_recordedBlocks.push_back(dataBuffer);
m_devImp->m_samplePerBlocks.push_back(fragsize);
int nread = read(m_devImp->m_dev, dataBuffer, fragsize);
if (nread == -1) break;
if (nread != fragsize) break;
m_devImp->m_recordedSampleCount += (fragsize / bytePerSample);
}
}
ioctl(m_devImp->m_dev, SNDCTL_DSP_RESET, 0);
}
//==============================================================================
TSoundInputDevice::TSoundInputDevice() : m_imp(new TSoundInputDeviceImp) {
m_imp->m_dev = open("/dev/dsp", O_RDONLY);
if (m_imp->m_dev < 0)
throw TSoundDeviceException(TSoundDeviceException::UnableOpenDevice,
"Cannot open the dsp device");
m_imp->insertAllRate();
try {
if (!m_imp->verifyRate())
throw TSoundDeviceException(TSoundDeviceException::UnablePrepare,
"No default samplerate are supported");
} catch (TSoundDeviceException &e) {
throw TSoundDeviceException(e.getType(), e.getMessage());
}
m_imp->doCloseDevice();
}
//------------------------------------------------------------------------------
TSoundInputDevice::~TSoundInputDevice() {
if (m_imp->m_dev != -1) m_imp->doCloseDevice();
delete m_imp;
}
//------------------------------------------------------------------------------
bool TSoundInputDevice::installed() {
bool ret = false;
int dev = ::open("/dev/dsp", O_RDONLY);
if (dev >= 0) {
ret = true;
::close(dev);
}
return ret;
}
//------------------------------------------------------------------------------
void TSoundInputDevice::record(const TSoundTrackFormat &format,
TSoundInputDevice::Source type) {
m_imp->m_recordedBlocks.clear();
m_imp->m_samplePerBlocks.clear();
// registra creando una nuova traccia
m_imp->m_oneShotRecording = false;
try {
if (m_imp->m_dev == -1) m_imp->doOpenDevice(format, type);
if (!selectInputDevice(type))
throw TSoundDeviceException(
TSoundDeviceException::UnableSetDevice,
"Input device is not supported for recording");
TSoundTrackFormat fmt = getPreferredFormat(format);
if (fmt != format)
throw TSoundDeviceException(TSoundDeviceException::UnsupportedFormat,
"Unsupported format");
} catch (TSoundDeviceException &e) {
throw TSoundDeviceException(e.getType(), e.getMessage());
}
try {
if (getVolume() == 0.0) {
double volume = 5.0;
setVolume(volume);
}
} catch (TSoundDeviceException &e) {
throw TSoundDeviceException(e.getType(), e.getMessage());
}
m_imp->m_currentFormat = format;
m_imp->m_isRecording = true;
m_imp->m_stopped = false;
m_imp->m_recordedSampleCount = 0;
// far partire il thread
/*TRecordThread *recordThread = new TRecordThread(m_imp);
if (!recordThread)
throw TSoundDeviceException(
TSoundDeviceException::UnablePrepare,
"Problemi per creare il thread");
recordThread->start();*/
m_imp->m_executor.addTask(new TRecordTask(m_imp));
}
//------------------------------------------------------------------------------
void TSoundInputDevice::record(const TSoundTrackP &st,
TSoundInputDevice::Source type) {
m_imp->m_recordedBlocks.clear();
m_imp->m_samplePerBlocks.clear();
try {
if (m_imp->m_dev == -1) m_imp->doOpenDevice(st->getFormat(), type);
if (!selectInputDevice(type))
throw TSoundDeviceException(
TSoundDeviceException::UnableSetDevice,
"Input device is not supported for recording");
TSoundTrackFormat fmt = getPreferredFormat(st->getFormat());
if (fmt != st->getFormat())
throw TSoundDeviceException(TSoundDeviceException::UnsupportedFormat,
"Unsupported format");
if (getVolume() == 0.0) {
double volume = 5.0;
setVolume(volume);
}
} catch (TSoundDeviceException &e) {
throw TSoundDeviceException(e.getType(), e.getMessage());
}
// Sovrascive un'intera o parte di traccia gia' esistente
m_imp->m_oneShotRecording = true;
m_imp->m_currentFormat = st->getFormat();
m_imp->m_isRecording = true;
m_imp->m_stopped = false;
m_imp->m_recordedSampleCount = 0;
m_imp->m_st = st;
m_imp->m_recordedBlocks.push_back((char *)st->getRawData());
// far partire il thread
/*TRecordThread *recordThread = new TRecordThread(m_imp);
if (!recordThread)
throw TSoundDeviceException(
TSoundDeviceException::UnablePrepare,
"Problemi per creare il thread");
recordThread->start();*/
m_imp->m_executor.addTask(new TRecordTask(m_imp));
}
//------------------------------------------------------------------------------
TSoundTrackP TSoundInputDevice::stop() {
TSoundTrackP st;
if (!m_imp->m_isRecording) return st;
m_imp->m_isRecording = false;
// mettere istruzioni per fermare il rec
ioctl(m_imp->m_dev, SNDCTL_DSP_SYNC, 0);
try {
m_imp->doCloseDevice();
} catch (TSoundDeviceException &e) {
throw TSoundDeviceException(e.getType(), e.getMessage());
}
// attendo 1/5 di secondo
usleep(200000);
if (m_imp->m_oneShotRecording)
st = m_imp->m_st;
else {
st = TSoundTrack::create(m_imp->m_currentFormat,
m_imp->m_recordedSampleCount);
TINT32 bytesCopied = 0;
for (int i = 0; i < (int)m_imp->m_recordedBlocks.size(); ++i) {
memcpy((void *)(st->getRawData() + bytesCopied),
m_imp->m_recordedBlocks[i], m_imp->m_samplePerBlocks[i]);
delete[] m_imp->m_recordedBlocks[i];
bytesCopied += m_imp->m_samplePerBlocks[i];
}
m_imp->m_samplePerBlocks.clear();
}
return st;
}
//------------------------------------------------------------------------------
double TSoundInputDevice::getVolume() {
int mixer;
if ((mixer = openMixer()) < 0)
throw TSoundDeviceException(TSoundDeviceException::NoMixer,
"Can't have the mixer");
int index;
if ((index = getCurrentRecordSource(mixer)) == -1) {
::close(mixer);
throw TSoundDeviceException(TSoundDeviceException::NoMixer,
"Can't obtain information by mixer");
}
int stereo;
if (ioctl(mixer, SOUND_MIXER_READ_STEREODEVS, &stereo) == -1) {
::close(mixer);
throw TSoundDeviceException(TSoundDeviceException::NoMixer,
"Can't obtain information by mixer");
}
int level;
if (ioctl(mixer, MIXER_READ(index), &level) == -1) {
::close(mixer);
throw TSoundDeviceException(TSoundDeviceException::UnableVolume,
"Can't read the volume value");
}
if ((1 << index) & stereo) {
int left = level & 0xff;
int right = ((level & 0xff00) >> 8);
::close(mixer);
return (left + right) / 20.0;
} else {
::close(mixer);
return (level & 0xff) / 10.0;
}
}
//------------------------------------------------------------------------------
bool TSoundInputDevice::setVolume(double volume) {
int mixer;
if ((mixer = openMixer()) < 0)
throw TSoundDeviceException(TSoundDeviceException::NoMixer,
"Can't have the mixer");
int caps;
if (ioctl(mixer, SOUND_MIXER_READ_CAPS, &caps) == -1) {
::close(mixer);
throw TSoundDeviceException(TSoundDeviceException::NoMixer,
"Can't obtain information by mixer");
}
if (!(caps & SOUND_CAP_EXCL_INPUT)) {
int rec;
if (ioctl(mixer, SOUND_MIXER_READ_RECMASK, &rec) == -1) {
::close(mixer);
throw TSoundDeviceException(TSoundDeviceException::NoMixer,
"Can't obtain information by mixer");
}
int i;
int nosound = 0;
for (i = 0; i < 32; ++i)
if (rec & (1 << i))
if (!writeVolume(nosound, mixer, i)) {
::close(mixer);
throw TSoundDeviceException(TSoundDeviceException::UnableVolume,
"Can't set the volume value");
}
}
int index;
if ((index = getCurrentRecordSource(mixer)) == -1) {
::close(mixer);
throw TSoundDeviceException(TSoundDeviceException::NoMixer,
"Can't obtain information by mixer");
}
int stereo;
if (ioctl(mixer, SOUND_MIXER_READ_STEREODEVS, &stereo) == -1) {
::close(mixer);
throw TSoundDeviceException(TSoundDeviceException::NoMixer,
"Can't obtain information by mixer");
}
int vol;
if ((1 << index) & stereo) {
volume *= 10.0;
vol = (int)volume + ((int)(volume * 256.0));
} else
vol = (int)(volume * 10.0);
if (!writeVolume(vol, mixer, index)) {
::close(mixer);
throw TSoundDeviceException(TSoundDeviceException::UnableVolume,
"Can't write the volume value");
}
::close(mixer);
return true;
}
//------------------------------------------------------------------------------
bool TSoundInputDevice::isRecording() { return m_imp->m_isRecording; }
//------------------------------------------------------------------------------
TSoundTrackFormat TSoundInputDevice::getPreferredFormat(TUINT32 sampleRate,
int channelCount,
int bitPerSample) {
TSoundTrackFormat fmt;
int status;
if (bitPerSample <= 8) {
bitPerSample = AFMT_U8;
fmt.m_signedSample = false;
} else if ((bitPerSample > 8 && bitPerSample < 16) || bitPerSample >= 16) {
bitPerSample = AFMT_S16_NE;
fmt.m_signedSample = true;
}
status = ioctl(m_imp->m_dev, SNDCTL_DSP_SETFMT, &bitPerSample);
if (status == -1)
throw TSoundDeviceException(TSoundDeviceException::UnablePrepare,
"Failed setting the specified number of bits");
fmt.m_bitPerSample = bitPerSample;
status = ioctl(m_imp->m_dev, SNDCTL_DSP_CHANNELS, &channelCount);
if (status == -1)
throw TSoundDeviceException(
TSoundDeviceException::UnablePrepare,
"Failed setting the specified number of channel");
fmt.m_channelCount = channelCount;
if (m_imp->m_supportedRate.find((int)sampleRate) ==
m_imp->m_supportedRate.end()) {
std::set<int>::iterator it =
m_imp->m_supportedRate.lower_bound((int)sampleRate);
if (it == m_imp->m_supportedRate.end()) {
it = std::max_element(m_imp->m_supportedRate.begin(),
m_imp->m_supportedRate.end());
if (it != m_imp->m_supportedRate.end())
sampleRate = *(m_imp->m_supportedRate.rbegin());
else
throw TSoundDeviceException(TSoundDeviceException::UnsupportedFormat,
"There isn't a supported rate");
} else
sampleRate = *it;
}
if (ioctl(m_imp->m_dev, SNDCTL_DSP_SPEED, &sampleRate) == -1)
throw TSoundDeviceException(TSoundDeviceException::UnablePrepare,
"Failed setting the specified sample rate");
fmt.m_sampleRate = sampleRate;
return fmt;
}
//------------------------------------------------------------------------------
TSoundTrackFormat TSoundInputDevice::getPreferredFormat(
const TSoundTrackFormat &format) {
try {
return getPreferredFormat(format.m_sampleRate, format.m_channelCount,
format.m_bitPerSample);
} catch (TSoundDeviceException &e) {
throw TSoundDeviceException(e.getType(), e.getMessage());
}
}
//******************************************************************************
//******************************************************************************
// funzioni per l'interazione con
// la
// libreria
// OSS
//******************************************************************************
//******************************************************************************
namespace {
string parseError(int error) {
switch (error) {
case EBADF:
return string("Bad file descriptor");
case EFAULT:
return string("Pointer to/from buffer data is invalid");
case EINTR:
return string("Signal interrupt the signal");
case EINVAL:
return string("Request/arg isn't valid for this device");
case EIO:
return string("Some phisical I/O error has occurred");
case ENOTTY:
return string(
"Fieldes isn't associated with a device that accepts control");
case ENXIO:
return string(
"Request/arg valid, but the requested cannot be performed on this "
"subdevice");
default:
return string("Unknown error");
break;
}
}
//------------------------------------------------------------------------------
TSoundInputDevice::Source stringToSource(string dev) {
if (dev == "mic")
return TSoundInputDevice::Mic;
else if (dev == "line")
return TSoundInputDevice::LineIn;
else if (dev == "cd")
return TSoundInputDevice::CdAudio;
else
return TSoundInputDevice::DigitalIn;
}
//------------------------------------------------------------------------------
string sourceToString(TSoundInputDevice::Source dev) {
switch (dev) {
case TSoundInputDevice::Mic:
return string("mic");
case TSoundInputDevice::LineIn:
return string("line");
case TSoundInputDevice::DigitalIn:
return string("digital");
default:
return string("cd");
}
}
//------------------------------------------------------------------------------
int openMixer() {
int mixer = open("/dev/mixer", O_RDWR);
if (mixer == -1) return false;
return mixer;
}
//------------------------------------------------------------------------------
int getCurrentRecordSource(int mixer) {
int recsrc;
if (ioctl(mixer, SOUND_MIXER_READ_RECSRC, &recsrc) == -1) return -1;
int index = -1;
for (index = 0; index < 32; ++index)
if (recsrc & 1 << index) break;
return index;
}
//------------------------------------------------------------------------------
bool writeVolume(int volume, int mixer, int indexDev) {
if (ioctl(mixer, MIXER_WRITE(indexDev), &volume) == -1) return false;
return true;
}
//------------------------------------------------------------------------------
bool controlEnableRecord(int mixer) {
int recmask;
if (ioctl(mixer, SOUND_MIXER_READ_RECMASK, &recmask) == -1) {
perror("Read recmask");
return false;
}
if (recmask & (1 << SOUND_MIXER_IGAIN)) {
int volume;
if (ioctl(mixer, MIXER_READ(SOUND_MIXER_IGAIN), &volume) == -1)
return false;
int app = (volume & 0xff);
if (app <= 30) {
volume = 80 | 80 << 8;
if (!writeVolume(volume, mixer, SOUND_MIXER_IGAIN)) return false;
}
}
return true;
}
//------------------------------------------------------------------------------
int isInputDeviceSupported(TSoundInputDevice::Source dev, int &mixer) {
int recmask;
if (ioctl(mixer, SOUND_MIXER_READ_RECMASK, &recmask) == -1) {
perror("Read recmask");
return -1;
}
int i;
string devS = sourceToString(dev);
const char *deviceName[] = SOUND_DEVICE_NAMES;
for (i = 0; i < 32; ++i) {
if (!(recmask & 1 << i)) continue;
if (strcmp(devS.c_str(), deviceName[i]) == 0) return i;
}
return -1;
}
//------------------------------------------------------------------------------
bool selectInputDevice(TSoundInputDevice::Source dev) {
int mixer;
if ((mixer = openMixer()) < 0) {
close(mixer);
return false; // throw TException("Can't open the mixer device");
}
int index = isInputDeviceSupported(dev, mixer);
if (index == -1) return false;
int recsrc;
if (ioctl(mixer, SOUND_MIXER_READ_RECSRC, &recsrc) == -1) {
perror("Read recsrc");
close(mixer);
return false;
}
if (!(recsrc & 1 << index)) {
recsrc = 1 << index;
if (ioctl(mixer, SOUND_MIXER_WRITE_RECSRC, &recsrc) == -1) {
perror("Write recsrc");
::close(mixer);
return false;
}
}
if (!controlEnableRecord(mixer)) {
close(mixer);
return false; // throw TException("Can't enable recording");
}
::close(mixer);
return true;
}
}