From 1d8f5de9eb6a342faaab2092995f1c5aedacacf0 Mon Sep 17 00:00:00 2001 From: Michael Tyson Date: Tue, 14 Jun 2016 19:09:35 +1000 Subject: [PATCH] Added channel count initializer for AEAudioFileRecorderModule --- .../Modules/Taps/AEAudioFileRecorderModule.h | 18 +++++++++ .../Modules/Taps/AEAudioFileRecorderModule.m | 37 +++++++++++++------ 2 files changed, 44 insertions(+), 11 deletions(-) diff --git a/TheAmazingAudioEngine/Modules/Taps/AEAudioFileRecorderModule.h b/TheAmazingAudioEngine/Modules/Taps/AEAudioFileRecorderModule.h index 15b35b7..54d5f0b 100644 --- a/TheAmazingAudioEngine/Modules/Taps/AEAudioFileRecorderModule.h +++ b/TheAmazingAudioEngine/Modules/Taps/AEAudioFileRecorderModule.h @@ -46,14 +46,31 @@ typedef void (^AEAudioFileRecorderModuleCompletionBlock)(); /*! * Default initialiser * + * Records stereo audio. + * + * @param renderer The renderer + * @param url URL to the file to write to + * @param type The type of the file to write + * @param error If not NULL, the error on output + */ +- (instancetype _Nullable)initWithRenderer:(AERenderer * _Nullable)renderer + URL:(NSURL * _Nonnull)url + type:(AEAudioFileType)type + error:(NSError * _Nullable * _Nullable)error; + +/*! + * Initialiser, with channel count + * * @param renderer The renderer * @param url URL to the file to write to * @param type The type of the file to write + * @param numberOfChannels Number of channels to record (will mix down or double channels if input is different) * @param error If not NULL, the error on output */ - (instancetype _Nullable)initWithRenderer:(AERenderer * _Nullable)renderer URL:(NSURL * _Nonnull)url type:(AEAudioFileType)type + numberOfChannels:(int)numberOfChannels error:(NSError * _Nullable * _Nullable)error; /*! @@ -71,6 +88,7 @@ typedef void (^AEAudioFileRecorderModuleCompletionBlock)(); */ - (void)stopRecordingAtTime:(AEHostTicks)time completionBlock:(AEAudioFileRecorderModuleCompletionBlock _Nullable)block; +@property (nonatomic, readonly) int numberOfChannels; //!< Number of channels that will be recorded @property (nonatomic, readonly) BOOL recording; //!< Whether recording is in progress @property (nonatomic, readonly) AESeconds recordedTime; //!< Current recording length, in seconds @end diff --git a/TheAmazingAudioEngine/Modules/Taps/AEAudioFileRecorderModule.m b/TheAmazingAudioEngine/Modules/Taps/AEAudioFileRecorderModule.m index 763f2ee..2af7713 100644 --- a/TheAmazingAudioEngine/Modules/Taps/AEAudioFileRecorderModule.m +++ b/TheAmazingAudioEngine/Modules/Taps/AEAudioFileRecorderModule.m @@ -11,6 +11,7 @@ #import "AETypes.h" #import "AEAudioBufferListUtilities.h" #import "AEWeakRetainingProxy.h" +#import "AEDSPUtilities.h" #import @interface AEAudioFileRecorderModule () { @@ -20,6 +21,7 @@ @interface AEAudioFileRecorderModule () { BOOL _complete; UInt32 _recordedFrames; } +@property (nonatomic, readwrite) int numberOfChannels; @property (nonatomic, readwrite) BOOL recording; @property (nonatomic, copy) void (^completionBlock)(); @property (nonatomic, strong) NSTimer * pollTimer; @@ -29,14 +31,21 @@ @implementation AEAudioFileRecorderModule - (instancetype)initWithRenderer:(AERenderer *)renderer URL:(NSURL *)url type:(AEAudioFileType)type error:(NSError **)error { + return [self initWithRenderer:renderer URL:url type:type numberOfChannels:2 error:error]; +} + +- (instancetype)initWithRenderer:(AERenderer *)renderer URL:(NSURL *)url type:(AEAudioFileType)type + numberOfChannels:(int)numberOfChannels error:(NSError **)error { + if ( !(self = [super initWithRenderer:renderer]) ) return nil; - if ( !(_audioFile = AEExtAudioFileRefCreate(url, type, self.renderer.sampleRate, 2, error)) ) return nil; + if ( !(_audioFile = AEExtAudioFileRefCreate(url, type, self.renderer.sampleRate, numberOfChannels, error)) ) return nil; // Prime async recording ExtAudioFileWriteAsync(_audioFile, 0, NULL); self.processFunction = AEAudioFileRecorderModuleProcess; + self.numberOfChannels = numberOfChannels; return self; } @@ -88,19 +97,25 @@ static void AEAudioFileRecorderModuleProcess(__unsafe_unretained AEAudioFileReco const AudioBufferList * abl = AEBufferStackGet(context->stack, 0); if ( !abl ) return; - // Prepare stereo buffer - AEAudioBufferListCreateOnStack(stereoBuffer); - for ( int i=0; imNumberBuffers; i++ ) { - stereoBuffer->mBuffers[i] = abl->mBuffers[MIN(abl->mNumberBuffers-1, i)]; + // Prepare buffer with the right number of channels + AEAudioBufferListCreateOnStackWithFormat(buffer, AEAudioDescriptionWithChannelsAndRate(THIS->_numberOfChannels, 0)); + for ( int i=0; imNumberBuffers; i++ ) { + buffer->mBuffers[i] = abl->mBuffers[MIN(abl->mNumberBuffers-1, i)]; + } + if ( buffer->mBuffers == 1 && abl->mNumberBuffers > 1 ) { + // Mix down to mono + for ( int i=1; imNumberBuffers; i++ ) { + AEDSPMixMono(abl->mBuffers[i].mData, buffer->mBuffers[0].mData, 1.0, 1.0, context->frames, buffer->mBuffers[0].mData); + } } // Advance frames, if we have a start time mid-buffer UInt32 frames = context->frames; if ( startTime && startTime > context->timestamp->mHostTime ) { UInt32 advanceFrames = round(AESecondsFromHostTicks(startTime - context->timestamp->mHostTime) * context->sampleRate); - for ( int i=0; imNumberBuffers; i++ ) { - stereoBuffer->mBuffers[i].mData += AEAudioDescription.mBytesPerFrame * advanceFrames; - stereoBuffer->mBuffers[i].mDataByteSize -= AEAudioDescription.mBytesPerFrame * advanceFrames; + for ( int i=0; imNumberBuffers; i++ ) { + buffer->mBuffers[i].mData += AEAudioDescription.mBytesPerFrame * advanceFrames; + buffer->mBuffers[i].mDataByteSize -= AEAudioDescription.mBytesPerFrame * advanceFrames; } frames -= advanceFrames; } @@ -108,13 +123,13 @@ static void AEAudioFileRecorderModuleProcess(__unsafe_unretained AEAudioFileReco // Truncate if we have a stop time mid-buffer if ( stopTime && stopTime < hostTimeAtBufferEnd ) { UInt32 truncateFrames = round(AESecondsFromHostTicks(hostTimeAtBufferEnd - stopTime) * context->sampleRate); - for ( int i=0; imNumberBuffers; i++ ) { - stereoBuffer->mBuffers[i].mDataByteSize -= AEAudioDescription.mBytesPerFrame * truncateFrames; + for ( int i=0; imNumberBuffers; i++ ) { + buffer->mBuffers[i].mDataByteSize -= AEAudioDescription.mBytesPerFrame * truncateFrames; } frames -= truncateFrames; } - AECheckOSStatus(ExtAudioFileWriteAsync(THIS->_audioFile, frames, stereoBuffer), "ExtAudioFileWriteAsync"); + AECheckOSStatus(ExtAudioFileWriteAsync(THIS->_audioFile, frames, buffer), "ExtAudioFileWriteAsync"); THIS->_recordedFrames += frames; if ( stopTime && stopTime < hostTimeAtBufferEnd ) {