//-------------------------------------------------------------------------------- // // File: AppleUSBAudioEngine.cpp // // Contains: Support for the USB Audio Class Stream Interface. // This includes support for setting sample rate (via // a sample rate endpoint control and appropriate // sized construction of USB isochronous frame lists), // channel depth selection and bit depth selection. // // Technology: Mac OS X // //-------------------------------------------------------------------------------- #define DEBUGTIMESTAMPS FALSE #define DEBUGZEROTIME FALSE #define FIXEDSIZEPACKETS TRUE #define NEWUSB FALSE #include "AppleUSBAudioCommon.h" #include "AppleUSBAudioDevice.h" #include "AppleUSBAudioLevelControl.h" #include "AppleUSBAudioMuteControl.h" #include "AppleUSBAudioClip.h" #include "AppleiSubEngine.h" #include "AppleUSBAudioEngine.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #define super IOAudioEngine OSDefineMetaClassAndStructors(AppleUSBAudioEngine, IOAudioEngine) #pragma mark -IOKit Routines- void AppleUSBAudioEngine::free () { UInt32 i; debug2IOLog ("+AppleUSBAudioEngine[%p]::free ()\n", this); /* Moving to stop because the engine might be leaked and we need to remove the notifier even in that case. if (NULL != iSubEngineNotifier) { iSubEngineNotifier->remove (); iSubEngineNotifier = NULL; } */ for (i = 0; i < numFrameLists; i++) { if (NULL != soundBuffer) { if (soundBuffer[i]) { soundBuffer[i]->release (); soundBuffer[i] = NULL; } IOFree (soundBuffer, numFrameLists * sizeof (IOMemoryDescriptor *)); soundBuffer = NULL; } } if (NULL != usbCompletion) { IOFree (usbCompletion, numFrameLists * sizeof (IOUSBIsocCompletion)); usbCompletion = NULL; } if (NULL != theFrames) { IOFree (theFrames, numFramesPerList * numFrameListsToQueue * sizeof (IOUSBIsocFrame)); theFrames = NULL; } if (neededSampleRate) { neededSampleRate->release (); neededSampleRate = NULL; } if (signal) { signal->release (); } if (theStartBufferDescriptor) { theStartBufferDescriptor->release (); theStartBufferDescriptor = NULL; } if (theStartBuffer) { IOFree (theStartBuffer, theStartBufferSize); theStartBuffer = NULL; } if (streamNotifier) { streamNotifier->remove (); streamNotifier = 0; } if (interfaceVendor) { interfaceVendor->release (); interfaceVendor = 0; } if (interfaceProduct) { interfaceProduct->release (); interfaceProduct = 0; } if (deviceReleaseNumber) { deviceReleaseNumber->release (); deviceReleaseNumber = 0; } if (configurationValue) { configurationValue->release (); configurationValue = 0; } if (interfaceNumber) { interfaceNumber->release (); interfaceNumber = 0; } if (readBuffer) { IOFree (readBuffer, readFrameListSize * numFrameLists); readBuffer = NULL; } if (NULL != getSampleBuffer ()) { IOFree (getSampleBuffer (), getSampleBufferSize ()); } if (NULL != lowFreqSamples) { IOFree (lowFreqSamples, getSampleBufferSize () * 2); } if (NULL != highFreqSamples) { IOFree (highFreqSamples, getSampleBufferSize () * 2); } if (NULL != iSubEngineNotifier) { iSubEngineNotifier->remove (); iSubEngineNotifier = NULL; } if (NULL != iSubAttach) { iSubAttach->release (); iSubAttach = NULL; } if (mainStream) { mainStream->release (); mainStream = NULL; } super::free (); debug2IOLog ("-AppleUSBAudioEngine[%p]::free()\n", this); } bool AppleUSBAudioEngine::init (OSDictionary *properties) { Boolean result; debug2IOLog("+AppleUSBAudioEngine[%p]::init ()\n", this); result = FALSE; FailIf (FALSE == super::init (NULL), Exit); // Change this to use defines from the IOAudioFamily when they are available setProperty ("IOAudioStreamSampleFormatByteOrder", "Little Endian"); signal = IOSyncer::create (FALSE); result = TRUE; srcPhase = 1.0; // aml 3.4.02 srcState = 0.0; // aml 3.6.02 justResetClipPosition = FALSE; // aml 3.29.02 Exit: debug2IOLog("-AppleUSBAudioEngine[%p]::init ()\n", this); return result; } bool AppleUSBAudioEngine::requestTerminate (IOService * provider, IOOptionBits options) { bool result; debug4IOLog ("+AppleUSBAudioEngine[%p]::requestTerminate (%p, %x)\n", this, provider, options); // if interface or audio device if (usbAudioDevice == provider || streamInterface == provider) { result = TRUE; // it is OK to terminate us } else { result = FALSE; // don't terminate us } debug4IOLog ("-AppleUSBAudioEngine[%p]::requestTerminate (%p, %x)\n", this, provider, options); return result; } bool AppleUSBAudioEngine::start (IOService * provider) { IONotifier * audioDeviceNotifier; bool resultCode; USBAudioConfigObject * usbAudio; debug3IOLog ("+AppleUSBAudioEngine[%p]::start (%p)\n", this, provider); resultCode = FALSE; // This snippet of code goes with the audiodevicePublished function // we wait until the Stream interface finds a partner. audioDeviceNotifier = addNotification (gIOPublishNotification, serviceMatching ("AppleUSBAudioDevice"), (IOServiceNotificationHandler)&audioDevicePublished, this, NULL); signal->wait (FALSE); audioDeviceNotifier->remove (); signal->reinit (); FailIf (NULL == usbAudioDevice, Exit); // If this is an iSub, we need to not go any further because we don't support it in this driver usbAudio = usbAudioDevice->GetUSBAudioConfigObject (); FailIf (NULL == usbAudio, Exit); // This will cause the driver to not load on any device that has _only_ a low frequency effect output terminal FailIf (usbAudio->GetNumOutputTerminals (0, 0) == 1 && usbAudio->GetIndexedOutputTerminalType (0, 0, 0) == OUTPUT_LOW_FREQUENCY_EFFECTS_SPEAKER, Exit); resultCode = super::start (provider, usbAudioDevice); debug4IOLog ("-%d = AppleUSBAudioEngine[%p]::start (%p)\n", resultCode, this, provider); Exit: return resultCode; } void AppleUSBAudioEngine::stop (IOService * provider) { debug3IOLog("+AppleUSBAudioEngine[%p]::stop (%p)\n", this, provider); if (NULL != iSubEngineNotifier) { iSubEngineNotifier->remove (); iSubEngineNotifier = NULL; } if (NULL != iSubEngine) { iSubTeardownConnection (); } if (usbAudioDevice) { usbAudioDevice->release (); usbAudioDevice = 0; } if (thePipe) { thePipe->release (); thePipe = NULL; } if (theAssociatedPipe) { theAssociatedPipe->release (); theAssociatedPipe = NULL; } if (streamInterface) { streamInterface->close (this); streamInterface = NULL; } super::stop (provider); debug4IOLog ("-AppleUSBAudioEngine[%p]::stop (%p) - rc=%ld\n", this, provider, getRetainCount()); } bool AppleUSBAudioEngine::terminate (IOOptionBits options) { bool shouldTerminate; bool result; result = TRUE; shouldTerminate = TRUE; debug2IOLog ("+AppleUSBAudioEngine[%p]::terminate ()\n", this); if (shouldTerminate) { result = super::terminate (options); } debug2IOLog ("-AppleUSBAudioEngine[%p]::terminate ()\n", this); return result; } #pragma mark -USB Audio driver- IOAudioEngineState AppleUSBAudioEngine::_setState(IOAudioEngineState newState) { IOAudioEngineState oldState; oldState = super::setState(newState); if (notifyDeviceOfStop && (newState != oldState) && (newState == kIOAudioEngineStopped)) { FailIf (NULL == usbAudioDevice, Exit); usbAudioDevice->streamStopped(this); } Exit: return oldState; } IOReturn AppleUSBAudioEngine::AddAvailableFormatsFromDevice (USBAudioConfigObject *usbAudio) { IOAudioStreamFormat streamFormat; IOAudioStreamFormatExtension streamFormatExtension; IOAudioSampleRate lowSampleRate; IOAudioSampleRate highSampleRate; UInt32 * sampleRates; IOReturn result; UInt16 numAltInterfaces; UInt8 numSampleRates; UInt8 altInterfaceIndx; UInt8 rateIndx; result = kIOReturnError; FailIf (NULL == usbAudio, Exit); FailIf (NULL == mainStream, Exit); numAltInterfaces = usbAudio->GetNumAltStreamInterfaces (ourInterfaceNumber); debug3IOLog ("There are %d alternate stream interfaces on interface %d\n", numAltInterfaces, ourInterfaceNumber); // Find all of the available formats on the device. for (altInterfaceIndx = 1; altInterfaceIndx < numAltInterfaces; altInterfaceIndx++) { numSampleRates = usbAudio->GetNumSampleRates (ourInterfaceNumber, altInterfaceIndx); sampleRates = usbAudio->GetSampleRates (ourInterfaceNumber, altInterfaceIndx); streamFormat.fNumChannels = usbAudio->GetNumChannels (ourInterfaceNumber, altInterfaceIndx); streamFormat.fBitDepth = usbAudio->GetSampleSize (ourInterfaceNumber, altInterfaceIndx); streamFormat.fBitWidth = usbAudio->GetSubframeSize (ourInterfaceNumber, altInterfaceIndx) * 8; streamFormat.fAlignment = kIOAudioStreamAlignmentLowByte; streamFormat.fByteOrder = kIOAudioStreamByteOrderLittleEndian; streamFormat.fDriverTag = (ourInterfaceNumber << 16) | altInterfaceIndx; streamFormatExtension.fVersion = kFormatExtensionCurrentVersion; streamFormatExtension.fFlags = 0; streamFormatExtension.fFramesPerPacket = 1; streamFormatExtension.fBytesPerPacket = usbAudio->GetNumChannels (ourInterfaceNumber, altInterfaceIndx) * usbAudio->GetSubframeSize (ourInterfaceNumber, altInterfaceIndx); switch (usbAudio->GetFormat (ourInterfaceNumber, altInterfaceIndx)) { case PCM: streamFormat.fSampleFormat = kIOAudioStreamSampleFormatLinearPCM; streamFormat.fNumericRepresentation = kIOAudioStreamNumericRepresentationSignedInt; streamFormat.fIsMixable = TRUE; break; case AC3: // just starting to stub something in for AC-3 support debugIOLog ("variable bit rate AC-3 audio format type!\n"); streamFormat.fSampleFormat = kIOAudioStreamSampleFormatAC3; streamFormat.fIsMixable = FALSE; streamFormat.fNumChannels = 6; streamFormat.fNumericRepresentation = kIOAudioStreamNumericRepresentationSignedInt; streamFormat.fBitDepth = 16; streamFormat.fBitWidth = 16; streamFormat.fByteOrder = kIOAudioStreamByteOrderBigEndian; streamFormatExtension.fFlags = usbAudio->GetAC3BSID (ourInterfaceNumber, altInterfaceIndx); // streamFormatExtension.fFramesPerPacket = usbAudio->GetSamplesPerFrame (ourInterfaceNumber, altInterfaceIndx); streamFormatExtension.fFramesPerPacket = 1536; // streamFormatExtension.fBytesPerPacket = ((usbAudio->GetMaxBitRate (ourInterfaceNumber, altInterfaceIndx) * 1024 / 8) + 500) / 1000; streamFormatExtension.fBytesPerPacket = streamFormatExtension.fFramesPerPacket * streamFormat.fNumChannels * usbAudio->GetSubframeSize (ourInterfaceNumber, altInterfaceIndx); break; case IEC1937_AC3: debugIOLog ("IEC1937 AC-3 audio format type!\n"); streamFormat.fSampleFormat = kIOAudioStreamSampleFormat1937AC3; streamFormat.fNumericRepresentation = kIOAudioStreamNumericRepresentationSignedInt; streamFormat.fIsMixable = FALSE; streamFormatExtension.fFramesPerPacket = 1536; streamFormatExtension.fBytesPerPacket = streamFormatExtension.fFramesPerPacket * streamFormat.fNumChannels * usbAudio->GetSubframeSize (ourInterfaceNumber, altInterfaceIndx); break; default: debug2IOLog ("interface format = %x\n", usbAudio->GetFormat (ourInterfaceNumber, altInterfaceIndx)); debugIOLog ("interface doesn't support a format that we can deal with, so we're not making it available\n"); continue; // skip this alternate interface } debug3IOLog ("Interface %d, Alt %d has a ", ourInterfaceNumber, altInterfaceIndx); debug2IOLog ("%d bit interface, ", streamFormat.fBitDepth); debug2IOLog ("%d channels, and ", streamFormat.fNumChannels); debug2IOLog ("%d sample rates, which are:\n", numSampleRates); if (numSampleRates) { for (rateIndx = 0; rateIndx < numSampleRates; rateIndx++) { debug2IOLog (" %d", sampleRates[rateIndx]); lowSampleRate.whole = sampleRates[rateIndx]; lowSampleRate.fraction = 0; mainStream->addAvailableFormat (&streamFormat, &streamFormatExtension, &lowSampleRate, &lowSampleRate); if (kIOAudioStreamSampleFormatLinearPCM == streamFormat.fSampleFormat) { streamFormat.fIsMixable = FALSE; mainStream->addAvailableFormat (&streamFormat, &streamFormatExtension, &lowSampleRate, &lowSampleRate); } } debugIOLog ("\n"); } else if (sampleRates) { debug3IOLog (" %d to %d\n", sampleRates[0], sampleRates[1]); lowSampleRate.whole = sampleRates[0]; lowSampleRate.fraction = 0; highSampleRate.whole = sampleRates[1]; highSampleRate.fraction = 0; mainStream->addAvailableFormat (&streamFormat, &streamFormatExtension, &lowSampleRate, &highSampleRate); if (kIOAudioStreamSampleFormatLinearPCM == streamFormat.fSampleFormat) { streamFormat.fIsMixable = FALSE; mainStream->addAvailableFormat (&streamFormat, &streamFormatExtension, &lowSampleRate, &lowSampleRate); } } } result = kIOReturnSuccess; Exit: return result; } bool AppleUSBAudioEngine::audioDevicePublished (AppleUSBAudioEngine * audioEngine, void * ref, IOService * newService) { AppleUSBAudioDevice * audioDevice; IOUSBInterface * thisControlInterface; IOUSBInterface * thisStreamInterface; bool resultCode; debug4IOLog ("+AppleUSBAudioEngine::audioDevicePublished (%p, %p, %p)\n", audioEngine, (UInt32*)ref, newService); resultCode = FALSE; // Assume failure // This one is a trick : because we are not sure in which order the parts of the // USB driver will be loaded, we have to wait until the stream interface finds a corresponding // USB partner. This is the test that is telling us when we can stop waiting FailIf (NULL == audioEngine, Exit); FailIf (NULL == newService, Exit); audioDevice = OSDynamicCast (AppleUSBAudioDevice, newService); FailIf (NULL == audioDevice, Exit); thisControlInterface = OSDynamicCast (IOUSBInterface, audioDevice->getProvider ()); FailIf (NULL == thisControlInterface, Exit); thisStreamInterface = OSDynamicCast (IOUSBInterface, audioEngine->getProvider ()); FailIf (NULL == thisStreamInterface, Exit); if (thisControlInterface->GetDevice () == thisStreamInterface->GetDevice ()) { debug4IOLog ("++AppleUSBAudioEngine[%p]: found device (%p) for Audio Engine (%p)\n", audioEngine, audioDevice, audioEngine); audioEngine->usbAudioDevice = audioDevice; audioEngine->signal->signal (kIOReturnSuccess, FALSE); resultCode = TRUE; // Success! } Exit: debug4IOLog ("-AppleUSBAudioEngine::audioDevicePublished (%p, %p, %p)\n", audioEngine, (UInt32 *)ref, newService); return resultCode; } UInt32 AppleUSBAudioEngine::CalculateNumSamplesPerFrameList (UInt32 sampleRate, UInt32 numFrames, UInt32 theNumFrameLists) { UInt32 numSamples; UInt32 i; UInt32 sampleNum; UInt32 sum; UInt16 samplesThisFrame; IOFixed fixedSampleRate; numSamples = 0; fixedSampleRate = IOUFixedDivide (sampleRate << 16, 1000 << 16); // we want a number like 44.101 to represent 44101Hz samplesThisFrame = fixedSampleRate >> 16; sampleNum = 2; sum = samplesThisFrame; for (i = 0; i < numFrames * theNumFrameLists; i++) { numSamples += samplesThisFrame; samplesThisFrame = (IOUFixedMultiply (fixedSampleRate, sampleNum << 16) >> 16) - sum; sum += samplesThisFrame; sampleNum++; if (1000 == sampleNum) { samplesThisFrame = fixedSampleRate >> 16; sampleNum = 2; sum = samplesThisFrame; } } return numSamples; } void AppleUSBAudioEngine::CalculateSamplesPerFrame (UInt32 sampleRate, UInt16 * averageFrameSamples, UInt16 * additionalSampleFrameFreq) { UInt32 divisor; *averageFrameSamples = sampleRate / 1000; divisor = (sampleRate % 1000); if (divisor) *additionalSampleFrameFreq = 1000 / divisor; else *additionalSampleFrameFreq = 0; } IOReturn AppleUSBAudioEngine::CheckForAssociatedEndpoint (USBAudioConfigObject *usbAudio) { IOUSBFindEndpointRequest associatedEndpoint; IOReturn result; UInt8 assocEndpoint; UInt8 address; UInt8 syncType; result = kIOReturnSuccess; theAssociatedPipe = NULL; address = usbAudio->GetIsocEndpointAddress (ourInterfaceNumber, alternateInterfaceID, direction); syncType = usbAudio->GetIsocEndpointSyncType (ourInterfaceNumber, alternateInterfaceID, address); if (kAsynchSyncType == syncType) { debug2IOLog ("checking endpoint %d for an associated endpoint\n", address); assocEndpoint = usbAudio->GetIsocAssociatedEndpointAddress (ourInterfaceNumber, alternateInterfaceID, address); if (assocEndpoint != 0) { debugIOLog ("This endpoint has an associated synch endpoint!\n"); refreshInterval = usbAudio->GetIsocAssociatedEndpointRefreshInt (ourInterfaceNumber, alternateInterfaceID, assocEndpoint); debug2IOLog ("The refresh interval is %d\n", refreshInterval); framesUntilRefresh = 1 << (10 - refreshInterval); // the same as 2^(10-refreshInterval) // The hardware might not need to be updated as often as we were planning on (currently we queue 10 lists with 64ms of audio each). // If they don't need to be updated that often, then just keep everything at 64ms intervals to keep things standard. if (framesUntilRefresh < numFramesPerList) numFramesPerList = framesUntilRefresh; // It needs to be updated more often, so run as the device requests. associatedEndpoint.type = kUSBIsoc; associatedEndpoint.direction = direction == kUSBOut ? kUSBIn : kUSBOut; // The associated endpoint goes the opposite direction of the primary endpoint associatedEndpoint.maxPacketSize = 3; // The sample rate is always 3 bytes associatedEndpoint.interval = 0xFF; // don't care theAssociatedPipe = streamInterface->FindNextPipe (NULL, &associatedEndpoint); FailWithAction (NULL == theAssociatedPipe, result = kIOReturnError, Exit); theAssociatedPipe->retain (); } else { debugIOLog ("Couldn't find the associated synch endpoint!\n"); } } else { debugIOLog ("This endpoint does not have an associated synch endpoint\n"); } Exit: return result; } IOReturn AppleUSBAudioEngine::clipOutputSamples (const void *mixBuf, void *sampleBuf, UInt32 firstSampleFrame, UInt32 numSampleFrames, const IOAudioStreamFormat *streamFormat, IOAudioStream *audioStream) { void * iSubBuffer = NULL; UInt32 iSubBufferLen = 0; UInt32 sampleRate; SInt32 offsetDelta; SInt32 safetyOffset; IOReturn result; iSubAudioFormatType iSubFormat; // aml 3.1.02 result = kIOReturnError; #if 0 #ifdef DEBUGLOG UInt32 currentLocation = getCurrentSampleFrame (); if (firstSampleFrame <= currentLocation && (firstSampleFrame + numSampleFrames) > currentLocation) { IOLog ("!!!!!!!!!!Buffer problems!!!!!!!!!\n"); IOLog ("currentLocation = 0x%lX, firstSampleFrame = 0x%lX, numSampleFrames = 0x%lX\n", currentLocation, firstSampleFrame, numSampleFrames); } else if (((currentLocation + 96) - firstSampleFrame) < numSampleFrames) { IOLog("!!!!!! Possible buffer problem !!!!!!\n"); IOLog ("currentLocation = 0x%lX, firstSampleFrame = 0x%lX, numSampleFrames = 0x%lX\n", currentLocation, firstSampleFrame, numSampleFrames); IOLog ("overlap = 0x%X [%d]\n", (currentLocation + 96) - firstSampleFrame, (currentLocation + 96) - firstSampleFrame); } #endif #endif if (TRUE == streamFormat->fIsMixable) { if (NULL != iSubBufferMemory && NULL != iSubEngine) { iSubBufferLen = iSubBufferMemory->getLength (); iSubBuffer = (void*)iSubBufferMemory->getVirtualSegment (0, &iSubBufferLen); // (iSubBufferLen / 2) is because iSubBufferOffset is in UInt16s so convert iSubBufferLen to UInt16 length iSubBufferLen = iSubBufferLen / 2; sampleRate = getSampleRate()->whole; // aml 3.1.02 iSubFormat.altInterface = iSubEngine->GetAltInterface(); iSubFormat.numChannels = iSubEngine->GetNumChannels(); iSubFormat.bytesPerSample = iSubEngine->GetBytesPerSample(); iSubFormat.outputSampleRate = iSubEngine->GetSampleRate(); // aml 3.29.02 begin move - moved out of sync check to before offset calculation // Detect being out of sync with the iSub // aml 3.29.02 updated to match AOA #if ABORT_PIPE_ON_START if (needToSync == FALSE && previousClippedToFrame == firstSampleFrame && 0x0 != iSubEngine->GetCurrentLoopCount ()) { #else if (needToSync == FALSE && previousClippedToFrame == firstSampleFrame && 0xFFFFFFFF != iSubEngine->GetCurrentLoopCount ()) { #endif // aml 3.29.02 reordered and ajdusted these checks to match AOA // aml - make the reader/writer check more strict - this helps get rid of long term crunchy iSub audio // the reader is now not allowed within one frame (one millisecond of audio) of the writer safetyOffset = iSubBufferOffset - ((iSubFormat.outputSampleRate) / 1000); // 6 samples at 6kHz if (safetyOffset < 0) { safetyOffset += iSubBufferLen; } if (iSubLoopCount == iSubEngine->GetCurrentLoopCount () && safetyOffset < (SInt32)(iSubEngine->GetCurrentByteCount () / 2)) { #if DEBUGLOG IOLog ("****iSub is in front of write head iSubBufferOffset = %ld, iSubEngine->GetCurrentByteCount () / 2 = %ld\n", iSubBufferOffset, iSubEngine->GetCurrentByteCount () / 2); #endif needToSync = TRUE; startiSub = TRUE; } else if (iSubLoopCount > (iSubEngine->GetCurrentLoopCount () + 1)) { #if DEBUGLOG IOLog ("****looped more than the iSub iSubLoopCount = %ld, iSubEngine->GetCurrentLoopCount () = %ld\n", iSubLoopCount, iSubEngine->GetCurrentLoopCount ()); #endif needToSync = TRUE; startiSub = TRUE; } else if (iSubLoopCount < iSubEngine->GetCurrentLoopCount ()) { #if DEBUGLOG IOLog ("****iSub is ahead of us iSubLoopCount = %ld, iSubEngine->GetCurrentLoopCount () = %ld\n", iSubLoopCount, iSubEngine->GetCurrentLoopCount ()); #endif needToSync = TRUE; startiSub = TRUE; } else if (iSubLoopCount == iSubEngine->GetCurrentLoopCount () && iSubBufferOffset > ((SInt32)((iSubEngine->GetCurrentByteCount() + (((iSubFormat.outputSampleRate)/1000 * NUM_ISUB_FRAME_LISTS_TO_QUEUE * NUM_ISUB_FRAMES_PER_LIST) * iSubFormat.bytesPerSample * iSubFormat.numChannels)) / 2))) { // aml 3.27.02, this is the right number here (buffersize was 2x too large). This number should come eventually from the iSub engine reporting it's maximum number of queued bytes. #if DEBUGLOG IOLog ("****iSub is too far behind write head iSubBufferOffset = %ld, (iSubEngine->GetCurrentByteCount () / 2 + iSubBufferLen) = %ld\n", iSubBufferOffset, (iSubEngine->GetCurrentByteCount() / 2 + iSubBufferLen)); #endif needToSync = TRUE; startiSub = TRUE; } } // aml 3.29.02 end move if (FALSE == needToSync && previousClippedToFrame != firstSampleFrame && !(previousClippedToFrame == getNumSampleFramesPerBuffer () && firstSampleFrame == 0)) { #if DEBUGLOG IOLog ("iSubBufferOffset was %ld\n", iSubBufferOffset); #endif if (firstSampleFrame < previousClippedToFrame) { // We've wrapped around the buffer // don't multiply by bit width because iSubBufferOffset is a UInt16 buffer pointer, not a UInt8 buffer pointer // aml 3.29.02 flipped +/- to match AOA offsetDelta = (getNumSampleFramesPerBuffer () - firstSampleFrame + previousClippedToFrame) * iSubEngine->GetNumChannels(); } else { offsetDelta = (firstSampleFrame - previousClippedToFrame) * iSubEngine->GetNumChannels(); } // aml 3.21.02, adjust for new sample rate offsetDelta = (offsetDelta * 1000) / ((sampleRate * 1000) / iSubFormat.outputSampleRate); iSubBufferOffset += offsetDelta; #if DEBUGLOG IOLog ("clip to point was %ld, now %ld (delta = %ld)\n", previousClippedToFrame, firstSampleFrame, offsetDelta); IOLog ("iSubBufferOffset is now %ld\n", iSubBufferOffset); #endif if (iSubBufferOffset > (SInt32)iSubBufferLen) { // Our calculated spot has actually wrapped around the iSub's buffer. iSubLoopCount += iSubBufferOffset / iSubBufferLen; iSubBufferOffset = iSubBufferOffset % iSubBufferLen; #if DEBUGLOG IOLog ("iSubBufferOffset > iSubBufferLen, iSubBufferOffset is now %ld\n", iSubBufferOffset); #endif } else if (iSubBufferOffset < 0) { iSubBufferOffset += iSubBufferLen; #if DEBUGLOG IOLog ("iSubBufferOffset < 0, iSubBufferOffset is now %ld\n", iSubBufferOffset); #endif } } // aml 3.29.02 added this check to match AOA if (TRUE == justResetClipPosition) { justResetClipPosition = FALSE; needToSync = FALSE; startiSub = FALSE; } // sync up with iSub if (TRUE == needToSync) { needToSync = FALSE; srcPhase = 1.0; // aml 3.4.02 srcState = 0.0; // aml 3.6.02 // start the filter over again since old filter state is invalid filterState.xl_1 = 0.0; filterState.xr_1 = 0.0; filterState.xl_2 = 0.0; filterState.xr_2 = 0.0; filterState.yl_1 = 0.0; filterState.yr_1 = 0.0; filterState.yl_2 = 0.0; filterState.yr_2 = 0.0; // aml 2.14.02 added for 4th order filter filterState2.xl_1 = 0.0; filterState2.xr_1 = 0.0; filterState2.xl_2 = 0.0; filterState2.xr_2 = 0.0; filterState2.yl_1 = 0.0; filterState2.yr_1 = 0.0; filterState2.yl_2 = 0.0; filterState2.yr_2 = 0.0; // aml 2.18.02 added for 4th order filter phase compensator phaseCompState.xl_1 = 0.0; phaseCompState.xr_1 = 0.0; phaseCompState.xl_2 = 0.0; phaseCompState.xr_2 = 0.0; phaseCompState.yl_1 = 0.0; phaseCompState.yr_1 = 0.0; phaseCompState.yl_2 = 0.0; phaseCompState.yr_2 = 0.0; #if ABORT_PIPE_ON_START // aml 4.25.02 wipe out the iSub buffer, changed due to moving zeroing of iSub buffer in AUA write handler when aborting the pipe bzero(iSubBuffer, iSubBufferLen); #endif // aml 3.21.02, moving this offset calc code in from AOA (replaces one line above) UInt32 curSampleFrame = getCurrentSampleFrame (); if (firstSampleFrame < curSampleFrame) { offsetDelta = (getNumSampleFramesPerBuffer () - curSampleFrame + firstSampleFrame) * iSubEngine->GetNumChannels(); } else { offsetDelta = (firstSampleFrame - curSampleFrame) * iSubEngine->GetNumChannels(); } // aml 3.21.02, adjust for new sample rate offsetDelta = (offsetDelta * 1000) / ((sampleRate * 1000) / iSubFormat.outputSampleRate); // aml 4.24.02 this was supposed to set the offset, not add it! Looks like a typo from case above. iSubBufferOffset = offsetDelta; #if DEBUGLOG IOLog ("USBEngine: need sync: starting iSubBufferOffset = %ld, iSubLoopCount = %ld\n", iSubBufferOffset, iSubLoopCount); #endif } if (iSubBufferOffset > (SInt32)iSubBufferLen) { needToSync = TRUE; // aml 4.24.02, requests larger than our buffer size = bad! // Our calculated spot has actually wrapped around the iSub's buffer. iSubLoopCount += iSubBufferOffset / iSubBufferLen; iSubBufferOffset = iSubBufferOffset % iSubBufferLen; #if DEBUGLOG IOLog ("iSubBufferOffset > iSubBufferLen, iSubBufferOffset is now %ld\n", iSubBufferOffset); #endif } else if (iSubBufferOffset < 0) { iSubBufferOffset += iSubBufferLen; #if DEBUGLOG IOLog ("iSubBufferOffset < 0, iSubBufferOffset is now %ld\n", iSubBufferOffset); #endif } // aml 2.21.02 added extra filter states for 4th order with phase compensation // iSub crossover // aml 3.1.02 added format param // aml 3.4.02 added srcPhase // aml 3.6.02 added srcState result = clipAppleUSBAudioToOutputStreamiSub (mixBuf, sampleBuf, &filterState, &filterState2, &phaseCompState, lowFreqSamples, highFreqSamples, firstSampleFrame, numSampleFrames, sampleRate, streamFormat, (SInt16*)iSubBuffer, &iSubLoopCount, &iSubBufferOffset, iSubBufferLen, &iSubFormat, &srcPhase, &srcState); if (TRUE == startiSub) { iSubEngine->StartiSub (); startiSub = FALSE; iSubLoopCount = 0; } // aml 3.29.02 moved here to match AOA previousClippedToFrame = firstSampleFrame + numSampleFrames; } else { result = clipAppleUSBAudioToOutputStream (mixBuf, sampleBuf, firstSampleFrame, numSampleFrames, streamFormat); } } else { UInt32 offset; offset = firstSampleFrame * streamFormat->fNumChannels * (streamFormat->fBitWidth / 8); memcpy ((UInt8 *)sampleBuf + offset, (UInt8 *)mixBuf, numSampleFrames * streamFormat->fNumChannels * (streamFormat->fBitWidth / 8)); previousClippedToFrame = firstSampleFrame + numSampleFrames; result = kIOReturnSuccess; } return result; } // This function is called from both the IOProc's call to convertInputSamples and by the readHandler. // To figure out where to start coalescing from, it looks at the currentFrameList, which is updated by the readHandler. // It will copy from currentFameList+1 the number of bytes requested or one USB frame list. // When numBytesToCoalesce == 0 it will coalesce the current USB frame list (however big it is). // If numBytesToCoalesce != 0, it will coalesce that many bytes starting from the current frame list and going to the next one if needed. // When called from the readHandler it will just coalesce one USB frame starting from currentFrameList. // When called from convertInputSamples, it will convert the number of bytes that corresponds to the number of samples that are being asked to be converted, // starting from currentFrameList. void AppleUSBAudioEngine::CoalesceInputSamples (UInt32 numBytesToCoalesce) { AbsoluteTime time; AbsoluteTime delay; #if DEBUGZEROTIME AbsoluteTime timestampOffset; UInt64 nanos; #endif UInt64 a; UInt64 b; UInt64 currentWrappedReadUSBFrame; SInt32 actualElapsedFrameLists; SInt32 totalFrameLists; UInt32 usbFrameIndex; UInt32 numBytesToCopy; UInt32 numBytesToEnd; UInt32 numBytesCopied; UInt32 originalBufferOffset; UInt32 usbFrameOffset; SInt32 numBytesLeft; UInt8 * source; UInt8 * dest; Boolean done; if (0 != numBytesToCoalesce) { originalBufferOffset = bufferOffset; // So that when we later get called from the readHandler that we'll put everything in the right spot } dest = (UInt8 *)(getSampleBuffer ()) + bufferOffset; source = ((UInt8 *)readBuffer) + (currentFrameList * readFrameListSize); usbFrameIndex = 0; numBytesCopied = 0; numBytesLeft = numBytesToCoalesce; done = FALSE; usbFrameOffset = ((currentFrameList % numFrameListsToQueue) * numFramesPerList); while (FALSE == done) { if (theFrames[usbFrameOffset + usbFrameIndex].frActCount < (theFrames[usbFrameOffset + usbFrameIndex].frReqCount - bytesPerSampleFrame)) { #if DEBUGLOG IOLog ("ERROR! short read packet %d min = %d\n", theFrames[usbFrameOffset + usbFrameIndex].frActCount, theFrames[usbFrameOffset + usbFrameIndex].frReqCount - bytesPerSampleFrame); #endif } numBytesToEnd = getSampleBufferSize () - bufferOffset; if ((UInt32)(theFrames[usbFrameOffset + usbFrameIndex].frActCount) > numBytesToEnd) { numBytesToCopy = numBytesToEnd; } else { numBytesToCopy = theFrames[usbFrameOffset + usbFrameIndex].frActCount; } memcpy (dest, source, numBytesToCopy); bufferOffset += numBytesToCopy; numBytesCopied = numBytesToCopy; numBytesLeft -= numBytesToCopy; if ((UInt32)(theFrames[usbFrameOffset + usbFrameIndex].frActCount) > numBytesToEnd) { numBytesToCopy = (theFrames[usbFrameOffset + usbFrameIndex].frActCount) - numBytesToEnd; dest = (UInt8 *)getSampleBuffer (); memcpy (dest, source + numBytesCopied, numBytesToCopy); bufferOffset = numBytesToCopy; numBytesLeft -= numBytesToCopy; if (0 == numBytesToCoalesce) { // we have wrapped and we were called by the completion routine -- take timestamp #if DEBUGZEROTIME clock_get_uptime (&time); SUB_ABSOLUTETIME (&time, &status->fLastLoopTime); nanoseconds_to_absolutetime ((numFramesPerList - usbFrameIndex) * 1000 * 1000, ×tampOffset); SUB_ABSOLUTETIME (&time, ×tampOffset); absolutetime_to_nanoseconds (time, &nanos); IOLog ("d=%ld, f#=%ld\n", (UInt32)(nanos / (1000 * 1000)), currentFrameList); #endif totalFrameLists = numFramesPerList * numFrameLists; // e.g. 8*40 = 320 currentWrappedReadUSBFrame = currentFrameList * numFramesPerList + usbFrameIndex; actualElapsedFrameLists = (SInt32)((SInt64)currentWrappedReadUSBFrame - (SInt64)previousWrappedReadUSBFrame) + totalFrameLists; if (actualElapsedFrameLists < totalFrameLists/2) actualElapsedFrameLists += totalFrameLists; else if (actualElapsedFrameLists >= totalFrameLists + totalFrameLists/2) actualElapsedFrameLists -= totalFrameLists; do { a = streamInterface->GetDevice()->GetBus()->GetFrameNumber (); clock_get_uptime (&time); b = streamInterface->GetDevice()->GetBus()->GetFrameNumber (); } while (a != b); nanoseconds_to_absolutetime ((a - (expectedBusFrame + actualElapsedFrameLists)) * 1000 * 1000, &delay); SUB_ABSOLUTETIME (&time, &delay); // time -= delay; takeTimeStamp (TRUE, &time); previousWrappedReadUSBFrame = currentWrappedReadUSBFrame; expectedBusFrame += actualElapsedFrameLists; } } dest += numBytesToCopy; source += theFrames[usbFrameOffset + usbFrameIndex].frReqCount; usbFrameIndex++; if ((0 == numBytesToCoalesce && numFramesPerList == usbFrameIndex) || // We've coalesced the current frame list (0 != numBytesToCoalesce && 0 >= numBytesLeft) || // We've coalesced the requested number of bytes (usbFrameOffset + usbFrameIndex >= (numFramesPerList * numFrameListsToQueue))) { // We've gone through all the frame lists and there's nothing left to coalesce (starvation case) done = TRUE; } } if (0 != numBytesToCoalesce) { bufferOffset = originalBufferOffset; } return; } // When convertInputSamples is called, we have a window of samples that might possibly still be in transit on the USB bus. // The number of samples that might be in transit depends on how long our USB read completion routines have been held off. // Best case is that we have already coalesced all the samples that have been recorded because we weren't held off. // Worst case is that we've been held off for longer than (framesPerList * numFrameListsToQueue) milliseconds and we haven't // coalesced anything that's been recorded and we don't have any more reads queued up and we're being asked for something // that hasn't been recorded. // The normal case should be not much worse than the best case and that's what we're trying to make better. // The case we are really trying to fix is when we have been held off for (framesPerList * 1 or 2) ms and we have the data // that's being asked for, it just hasn't been coalesced yet. // What we have is a window of samples that are outstanding, either they have been recorded and we haven't gotten the // completion routine called for them, or they are still in the future and haven't been recorded yet. We must figure out // where that window is in our buffer and if the request is for sample inside of that window, coalesce and return those // samples. If the request is for samples outside of that window, just return those samples because there is no possibility // that they are in the recording buffer (it's either the worst case, or someone is asking for old data). // The window goes from (currentFrameList + 1) to (currentFrameList + numFramesListsToQueue) which is at most // readFrameListSize * numFrameListsToQueue in size. It's actually probably smaller than that, but we'll assume that it's // that big and if can't get coalesce enough bytes from the read buffer, then we'll return old data since there is nothing // else that we can do anyway (perhaps we could return an error to the HAL too). IOReturn AppleUSBAudioEngine::convertInputSamples (const void *sampleBuf, void *destBuf, UInt32 firstSampleFrame, UInt32 numSampleFrames, const IOAudioStreamFormat *streamFormat, IOAudioStream *audioStream) { #if NEWUSB UInt32 lastSampleByte; UInt32 windowStartByte; UInt32 windowEndByte; #endif #if DEBUGLOG #if 0 UInt32 lastSampleFrame = firstSampleFrame + numSampleFrames; UInt32 numSampleFramesPerBuffer, currentSampleFrame; numSampleFramesPerBuffer = getNumSampleFramesPerBuffer (); if (lastSampleFrame >= numSampleFramesPerBuffer) { lastSampleFrame -= numSampleFramesPerBuffer; } currentSampleFrame = getCurrentSampleFrame (); if (firstSampleFrame < lastSampleFrame) { if ((currentSampleFrame > firstSampleFrame) && (currentSampleFrame < lastSampleFrame)) { IOLog ("input error - samples not input yet! (%lx, %lx, curr = %lx)\n", firstSampleFrame, numSampleFrames, currentSampleFrame); } } else { if ((currentSampleFrame > firstSampleFrame) || (currentSampleFrame < lastSampleFrame)) { IOLog ("input error - samples not input yet! (%lx, %lx, curr = %lx)\n", firstSampleFrame, numSampleFrames, currentSampleFrame); } } #endif #endif #if NEWUSB lastSampleByte = (firstSampleFrame + numSampleFrames) * streamFormat->fNumChannels * (streamFormat->fBitWidth / 8); // Is the request inside our window of possibly recorded samples? if (bufferOffset + 1 > getSampleBufferSize ()) { windowStartByte = 0; } else { windowStartByte = bufferOffset + 1; } windowEndByte = ((currentFrameList + numFrameListsToQueue) % numFrameLists) * readFrameListSize; // make the window a little larger than it should be, but that's OK if ((windowStartByte < lastSampleByte && windowEndByte > lastSampleByte) || (windowEndByte > lastSampleByte && windowStartByte > windowEndByte) || (windowStartByte < lastSampleByte && windowStartByte > windowEndByte && windowEndByte < lastSampleByte)) { // IOLog ("coalese: start=%ld, num=%ld, end=%ld -- curFrame=%ld, bufferOffset=%ld, windowStartByte=%ld, windowEndByte=%ld\n", // firstSampleFrame * 4, numSampleFrames, lastSampleByte, currentFrameList, bufferOffset, windowStartByte, windowEndByte); if (bufferOffset < lastSampleByte) { CoalesceInputSamples (lastSampleByte - bufferOffset); } else { // Have to wrap around the buffer. if (getSampleBufferSize () - bufferOffset) { CoalesceInputSamples (getSampleBufferSize () - bufferOffset); } CoalesceInputSamples (lastSampleByte); } } #endif return convertFromAppleUSBAudioInputStream_NoWrap (sampleBuf, destBuf, firstSampleFrame, numSampleFrames, streamFormat); } UInt32 AppleUSBAudioEngine::getCurrentSampleFrame () { const IOAudioStreamFormat *theFormat; UInt32 currentSampleFrame; // UInt32 i; // UInt32 offset; // UInt32 thisFrameListSize; // UInt16 averageFrameSamples; // UInt16 additionalSampleFrameFreq; #if 0 AbsoluteTime uptime; static AbsoluteTime lastUptime = {0, 0}; AbsoluteTime delta; UInt64 nanos; #endif currentSampleFrame = 0; FailIf (NULL == mainStream, Exit); theFormat = mainStream->getFormat (); if (getDirection () == kIOAudioStreamDirectionOutput) { /* CalculateSamplesPerFrame (curSampleRate.whole, &averageFrameSamples, &additionalSampleFrameFreq); if (additionalSampleFrameFreq) { offset = 0; // This will have to be changed if numFramesPerList is ever larger than additionalSampleFrameFreq if (additionalSampleFrameFreq == numFramesPerList) { thisFrameListSize = frameListSize; } else { thisFrameListSize = frameListSize - ((theFormat->fBitWidth / 8) * theFormat->fNumChannels); } for (i = 0; i < currentFrameList; i++) { offset += thisFrameListSize; if (i % (additionalSampleFrameFreq / numFramesPerList) == 0) { thisFrameListSize = frameListSize; } else { thisFrameListSize = frameListSize - ((theFormat->fBitWidth / 8) * theFormat->fNumChannels); } } currentSampleFrame = offset; } else { */ currentSampleFrame = currentFrameList * frameListSize; // } } else { currentSampleFrame = bufferOffset == bufferSize ? 0 : bufferOffset; } currentSampleFrame /= (theFormat->fNumChannels * (theFormat->fBitWidth / 8)); #if 0 clock_get_uptime (&uptime); delta = uptime; SUB_ABSOLUTETIME (&delta, &lastUptime); absolutetime_to_nanoseconds (delta, &nanos); debug3IOLog ("getCurrentSampleFrame = %ld, delta = %ld\n", currentSampleFrame, (UInt32)(nanos / (1000 * 1000))); lastUptime = uptime; #endif Exit: #if DEBUGLOG if (currentSampleFrame > getNumSampleFramesPerBuffer ()) { char panicString[255]; sprintf (panicString, "currentSampleFrame = %d, getNumSampleFramesPerBuffer () = %d, currentFrameList = %d, frameListSize = %d, bufferOffset = %d", currentSampleFrame, getNumSampleFramesPerBuffer (), currentFrameList, frameListSize, bufferOffset); panic (panicString); } #endif return currentSampleFrame; } void AppleUSBAudioEngine::GetDeviceInfo (void) { OSObject *obj; if (obj = streamInterface->getProperty (kUSBVendorName)) { obj->retain(); interfaceVendor = obj; } if (obj = streamInterface->getProperty (kUSBProductName)) { obj->retain(); interfaceProduct = obj; } if (obj = streamInterface->getProperty (kUSBDeviceReleaseNumber)) { obj->retain(); deviceReleaseNumber = obj; } if (obj = streamInterface->getProperty (kUSBConfigurationValue)) { obj->retain(); configurationValue = obj; } if (obj = streamInterface->getProperty (kUSBInterfaceNumber)) { obj->retain(); interfaceNumber = obj; } } IOAudioStreamDirection AppleUSBAudioEngine::getDirection () { IOAudioStreamDirection direction; direction = kIOAudioStreamDirectionOutput; FailIf (NULL == mainStream, Exit); direction = mainStream->getDirection (); Exit: return direction; } OSString * AppleUSBAudioEngine::getGlobalUniqueID () { char * uniqueIDStr; OSString * localID; OSString * uniqueID; OSNumber * usbLocation; IOReturn err; UInt32 uniqueIDSize; UInt32 locationID; UInt8 stringIndex; UInt8 interfaceNumber; char productString[kStringBufferSize]; char manufacturerString[kStringBufferSize]; char serialNumberString[kStringBufferSize]; char locationIDString[kStringBufferSize]; char interfaceNumberString[4]; // biggest string possible is "255" usbLocation = NULL; uniqueIDStr = NULL; localID = NULL; uniqueID = NULL; uniqueIDSize = 5; // This is the number of ":" in the final string, plus space for the trailing NULL that ends the string uniqueIDSize += strlen ("AppleUSBAudioEngine"); err = kIOReturnSuccess; manufacturerString[0] = 0; stringIndex = streamInterface->GetDevice()->GetManufacturerStringIndex (); if (0 != stringIndex) { err = streamInterface->GetDevice()->GetStringDescriptor (stringIndex, manufacturerString, kStringBufferSize); } if (0 == manufacturerString[0] || kIOReturnSuccess != err) { strcpy (manufacturerString, "Unknown Manufacturer"); } uniqueIDSize += strlen (manufacturerString); err = kIOReturnSuccess; productString[0] = 0; stringIndex = streamInterface->GetDevice()->GetProductStringIndex (); if (0 != stringIndex) { err = streamInterface->GetDevice()->GetStringDescriptor (stringIndex, productString, kStringBufferSize); } if (0 == productString[0] || kIOReturnSuccess != err) { strcpy (productString, "Unknown USB Audio Device"); } uniqueIDSize += strlen (productString); err = kIOReturnSuccess; serialNumberString[0] = 0; stringIndex = streamInterface->GetDevice()->GetSerialNumberStringIndex (); if (0 != stringIndex) { err = streamInterface->GetDevice()->GetStringDescriptor (stringIndex, serialNumberString, kStringBufferSize); } if (0 == serialNumberString[0] || kIOReturnSuccess != err) { // device doesn't have a serial number, get its location ID usbLocation = OSDynamicCast (OSNumber, streamInterface->GetDevice()->getProperty (kUSBDevicePropertyLocationID)); if (NULL != usbLocation) { locationID = usbLocation->unsigned32BitValue (); sprintf (locationIDString, "%x", locationID); } else { strcpy (locationIDString, "Unknown location"); } uniqueIDSize += strlen (locationIDString); } else { // device has a serial number that we can use to track it debug2IOLog ("device has a serial number = %s\n", serialNumberString); uniqueIDSize += strlen (serialNumberString); } interfaceNumber = streamInterface->GetInterfaceNumber (); sprintf (interfaceNumberString, "%d", interfaceNumber); uniqueIDSize += strlen (interfaceNumberString); uniqueIDStr = (char *)IOMalloc (uniqueIDSize); if (NULL != uniqueIDStr) { uniqueIDStr[0] = 0; if (0 == serialNumberString[0]) { sprintf (uniqueIDStr, "AppleUSBAudioEngine:%s:%s:%s:%s", manufacturerString, productString, locationIDString, interfaceNumberString); } else { sprintf (uniqueIDStr, "AppleUSBAudioEngine:%s:%s:%s:%s", manufacturerString, productString, serialNumberString, interfaceNumberString); } uniqueID = OSString::withCString (uniqueIDStr); debug2IOLog ("getGlobalUniqueID = %s\n", uniqueIDStr); IOFree (uniqueIDStr, uniqueIDSize); } return uniqueID; } void * AppleUSBAudioEngine::getSampleBuffer (void) { void * sampleBuffer; sampleBuffer = NULL; FailIf (NULL == mainStream, Exit); sampleBuffer = mainStream->getSampleBuffer (); Exit: return sampleBuffer; } UInt32 AppleUSBAudioEngine::getSampleBufferSize (void) { UInt32 bufferSize; bufferSize = 0; FailIf (NULL == mainStream, Exit); bufferSize = mainStream->getSampleBufferSize (); Exit: return bufferSize; } //-------------------------------------------------------------------------------- bool AppleUSBAudioEngine::initHardware (IOService *provider) { USBAudioConfigObject * usbAudio; IOAudioStreamFormat streamFormat; Boolean resultCode; UInt16 terminalType; debug3IOLog ("+AppleUSBAudioEngine[%p]::initHardware (%p)\n", this, provider); resultCode = FALSE; FailIf (FALSE == super::initHardware (provider), Exit); streamInterface = OSDynamicCast (IOUSBInterface, provider); FailIf (NULL == streamInterface, Exit); previousInterface = streamInterface; numFrameLists = NUM_FRAME_LISTS; numFramesPerList = NUM_FRAMES_PER_LIST; numFrameListsToQueue = NUM_FRAME_LISTS_TO_QUEUE; // Find out what interface number the driver is being instantiated against so that we always ask about // our particular interface and not some other interface the device might have. ourInterfaceNumber = streamInterface->GetInterfaceNumber (); debug2IOLog ("ourInterfaceNumber = %d\n", ourInterfaceNumber); usbAudio = usbAudioDevice->GetUSBAudioConfigObject (); FailIf (NULL == usbAudio, Exit); mainStream = new IOAudioStream; FailIf (NULL == mainStream, Exit); sampleRate.whole = kDefaultSamplingRate; sampleRate.fraction = 0; // We, as the driver, have to pick a default sample rate, size, and number of channels, so pick 16-bit stereo 44.1kHz alternateInterfaceID = usbAudio->FindAltInterfaceWithSettings (ourInterfaceNumber, kChannelDepth_STEREO, kBitDepth_16bits, sampleRate.whole); if (255 == alternateInterfaceID) { // Didn't have stereo, so let's hope it has mono alternateInterfaceID = usbAudio->FindAltInterfaceWithSettings (ourInterfaceNumber, kChannelDepth_MONO, kBitDepth_16bits, sampleRate.whole); } if (255 == alternateInterfaceID) { // They don't have a mono or stereo 16-bit 44.1kHz interface, so let's try for a stereo 16-bit interface with any sample rate alternateInterfaceID = usbAudio->FindAltInterfaceWithSettings (ourInterfaceNumber, kChannelDepth_STEREO, kBitDepth_16bits); sampleRate.whole = usbAudio->GetHighestSampleRate (ourInterfaceNumber, alternateInterfaceID); // we'll run at the highest sample rate that the device has } if (255 == alternateInterfaceID) { // They don't have a stereo 16-bit interface, so let's try for a mono 16-bit interface with any sample rate alternateInterfaceID = usbAudio->FindAltInterfaceWithSettings (ourInterfaceNumber, kChannelDepth_MONO, kBitDepth_16bits); sampleRate.whole = usbAudio->GetHighestSampleRate (ourInterfaceNumber, alternateInterfaceID); // we'll run at the highest sample rate that the device has } if (255 == alternateInterfaceID) { // OK, we're not going to be picky here (since that didn't get us anywhere), we're going to try to run with the first interface we find. alternateInterfaceID = 1; sampleRate.whole = usbAudio->GetHighestSampleRate (ourInterfaceNumber, alternateInterfaceID); } debug2IOLog ("default sample rate is %d\n", sampleRate.whole); FailIf (0 == sampleRate.whole, Exit); direction = usbAudio->GetIsocEndpointDirection (ourInterfaceNumber, alternateInterfaceID); mainStream->initWithAudioEngine (this, (IOAudioStreamDirection)direction, 1); if (kUSBIn == direction) { // look for a streaming output terminal that's connected to a non-streaming input terminal debugIOLog ("This is an input type endpoint (mic, etc.)\n"); UInt32 index = 0; do { terminalType = usbAudio->GetIndexedInputTerminalType (usbAudioDevice->controlInterface->GetInterfaceNumber (), 0, index++); } while (terminalType == INPUT_UNDEFINED && index < 256); mainStream->setTerminalType (terminalType); } else if (kUSBOut == direction) { debugIOLog ("This is an output type endpoint (speaker, etc.)\n"); UInt32 index = 0; do { terminalType = usbAudio->GetIndexedOutputTerminalType (usbAudioDevice->controlInterface->GetInterfaceNumber (), 0, index++); } while (terminalType == OUTPUT_UNDEFINED && index < 256); mainStream->setTerminalType (terminalType); } else { FailMessage ("Couldn't get the endpoint direction!\n", Exit); } FailIf (kIOReturnSuccess != AddAvailableFormatsFromDevice (usbAudio), Exit); curSampleRate = sampleRate; setSampleRate (&sampleRate); // Tell the IOAudioFamily what format we are going to be running in. streamFormat.fNumChannels = usbAudio->GetNumChannels (ourInterfaceNumber, alternateInterfaceID); streamFormat.fBitDepth = usbAudio->GetSampleSize (ourInterfaceNumber, alternateInterfaceID); streamFormat.fBitWidth = usbAudio->GetSubframeSize (ourInterfaceNumber, alternateInterfaceID) * 8; streamFormat.fAlignment = kIOAudioStreamAlignmentLowByte; streamFormat.fByteOrder = kIOAudioStreamByteOrderLittleEndian; streamFormat.fDriverTag = (ourInterfaceNumber << 16) | alternateInterfaceID; switch (usbAudio->GetFormat (ourInterfaceNumber, alternateInterfaceID)) { case PCM: streamFormat.fSampleFormat = kIOAudioStreamSampleFormatLinearPCM; streamFormat.fNumericRepresentation = kIOAudioStreamNumericRepresentationSignedInt; streamFormat.fIsMixable = TRUE; break; case AC3: // just starting to stub something in for AC-3 support streamFormat.fSampleFormat = kIOAudioStreamSampleFormatAC3; streamFormat.fNumericRepresentation = kIOAudioStreamNumericRepresentationSignedInt; streamFormat.fIsMixable = FALSE; streamFormat.fNumChannels = 6; streamFormat.fBitDepth = 16; streamFormat.fBitWidth = 16; streamFormat.fByteOrder = kIOAudioStreamByteOrderBigEndian; break; case IEC1937_AC3: streamFormat.fSampleFormat = kIOAudioStreamSampleFormat1937AC3; streamFormat.fNumericRepresentation = kIOAudioStreamNumericRepresentationSignedInt; streamFormat.fIsMixable = FALSE; break; default: FailMessage ("!!!!interface doesn't support a format that we can deal with!!!!\n", Exit); } FailIf (FALSE == streamInterface->open (this), Exit); // Have to open the interface because calling setFormat will call performFormatChange, which expects the interface to be open. FailIf (kIOReturnSuccess != mainStream->setFormat (&streamFormat), Exit); FailIf (kIOReturnSuccess != addAudioStream (mainStream), Exit); // Verify that this 'start' request is targeting a USB Audio Stream interface // (i.e. it must be an audio class and a stream subclass). FailIf (kUSBAudioClass != usbAudio->GetInterfaceClass (ourInterfaceNumber, alternateInterfaceID), Exit); FailIf (kUSBAudioStreamInterfaceSubclass != usbAudio->GetInterfaceSubClass (ourInterfaceNumber, alternateInterfaceID), Exit); GetDeviceInfo (); // Figure out what this device is so if it is unplugged we can tell if it's replugged. if (kUSBOut == direction) { iSubBufferMemory = NULL; iSubEngine = NULL; // Set up a control that sound prefs can set to tell us if we should install our notifier or not iSubAttach = IOAudioToggleControl::create (FALSE, kIOAudioControlChannelIDAll, kIOAudioControlChannelNameAll, 0, kIOAudioToggleControlSubTypeiSubAttach, kIOAudioControlUsageOutput); if (NULL != iSubAttach) { addDefaultAudioControl (iSubAttach); iSubAttach->setValueChangeHandler ((IOAudioControl::IntValueChangeHandler)iSubAttachChangeHandler, this); // don't release it as we might use it later } } usbAudioDevice->activateAudioEngine (this, FALSE, ourInterfaceNumber, alternateInterfaceID); resultCode = TRUE; if (resultCode) { usbAudioDevice->retain (); } Exit: debug4IOLog("-%d = AppleUSBAudioEngine[%p]::initHardware(%p)\n", resultCode, this, provider); return resultCode; } IOReturn AppleUSBAudioEngine::iSubAttachChangeHandler (IOService *target, IOAudioControl *theControl, SInt32 oldValue, SInt32 newValue) { IOReturn result; AppleUSBAudioEngine * audioEngine; debug5IOLog ("+ AppleUSBAudioEngine::iSubAttachChangeHandler (%p, %p, %ld, %ld)\n", target, theControl, oldValue, newValue); result = kIOReturnSuccess; FailIf (oldValue == newValue, Exit); audioEngine = OSDynamicCast (AppleUSBAudioEngine, target); FailIf (NULL == audioEngine, Exit); if (newValue) { // Set up notifier to run when iSub shows up audioEngine->iSubEngineNotifier = addNotification (gIOPublishNotification, serviceMatching ("AppleiSubEngine"), (IOServiceNotificationHandler)&iSubEnginePublished, audioEngine); if (NULL != audioEngine->iSubBufferMemory) { // it looks like the notifier could be called before iSubEngineNotifier is set, // so if it was called, then iSubBufferMemory would no longer be NULL and we can remove the notifier audioEngine->iSubEngineNotifier->remove (); audioEngine->iSubEngineNotifier = NULL; } } else { if (NULL != audioEngine->iSubBufferMemory && NULL != audioEngine->iSubEngine) { // We're already attached to an iSub, so detach. Runs on a thread to avoid deadlocks. audioEngine->iSubTeardownConnection (); } // We're not attached to the iSub, so just remove our notifier if (NULL != audioEngine->iSubEngineNotifier) { audioEngine->iSubEngineNotifier->remove (); audioEngine->iSubEngineNotifier = NULL; } } Exit: debugIOLog("- AppleUSBAudioEngine::iSubAttachChangeHandler\n"); return result; } // This runs on the IOAudioFamily workloop on a separate thread than the one it was called on, which might have been the USB workloop (in the case of a hot unplug). IOReturn AppleUSBAudioEngine::iSubCloseAction (OSObject *owner, void *arg1, void *arg2, void *arg3, void *arg4) { debugIOLog("+AppleUSBAudioEngine::iSubCloseAction\n"); AppleUSBAudioEngine *audioEngine = OSDynamicCast (AppleUSBAudioEngine, owner); FailIf (NULL == audioEngine, Exit); if (TRUE == audioEngine->iSubIsOpen && NULL != audioEngine->oldiSubEngine) { audioEngine->oldiSubEngine->closeiSub (audioEngine); debugIOLog ("iSub closed\n"); audioEngine->detach (audioEngine->oldiSubEngine); audioEngine->iSubIsOpen = FALSE; audioEngine->iSubEngine = NULL; audioEngine->iSubBufferMemory = NULL; #if DEBUGLOG } else { if (TRUE != audioEngine->iSubIsOpen) {IOLog ("TRUE != audioEngine->iSubIsOpen\n");} if (NULL == audioEngine->oldiSubEngine) {IOLog ("NULL == audioEngine->oldiSubEngine\n");} #endif } if (NULL != audioEngine->lowFreqSamples) { IOFree (audioEngine->lowFreqSamples, audioEngine->getSampleBufferSize () * 2); } if (NULL != audioEngine->highFreqSamples) { IOFree (audioEngine->highFreqSamples, audioEngine->getSampleBufferSize () * 2); } Exit: debugIOLog("-AppleUSBAudioEngine::iSubCloseAction\n"); return kIOReturnSuccess; } bool AppleUSBAudioEngine::iSubEnginePublished (AppleUSBAudioEngine * usbAudioEngineObject, void * refCon, IOService * newService) { debug4IOLog ("AppleUSBAudioEngine::iSubEnginePublished (%p, %p, %p)\n", usbAudioEngineObject, refCon, newService); FailIf (NULL == usbAudioEngineObject, Exit); FailIf (NULL == newService, Exit); if (FALSE == usbAudioEngineObject->iSubIsOpen) { usbAudioEngineObject->iSubEngine = (AppleiSubEngine *)newService; usbAudioEngineObject->iSubOpenThreadCall = thread_call_allocate ((thread_call_func_t)usbAudioEngineObject->iSubOpen, (thread_call_param_t)usbAudioEngineObject); if (NULL != usbAudioEngineObject->iSubOpenThreadCall) { thread_call_enter (usbAudioEngineObject->iSubOpenThreadCall); } else { // error - do something } } else { debugIOLog ("iSub is already open\n"); } Exit: return TRUE; } void AppleUSBAudioEngine::iSubOpen (AppleUSBAudioEngine * usbAudioEngineObject) { IOReturn result; IOCommandGate * cg; debug2IOLog ("+AppleUSBAudioEngine::iSubOpen (%p)\n", usbAudioEngineObject); FailIf (NULL == usbAudioEngineObject, Exit); cg = usbAudioEngineObject->getCommandGate (); if (NULL != cg) { result = cg->runAction (iSubOpenAction); } Exit: if (NULL != usbAudioEngineObject && NULL != usbAudioEngineObject->iSubOpenThreadCall) { thread_call_free(usbAudioEngineObject->iSubOpenThreadCall); } debug2IOLog ("-AppleUSBAudioEngine::iSubOpen (%p)\n", usbAudioEngineObject); return; } IOReturn AppleUSBAudioEngine::iSubOpenAction (OSObject *owner, void *arg1, void *arg2, void *arg3, void *arg4) { AppleUSBAudioEngine * usbAudioEngineObject; IOReturn result; bool resultBool; UInt32 sampleRateWhole; UInt32 numTries; debug2IOLog ("+AppleUSBAudioEngine::iSubOpenAction (%p)\n", owner); result = kIOReturnError; usbAudioEngineObject = OSDynamicCast (AppleUSBAudioEngine, owner); FailIf (NULL == usbAudioEngineObject, Exit); FailIf (NULL == usbAudioEngineObject->iSubEngine, Exit); // We only run the iSub if we're currently running at a sampling rate of 44.1kHz sampleRateWhole = usbAudioEngineObject->curSampleRate.whole; // aml XXX iSub filters are set up for these sample rates: if ((8000 != sampleRateWhole) && (11025 != sampleRateWhole) && (22050 != sampleRateWhole) && (44100 != sampleRateWhole) && (48000 != sampleRateWhole) && (96000 != sampleRateWhole)) { goto Exit; } // Allocate memory for filter function to write samples into usbAudioEngineObject->lowFreqSamples = (float *)IOMallocAligned (round_page (usbAudioEngineObject->getSampleBufferSize () * 2), PAGE_SIZE); FailIf (NULL == usbAudioEngineObject->lowFreqSamples, Cleanup); usbAudioEngineObject->highFreqSamples = (float *)IOMallocAligned (round_page (usbAudioEngineObject->getSampleBufferSize () * 2), PAGE_SIZE); FailIf (NULL == usbAudioEngineObject->highFreqSamples, Cleanup); usbAudioEngineObject->attach (usbAudioEngineObject->iSubEngine); // Open the iSub which will cause it to create mute and volume controls numTries = 0; do { IOSleep (102); resultBool = usbAudioEngineObject->iSubEngine->openiSub (usbAudioEngineObject); // if (FALSE == resultBool) IOLog ("Couldn't open iSub, trying again (%ld)\n", numTries); numTries++; } while (FALSE == resultBool && numTries < 5); FailWithAction (FALSE == resultBool, usbAudioEngineObject->detach (usbAudioEngineObject->iSubEngine), Cleanup); result = kIOReturnSuccess; // aml 7.17.02, can't clean up from first few FailIf's because usbAudioEngineObject will be NULL. Cleanup: // remove our notifier because we only care about the first iSub // always remove it, even if we didn't successfully open the iSub. if (NULL != usbAudioEngineObject->iSubEngineNotifier) { usbAudioEngineObject->iSubEngineNotifier->remove (); usbAudioEngineObject->iSubEngineNotifier = NULL; } if (kIOReturnSuccess == result) { debugIOLog ("successfully opened the iSub\n"); usbAudioEngineObject->iSubBufferMemory = usbAudioEngineObject->iSubEngine->GetSampleBuffer (); usbAudioEngineObject->iSubIsOpen = TRUE; } else { // We didn't actually open the iSub usbAudioEngineObject->iSubBufferMemory = NULL; usbAudioEngineObject->iSubEngine = NULL; usbAudioEngineObject->iSubIsOpen = FALSE; if (NULL != usbAudioEngineObject->lowFreqSamples) { IOFree (usbAudioEngineObject->lowFreqSamples, usbAudioEngineObject->getSampleBufferSize () * 2); } if (NULL != usbAudioEngineObject->highFreqSamples) { IOFree (usbAudioEngineObject->highFreqSamples, usbAudioEngineObject->getSampleBufferSize () * 2); } } // aml 7.17.02, moved exit below because usbAudioEngineObject will be NULL from first few FailIf's Exit: debug3IOLog ("-AppleUSBAudioEngine::iSubOpenAction (%p) = 0x%lx\n", owner, result); return result; } // Runs on a thread to avoid waiting on the primary thread. void AppleUSBAudioEngine::iSubTeardown (AppleUSBAudioEngine * usbAudioEngine, thread_call_t iSubTeardownThreadCall) { IOCommandGate * cg; debugIOLog ("+AppleUSBAudioEngine::iSubTeardown ()\n"); if (NULL != usbAudioEngine) { usbAudioEngine->pauseAudioEngine (); usbAudioEngine->beginConfigurationChange (); cg = usbAudioEngine->getCommandGate (); if (NULL != cg) { cg->runAction (iSubCloseAction); } usbAudioEngine->completeConfigurationChange (); usbAudioEngine->resumeAudioEngine (); } if (NULL != iSubTeardownThreadCall) { thread_call_free(iSubTeardownThreadCall); } debugIOLog ("-AppleUSBAudioEngine::iSubTeardown ()\n"); } void AppleUSBAudioEngine::iSubTeardownConnection (void) { debugIOLog ("+AppleUSBAudioEngine::iSubTeardownConnection ()\n"); oldiSubEngine = iSubEngine; iSubTeardownThreadCall = thread_call_allocate ((thread_call_func_t)iSubTeardown, (thread_call_param_t)this); if (NULL != iSubTeardownThreadCall) { thread_call_enter1 (iSubTeardownThreadCall, iSubTeardownThreadCall); } else { // error - do something } debugIOLog ("-AppleUSBAudioEngine::iSubTeardownConnection ()\n"); return; } IOReturn AppleUSBAudioEngine::performAudioEngineStart () { IOReturn resultCode; debug2IOLog ("+AppleUSBAudioEngine[%p]::performAudioEngineStart ()\n", this); resultCode = kIOReturnSuccess; if (NULL != iSubEngine) { startiSub = TRUE; needToSync = TRUE; } if (!usbStreamRunning) { resultCode = startUSBStream (); } if (resultCode != kIOReturnSuccess) { debug2IOLog ("++AppleUSBAudioEngine[%p]::performAudioEngineStart () -- NOT started, error = %d", resultCode); } else { debug2IOLog ("++AppleUSBAudioEngine[%p]::performAudioEngineStart () - started.\n", this); } debug2IOLog ("-AppleUSBAudioEngine[%p]::performAudioEngineStart ()\n", this); return resultCode; } IOReturn AppleUSBAudioEngine::performAudioEngineStop() { debug2IOLog("+AppleUSBAudioEngine[%p]::performAudioEngineStop ()\n", this); if (NULL != iSubEngine) { iSubEngine->StopiSub (); needToSync = TRUE; } if (usbStreamRunning) { stopUSBStream (); } /* We can't do this here any more because the device depends on having our status already * changed and the _setStatus() call is now made after the stopAudioEngine() call if (notifyDeviceOfStop) { usbAudioDevice->streamStopped(this); } */ debug2IOLog("++AppleUSBAudioEngine[%p]::performAudioEngineStop() - stopped\n", this); debug2IOLog("-AppleUSBAudioEngine[%p]::performAudioEngineStop()\n", this); return kIOReturnSuccess; } // This gets called when the HAL wants to select one of the different formats that we made available via mainStream->addAvailableFormat IOReturn AppleUSBAudioEngine::performFormatChange (IOAudioStream *audioStream, const IOAudioStreamFormat *newFormat, const IOAudioSampleRate *newSampleRate) { USBAudioConfigObject * usbAudio; IOReturn result; void * sampleBuffer; UInt32 i; UInt32 oldBufferSize; UInt32 totalFrames; UInt32 numAlternateFrames; UInt32 numAverageFrames; UInt32 safetyOffset; UInt16 averageFrameSamples; UInt16 averageFrameSize; UInt16 alternateFrameSize; UInt16 additionalSampleFrameFreq; UInt16 oldNumFrameLists; UInt16 oldNumFramesPerList; UInt16 oldReadFrameListSize; UInt8 ourInterfaceNumber; UInt8 newAlternateInterfaceID; UInt8 newDirection; debugIOLog ("+AppleUSBAudioEngine::performFormatChange\n"); result = kIOReturnError; oldBufferSize = bufferSize; FailIf (NULL == streamInterface, Exit); FailIf (NULL == usbAudioDevice, Exit); usbAudio = usbAudioDevice->GetUSBAudioConfigObject (); FailIf (NULL == usbAudio, Exit); FailIf (NULL == newFormat, Exit); ourInterfaceNumber = (UInt8)(newFormat->fDriverTag >> 16); newAlternateInterfaceID = (UInt8)(newFormat->fDriverTag); if (newSampleRate) { FailWithAction (FALSE == usbAudio->VerifySampleRateIsSupported (ourInterfaceNumber, newAlternateInterfaceID, newSampleRate->whole), result = kIOReturnError, Exit); } newDirection = usbAudio->GetIsocEndpointDirection (ourInterfaceNumber, newAlternateInterfaceID); FailIf (newDirection != direction, Exit); debug3IOLog ("++about to set: ourInterfaceNumber = %d & newAlternateInterfaceID = %d\n", ourInterfaceNumber, newAlternateInterfaceID); alternateInterfaceID = newAlternateInterfaceID; if (newSampleRate) { debug2IOLog ("changing sampling rate to %d\n", newSampleRate->whole); curSampleRate = *newSampleRate; } else { debug2IOLog ("keeping the existing sampling rate of %d\n", curSampleRate.whole); } // Set the sampling rate on the device /*result = */(void)SetSampleRate (usbAudio, curSampleRate.whole); // no need to check the error, it's not a real problem if it doesn't work // FailIf (kIOReturnSuccess != result, Exit); result = kIOReturnError; // reset error in case something goes wrong averageSampleRate = curSampleRate.whole; // Set this as the default until we are told otherwise debug2IOLog ("averageSampleRate = %d\n", averageSampleRate); CalculateSamplesPerFrame (curSampleRate.whole, &averageFrameSamples, &additionalSampleFrameFreq); debug3IOLog ("averageFrameSamples = %d, additionalSampleFrameFreq = %d\n", averageFrameSamples, additionalSampleFrameFreq); oldNumFrameLists = numFrameLists; oldNumFramesPerList = numFramesPerList; oldReadFrameListSize = readFrameListSize; if (additionalSampleFrameFreq) { numFramesPerList = additionalSampleFrameFreq; } else { numFramesPerList = NUM_FRAMES_PER_LIST; } if (additionalSampleFrameFreq) { while (numFrameLists % additionalSampleFrameFreq != 0) { // We need more frame lists so that the additional samples always fit into the buffer numFrameLists++; } debug2IOLog ("numFramelists = %d\n", numFrameLists); while (additionalSampleFrameFreq % numFramesPerList != 0) { // We need to make sure that the frame list that has the additional sample does not shift numFramesPerList++; } debug2IOLog ("numFramesPerList = %d\n", numFramesPerList); } averageFrameSize = averageFrameSamples * (newFormat->fNumChannels * (newFormat->fBitWidth / 8)); alternateFrameSize = (averageFrameSamples + 1) * (newFormat->fNumChannels * (newFormat->fBitWidth / 8)); debug3IOLog ("averageFrameSize = %d, alternateFrameSize = %d\n", averageFrameSize, alternateFrameSize); if (additionalSampleFrameFreq) { if (kUSBIn == direction) { if (NUM_FRAMES_PER_LIST == numFramesPerList) { numFramesPerList = 8; } // You have to make the read buffer the size of the alternate frame size because we have to ask for alternateFrameSize bytes with each read // If you don't make the buffer big enough, you don't get all the data from the last frame... readFrameListSize = alternateFrameSize * numFramesPerList; debug2IOLog ("readFrameListSize = %d\n", readFrameListSize); safetyOffset = 16 * averageFrameSamples + 2; } // one second of audio is packetized into packets of size n and n+1 (maybe n-1, n, and n+1, but so far we haven't needed to do that) // n*p + (n+1)q = sampleRate // p + q = 1000, where p is number of small packets (average size) and q is number of large packets (alternate size) // **** Can't calculate frameListSize like this because it doesn't work if the numFramesPerList doesn't contain exactly one additional sample frame **** // frameListSize = ((averageFrameSize * (numFramesPerList / additionalSampleFrameFreq * (additionalSampleFrameFreq - 1))) + (alternateFrameSize * (numFramesPerList / additionalSampleFrameFreq))); // frameListSize = ((numFramesPerList - 1) * (averageFrameSamples * newFormat->fNumChannels * (newFormat->fBitWidth / 8))) + ((averageFrameSamples + 1) * newFormat->fNumChannels * (newFormat->fBitWidth / 8)); debug3IOLog ("newFormat->fNumChannels = %d, averageFrameSize = %d\n", newFormat->fNumChannels, averageFrameSize); totalFrames = numFrameLists * numFramesPerList; numAlternateFrames = totalFrames / additionalSampleFrameFreq; numAverageFrames = totalFrames - numAlternateFrames; bufferSize = (averageFrameSize * numAverageFrames) + (alternateFrameSize * numAlternateFrames); frameListSize = bufferSize / numFrameLists; // bufferSize = CalculateNumSamplesPerFrameList (curSampleRate.whole, numFramesPerList, numFrameLists) * (newFormat->fNumChannels * (newFormat->fBitWidth / 8)); // frameListSize = CalculateNumSamplesPerFrameList (curSampleRate.whole, numFramesPerList) * (newFormat->fNumChannels * (newFormat->fBitWidth / 8)); debug3IOLog ("the frameListSize is %d, the bufferSize is %d\n", frameListSize, bufferSize); } else { if (kUSBIn == direction) { // USB says that if the device is running at an even multiple of the bus clock (i.e. 48KHz) that it can send frames that // have +/- one sample (i.e. 47, 48, or 49 samples per frame) from the average. This differs from when it's not a even // multiple and it can send only +1.5 sample from the average. if (NUM_FRAMES_PER_LIST == numFramesPerList) { numFramesPerList = 8; } frameListSize = (averageFrameSize + 1) * numFramesPerList; bufferSize = averageFrameSize * numFramesPerList * numFrameLists; readFrameListSize = frameListSize; debug2IOLog ("frameListSize is (averageFrameSize + 1) * numFramesPerList = %d\n", frameListSize); safetyOffset = 16 * averageFrameSize; } else { frameListSize = averageFrameSize * numFramesPerList; bufferSize = frameListSize * numFrameLists; readFrameListSize = frameListSize; debug2IOLog ("frameListSize is averageFrameSize * numFramesPerList = %d\n", frameListSize); } } // Dispose of the old frame list buffers and make new ones with the new size if (NULL != soundBuffer) { for (i = 0; i < oldNumFrameLists; i++) { if (NULL != soundBuffer[i]) { soundBuffer[i]->release (); soundBuffer[i] = NULL; } } } if (oldNumFrameLists != numFrameLists) { if (NULL != usbCompletion) { IOFree (usbCompletion, oldNumFrameLists * sizeof (IOUSBIsocCompletion)); usbCompletion = NULL; } } if (oldNumFramesPerList != numFramesPerList) { if (NULL != theFrames) { IOFree (theFrames, oldNumFramesPerList * numFrameListsToQueue * sizeof (IOUSBIsocFrame)); theFrames = NULL; } } // Make everything again at the right size for the new format if (NULL == usbCompletion) { usbCompletion = (IOUSBIsocCompletion *)IOMalloc (numFrameLists * sizeof (IOUSBIsocCompletion)); FailIf (NULL == usbCompletion, Exit); } if (NULL == theFrames) { theFrames = (IOUSBIsocFrame *)IOMalloc (numFramesPerList * numFrameListsToQueue * sizeof (IOUSBIsocFrame)); FailIf (NULL == theFrames, Exit); } // aml 7.16.02 iSub filters are set up for these sample rates: if (NULL != iSubBufferMemory) { if ((8000 != curSampleRate.whole) && (11025 != curSampleRate.whole) && (22050 != curSampleRate.whole) && (44100 != curSampleRate.whole) && (48000 != curSampleRate.whole) && (96000 != curSampleRate.whole)) { if (NULL != lowFreqSamples) { IOFree (lowFreqSamples, getSampleBufferSize () * 2); } if (NULL != highFreqSamples) { IOFree (highFreqSamples, getSampleBufferSize () * 2); } // Close our connection to the iSub because we can't use it if we're running at a rate other than 44.1kHz iSubTeardownConnection (); } } if (oldBufferSize != bufferSize) { if (NULL != theStartBuffer) { IOFree (theStartBuffer, theStartBufferSize); } theStartBuffer = IOMalloc (alternateFrameSize); FailIf (NULL == theStartBuffer, Exit); bzero (theStartBuffer, alternateFrameSize); theStartBufferSize = alternateFrameSize; if (NULL != soundBuffer) { IOFree (soundBuffer, oldNumFrameLists * sizeof (IOMemoryDescriptor *)); } soundBuffer = (IOMemoryDescriptor **)IOMalloc (numFrameLists * sizeof (IOMemoryDescriptor *)); FailIf (NULL == soundBuffer, Exit); if (kUSBIn == direction) { if (NULL != readBuffer) { IOFree (readBuffer, oldNumFrameLists * oldReadFrameListSize); } readBuffer = IOMallocAligned (round_page (numFrameLists * readFrameListSize), PAGE_SIZE); FailIf (NULL == readBuffer, Exit); } if (NULL != iSubBufferMemory) { if (NULL != lowFreqSamples) { IOFree (lowFreqSamples, getSampleBufferSize () * 2); } if (NULL != highFreqSamples) { IOFree (highFreqSamples, getSampleBufferSize () * 2); } // aml XXX iSub filters are set up for these sample rates: if ((8000 == curSampleRate.whole) || (11025 == curSampleRate.whole) || (22050 == curSampleRate.whole) || (44100 == curSampleRate.whole) || (48000 == curSampleRate.whole) || (96000 == curSampleRate.whole)) { lowFreqSamples = (float *)IOMallocAligned (round_page (bufferSize * 2), PAGE_SIZE); FailIf (NULL == lowFreqSamples, Exit); highFreqSamples = (float *)IOMallocAligned (round_page (bufferSize * 2), PAGE_SIZE); FailIf (NULL == highFreqSamples, Exit); } } if (NULL != getSampleBuffer ()) { IOFree (getSampleBuffer (), getSampleBufferSize ()); } sampleBuffer = IOMallocAligned (round_page (bufferSize), PAGE_SIZE); FailIf (NULL == sampleBuffer, Exit); mainStream->setSampleBuffer (sampleBuffer, bufferSize); } setNumSampleFramesPerBuffer (bufferSize / (newFormat->fNumChannels * (newFormat->fBitWidth / 8))); if (kUSBIn == direction) { // Set the latency for input devices (microphones, etc.) // You have to wait for an entire frame list to be recorded so that we can copy the data to the HAL in our completion routine. // setSampleOffset (frameListSize / (newFormat->fNumChannels * (newFormat->fBitWidth / 8)) * 2); setSampleOffset (safetyOffset); setSampleLatency (averageFrameSamples * kMinimumFrameOffset); } else { // Set the latency for output devices (speakers, etc.) // Set it to two milliseconds instead of just one to help prevent breakups setSampleOffset ((additionalSampleFrameFreq ? averageFrameSamples + 1 : averageFrameSamples) * 2); // setSampleOffset (additionalSampleFrameFreq ? averageFrameSamples + 1 : averageFrameSamples); setSampleLatency (averageFrameSamples * kMinimumFrameOffset); } debug2IOLog ("called setNumSampleFramesPerBuffer with %d\n", bufferSize / (newFormat->fNumChannels * (newFormat->fBitWidth / 8))); debug4IOLog ("frameListSize = %d, newFormat->fNumChannels = %d, newFormat->fBitWidth %d\n", frameListSize, newFormat->fNumChannels, newFormat->fBitWidth); debug2IOLog ("called setSampleOffset with %d\n", numFramesPerList); result = PrepareFrameLists (numFrameLists, (newFormat->fBitWidth / 8), newFormat->fNumChannels); FailIf (kIOReturnSuccess != result, Exit); Exit: debug2IOLog ("-AppleUSBAudioEngine::performFormatChange, result = %x\n", result); return result; } IOReturn AppleUSBAudioEngine::PrepareFrameLists (UInt32 numFrameLists, UInt8 sampleSize, UInt8 numChannels) { IOReturn result; UInt32 i; // UInt32 offset; // UInt32 thisFrameListSize; // UInt16 averageFrameSamples; // UInt16 additionalSampleFrameFreq; debug4IOLog ("+AppleUSBAudioEngine::PrepareFrameLists (%ld, %d, %d)\n", numFrameLists, sampleSize, numChannels); result = kIOReturnError; // assume failure for (i = 0; i < numFrameLists; i++) { usbCompletion[i].target = (void *)this; usbCompletion[i].parameter = (void *)i; // what frame list index this buffer is } // CalculateSamplesPerFrame (curSampleRate.whole, &averageFrameSamples, &additionalSampleFrameFreq); if (getDirection () == kIOAudioStreamDirectionOutput) { if (NULL != theAssociatedPipe) { sampleRateCompletion.target = (void *)this; sampleRateCompletion.parameter = 0; sampleRateCompletion.action = sampleRateHandler; neededSampleRate = IOMemoryDescriptor::withAddress (&aveSampleRateBuf, 4, kIODirectionNone); FailIf (NULL == neededSampleRate, Exit); } // This code isn't needed unless we start running "interesting" frame list sizes // offset = 0; // if (additionalSampleFrameFreq == numFramesPerList) { // thisFrameListSize = frameListSize; // } else { // thisFrameListSize = frameListSize - (sampleSize * numChannels); // } for (i = 0; i < numFrameLists; i++) { usbCompletion[i].action = writeHandler; // if (additionalSampleFrameFreq) { // soundBuffer[i] = IOMemoryDescriptor::withAddress (((UInt8 *)getSampleBuffer ()) + offset, thisFrameListSize, kIODirectionNone); // FailIf (NULL == soundBuffer[i], Exit); // offset += thisFrameListSize; // if (i % (additionalSampleFrameFreq / numFramesPerList) == 0) { // thisFrameListSize = frameListSize; // } else { // thisFrameListSize = frameListSize - (sampleSize * numChannels); // } // } else { soundBuffer[i] = IOMemoryDescriptor::withAddress (((UInt8 *)getSampleBuffer ()) + (i * frameListSize), frameListSize, kIODirectionNone); // } } } else if (getDirection () == kIOAudioStreamDirectionInput) { for (i = 0; i < numFrameLists; i++) { usbCompletion[i].action = readHandler; soundBuffer[i] = IOMemoryDescriptor::withAddress (((UInt8 *)readBuffer) + (i * readFrameListSize), readFrameListSize, kIODirectionNone); FailIf (NULL == soundBuffer[i], Exit); } } result = kIOReturnSuccess; Exit: debugIOLog ("-AppleUSBAudioEngine::PrepareFrameLists ()\n"); return result; } IOReturn AppleUSBAudioEngine::readFrameList (UInt32 frameListNum) { const IOAudioStreamFormat * theFormat; UInt32 i, firstFrame; UInt16 averageFrameSamples; UInt16 additionalSampleFrameFreq; UInt16 bytesToRead; IOReturn result; result = kIOReturnError; firstFrame = (frameListNum % numFrameListsToQueue) * numFramesPerList; theFormat = mainStream->getFormat (); CalculateSamplesPerFrame (curSampleRate.whole, &averageFrameSamples, &additionalSampleFrameFreq); bytesPerSampleFrame = theFormat->fNumChannels * (theFormat->fBitWidth / 8); if (0 != additionalSampleFrameFreq) { bytesToRead = (averageFrameSamples + 1) * bytesPerSampleFrame; } else { bytesToRead = averageFrameSamples * bytesPerSampleFrame; } for (i = 0; i < numFramesPerList; i++) { theFrames[firstFrame + i].frStatus = -1; theFrames[firstFrame + i].frReqCount = bytesToRead; theFrames[firstFrame + i].frActCount = 0; } FailIf (NULL == thePipe, Exit); // retain (); // Don't terminate the driver until the completion routine has run. result = thePipe->Read (soundBuffer[frameListNum], theFirstFrame, numFramesPerList, &theFrames[firstFrame], &usbCompletion[frameListNum]); if (result) debug2IOLog ("error from thePipe->Read 0x%X\n", result); theFirstFrame += numFramesPerList; Exit: return result; } void AppleUSBAudioEngine::readHandler (void * object, void * parameter, IOReturn result, IOUSBIsocFrame * pFrames) { AppleUSBAudioEngine * self; // AbsoluteTime timestampOffset; // AbsoluteTime time; // AbsoluteTime offset; // SInt64 busOffset; // UInt64 a; // UInt64 b; #if DEBUGTIMESTAMPS static AbsoluteTime lastTimeCalled = {0, 0}; AbsoluteTime timeAdded; #endif #if DEBUGTIMESTAMPS || DEBUGZEROTIME // AbsoluteTime diff; // UInt64 nanos; #endif UInt64 currentUSBFrame; // UInt32 frameIndex; // UInt32 frameListIndex; UInt32 frameListToRead; // UInt32 numBytesToCopy; // UInt32 numBytesToEnd; // UInt32 numBytesCopied; // UInt8 * source; // UInt8 * dest; // Boolean haveWrapped; self = (AppleUSBAudioEngine *)object; // frameListIndex = (UInt32)parameter; FailIf (NULL == self->streamInterface, Exit); #if DEBUGTIMESTAMPS clock_get_uptime (&diff); if (lastTimeCalled.hi == 0 && lastTimeCalled.lo == 0) { lastTimeCalled.hi = self->status->fLastLoopTime.hi; lastTimeCalled.lo = self->status->fLastLoopTime.lo; nanoseconds_to_absolutetime ((self->numFramesPerList) * (1000 * 1000), &timeAdded); SUB_ABSOLUTETIME (&lastTimeCalled, &timeAdded); } SUB_ABSOLUTETIME (&diff, &lastTimeCalled); absolutetime_to_nanoseconds (diff, &nanos); IOLog ("%ld\n", (UInt32)(nanos / (1000 * 1000))); clock_get_uptime (&diff); lastTimeCalled = diff; #endif if (kIOReturnSuccess != result) { #if DEBUGLOG IOLog ("Error 0x%X from USB read\n", result); #endif currentUSBFrame = self->streamInterface->GetDevice()->GetBus()->GetFrameNumber (); // skip ahead and see if that helps if (self->theFirstFrame <= currentUSBFrame) { self->theFirstFrame = currentUSBFrame + kMinimumFrameOffset; self->expectedBusFrame += kMinimumFrameOffset; } } // haveWrapped = FALSE; // dest = (UInt8 *)(self->getSampleBuffer ()) + self->bufferOffset; // source = ((UInt8 *)self->readBuffer) + (frameListIndex * self->readFrameListSize); // if (self->bufferOffset == 0 && !self->startingEngine) { // haveWrapped = TRUE; // } self->startingEngine = FALSE; // The engine is fully started (it's no longer starting... it's running) self->CoalesceInputSamples (0); // for (frameIndex = 0; frameIndex < self->numFramesPerList; frameIndex++) { // if (pFrames[frameIndex].frActCount < (pFrames[frameIndex].frReqCount - self->bytesPerSampleFrame)) { // #if DEBUGLOG // IOLog ("short read packet!!! %d\n", (UInt32)(pFrames[frameIndex].frActCount)); // #endif // } // numBytesToEnd = self->getSampleBufferSize () - self->bufferOffset; // if ((UInt32)(pFrames[frameIndex].frActCount) > numBytesToEnd) { // numBytesToCopy = numBytesToEnd; // } else { // numBytesToCopy = pFrames[frameIndex].frActCount; // } // memcpy (dest, source, numBytesToCopy); // self->bufferOffset += numBytesToCopy; // numBytesCopied = numBytesToCopy; // if ((UInt32)(pFrames[frameIndex].frActCount) > numBytesToEnd) { // numBytesToCopy = (pFrames[frameIndex].frActCount) - numBytesToEnd; // dest = (UInt8 *)self->getSampleBuffer (); // memcpy (dest, source + numBytesCopied, numBytesToCopy); // self->bufferOffset = numBytesToCopy; // haveWrapped = TRUE; // } // if (TRUE == haveWrapped) { // we wrapped - take timestamp // #if DEBUGZEROTIME // clock_get_uptime (&time); // SUB_ABSOLUTETIME (&time, &self->status->fLastLoopTime); // nanoseconds_to_absolutetime (self->numFramesPerList * 1000 * 1000, ×tampOffset); // ADD_ABSOLUTETIME (&time, ×tampOffset); // absolutetime_to_nanoseconds (time, &nanos); // IOLog ("d=%ld, f#=%ld\n", (UInt32)(nanos / (1000 * 1000)), self->currentFrameList); // #endif // do { // a = self->streamInterface->GetDevice()->GetBus()->GetFrameNumber (); // clock_get_uptime (&time); // b = self->streamInterface->GetDevice()->GetBus()->GetFrameNumber (); // } while (a != b); // time = offset - (a - expected) * 1ms // busOffset = a - (self->expectedBusFrame /*+ self->numFramesPerList*/); // nanoseconds_to_absolutetime (busOffset * 1000 * 1000, &offset); // SUB_ABSOLUTETIME (&time, &offset); // time -= offset // self->expectedBusFrame += self->numFramesPerList * self->numFrameLists; // nanoseconds_to_absolutetime (self->numFramesPerList * 1000 * 1000, ×tampOffset); // clock_get_uptime (&time); // SUB_ABSOLUTETIME (&time, ×tampOffset); // self->takeTimeStamp (TRUE, &time); // haveWrapped = FALSE; // } // dest += numBytesToCopy; // source += pFrames[frameIndex].frReqCount; // } if (self->shouldStop > 0) { #if DEBUGLOG IOLog("++AppleUSBAudioEngine::readHandler() - stopping: %d\n", self->shouldStop); #endif self->shouldStop++; if (self->shouldStop > self->numFrameListsToQueue && TRUE == self->terminatingDriver) { self->streamInterface->close (self); self->streamInterface = NULL; } } else { if (((UInt32)self->numFrameLists - 1) == self->currentFrameList) { self->currentFrameList = 0; } else { self->currentFrameList++; } frameListToRead = self->currentFrameList + self->numFrameListsToQueue - 1; if (frameListToRead >= self->numFrameLists) { frameListToRead -= self->numFrameLists; } self->readFrameList (frameListToRead); } Exit: // self->release (); // It's safe to terminate the driver now. return; } void AppleUSBAudioEngine::resetClipPosition (IOAudioStream *audioStream, UInt32 clipSampleFrame) { // aml 7.16.02, need to check for iSubEngine too [2999205] if ((NULL != iSubBufferMemory) && (NULL != iSubEngine)) { srcPhase = 1.0; // aml 3.4.02 srcState = 0.0; // aml 3.4.02 // start the filter over again since old filter state is invalid filterState.xl_1 = 0.0; filterState.xr_1 = 0.0; filterState.xl_2 = 0.0; filterState.xr_2 = 0.0; filterState.yl_1 = 0.0; filterState.yr_1 = 0.0; filterState.yl_2 = 0.0; filterState.yr_2 = 0.0; // aml 2.14.02 added for 4th order filter filterState2.xl_1 = 0.0; filterState2.xr_1 = 0.0; filterState2.xl_2 = 0.0; filterState2.xr_2 = 0.0; filterState2.yl_1 = 0.0; filterState2.yr_1 = 0.0; filterState2.yl_2 = 0.0; filterState2.yr_2 = 0.0; // aml 2.18.02 added for 4th order filter phase compensator phaseCompState.xl_1 = 0.0; phaseCompState.xr_1 = 0.0; phaseCompState.xl_2 = 0.0; phaseCompState.xr_2 = 0.0; phaseCompState.yl_1 = 0.0; phaseCompState.yr_1 = 0.0; phaseCompState.yl_2 = 0.0; phaseCompState.yr_2 = 0.0; #if DEBUGLOG IOLog ("+resetClipPosition, iSubBufferOffset=%ld, previousClippedToFrame=%ld, clipSampleFrame=%ld\n", iSubBufferOffset, previousClippedToFrame, clipSampleFrame); #endif UInt32 clipAdjustment; if (previousClippedToFrame < clipSampleFrame) { // aml 3.13.02 added iSubEngine format instead of hard coded 2, changed divide by 4 to include channels // iSubBufferOffset -= (previousClippedToFrame + ((getSampleBufferSize () / (2 * iSubEngine->GetNumChannels())) - clipSampleFrame)) * iSubEngine->GetNumChannels(); // aml 3.21.02, calc temp var clipAdjustment = (previousClippedToFrame + ((getSampleBufferSize () / (2 * iSubEngine->GetNumChannels())) - clipSampleFrame)) * iSubEngine->GetNumChannels(); } else { // aml 3.13.02 added iSubEngine format instead of hard coded 2 //iSubBufferOffset -= (previousClippedToFrame - clipSampleFrame) * iSubEngine->GetNumChannels(); // aml 3.21.02, calc temp var clipAdjustment = (previousClippedToFrame - clipSampleFrame) * iSubEngine->GetNumChannels(); } // aml 3.21.02, adjust for different sample rates clipAdjustment = (clipAdjustment * 1000) / ((1000 * getSampleRate()->whole) / iSubEngine->GetSampleRate()); iSubBufferOffset -= clipAdjustment; if (iSubBufferOffset < 0) { iSubBufferOffset += (iSubBufferMemory->getLength () / 2); iSubLoopCount--; // aml 3.29.02 added to match AOA } previousClippedToFrame = clipSampleFrame; justResetClipPosition = TRUE; // aml 3.29.02 added to match AOA #if DEBUGLOG IOLog ("-resetClipPosition, iSubBufferOffset=%ld, previousClippedToFrame=%ld\n", iSubBufferOffset, previousClippedToFrame); #endif } } /* The purpose of this function is to deal with asynchronous synchronization of isochronous output streams. On devices that can lock their output clock to an external source, they can report that value to the driver so that the driver doesn't feed data too quickly or too slowly to the device (so that the device's FIFO's don't overrun or underrun). The device returns a 10.14 unsigned fixed point value in a 24 bit result. This value says how many samples per frame the device wants for the current sampling period. The device reports the current sampling period in its alternate endpoint, which can be retrieved with the GetIsocAssociatedEndpointRefreshInt call (which is interpreted as 2^(10-x) frames where x is the value returned by GetIsocAssociatedEndpointRefreshInt). The endpoint should not be read from more often than once every 2^(10-x) frames as the number isn't updated by the device any more often than that. Because x can range from 1 to 9, the sample rate may need to be adjusted anywhere from once every 2 frames to once every 512 frames. If the number returned is larger than the last number returned, the device needs more data, if it is smaller than the previous value, the device needs less data. In typical usage, the value should not change by a large value (less than 1% of the clock value). A typical result would be be a value of 0x0b0667 which in 10.14 is 44.10004. This is represented in the driver as 0x2c199c which is the 16.16 value for 44.10004. I don't really like working with fixed point values, so this number is then converted to a normal integer representing the sampling rate (instead of the samples per millisecond), in this case 44100. */ void AppleUSBAudioEngine::sampleRateHandler (void * target, void * parameter, IOReturn result, IOUSBIsocFrame * pFrames) { AppleUSBAudioEngine * self; IOFixed sampleRate; UInt16 fixed; IOFixed fract; UInt32 oldSampleRate; self = (AppleUSBAudioEngine *)target; sampleRate = USBToHostLong (self->aveSampleRateBuf); oldSampleRate = self->averageSampleRate; // turn the 10.14 value returned by the device into a normal UInt32 sampleRate = sampleRate << 2; fixed = sampleRate >> 16; self->averageSampleRate = fixed * 1000; fract = IOFixedMultiply (sampleRate & 0x0000FFFF, 1000 << 16); self->averageSampleRate += (fract & 0xFFFF0000) >> 16; if (self->averageSampleRate != oldSampleRate) { // The device has changed the sample rate that it needs, let's roll with the new sample rate self->sampleNum = 0; // This will cause us to recalculate the samples per packet the next time we queue up some packets. debugIOLog ("sample rate changed!!!!!\n"); debug2IOLog ("requestedFrameRate = %d\n", self->averageSampleRate); } self->theSampleRateFrame.frStatus = -1; self->theSampleRateFrame.frReqCount = 3; self->theSampleRateFrame.frActCount = 0; if (0 == self->shouldStop) { self->nextSynchReadFrame += (1 << (10 - self->refreshInterval)); // self->retain (); // Don't dispose of the driver until the completion routine runs. result = self->theAssociatedPipe->Read (self->neededSampleRate, self->nextSynchReadFrame, 1, &self->theSampleRateFrame, &self->sampleRateCompletion); } // self->release (); // It's safe to terminate the driver now (there is a retain from the orignal read outside of this completion as well). return; } IOReturn AppleUSBAudioEngine::SetSampleRate (USBAudioConfigObject *usbAudio, UInt32 sampleRate) { IOUSBDevRequest devReq; UInt32 theSampleRate; IOReturn result; result = kIOReturnError; if (usbAudio->IsocEndpointHasSampleFreqControl (ourInterfaceNumber, alternateInterfaceID)) { devReq.bmRequestType = USBmakebmRequestType (kUSBOut, kUSBClass, kUSBEndpoint); devReq.bRequest = SET_CUR; devReq.wValue = (SAMPLING_FREQ_CONTROL << 8) | 0; devReq.wIndex = usbAudio->GetIsocEndpointAddress (ourInterfaceNumber, alternateInterfaceID, direction); devReq.wLength = 3; theSampleRate = OSSwapHostToLittleInt32 (sampleRate); devReq.pData = &theSampleRate; debug5IOLog ("Set freq control interface %d, alt interface %d, endpoint address 0x%X, sample rate (little endian) 0x%X\n", ourInterfaceNumber, alternateInterfaceID, devReq.wIndex, theSampleRate); result = streamInterface->GetDevice()->DeviceRequest (&devReq); FailIf (kIOReturnSuccess != result, Exit); } else { result = kIOReturnSuccess; debugIOLog ("Device doesn't have a sample freq control!\n"); } Exit: return result; } IOReturn AppleUSBAudioEngine::startUSBStream () { IOReturn resultCode; IOUSBFindEndpointRequest audioIsochEndpoint; USBAudioConfigObject * usbAudio; AbsoluteTime uptime; AbsoluteTime initialTimestampOffset; UInt32 frameListNum; UInt32 numQueued; UInt32 numRetries; debug2IOLog ("+AppleUSBAudioEngine[%p]::startUSBStream ()\n", this); resultCode = kIOReturnError; numQueued = 0; // Start the IO audio engine // Enable interrupts for this stream // The interrupt should be triggered at the start of the sample buffer // The interrupt handler should increment the fCurrentLoopCount and fLastLoopTime fields // We may need to restart at block 0 here temporarily // It shouldn't be necessary, but I don't think the HAL is flushing the stream // Which causes extra sample data to be left over in the buffer when it disconnects // Those samples get played the next time the stream is opened if (status->fLastLoopTime.hi == 0 && status->fLastLoopTime.lo == 0) { // nanoseconds_to_absolutetime ((kMinimumFrameOffset) * 1000 * 1000, &initialTimestampOffset); clock_get_uptime (&uptime); absolutetime_to_nanoseconds (uptime, &startTime); // ADD_ABSOLUTETIME (&uptime, &initialTimestampOffset); // takeTimeStamp (FALSE, &uptime); // status->fLastLoopTime.hi = uptime.hi; // status->fLastLoopTime.lo = uptime.lo; currentFrameList = 0; bufferOffset = 0; previous = 0; startingEngine = TRUE; previousWrappedReadUSBFrame = 0; } shouldStop = 0; FailIf ((numFrameLists < numFrameListsToQueue), Exit); numRetries = 0; // Allocate the pipe now so that we don't keep it open when we're not streaming audio to the device. usbAudio = usbAudioDevice->GetUSBAudioConfigObject (); FailIf (NULL == usbAudio, Exit); // It is important to configure the root interface (0) prior to configuring the // target interface to guarantee that the device will stream correctly. // We need to set the alternate interface that supports the format that we will be sending it. FailIf (NULL == streamInterface, Exit); resultCode = streamInterface->SetAlternateInterface (this, kRootAlternateSetting); FailIf (kIOReturnSuccess != resultCode, Exit); debug2IOLog ("streamInterface->SetAlternateInterface (this, 0) = 0x%X\n", resultCode); resultCode = streamInterface->SetAlternateInterface (this, alternateInterfaceID); FailIf (kIOReturnSuccess != resultCode, Exit); debug3IOLog ("streamInterface->SetAlternateInterface (this, %d) = 0x%X\n", alternateInterfaceID, resultCode); SetSampleRate (usbAudio, curSampleRate.whole); // Acquire a PIPE for the isochronous stream. audioIsochEndpoint.type = kUSBIsoc; audioIsochEndpoint.direction = direction; thePipe = streamInterface->FindNextPipe (NULL, &audioIsochEndpoint); FailIf (NULL == thePipe, Exit); thePipe->retain (); // Not concerned with errors in this function, and I think the emi 2|6 triggers a false error anyway (void)CheckForAssociatedEndpoint (usbAudio); theMaxPacket = thePipe->GetEndpoint()->maxPacketSize; FailIf (0 == theMaxPacket, Exit); // Make sure we have an endpoint that accepts data. theFirstFrame = streamInterface->GetDevice()->GetBus()->GetFrameNumber() + kMinimumFrameOffset + 1; // Make room for a short transfer to prime things if (getDirection () == kIOAudioStreamDirectionInput) { nanoseconds_to_absolutetime ((kMinimumFrameOffset + 1) * 1000 * 1000, &initialTimestampOffset); ADD_ABSOLUTETIME (&uptime, &initialTimestampOffset); takeTimeStamp (FALSE, &uptime); if (NUM_FRAMES_PER_LIST == numFramesPerList) { numFramesPerList = 8; } expectedBusFrame = theFirstFrame; // for input, this variable is just the current frame } else { expectedBusFrame = theFirstFrame - 1; theStartFrame.frStatus = -1; theStartFrame.frReqCount = theStartBufferSize; theStartFrame.frActCount = 0; theStartBufferDescriptor = IOMemoryDescriptor::withAddress (theStartBuffer, theStartBufferSize, kIODirectionNone); theStartCompletion.target = (void *)this; theStartCompletion.parameter = 0; theStartCompletion.action = writeHandler; FailIf (NULL == theStartBufferDescriptor, Exit); // retain (); // Don't nobody go no where... // IOLog ("thePipe->Write (%p, 0x%lx%lx, %ld, %p, %p);\n", theStartBufferDescriptor, (theFirstFrame - 1) >> 32, (UInt32)(theFirstFrame - 1), 1, &theStartFrame, theStartCompletion); thePipe->Write (theStartBufferDescriptor, theFirstFrame - 1, 1, &theStartFrame, &theStartCompletion); } // expectedBusFrame = theFirstFrame + numFramesPerList * numFrameLists; if (NULL != theAssociatedPipe) { nextSynchReadFrame = theFirstFrame; // retain (); // Don't dispose of the driver until the completion routine runs. (void)theAssociatedPipe->Read (neededSampleRate, nextSynchReadFrame, 1, &theSampleRateFrame, &sampleRateCompletion); } for (frameListNum = currentFrameList; (frameListNum < numFrameLists) && (numQueued < numFrameListsToQueue); frameListNum++) { while ((getDirection () == kIOAudioStreamDirectionOutput ? (writeFrameList (frameListNum) != kIOReturnSuccess) : (readFrameList (frameListNum) != kIOReturnSuccess)) && (numRetries < MAX_WRITE_RETRIES)) { numRetries++; } FailIf ((numRetries == MAX_WRITE_RETRIES), Exit); numQueued++; } numRetries = 0; for (frameListNum = 0; numQueued < numFrameListsToQueue; frameListNum++) { while ((getDirection () == kIOAudioStreamDirectionOutput ? (writeFrameList (frameListNum) != kIOReturnSuccess) : (readFrameList (frameListNum) != kIOReturnSuccess)) && (numRetries < MAX_WRITE_RETRIES)) { numRetries++; } FailIf ((numRetries == MAX_WRITE_RETRIES), Exit); numQueued++; } usbStreamRunning = TRUE; resultCode = kIOReturnSuccess; Exit: debug2IOLog ("-AppleUSBAudioEngine[%p]::startUSBStream ()\n", this); return resultCode; } IOReturn AppleUSBAudioEngine::stopUSBStream () { debug2IOLog ("+AppleUSBAudioEngine[%p]::stopUSBStream ()\n", this); if (NULL != streamInterface) { // if we don't have an interface, message() got called and we are being terminated shouldStop = 1; } if (NULL != thePipe) { // Have to close the current pipe so we can open a new one because changing the alternate interface will tear down the current pipe thePipe->release (); thePipe = NULL; } if (NULL != theAssociatedPipe) { theAssociatedPipe->release (); theAssociatedPipe = NULL; } usbStreamRunning = FALSE; if (NULL != streamInterface) { // if we don't have an interface, message() got called and we are being terminated (void)streamInterface->SetAlternateInterface (this, kRootAlternateSetting); } debug2IOLog ("-AppleUSBAudioEngine[%p]::stopUSBStream ()\n", this); return kIOReturnSuccess; } bool AppleUSBAudioEngine::willTerminate (IOService * provider, IOOptionBits options) { debug3IOLog ("+AppleUSBAudioEngine[%p]::willTerminate (%p)\n", this, provider); if (iSubEngine == (AppleiSubEngine *)provider) { debugIOLog ("iSub requesting termination\n"); iSubTeardownConnection (); // Set up notifier to run when iSub shows up again if (iSubAttach->getIntValue ()) { iSubEngineNotifier = addNotification (gIOPublishNotification, serviceMatching ("AppleiSubEngine"), (IOServiceNotificationHandler)&iSubEnginePublished, this); } } if (streamInterface == provider) { terminatingDriver = TRUE; if (FALSE == usbStreamRunning) { // Close our stream interface and go away because we're not running. debugIOLog ("willTerminate closing down\n"); streamInterface->close (this); streamInterface = NULL; } else { // Have the write completion routine clean everything up because we are running. debug2IOLog ("willTerminate checking shouldStop = 0x%x\n", shouldStop); if (0 == shouldStop) { shouldStop++; } } iSubCloseAction (this, NULL, NULL, NULL, NULL); } debug2IOLog ("-AppleUSBAudioEngine[%p]::willTerminate\n", this); return super::willTerminate (provider, options); } IOReturn AppleUSBAudioEngine::writeFrameList (UInt32 frameListNum) { const IOAudioStreamFormat * theFormat; UInt32 i, firstFrame; #if FIXEDSIZEPACKETS || DEBUGLOG UInt16 averageFrameSamples; UInt16 averageFrameSize; UInt16 alternateFrameSize; UInt16 additionalSampleFrameFreq; #endif IOReturn result; #if !FIXEDSIZEPACKETS UInt32 sampleRate; UInt32 bytesPerSample; UInt32 bytesThisFrameList; IOFixed fixedSampleRate; #endif // IOLog ("+writeFrameList\n"); result = kIOReturnError; firstFrame = (frameListNum % numFrameListsToQueue) * numFramesPerList; theFormat = mainStream->getFormat (); #if FIXEDSIZEPACKETS || DEBUGLOG CalculateSamplesPerFrame (curSampleRate.whole, &averageFrameSamples, &additionalSampleFrameFreq); averageFrameSize = averageFrameSamples * (theFormat->fNumChannels * (theFormat->fBitWidth / 8)); #endif #if FIXEDSIZEPACKETS alternateFrameSize = (averageFrameSamples + 1) * (theFormat->fNumChannels * (theFormat->fBitWidth / 8)); if (additionalSampleFrameFreq) { if (additionalSampleFrameFreq == numFramesPerList) { // IOLog ("additionalSampleFrameFreq == numFramesPerList\n"); for (i = 0; i < numFramesPerList; i++) { theFrames[firstFrame + i].frStatus = -1; if ((i % additionalSampleFrameFreq) == (UInt16)(additionalSampleFrameFreq - 1)) { theFrames[firstFrame + i].frReqCount = alternateFrameSize; // IOLog ("%d ", alternateFrameSize); } else { theFrames[firstFrame + i].frReqCount = averageFrameSize; // IOLog ("%d ", averageFrameSize); } } } else { for (i = 0; i < numFramesPerList; i++) { if (((i + previous++) % additionalSampleFrameFreq) == (UInt16)(additionalSampleFrameFreq - 1)) { theFrames[firstFrame + i].frReqCount = alternateFrameSize; } else { theFrames[firstFrame + i].frReqCount = averageFrameSize; } if (previous == additionalSampleFrameFreq) { previous = 0; } } theFrames[firstFrame + i].frActCount = 0; } } else { for (i = 0; i < numFramesPerList; i++) { theFrames[firstFrame + i].frStatus = -1; theFrames[firstFrame + i].frReqCount = averageFrameSize; theFrames[firstFrame + i].frActCount = 0; } } #else sampleRate = averageSampleRate; // we want to make packets based on what the hardware is telling us it _really_ needs, not it's overall average sample rate bytesPerSample = theFormat->fNumChannels * (theFormat->fBitWidth / 8); fixedSampleRate = IOUFixedDivide (sampleRate << 16, 1000 << 16); // we want a number like 44.101 to represent 44101Hz if (1000 == sampleNum || 0 == sampleNum) { samplesThisFrame = fixedSampleRate >> 16; sampleNum = 2; sum = samplesThisFrame; } for (i = 0; i < numFramesPerList; i++) { theFrames[firstFrame + i].frStatus = -1; theFrames[firstFrame + i].frActCount = 0; theFrames[firstFrame + i].frReqCount = samplesThisFrame * bytesPerSample; bytesThisFrameList += theFrames[firstFrame + i].frReqCount; bytesQueued += bytesThisFrameList; samplesThisFrame = (IOUFixedMultiply (fixedSampleRate, sampleNum << 16) >> 16) - sum; #if DEBUGLOG if (samplesThisFrame < averageFrameSamples || samplesThisFrame > (averageFrameSamples + 1)) { IOLog ("error !!! samplesThisFrame = %d\n", samplesThisFrame); IOLog ("sum = %d\n", sum); IOLog ("sampleNum = %d\n", sampleNum); } #endif sum += samplesThisFrame; sampleNum++; if (1000 == sampleNum) { samplesThisFrame = fixedSampleRate >> 16; sampleNum = 2; sum = samplesThisFrame; } } #endif /* if ((frameListNum % 20) == 0) { debug4IOLog("AppleUSBAudioEngine::writeFrameList(%d) to frame: %lu - at frame: %lu\n", frameListNum, (UInt32)theFirstFrame, (UInt32)streamInterface->GetFrameNumber()); } */ /* // test frame lists { UInt32 i, j, numSegs, offset; IOPhysicalSegment segs[2]; IONaturalMemoryCursor * _isoCursor; offset = 0; _isoCursor = IONaturalMemoryCursor::withSpecification (1023, 1023); if (NULL == _isoCursor) { IOLog ("********couldn't allocate the memory cursor*********\n"); } else { for (j = 0; j < numFrameLists; j++) { for (i = 0; i < numFramesPerList; i++) { numSegs = _isoCursor->getPhysicalSegments (soundBuffer[j], offset, segs, 2, theFrames[firstFrame + i].frReqCount); offset += segs[0].length; if (numSegs == 0) { Debugger ("numSegs is 0!"); } } offset = 0; } } } */ FailIf (NULL == thePipe, Exit); // Just in case the device is unplugged while our write is outstanding, we need to make sure that the driver isn't torn down until we complete the write // retain (); // Don't nobody go no where... result = thePipe->Write (soundBuffer[frameListNum], theFirstFrame, numFramesPerList, &theFrames[firstFrame], &usbCompletion[frameListNum]); // IOLog ("++after thePipe->Write\n"); if (result != kIOReturnSuccess) { debug6IOLog ("++AppleUSBAudioEngine[%p]::writeFrameList(%d) - error writing to pipe at frame %lu - current = %lu: 0x%x\n", this, frameListNum, (UInt32)theFirstFrame, (UInt32)streamInterface->GetDevice()->GetBus()->GetFrameNumber(), result); } else { theFirstFrame += numFramesPerList; } Exit: // IOLog ("-writeFrameList\n"); return result; } void AppleUSBAudioEngine::writeHandler (void * object, void * parameter, IOReturn result, IOUSBIsocFrame * pFrames) { AppleUSBAudioEngine * self; AbsoluteTime time; AbsoluteTime delay; UInt32 frameListToWrite; UInt64 a; UInt64 b; UInt64 currentUSBFrame; #if DEBUGTIMESTAMPS static AbsoluteTime lastTimeCalled = {0, 0}; AbsoluteTime timeAdded; #endif #if DEBUGTIMESTAMPS || DEBUGZEROTIME AbsoluteTime newLoopTime; AbsoluteTime oldLoopTime; UInt64 nanos; #endif #if DEBUGLOG UInt64 USBFrameNumber; UInt32 i; #endif // IOLog ("+writeHandler\n"); self = (AppleUSBAudioEngine *)object; FailIf (NULL == self->streamInterface, Exit); // Take initial 0 timestamp, but don't do anything else because this was just a 0 write to get us started if (TRUE == self->startingEngine) { self->startingEngine = FALSE; do { a = self->streamInterface->GetDevice()->GetBus()->GetFrameNumber (); clock_get_uptime (&time); b = self->streamInterface->GetDevice()->GetBus()->GetFrameNumber (); } while (a != b); nanoseconds_to_absolutetime ((a - self->expectedBusFrame) * 1000 * 1000, &delay); SUB_ABSOLUTETIME (&time, &delay); // time -= delay; self->takeTimeStamp (TRUE, &time); self->expectedBusFrame += self->numFramesPerList * self->numFrameLists; goto Exit; // Don't do anything this time around... } #if DEBUGTIMESTAMPS clock_get_uptime (&diff); if (lastTimeCalled.hi == 0 && lastTimeCalled.lo == 0) { lastTimeCalled.hi = self->status->fLastLoopTime.hi; lastTimeCalled.lo = self->status->fLastLoopTime.lo; nanoseconds_to_absolutetime ((self->numFramesPerList) * (1000 * 1000), &timeAdded); SUB_ABSOLUTETIME (&lastTimeCalled, &timeAdded); } SUB_ABSOLUTETIME (&diff, &lastTimeCalled); absolutetime_to_nanoseconds (diff, &nanos); IOLog ("%ld\n", (UInt32)(nanos / (1000 * 1000))); clock_get_uptime (&diff); lastTimeCalled = diff; #endif if (result != kIOReturnSuccess) { #if DEBUGLOG USBFrameNumber = self->streamInterface->GetDevice()->GetBus()->GetFrameNumber (); IOLog ("++AppleUSBAudioEngine[%p]::writeHandler () - error 0x%x on USB frame 0x%lx%lx\n", self, result, (UInt32)(USBFrameNumber >> 32), (UInt32)USBFrameNumber); IOLog ("current frame list = %d\n", self->currentFrameList); for (i = 0; i < self->numFramesPerList; i++) { if (kIOReturnSuccess != pFrames->frStatus) { IOLog ("fr %d: 0x%lx ", i, pFrames->frStatus); } } IOLog ("\n"); #endif currentUSBFrame = self->streamInterface->GetDevice()->GetBus()->GetFrameNumber (); // skip ahead and see if that helps if (self->theFirstFrame <= currentUSBFrame) { self->theFirstFrame = currentUSBFrame + kMinimumFrameOffset; self->expectedBusFrame += kMinimumFrameOffset; } } // Take timestamp if (((UInt32)self->numFrameLists - 1) == self->currentFrameList) { #if DEBUGZEROTIME oldLoopTime.hi = self->status->fLastLoopTime.hi; oldLoopTime.lo = self->status->fLastLoopTime.lo; #endif do { a = self->streamInterface->GetDevice()->GetBus()->GetFrameNumber (); clock_get_uptime (&time); b = self->streamInterface->GetDevice()->GetBus()->GetFrameNumber (); } while (a != b); nanoseconds_to_absolutetime ((a - self->expectedBusFrame) * 1000 * 1000, &delay); SUB_ABSOLUTETIME (&time, &delay); // time -= delay; self->takeTimeStamp (TRUE, &time); self->expectedBusFrame += self->numFramesPerList * self->numFrameLists; #if DEBUGZEROTIME newLoopTime.hi = self->status->fLastLoopTime.hi; newLoopTime.lo = self->status->fLastLoopTime.lo; SUB_ABSOLUTETIME (&newLoopTime, &oldLoopTime); absolutetime_to_nanoseconds (newLoopTime, &nanos); IOLog ("delta=%ld\n", (UInt32)(nanos / (1000 * 1000))); #endif self->currentFrameList = 0; } else { self->currentFrameList++; } // currentFrameList is the one whose completion we are awaiting if (self->shouldStop > 0) { debug2IOLog ("++AppleUSBAudioEngine::writeHandler() - stopping: %d\n", self->shouldStop); self->shouldStop++; if (self->shouldStop > self->numFrameListsToQueue && TRUE == self->terminatingDriver) { self->streamInterface->close (self); self->streamInterface = NULL; } } else { frameListToWrite = self->currentFrameList + self->numFrameListsToQueue - 1; if (frameListToWrite >= self->numFrameLists) { frameListToWrite -= self->numFrameLists; } self->writeFrameList (frameListToWrite); } Exit: // IOLog ("-writeHandler\n"); // self->release (); // It's safe to be terminated now. return; }