/* * Copyright (c) 2004 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * The contents of this file constitute Original Code as defined in and * are subject to the Apple Public Source License Version 1.1 (the * "License"). You may not use this file except in compliance with the * License. Please obtain a copy of the License at * http://www.apple.com/publicsource and read it before using this file. * * This Original Code and all software distributed under the License are * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the * License for the specific language governing rights and limitations * under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include #include #include "AppleAC97AudioEngine.h" #include "IOAC97Debug.h" #define kBytesPerSample 2 // number of bytes per 16-bit sample #define kConfigArraySize 6 #define CLASS AppleAC97AudioEnginePCMOut #define super AppleAC97AudioEngine OSDefineMetaClassAndStructors( AppleAC97AudioEnginePCMOut, AppleAC97AudioEngine ) //--------------------------------------------------------------------------- bool CLASS::init( IOAudioDevice * provider, IOAC97AudioCodec * codec ) { if (!provider || !codec) return false; if (super::init(provider, codec) == false) return false; fRoot = codec->getAudioController(); if (!fRoot) return false; fConfigArray = OSArray::withCapacity(kConfigArraySize); if (!fConfigArray) return false; return true; } //--------------------------------------------------------------------------- void CLASS::free( void ) { DebugLog("%s::%s %p\n", getName(), __FUNCTION__, this); fRoot = 0; fConfigActive = 0; if (fConfigArray) { fConfigArray->release(); fConfigArray = 0; } if (fOutputStream) { fOutputStream->release(); fOutputStream = 0; } super::free(); } //--------------------------------------------------------------------------- bool CLASS::initAudioConfigurations( void ) { IOAudioSampleRate sampleRate; IOAC97AudioConfig * config; IOReturn ret; // Generate configurations for 2, 4 and 6 channel PCM audio. if (!fConfigArray) return false; fConfigArray->flushCollection(); for (int index = 0; index < kAudioConfigCount; index++) { int numChannels = (index+1) * 2; // 44.1 KHz sample rate config = IOAC97AudioConfig::audioConfig( kIOAC97DMAEngineTypeAudioPCM, kIOAC97DMADataDirectionOutput, numChannels, kIOAC97SampleFormatPCM16, kIOAC97SampleRate44_1K ); if (config && fRoot->prepareAudioConfiguration(config) == kIOReturnSuccess && config->isValid()) { config->setClientData((void *)fConfigArray->getCount()); fConfigArray->setObject(config); DebugLog("%s: %u-ch/44.1K config success\n", getName(), numChannels); } if (config) { config->release(); config = 0; } // 48 KHz sample rate config = IOAC97AudioConfig::audioConfig( kIOAC97DMAEngineTypeAudioPCM, kIOAC97DMADataDirectionOutput, numChannels, kIOAC97SampleFormatPCM16, kIOAC97SampleRate48K ); if (config && fRoot->prepareAudioConfiguration(config) == kIOReturnSuccess && config->isValid()) { config->setClientData((void *)fConfigArray->getCount()); fConfigArray->setObject(config); DebugLog("%s: %u-ch/48K config success\n", getName(), numChannels); } if (config) { config->release(); config = 0; } } // Activate the first supported configuration. #if ACTIVATE_LOWEST_CONFIG_FIRST config = (IOAC97AudioConfig *) fConfigArray->getObject(0); if (config == 0) { DebugLog("%s: no PCM Out configuration\n", getName()); return false; } #else config = (IOAC97AudioConfig *) fConfigArray->getLastObject(); if (config == 0) { DebugLog("%s: no PCM Out configuration\n", getName()); return false; } #endif ret = fRoot->activateAudioConfiguration( config, this, interruptHandler, NULL ); if (ret != kIOReturnSuccess) { DebugLog("%s: activateAudioConfiguration failed\n", getName()); return false; } // Set the engine's initial sample rate. Is this necessary? sampleRate.whole = config->getSampleRate(); sampleRate.fraction = 0; setSampleRate( &sampleRate ); fConfigActive = config; return true; } //--------------------------------------------------------------------------- bool CLASS::initAudioStreams( void ) { IOAC97AudioConfig * config; IOAudioSampleRate sampleRate; IOAudioStreamFormat streamFormat = { /* fNumChannels */ 0, /* fSampleFormat */ kIOAudioStreamSampleFormatLinearPCM, /* fNumericRep */ kIOAudioStreamNumericRepresentationSignedInt, /* fBitDepth */ 16, /* fBitWidth */ 16, /* fAlignment */ kIOAudioStreamAlignmentLowByte, /* fByteOrder */ kIOAudioStreamByteOrderLittleEndian, /* fIsMixable */ true, /* fDriverTag */ 0 }; // Create an audio stream for PCM out. fOutputStream = new IOAudioStream; if ( fOutputStream == 0 || fOutputStream->initWithAudioEngine( this, /* direction */ kIOAudioStreamDirectionOutput, /* startChannelID */ 1 ) != true ) { DebugLog("%s: output stream error\n", getName()); return false; } // Go through our configuration array and add a stream format // for each configuration. for (int i = 0; config = (IOAC97AudioConfig *) fConfigArray->getObject(i); i++) { streamFormat.fNumChannels = config->getAudioChannelCount(); streamFormat.fDriverTag = (UInt32) config->getClientData(); sampleRate.whole = config->getSampleRate(); sampleRate.fraction = 0; fOutputStream->addAvailableFormat( /* streamFormat */ &streamFormat, /* minRate */ &sampleRate, /* maxRate */ &sampleRate ); DebugLog("%s: added %u-ch/%u format to output stream\n", getName(), streamFormat.fNumChannels, sampleRate.whole); } // Add output stream to the engine. addAudioStream( fOutputStream ); // Apply the initial audio format. This will cause performFormatChange // to be called. if (fConfigActive) { streamFormat.fNumChannels = fConfigActive->getAudioChannelCount(); streamFormat.fDriverTag = (UInt32)fConfigActive->getClientData(); fOutputStream->setFormat( &streamFormat ); DebugLog("%s: set output stream format to %lu-ch, tag %lu\n", getName(), streamFormat.fNumChannels, streamFormat.fDriverTag); } return true; } //--------------------------------------------------------------------------- bool CLASS::initHardware( IOService * provider ) { DebugLog("%s::%s(%p)\n", getName(), __FUNCTION__, provider); if (super::initHardware(provider) == false) { DebugLog("%s: super::initHardware failed\n", getName()); goto exit; } if (initAudioConfigurations() == false) { DebugLog("%s: initAudioConfigurations failed\n", getName()); goto exit; } if (initAudioStreams() == false) { DebugLog("%s: initAudioStreams failed\n", getName()); goto exit; } // Report the number of samples ahead of the playback head that // is safe to write into the sample buffer. setSampleOffset( kSampleOffset ); setSampleLatency( kSampleLatency ); setDescription( "AC97 Audio (PCM)" ); return true; exit: stopHardware(provider); return false; } //--------------------------------------------------------------------------- void CLASS::stopHardware( IOService * provider ) { if (fRoot && fConfigActive) { fRoot->deactivateAudioConfiguration( fConfigActive ); fConfigActive = 0; } } //--------------------------------------------------------------------------- IOReturn CLASS::performAudioEngineStart( void ) { DebugLog("%s::%s\n", getName(), __FUNCTION__); takeTimeStamp( false ); fIsRunning = true; fRoot->startDMAEngine( fDMAEngineID ); return kIOReturnSuccess; } //--------------------------------------------------------------------------- IOReturn CLASS::performAudioEngineStop( void ) { DebugLog("%s::%s\n", getName(), __FUNCTION__); fRoot->stopDMAEngine( fDMAEngineID ); fIsRunning = false; return kIOReturnSuccess; } //--------------------------------------------------------------------------- void CLASS::interruptHandler( void * target, void * param ) { CLASS * me = (CLASS *) target; if (me && me->fIsRunning) { me->takeTimeStamp(); } } //--------------------------------------------------------------------------- UInt32 CLASS::getCurrentSampleFrame( void ) { UInt32 sampleFrame; // Report the start of the current buffer in sample frame units. // This tells the audio family that it is safe to erase samples // up to but not including the current position. sampleFrame = fRoot->getDMAEngineHardwarePointer(fDMAEngineID) / (fNumChannels * kBytesPerSample); DebugLog("[SF: %lx]\n", sampleFrame); return sampleFrame; } //--------------------------------------------------------------------------- void CLASS::timerFired( void ) { // Override the call from IOAudioEngine to periodically update the // tail of the descriptor list(s). if ( fIsRunning ) { fRoot->startDMAEngine(fDMAEngineID); } super::timerFired(); } //--------------------------------------------------------------------------- void CLASS::setActiveConfiguration( IOAC97AudioConfig * config ) { OSArray * array; IOItemCount numAudioSamples; IOItemCount numSampleFrames; UInt32 sampleBufferSize; IOBufferMemoryDescriptor * sampleBuffer; IOAudioSampleRate sampleRate; DebugLog("%s::%s(%p)\n", getName(), __FUNCTION__, config); IOAC97Assert(config != 0); sampleBuffer = config->getDMABufferMemory(); IOAC97Assert(sampleBuffer != 0); sampleBufferSize = config->getDMABufferCount() * config->getDMABufferSize(); IOAC97Assert(sampleBufferSize > 0); // Install the active configuration fConfigActive = config; fDMAEngineID = config->getDMAEngineID(); fNumChannels = config->getAudioChannelCount(); DebugLog("%s: DMA engine ID = %lu\n", getName(), fDMAEngineID); DebugLog("%s: channel count = %lu\n", getName(), fNumChannels); if (fOutputStream) { DebugLog("%s: output sample buffer changed\n", getName()); fOutputStream->setSampleBuffer( sampleBuffer->getBytesNoCopy(), sampleBufferSize ); } numAudioSamples = sampleBufferSize / kBytesPerSample; numSampleFrames = numAudioSamples / fNumChannels; DebugLog("%s: buffer samples %lu sample frames %lu\n", getName(), numAudioSamples, numSampleFrames); // Report the size of the sample buffer in sample frame units. setNumSampleFramesPerBuffer( numSampleFrames ); // Report the engine's sample rate. sampleRate.whole = config->getSampleRate(); sampleRate.fraction = 0; setSampleRate( &sampleRate ); // Create audio controls for this configuration, then add the controls // to the audio engine. removeAllDefaultAudioControls(); array = OSArray::withCapacity( 16 ); if (array) { IOAudioControl * control; fRoot->createAudioControls( config, array ); for (int i = 0; control = OSDynamicCast(IOAudioControl, array->getObject(i)); i++) { addDefaultAudioControl( control ); control->flushValue(); } array->release(); } } //--------------------------------------------------------------------------- IOReturn CLASS::performFormatChange( IOAudioStream * audioStream, const IOAudioStreamFormat * newFormat, const IOAudioSampleRate * newSampleRate ) { IOReturn ret = kIOReturnUnsupported; UInt32 rate; DebugLog("%s::%s(%p, %p, %p)\n", getName(), __FUNCTION__, audioStream, newFormat, newSampleRate); rate = (newSampleRate) ? newSampleRate->whole : (fConfigActive) ? fConfigActive->getSampleRate() : 0; if (newFormat) { IOAC97AudioConfig * config; IOAC97AudioConfig * newConfig = 0; DebugLog("%s::%s new format tag %lu rate %u\n", getName(), __FUNCTION__, newFormat->fDriverTag, rate); // Stream formats with different sample rates will be called with // the same fDriverTag. So search the configuration list manually // rather than using newFormat->fDriverTag as an array index. for (int i = 0; config = (IOAC97AudioConfig *)fConfigArray->getObject(i); i++) { if (config->getAudioChannelCount() == newFormat->fNumChannels && (!rate || config->getSampleRate() == rate)) { newConfig = config; break; } } if (newConfig) { ret = kIOReturnSuccess; if (newConfig != fConfigActive) { // Close the currently active configuration. fRoot->deactivateAudioConfiguration( fConfigActive ); // Open the new configuration. Recover from errors by // re-opening the old config. ret = fRoot->activateAudioConfiguration( newConfig, this, interruptHandler, NULL ); if (ret != kIOReturnSuccess) { DebugLog("%s: configuration change failed\n", getName()); fRoot->activateAudioConfiguration( fConfigActive, this, interruptHandler, NULL ); newConfig = fConfigActive; } //updateChannelNumbers(); } setActiveConfiguration( newConfig ); } } DebugLog("%s::%s return value %lx\n", getName(), __FUNCTION__, ret); return ret; } //--------------------------------------------------------------------------- IOReturn CLASS::handlePowerStateChange( UInt32 whatState ) { IOReturn ior = kIOReturnSuccess; DebugLog("%s::%s state = %lu\n", getName(), __FUNCTION__, whatState); IOAC97Assert(fIsRunning == false); if ((whatState == kPowerStateActive) && fIsSleeping) { if (fConfigActive) ior = fRoot->activateAudioConfiguration( fConfigActive, this, interruptHandler, NULL ); fIsSleeping = false; } else if ((whatState == kPowerStateSleep) && !fIsSleeping) { if (fConfigActive) fRoot->deactivateAudioConfiguration( fConfigActive ); fIsSleeping = true; } return ior; }