#include "tfilepath.h" #include "tsound.h" #include "tsound_io.h" #include "tsop.h" #include "tthread.h" #include "texception.h" #include "tsystem.h" #include #include #include #include #include #include #include // 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 m_supportedRate; static std::multimap m_supportFormats; typedef pair WaitPair; vector m_waitingTracks; std::set 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 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<::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 << " " <::iterator it; pair::iterator, std::multimap::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 " << // (deltamaxDelay?"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"< 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"<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 " << // (deltamaxDelay?"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"<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"<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"<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"<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::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 m_supportedRate; TINT32 m_recordedSampleCount; vector m_recordedBlocks; vector 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::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::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; } }