IBTAudioRecorder.m 13.1 KB
//
//  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