Files
soundflower-mirror/Source/SoundflowerEngine.cpp
2014-03-28 13:52:45 -05:00

663 lines
23 KiB
C++
Executable File

/*
File:SoundflowerEngine.cpp
Version: 1.0.1, ma++ ingalls
Copyright (c) 2004 Cycling '74
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#include "SoundflowerEngine.h"
#include <IOKit/audio/IOAudioControl.h>
#include <IOKit/audio/IOAudioLevelControl.h>
#include <IOKit/audio/IOAudioToggleControl.h>
#include <IOKit/audio/IOAudioDefines.h>
#include <IOKit/IOLib.h>
#include <IOKit/IOWorkLoop.h>
#include <IOKit/IOTimerEventSource.h>
#define INITIAL_SAMPLE_RATE 44100
#define BLOCK_SIZE 512 // Sample frames
#define NUM_BLOCKS 32
#define NUM_STREAMS 1
#define super IOAudioEngine
OSDefineMetaClassAndStructors(SoundflowerEngine, IOAudioEngine)
bool SoundflowerEngine::init(OSDictionary *properties)
{
bool result = false;
OSNumber *number = NULL;
//IOLog("SoundflowerEngine[%p]::init()\n", this);
if (!super::init(properties)) {
goto Done;
}
// Do class-specific initialization here
// If no non-hardware initialization is needed, this function can be removed
/* The below clojure code creates the lookup table. You can run it in the
online repl at http://try-clojure.org/, although it seems copy and
paste doesn't work. The easiest way to get running with clojure on your
own machine is to install https://github.com/technomancy/leiningen
and type "lien repl"
(defn dB->scale [ind dB] [ind (float (Math/pow 10.0 (/ dB 10.0)))])
(defn val->dB [min-dB v] (+ (/ (* (- min-dB) v) 99.0) min-dB))
(doseq [[i v] (map-indexed dB->scale (map #(val->dB -40.0 %) (range 0 100)))] (println "\tlogTable[" i "] = " v ";"))
To adjust the minimum volume, change the -40 (in dB) value in the last line and also the
corresponding visual aid in SoundflowerDevice.cpp Do not change the number of volume points
without also changing the minVolume/minGain constants in SoundflowerDevice.cpp.
Initially, I used -71 as the minimum volume, but in reality my setup seems to reach zero
muchbefore -71. A floor of -40 seems to work *ok* for my setup.
*/
logTable[ 0 ] = 1.0E-4 ;
logTable[ 1 ] = 1.09749875E-4 ;
logTable[ 2 ] = 1.2045036E-4 ;
logTable[ 3 ] = 1.3219411E-4 ;
logTable[ 4 ] = 1.4508287E-4 ;
logTable[ 5 ] = 1.5922828E-4 ;
logTable[ 6 ] = 1.7475284E-4 ;
logTable[ 7 ] = 1.9179103E-4 ;
logTable[ 8 ] = 2.1049041E-4 ;
logTable[ 9 ] = 2.3101296E-4 ;
logTable[ 10 ] = 2.5353645E-4 ;
logTable[ 11 ] = 2.7825593E-4 ;
logTable[ 12 ] = 3.0538556E-4 ;
logTable[ 13 ] = 3.3516026E-4 ;
logTable[ 14 ] = 3.67838E-4 ;
logTable[ 15 ] = 4.0370174E-4 ;
logTable[ 16 ] = 4.4306213E-4 ;
logTable[ 17 ] = 4.8626016E-4 ;
logTable[ 18 ] = 5.336699E-4 ;
logTable[ 19 ] = 5.857021E-4 ;
logTable[ 20 ] = 6.4280734E-4 ;
logTable[ 21 ] = 7.054802E-4 ;
logTable[ 22 ] = 7.742637E-4 ;
logTable[ 23 ] = 8.4975344E-4 ;
logTable[ 24 ] = 9.326034E-4 ;
logTable[ 25 ] = 0.0010235311 ;
logTable[ 26 ] = 0.001123324 ;
logTable[ 27 ] = 0.0012328468 ;
logTable[ 28 ] = 0.0013530478 ;
logTable[ 29 ] = 0.0014849682 ;
logTable[ 30 ] = 0.0016297508 ;
logTable[ 31 ] = 0.0017886495 ;
logTable[ 32 ] = 0.0019630406 ;
logTable[ 33 ] = 0.0021544348 ;
logTable[ 34 ] = 0.0023644895 ;
logTable[ 35 ] = 0.0025950242 ;
logTable[ 36 ] = 0.002848036 ;
logTable[ 37 ] = 0.0031257158 ;
logTable[ 38 ] = 0.0034304692 ;
logTable[ 39 ] = 0.0037649358 ;
logTable[ 40 ] = 0.0041320124 ;
logTable[ 41 ] = 0.0045348783 ;
logTable[ 42 ] = 0.0049770237 ;
logTable[ 43 ] = 0.005462277 ;
logTable[ 44 ] = 0.0059948424 ;
logTable[ 45 ] = 0.006579332 ;
logTable[ 46 ] = 0.007220809 ;
logTable[ 47 ] = 0.007924829 ;
logTable[ 48 ] = 0.00869749 ;
logTable[ 49 ] = 0.009545485 ;
logTable[ 50 ] = 0.010476157 ;
logTable[ 51 ] = 0.01149757 ;
logTable[ 52 ] = 0.012618569 ;
logTable[ 53 ] = 0.013848864 ;
logTable[ 54 ] = 0.015199111 ;
logTable[ 55 ] = 0.016681006 ;
logTable[ 56 ] = 0.018307382 ;
logTable[ 57 ] = 0.02009233 ;
logTable[ 58 ] = 0.022051308 ;
logTable[ 59 ] = 0.024201283 ;
logTable[ 60 ] = 0.026560878 ;
logTable[ 61 ] = 0.02915053 ;
logTable[ 62 ] = 0.03199267 ;
logTable[ 63 ] = 0.03511192 ;
logTable[ 64 ] = 0.038535286 ;
logTable[ 65 ] = 0.042292427 ;
logTable[ 66 ] = 0.046415888 ;
logTable[ 67 ] = 0.05094138 ;
logTable[ 68 ] = 0.055908103 ;
logTable[ 69 ] = 0.061359074 ;
logTable[ 70 ] = 0.06734151 ;
logTable[ 71 ] = 0.07390722 ;
logTable[ 72 ] = 0.081113085 ;
logTable[ 73 ] = 0.08902151 ;
logTable[ 74 ] = 0.097701 ;
logTable[ 75 ] = 0.10722672 ;
logTable[ 76 ] = 0.1176812 ;
logTable[ 77 ] = 0.12915497 ;
logTable[ 78 ] = 0.14174742 ;
logTable[ 79 ] = 0.15556762 ;
logTable[ 80 ] = 0.17073527 ;
logTable[ 81 ] = 0.18738174 ;
logTable[ 82 ] = 0.20565122 ;
logTable[ 83 ] = 0.22570197 ;
logTable[ 84 ] = 0.24770764 ;
logTable[ 85 ] = 0.2718588 ;
logTable[ 86 ] = 0.29836473 ;
logTable[ 87 ] = 0.32745492 ;
logTable[ 88 ] = 0.35938138 ;
logTable[ 89 ] = 0.3944206 ;
logTable[ 90 ] = 0.43287614 ;
logTable[ 91 ] = 0.47508103 ;
logTable[ 92 ] = 0.5214008 ;
logTable[ 93 ] = 0.5722368 ;
logTable[ 94 ] = 0.62802917 ;
logTable[ 95 ] = 0.6892612 ;
logTable[ 96 ] = 0.75646335 ;
logTable[ 97 ] = 0.83021754 ;
logTable[ 98 ] = 0.91116273 ;
logTable[ 99 ] = 1.0 ;
number = OSDynamicCast(OSNumber, getProperty(NUM_BLOCKS_KEY));
if (number) {
numBlocks = number->unsigned32BitValue();
}
else {
numBlocks = NUM_BLOCKS;
}
number = OSDynamicCast(OSNumber, getProperty(BLOCK_SIZE_KEY));
if (number) {
blockSize = number->unsigned32BitValue();
}
else {
blockSize = BLOCK_SIZE;
}
inputStream = outputStream = NULL;
duringHardwareInit = FALSE;
mLastValidSampleFrame = 0;
result = true;
Done:
return result;
}
bool SoundflowerEngine::initHardware(IOService *provider)
{
bool result = false;
IOAudioSampleRate initialSampleRate;
IOWorkLoop *wl;
//IOLog("SoundflowerEngine[%p]::initHardware(%p)\n", this, provider);
duringHardwareInit = TRUE;
if (!super::initHardware(provider)) {
goto Done;
}
initialSampleRate.whole = 0;
initialSampleRate.fraction = 0;
if (!createAudioStreams(&initialSampleRate)) {
IOLog("SoundflowerEngine::initHardware() failed\n");
goto Done;
}
if (initialSampleRate.whole == 0) {
goto Done;
}
// calculate our timeout in nanosecs, taking care to keep 64bits
blockTimeoutNS = blockSize;
blockTimeoutNS *= 1000000000;
blockTimeoutNS /= initialSampleRate.whole;
setSampleRate(&initialSampleRate);
// Set the number of sample frames in each buffer
setNumSampleFramesPerBuffer(blockSize * numBlocks);
wl = getWorkLoop();
if (!wl) {
goto Done;
}
timerEventSource = IOTimerEventSource::timerEventSource(this, ourTimerFired);
if (!timerEventSource) {
goto Done;
}
workLoop->addEventSource(timerEventSource);
result = true;
Done:
duringHardwareInit = FALSE;
return result;
}
bool SoundflowerEngine::createAudioStreams(IOAudioSampleRate *initialSampleRate)
{
bool result = false;
OSNumber* number = NULL;
UInt32 numStreams;
UInt32 streamNum;
OSArray* formatArray = NULL;
OSArray* sampleRateArray = NULL;
UInt32 startingChannelID = 1;
OSString* desc;
desc = OSDynamicCast(OSString, getProperty(DESCRIPTION_KEY));
if (desc)
setDescription(desc->getCStringNoCopy());
number = OSDynamicCast(OSNumber, getProperty(NUM_STREAMS_KEY));
if (number)
numStreams = number->unsigned32BitValue();
else
numStreams = NUM_STREAMS;
formatArray = OSDynamicCast(OSArray, getProperty(FORMATS_KEY));
if (formatArray == NULL) {
IOLog("SF formatArray is NULL\n");
goto Done;
}
sampleRateArray = OSDynamicCast(OSArray, getProperty(SAMPLE_RATES_KEY));
if (sampleRateArray == NULL) {
IOLog("SF sampleRateArray is NULL\n");
goto Done;
}
for (streamNum = 0; streamNum < numStreams; streamNum++) {
UInt32 maxBitWidth = 0;
UInt32 maxNumChannels = 0;
OSCollectionIterator* formatIterator = NULL;
OSCollectionIterator* sampleRateIterator = NULL;
OSDictionary* formatDict;
IOAudioSampleRate sampleRate;
IOAudioStreamFormat initialFormat;
bool initialFormatSet;
UInt32 channelID;
char outputStreamName[64];
char inputStreamName[64];
initialFormatSet = false;
sampleRate.whole = 0;
sampleRate.fraction = 0;
inputStream = new IOAudioStream;
if (inputStream == NULL) {
IOLog("SF could not create new input IOAudioStream\n");
goto Error;
}
outputStream = new IOAudioStream;
if (outputStream == NULL) {
IOLog("SF could not create new output IOAudioStream\n");
goto Error;
}
snprintf(inputStreamName, 64, "Soundflower Input Stream #%u", (unsigned int)streamNum + 1);
snprintf(outputStreamName, 64, "Soundflower Output Stream #%u", (unsigned int)streamNum + 1);
if (!inputStream->initWithAudioEngine(this, kIOAudioStreamDirectionInput, startingChannelID, inputStreamName) ||
!outputStream->initWithAudioEngine(this, kIOAudioStreamDirectionOutput, startingChannelID, outputStreamName)) {
IOLog("SF could not init one of the streams with audio engine. \n");
goto Error;
}
formatIterator = OSCollectionIterator::withCollection(formatArray);
if (!formatIterator) {
IOLog("SF NULL formatIterator\n");
goto Error;
}
sampleRateIterator = OSCollectionIterator::withCollection(sampleRateArray);
if (!sampleRateIterator) {
IOLog("SF NULL sampleRateIterator\n");
goto Error;
}
formatIterator->reset();
while ((formatDict = (OSDictionary *)formatIterator->getNextObject())) {
IOAudioStreamFormat format;
if (OSDynamicCast(OSDictionary, formatDict) == NULL) {
IOLog("SF error casting formatDict\n");
goto Error;
}
if (IOAudioStream::createFormatFromDictionary(formatDict, &format) == NULL) {
IOLog("SF error in createFormatFromDictionary()\n");
goto Error;
}
if (!initialFormatSet) {
initialFormat = format;
}
sampleRateIterator->reset();
while ((number = (OSNumber *)sampleRateIterator->getNextObject())) {
if (!OSDynamicCast(OSNumber, number)) {
IOLog("SF error iterating sample rates\n");
goto Error;
}
sampleRate.whole = number->unsigned32BitValue();
inputStream->addAvailableFormat(&format, &sampleRate, &sampleRate);
outputStream->addAvailableFormat(&format, &sampleRate, &sampleRate);
if (format.fNumChannels > maxNumChannels) {
maxNumChannels = format.fNumChannels;
}
if (format.fBitWidth > maxBitWidth) {
maxBitWidth = format.fBitWidth;
}
if (initialSampleRate->whole == 0) {
initialSampleRate->whole = sampleRate.whole;
}
}
}
mBufferSize = blockSize * numBlocks * maxNumChannels * maxBitWidth / 8;
//IOLog("Soundflower streamBufferSize: %ld\n", mBufferSize);
if (mBuffer == NULL) {
mBuffer = (void *)IOMalloc(mBufferSize);
if (!mBuffer) {
IOLog("Soundflower: Error allocating output buffer - %lu bytes.\n", (unsigned long)mBufferSize);
goto Error;
}
mThruBuffer = (float*)IOMalloc(mBufferSize);
if (!mThruBuffer) {
IOLog("Soundflower: Error allocating thru buffer - %lu bytes.\n", (unsigned long)mBufferSize);
goto Error;
}
memset((UInt8*)mThruBuffer, 0, mBufferSize);
}
inputStream->setFormat(&initialFormat);
inputStream->setSampleBuffer(mBuffer, mBufferSize);
addAudioStream(inputStream);
inputStream->release();
outputStream->setFormat(&initialFormat);
outputStream->setSampleBuffer(mBuffer, mBufferSize);
addAudioStream(outputStream);
outputStream->release();
formatIterator->release();
sampleRateIterator->release();
for (channelID = startingChannelID; channelID < (startingChannelID + maxNumChannels); channelID++) {
char channelName[20];
snprintf(channelName, 20, "Channel %u", (unsigned int)channelID);
}
startingChannelID += maxNumChannels;
continue;
Error:
IOLog("SoundflowerEngine[%p]::createAudioStreams() - ERROR\n", this);
if (inputStream)
inputStream->release();
if (outputStream)
outputStream->release();
if (formatIterator)
formatIterator->release();
if (sampleRateIterator)
sampleRateIterator->release();
goto Done;
}
result = true;
Done:
if (!result)
IOLog("SoundflowerEngine[%p]::createAudioStreams() - failed!\n", this);
return result;
}
void SoundflowerEngine::free()
{
//IOLog("SoundflowerEngine[%p]::free()\n", this);
if (mBuffer) {
IOFree(mBuffer, mBufferSize);
mBuffer = NULL;
}
if (mThruBuffer) {
IOFree(mThruBuffer, mBufferSize);
mThruBuffer = NULL;
}
super::free();
}
IOReturn SoundflowerEngine::performAudioEngineStart()
{
//IOLog("SoundflowerEngine[%p]::performAudioEngineStart()\n", this);
// When performAudioEngineStart() gets called, the audio engine should be started from the beginning
// of the sample buffer. Because it is starting on the first sample, a new timestamp is needed
// to indicate when that sample is being read from/written to. The function takeTimeStamp()
// is provided to do that automatically with the current time.
// By default takeTimeStamp() will increment the current loop count in addition to taking the current
// timestamp. Since we are starting a new audio engine run, and not looping, we don't want the loop count
// to be incremented. To accomplish that, false is passed to takeTimeStamp().
// The audio engine will also have to take a timestamp each time the buffer wraps around
// How that is implemented depends on the type of hardware - PCI hardware will likely
// receive an interrupt to perform that task
takeTimeStamp(false);
currentBlock = 0;
timerEventSource->setTimeout(blockTimeoutNS);
uint64_t time;
clock_get_uptime(&time);
absolutetime_to_nanoseconds(time, &nextTime);
nextTime += blockTimeoutNS;
return kIOReturnSuccess;
}
IOReturn SoundflowerEngine::performAudioEngineStop()
{
//IOLog("SoundflowerEngine[%p]::performAudioEngineStop()\n", this);
timerEventSource->cancelTimeout();
return kIOReturnSuccess;
}
UInt32 SoundflowerEngine::getCurrentSampleFrame()
{
//IOLog("SoundflowerEngine[%p]::getCurrentSampleFrame() - currentBlock = %lu\n", this, currentBlock);
// In order for the erase process to run properly, this function must return the current location of
// the audio engine - basically a sample counter
// It doesn't need to be exact, but if it is inexact, it should err towards being before the current location
// rather than after the current location. The erase head will erase up to, but not including the sample
// frame returned by this function. If it is too large a value, sound data that hasn't been played will be
// erased.
return currentBlock * blockSize;
}
IOReturn SoundflowerEngine::performFormatChange(IOAudioStream *audioStream, const IOAudioStreamFormat *newFormat, const IOAudioSampleRate *newSampleRate)
{
if (!duringHardwareInit) {
// IOLog("SoundflowerEngine[%p]::peformFormatChange(%p, %p, %p)\n", this, audioStream, newFormat, newSampleRate);
}
// It is possible that this function will be called with only a format or only a sample rate
// We need to check for NULL for each of the parameters
if (newFormat) {
if (!duringHardwareInit) {
// #### do we need to make sure output format == input format??
}
}
if (newSampleRate) {
if (!duringHardwareInit) {
UInt64 newblockTime = blockSize;
newblockTime *= 1000000000;
blockTimeoutNS = newblockTime / newSampleRate->whole;
}
}
return kIOReturnSuccess;
}
void SoundflowerEngine::ourTimerFired(OSObject *target, IOTimerEventSource *sender)
{
if (target) {
SoundflowerEngine *audioEngine = OSDynamicCast(SoundflowerEngine, target);
UInt64 thisTimeNS;
uint64_t time;
SInt64 diff;
if (audioEngine) {
// make sure we have a client, and thus new data so we don't keep on
// just looping around the last client's last buffer!
IOAudioStream *outStream = audioEngine->getAudioStream(kIOAudioStreamDirectionOutput, 1);
if (outStream->numClients == 0) {
// it has, so clean the buffer
memset((UInt8*)audioEngine->mThruBuffer, 0, audioEngine->mBufferSize);
}
audioEngine->currentBlock++;
if (audioEngine->currentBlock >= audioEngine->numBlocks) {
audioEngine->currentBlock = 0;
audioEngine->takeTimeStamp();
}
// calculate next time to fire, by taking the time and comparing it to the time we requested.
clock_get_uptime(&time);
absolutetime_to_nanoseconds(time, &thisTimeNS);
// this next calculation must be signed or we will introduce distortion after only a couple of vectors
diff = ((SInt64)audioEngine->nextTime - (SInt64)thisTimeNS);
sender->setTimeout(audioEngine->blockTimeoutNS + diff);
audioEngine->nextTime += audioEngine->blockTimeoutNS;
}
}
}
IOReturn SoundflowerEngine::clipOutputSamples(const void *mixBuf, void *sampleBuf, UInt32 firstSampleFrame, UInt32 numSampleFrames, const IOAudioStreamFormat *streamFormat, IOAudioStream *audioStream)
{
UInt32 channelCount = streamFormat->fNumChannels;
UInt32 offset = firstSampleFrame * channelCount;
UInt32 byteOffset = offset * sizeof(float);
UInt32 numBytes = numSampleFrames * channelCount * sizeof(float);
SoundflowerDevice* device = (SoundflowerDevice*)audioDevice;
#if 0
IOLog("SoundflowerEngine[%p]::clipOutputSamples() -- channelCount:%u \n", this, (uint)channelCount);
IOLog(" input -- numChannels: %u", (uint)inputStream->format.fNumChannels);
IOLog(" bitDepth: %u", (uint)inputStream->format.fBitDepth);
IOLog(" bitWidth: %u", (uint)inputStream->format.fBitWidth);
IOLog(" \n");
IOLog(" output -- numChannels: %u", (uint)inputStream->format.fNumChannels);
IOLog(" bitDepth: %u", (uint)inputStream->format.fBitDepth);
IOLog(" bitWidth: %u", (uint)inputStream->format.fBitWidth);
IOLog(" \n");
#endif
#if 0
IOLog("INPUT: firstSampleFrame: %u numSampleFrames: %u \n", (uint)firstSampleFrame, (uint)numSampleFrames);
#endif
mLastValidSampleFrame = firstSampleFrame+numSampleFrames;
// TODO: where is the sampleFrame wrapped?
// TODO: try to put a mutex around reading and writing
// TODO: why is the reading always trailing by at least 512 frames? (when 512 is the input framesize)?
if (device->mMuteIn[0]) {
memset((UInt8*)mThruBuffer + byteOffset, 0, numBytes);
}
else {
memcpy((UInt8*)mThruBuffer + byteOffset, (UInt8 *)mixBuf + byteOffset, numBytes);
float masterGain = logTable[ device->mGain[0] ];
float masterVolume = logTable[ device->mVolume[0] ];
for (UInt32 channel = 0; channel < channelCount; channel++) {
SInt32 channelMute = device->mMuteIn[channel+1];
float channelGain = logTable[ device->mGain[channel+1] ];
float channelVolume = logTable[ device->mVolume[channel+1] ];
float adjustment = masterVolume * channelVolume * masterGain * channelGain;
for (UInt32 channelBufferIterator = 0; channelBufferIterator < numSampleFrames; channelBufferIterator++) {
if (channelMute)
mThruBuffer[offset + channelBufferIterator*channelCount + channel] = 0;
else
mThruBuffer[offset + channelBufferIterator*channelCount + channel] *= adjustment;
}
}
}
return kIOReturnSuccess;
}
// This is called when client apps need input audio. Here we give them saved audio from the clip routine.
IOReturn SoundflowerEngine::convertInputSamples(const void *sampleBuf, void *destBuf, UInt32 firstSampleFrame, UInt32 numSampleFrames, const IOAudioStreamFormat *streamFormat, IOAudioStream *audioStream)
{
UInt32 frameSize = streamFormat->fNumChannels * sizeof(float);
UInt32 offset = firstSampleFrame * frameSize;
SoundflowerDevice* device = (SoundflowerDevice*)audioDevice;
#if 0
//IOLog("SoundflowerEngine[%p]::convertInputSamples() -- channelCount:%u \n", this, (uint)streamFormat->fNumChannels);
IOLog("OUTPUT: firstSampleFrame: %u numSampleFrames: %u \n", (uint)firstSampleFrame, (uint)numSampleFrames);
IOLog(" mLastValidSampleFrame: %u (diff: %ld) \n", (uint)mLastValidSampleFrame, long(mLastValidSampleFrame) - long(firstSampleFrame+numSampleFrames));
#endif
if (device->mMuteOut[0])
memset((UInt8*)destBuf, 0, numSampleFrames * frameSize);
else
memcpy((UInt8*)destBuf, (UInt8*)mThruBuffer + offset, numSampleFrames * frameSize);
return kIOReturnSuccess;
}