Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 71 additions & 1 deletion TheAmazingAudioEngine/Utilities/AEIOAudioUnit.m
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#import "AETime.h"
#import "AEAudioBufferListUtilities.h"
@import AVFoundation;
#import <mach/mach_time.h>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've actually wrapped all the mach_time functions in AETime.h, so no need for that


NSString * const AEIOAudioUnitDidUpdateStreamFormatNotification = @"AEIOAudioUnitDidUpdateStreamFormatNotification";

Expand All @@ -48,6 +49,69 @@ @interface AEIOAudioUnit ()
#endif
@end


////////////////////////////////////////////////////////////////////////////////
// sets nr of seconds between reports, 0 = no reporting
#define AE_REPORT_TIME 2

#if AE_REPORT_TIME

static OSStatus notifyCallback(
void * inRefCon,
AudioUnitRenderActionFlags * ioActionFlags,
const AudioTimeStamp * inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList * __nullable ioData)
{
static UInt64 startTime = 0;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There can be multiple IO units in the same process, so these variables should rather be instance variables.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's how Michael had it initially, but eventually I decided statics, because it is easier to copy-paste the code. Having said that, for public code, it probably is better to encapsulate it in an object or struct, but I prefer it not to be part of the actual module.

static UInt64 finishTime = 0;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uint64_t = AEHostTicks


if (*ioActionFlags & kAudioUnitRenderAction_PreRender)
{
startTime = mach_absolute_time();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mach_absolute_time = AECurrentTimeInHostTicks

}
else
if (*ioActionFlags & kAudioUnitRenderAction_PostRender)
{
finishTime = mach_absolute_time();

double renderTime = AESecondsFromHostTicks(finishTime - startTime);

// Compute short term average time
static double avgTime = 0;
avgTime += 0.1 * (renderTime - avgTime);

// Compute maximum time since last report
static double maxTime = 0;
if (maxTime < renderTime)
{ maxTime = renderTime; }

// Check report frequency
static double lastTime = 0;
double time = AECurrentTimeInSeconds();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

double = AESeconds =)

if (lastTime + AE_REPORT_TIME <= time)
{
lastTime = time;
double reportTime1 = avgTime;
double reportTime2 = maxTime;
dispatch_async(dispatch_get_main_queue(), \
^{
NSLog(@"Render time avg = %lfs)", reportTime1);
NSLog(@"Render time max = %lfs)", reportTime2);
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I know, the render notify callback runs on the audio thread. Unfortunately dispatch_async is not real-time safe and should not be used here. But I see that the idea here is for debug purposes only, perhaps AE_REPORT_TIME should only be set if DEBUG? And then you don't need dispatch_async either, NSLog can be used from the audio thread directly (for debugging, not in production code).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, debugging only. In fact: this is primarily a check for average and max time spend, to compare module algorithms; e.g. with/without vDSP calls etc.


maxTime = 0;
}
}

return noErr;
}

#endif // AE_REPORT_TIME
////////////////////////////////////////////////////////////////////////////////


@implementation AEIOAudioUnit
@dynamic running;

Expand Down Expand Up @@ -152,7 +216,13 @@ - (BOOL)setup:(NSError * _Nullable __autoreleasing *)error {
return NO;
}
}



#if AE_REPORT_TIME
AudioUnitAddRenderNotify(_audioUnit, notifyCallback, nil);
#endif


// Initialize
result = AudioUnitInitialize(_audioUnit);
if ( !AECheckOSStatus(result, "AudioUnitInitialize")) {
Expand Down