#include "tsound_t.h" #include "texception.h" #include "tthread.h" #include "tthreadmessage.h" #include #include #include #include #include #include #include #include using namespace std; //============================================================================== namespace { TThread::Mutex MutexOut; } class TSoundOutputDeviceImp { public: bool m_isPlaying; bool m_looped; TSoundTrackFormat m_currentFormat; std::set m_supportedRate; bool m_opened; AudioFileID musicFileID; AudioUnit theOutputUnit; AudioStreamBasicDescription fileASBD; AudioStreamBasicDescription outputASBD; AudioConverterRef converter; TSoundOutputDeviceImp() : m_isPlaying(false), m_looped(false), m_supportedRate(), m_opened(false){}; std::set m_listeners; ~TSoundOutputDeviceImp(){}; bool doOpenDevice(); bool doSetStreamFormat(const TSoundTrackFormat &format); bool doStopDevice(); void play(const TSoundTrackP &st, TINT32 s0, TINT32 s1, bool loop, bool scrubbing); }; //----------------------------------------------------------------------------- namespace { struct MyData { char *entireFileBuffer; UInt64 totalPacketCount; UInt64 fileByteCount; UInt32 maxPacketSize; UInt64 packetOffset; UInt64 byteOffset; bool m_doNotify; void *sourceBuffer; AudioConverterRef converter; TSoundOutputDeviceImp *imp; bool isLooping; MyData() : entireFileBuffer(0), totalPacketCount(0), fileByteCount(0), maxPacketSize(0), packetOffset(0), byteOffset(0), sourceBuffer(0), isLooping(false), imp(0), m_doNotify(true) { } }; class PlayCompletedMsg : public TThread::Message { std::set m_listeners; MyData *m_data; public: PlayCompletedMsg(MyData *data) : m_data(data) { } TThread::Message *clone() const { return new PlayCompletedMsg(*this); } void onDeliver() { if (m_data->imp) { if (m_data->m_doNotify == false) return; m_data->m_doNotify = false; if (m_data->imp->m_isPlaying) m_data->imp->doStopDevice(); std::set::iterator it = m_data->imp->m_listeners.begin(); for (; it != m_data->imp->m_listeners.end(); ++it) (*it)->onPlayCompleted(); } } }; } #define checkStatus(err) \ if (err) { \ printf("Error: 0x%x -> %s: %d\n", (int)err, __FILE__, __LINE__); \ fflush(stdout); \ } extern "C" { //This is an example of a Input Procedure from a call to AudioConverterFillComplexBuffer. //The total amount of data needed is "ioNumberDataPackets" when this method is first called. //On exit, "ioNumberDataPackets" must be set to the actual amount of data obtained. //Upon completion, all new input data must point to the AudioBufferList in the parameter ( "ioData" ) OSStatus MyACComplexInputProc(AudioConverterRef inAudioConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData, AudioStreamPacketDescription **outDataPacketDescription, void *inUserData) { OSStatus err = noErr; UInt32 bytesCopied = 0; MyData *myData = static_cast(inUserData); // initialize in case of failure ioData->mBuffers[0].mData = NULL; ioData->mBuffers[0].mDataByteSize = 0; { //TThread::ScopedLock sl(MutexOut); if (myData->imp->m_isPlaying == false) return noErr; } // if there are not enough packets to satisfy request, then read what's left if (myData->packetOffset + *ioNumberDataPackets > myData->totalPacketCount) *ioNumberDataPackets = myData->totalPacketCount - myData->packetOffset; // do nothing if there are no packets available if (*ioNumberDataPackets) { if (myData->sourceBuffer != NULL) { free(myData->sourceBuffer); myData->sourceBuffer = NULL; } //the total amount of data requested by the AudioConverter bytesCopied = *ioNumberDataPackets * myData->maxPacketSize; //alloc a small buffer for the AudioConverter to use. myData->sourceBuffer = (void *)calloc(1, bytesCopied); //copy the amount of data needed (bytesCopied) from buffer of audio file memcpy(myData->sourceBuffer, myData->entireFileBuffer + myData->byteOffset, bytesCopied); // keep track of where we want to read from next time myData->byteOffset += *ioNumberDataPackets * myData->maxPacketSize; myData->packetOffset += *ioNumberDataPackets; ioData->mBuffers[0].mData = myData->sourceBuffer; // tell the Audio Converter where it's source data is ioData->mBuffers[0].mDataByteSize = bytesCopied; // tell the Audio Converter how much data in each buffer } else { // there aren't any more packets to read. // Set the amount of data read (mDataByteSize) to zero // and return noErr to signal the AudioConverter there are // no packets left. ioData->mBuffers[0].mData = NULL; ioData->mBuffers[0].mDataByteSize = 0; delete[] myData->entireFileBuffer; myData->entireFileBuffer = 0; err = noErr; /* { TThread::ScopedLock sl(MutexOut); *(myData->isPlaying) = false; //questo lo faccio nel main thread } */ PlayCompletedMsg(myData).send(); } return err; } OSStatus MyFileRenderProc(void *inRefCon, AudioUnitRenderActionFlags *inActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumFrames, AudioBufferList *ioData) { MyData *myData = static_cast(inRefCon); OSStatus err = noErr; void *inInputDataProcUserData = inRefCon; AudioStreamPacketDescription *outPacketDescription = NULL; //To obtain a data buffer of converted data from a complex input source(compressed files, etc.) //use AudioConverterFillComplexBuffer. The total amount of data requested is "inNumFrames" and //on return is set to the actual amount of data recieved. //All converted data is returned to "ioData" (AudioBufferList). err = AudioConverterFillComplexBuffer(myData->converter, MyACComplexInputProc, inInputDataProcUserData, &inNumFrames, ioData, outPacketDescription); /*Parameters for AudioConverterFillComplexBuffer() converter - the converter being used ACComplexInputProc() - input procedure to supply data to the Audio Converter inInputDataProcUserData - Used to hold any data that needs to be passed on. Not needed in this example. inNumFrames - The amount of requested data. On output, this number is the amount actually received. ioData - Buffer of the converted data recieved on return outPacketDescription - contains the format of the returned data. Not used in this example. */ //checkStatus(err); return err; } } //extern "C" void PrintStreamDesc(AudioStreamBasicDescription *inDesc) { if (!inDesc) { printf("Can't print a NULL desc!\n"); return; } printf("- - - - - - - - - - - - - - - - - - - -\n"); printf(" Sample Rate:%f\n", inDesc->mSampleRate); printf(" Format ID:%.*s\n", (int)sizeof(inDesc->mFormatID), (char *)&inDesc->mFormatID); printf(" Format Flags:%lX\n", inDesc->mFormatFlags); printf(" Bytes per Packet:%ld\n", inDesc->mBytesPerPacket); printf(" Frames per Packet:%ld\n", inDesc->mFramesPerPacket); printf(" Bytes per Frame:%ld\n", inDesc->mBytesPerFrame); printf(" Channels per Frame:%ld\n", inDesc->mChannelsPerFrame); printf(" Bits per Channel:%ld\n", inDesc->mBitsPerChannel); printf("- - - - - - - - - - - - - - - - - - - -\n"); } bool TSoundOutputDeviceImp::doOpenDevice() { m_opened = false; OSStatus err = noErr; ComponentDescription desc; Component comp; desc.componentType = kAudioUnitType_Output; desc.componentSubType = kAudioUnitSubType_DefaultOutput; //all Audio Units in AUComponent.h must use "kAudioUnitManufacturer_Apple" as the Manufacturer desc.componentManufacturer = kAudioUnitManufacturer_Apple; desc.componentFlags = 0; desc.componentFlagsMask = 0; comp = FindNextComponent(NULL, &desc); //Finds an component that meets the desc spec's if (comp == NULL) return false; err = OpenAComponent(comp, &theOutputUnit); //gains access to the services provided by the component if (err) return false; UInt32 size; Boolean outWritable; UInt32 theInputBus = 0; //Gets the size of the Stream Format Property and if it is writable err = AudioUnitGetPropertyInfo(theOutputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &size, &outWritable); //Get the current stream format of the output err = AudioUnitGetProperty(theOutputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &outputASBD, &size); checkStatus(err); //Set the stream format of the output to match the input err = AudioUnitSetProperty(theOutputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, theInputBus, &outputASBD, size); checkStatus(err); // Initialize AudioUnit, alloc mem buffers for processing err = AudioUnitInitialize(theOutputUnit); checkStatus(err); if (err == noErr) m_opened = true; return m_opened; } bool TSoundOutputDeviceImp::doSetStreamFormat(const TSoundTrackFormat &format) { if (!m_opened) doOpenDevice(); if (!m_opened) return false; fileASBD.mSampleRate = format.m_sampleRate; fileASBD.mFormatID = kAudioFormatLinearPCM; fileASBD.mFormatFlags = 14; /* Standard flags: kAudioFormatFlagIsFloat = (1L << 0) kAudioFormatFlagIsBigEndian = (1L << 1) kAudioFormatFlagIsSignedInteger = (1L << 2) kAudioFormatFlagIsPacked = (1L << 3) kAudioFormatFlagIsAlignedHigh = (1L << 4) kAudioFormatFlagIsNonInterleaved = (1L << 5) kAudioFormatFlagsAreAllClear = (1L << 31) Linear PCM flags: kLinearPCMFormatFlagIsFloat = kAudioFormatFlagIsFloat kLinearPCMFormatFlagIsBigEndian = kAudioFormatFlagIsBigEndian kLinearPCMFormatFlagIsSignedInteger = kAudioFormatFlagIsSignedInteger kLinearPCMFormatFlagIsPacked = kAudioFormatFlagIsPacked kLinearPCMFormatFlagIsAlignedHigh = kAudioFormatFlagIsAlignedHigh kLinearPCMFormatFlagIsNonInterleaved = kAudioFormatFlagIsNonInterleaved kLinearPCMFormatFlagsAreAllClear = kAudioFormatFlagsAreAllClear */ fileASBD.mBytesPerPacket = (format.m_bitPerSample >> 3) * format.m_channelCount; fileASBD.mFramesPerPacket = 1; fileASBD.mBytesPerFrame = (format.m_bitPerSample >> 3) * format.m_channelCount; fileASBD.mChannelsPerFrame = format.m_channelCount; fileASBD.mBitsPerChannel = format.m_bitPerSample; fileASBD.mReserved = 0; //PrintStreamDesc(&fileASBD); m_opened = true; return true; } //============================================================================== TSoundOutputDevice::TSoundOutputDevice() : m_imp(new TSoundOutputDeviceImp) { try { supportsVolume(); } catch (TSoundDeviceException &e) { throw TSoundDeviceException(e.getType(), e.getMessage()); } } //------------------------------------------------------------------------------ TSoundOutputDevice::~TSoundOutputDevice() { stop(); close(); delete m_imp; } //------------------------------------------------------------------------------ bool TSoundOutputDevice::installed() { return true; } //------------------------------------------------------------------------------ bool TSoundOutputDevice::open(const TSoundTrackP &st) { if (!m_imp->doOpenDevice()) throw TSoundDeviceException( TSoundDeviceException::UnableOpenDevice, "Problem to open the output device"); if (!m_imp->doSetStreamFormat(st->getFormat())) throw TSoundDeviceException( TSoundDeviceException::UnableOpenDevice, "Problem to open the output device setting some params"); return true; } //------------------------------------------------------------------------------ bool TSoundOutputDevice::close() { stop(); m_imp->m_opened = false; AudioUnitUninitialize(m_imp->theOutputUnit); //release resources without closing the component CloseComponent(m_imp->theOutputUnit); //Terminates your application's access to the services provided return true; } //------------------------------------------------------------------------------ void TSoundOutputDevice::play(const TSoundTrackP &st, TINT32 s0, TINT32 s1, bool loop, bool scrubbing) { //TThread::ScopedLock sl(MutexOut); int lastSample = st->getSampleCount() - 1; notLessThan(0, s0); notLessThan(0, s1); notMoreThan(lastSample, s0); notMoreThan(lastSample, s1); if (s0 > s1) { #ifdef DEBUG cout << "s0 > s1; reorder" << endl; #endif swap(s0, s1); } if (isPlaying()) { #ifdef DEBUG cout << "is playing, stop it!" << endl; #endif stop(); } m_imp->play(st, s0, s1, loop, scrubbing); } //------------------------------------------------------------------------------ void TSoundOutputDeviceImp::play(const TSoundTrackP &st, TINT32 s0, TINT32 s1, bool loop, bool scrubbing) { if (!doSetStreamFormat(st->getFormat())) return; OSStatus err = noErr; MyData *myData = new MyData(); myData->imp = this; UInt32 magicCookieSize = 0; //PrintStreamDesc(&outputASBD); err = AudioConverterNew(&fileASBD, &outputASBD, &converter); checkStatus(err); err = AudioFileGetPropertyInfo(musicFileID, kAudioFilePropertyMagicCookieData, &magicCookieSize, NULL); if (err == noErr) { void *magicCookie = calloc(1, magicCookieSize); if (magicCookie) { //Get Magic Cookie data from Audio File err = AudioFileGetProperty(musicFileID, kAudioFilePropertyMagicCookieData, &magicCookieSize, magicCookie); // Give the AudioConverter the magic cookie decompression params if there are any if (err == noErr) { err = AudioConverterSetProperty(myData->converter, kAudioConverterDecompressionMagicCookie, magicCookieSize, magicCookie); } err = noErr; if (magicCookie) free(magicCookie); } } else //this is OK because some audio data doesn't need magic cookie data err = noErr; checkStatus(err); myData->converter = converter; myData->totalPacketCount = s1 - s0; myData->fileByteCount = (s1 - s0) * st->getSampleSize(); myData->entireFileBuffer = new char[myData->fileByteCount]; #if defined(i386) if (st->getBitPerSample() == 16) { int i; USHORT *dst = (USHORT *)(myData->entireFileBuffer); USHORT *src = (USHORT *)(st->getRawData() + s0 * st->getSampleSize()); for (i = 0; i < myData->fileByteCount / 2; i++) *dst++ = swapUshort(*src++); } else memcpy(myData->entireFileBuffer, st->getRawData() + s0 * st->getSampleSize(), myData->fileByteCount); #else memcpy(myData->entireFileBuffer, st->getRawData() + s0 * st->getSampleSize(), myData->fileByteCount); #endif myData->maxPacketSize = fileASBD.mFramesPerPacket * fileASBD.mBytesPerFrame; { //TThread::ScopedLock sl(MutexOut); m_isPlaying = true; } myData->isLooping = loop; //cout << "total packet count = " << myData->totalPacketCount <fileByteCount << endl; AURenderCallbackStruct renderCallback; memset(&renderCallback, 0, sizeof(AURenderCallbackStruct)); renderCallback.inputProc = MyFileRenderProc; renderCallback.inputProcRefCon = myData; //Sets the callback for the Audio Unit to the renderCallback err = AudioUnitSetProperty(theOutputUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &renderCallback, sizeof(AURenderCallbackStruct)); checkStatus(err); err = AudioOutputUnitStart(theOutputUnit); checkStatus(err); } //------------------------------------------------------------------------------ bool TSoundOutputDeviceImp::doStopDevice() { m_isPlaying = false; AudioOutputUnitStop(theOutputUnit); //you must stop the audio unit from processing AudioConverterDispose(converter); //deallocates the memory used by inAudioConverter return true; } //------------------------------------------------------------------------------ void TSoundOutputDevice::stop() { //TThread::ScopedLock sl(MutexOut); if (m_imp->m_opened == false) return; //TThread::ScopedLock sl(MutexOut); m_imp->doStopDevice(); } //------------------------------------------------------------------------------ void TSoundOutputDevice::attach(TSoundOutputDeviceListener *listener) { m_imp->m_listeners.insert(listener); } //------------------------------------------------------------------------------ void TSoundOutputDevice::detach(TSoundOutputDeviceListener *listener) { m_imp->m_listeners.erase(listener); } //------------------------------------------------------------------------------ double TSoundOutputDevice::getVolume() { if (!m_imp->m_opened) m_imp->doOpenDevice(); Float32 leftVol, rightVol; AudioUnitGetParameter( m_imp->theOutputUnit, kHALOutputParam_Volume, kAudioUnitScope_Output, 0, &leftVol); AudioUnitGetParameter( m_imp->theOutputUnit, kHALOutputParam_Volume, kAudioUnitScope_Output, 0, &rightVol); double vol = (leftVol + rightVol) / 2; return (vol < 0. ? 0. : vol); } //------------------------------------------------------------------------------ bool TSoundOutputDevice::setVolume(double volume) { Float32 vol = volume; AudioUnitSetParameter( m_imp->theOutputUnit, kHALOutputParam_Volume, kAudioUnitScope_Output, 0, vol, 0); AudioUnitSetParameter( m_imp->theOutputUnit, kHALOutputParam_Volume, kAudioUnitScope_Output, 0, vol, 0); return true; } //------------------------------------------------------------------------------ bool TSoundOutputDevice::supportsVolume() { return true; } //------------------------------------------------------------------------------ bool TSoundOutputDevice::isPlaying() const { // TThread::ScopedLock sl(MutexOut); return m_imp->m_isPlaying; } //------------------------------------------------------------------------------ bool TSoundOutputDevice::isLooping() { //TThread::ScopedLock sl(MutexOut); return m_imp->m_looped; } //------------------------------------------------------------------------------ void TSoundOutputDevice::setLooping(bool loop) { //TThread::ScopedLock sl(MutexOut); m_imp->m_looped = loop; } //------------------------------------------------------------------------------ TSoundTrackFormat TSoundOutputDevice::getPreferredFormat( TUINT32 sampleRate, int channelCount, int bitPerSample) { TSoundTrackFormat fmt( sampleRate, bitPerSample, channelCount, true); 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()); }*/ } //============================================================================== //============================================================================== // REGISTRAZIONE //============================================================================== //============================================================================== class TSoundInputDeviceImp { public: //ALport m_port; bool m_stopped; bool m_isRecording; bool m_oneShotRecording; long m_recordedSampleCount; TSoundTrackFormat m_currentFormat; TSoundTrackP m_st; std::set m_supportedRate; TThread::Executor m_executor; TSoundInputDeviceImp() : m_stopped(false), m_isRecording(false) // , m_port(NULL) , m_oneShotRecording(false), m_recordedSampleCount(0), m_st(0), m_supportedRate(){}; ~TSoundInputDeviceImp(){}; bool doOpenDevice(const TSoundTrackFormat &format, TSoundInputDevice::Source devType); }; bool TSoundInputDeviceImp::doOpenDevice(const TSoundTrackFormat &format, TSoundInputDevice::Source devType) { return true; } //============================================================================== class RecordTask : public TThread::Runnable { public: TSoundInputDeviceImp *m_devImp; int m_ByteToSample; RecordTask(TSoundInputDeviceImp *devImp, int numByte) : TThread::Runnable(), m_devImp(devImp), m_ByteToSample(numByte){}; ~RecordTask(){}; void run(); }; void RecordTask::run() { } //============================================================================== TSoundInputDevice::TSoundInputDevice() : m_imp(new TSoundInputDeviceImp) { } //------------------------------------------------------------------------------ TSoundInputDevice::~TSoundInputDevice() { /* if(m_imp->m_port) alClosePort(m_imp->m_port); delete m_imp; */ } //------------------------------------------------------------------------------ bool TSoundInputDevice::installed() { /* if (alQueryValues(AL_SYSTEM, AL_DEFAULT_INPUT, 0, 0, 0, 0) <=0) return false; */ return true; } //------------------------------------------------------------------------------ void TSoundInputDevice::record(const TSoundTrackFormat &format, TSoundInputDevice::Source type) { } //------------------------------------------------------------------------------ void TSoundInputDevice::record(const TSoundTrackP &st, TSoundInputDevice::Source type) { } //------------------------------------------------------------------------------ TSoundTrackP TSoundInputDevice::stop() { TSoundTrackP st; return st; } //------------------------------------------------------------------------------ double TSoundInputDevice::getVolume() { return 0.0; } //------------------------------------------------------------------------------ bool TSoundInputDevice::setVolume(double volume) { return true; } //------------------------------------------------------------------------------ bool TSoundInputDevice::supportsVolume() { return true; } //------------------------------------------------------------------------------ TSoundTrackFormat TSoundInputDevice::getPreferredFormat( TUINT32 sampleRate, int channelCount, int bitPerSample) { TSoundTrackFormat fmt; 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()); } */ } //------------------------------------------------------------------------------ bool TSoundInputDevice::isRecording() { return m_imp->m_isRecording; }