#include "AppleiSubEngine.h" #include "AppleUSBAudioCommon.h" #include "AppleUSBAudioLevelControl.h" #include "AppleUSBAudioMuteControl.h" #include "USBAudioObject.h" #include #include #define kiSubFeatureUnitID 2 #define kiSubMuteControlChannelNum 0 #define kiSubVolumeControlLeftChannelNum 1 #define kiSubVolumeControlRightChannelNum 2 // Has to be true or else the iSub looses the beginning of sounds if it has auto powered down #define CONTINUOUS_STREAMING TRUE #define DEBUGTIMESTAMPS FALSE #define DEBUGLOG FALSE #define super IOService OSDefineMetaClassAndStructors (AppleiSubEngine, super) #pragma mark -IOKit Routines- // This has to be called on the audio engine's work loop so that we don't interrupt the IOAudioFamily void AppleiSubEngine::close (IOService * forClient, IOOptionBits options) { IOReturn result; debug4IOLog ("+AppleiSubEngine[%p]::close (%p, 0x%lx)\n", this, forClient, options); if (NULL != audioEngine) { debugIOLog ("removing iSub audio controls\n"); // audioEngine->pauseAudioEngine (); audioEngine->beginConfigurationChange (); result = audioEngine->removeDefaultAudioControl (leftVolumeControl); #if DEBUGLOG if (kIOReturnSuccess != result) IOLog ("error 0x%x removing left iSub control\n", result); #endif result = audioEngine->removeDefaultAudioControl (rightVolumeControl); #if DEBUGLOG if (kIOReturnSuccess != result) IOLog ("error 0x%x removing right iSub control\n", result); #endif result = audioEngine->removeDefaultAudioControl (muteControl); #if DEBUGLOG if (kIOReturnSuccess != result) IOLog ("error 0x%x removing mute iSub control\n", result); #endif audioEngine->completeConfigurationChange (); // audioEngine->resumeAudioEngine (); audioEngine = NULL; } super::close (forClient, options); debug4IOLog ("-AppleiSubEngine[%p]::close (%p, 0x%lx)\n", this, forClient, options); } bool AppleiSubEngine::finalize (IOOptionBits options) { Boolean resultCode; debug4IOLog ("+AppleiSubEngine[%p]::finalize (%p), rc=%d\n", this, options, getRetainCount ()); resultCode = super::finalize (options); debug5IOLog ("-AppleiSubEngine[%p]::finalize (%p) = %d, rc=%d\n", this, options, resultCode, getRetainCount ()); return resultCode; } void AppleiSubEngine::free (void) { UInt32 frameIndex; debug2IOLog ("+AppleiSubEngine[%p]::free ()\n", this); if (NULL != thePipe) { thePipe->release (); thePipe = NULL; } if (NULL != sampleBufferDescriptor) { sampleBufferDescriptor->release (); sampleBufferDescriptor = NULL; } if (NULL != sampleBuffer) { IOFree (sampleBuffer, bufferSize); sampleBuffer = NULL; } for (frameIndex = 0; frameIndex < NUM_ISUB_FRAME_LISTS; frameIndex++) { if (NULL != soundBuffer[frameIndex]) { soundBuffer[frameIndex]->release (); soundBuffer[frameIndex] = NULL; } } IORecursiveLockFree (interfaceLock); super::free (); debug2IOLog ("-AppleiSubEngine[%p]::free ()\n", this); return; } // This has to be called on the audio engine's work loop so that we don't interrupt the IOAudioFamily bool AppleiSubEngine::handleOpen (IOService * forClient, IOOptionBits options, void * arg) { IOUSBFindInterfaceRequest findRequest; IOUSBFindEndpointRequest audioIsochEndpoint; IOReturn resultIOReturn; UInt32 i; #if CONTINUOUS_STREAMING AbsoluteTime currentTime; AbsoluteTime initialTimestampOffset; UInt32 frameListNum; UInt16 numQueued; #endif bool resultCode; debug5IOLog ("+AppleiSubEngine[%p]::handleOpen (%p, 0x%lx, %p)\n", this, forClient, options, arg); resultCode = FALSE; FailIf (FALSE == super::handleOpen (forClient, options, arg), Exit); FailIf (NULL == forClient, Exit); audioEngine = (IOAudioEngine *)forClient; frameListSize = CalculateNumSamplesPerBuffer (44100, NUM_ISUB_FRAMES_PER_LIST) * 4; debug2IOLog ("frameListSize = %d\n", frameListSize); bufferSize = CalculateNumSamplesPerBuffer (44100, NUM_ISUB_FRAMES_PER_LIST, NUM_ISUB_FRAME_LISTS) * 4; debug2IOLog ("bufferSize = %d\n", bufferSize); sampleBuffer = IOMallocAligned (round_page (bufferSize), PAGE_SIZE); FailIf (NULL == sampleBuffer, Exit); for (i = 0; i < bufferSize / 4; i++) { ((UInt32*)sampleBuffer)[i] = 0; } sampleBufferDescriptor = IOMemoryDescriptor::withAddress (sampleBuffer, bufferSize, kIODirectionNone); FailIf (NULL == sampleBufferDescriptor, Exit); FailIf (kIOReturnSuccess != PrepareFrameLists (frameListSize), Exit); FailIf (FALSE == streamInterface->open (this), Exit); resultIOReturn = streamInterface->SetAlternateInterface (this, kRootAlternateSetting); FailIf (kIOReturnSuccess != resultIOReturn, Exit); resultIOReturn = streamInterface->SetAlternateInterface (this, 4); // This is the 16-bit stereo interface FailIf (kIOReturnSuccess != resultIOReturn, Exit); // Acquire a PIPE for the isochronous stream. audioIsochEndpoint.type = kUSBIsoc; audioIsochEndpoint.direction = kUSBOut; thePipe = streamInterface->FindNextPipe (NULL, &audioIsochEndpoint); FailIf (NULL == thePipe, Exit); thePipe->retain (); #if CONTINUOUS_STREAMING // Start the iSub playing silence now loopCount = 0; nanoseconds_to_absolutetime ((kMinimumiSubFrameOffset) * 1000 * 1000, &initialTimestampOffset); clock_get_uptime (¤tTime); ADD_ABSOLUTETIME (¤tTime, &initialTimestampOffset); lastLoopTime.hi = currentTime.hi; lastLoopTime.lo = currentTime.lo; shouldStop = 0; numQueued = 0; currentFrameList = 0; currentByteOffset = 0; theFirstFrame = streamInterface->GetDevice()->GetBus()->GetFrameNumber () + kMinimumiSubFrameOffset; for (frameListNum = currentFrameList; numQueued < NUM_ISUB_FRAME_LISTS_TO_QUEUE; frameListNum++) { resultIOReturn = WriteFrameList (frameListNum); FailIf (kIOReturnSuccess != resultIOReturn, Exit); numQueued++; } #endif // Find control interface on iSub device findRequest.bInterfaceClass = 1; findRequest.bInterfaceSubClass = 1; findRequest.bInterfaceProtocol = kIOUSBFindInterfaceDontCare; findRequest.bAlternateSetting = kIOUSBFindInterfaceDontCare; controlInterface = streamInterface->GetDevice()->FindNextInterface (NULL, &findRequest); FailIf (NULL == controlInterface, Exit); audioEngine->pauseAudioEngine (); audioEngine->beginConfigurationChange (); leftVolumeControl = AppleUSBAudioLevelControl::create (kiSubFeatureUnitID, controlInterface->GetInterfaceNumber (), VOLUME_CONTROL, kiSubVolumeControlLeftChannelNum, FALSE, (USBDeviceRequest)&deviceRequest, this, 'subL', kIOAudioControlUsageOutput); FailIf (NULL == leftVolumeControl, Exit); audioEngine->addDefaultAudioControl (leftVolumeControl); leftVolumeControl->release (); rightVolumeControl = AppleUSBAudioLevelControl::create (kiSubFeatureUnitID, controlInterface->GetInterfaceNumber (), VOLUME_CONTROL, kiSubVolumeControlRightChannelNum, FALSE, (USBDeviceRequest)&deviceRequest, this, 'subR', kIOAudioControlUsageOutput); FailIf (NULL == rightVolumeControl, Exit); audioEngine->addDefaultAudioControl (rightVolumeControl); rightVolumeControl->release (); muteControl = AppleUSBAudioMuteControl::create (kiSubFeatureUnitID, controlInterface->GetInterfaceNumber (), 0, (USBDeviceRequest)&deviceRequest, this, kIOAudioControlUsageOutput, 'subM'); FailIf (NULL == muteControl, Exit); audioEngine->addDefaultAudioControl (muteControl); muteControl->release (); audioEngine->completeConfigurationChange (); audioEngine->resumeAudioEngine (); resultCode = TRUE; Exit: debug5IOLog ("-AppleiSubEngine[%p]::handleOpen (%p, 0x%lx, %p)\n", this, forClient, options, arg); return resultCode; } bool AppleiSubEngine::init (OSDictionary * properties) { Boolean resultCode; debug3IOLog ("+AppleiSubEngine[%p]::init (%p)\n", this, properties); resultCode = FALSE; FailIf (FALSE == super::init (properties), Exit); resultCode = TRUE; Exit: debug4IOLog ("-AppleiSubEngine[%p]::init (%p) = %d\n", this, properties, resultCode); return resultCode; } IOReturn AppleiSubEngine::message (UInt32 type, IOService * provider, void * arg) { IOReturn resultCode; debug4IOLog ("+AppleiSubEngine[%p]::message (0x%x, %p)\n", this, type, provider); resultCode = kIOReturnSuccess; switch (type) { case kIOMessageServiceIsRequestingClose: debugIOLog ("kIOMessageServiceIsRequestingClose\n"); case kIOMessageServiceIsTerminated: if (kIOMessageServiceIsRequestingClose != type) debugIOLog ("kIOMessageServiceIsTerminated\n"); if ((NULL != streamInterface) && (streamInterface == provider)) { debugIOLog ("stopping iSub\n"); shouldStop = 1; streamInterface->close (this); streamInterface = NULL; } break; default: ; } debug4IOLog ("-AppleiSubEngine[%p]::message (0x%x, %p)\n", this, type, provider); return resultCode; } bool AppleiSubEngine::start (IOService * provider) { Boolean resultBool; debug3IOLog ("+AppleiSubEngine[%p]::start (%p)\n", this, provider); resultBool = FALSE; FailIf (FALSE == super::start (provider), Exit); streamInterface = OSDynamicCast (IOUSBInterface, provider); FailIf (NULL == streamInterface, Exit); ourInterfaceNumber = streamInterface->GetInterfaceNumber (); debug2IOLog ("AppleiSubEngine->ourInterfaceNumber = %d\n", ourInterfaceNumber); alternateInterfaceID = 4; interfaceLock = IORecursiveLockAlloc (); FailIf (NULL == interfaceLock, Exit); registerService (); resultBool = TRUE; Exit: debug4IOLog ("-AppleiSubEngine[%p]::start (%p) = %d\n", this, provider, resultBool); return resultBool; } void AppleiSubEngine::stop (IOService * provider) { debug2IOLog("+AppleiSubEngine[%p]::stop ()\n", this); // We've been asked to go away, so we should really stop now shouldStop = 1; super::stop (provider); debug2IOLog("-AppleiSubEngine[%p]::stop ()\n", this); return; } bool AppleiSubEngine::terminate (IOOptionBits options) { Boolean resultCode; debug4IOLog ("+AppleiSubEngine[%p]::terminate (0x%x), rc=%d\n", this, options, getRetainCount ()); resultCode = super::terminate (options); debug5IOLog ("-AppleiSubEngine[%p]::terminate (0x%x) = %d, rc=%d\n", this, options, resultCode, getRetainCount ()); return resultCode; } #pragma mark -iSub Routines- UInt32 AppleiSubEngine::CalculateNumSamplesPerBuffer (UInt32 sampleRate, UInt32 theNumFramesPerList, UInt32 theNumFrameLists) { UInt32 numSamplesPerFrameList; UInt32 totalFrames; UInt32 numAlternateFrames; UInt32 numAverageFrames; UInt16 averageSamplesPerFrame; UInt16 additionalSampleFrameFreq; CalculateSamplesPerFrame (sampleRate, &averageSamplesPerFrame, &additionalSampleFrameFreq); if (0 == additionalSampleFrameFreq) { numSamplesPerFrameList = averageSamplesPerFrame * theNumFramesPerList * theNumFrameLists; } else { totalFrames = theNumFramesPerList * theNumFrameLists; numAlternateFrames = totalFrames / additionalSampleFrameFreq; numAverageFrames = totalFrames - numAlternateFrames; numSamplesPerFrameList = (numAverageFrames * averageSamplesPerFrame) + (numAlternateFrames * (averageSamplesPerFrame + 1)); } return numSamplesPerFrameList; } void AppleiSubEngine::CalculateSamplesPerFrame (UInt32 sampleRate, UInt16 * averageSamplesPerFrame, UInt16 * additionalSampleFrameFreq) { UInt32 divisor; *averageSamplesPerFrame = sampleRate / 1000; divisor = (sampleRate % 1000); if (divisor) *additionalSampleFrameFreq = 1000 / divisor; else *additionalSampleFrameFreq = 0; } IOReturn AppleiSubEngine::deviceRequest (IOUSBDevRequest * request, AppleiSubEngine * self, IOUSBCompletion * completion) { IOReturn result; debug4IOLog ("+AppleiSubEngine[%p]::deviceRequest (%p, %p)\n", self, request, completion); result = kIOReturnSuccess; if (self->streamInterface && request) { FailIf (NULL == self->interfaceLock, Exit); IORecursiveLockLock (self->interfaceLock); result = self->streamInterface->DeviceRequest (request, completion); IORecursiveLockUnlock (self->interfaceLock); } debug4IOLog ("-AppleiSubEngine[%p]::deviceRequest (%p, %p)\n", self, request, completion); Exit: return result; } UInt32 AppleiSubEngine::GetCurrentByteCount (void) { return currentByteOffset; } UInt16 AppleiSubEngine::GetCurrentFrameList (void) { return currentFrameList; } UInt32 AppleiSubEngine::GetCurrentLoopCount (void) { return loopCount; } IOMemoryDescriptor * AppleiSubEngine::GetSampleBuffer (void) { return sampleBufferDescriptor; } volatile AbsoluteTime * AppleiSubEngine::GetLoopTime (void) { return &lastLoopTime; } IOReturn AppleiSubEngine::PrepareFrameLists (UInt32 frameListSize) { IOReturn result; UInt32 frameIndex; result = kIOReturnError; for (frameIndex = 0; frameIndex < NUM_ISUB_FRAME_LISTS; frameIndex++) { usbCompletion[frameIndex].target = (void *)this; usbCompletion[frameIndex].parameter = (void *)((UInt8 *)sampleBuffer + (frameIndex * frameListSize)); // pointer into the buffer that is the start of this frame list usbCompletion[frameIndex].action = WriteHandler; soundBuffer[frameIndex] = IOMemoryDescriptor::withAddress ((UInt8 *)usbCompletion[frameIndex].parameter, frameListSize, kIODirectionNone); FailIf (NULL == soundBuffer[frameIndex], Exit); } result = kIOReturnSuccess; Exit: return result; } IOReturn AppleiSubEngine::StartiSub (void) { AbsoluteTime currentTime; AbsoluteTime initialTimestampOffset; UInt32 frameListNum; UInt16 numQueued; IOReturn resultCode; debug2IOLog ("+AppleiSubEngine[%p]::StartiSub ()\n", this); resultCode = kIOReturnError; #if !CONTINUOUS_STREAMING loopCount = 0; nanoseconds_to_absolutetime ((kMinimumiSubFrameOffset) * 1000 * 1000, &initialTimestampOffset); clock_get_uptime (¤tTime); ADD_ABSOLUTETIME (¤tTime, &initialTimestampOffset); lastLoopTime.hi = currentTime.hi; lastLoopTime.lo = currentTime.lo; shouldStop = 0; numQueued = 0; currentFrameList = 0; currentByteOffset = 0; theFirstFrame = streamInterface->GetDevice()->GetBus()->GetFrameNumber () + kMinimumiSubFrameOffset; for (frameListNum = currentFrameList; numQueued < NUM_ISUB_FRAME_LISTS_TO_QUEUE; frameListNum++) { resultCode = WriteFrameList (frameListNum); FailIf (kIOReturnSuccess != resultCode, Exit); numQueued++; } #else iSubRunning = TRUE; loopCount = 0xFFFFFFFF; // so that it will go to 0 in the write completion routine when the frames are aborted // clock_get_uptime (¤tTime); // lastLoopTime.hi = currentTime.hi; // lastLoopTime.lo = currentTime.lo; currentFrameList = NUM_ISUB_FRAME_LISTS - 1; // theFirstFrame = 0; // force the completion routine to calculate the correct starting frame // FailIf (NULL == thePipe, Exit); // thePipe->Abort (); // let's kill all outstanding IO and start right back at the beginning // currentByteOffset = 0; resultCode = kIOReturnSuccess; if (FALSE == iSubUSBRunning) { // Why aren't the USB writes running? They should be always running... #if DEBUGLOG IOLog ("!!!iSub USB transport isn't running!!!\n"); #endif loopCount = 0; nanoseconds_to_absolutetime ((kMinimumiSubFrameOffset) * 1000 * 1000, &initialTimestampOffset); clock_get_uptime (¤tTime); ADD_ABSOLUTETIME (¤tTime, &initialTimestampOffset); lastLoopTime.hi = currentTime.hi; lastLoopTime.lo = currentTime.lo; shouldStop = 0; numQueued = 0; currentFrameList = 0; currentByteOffset = 0; theFirstFrame = streamInterface->GetDevice()->GetBus()->GetFrameNumber () + kMinimumiSubFrameOffset; for (frameListNum = currentFrameList; numQueued < NUM_ISUB_FRAME_LISTS_TO_QUEUE; frameListNum++) { resultCode = WriteFrameList (frameListNum); FailIf (kIOReturnSuccess != resultCode, Exit); numQueued++; } } #endif Exit: debug3IOLog ("-AppleiSubEngine[%p]::StartiSub (), result = %d\n", this, resultCode); return resultCode; } IOReturn AppleiSubEngine::StopiSub (void) { debug2IOLog ("+AppleiSubEngine[%p]::StopiSub ()\n", this); iSubRunning = FALSE; #if !CONTINUOUS_STREAMING shouldStop = 1; #endif debug2IOLog ("-AppleiSubEngine[%p]::StopiSub ()\n", this); return kIOReturnSuccess; } IOReturn AppleiSubEngine::WriteFrameList (UInt32 frameListNum) { UInt32 frameIndex; UInt32 firstFrame; UInt16 averageFrameSamples; UInt16 averageFrameSize; UInt16 alternateFrameSize; UInt16 additionalSampleFrameFreq; IOReturn result; result = kIOReturnError; firstFrame = (frameListNum % NUM_ISUB_FRAME_LISTS_TO_QUEUE) * NUM_ISUB_FRAMES_PER_LIST; CalculateSamplesPerFrame (44100, &averageFrameSamples, &additionalSampleFrameFreq); averageFrameSize = averageFrameSamples * 4; alternateFrameSize = (averageFrameSamples + 1) * 4; if (additionalSampleFrameFreq) { for (frameIndex = 0; frameIndex < NUM_ISUB_FRAMES_PER_LIST; frameIndex++) { theFrames[firstFrame + frameIndex].frStatus = -1; if ((frameIndex % additionalSampleFrameFreq) == (UInt16)(additionalSampleFrameFreq - 1)) { theFrames[firstFrame + frameIndex].frReqCount = alternateFrameSize; } else { theFrames[firstFrame + frameIndex].frReqCount = averageFrameSize; } theFrames[firstFrame + frameIndex].frActCount = 0; } } else { for (frameIndex = 0; frameIndex < NUM_ISUB_FRAMES_PER_LIST; frameIndex++) { theFrames[firstFrame + frameIndex].frStatus = -1; theFrames[firstFrame + frameIndex].frReqCount = averageFrameSize; theFrames[firstFrame + frameIndex].frActCount = 0; } } retain (); // Don't want the driver being terminated until our completion routine runs. result = thePipe->Write (soundBuffer[frameListNum], theFirstFrame, NUM_ISUB_FRAMES_PER_LIST, &theFrames[firstFrame], &usbCompletion[frameListNum]); if (result != kIOReturnSuccess) { debug6IOLog ("++AppleiSubEngine[%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); iSubUSBRunning = FALSE; } else { theFirstFrame += NUM_ISUB_FRAMES_PER_LIST; iSubUSBRunning = TRUE; } return result; } void AppleiSubEngine::WriteHandler (AppleiSubEngine * self, UInt32 * buffer, IOReturn result, IOUSBIsocFrame * pFrames) { AbsoluteTime currentTime; UInt64 currentUSBFrame; // static UInt64 lastScheduledUSBFrame = 0; UInt32 frameListToWrite; UInt32 i; #if DEBUGLOG UInt32 delta; static AbsoluteTime lastTime = {0, 0}; AbsoluteTime diff; UInt64 nanos; #endif #if DEBUGTIMESTAMPS static AbsoluteTime lastLoopTime = {0, 0}; AbsoluteTime diff; UInt64 nanos; #endif // Zero the data in the buffer so that this buffer just contains silence for (i = 0; i < self->frameListSize / 4; i++) { buffer[i] = 0; } if (result != kIOReturnSuccess) { #if DEBUGLOG IOLog ("++AppleiSubEngine::WriteHandler () - error 0x%x\n", result); #endif FailIf (NULL == self->streamInterface, Exit); currentUSBFrame = self->streamInterface->GetDevice()->GetBus()->GetFrameNumber (); switch (result) { case kIOReturnOverrun: case kIOReturnAborted: default: // skip ahead and see if that helps if (self->theFirstFrame <= currentUSBFrame) { self->theFirstFrame = currentUSBFrame + kMinimumiSubFrameOffset; #if DEBUGLOG IOLog ("+++AppleiSubEngine::skipping ahead to frame %ld\n", self->theFirstFrame); #endif } break; } } if ((NUM_ISUB_FRAME_LISTS - 1) == self->currentFrameList) { clock_get_uptime (¤tTime); #if DEBUGLOG diff.hi = currentTime.hi; diff.lo = currentTime.lo; SUB_ABSOLUTETIME (&diff, &lastTime); lastTime.hi = currentTime.hi; lastTime.lo = currentTime.lo; absolutetime_to_nanoseconds (diff, &nanos); delta = (UInt32)(nanos / (1000 * 1000)); if (delta > 10) { if (delta - 10 > 2) IOLog ("late = %ld\n", delta - 10); } else { if (10 - delta > 2) IOLog ("early = %ld\n", 10 - delta); } #endif #if DEBUGTIMESTAMPS diff.hi = currentTime.hi; diff.lo = currentTime.lo; SUB_ABSOLUTETIME (&diff, &lastLoopTime); lastLoopTime.hi = currentTime.hi; lastLoopTime.lo = currentTime.lo; absolutetime_to_nanoseconds (diff, &nanos); IOLog ("delta = %ld\n", (UInt32)(nanos / (1000 * 1000))); #endif self->lastLoopTime.hi = currentTime.hi; self->lastLoopTime.lo = currentTime.lo; if (TRUE == self->iSubRunning) { self->loopCount++; self->currentByteOffset = 0; } self->currentFrameList = 0; } else { #if DEBUGLOG clock_get_uptime (¤tTime); diff.hi = currentTime.hi; diff.lo = currentTime.lo; SUB_ABSOLUTETIME (&diff, &lastTime); lastTime.hi = currentTime.hi; lastTime.lo = currentTime.lo; absolutetime_to_nanoseconds (diff, &nanos); delta = (UInt32)(nanos / (1000 * 1000)); if (delta > 10) { if (delta - 10 > 2) IOLog ("late = %ld\n", delta - 10); } else { if (10 - delta > 2) IOLog ("early = %ld\n", 10 - delta); } #endif self->currentFrameList++; if (TRUE == self->iSubRunning) { self->currentByteOffset = self->currentFrameList * self->frameListSize; } } if (self->shouldStop > 0) { debug3IOLog ("++AppleiSubEngine[%p]::WriteHandler () - stopping: %d\n", self, self->shouldStop); self->shouldStop++; } else { frameListToWrite = self->currentFrameList + NUM_ISUB_FRAME_LISTS_TO_QUEUE - 1; if (frameListToWrite >= NUM_ISUB_FRAME_LISTS) { frameListToWrite -= NUM_ISUB_FRAME_LISTS; } self->WriteFrameList (frameListToWrite); } Exit: self->release (); return; }