| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361 |
- // Created by Tencent on 2023/06/09.
- // Copyright © 2023 Tencent. All rights reserved.
- //
- // TUIAudioRecorder.m
- // TUIChat
- //
- #import "TUIAudioRecorder.h"
- #import <AVFoundation/AVFoundation.h>
- #import <TIMCommon/TIMCommonModel.h>
- #import <TIMCommon/TIMDefine.h>
- #import <TUICore/TUICore.h>
- #import <TUICore/TUILogin.h>
- #import "TUIAIDenoiseSignatureManager.h"
- @interface TUIAudioRecorder () <AVAudioRecorderDelegate, TUINotificationProtocol>
- @property(nonatomic, strong) AVAudioRecorder *recorder;
- @property(nonatomic, strong) NSTimer *recordTimer;
- @property(nonatomic, assign) BOOL isUsingCallKitRecorder;
- @property(nonatomic, copy, readwrite) NSString *recordedFilePath;
- @property(nonatomic, assign) NSTimeInterval currentRecordTime;
- @end
- @implementation TUIAudioRecorder
- - (instancetype)init {
- self = [super init];
- if (self) {
- [self configNotify];
- }
- return self;
- }
- - (void)configNotify {
- [TUICore registerEvent:TUICore_RecordAudioMessageNotify subKey:TUICore_RecordAudioMessageNotify_RecordAudioVoiceVolumeSubKey object:self];
- }
- - (void)dealloc {
- [TUICore unRegisterEventByObject:self];
- }
- #pragma mark - Public
- - (void)record {
- [self checkMicPermissionWithCompletion:^(BOOL isGranted, BOOL isFirstChek) {
- if (TUILogin.getCurrentBusinessScene != None) {
- [TUITool makeToast:TIMCommonLocalizableString(TUIKitMessageTypeOtherUseMic) duration:3];
- return;
- }
- if (isFirstChek) {
- if (self.delegate && [self.delegate respondsToSelector:@selector(audioRecorder:didCheckPermission:isFirstTime:)]) {
- [self.delegate audioRecorder:self didCheckPermission:isGranted isFirstTime:YES];
- }
- return;
- }
- if (self.delegate && [self.delegate respondsToSelector:@selector(audioRecorder:didCheckPermission:isFirstTime:)]) {
- [self.delegate audioRecorder:self didCheckPermission:isGranted isFirstTime:NO];
- }
- if (isGranted) {
- [self createRecordedFilePath];
- if (![self startCallKitRecording]) {
- [self startSystemRecording];
- }
- }
- }];
- }
- - (void)stop {
- [self stopRecordTimer];
- if (self.isUsingCallKitRecorder) {
- [self stopCallKitRecording];
- } else {
- [self stopSystemRecording];
- }
- }
- - (void)cancel {
- [self stopRecordTimer];
- if (self.isUsingCallKitRecorder) {
- [self stopCallKitRecording];
- } else {
- [self cancelSystemRecording];
- }
- }
- #pragma mark - Private
- - (void)createRecordedFilePath {
- self.recordedFilePath = [TUIKit_Voice_Path stringByAppendingString:[TUITool genVoiceName:nil withExtension:@"m4a"]];
- }
- - (void)stopRecordTimer {
- if (self.recordTimer) {
- [self.recordTimer invalidate];
- self.recordTimer = nil;
- }
- }
- #pragma mark-- Timer
- - (void)triggerRecordTimer {
- self.currentRecordTime = 0;
- self.recordTimer = [NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:@selector(onRecordTimerTriggered:) userInfo:nil repeats:YES];
- }
- - (void)onRecordTimerTriggered:(NSTimer *)timer {
- [self.recorder updateMeters];
- if (self.isUsingCallKitRecorder) {
- /// To ensure the callkit recorder's recording time is enough for 60 seconds.
- self.currentRecordTime += 0.2;
- if (self.delegate && [self.delegate respondsToSelector:@selector(audioRecorder:didRecordTimeChanged:)]) {
- [self.delegate audioRecorder:self didRecordTimeChanged:self.currentRecordTime];
- }
- } else {
- float power = [self.recorder averagePowerForChannel:0];
- NSTimeInterval currentTime = self.recorder.currentTime;
- if (self.delegate && [self.delegate respondsToSelector:@selector(audioRecorder:didPowerChanged:)]) {
- [self.delegate audioRecorder:self didPowerChanged:power];
- }
- if (self.delegate && [self.delegate respondsToSelector:@selector(audioRecorder:didRecordTimeChanged:)]) {
- [self.delegate audioRecorder:self didRecordTimeChanged:currentTime];
- }
- }
- }
- - (void)checkMicPermissionWithCompletion:(void (^)(BOOL isGranted, BOOL isFirstChek))completion {
- AVAudioSessionRecordPermission permission = AVAudioSession.sharedInstance.recordPermission;
- /**
- * For the first request for authorization after a new installation, it is necessary to
- * determine whether it is Undetermined again to avoid errors.
- */
- if (permission == AVAudioSessionRecordPermissionDenied || permission == AVAudioSessionRecordPermissionUndetermined) {
- [AVAudioSession.sharedInstance requestRecordPermission:^(BOOL granted) {
- dispatch_async(dispatch_get_main_queue(), ^{
- if (completion) {
- completion(granted, YES);
- }
- });
- }];
- return;
- }
- BOOL isGranted = permission == AVAudioSessionRecordPermissionGranted;
- if (completion) {
- completion(isGranted, NO);
- }
- }
- #pragma mark-- Record audio using system framework
- - (void)startSystemRecording {
- self.isUsingCallKitRecorder = NO;
- AVAudioSession *session = [AVAudioSession sharedInstance];
- NSError *error = nil;
- [session setCategory:AVAudioSessionCategoryPlayAndRecord error:&error];
- [session setActive:YES error:&error];
- NSDictionary *recordSetting = [[NSDictionary alloc] initWithObjectsAndKeys:
- /**
- * Sampling rate: 8000/11025/22050/44100/96000 (this parameter affects the audio
- * quality)
- */
- [NSNumber numberWithFloat:16000.0], AVSampleRateKey,
- /**
- * Audio format
- */
- [NSNumber numberWithInt:kAudioFormatMPEG4AAC], AVFormatIDKey,
- /**
- * Sampling bits: 8, 16, 24, 32, default is 16
- */
- [NSNumber numberWithInt:16], AVLinearPCMBitDepthKey,
- /**
- * Number of audio channels 1 or 2
- */
- [NSNumber numberWithInt:1], AVNumberOfChannelsKey,
- /**
- * Recording quality
- */
- [NSNumber numberWithInt:AVAudioQualityHigh], AVEncoderAudioQualityKey, nil];
- [self createRecordedFilePath];
- NSURL *url = [NSURL fileURLWithPath:self.recordedFilePath];
- self.recorder = [[AVAudioRecorder alloc] initWithURL:url settings:recordSetting error:nil];
- self.recorder.meteringEnabled = YES;
- [self.recorder prepareToRecord];
- [self.recorder record];
- [self.recorder updateMeters];
- [self triggerRecordTimer];
- NSLog(@"start system recording");
- }
- - (void)stopSystemRecording {
- if (AVAudioSession.sharedInstance.recordPermission == AVAudioSessionRecordPermissionDenied) {
- return;
- }
- if ([self.recorder isRecording]) {
- [self.recorder stop];
- }
- self.recorder = nil;
- NSLog(@"stop system recording");
- }
- - (void)cancelSystemRecording {
- if ([self.recorder isRecording]) {
- [self.recorder stop];
- }
- NSString *path = self.recorder.url.path;
- if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
- [[NSFileManager defaultManager] removeItemAtPath:path error:nil];
- }
- self.recorder = nil;
- NSLog(@"cancel system recording");
- }
- #pragma mark-- Record audio using TUICallKit framework
- - (BOOL)startCallKitRecording {
- if (![TUICore getService:TUICore_TUIAudioMessageRecordService]) {
- NSLog(@"TUICallKit audio recording service does not exist");
- return NO;
- }
- NSString *signature = [TUIAIDenoiseSignatureManager sharedInstance].signature;
- if (signature.length == 0) {
- NSLog(@"denoise signature is empty");
- return NO;
- }
- NSMutableDictionary *audioRecordParam = [[NSMutableDictionary alloc] init];
- [audioRecordParam setValue:signature forKey:TUICore_TUIAudioMessageRecordService_StartRecordAudioMessageMethod_SignatureKey];
- [audioRecordParam setValue:@([TUILogin getSdkAppID]) forKey:TUICore_TUIAudioMessageRecordService_StartRecordAudioMessageMethod_SdkappidKey];
- [audioRecordParam setValue:self.recordedFilePath forKey:TUICore_TUIAudioMessageRecordService_StartRecordAudioMessageMethod_PathKey];
- @weakify(self);
- void (^startCallBack)(NSInteger errorCode, NSString *errorMessage, NSDictionary *param) =
- ^(NSInteger errorCode, NSString *errorMessage, NSDictionary *param) {
- @strongify(self);
- NSString *method = param[@"method"];
- if ([method isEqualToString:TUICore_RecordAudioMessageNotify_StartRecordAudioMessageSubKey]) {
- [self onTUICallKitRecordStarted:errorCode];
- }
- };
- [TUICore callService:TUICore_TUIAudioMessageRecordService
- method:TUICore_TUIAudioMessageRecordService_StartRecordAudioMessageMethod
- param:audioRecordParam
- resultCallback:startCallBack];
- self.isUsingCallKitRecorder = YES;
- NSLog(@"start TUICallKit recording");
- return true;
- }
- - (void)stopCallKitRecording {
- @weakify(self);
- void (^stopCallBack)(NSInteger errorCode, NSString *errorMessage, NSDictionary *param) =
- ^(NSInteger errorCode, NSString *errorMessage, NSDictionary *param) {
- @strongify(self);
- NSString *method = param[@"method"];
- if ([method isEqualToString:TUICore_RecordAudioMessageNotify_StopRecordAudioMessageSubKey]) {
- [self onTUICallKitRecordCompleted:errorCode];
- }
- };
- [TUICore callService:TUICore_TUIAudioMessageRecordService
- method:TUICore_TUIAudioMessageRecordService_StopRecordAudioMessageMethod
- param:nil
- resultCallback:stopCallBack];
- NSLog(@"stop TUICallKit recording");
- }
- #pragma mark - TUINotificationProtocol
- - (void)onNotifyEvent:(NSString *)key subKey:(NSString *)subKey object:(nullable id)anObject param:(NSDictionary *)param {
- if ([key isEqualToString:TUICore_RecordAudioMessageNotify]) {
- if (param == nil) {
- NSLog(@"TUICallKit notify param is invalid");
- return;
- }
- if ([subKey isEqualToString:TUICore_RecordAudioMessageNotify_RecordAudioVoiceVolumeSubKey]) {
- NSUInteger volume = [param[@"volume"] unsignedIntegerValue];
- [self onTUICallKitVolumeChanged:volume];
- }
- }
- }
- - (void)onTUICallKitRecordStarted:(NSInteger)errorCode {
- switch (errorCode) {
- case TUICore_RecordAudioMessageNotifyError_None: {
- [self triggerRecordTimer];
- break;
- }
- case TUICore_RecordAudioMessageNotifyError_MicPermissionRefused: {
- break;
- }
- case TUICore_RecordAudioMessageNotifyError_StatusInCall: {
- [TUITool makeToast:TIMCommonLocalizableString(TUIKitInputRecordRejectedInCall)];
- break;
- }
- case TUICore_RecordAudioMessageNotifyError_StatusIsAudioRecording: {
- [TUITool makeToast:TIMCommonLocalizableString(TUIKitInputRecordRejectedIsRecording)];
- break;
- }
- case TUICore_RecordAudioMessageNotifyError_RequestAudioFocusFailed:
- case TUICore_RecordAudioMessageNotifyError_RecordInitFailed:
- case TUICore_RecordAudioMessageNotifyError_PathFormatNotSupport:
- case TUICore_RecordAudioMessageNotifyError_MicStartFail:
- case TUICore_RecordAudioMessageNotifyError_MicNotAuthorized:
- case TUICore_RecordAudioMessageNotifyError_MicSetParamFail:
- case TUICore_RecordAudioMessageNotifyError_MicOccupy: {
- [self stopCallKitRecording];
- NSLog(@"start TUICallKit recording failed, errorCode: %ld", (long)errorCode);
- break;
- }
- case TUICore_RecordAudioMessageNotifyError_InvalidParam:
- case TUICore_RecordAudioMessageNotifyError_SignatureError:
- case TUICore_RecordAudioMessageNotifyError_SignatureExpired:
- default: {
- [self stopCallKitRecording];
- [self startSystemRecording];
- NSLog(@"start TUICallKit recording failed, errorCode: %ld, switch to system recorder", (long)errorCode);
- break;
- }
- }
- }
- - (void)onTUICallKitRecordCompleted:(NSInteger)errorCode {
- switch (errorCode) {
- case TUICore_RecordAudioMessageNotifyError_None: {
- [self stopRecordTimer];
- break;
- }
- case TUICore_RecordAudioMessageNotifyError_NoMessageToRecord:
- case TUICore_RecordAudioMessageNotifyError_RecordFailed: {
- NSLog(@"stop TUICallKit recording failed, errorCode: %ld", (long)errorCode);
- }
- default:
- break;
- }
- }
- - (void)onTUICallKitVolumeChanged:(NSUInteger)volume {
- /// Adapt volume to power.
- float power = (NSInteger)volume - 90;
- if (self.delegate && [self.delegate respondsToSelector:@selector(audioRecorder:didPowerChanged:)]) {
- [self.delegate audioRecorder:self didPowerChanged:power];
- }
- }
- @end
|