// // IBTAudioRecorder.m // XFFruit // // Created by Xummer on 4/17/15. // Copyright (c) 2015 Xummer. All rights reserved. // #import "IBTAudioRecorder.h" #import "IBTAudioController.h" #import "IBTCommon.h" @import AVFoundation; @import AudioToolbox; #define kRecordersFileExists @"Audios" #define AUDIO_SAMPLE_RATE 22050.0 // 44100.0 : 44100 Hz @interface IBTAudioRecorder () < AVAudioRecorderDelegate > { NSTimer *recordTimer; UInt32 m_eRecordAudioType; } @property (nonatomic, strong) AVAudioRecorder *m_audioRecorder; @property (nonatomic, strong) NSString *m_nsRecorderingPath; @property (nonatomic) BOOL m_bDeletedRecording; - (NSDictionary *)recordingSettings; //- (NSDictionary *)recordingSettingsWithAudioFormat:(NSNumber*)format; + (NSString*)archivePath:(NSString*)path; + (NSString *)globallyUniqueString; - (void)startReceivedRecordingCallBackTimer; @end @implementation IBTAudioRecorder - (void)dealloc { self.m_audioRecorder = nil; self.m_nsRecorderingPath = nil; self.finishRecordingBlock = NULL; self.receivedRecordingBlock = NULL; self.encodeErrorRecordingBlock = NULL; } - (id)init { self = [super init]; if (self) { /* ------- (支持) kAudioFormatMPEG4AAC acc 'aac ' .m4a kAudioFormatAppleLossless alac 'alac' kAudioFormatULaw ulaw 'ulaw' kAudioFormatALaw alaw 'alaw' kAudioFormatiLBC ilbc 'ilbc' kAudioFormatDVIIntelIMA ima4 0x6D730011 kAudioFormatLinearPCM lpcm 'lpcm' ------- (不支持) kAudioFormatMPEGLayer3 mp3 .mp3 */ m_eRecordAudioType = kAudioFormatMPEG4AAC; } return self; } + (void)checkMicrophonePermissionAndRunAction:(void (^)(void))action { AVAudioSession *audioSession = [AVAudioSession sharedInstance]; if ([audioSession respondsToSelector:@selector(requestRecordPermission:)]) { [audioSession requestRecordPermission:^(BOOL granted) { if (granted) { // Microphone enabled code if (action) { action(); } } else { // Microphone disabled code // We're in a background thread here, so jump to main thread to do UI work. dispatch_async(dispatch_get_main_queue(), ^{ [[[UIAlertView alloc] initWithTitle:@"Microphone Access Denied" message:@"This app requires access to your device's Microphone.\n\nPlease enable Microphone access for this app in Settings / Privacy / Microphone" delegate:nil cancelButtonTitle:@"Dismiss" otherButtonTitles:nil] show]; }); } }]; } else { if (action) { action(); } } } - (NSString*)recorderingPath { return _m_nsRecorderingPath; } - (BOOL)m_bIsRecording { return _m_audioRecorder.recording; } - (BOOL)startRecord { return [self startRecordForDuration:0]; } - (BOOL)startRecordForDuration: (NSTimeInterval) duration { if (!_m_audioRecorder.recording) { [[IBTAudioController sharedController] activate]; [[IBTAudioController sharedController] switchToPlayAndRecordMode]; AVAudioSession *audioSession = [AVAudioSession sharedInstance]; CLog(@"AVAudioRecorder category %@",[audioSession category] ); if ([[audioSession category] isEqualToString:AVAudioSessionCategoryPlayAndRecord] || [[audioSession category] isEqualToString:AVAudioSessionCategoryRecord] ) { if ([audioSession isInputAvailable]) { NSError * error = NULL; NSString *fileExists = [[self class] archivePath:kRecordersFileExists]; if (![[NSFileManager defaultManager] fileExistsAtPath:fileExists]) { [[NSFileManager defaultManager] createDirectoryAtPath:fileExists withIntermediateDirectories:YES attributes:nil error:&error]; } if (!error) { NSString *ext = [[self class] pathExtensionForAudioType:m_eRecordAudioType]; NSString *fileFullName = [[self class] globallyUniqueString]; fileFullName = [fileFullName stringByAppendingPathExtension:ext]; NSString *fullPath = [fileExists stringByAppendingPathComponent:fileFullName]; NSDictionary *recordingSettings = [self recordingSettings]; NSURL *fullPathURL = [NSURL fileURLWithPath:fullPath]; AVAudioRecorder *recorder = [[AVAudioRecorder alloc] initWithURL:fullPathURL settings:recordingSettings error:&error]; self.m_audioRecorder = recorder; _m_audioRecorder.delegate = self; _m_audioRecorder.meteringEnabled = YES; if (!error) { if ([_m_audioRecorder prepareToRecord]) { self.m_nsRecorderingPath = fullPath; _m_bDeletedRecording = NO; [self startReceivedRecordingCallBackTimer]; if (duration < 0.001) { return [_m_audioRecorder record]; } else { return [_m_audioRecorder recordForDuration:duration]; } } else CLog(@"AVAudioRecorder prepareToRecord failure."); } else { CLog(@"AVAudioRecorder alloc failure.Error: %@", [error description]); } } else{ CLog(@"AVAudioRecorder createDirectoryAtPath failure.Error: %@", [error description]); } } else CLog(@"Audio input hardware not available."); } else CLog(@"AudioSession not allowed to record."); } else CLog(@"AVAudioRecorder already in recording."); return NO; } - (void)stopRecord { double delayInSeconds = 0.1; //0629 0.1 dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC); dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ if(recordTimer){ [recordTimer invalidate]; recordTimer = nil; } if (_m_audioRecorder.recording) { [_m_audioRecorder stop]; [[IBTAudioController sharedController] deactivate]; } else CLog(@"AVAudioRecorder in not in recording."); }); } - (void)stopAndDeleteRecord { double delayInSeconds = 0.1; //0.1 dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC); dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ if(recordTimer) { [recordTimer invalidate]; recordTimer = nil; } if (_m_audioRecorder.recording) { [_m_audioRecorder stop]; } if (!_m_bDeletedRecording) { _m_bDeletedRecording = [_m_audioRecorder deleteRecording]; } }); } - (void)stopAndDeleteAllRecords { double delayInSeconds = 0.1; //0.1 dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC); dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ if(recordTimer) { [recordTimer invalidate]; recordTimer = nil; } if (_m_audioRecorder.recording) { [_m_audioRecorder stop]; } if (!_m_bDeletedRecording) { _m_bDeletedRecording = [_m_audioRecorder deleteRecording]; } NSString *fileExists = [[self class] archivePath:kRecordersFileExists]; if ([[NSFileManager defaultManager] fileExistsAtPath:fileExists]) { [[NSFileManager defaultManager] createDirectoryAtPath:fileExists withIntermediateDirectories:YES attributes:nil error:nil]; } }); } #pragma mark #pragma mark AVAudioRecorderDelegate - (void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)flag { if(recordTimer) { [recordTimer invalidate]; recordTimer = nil; } if (_finishRecordingBlock) { _finishRecordingBlock(self,flag); } [[IBTAudioController sharedController] deactivate]; } - (void)audioRecorderEncodeErrorDidOccur:(AVAudioRecorder *)recorder error:(NSError *)error { if(recordTimer) { [recordTimer invalidate]; recordTimer = nil; } if (_encodeErrorRecordingBlock) { _encodeErrorRecordingBlock(self,error); } [[IBTAudioController sharedController] deactivate]; } - (void)audioRecorderReceivedRecordingCallBack:(NSTimer*)timer { if (_receivedRecordingBlock) { if (_m_audioRecorder.recording) { [_m_audioRecorder updateMeters]; CLog(@"%f", [_m_audioRecorder peakPowerForChannel:0]); float peakPower = pow(10, (0.05 * [_m_audioRecorder peakPowerForChannel:0])); float averagePower = pow(10, (0.05 * [_m_audioRecorder averagePowerForChannel:0])); float currentTime = _m_audioRecorder.currentTime; _receivedRecordingBlock(self,peakPower,averagePower,currentTime); } } } #pragma mark #pragma mark Private - (void)startReceivedRecordingCallBackTimer{ if(recordTimer) { [recordTimer invalidate]; recordTimer = nil; } recordTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(audioRecorderReceivedRecordingCallBack:) userInfo:nil repeats:YES]; NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; [runLoop addTimer:recordTimer forMode:NSDefaultRunLoopMode]; } - (NSDictionary *)recordingSettings { #if TARGET_IPHONE_SIMULATOR return [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt:kAudioFormatLinearPCM], AVFormatIDKey, [NSNumber numberWithFloat:AUDIO_SAMPLE_RATE], AVSampleRateKey, [NSNumber numberWithInt: 2], AVNumberOfChannelsKey, [NSNumber numberWithInt:16], AVLinearPCMBitDepthKey, [NSNumber numberWithInt:AVAudioQualityMax],AVEncoderAudioQualityKey, [NSNumber numberWithBool:NO], AVLinearPCMIsBigEndianKey, [NSNumber numberWithBool:NO], AVLinearPCMIsFloatKey, nil]; #else return @{ AVFormatIDKey : @(m_eRecordAudioType), //ID AVSampleRateKey : @(AUDIO_SAMPLE_RATE), //采样率 AVNumberOfChannelsKey : @(1), //通道数 AVLinearPCMBitDepthKey : @(8), //采样位数 AVLinearPCMIsBigEndianKey :@(NO), AVLinearPCMIsFloatKey : @(NO), // AVEncoderBitRateKey : @(96), //比特率 16bit - 96db AVEncoderAudioQualityKey : @(AVAudioQualityLow), }; // return [NSDictionary dictionaryWithObjectsAndKeys: // [NSNumber numberWithInt:kAudioFormatLinearPCM], AVFormatIDKey,//ID // [NSNumber numberWithFloat:AUDIO_SAMPLE_RATE], AVSampleRateKey,//采样率 // [NSNumber numberWithInt:2], AVNumberOfChannelsKey,//通道数 // [NSNumber numberWithInt:16], AVLinearPCMBitDepthKey,//采样位数 // [NSNumber numberWithBool:NO], AVLinearPCMIsBigEndianKey, // [NSNumber numberWithBool:NO], AVLinearPCMIsFloatKey, // //// [NSNumber numberWithInt:96], AVEncoderBitRateKey, //// [NSNumber numberWithInt:AVAudioQualityLow],AVEncoderAudioQualityKey, // // nil]; // //24000.0 1 8 96 #endif } + (NSString *)pathExtensionForAudioType:(UInt32)type { switch (type) { case kAudioFormatMPEG4AAC: return @"m4a"; break; default: return @"caf"; break; } } + (NSString *)archivePath:(NSString*)path { NSString *filePath = [IBTCommon archivePathForCurrentUser]; return [filePath stringByAppendingPathComponent:path]; } + (NSString *)globallyUniqueString { return [[NSProcessInfo processInfo] globallyUniqueString]; } + (NSString *)recordFilePath { return [[self class] archivePath:kRecordersFileExists]; } @end