/*============================================================================= CAAUProcessor.h $Log: CAAUProcessor.cpp,v $ Revision 1.2 2004/03/16 20:24:29 bills finish this guy Revision 1.1 2004/03/13 01:34:15 bills initial checkin Created by William Stewart on Thurs Mar 12 2004. Copyright (c) 2004 Apple Computer. All rights reserved. =============================================================================*/ #include "CAAUProcessor.h" static OSStatus SilenceInputCallback (void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) { AudioBuffer *buf = ioData->mBuffers; for (UInt32 i = ioData->mNumberBuffers; i--; ++buf) memset((Byte *)buf->mData, 0, buf->mDataByteSize); //provide a hint that our input data is silent. *ioActionFlags &= kAudioUnitRenderAction_OutputIsSilence; return noErr; } static AURenderCallbackStruct sSilentCallback = { SilenceInputCallback, NULL }; CAAUProcessor::CAAUProcessor (const CAComponent& inComp) : mPreflightABL(NULL) { OSStatus result = CAAudioUnit::Open (inComp, mUnit); if (result) throw result; memset (&mUserCallback, 0, sizeof (AURenderCallbackStruct)); mMaxTailTime = 10.; } CAAUProcessor::~CAAUProcessor () { if (mPreflightABL) delete mPreflightABL; } inline OSStatus SetInputCallback (CAAudioUnit &inUnit, AURenderCallbackStruct &inInputCallback) { return inUnit.SetProperty (kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &inInputCallback, sizeof(inInputCallback)); } OSStatus CAAUProcessor::EstablishInputCallback (AURenderCallbackStruct &inInputCallback) { OSStatus result = SetInputCallback (mUnit, inInputCallback); if (!result) memcpy (&mUserCallback, &inInputCallback, sizeof(AURenderCallbackStruct)); else memset (&mUserCallback, 0, sizeof (AURenderCallbackStruct)); return result; } OSStatus CAAUProcessor::SetAUPreset (CFPropertyListRef inPreset) { return mUnit.SetProperty (kAudioUnitProperty_ClassInfo, kAudioUnitScope_Global, 0, &inPreset, sizeof(inPreset)); } UInt32 CAAUProcessor::MaxFramesPerRender () const { UInt32 maxFrames; UInt32 propSize = sizeof (maxFrames); if (mUnit.GetProperty (kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &maxFrames, &propSize)) { return 0; } return maxFrames; } OSStatus CAAUProcessor::SetMaxFramesPerRender (UInt32 inMaxFrames) { return mUnit.SetProperty (kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &inMaxFrames, sizeof(inMaxFrames)); } OSStatus CAAUProcessor::Initialize (const CAStreamBasicDescription &inInputDesc, const CAStreamBasicDescription &inOutputDesc, UInt64 inNumInputSamples) { return DoInitialisation (inInputDesc, inOutputDesc, inNumInputSamples, MaxFramesPerRender()); } OSStatus CAAUProcessor::Reinitialize (UInt32 inNewMaxFrames) { OSStatus result; CAStreamBasicDescription inputDesc, outputDesc; require_noerr (result = mUnit.GetFormat (kAudioUnitScope_Input, 0, inputDesc), home); require_noerr (result = mUnit.GetFormat (kAudioUnitScope_Output, 0, outputDesc), home); require_noerr (result = DoInitialisation (inputDesc, outputDesc, mNumInputSamples, inNewMaxFrames), home); home: return result; } OSStatus CAAUProcessor::DoInitialisation (const CAStreamBasicDescription &inInputFormat, const CAStreamBasicDescription &inOutputFormat, UInt64 inNumInputSamples, UInt32 inMaxFrames) { OSStatus result; if (inNumInputSamples == 0 && IsOfflineAU()) return kAudioUnitErr_InvalidOfflineRender; mNumInputSamples = inNumInputSamples; // first check that we can do this number of channels if (mUnit.CanDo (inInputFormat.NumberChannels(), inOutputFormat.NumberChannels()) == false) require_noerr (result = kAudioUnitErr_FailedInitialization, home); // just uninitialise the AU as a matter of course require_noerr (result = mUnit.Uninitialize(), home); require_noerr (result = mUnit.SetFormat (kAudioUnitScope_Input, 0, inInputFormat), home); require_noerr (result = mUnit.SetFormat (kAudioUnitScope_Output, 0, inOutputFormat), home); require_noerr (result = SetMaxFramesPerRender (inMaxFrames), home); // if we're any AU but an offline AU, we should tell it that we've processing offline if (!IsOfflineAU()) { UInt32 isOffline = (IsOfflineContext() ? 1 : 0); // don't care whether this succeeds of fails as many AU's don't care about this // but the ones that do its important that they are told their render context mUnit.SetProperty (kAudioUnitProperty_OfflineRender, kAudioUnitScope_Global, 0, &isOffline, sizeof(isOffline)); } else { // tell the offline unit how many input samples we wish to process... mUnit.SetProperty (kAudioOfflineUnitProperty_InputSize, kAudioUnitScope_Global, 0, &mNumInputSamples, sizeof(mNumInputSamples)); } require_noerr (result = mUnit.Initialize(), home); require_noerr (result = SetInputCallback (mUnit, mUserCallback), home); CalculateRemainderSamples (inOutputFormat.mSampleRate); // finally reset our time stamp // the time stamp we use with the AU Render - only sample count is valid memset (&mRenderTimeStamp, 0, sizeof(mRenderTimeStamp)); mRenderTimeStamp.mFlags = kAudioTimeStampSampleTimeValid; // now, if we're NOT an offline AU, preflighting is not required // if we are an offline AU, we should preflight.. an offline AU will tell us when its preflighting is done mPreflightDone = false; if (mPreflightABL) { delete mPreflightABL; mPreflightABL = NULL; } mPreflightABL = new AUOutputBL (inOutputFormat); mLastPercentReported = 0; home: return result; } void CAAUProcessor::CalculateRemainderSamples (Float64 inSampleRate) { mLatencySamples = 0; mTailSamplesToProcess = 0; mTailSamples = 0; mTailSamplesRemaining = 0; // nothing to do because we're not processing offline if (IsOfflineContext() == false) return; // because an offline unit has some indeterminancy about what it does with the input samples // it is *required* to deal internally with both latency and tail if (!IsOfflineAU()) { // when offline we need to deal with both latency and tail // if the AU has latency - how many samples at the start will be zero? // we'll end up chucking these away. Float64 renderTimeProps; UInt32 propSize = sizeof (renderTimeProps); OSStatus result = mUnit.GetProperty (kAudioUnitProperty_Latency, kAudioUnitScope_Global, 0, &renderTimeProps, &propSize); Float64 latencySamples = 0; if (result == noErr) // we have latency to deal with - its reported in seconds latencySamples = renderTimeProps * inSampleRate; // AU tail // if the AU has a tail - we'll pull that many zeroes through at the end to flush // out this tail - think of a decaying digital delay or reverb... result = mUnit.GetProperty (kAudioUnitProperty_TailTime, kAudioUnitScope_Global, 0, &renderTimeProps, &propSize); if (renderTimeProps > mMaxTailTime) renderTimeProps = mMaxTailTime; Float64 tailSamples = 0; if (result == noErr) tailSamples = renderTimeProps * inSampleRate; // this dictates how many samples at the end we need to pull through... // we add latency to tail because we throw the latency samples away from the start of the rendering // and we have to pull that many samples after the end of course to get the last of the original data // then to that is added the tail of the effect... mTailSamplesToProcess = UInt32(tailSamples + latencySamples); mTailSamples = UInt32(tailSamples); mLatencySamples = UInt32(latencySamples); } } CFStringRef CAAUProcessor::GetOLPreflightName () const { if (OfflineAUNeedsPreflight()) { CFStringRef str; UInt32 size = sizeof(str); OSStatus result = mUnit.GetProperty (kAudioUnitOfflineProperty_PreflightName, kAudioUnitScope_Global, 0, &str, &size); return result ? NULL : str; } return NULL; // says NO to preflighting } bool CAAUProcessor::OfflineAUNeedsPreflight () const { if (IsOfflineAU()) { UInt32 preflightRequirements; UInt32 size = sizeof(preflightRequirements); OSStatus result = mUnit.GetProperty (kAudioUnitOfflineProperty_PreflightRequirements, kAudioUnitScope_Global, 0, &preflightRequirements, &size); if (result) return false; return preflightRequirements; } return false; } OSStatus CAAUProcessor::Preflight (bool inProcessPreceedingTail) { //we're preflighting again, so reset ourselves if (mPreflightDone) { mPreflightDone = false; // the time stamp we use with the AU Render - only sample count is valid memset (&mRenderTimeStamp, 0, sizeof(mRenderTimeStamp)); mRenderTimeStamp.mFlags = kAudioTimeStampSampleTimeValid; } UInt32 numFrames = MaxFramesPerRender(); if (numFrames == 0) return kAudioUnitErr_InvalidProperty; OSStatus result = noErr; if (!IsOfflineAU()) { if ((IsOfflineContext() == false && inProcessPreceedingTail) || IsOfflineContext()) { // re-establish the user's input callback require_noerr (result = SetInputCallback (mUnit, mUserCallback), home); // Consume the number of input samples indicated by the AU's latency or tail // based on whether the AU is being used in an offline context or not. UInt32 latSamps = IsOfflineContext() ? mLatencySamples : mTailSamples; while (latSamps > 0) { if (latSamps < numFrames) numFrames = latSamps; // process the samples (the unit's input callback will read the samples // from the file and convert them to float for processing AudioUnitRenderActionFlags renderFlags = 0; mPreflightABL->Prepare(); require_noerr (result = mUnit.Render (&renderFlags, &mRenderTimeStamp, 0, numFrames, mPreflightABL->ABL()), home); mRenderTimeStamp.mSampleTime += numFrames; latSamps -= numFrames; } if (IsOfflineContext()) mRenderTimeStamp.mSampleTime = mLatencySamples; } else { // processing real-time but not processing preceeding tail, so we should preroll the AU require_noerr (result = mUnit.Preroll(numFrames), home); // re-establish the user's input callback require_noerr (result = SetInputCallback (mUnit, mUserCallback), home); mRenderTimeStamp.mSampleTime = 0; } } else { // re-establish the user's input callback require_noerr (result = SetInputCallback (mUnit, mUserCallback), home); UInt32 preflightRequirements; UInt32 size; size = sizeof(preflightRequirements); require_noerr (result = mUnit.GetProperty (kAudioUnitOfflineProperty_PreflightRequirements, kAudioUnitScope_Global, 0, &preflightRequirements, &size), home); // 0 indicates none, otherwise optional or required -> we do it for either if (preflightRequirements) { for (;;) { // here we need to do the preflight loop - we don't expect any data back, but have to // give the offline unit all of its input data to allow it to prepare its processing AudioUnitRenderActionFlags renderFlags = kAudioOfflineUnitRenderAction_Preflight; mPreflightABL->Prepare(); require_noerr (result = mUnit.Render (&renderFlags, &mRenderTimeStamp, 0, numFrames, mPreflightABL->ABL()), home); mRenderTimeStamp.mSampleTime += numFrames; if (renderFlags & kAudioOfflineUnitRenderAction_Complete) break; } } // the time stamp we use with the AU Render - only sample count is valid mRenderTimeStamp.mSampleTime = 0; } if (result == noErr) { mPreflightDone = true; } home: return result; } OSStatus CAAUProcessor::OfflineAUPreflight (UInt32 inNumFrames, bool &outIsDone) { if (!IsOfflineAU()) return paramErr; if (mNumInputSamples == 0) return paramErr; UInt32 preflightRequirements; UInt32 size = sizeof(preflightRequirements); OSStatus result; require_noerr (result = mUnit.GetProperty (kAudioUnitOfflineProperty_PreflightRequirements, kAudioUnitScope_Global, 0, &preflightRequirements, &size), home); // 0 indicates none, otherwise optional or required -> we do it for either if (preflightRequirements) { AudioUnitRenderActionFlags renderFlags = kAudioOfflineUnitRenderAction_Preflight; mPreflightABL->Prepare(); require_noerr (result = mUnit.Render (&renderFlags, &mRenderTimeStamp, 0, inNumFrames, mPreflightABL->ABL()), home); mRenderTimeStamp.mSampleTime += inNumFrames; if (renderFlags & kAudioOfflineUnitRenderAction_Complete) { outIsDone = true; mRenderTimeStamp.mSampleTime = 0; mPreflightDone = true; mLastPercentReported = 0; } } else { outIsDone = true; mRenderTimeStamp.mSampleTime = 0; mPreflightDone = true; mLastPercentReported = 0; } home: return result; } void SetBufferListToNumFrames (AudioBufferList &list, UInt32 inNumFrames) { for (unsigned int i = 0; i < list.mNumberBuffers; ++i) { AudioBuffer &buf = list.mBuffers[i]; if (buf.mDataByteSize > 0) buf.mDataByteSize = inNumFrames * sizeof (Float32); } } OSStatus CAAUProcessor::Render (AudioBufferList *ioData, UInt32 &ioNumFrames, bool &outIsSilence, bool *outOLCompleted, bool *outOLRequiresPostProcess) { if (IsOfflineContext()) { if (!mPreflightDone) return kAudioUnitErr_InvalidOfflineRender; // YES - this is correct!!! you have to provide both if rendering in an offline Context *outOLCompleted = false; *outOLRequiresPostProcess = false; if (!IsOfflineAU() && !mUnit.Comp().Desc().IsFConv()) { // have we processed the input we expect too? // in an offline case, we want to create output that matches the input // for an OfflineAU type, it manages this internally, so we don't have to do anything // for a FormatConverter AU, we don't know and can't tell, so we can't do anything here // for any other AU type (effect, instrument) the Prime assumption is that it will // ask for the same number of frames of input as it is asked to output // so we can ask what it is doing, and get a sample accurate output (which is input + tail time) if (mRenderTimeStamp.mSampleTime + ioNumFrames >= InputSampleCount()) { // if we fall into here, we have just a partial number of input samples left // (less input less than what we've been asked to produce output for. *outOLCompleted = true; // we require post processing if we've got some tail (or latency) samples to flush through *outOLRequiresPostProcess = mTailSamplesToProcess > 0; if (InputSampleCount() > mRenderTimeStamp.mSampleTime) { ioNumFrames = (UInt32)(InputSampleCount() - mRenderTimeStamp.mSampleTime); } else { ioNumFrames = 0; } mTailSamplesRemaining = mTailSamplesToProcess; // we've got no input samples to process this time. SetBufferListToNumFrames (*ioData, ioNumFrames); if (ioNumFrames == 0) { if (*outOLRequiresPostProcess) SetInputCallback (mUnit, sSilentCallback); else mUnit.GlobalReset (); //flush this out, as we're done with this phase return noErr; } } } AudioUnitRenderActionFlags renderFlags = IsOfflineAU() ? kAudioOfflineUnitRenderAction_Render : 0; OSStatus result = mUnit.Render (&renderFlags, &mRenderTimeStamp, 0, ioNumFrames, ioData); if (result) { if (mUnit.Comp().Desc().IsFConv()) { // this is the only way we can tell we're done with a FormatConverter AU // - ie. client returns an error from input result = noErr; *outOLCompleted = true; *outOLRequiresPostProcess = mTailSamplesToProcess > 0; ioNumFrames = 0; SetBufferListToNumFrames (*ioData, ioNumFrames); } else return result; } mRenderTimeStamp.mSampleTime += ioNumFrames; outIsSilence = (renderFlags & kAudioUnitRenderAction_OutputIsSilence); // if we're an Offline AU type, it will set this flag on completion of its processing if (renderFlags & kAudioOfflineUnitRenderAction_Complete) { // we now need to calculate how many frames we rendered. // as we're dealing with PCM non-interleaved buffers, we can calculate the numFrames simply ioNumFrames = ioData->mBuffers[0].mDataByteSize / sizeof(Float32); *outOLCompleted = true; *outOLRequiresPostProcess = false; mUnit.GlobalReset (); //flush this out, as we're done with this phase } else { if (*outOLCompleted) { if (*outOLRequiresPostProcess) result = SetInputCallback (mUnit, sSilentCallback); else mUnit.GlobalReset (); //flush this out, as we're done with this phase } } return result; } // rendering in a RT context: AudioUnitRenderActionFlags renderFlags = 0; OSStatus result = mUnit.Render (&renderFlags, &mRenderTimeStamp, 0, ioNumFrames, ioData); if (!result) { mRenderTimeStamp.mSampleTime += ioNumFrames; outIsSilence = (renderFlags & kAudioUnitRenderAction_OutputIsSilence); } return result; } OSStatus CAAUProcessor::PostProcess (AudioBufferList *ioData, UInt32 &ioNumFrames, bool &outIsSilence, bool &outDone) { if (IsOfflineAU() || !IsOfflineContext()) return kAudioUnitErr_CannotDoInCurrentContext; outDone = false; // we've got less samples to process than we've been asked to process if (mTailSamplesRemaining <= SInt32(ioNumFrames)) { outDone = true; ioNumFrames = mTailSamplesRemaining > 0 ? mTailSamplesRemaining : 0; SetBufferListToNumFrames (*ioData, ioNumFrames); if (ioNumFrames == 0) return noErr; } AudioUnitRenderActionFlags renderFlags = 0; OSStatus result; require_noerr (result = mUnit.Render (&renderFlags, &mRenderTimeStamp, 0, ioNumFrames, ioData), home); mRenderTimeStamp.mSampleTime += ioNumFrames; mTailSamplesRemaining -= ioNumFrames; outIsSilence = (renderFlags & kAudioUnitRenderAction_OutputIsSilence); if (outDone) { require_noerr (result = SetInputCallback (mUnit, mUserCallback), home); mUnit.GlobalReset (); //flush this out, as we're done with this phase } home: return result; } Float32 CAAUProcessor::GetOLPercentComplete () { if (!IsOfflineContext()) return 0; Float32 percentDone = mLastPercentReported; if (IsOfflineAU()) { // we get the output size every time, as this can change as parameters are changed UInt64 numOutputSamples = mNumInputSamples; UInt32 propSize = sizeof(numOutputSamples); mUnit.GetProperty (kAudioOfflineUnitProperty_OutputSize, kAudioUnitScope_Global, 0, &numOutputSamples, &propSize); percentDone = (mRenderTimeStamp.mSampleTime / Float64(numOutputSamples)) * 100.; } else { percentDone = (mRenderTimeStamp.mSampleTime / Float64(mNumInputSamples + mTailSamples)) * 100.; } if (percentDone > mLastPercentReported) mLastPercentReported = percentDone; return mLastPercentReported; }