AudioUnitOfflineEffectExample/OfflineSources/ReverseOfflineUnit.cpp
/* |
Copyright (C) 2016 Apple Inc. All Rights Reserved. |
See LICENSE.txt for this sample’s licensing information |
Abstract: |
Offline AU |
*/ |
/* |
An effect unit will work on the Float32 audio unit sample format |
Its input and output formats are equivalent - it does NO transformation of |
the format of its input to its output. |
It assumes that there will only ever be one input bus and one output bus. |
*/ |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
// ReverseOfflineUnit.cpp |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
#include "AUBase.h" |
#include "ReverseOfflineUnitVersion.h" |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
#pragma mark ____ReverseOfflineUnit |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
// THIS UNIT HAS NO PARAMETERS... |
// |
class ReverseOfflineUnit : public AUBase |
{ |
public: |
ReverseOfflineUnit(AudioUnit component); |
virtual OSStatus GetPropertyInfo( AudioUnitPropertyID inID, |
AudioUnitScope inScope, |
AudioUnitElement inElement, |
UInt32 & outDataSize, |
Boolean & outWritable ); |
virtual OSStatus GetProperty( AudioUnitPropertyID inID, |
AudioUnitScope inScope, |
AudioUnitElement inElement, |
void * outData ); |
virtual OSStatus SetProperty( AudioUnitPropertyID inID, |
AudioUnitScope inScope, |
AudioUnitElement inElement, |
const void * inData, |
UInt32 inDataSize); |
// same logic as AUEffectBase |
virtual OSStatus Initialize(); |
virtual bool StreamFormatWritable( AudioUnitScope scope, |
AudioUnitElement element); |
virtual OSStatus Render( AudioUnitRenderActionFlags & ioActionFlags, |
const AudioTimeStamp & inTimeStamp, |
UInt32 nFrames); |
// in this case we don't have a preflight string |
// outStr can be NULL |
// if returning a string, this has to be a fresh copy that will be released |
// by the host after its done with it. |
virtual bool GetPreflightString(CFStringRef *outStr) const { return false; } |
virtual OSStatus Version() { return kReverseOfflineUnitVersion; } |
virtual bool CanScheduleParameters() const { return false; } |
private: |
UInt64 mNumInputSamples; |
UInt64 mStartOffset; |
}; |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
AUDIOCOMPONENT_ENTRY(AUBaseFactory, ReverseOfflineUnit) |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
// ReverseOfflineUnit::ReverseOfflineUnit |
// |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
ReverseOfflineUnit::ReverseOfflineUnit(AudioUnit component) |
: AUBase(component, 1, 1), |
mNumInputSamples(0), |
mStartOffset (0) |
{ |
} |
#pragma mark ____ReverseOfflineProperties |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
// ReverseOfflineUnit::GetPropertyInfo |
// |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
OSStatus ReverseOfflineUnit::GetPropertyInfo (AudioUnitPropertyID inID, |
AudioUnitScope inScope, |
AudioUnitElement inElement, |
UInt32 & outDataSize, |
Boolean & outWritable) |
{ |
if (inScope == kAudioUnitScope_Global) { |
switch (inID) { |
case kAudioUnitOfflineProperty_InputSize: |
outDataSize = sizeof(mNumInputSamples); |
outWritable = true; |
return noErr; |
case kAudioUnitOfflineProperty_OutputSize: |
outDataSize = sizeof(mNumInputSamples); // in this case, this is the same |
outWritable = false; |
return noErr; |
case kAudioUnitOfflineProperty_StartOffset: |
outDataSize = sizeof(mStartOffset); |
outWritable = false; |
return noErr; |
case kAudioUnitOfflineProperty_PreflightRequirements: |
outDataSize = sizeof (UInt32); |
outWritable = false; |
return noErr; |
case kAudioUnitOfflineProperty_PreflightName: |
if (GetPreflightString (NULL)) { |
outDataSize = sizeof (CFStringRef); |
outWritable = false; |
return noErr; |
} |
return kAudioUnitErr_InvalidProperty; |
} |
} |
return AUBase::GetPropertyInfo (inID, inScope, inElement, outDataSize, outWritable); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
// ReverseOfflineUnit::GetProperty |
// |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
OSStatus ReverseOfflineUnit::GetProperty (AudioUnitPropertyID inID, |
AudioUnitScope inScope, |
AudioUnitElement inElement, |
void * outData) |
{ |
if (inScope == kAudioUnitScope_Global) { |
switch (inID) { |
case kAudioUnitOfflineProperty_OutputSize: |
*(UInt64*)outData = (mNumInputSamples - mStartOffset); |
return noErr; |
case kAudioUnitOfflineProperty_InputSize: |
*(UInt64*)outData = mNumInputSamples; |
return noErr; |
case kAudioUnitOfflineProperty_StartOffset: |
*(UInt64*)outData = mStartOffset; |
return noErr; |
case kAudioUnitOfflineProperty_PreflightRequirements: |
*(UInt32*)outData = kOfflinePreflight_NotRequired; |
return noErr; |
case kAudioUnitOfflineProperty_PreflightName: |
if (GetPreflightString (NULL)) { |
GetPreflightString ((CFStringRef*)outData); |
return noErr; |
} |
return kAudioUnitErr_InvalidProperty; |
} |
} |
return AUBase::GetProperty (inID, inScope, inElement, outData); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
// ReverseOfflineUnit::GetProperty |
// |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
OSStatus ReverseOfflineUnit::SetProperty(AudioUnitPropertyID inID, |
AudioUnitScope inScope, |
AudioUnitElement inElement, |
const void * inData, |
UInt32 inDataSize) |
{ |
if (inScope == kAudioUnitScope_Global) { |
switch (inID) { |
//whenever these properties are set we *MAY* take this to mean the input has changed |
// at this point we require preflighting again... |
case kAudioUnitOfflineProperty_InputSize: |
if (inDataSize < sizeof(UInt64)) return kAudioUnitErr_InvalidPropertyValue; |
mNumInputSamples = *(UInt64*)inData; |
return noErr; |
case kAudioUnitOfflineProperty_StartOffset: |
if (inDataSize < sizeof(UInt64)) return kAudioUnitErr_InvalidPropertyValue; |
mStartOffset = *(UInt64*)inData; |
return noErr; |
} |
} |
return AUBase::SetProperty (inID, inScope, inElement, inData, inDataSize); |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
#pragma mark ____Initialization_Formats |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
// ReverseOfflineUnit::Initialize |
// |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
OSStatus ReverseOfflineUnit::Initialize() |
{ |
const AUChannelInfo *auChannelConfigs = NULL; |
UInt32 numIOconfigs = SupportedNumChannels(&auChannelConfigs); |
// does the unit publish specific information about channel configurations? |
if ((numIOconfigs > 0) && (auChannelConfigs != NULL)) |
{ |
SInt16 auNumInputs = (SInt16) GetStreamFormat(kAudioUnitScope_Input, 0).mChannelsPerFrame; |
SInt16 auNumOutputs = (SInt16) GetStreamFormat(kAudioUnitScope_Output, 0).mChannelsPerFrame; |
bool foundMatch = false; |
for (UInt32 i = 0; (i < numIOconfigs) && !foundMatch; ++i) |
{ |
SInt16 configNumInputs = auChannelConfigs[i].inChannels; |
SInt16 configNumOutputs = auChannelConfigs[i].outChannels; |
if ((configNumInputs < 0) && (configNumOutputs < 0)) |
{ |
// unit accepts any number of channels on input and output |
if (((configNumInputs == -1) && (configNumOutputs == -2)) || ((configNumInputs == -2) && (configNumOutputs == -1))) |
foundMatch = true; |
// unit accepts any number of channels on input and output IFF they are the same number on both scopes |
else if (((configNumInputs == -1) && (configNumOutputs == -1)) && (auNumInputs == auNumOutputs)) |
foundMatch = true; |
// unit has specified a particular number of channels on both scopes |
else |
continue; |
} |
else |
{ |
// the -1 case on either scope is saying that the unit doesn't care about the |
// number of channels on that scope |
bool inputMatch = (auNumInputs == configNumInputs) || (configNumInputs == -1); |
bool outputMatch = (auNumOutputs == configNumOutputs) || (configNumOutputs == -1); |
if (inputMatch && outputMatch) |
foundMatch = true; |
} |
} |
if (!foundMatch) |
return kAudioUnitErr_FormatNotSupported; |
} |
else |
{ |
// there is no specifically published channel info |
// so for those kinds of effects, the assumption is that the channels (whatever their number) |
// should match on both scopes |
UInt32 outputChannels = GetOutput(0)->GetStreamFormat().mChannelsPerFrame; |
UInt32 inputChannels = GetInput(0)->GetStreamFormat().mChannelsPerFrame; |
if ((outputChannels != inputChannels) || (outputChannels == 0)) |
return kAudioUnitErr_FormatNotSupported; |
} |
return noErr; |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
// ReverseOfflineUnit::StreamFormatWritable |
// |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
bool ReverseOfflineUnit::StreamFormatWritable( AudioUnitScope scope, |
AudioUnitElement element) |
{ |
return IsInitialized() ? false : true; |
} |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
#pragma mark ____Render |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
// ReverseOfflineUnit::Render |
// |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
OSStatus ReverseOfflineUnit::Render( AudioUnitRenderActionFlags &ioActionFlags, |
const AudioTimeStamp & inTimeStamp, |
UInt32 nFrames) |
{ |
if (!HasInput(0)) |
return kAudioUnitErr_NoConnection; |
// first we have to make sure that the rendering flag matches our internal state... |
bool preflight = (ioActionFlags & kAudioOfflineUnitRenderAction_Preflight); |
bool doRender = (ioActionFlags & kAudioOfflineUnitRenderAction_Render); |
// one of these two flags have to be provided |
if (!preflight && !doRender) |
return kAudioUnitErr_InvalidOfflineRender; |
if (preflight) { |
ioActionFlags |= kAudioOfflineUnitRenderAction_Complete; |
return noErr; |
} |
// OK - now we have to figure out which input we want based on the output time |
// we're asked for |
// in this case, as we're a reverse unit this is a simple calculation... |
// we need a new time stamp based on the one we were given. |
AudioTimeStamp ts (inTimeStamp); |
ts.mSampleTime = (mNumInputSamples - mStartOffset) - nFrames - inTimeStamp.mSampleTime; |
UInt32 numFramesToPull = nFrames; |
bool renderPhaseComplete = false; |
if (ts.mSampleTime < mStartOffset) { |
// one word of caution.. if we're preflighting we need to change that state to be ready to render |
// as to get here we're basically done with our sample processing |
// do we have a partial buffer to fill |
ts.mSampleTime += nFrames; |
if (ts.mSampleTime > mStartOffset) { |
numFramesToPull = (UInt32)ts.mSampleTime; |
ts.mSampleTime = mStartOffset; |
renderPhaseComplete = true; |
} else { |
// this is just a protection if someone pulls us for data past what we have... |
ioActionFlags |= kAudioOfflineUnitRenderAction_Complete; |
} |
} |
// OK - now we are good to go... |
AUOutputElement *theOutput = GetOutput(0); // throws if error |
AUInputElement *theInput = GetInput(0); |
OSStatus result = theInput->PullInput (ioActionFlags, ts, 0 /* element */, numFramesToPull); |
if (result) return result; |
// ok - now we reverse our input data |
// if we have a remainder we need to zero out the output buffer |
AudioBufferList &inputBuffer = theInput->GetBufferList(); |
AudioBufferList &outputBuffer = theOutput->GetBufferList(); |
// we'll do the reverse one channel at a time... |
for (UInt32 i = 0; i < inputBuffer.mNumberBuffers; ++i) |
{ |
Float32* inSampleData = (Float32*)inputBuffer.mBuffers[i].mData; |
Float32* outSampleData = (Float32*)outputBuffer.mBuffers[i].mData; |
for (SInt32 in = numFramesToPull, out = 0; --in >= 0 ;++out) |
outSampleData[out] = inSampleData[in]; |
} |
if (renderPhaseComplete) { |
UInt32 numValidBytes = numFramesToPull * sizeof (Float32); |
// we just need to reset the numbytes field as that indicates the valid portion of the buffer |
for (UInt32 i = 0; i < outputBuffer.mNumberBuffers; ++i) { |
outputBuffer.mBuffers[i].mDataByteSize = numValidBytes; |
} |
ioActionFlags |= kAudioOfflineUnitRenderAction_Complete; |
} |
return noErr; |
} |
Copyright © 2016 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2016-02-19