Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
AREngine.cpp
/* Copyright © 2007 Apple Inc. All Rights Reserved. |
Disclaimer: IMPORTANT: This Apple software is supplied to you by |
Apple Inc. ("Apple") in consideration of your agreement to the |
following terms, and your use, installation, modification or |
redistribution of this Apple software constitutes acceptance of these |
terms. If you do not agree with these terms, please do not use, |
install, modify or redistribute this Apple software. |
In consideration of your agreement to abide by the following terms, and |
subject to these terms, Apple grants you a personal, non-exclusive |
license, under Apple's copyrights in this original Apple software (the |
"Apple Software"), to use, reproduce, modify and redistribute the Apple |
Software, with or without modifications, in source and/or binary forms; |
provided that if you redistribute the Apple Software in its entirety and |
without modifications, you must retain this notice and the following |
text and disclaimers in all such redistributions of the Apple Software. |
Neither the name, trademarks, service marks or logos of Apple Inc. |
may be used to endorse or promote products derived from the Apple |
Software without specific prior written permission from Apple. Except |
as expressly stated in this notice, no other rights or licenses, express |
or implied, are granted by Apple herein, including but not limited to |
any patent rights that may be infringed by your derivative works or by |
other works in which the Apple Software may be incorporated. |
The Apple Software is provided by Apple on an "AS IS" basis. APPLE |
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION |
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS |
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND |
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. |
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL |
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, |
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED |
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), |
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE |
POSSIBILITY OF SUCH DAMAGE. |
*/ |
//================================================================================================== |
// Includes |
//================================================================================================== |
// Self Include |
#include "AREngine.h" |
// Local Includes |
#include "ARDebug.h" |
#include "ARDevice.h" |
#include "ARStereoPanControl.h" |
// System Incluces |
#include <libkern/sysctl.h> |
#include <IOKit/audio/IOAudioControl.h> |
#include <IOKit/audio/IOAudioDefines.h> |
#include <IOKit/audio/IOAudioLevelControl.h> |
#include <IOKit/audio/IOAudioSelectorControl.h> |
#include <IOKit/audio/IOAudioToggleControl.h> |
#include <IOKit/audio/IOAudioTypes.h> |
#include <IOKit/IOWorkLoop.h> |
#include <IOKit/IOTimerEventSource.h> |
//================================================================================================== |
// Constants |
//================================================================================================== |
#define DESCRIPTION_KEY "Description" |
#define BLOCK_SIZE_KEY "BlockSize" |
#define NUM_BLOCKS_KEY "NumBlocks" |
#define NUM_STREAMS_KEY "NumStreams" |
#define FORMATS_KEY "Formats" |
#define SAMPLE_RATES_KEY "SampleRates" |
#define INITIAL_SAMPLE_RATE 44100 |
#define BLOCK_SIZE 512 |
#define NUM_BLOCKS 32 |
#define NUM_STREAMS 1 |
//================================================================================================== |
// AREngine |
//================================================================================================== |
OSDefineMetaClassAndStructors(AREngine, IOAudioEngine) |
bool AREngine::init(OSDictionary* inProperties) |
{ |
bool theAnswer = false; |
// figure out if altivec is present |
size_t theValue = 0; |
size_t theValueSize = sizeof(UInt32); |
int theError = 1; |
#if __ppc__ |
theError = sysctlbyname("hw.optional.altivec", &theValue, &theValueSize, NULL, 0); |
#elif __i386__ || __x86_64__ |
theError = sysctlbyname("hw.optional.sse3", &theValue, &theValueSize, NULL, 0); |
#else |
#error unknown processor |
#endif |
mHasVectorUnit = theError == 0; |
if(IOAudioEngine::init(inProperties)) |
{ |
// get the number of blocks |
mNumberBlocks = NUM_BLOCKS; |
OSNumber* theOSNumber = OSDynamicCast(OSNumber, getProperty(NUM_BLOCKS_KEY)); |
if(theOSNumber != NULL) |
{ |
mNumberBlocks = theOSNumber->unsigned32BitValue(); |
} |
// get the block size |
mBlockSize = BLOCK_SIZE; |
theOSNumber = OSDynamicCast(OSNumber, getProperty(BLOCK_SIZE_KEY)); |
if(theOSNumber != NULL) |
{ |
mBlockSize = theOSNumber->unsigned32BitValue(); |
} |
theAnswer = true; |
} |
return theAnswer; |
} |
void AREngine::free() |
{ |
// clean up the timer |
if(mTimerEventSource != NULL) |
{ |
mTimerEventSource->cancelTimeout(); |
IOWorkLoop* theWorkLoop = getWorkLoop(); |
if(theWorkLoop != NULL) |
{ |
theWorkLoop->removeEventSource(mTimerEventSource); |
} |
mTimerEventSource->release(); |
mTimerEventSource = NULL; |
} |
// free the input buffer, but only if it was allocated separately |
if((mInputBuffer != NULL) && (mInputBuffer != mOutputBuffer)) |
{ |
IOFreeAligned(mInputBuffer, mInputBufferSize); |
} |
mInputBuffer = NULL; |
// free the output buffer |
if(mOutputBuffer != NULL) |
{ |
IOFreeAligned(mOutputBuffer, mOutputBufferSize); |
mOutputBuffer = NULL; |
} |
// let the super class do it's work |
IOAudioEngine::free(); |
} |
bool AREngine::initHardware(IOService* inProvider) |
{ |
bool theAnswer = false; |
if(IOAudioEngine::initHardware(inProvider)) |
{ |
IOAudioSampleRate theInitialSampleRate = { 0, 0 }; |
UInt32 theNumberChannels = 0; |
// create the streams |
if(CreateStreams(&theInitialSampleRate, &theNumberChannels) && (theInitialSampleRate.whole != 0)) |
{ |
CreateControls(theNumberChannels); |
CreateChannelNames(theNumberChannels); |
// figure out how long each block is in microseconds |
mBlockTimeoutMicroseconds = 1000000 * mBlockSize / theInitialSampleRate.whole; |
setSampleRate(&theInitialSampleRate); |
// Set the number of sample frames in each buffer |
setNumSampleFramesPerBuffer(mBlockSize * mNumberBlocks); |
// set up the timer |
IOWorkLoop* theWorkLoop = getWorkLoop(); |
if(theWorkLoop != NULL) |
{ |
mTimerEventSource = IOTimerEventSource::timerEventSource(this, TimerFired); |
if(mTimerEventSource != NULL) |
{ |
theWorkLoop->addEventSource(mTimerEventSource); |
theAnswer = true; |
} |
} |
// set the safety offset |
// note that due to cache issues, it probably isn't wise to leave the safety offset at 0, |
// we set it to 4 here, just to be safe. |
setSampleOffset(4); |
// set up the time stamp generator |
mTimeStampGenerator.SetSampleRate(theInitialSampleRate.whole); |
mTimeStampGenerator.SetFramesPerRingBuffer(mBlockSize * mNumberBlocks); |
// nate that the rate scalar is a 4.28 fixed point number |
// this means that each incremnt is 1/2^28 |
mTimeStampGenerator.SetRateScalar(1UL << 28); |
// set the maximum jitter |
// AbsoluteTime theMaximumJitter = { 0, 0 }; |
// nanoseconds_to_absolutetime(5ULL * 1000ULL, &theMaximumJitter); |
// mTimeStampGenerator.SetMaximumJitter(theMaximumJitter.lo); |
} |
} |
return theAnswer; |
} |
bool AREngine::CreateStreams(IOAudioSampleRate* outInitialSampleRate, UInt32* outNumberChannels) |
{ |
// set the return values |
bool theAnswer = true; |
*outNumberChannels = 0; |
// set up some local variables |
OSArray* theFormatArray = NULL; |
OSArray* theSampleRateArray = NULL; |
OSString* theOSString = NULL; |
UInt32 theNumberStreams = NUM_STREAMS; |
OSNumber* theOSNumber = NULL; |
// get the array of formats |
theFormatArray = OSDynamicCast(OSArray, getProperty(FORMATS_KEY)); |
FailIfNULLWithAction(theFormatArray, theAnswer = false, Done, "AREngine::CreateStreams: Couldn't get the format array"); |
// get the array of sample rates |
theSampleRateArray = OSDynamicCast(OSArray, getProperty(SAMPLE_RATES_KEY)); |
FailIfNULLWithAction(theSampleRateArray, theAnswer = false, Done, "AREngine::CreateStreams: Couldn't get the sample rate array"); |
// get the description |
theOSString = OSDynamicCast(OSString, getProperty(DESCRIPTION_KEY)); |
if(theOSString != NULL) |
{ |
setDescription(theOSString->getCStringNoCopy()); |
} |
// get the number of streams |
theOSNumber = OSDynamicCast(OSNumber, getProperty(NUM_STREAMS_KEY)); |
if(theOSNumber != NULL) |
{ |
theNumberStreams = theOSNumber->unsigned32BitValue(); |
} |
// make the streams |
for(UInt32 theStreamNumber = 0; theStreamNumber < theNumberStreams; ++theStreamNumber) |
{ |
// initialize some local variables |
bool theResult = false; |
UInt32 theMaxBitWidth = 0; |
UInt32 theMaxNumberChannels = 0; |
IOAudioStream* theInputStream = NULL; |
IOAudioStream* theOutputStream = NULL; |
OSCollectionIterator* theFormatIterator = NULL; |
OSCollectionIterator* theSampleRateIterator = NULL; |
OSDictionary* theFormatDictionary = NULL; |
IOAudioSampleRate theSampleRate = { 0, 0 }; |
IOAudioStreamFormat theInitialFormat; |
bool theInitialFormatSet = false; |
char theInputStreamName[32]; |
char theOutputStreamName[32]; |
UInt32 theStreamBufferSize = 0; |
// allocate and initialize the input stream |
if(theNumberStreams > 1) |
{ |
snprintf(theInputStreamName, 32, "Input Stream #%ld", (long int)theStreamNumber + 1); |
} |
else |
{ |
snprintf(theInputStreamName, 32, "Input Stream"); |
} |
theInputStream = new IOAudioStream; |
FailIfNULLWithAction(theInputStream, theAnswer = false, Error, "AREngine::CreateStreams: couldn't create the input stream"); |
theResult = theInputStream->initWithAudioEngine(this, kIOAudioStreamDirectionInput, *outNumberChannels + 1, theInputStreamName); |
FailIfWithAction(!theResult, theAnswer = false, Error, "AREngine::CreateStreams: couldn't initialize the input stream"); |
// allocate and initialize the output stream |
if(theNumberStreams > 1) |
{ |
snprintf(theOutputStreamName, 32, "Output Stream #%ld", (long int)theStreamNumber + 1); |
} |
else |
{ |
snprintf(theOutputStreamName, 32, "Output Stream"); |
} |
theOutputStream = new IOAudioStream; |
FailIfNULLWithAction(theOutputStream, theAnswer = false, Error, "AREngine::CreateStreams: couldn't create the output stream"); |
theResult = theOutputStream->initWithAudioEngine(this, kIOAudioStreamDirectionOutput, *outNumberChannels + 1, theOutputStreamName); |
FailIfWithAction(!theResult, theAnswer = false, Error, "AREngine::CreateStreams: couldn't initialize the output stream"); |
// make an iterator for the format array |
theFormatIterator = OSCollectionIterator::withCollection(theFormatArray); |
FailIfNULLWithAction(theFormatIterator, theAnswer = false, Error, "AREngine::CreateStreams: couldn't create the format iterator"); |
// make an iterator for the sample rate array |
theSampleRateIterator = OSCollectionIterator::withCollection(theSampleRateArray); |
FailIfNULLWithAction(theSampleRateIterator, theAnswer = false, Error, "AREngine::CreateStreams: couldn't create the sample rate iterator"); |
// iterate through the formats |
theFormatIterator->reset(); |
theFormatDictionary = (OSDictionary*)theFormatIterator->getNextObject(); |
while(theFormatDictionary != NULL) |
{ |
// make sure we have a dictionary |
if(OSDynamicCast(OSDictionary, theFormatDictionary) != NULL) |
{ |
// convert the dictionary into something we can deal with |
IOAudioStreamFormat theFormat; |
FailIfNULLWithAction(IOAudioStream::createFormatFromDictionary(theFormatDictionary, &theFormat), theAnswer = false, Error, "AREngine::CreateStreams: couldn't make a format out of the dictionary"); |
// make sure the initial format is set |
if(!theInitialFormatSet) |
{ |
theInitialFormat = theFormat; |
} |
// iterate through the sample rates |
theSampleRateIterator->reset(); |
theOSNumber = (OSNumber*)theSampleRateIterator->getNextObject(); |
while(theOSNumber != NULL) |
{ |
// make sure we have a number |
if(OSDynamicCast(OSNumber, theOSNumber) != NULL) |
{ |
// get the sample rate |
theSampleRate.whole = theOSNumber->unsigned32BitValue(); |
// make sure the initial sample rate is set |
if(outInitialSampleRate->whole == 0) |
{ |
outInitialSampleRate->whole = theSampleRate.whole; |
} |
// add the format to the input stream |
theInputStream->addAvailableFormat(&theFormat, &theSampleRate, &theSampleRate); |
// add the format to the output stream |
theOutputStream->addAvailableFormat(&theFormat, &theSampleRate, &theSampleRate); |
// track a few things |
theMaxNumberChannels = (theFormat.fNumChannels > theMaxNumberChannels) ? theFormat.fNumChannels : theMaxNumberChannels; |
theMaxBitWidth = (theFormat.fBitWidth > theMaxBitWidth) ? theFormat.fBitWidth : theMaxBitWidth; |
} |
// go to the next sample rate |
theOSNumber = (OSNumber*)theSampleRateIterator->getNextObject(); |
} |
} |
// go to the next format |
theFormatDictionary = (OSDictionary*)theFormatIterator->getNextObject(); |
} |
// calculate the size of the stream buffer |
theStreamBufferSize = mBlockSize * mNumberBlocks * theMaxNumberChannels * theMaxBitWidth / 8; |
// allocate the buffers if necessary |
if(mOutputBuffer == NULL) |
{ |
// calculate the size |
mOutputBufferSize = theStreamBufferSize * theNumberStreams; |
// allocate the output buffer |
mOutputBuffer = (void*)IOMallocAligned(mOutputBufferSize, PAGE_SIZE); |
FailIfNULLWithAction(mOutputBuffer, theAnswer = false, Error, "AREngine::CreateStreams: couldn't allocate the output buffer"); |
// the input size is the same as the output size |
mInputBufferSize = mOutputBufferSize; |
// allocate the input buffer |
mInputBuffer = mOutputBuffer; |
} |
// set some info about the stream |
theInputStream->setTerminalType(INPUT_UNDEFINED); |
theOutputStream->setTerminalType(OUTPUT_UNDEFINED); |
// set the initial stream formats |
theInputStream->setFormat(&theInitialFormat, false); |
theOutputStream->setFormat(&theInitialFormat, false); |
// set the data buffer for the streams |
theInputStream->setSampleBuffer(&((UInt8*)mInputBuffer)[theStreamBufferSize * theStreamNumber], theStreamBufferSize); |
theOutputStream->setSampleBuffer(&((UInt8*)mOutputBuffer)[theStreamBufferSize * theStreamNumber], theStreamBufferSize); |
// add the streams to the engine |
addAudioStream(theInputStream); |
theInputStream->release(); |
theInputStream = NULL; |
addAudioStream(theOutputStream); |
theOutputStream->release(); |
theOutputStream = NULL; |
theFormatIterator->release(); |
theFormatIterator = NULL; |
theSampleRateIterator->release(); |
theSampleRateIterator = NULL; |
*outNumberChannels += theMaxNumberChannels; |
continue; |
Error: |
if(theInputStream) |
{ |
theInputStream->release(); |
} |
if(theOutputStream) |
{ |
theOutputStream->release(); |
} |
if(theFormatIterator) |
{ |
theFormatIterator->release(); |
} |
if(theSampleRateIterator) |
{ |
theSampleRateIterator->release(); |
} |
goto Done; |
} |
Done: |
return theAnswer; |
} |
void AREngine::CreateControls(UInt32 inNumberChannels) |
{ |
#define MakeMasterVolumeControls true |
for(UInt32 theChannelID = 0; theChannelID <= inNumberChannels; ++theChannelID) |
{ |
IOAudioControl* theControl; |
IOAudioSelectorControl* theSelectorControl; |
char theChannelName[32]; |
if(theChannelID > 0) |
{ |
snprintf(theChannelName, 32, "Channel %lu", (long unsigned)theChannelID); |
} |
else |
{ |
strncpy(theChannelName, kIOAudioControlChannelNameAll, 32); |
} |
// clock source selector |
if(theChannelID == 0) |
{ |
theSelectorControl = IOAudioSelectorControl::create(0, theChannelID, theChannelName, 0, kIOAudioSelectorControlSubTypeClockSource, kIOAudioControlUsageInput); |
if(theSelectorControl != NULL) |
{ |
theSelectorControl->addAvailableSelection(0, "Internal"); |
theSelectorControl->addAvailableSelection(1, "External 1"); |
theSelectorControl->addAvailableSelection(2, "External 2"); |
theSelectorControl->addAvailableSelection(3, "External 3"); |
theSelectorControl->setValueChangeHandler((IOAudioControl::IntValueChangeHandler)IntegerControlChangeHandler, this); |
addDefaultAudioControl(theSelectorControl); |
theSelectorControl->release(); |
theSelectorControl = NULL; |
} |
} |
// output volume |
if((MakeMasterVolumeControls && (theChannelID == 0)) || (theChannelID != 0)) |
{ |
theControl = IOAudioLevelControl::createVolumeControl(65535, 0, 65535, (-22 << 16) + (32768), 0, theChannelID, theChannelName, 0, kIOAudioControlUsageOutput); |
if(theControl != NULL) |
{ |
theControl->setValueChangeHandler((IOAudioControl::IntValueChangeHandler)IntegerControlChangeHandler, this); |
addDefaultAudioControl(theControl); |
theControl->release(); |
theControl = NULL; |
} |
} |
// output mute |
theControl = IOAudioToggleControl::createMuteControl(false, theChannelID, theChannelName, 0, kIOAudioControlUsageOutput); |
if(theControl != NULL) |
{ |
theControl->setValueChangeHandler((IOAudioControl::IntValueChangeHandler)IntegerControlChangeHandler, this); |
addDefaultAudioControl(theControl); |
theControl->release(); |
theControl = NULL; |
} |
// output solo |
if(theChannelID != 0) |
{ |
theControl = IOAudioToggleControl::create(false, theChannelID, theChannelName, 0, kIOAudioToggleControlSubTypeSolo, kIOAudioControlUsageOutput); |
if(theControl != NULL) |
{ |
theControl->setValueChangeHandler((IOAudioControl::IntValueChangeHandler)IntegerControlChangeHandler, this); |
addDefaultAudioControl(theControl); |
theControl->release(); |
theControl = NULL; |
} |
} |
// output data source |
if(theChannelID != 0) |
{ |
theSelectorControl = IOAudioSelectorControl::create(0, theChannelID, theChannelName, 0, kIOAudioSelectorControlSubTypeOutput, kIOAudioControlUsageOutput); |
if(theSelectorControl != NULL) |
{ |
theSelectorControl->addAvailableSelection(0, "Source 1"); |
theSelectorControl->addAvailableSelection(1, "Source 2"); |
theSelectorControl->addAvailableSelection(2, "Source 3"); |
theSelectorControl->addAvailableSelection(3, "Source 4"); |
theSelectorControl->setValueChangeHandler((IOAudioControl::IntValueChangeHandler)IntegerControlChangeHandler, this); |
addDefaultAudioControl(theSelectorControl); |
theSelectorControl->release(); |
theSelectorControl = NULL; |
} |
} |
// output line level |
if(theChannelID != 0) |
{ |
theSelectorControl = IOAudioSelectorControl::create(kIOAudioSelectorControlSubTypeChannelLevelMinus10dBV, theChannelID, theChannelName, 0, kIOAudioSelectorControlSubTypeChannelNominalLineLevel, kIOAudioControlUsageOutput); |
if(theSelectorControl != NULL) |
{ |
theSelectorControl->addAvailableSelection(kIOAudioSelectorControlSubTypeChannelLevelPlus4dBu, "+4dBu"); |
theSelectorControl->addAvailableSelection(kIOAudioSelectorControlSubTypeChannelLevelMinus10dBV, "-10dBV"); |
theSelectorControl->addAvailableSelection(kIOAudioSelectorControlSubTypeChannelLevelMinus20dBV, "-20dBV"); |
theSelectorControl->addAvailableSelection(kIOAudioSelectorControlSubTypeChannelLevelMicLevel, "Mic"); |
theSelectorControl->addAvailableSelection(kIOAudioSelectorControlSubTypeChannelLevelInstrumentLevel, "Instrument"); |
theSelectorControl->setValueChangeHandler((IOAudioControl::IntValueChangeHandler)IntegerControlChangeHandler, this); |
addDefaultAudioControl(theSelectorControl); |
theSelectorControl->release(); |
theSelectorControl = NULL; |
} |
} |
// input volume |
if((MakeMasterVolumeControls && (theChannelID == 0)) || (theChannelID != 0)) |
{ |
theControl = IOAudioLevelControl::createVolumeControl(65535, 0, 65535, (-22 << 16) + (32768), 0, theChannelID, theChannelName, 0, kIOAudioControlUsageInput); |
if(theControl != NULL) |
{ |
theControl->setValueChangeHandler((IOAudioControl::IntValueChangeHandler)IntegerControlChangeHandler, this); |
addDefaultAudioControl(theControl); |
theControl->release(); |
theControl = NULL; |
} |
} |
// input mute |
theControl = IOAudioToggleControl::createMuteControl(false, theChannelID, theChannelName, 0, kIOAudioControlUsageInput); |
if(theControl != NULL) |
{ |
theControl->setValueChangeHandler((IOAudioControl::IntValueChangeHandler)IntegerControlChangeHandler, this); |
addDefaultAudioControl(theControl); |
theControl->release(); |
theControl = NULL; |
} |
// input solo |
if(theChannelID != 0) |
{ |
theControl = IOAudioToggleControl::create(false, theChannelID, theChannelName, 0, kIOAudioToggleControlSubTypeSolo, kIOAudioControlUsageInput); |
if(theControl != NULL) |
{ |
theControl->setValueChangeHandler((IOAudioControl::IntValueChangeHandler)IntegerControlChangeHandler, this); |
addDefaultAudioControl(theControl); |
theControl->release(); |
theControl = NULL; |
} |
} |
// input data source |
if(theChannelID != 0) |
{ |
theSelectorControl = IOAudioSelectorControl::create(0, theChannelID, theChannelName, 0, kIOAudioSelectorControlSubTypeInput, kIOAudioControlUsageInput); |
if(theSelectorControl != NULL) |
{ |
theSelectorControl->addAvailableSelection(0, "Source 1"); |
theSelectorControl->addAvailableSelection(1, "Source 2"); |
theSelectorControl->addAvailableSelection(2, "Source 3"); |
theSelectorControl->addAvailableSelection(3, "Source 4"); |
theSelectorControl->setValueChangeHandler((IOAudioControl::IntValueChangeHandler)IntegerControlChangeHandler, this); |
addDefaultAudioControl(theSelectorControl); |
theSelectorControl->release(); |
theSelectorControl = NULL; |
} |
} |
// input line level |
if(theChannelID != 0) |
{ |
theSelectorControl = IOAudioSelectorControl::create(kIOAudioSelectorControlSubTypeChannelLevelMinus10dBV, theChannelID, theChannelName, 0, kIOAudioSelectorControlSubTypeChannelNominalLineLevel, kIOAudioControlUsageInput); |
if(theSelectorControl != NULL) |
{ |
theSelectorControl->addAvailableSelection(kIOAudioSelectorControlSubTypeChannelLevelPlus4dBu, "+4dBu"); |
theSelectorControl->addAvailableSelection(kIOAudioSelectorControlSubTypeChannelLevelMinus10dBV, "-10dBV"); |
theSelectorControl->addAvailableSelection(kIOAudioSelectorControlSubTypeChannelLevelMinus20dBV, "-20dBV"); |
theSelectorControl->addAvailableSelection(kIOAudioSelectorControlSubTypeChannelLevelMicLevel, "Mic"); |
theSelectorControl->addAvailableSelection(kIOAudioSelectorControlSubTypeChannelLevelInstrumentLevel, "Instrument"); |
theSelectorControl->setValueChangeHandler((IOAudioControl::IntValueChangeHandler)IntegerControlChangeHandler, this); |
addDefaultAudioControl(theSelectorControl); |
theSelectorControl->release(); |
theSelectorControl = NULL; |
} |
} |
// play through volume |
theControl = IOAudioLevelControl::createVolumeControl(65535, 0, 65535, (-22 << 16) + (32768), 0, theChannelID, theChannelName, 0, kIOAudioControlUsagePassThru); |
if(theControl != NULL) |
{ |
theControl->setValueChangeHandler((IOAudioControl::IntValueChangeHandler)IntegerControlChangeHandler, this); |
addDefaultAudioControl(theControl); |
theControl->release(); |
theControl = NULL; |
} |
// play through on/off |
theControl = IOAudioToggleControl::createMuteControl(true, theChannelID, theChannelName, 0, kIOAudioControlUsagePassThru); |
if(theControl != NULL) |
{ |
theControl->setValueChangeHandler((IOAudioControl::IntValueChangeHandler)IntegerControlChangeHandler, this); |
addDefaultAudioControl(theControl); |
theControl->release(); |
theControl = NULL; |
} |
// play through solo |
if(theChannelID != 0) |
{ |
theControl = IOAudioToggleControl::create(false, theChannelID, theChannelName, 0, kIOAudioToggleControlSubTypeSolo, kIOAudioControlUsagePassThru); |
if(theControl != NULL) |
{ |
theControl->setValueChangeHandler((IOAudioControl::IntValueChangeHandler)IntegerControlChangeHandler, this); |
addDefaultAudioControl(theControl); |
theControl->release(); |
theControl = NULL; |
} |
} |
// play through data destination |
if(theChannelID != 0) |
{ |
theSelectorControl = IOAudioSelectorControl::create(0, theChannelID, theChannelName, 0, kIOAudioSelectorControlSubTypeDestination, kIOAudioControlUsagePassThru); |
if(theSelectorControl != NULL) |
{ |
theSelectorControl->addAvailableSelection(0, "Destination 1"); |
theSelectorControl->addAvailableSelection(1, "Destination 2"); |
theSelectorControl->addAvailableSelection(2, "Destination 3"); |
theSelectorControl->addAvailableSelection(3, "Destination 4"); |
theSelectorControl->setValueChangeHandler((IOAudioControl::IntValueChangeHandler)IntegerControlChangeHandler, this); |
addDefaultAudioControl(theSelectorControl); |
theSelectorControl->release(); |
theSelectorControl = NULL; |
} |
} |
// play through stereo pan |
if((inNumberChannels > 2) && (theChannelID != 0)) |
{ |
theControl = ARStereoPanControl::create(0, -128, 0, 128, 1, 2, theChannelID, theChannelName, 0, ARStereoPanControl::kIOAudioControlSubTypeStereoPan, kIOAudioControlUsagePassThru); |
if(theControl != NULL) |
{ |
theControl->setValueChangeHandler((IOAudioControl::IntValueChangeHandler)IntegerControlChangeHandler, this); |
addDefaultAudioControl(theControl); |
theControl->release(); |
theControl = NULL; |
} |
} |
// LFE volume |
if(theChannelID == 0) |
{ |
theControl = IOAudioLevelControl::create(65535, 0, 65535, (-22 << 16) + (32768), 0, theChannelID, theChannelName, 0, kIOAudioLevelControlSubTypeLFEVolume, kIOAudioControlUsageOutput); |
if(theControl != NULL) |
{ |
theControl->setValueChangeHandler((IOAudioControl::IntValueChangeHandler)IntegerControlChangeHandler, this); |
addDefaultAudioControl(theControl); |
theControl->release(); |
theControl = NULL; |
} |
} |
// LFE mute |
if(theChannelID == 0) |
{ |
theControl = IOAudioToggleControl::create(false, theChannelID, theChannelName, 0, kIOAudioToggleControlSubTypeLFEMute, kIOAudioControlUsageOutput); |
if(theControl != NULL) |
{ |
theControl->setValueChangeHandler((IOAudioControl::IntValueChangeHandler)IntegerControlChangeHandler, this); |
addDefaultAudioControl(theControl); |
theControl->release(); |
theControl = NULL; |
} |
} |
} |
} |
void AREngine::CreateChannelNames(UInt32 inNumberChannels) |
{ |
// create the dictionary to hold the channel names |
OSDictionary* theFullChannelNameDictionary = OSDictionary::withCapacity(2 * (inNumberChannels + 1)); |
OSDictionary* theChannelCategoryNameDictionary = OSDictionary::withCapacity(2 * (inNumberChannels + 1)); |
OSDictionary* theChannelNumberNameDictionary = OSDictionary::withCapacity(2 * (inNumberChannels + 1)); |
// make an input and an output channel name key and channel name for each channel |
for(unsigned theChannel = 0; theChannel <= inNumberChannels; ++theChannel) |
{ |
// make the input key |
char theString[256]; |
snprintf(theString, 255, kIOAudioEngineFullChannelNameKeyInputFormat, theChannel); |
OSString* theKey = OSString::withCString(theString); |
// make the input full name |
snprintf(theString, 255, "Input Channel %u", theChannel); |
OSString* theFullName = OSString::withCString(theString); |
// make the input category name |
snprintf(theString, 255, "Input"); |
OSString* theCategoryName = OSString::withCString(theString); |
// make the input number name |
snprintf(theString, 255, "%u", theChannel); |
OSString* theNumberName = OSString::withCString(theString); |
// add them to the dictionary |
theFullChannelNameDictionary->setObject(theKey, theFullName); |
theChannelCategoryNameDictionary->setObject(theKey, theCategoryName); |
theChannelNumberNameDictionary->setObject(theKey, theNumberName); |
// release the strings |
theKey->release(); |
theFullName->release(); |
theCategoryName->release(); |
theNumberName->release(); |
// make the output key |
snprintf(theString, 255, kIOAudioEngineFullChannelNameKeyOutputFormat, theChannel); |
theKey = OSString::withCString(theString); |
// make the output full name |
snprintf(theString, 255, "Output Channel %u", theChannel); |
theFullName = OSString::withCString(theString); |
// make the output category name |
snprintf(theString, 255, "Output"); |
theCategoryName = OSString::withCString(theString); |
// make the output number name |
snprintf(theString, 255, "%u", theChannel); |
theNumberName = OSString::withCString(theString); |
// add them to the dictionary |
theFullChannelNameDictionary->setObject(theKey, theFullName); |
theChannelCategoryNameDictionary->setObject(theKey, theCategoryName); |
theChannelNumberNameDictionary->setObject(theKey, theNumberName); |
// release the strings |
theKey->release(); |
theFullName->release(); |
theCategoryName->release(); |
theNumberName->release(); |
} |
// put the channel dictionary in the registry |
setProperty(kIOAudioEngineFullChannelNamesKey, theFullChannelNameDictionary); |
setProperty(kIOAudioEngineFullChannelCategoryNamesKey, theChannelCategoryNameDictionary); |
setProperty(kIOAudioEngineFullChannelNumberNamesKey, theChannelNumberNameDictionary); |
theFullChannelNameDictionary->release(); |
theChannelCategoryNameDictionary->release(); |
theChannelNumberNameDictionary->release(); |
} |
OSString* AREngine::getGlobalUniqueID() |
{ |
return OSString::withCString("Audio_Reflector"); |
} |
IOReturn AREngine::performAudioEngineStart() |
{ |
// reset the time stamp generator |
mTimeStampGenerator.Reset(); |
// set the current block to the last one before the wrap around |
mCurrentBlock = mNumberBlocks - 1; |
// start the timer, the first time stamp will be taken when it goes off |
mTimerEventSource->setTimeoutUS(mBlockTimeoutMicroseconds); |
return kIOReturnSuccess; |
} |
IOReturn AREngine::performAudioEngineStop() |
{ |
mTimerEventSource->cancelTimeout(); |
return kIOReturnSuccess; |
} |
UInt32 AREngine::getCurrentSampleFrame() |
{ |
// this keeps the the erase head one full block behind |
UInt32 theAnswer = mCurrentBlock * mBlockSize; |
if(mCurrentBlock != 0) |
{ |
theAnswer -= 1; |
} |
return theAnswer; |
} |
IOReturn AREngine::performFormatChange(IOAudioStream* inStream, const IOAudioStreamFormat* inNewFormat, const IOAudioSampleRate* inNewSampleRate) |
{ |
// set up the time stamp generator |
if(inNewSampleRate != NULL) |
{ |
mTimeStampGenerator.SetSampleRate(inNewSampleRate->whole); |
mBlockTimeoutMicroseconds = 1000000 * mBlockSize / inNewSampleRate->whole; |
} |
// When the format of a stream changes, we have to synch the corresponding stream in the other direction |
// so that the reflection works without munging channels. |
if(inNewFormat != NULL) |
{ |
// we're going to change a lot of stuff, including controls |
beginConfigurationChange(); |
// get the starting channel for the stream |
UInt32 theStartingChannel = inStream->getStartingChannelID(); |
// get the direction for the stream |
IOAudioStreamDirection theDirection = inStream->getDirection(); |
// flip the direction |
theDirection = (theDirection == kIOAudioStreamDirectionOutput) ? kIOAudioStreamDirectionInput : kIOAudioStreamDirectionOutput; |
// look up the stream in the opposite direction |
IOAudioStream* theOppositeStream = getAudioStream(theDirection, theStartingChannel); |
// tell the opposite stream to change format too |
if(theOppositeStream != NULL) |
{ |
theOppositeStream->setFormat(inNewFormat, false); |
} |
// we're done changing stuff |
completeConfigurationChange(); |
} |
return kIOReturnSuccess; |
} |
IOReturn AREngine::IntegerControlChangeHandler(IOService* inTarget, IOAudioControl* inControl, SInt32 inOldValue, SInt32 inNewValue) |
{ |
IOReturn theAnswer = kIOReturnBadArgument; |
// validate the engine |
AREngine* theEngine = OSDynamicCast(AREngine, inTarget); |
if(theEngine != NULL) |
{ |
// validate the control |
if(inControl != NULL) |
{ |
// figure out what kind of control we have here |
// dispatch to the appropriate handler |
theAnswer = kIOReturnSuccess; |
} |
} |
return theAnswer; |
} |
void AREngine::TimerFired(OSObject* inTarget, IOTimerEventSource* inSender) |
{ |
// validate the engine |
AREngine* theEngine = OSDynamicCast(AREngine, inTarget); |
if(theEngine != NULL) |
{ |
// Important Note! |
// |
// This timer code executes on a normal kernel work loop, which is generally a priority 80 |
// thread. When the system is idle, this isn't a problem. However, when the system starts |
// to get busy, this timer can go off so late that more than one block's worth of time has |
// gone by. This will cause the device to slowly drift offf of the proper time and will |
// cause the erase head to erase too much too soon resulting in gaps in the reflected data. |
// The proper way to fix this would involve keeping track of the amount of time between |
// timers firing and use that to account for the proper number of blocks, being sure to |
// account for the rate scalar set on the time stamp generator. This is left as an exercise |
// for the reader. |
// go to the next block |
theEngine->mCurrentBlock += 1; |
// check to see if we have wrapped around |
if(theEngine->mCurrentBlock >= theEngine->mNumberBlocks) |
{ |
// we have |
theEngine->mCurrentBlock = 0; |
// get the next time stamp |
#if __LP64__ |
AbsoluteTime theTimeStamp = 0; |
#else |
AbsoluteTime theTimeStamp = { 0, 0 }; |
#endif |
theEngine->mTimeStampGenerator.GetNextTimeStamp(theEngine->status->fCurrentLoopCount, &theTimeStamp); |
// tell the family about it |
theEngine->takeTimeStamp(true, &theTimeStamp); |
} |
// set the timer to go off in one block |
inSender->setTimeoutUS(theEngine->mBlockTimeoutMicroseconds); |
} |
} |
Copyright © 2009 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2009-04-21