#include #include #include "OutputDistrib.h" #include "arplayer.h" void HALOverloadListener(void *inRefCon, AudioUnit ci, AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement) { Distributor *output = (Distributor *)inRefCon; if(msg){ output_map_ptr dp; string dataStr = "unknown"; // find the output in the output distributer list pthread_mutex_lock( &oMutex ); for (dp=outputMap.begin(); dp!=outputMap.end(); dp++){ if(&((*dp).second) == output) dataStr = (*dp).first; } pthread_mutex_unlock( &oMutex ); // print message ServerLoger->MakeEntry("Output- "+ dataStr + ": processor Overload"); } return; } Distributor::Distributor(void) { channelMap = NULL; devID = 0L; LastOutputTime = -1; IOOffset = 0; delay = 0; TargetLatency = 0; errorFactor = 0; outHALUnit = 0; muteGain = ~(0L); volume = 1.0; mute = 0L; } Distributor::~Distributor(void) { //clean up tearDownAudioOutput(); if(channelMap) delete [] channelMap; } unsigned char Distributor::Initialize(string passUID, int *chMap, int chMapSize, unsigned char isMaster, unsigned long inmute) { deviceUID = passUID; muteGain = inmute; devID = GetDeviceID(deviceUID.c_str()); if(!SetupAudio()) return false; if(!SetChannelMap(chMap, chMapSize)) return false; if(isMaster) SetMaster(true); else SetMaster(false); if(AudioOutputUnitStart(outHALUnit)) return false; return true; } unsigned char Distributor::SetMaster(unsigned char isMaster) { // need to check to make sure the current master isn't in it's getting data from the mixer right now... grab mMutex! if(isMaster){ if(Master != this){ if(Master != NULL) // some one is already master and it's not us... switch them to slave! Master->SetMaster(false); pthread_mutex_lock(&mMutex); Master = this; pthread_mutex_unlock(&mMutex); } }else{ if(Master == this){ // we are the master... switch us to slave. pthread_mutex_lock(&mMutex); LastOutputTime = -1; Master = NULL; pthread_mutex_unlock(&mMutex); } } return true; } unsigned char Distributor::SetChannelMap(int *Map, int size) { OSStatus err; int i; unsigned char emptyMap = true; if(channelMap) delete [] channelMap; channelMapSize = size; channelMap = new int[size]; for(i=0; i < size; i++){ channelMap[i] = Map[i]; if(Map[i] > -1) emptyMap = false; } if(emptyMap){ AudioOutputUnitStop(outHALUnit); LastOutputTime = -1; return false; } //Setup the input to output channel maping. if(Map == NULL) return false; err = AudioUnitSetProperty( outHALUnit, kAudioOutputUnitProperty_ChannelMap, kAudioUnitScope_Input, 0, Map, size * sizeof(long)); if(err) return false; AudioOutputUnitStart(outHALUnit); return true; } void Distributor::UpdateVol(void) { float newVal; float localVal; unsigned long groupGain; // set output device volume to lowest active mute group gain * current device volume newVal = 1.0; // Default, unity gain if(mute & (1L << 1)){ // cue mute enabled groupGain = muteGain & 255L; //Lower byte newVal = ((float)groupGain / 255); // make a float } if(mute & (1L << 24)){ // mute group A enabled groupGain = (muteGain >> 8); //second byte groupGain = (groupGain & 255L); // truncate to lower byte localVal = ((float)groupGain / 255); // make a float if(localVal < newVal){ // lowest gain so far, make the current gain newVal = localVal; } } if(mute & (1L << 25)){ // mute group B enabled groupGain = (muteGain >> 8); //second byte groupGain = (groupGain & 255L); // truncate to lower byte localVal = ((float)groupGain / 255); // make a float if(localVal < newVal){ // lowest gain so far, make the current gain newVal = localVal; } } if(mute & (1L << 26)){ // mute group C enabled groupGain = (muteGain >> 8); //second byte groupGain = (groupGain & 255L); // truncate to lower byte localVal = ((float)groupGain / 255); // make a float if(localVal < newVal){ // lowest gain so far, make the current gain newVal = localVal; } } newVal = newVal * volume; AudioUnitSetParameter(outHALUnit, kHALOutputParam_Volume, kAudioUnitScope_Global, 0, newVal, 0); } void Distributor::SetVolume(float vol) { volume = vol; UpdateVol(); } void Distributor::SetMute(unsigned long newMute) { mute = newMute; UpdateVol(); } unsigned char Distributor::SetDelay(float sec) { if(sec < 0) return false; if(sec > 10) return false; delay = sec * outFormat.mSampleRate; return true; } float Distributor::GetDelay(void) { return delay / outFormat.mSampleRate; } OSStatus Distributor::SetupAudio(void) { OSStatus err; AURenderCallbackStruct rcbs; unsigned long size; ComponentDescription desc; Component comp; desc.componentType = kAudioUnitType_Output; desc.componentSubType = kAudioUnitSubType_HALOutput; desc.componentManufacturer = kAudioUnitManufacturer_Apple; desc.componentFlags = 0; desc.componentFlagsMask = 0; if(comp = FindNextComponent(0, &desc)){ err = OpenAComponent(comp, &outHALUnit); if (err) return false; }else{ return false; } size = sizeof(AudioDeviceID); //Set the in Device to the AUHAL. err = AudioUnitSetProperty( outHALUnit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, 0, &devID, size); if(err) return false; // get the mixer output audio data format size = sizeof(outFormat); err = AudioUnitGetProperty( mixer, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &outFormat, &size ); if (err) return false; // set AUHAL input to mixer output format err = AudioUnitSetProperty( outHALUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &outFormat, sizeof(outFormat)); if (err) return false; rcbs.inputProc = &Distributor ::OutputCallback; rcbs.inputProcRefCon = this; //Setup the input callback to our ring buffer filler routine. err = AudioUnitSetProperty( outHALUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &rcbs, sizeof(rcbs)); if(err) return false; // set up notification of processor overload err = AudioUnitAddPropertyListener(outHALUnit, kAudioDeviceProcessorOverload, HALOverloadListener, this); if (err) return false; //Get the size of the HALoutput device safty margin size = sizeof(safteyFrames); AudioUnitGetProperty( outHALUnit, kAudioDevicePropertySafetyOffset, kAudioUnitScope_Global, 0, &safteyFrames, &size); if(err) return false; //Get the size of the HALoutput device buffer size = sizeof(outbufferSizeFrames); err = AudioUnitGetProperty( outHALUnit, kAudioDevicePropertyBufferFrameSize, kAudioUnitScope_Global, 0, &outbufferSizeFrames, &size); if(err) return false; /* //Get the size range the HALoutput device buffer AudioValueRange buffRange; size = sizeof(buffRange); err = AudioUnitGetProperty( outHALUnit, kAudioDevicePropertyBufferFrameSizeRange, kAudioUnitScope_Global, 0, &buffRange, &size); if(err) return false; outbufferSizeFrames = (unsigned long)(outbufferSizeFrames * latency_factor); if(outbufferSizeFrames > (unsigned long int)buffRange.mMaximum) outbufferSizeFrames = (unsigned long int)buffRange.mMaximum; if(outbufferSizeFrames < (unsigned long int)buffRange.mMinimum) outbufferSizeFrames = (unsigned long int)buffRange.mMinimum; //set the buffer size to it new value err = AudioUnitSetProperty( outHALUnit, kAudioDevicePropertyBufferFrameSize, kAudioUnitScope_Global, 0, &outbufferSizeFrames, sizeof(outbufferSizeFrames)); if(err) return false; //Get the size of the HALoutput device buffer size = sizeof(outbufferSizeFrames); err = AudioUnitGetProperty( outHALUnit, kAudioDevicePropertyBufferFrameSize, kAudioUnitScope_Global, 0, &outbufferSizeFrames, &size); if(err) return false; */ // initialize the HALoutput AU err = AudioUnitInitialize(outHALUnit); if (err) return false; return true; } void Distributor::tearDownAudioOutput(void) { AudioOutputUnitStop(outHALUnit); AudioUnitUninitialize(outHALUnit); CloseComponent(outHALUnit); } OSStatus Distributor::OutputCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *TimeStamp, unsigned long inBusNumber, unsigned long inNumberFrames, AudioBufferList * ioData) { unsigned int i; AudioTimeStamp mixerTimeStamp; double actualLatency, error, adjust, localMasterTime, dither; OSStatus err = noErr; Distributor *parent = (Distributor *)inRefCon; if(parent == Master){ pthread_mutex_lock(&mMutex); // we are the master output device! It's our job to pull on the mixer and copy results to the master ring buffer for others to use mixerTimeStamp = *TimeStamp; mixerTimeStamp.mSampleTime = MasterOutputTime; //Get the mixer audio data err= AudioUnitRender( mixer, ioActionFlags, &mixerTimeStamp, 0, inNumberFrames, //# of frames requested ioData); if(err == noErr){ // store it in the master buffer at the mixer time stamp BusBuffer->Store(ioData, inNumberFrames, (long long int)mixerTimeStamp.mSampleTime); // update the masterTime sample counter MasterOutputTime = MasterOutputTime + inNumberFrames; // wake all threads we are pushing output through (i.e. record/encoders) pthread_cond_broadcast(&pushSemaphore); if(parent->delay){ if(BusBuffer->Fetch(ioData, inNumberFrames, (long long int)(mixerTimeStamp.mSampleTime - parent->delay), 0, NULL) != kAudioRingBufferError_OK) goto silence; } }else{ if(msg){ fprintf(stdout, " MESSAGE: Mixer render error #%lu\n", err); fprintf(stdout, constPrompt); fflush(stdout); } } pthread_mutex_unlock(&mMutex); }else{ // we are a slave output device! // First time through figure out the delta between the end of the ring buffer and the local sample time end of the requested buffer localMasterTime = MasterOutputTime; if (parent->LastOutputTime < 0.) { parent->LastOutputTime = TimeStamp->mSampleTime; unsigned int larger = parent->outbufferSizeFrames; if(larger < inNumberFrames) larger = inNumberFrames; parent->TargetLatency = larger + parent->safteyFrames + 4; // Slave output... IOOffset includes latency parent->IOOffset = TimeStamp->mSampleTime + inNumberFrames + parent->TargetLatency - localMasterTime; parent->errorFactor = 0; } if (MasterOutputTime == 0.) // master output has not filed buffer yet goto silence; // device sync control algorithm actualLatency = localMasterTime - (TimeStamp->mSampleTime + inNumberFrames - parent->IOOffset); error = actualLatency - parent->TargetLatency; if(fabs(error) > parent->TargetLatency){ // if we get VERY far off, skip by what ever the error is... this will be audible as a skip. adjust = error; dither = 0; parent->errorFactor = 0; }else{ // avarage the error... to avarage out the buffer size quantization float avrFactor = 1.0 / inNumberFrames; parent->errorFactor = (parent->errorFactor * (1.0 - avrFactor)) + (error * avrFactor); // create a dither pointer between 0 and inNumberFrames to randomly pick were in the buffer a sample (if any) is added or removed dither = (float)rand() / RAND_MAX; dither = floor((inNumberFrames - 2) * dither); // limit/quantize to +1, 0 or -1 sample change adjust = 0; dither = 0; if(parent->errorFactor > 1){ adjust = 1; } if(parent->errorFactor < -1){ adjust = -1; } parent->errorFactor = parent->errorFactor - adjust; if(dither > 0){ //copy the data from the buffer starting at this timestamp less the offset time if(BusBuffer->Fetch(ioData, (long unsigned int)dither, (long long int)(TimeStamp->mSampleTime - parent->IOOffset - parent->delay), 0, NULL) != kAudioRingBufferError_OK) goto silence; } } // adjust offset accordingly... parent->IOOffset = parent->IOOffset - adjust; //copy the data from the buffer starting at this timestamp less the offset time if(BusBuffer->Fetch(ioData, (long unsigned int)(inNumberFrames - dither), (long long int)(TimeStamp->mSampleTime - parent->IOOffset - parent->delay), (long unsigned int)dither, NULL) != kAudioRingBufferError_OK) goto silence; } return noErr; silence: // clear the actual buffers for(i = 0 ; i < ioData->mNumberBuffers ; i++){ bzero(ioData->mBuffers[i].mData, ioData->mBuffers[i].mDataByteSize); } return noErr; }