| |
| |
| #include "tsound_t.h" |
| #include "texception.h" |
| #include "tthread.h" |
| #include "tthreadmessage.h" |
| |
| #include <errno.h> |
| #include <unistd.h> |
| #include <queue> |
| #include <set> |
| |
| #include <CoreServices/CoreServices.h> |
| #include <AudioUnit/AudioUnit.h> |
| #include <CoreAudio/CoreAudio.h> |
| #include <AudioToolbox/AudioToolbox.h> |
| using namespace std; |
| |
| |
| namespace { |
| TThread::Mutex MutexOut; |
| } |
| |
| class TSoundOutputDeviceImp |
| : public std::enable_shared_from_this<TSoundOutputDeviceImp> { |
| public: |
| bool m_isPlaying; |
| bool m_looped; |
| TSoundTrackFormat m_currentFormat; |
| std::set<int> 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<TSoundOutputDeviceListener *> 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; |
| std::shared_ptr<TSoundOutputDeviceImp> imp; |
| bool isLooping; |
| MyData() |
| : entireFileBuffer(0) |
| , totalPacketCount(0) |
| , fileByteCount(0) |
| , maxPacketSize(0) |
| , packetOffset(0) |
| , byteOffset(0) |
| , sourceBuffer(0) |
| , isLooping(false) |
| , m_doNotify(true) {} |
| }; |
| |
| class PlayCompletedMsg : public TThread::Message { |
| std::set<TSoundOutputDeviceListener *> 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<TSoundOutputDeviceListener *>::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" { |
| |
| |
| |
| |
| |
| |
| |
| |
| OSStatus MyACComplexInputProc( |
| AudioConverterRef inAudioConverter, UInt32 *ioNumberDataPackets, |
| AudioBufferList *ioData, |
| AudioStreamPacketDescription **outDataPacketDescription, void *inUserData) { |
| OSStatus err = noErr; |
| UInt32 bytesCopied = 0; |
| |
| MyData *myData = static_cast<MyData *>(inUserData); |
| |
| |
| ioData->mBuffers[0].mData = NULL; |
| ioData->mBuffers[0].mDataByteSize = 0; |
| |
| { |
| |
| if (myData->imp->m_isPlaying == false) return noErr; |
| } |
| |
| |
| if (myData->packetOffset + *ioNumberDataPackets > myData->totalPacketCount) |
| *ioNumberDataPackets = myData->totalPacketCount - myData->packetOffset; |
| |
| |
| if (*ioNumberDataPackets) { |
| if (myData->sourceBuffer != NULL) { |
| free(myData->sourceBuffer); |
| myData->sourceBuffer = NULL; |
| } |
| |
| |
| bytesCopied = *ioNumberDataPackets * myData->maxPacketSize; |
| |
| myData->sourceBuffer = (void *)calloc(1, bytesCopied); |
| |
| memcpy(myData->sourceBuffer, myData->entireFileBuffer + myData->byteOffset, |
| bytesCopied); |
| |
| |
| myData->byteOffset += *ioNumberDataPackets * myData->maxPacketSize; |
| myData->packetOffset += *ioNumberDataPackets; |
| |
| ioData->mBuffers[0].mData = myData->sourceBuffer; |
| |
| |
| ioData->mBuffers[0].mDataByteSize = |
| bytesCopied; |
| } else { |
| |
| |
| |
| |
| |
| ioData->mBuffers[0].mData = NULL; |
| ioData->mBuffers[0].mDataByteSize = 0; |
| delete[] myData->entireFileBuffer; |
| myData->entireFileBuffer = 0; |
| err = noErr; |
| |
| |
| |
| |
| |
| |
| PlayCompletedMsg(myData).send(); |
| } |
| |
| return err; |
| } |
| |
| OSStatus MyFileRenderProc(void *inRefCon, |
| AudioUnitRenderActionFlags *inActionFlags, |
| const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, |
| UInt32 inNumFrames, AudioBufferList *ioData) { |
| MyData *myData = static_cast<MyData *>(inRefCon); |
| OSStatus err = noErr; |
| void *inInputDataProcUserData = inRefCon; |
| AudioStreamPacketDescription *outPacketDescription = NULL; |
| |
| |
| |
| |
| |
| |
| err = AudioConverterFillComplexBuffer(myData->converter, MyACComplexInputProc, |
| inInputDataProcUserData, &inNumFrames, |
| ioData, outPacketDescription); |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| return err; |
| } |
| |
| } |
| |
| 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; |
| |
| |
| desc.componentManufacturer = kAudioUnitManufacturer_Apple; |
| desc.componentFlags = 0; |
| desc.componentFlagsMask = 0; |
| |
| comp = FindNextComponent( |
| NULL, &desc); |
| if (comp == NULL) return false; |
| err = OpenAComponent(comp, &theOutputUnit); |
| |
| if (err) return false; |
| |
| UInt32 size; |
| Boolean outWritable; |
| UInt32 theInputBus = 0; |
| |
| err = |
| AudioUnitGetPropertyInfo(theOutputUnit, kAudioUnitProperty_StreamFormat, |
| kAudioUnitScope_Output, 0, &size, &outWritable); |
| |
| err = AudioUnitGetProperty(theOutputUnit, kAudioUnitProperty_StreamFormat, |
| kAudioUnitScope_Output, 0, &outputASBD, &size); |
| checkStatus(err); |
| |
| err = AudioUnitSetProperty(theOutputUnit, kAudioUnitProperty_StreamFormat, |
| kAudioUnitScope_Input, theInputBus, &outputASBD, |
| size); |
| checkStatus(err); |
| |
| |
| 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; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 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; |
| |
| 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(); |
| } |
| |
| |
| |
| 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); |
| CloseComponent(m_imp->theOutputUnit); |
| |
| return true; |
| } |
| |
| |
| |
| void TSoundOutputDevice::play(const TSoundTrackP &st, TINT32 s0, TINT32 s1, |
| bool loop, bool scrubbing) { |
| |
| 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 = shared_from_this(); |
| UInt32 magicCookieSize = 0; |
| |
| err = AudioConverterNew(&fileASBD, &outputASBD, &converter); |
| checkStatus(err); |
| err = AudioFileGetPropertyInfo(musicFileID, kAudioFilePropertyMagicCookieData, |
| &magicCookieSize, NULL); |
| |
| if (err == noErr) { |
| void *magicCookie = calloc(1, magicCookieSize); |
| if (magicCookie) { |
| |
| err = AudioFileGetProperty(musicFileID, kAudioFilePropertyMagicCookieData, |
| &magicCookieSize, magicCookie); |
| |
| |
| |
| if (err == noErr) { |
| err = AudioConverterSetProperty(myData->converter, |
| kAudioConverterDecompressionMagicCookie, |
| magicCookieSize, magicCookie); |
| } |
| err = noErr; |
| if (magicCookie) free(magicCookie); |
| } |
| } else |
| 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; |
| { |
| |
| m_isPlaying = true; |
| } |
| myData->isLooping = loop; |
| |
| |
| |
| |
| AURenderCallbackStruct renderCallback; |
| memset(&renderCallback, 0, sizeof(AURenderCallbackStruct)); |
| |
| renderCallback.inputProc = MyFileRenderProc; |
| renderCallback.inputProcRefCon = myData; |
| |
| |
| 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); |
| AudioConverterDispose( |
| converter); |
| return true; |
| } |
| |
| |
| |
| void TSoundOutputDevice::stop() { |
| |
| if (m_imp->m_opened == false) return; |
| |
| |
| 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 { |
| |
| return m_imp->m_isPlaying; |
| } |
| |
| |
| |
| bool TSoundOutputDevice::isLooping() { |
| |
| return m_imp->m_looped; |
| } |
| |
| |
| |
| void TSoundOutputDevice::setLooping(bool loop) { |
| |
| 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) { |
| |
| return getPreferredFormat(format.m_sampleRate, format.m_channelCount, |
| format.m_bitPerSample); |
| |
| |
| |
| |
| } |
| |
| |
| |
| |
| |
| |
| |
| class TSoundInputDeviceImp { |
| public: |
| |
| bool m_stopped; |
| bool m_isRecording; |
| bool m_oneShotRecording; |
| |
| long m_recordedSampleCount; |
| |
| TSoundTrackFormat m_currentFormat; |
| TSoundTrackP m_st; |
| std::set<int> m_supportedRate; |
| |
| TThread::Executor m_executor; |
| |
| TSoundInputDeviceImp() |
| : m_stopped(false) |
| , m_isRecording(false) |
| |
| , 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() {} |
| |
| |
| |
| bool TSoundInputDevice::installed() { |
| |
| |
| |
| |
| 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) { |
| |
| |
| |
| return getPreferredFormat(format.m_sampleRate, format.m_channelCount, |
| format.m_bitPerSample); |
| |
| |
| |
| |
| |
| |
| } |
| |
| |
| |
| bool TSoundInputDevice::isRecording() { return m_imp->m_isRecording; } |
| |