From: liangzhenying <867174124@qq.com> Date: Sat, 30 Mar 2024 03:40:57 +0000 (+0800) Subject: 增加2.4g数据传输 X-Git-Tag: 阿里鉴权~8 X-Git-Url: http://git.snicker.com.cn/Users/apple/Desktop/static/gitweb.css?a=commitdiff_plain;h=67f96bb9a237cd806640176a65f22d2a0f92d8e4;p=mirrors%2FMouseSdk-MacOS.git 增加2.4g数据传输 --- diff --git a/MouseSdk/AiMouseSdk/AiAudioManager.h b/MouseSdk/AiMouseSdk/AiAudioManager.h index fe67d0c..d3e14dc 100644 --- a/MouseSdk/AiMouseSdk/AiAudioManager.h +++ b/MouseSdk/AiMouseSdk/AiAudioManager.h @@ -11,7 +11,7 @@ NS_ASSUME_NONNULL_BEGIN @interface AiAudioManager : NSObject @property (nonatomic,strong)void (^outputData)(NSData *data); -@property (nonatomic,strong)void (^audioRecordCallBack)(NSData *data); +@property (nonatomic,strong)void (^decodeCallBack)(NSData *data); @property (nonatomic,strong)void (^outputLog)(NSString *log); diff --git a/MouseSdk/AiMouseSdk/AiAudioManager.m b/MouseSdk/AiMouseSdk/AiAudioManager.m index 1b6d808..2e53feb 100644 --- a/MouseSdk/AiMouseSdk/AiAudioManager.m +++ b/MouseSdk/AiMouseSdk/AiAudioManager.m @@ -49,7 +49,7 @@ __weak typeof(self) ws = self; self.audioDecoder.onRecvPCMData = ^(NSData * _Nonnull pcmData) { // [ws showLog:[NSString stringWithFormat:@"转换后数据%@",[AiMouseTools dataToHexString:pcmData]]]; - if(ws.audioRecordCallBack)ws.audioRecordCallBack(pcmData); + if(ws.decodeCallBack)ws.decodeCallBack(pcmData); }; [self.audioDecoder initSBCDecodec]; } diff --git a/MouseSdk/AiMouseSdk/AiAuthManager.m b/MouseSdk/AiMouseSdk/AiAuthManager.m index 0a9b99a..64b4276 100644 --- a/MouseSdk/AiMouseSdk/AiAuthManager.m +++ b/MouseSdk/AiMouseSdk/AiAuthManager.m @@ -24,7 +24,7 @@ _bizbusManager.bleSendDataDelegate = self; _bizbusManager.appVersion = @"--"; _bizbus_connect_queue = dispatch_queue_create("bizbus_connect_queue", DISPATCH_QUEUE_SERIAL); - [self enableBizbusLog:false]; + [self enableBizbusLog:true]; } return self; } diff --git a/MouseSdk/AiMouseSdk/AiMouse.h b/MouseSdk/AiMouseSdk/AiMouse.h index fd49e55..9f3f9a2 100644 --- a/MouseSdk/AiMouseSdk/AiMouse.h +++ b/MouseSdk/AiMouseSdk/AiMouse.h @@ -8,6 +8,11 @@ #import #import +typedef NS_ENUM(NSInteger,AiMouseConnectType) { + AiMouseConnectType_bluetooth = 0,///蓝牙 + AiMouseConnectType_usb,///usb +}; + typedef NS_ENUM(NSInteger,AiMouseAuthState) { AiMouseAuthState_Unknow = 0,///未知 AiMouseAuthState_Success,///鉴权成功 @@ -26,9 +31,9 @@ typedef NS_ENUM(NSInteger,AiMouseAuthState) { /// 鼠标按键指令回调 /// - Parameter data: 数据。 -/// M 键 短按0x42;长按按下(开始录音)0x25, 长按抬起(结束录音) 0x26; -/// AI键 短按0x43 ,长按按下(开始录音)0x27, 长按抬起(结束录音) 0x28; -/// 语音键 按下(开始录音) 0x31,弹起(结束录音)0x32; +/// 前 第一次单击31(开始录音), 第二次单击32(结束录音) , 按下21(开始录音)抬起22(结束录音) +/// 中 单击42, 按下25(开始录音)抬起26(结束录音) +/// 后 单击43,按下27(开始录音)抬起28(结束录音) /// 其他未定义 - (void)aiMouseSendCmdData:(NSData *)data; @@ -41,11 +46,15 @@ typedef NS_ENUM(NSInteger,AiMouseAuthState) { @interface AiMouse : NSObject @property (nonatomic, readonly) NSString *version;/// 版本号 @property (nonatomic, weak) id delegate;/// 代理 +@property (nonatomic, readonly) AiMouseConnectType connectType;//连接类型 @property (nonatomic, readonly) AiMouseAuthState authState;/// 鉴权状态,默认是Unknow。所以必须在连上蓝牙设备找到鉴权特征后,需要主动调用startAuthentication方法去鉴权。(只有鉴权成功,AI键,语音键、M键才可用) /// 单利 +(AiMouse *)shared; +/// 切换连接类型 +/// - Parameter type: 连接类型 +- (void)switchConnect:(AiMouseConnectType)type; /// 开始鉴权 /// - Parameter finished: 鉴权结果回调 diff --git a/MouseSdk/AiMouseSdk/AiMouse.m b/MouseSdk/AiMouseSdk/AiMouse.m index b1e0860..897d041 100644 --- a/MouseSdk/AiMouseSdk/AiMouse.m +++ b/MouseSdk/AiMouseSdk/AiMouse.m @@ -5,18 +5,24 @@ // Created by incar on 2024/2/28. // + + #import "AiMouse.h" #import "AiFrameManager.h" #import "AiAuthManager.h" #import "AiAudioManager.h" #import "AiMouseTools.h" +#import "AiMouseTools2.h" #import "AiMouseDeviceInfo.h" +#import "AiMouseConfig.h" +#import "AiRecordManager.h" @interface AiMouse() @property (nonatomic, strong) AiFrameManager *frameManager; @property (nonatomic, strong) AiAuthManager *authManager; @property (nonatomic, strong) AiAudioManager *audioManager; @property (nonatomic, strong) AiMouseDeviceInfo *deviceInfo; +@property (nonatomic, strong) AiRecordManager *recordManager; @property (nonatomic, strong) void (^deviceInfoBlock)(AiMouseDeviceInfo *deviceInfo,NSError *error); @property (nonatomic, strong) void (^setCPIBlock)(BOOL isSuccess,NSError *error); @property (nonatomic, strong) void (^authBlock)(AiMouseAuthState state); @@ -35,6 +41,7 @@ -(instancetype)init{ if (self = [super init]) { _authState = AiMouseAuthState_Unknow; + _connectType = AiMouseConnectType_bluetooth; } return self; } @@ -73,7 +80,7 @@ if (!_audioManager) { _audioManager = [[AiAudioManager alloc] init]; __weak typeof(self) ws = self; - _audioManager.audioRecordCallBack = ^(NSData * _Nonnull data) { + _audioManager.decodeCallBack = ^(NSData * _Nonnull data) { [ws sendRecordData:data]; }; _audioManager.outputLog = ^(NSString * _Nonnull log) { @@ -82,9 +89,26 @@ } return _audioManager; } + +- (AiRecordManager *)recordManager{ + if (!_recordManager) { + _recordManager = [[AiRecordManager alloc] init]; + __weak typeof(self) ws = self; + _recordManager.didRecordData = ^(NSData * _Nonnull data) { + [ws showLog:[NSString stringWithFormat:@"%@",data]]; + [ws sendRecordData:data]; + }; + } + return _recordManager; +} #pragma mark - pub Api -- (void)initSDK{ +- (void)switchConnect:(AiMouseConnectType)type{ + _connectType = type; + [self showLog:type == AiMouseConnectType_bluetooth?@"sdk切换到蓝牙模式":@"sdk切换到usb模式"]; + if (type == AiMouseConnectType_usb) { + [self.recordManager checkPermissions]; + } } //开始鉴权 - (void)startAuthentication:(void (^)(AiMouseAuthState))finished{ @@ -99,28 +123,56 @@ } - (void)sendAiDataToSdk:(NSData *)data{ - NSString *str = [[AiMouseTools dataToHexString:data] lowercaseString]; - NSString *cmd = [str substringWithRange:NSMakeRange(0, 12)]; - // 0A65004E003200010101C0C10000A5A5 - if(data.length == 16){ - NSData *cmd = [data subdataWithRange:NSMakeRange(5, 1)]; -// NSString *cmdStr = [[AiMouseTools dataToHexString:cmd] lowercaseString]; -// NSLog(@"cmd = %@",cmdStr); - [self sendCmdData:cmd]; - } - else if([cmd hasPrefix:@"9c31"]){ - //声音 - [self.audioManager receiveData:data]; + if(self.connectType == AiMouseConnectType_usb){ +// 0AFFFF12BC32 + if(data.length == 6){ + NSData *subData = [data subdataWithRange:NSMakeRange(5, 1)]; + [self sendCmdData:subData]; + + uint8 cmd; + [subData getBytes:&cmd length:1]; +// [self showLog:[NSString stringWithFormat:@"cmd = %X",cmdInt]]; + if(cmd == 0x31 || cmd == 0x21 || cmd == 0x25 ||cmd == 0x27){ + [self startRecord]; + } + else if(cmd == 0x32 || cmd == 0x22 || cmd == 0x26 ||cmd == 0x28){ + [self stopRecord]; + } + } + else{ + [self showLog:@"未实现指令"]; + } } - else{ - [self showLog:@"未实现指令"]; + else if (self.connectType == AiMouseConnectType_bluetooth){ + NSString *str = [[AiMouseTools dataToHexString:data] lowercaseString]; + NSString *cmd = [str substringWithRange:NSMakeRange(0, 12)]; + // 0A65004E003200010101C0C10000A5A5 + if(data.length == 16){ + NSData *cmd = [data subdataWithRange:NSMakeRange(5, 1)]; + // NSString *cmdStr = [[AiMouseTools dataToHexString:cmd] lowercaseString]; + // NSLog(@"cmd = %@",cmdStr); + [self sendCmdData:cmd]; + } + else if([cmd hasPrefix:@"9c31"]){ + //声音 + [self.audioManager receiveData:data]; + } + else{ + [self showLog:@"未实现指令"]; + } } } -(void)sendDeviceDataToSdk:(NSData *)data{ - NSDictionary *dict = [AiMouseTools dataToDictionary:data]; - NSString *str = [AiMouseTools dictionaryToString:dict]; - [self showLog:str]; + NSString *str = [AiMouseTools dataToUtf8String:data]; + NSString *str2 = [AiMouseTools2 fixDeviceData:str]; + NSDictionary *dict = [AiMouseTools stringToDictionary:str2]; + if (dict == nil) { + //鼠标移动数据,不处理 + return; + } + [self showLog:[NSString stringWithFormat:@"fix前 = %@",str]]; + [self showLog:[NSString stringWithFormat:@"fix后 = %@",str2]]; if(dict[@"set_config"]){ if([dict[@"set_config"] intValue] == 1){ if(self.setCPIBlock)self.setCPIBlock(true, nil); @@ -198,30 +250,31 @@ } - (void)sendRecordData:(NSData *)data{ - if (self.authState != AiMouseAuthState_Success) return; +// if (self.authState != AiMouseAuthState_Success) return; if (self.delegate) { [self.delegate aiMouseSendRecordData:data]; } } - (void)sendCmdData:(NSData *)data{ - if (self.authState == AiMouseAuthState_Success){ +// if (self.authState == AiMouseAuthState_Success){ if (self.delegate) { [self.delegate aiMouseSendCmdData:data]; } - } - else{ -// [self showLog:@"设备鉴权失败"]; - } +// } +// else{ +//// [self showLog:@"设备鉴权失败"]; +// } } - (void)authResult:(BOOL)isSuccess{ _authState = isSuccess ? AiMouseAuthState_Success:AiMouseAuthState_Failure; + [self showLog:isSuccess?@"鉴权成功":@"鉴权失败"]; self.authBlock(_authState); [self setAuthStateToDevice:isSuccess]; } - (void)showLog:(NSString *)log{ -// NSLog(@"[sdk]%@",log); + NSLog(@"[sdk]%@",log); } - (void)sendTestLP{ @@ -235,4 +288,15 @@ NSData *data = [AiMouseTools dictionaryToData:@{@"cmd":@"set_config",@"set_activated_dtate":isSuccess?@"1":@"0"}]; [self sendDeviceInfoData:data]; } + +- (void)startRecord{ +// [self showLog:@"开始录音"]; + [self.recordManager start]; +} + +- (void)stopRecord{ + [self.recordManager stop]; +// [self showLog:@"结束录音"]; +} + @end diff --git a/MouseSdk/AiMouseSdk/AiMouseAudioRecord.h b/MouseSdk/AiMouseSdk/AiMouseAudioRecord.h new file mode 100644 index 0000000..c54cf62 --- /dev/null +++ b/MouseSdk/AiMouseSdk/AiMouseAudioRecord.h @@ -0,0 +1,36 @@ +// +// AiMouseAudioRecord.h +// AVDemo +// +// Created by incar on 2024/3/27. +// + +#import +#import + +@interface AiMouseAudioConfig : NSObject ++ (instancetype)defaultConfig; + +@property (nonatomic, assign) NSUInteger channels; // 声道数,default: 2。 +@property (nonatomic, assign) NSUInteger sampleRate; // 采样率,default: 44100。 +@property (nonatomic, assign) NSUInteger bitDepth; // 量化位深,default: 16。 +@end + + + +NS_ASSUME_NONNULL_BEGIN + +@interface AiMouseAudioRecord : NSObject ++ (instancetype)new NS_UNAVAILABLE; +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)initWithConfig:(AiMouseAudioConfig *)config; + +@property (nonatomic, strong, readonly) AiMouseAudioConfig *config; +@property (nonatomic, copy) void (^sampleBufferOutputCallBack)(CMSampleBufferRef sample); // 音频采集数据回调。 +@property (nonatomic, copy) void (^errorCallBack)(NSError *error); // 音频采集错误回调。 + +- (void)startRunning; // 开始采集音频数据。 +- (void)stopRunning; // 停止采集音频数据。 +@end + +NS_ASSUME_NONNULL_END diff --git a/MouseSdk/AiMouseSdk/AiMouseAudioRecord.m b/MouseSdk/AiMouseSdk/AiMouseAudioRecord.m new file mode 100644 index 0000000..2d1f250 --- /dev/null +++ b/MouseSdk/AiMouseSdk/AiMouseAudioRecord.m @@ -0,0 +1,268 @@ +// +// AiMouseAudioRecord.m +// AVDemo +// +// Created by incar on 2024/3/27. +// + +#import "AiMouseAudioRecord.h" +#import +#import + +@implementation AiMouseAudioConfig ++ (instancetype)defaultConfig { + AiMouseAudioConfig *config = [[self alloc] init]; + config.channels = 2; + config.sampleRate = 44100; + config.bitDepth = 16; + + return config; +} + +@end + +@interface AiMouseAudioRecord () +@property (nonatomic, assign) AudioComponentInstance audioCaptureInstance; // 音频采集实例。 +@property (nonatomic, assign) AudioStreamBasicDescription audioFormat; // 音频采集参数。 +@property (nonatomic, strong, readwrite) AiMouseAudioConfig *config; +@property (nonatomic, strong) dispatch_queue_t captureQueue; +@property (nonatomic, assign) BOOL isError; +@end + +@implementation AiMouseAudioRecord + +#pragma mark - Lifecycle +- (instancetype)initWithConfig:(AiMouseAudioConfig *)config { + self = [super init]; + if (self) { + _config = config; + _captureQueue = dispatch_queue_create("com.KeyFrameKit.audioCapture", DISPATCH_QUEUE_SERIAL); + } + + return self; +} + +- (void)dealloc { + // 清理音频采集实例。 + if (_audioCaptureInstance) { + AudioOutputUnitStop(_audioCaptureInstance); + AudioComponentInstanceDispose(_audioCaptureInstance); + _audioCaptureInstance = nil; + } +} + +#pragma mark - Action +- (void)startRunning { + if (self.isError) { + return; + } + + __weak typeof(self) weakSelf = self; + dispatch_async(_captureQueue, ^{ + if (!weakSelf.audioCaptureInstance) { + NSError *error = nil; + // 第一次 startRunning 时创建音频采集实例。 + [weakSelf setupAudioCaptureInstance:&error]; + if (error) { + // 捕捉并回调创建音频实例时的错误。 + [weakSelf callBackError:error]; + return; + } + } + + // 开始采集。 + OSStatus startStatus = AudioOutputUnitStart(weakSelf.audioCaptureInstance); + if (startStatus != noErr) { + // 捕捉并回调开始采集时的错误。 + [weakSelf callBackError:[NSError errorWithDomain:NSStringFromClass([AiMouseAudioRecord class]) code:startStatus userInfo:nil]]; + } + }); +} + +- (void)stopRunning { + if (self.isError) { + return; + } + + __weak typeof(self) weakSelf = self; + dispatch_async(_captureQueue, ^{ + if (weakSelf.audioCaptureInstance) { + // 停止采集。 + OSStatus stopStatus = AudioOutputUnitStop(weakSelf.audioCaptureInstance); + if (stopStatus != noErr) { + // 捕捉并回调停止采集时的错误。 + [weakSelf callBackError:[NSError errorWithDomain:NSStringFromClass([AiMouseAudioRecord class]) code:stopStatus userInfo:nil]]; + } + } + }); +} + +#pragma mark - Utility +- (void)setupAudioCaptureInstance:(NSError **)error { + // 1、设置音频组件描述。 + AudioComponentDescription acd = { + .componentType = kAudioUnitType_Output, + //.componentSubType = kAudioUnitSubType_VoiceProcessingIO, // 回声消除模式 +// .componentSubType = kAudioUnitSubType_RemoteIO, + .componentManufacturer = kAudioUnitManufacturer_Apple, + .componentFlags = 0, + .componentFlagsMask = 0, + }; + + // 2、查找符合指定描述的音频组件。 + AudioComponent component = AudioComponentFindNext(NULL, &acd); + + // 3、创建音频组件实例。 + OSStatus status = AudioComponentInstanceNew(component, &_audioCaptureInstance); + if (status != noErr) { + *error = [NSError errorWithDomain:NSStringFromClass(self.class) code:status userInfo:nil]; + return; + } + + // 4、设置实例的属性:可读写。0 不可读写,1 可读写。 + UInt32 flagOne = 1; + AudioUnitSetProperty(_audioCaptureInstance, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, &flagOne, sizeof(flagOne)); + + // 5、设置实例的属性:音频参数,如:数据格式、声道数、采样位深、采样率等。 + AudioStreamBasicDescription asbd = {0}; + asbd.mFormatID = kAudioFormatLinearPCM; // 原始数据为 PCM,采用声道交错格式。 + asbd.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked; + asbd.mChannelsPerFrame = (UInt32) self.config.channels; // 每帧的声道数 + asbd.mFramesPerPacket = 1; // 每个数据包帧数 + asbd.mBitsPerChannel = (UInt32) self.config.bitDepth; // 采样位深 + asbd.mBytesPerFrame = asbd.mChannelsPerFrame * asbd.mBitsPerChannel / 8; // 每帧字节数 (byte = bit / 8) + asbd.mBytesPerPacket = asbd.mFramesPerPacket * asbd.mBytesPerFrame; // 每个包的字节数 + asbd.mSampleRate = self.config.sampleRate; // 采样率 + self.audioFormat = asbd; + status = AudioUnitSetProperty(_audioCaptureInstance, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &asbd, sizeof(asbd)); + if (status != noErr) { + *error = [NSError errorWithDomain:NSStringFromClass(self.class) code:status userInfo:nil]; + return; + } + + // 6、设置实例的属性:数据回调函数。 + AURenderCallbackStruct cb; + cb.inputProcRefCon = (__bridge void *) self; + cb.inputProc = audioBufferCallBack; + status = AudioUnitSetProperty(_audioCaptureInstance, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, 1, &cb, sizeof(cb)); + if (status != noErr) { + *error = [NSError errorWithDomain:NSStringFromClass(self.class) code:status userInfo:nil]; + return; + } + + // 7、初始化实例。 + status = AudioUnitInitialize(_audioCaptureInstance); + if (status != noErr) { + *error = [NSError errorWithDomain:NSStringFromClass(self.class) code:status userInfo:nil]; + return; + } +} + +- (void)callBackError:(NSError *)error { + self.isError = YES; + if (error && self.errorCallBack) { + dispatch_async(dispatch_get_main_queue(), ^{ + self.errorCallBack(error); + }); + } +} + ++ (CMSampleBufferRef)sampleBufferFromAudioBufferList:(AudioBufferList)buffers inTimeStamp:(const AudioTimeStamp *)inTimeStamp inNumberFrames:(UInt32)inNumberFrames description:(AudioStreamBasicDescription)description { + CMSampleBufferRef sampleBuffer = NULL; // 待生成的 CMSampleBuffer 实例的引用。 + + // 1、创建音频流的格式描述信息。 + CMFormatDescriptionRef format = NULL; + OSStatus status = CMAudioFormatDescriptionCreate(kCFAllocatorDefault, &description, 0, NULL, 0, NULL, NULL, &format); + if (status != noErr) { + CFRelease(format); + return nil; + } + + // 2、处理音频帧的时间戳信息。 + mach_timebase_info_data_t info = {0, 0}; + mach_timebase_info(&info); + uint64_t time = inTimeStamp->mHostTime; + // 转换为纳秒。 + time *= info.numer; + time /= info.denom; + // PTS。 + CMTime presentationTime = CMTimeMake(time, 1000000000.0f); + // 对于音频,PTS 和 DTS 是一样的。 + CMSampleTimingInfo timing = {CMTimeMake(1, description.mSampleRate), presentationTime, presentationTime}; + + // 3、创建 CMSampleBuffer 实例。 + status = CMSampleBufferCreate(kCFAllocatorDefault, NULL, false, NULL, NULL, format, (CMItemCount) inNumberFrames, 1, &timing, 0, NULL, &sampleBuffer); + if (status != noErr) { + CFRelease(format); + return nil; + } + + // 4、创建 CMBlockBuffer 实例。其中数据拷贝自 AudioBufferList,并将 CMBlockBuffer 实例关联到 CMSampleBuffer 实例。 + status = CMSampleBufferSetDataBufferFromAudioBufferList(sampleBuffer, kCFAllocatorDefault, kCFAllocatorDefault, 0, &buffers); + if (status != noErr) { + CFRelease(format); + return nil; + } + + CFRelease(format); + return sampleBuffer; +} + +#pragma mark - Capture CallBack +static OSStatus audioBufferCallBack(void *inRefCon, + AudioUnitRenderActionFlags *ioActionFlags, + const AudioTimeStamp *inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberFrames, + AudioBufferList *ioData) { + @autoreleasepool { + AiMouseAudioRecord *capture = (__bridge AiMouseAudioRecord *) inRefCon; + if (!capture) { + return -1; + } + + // 1、创建 AudioBufferList 空间,用来接收采集回来的数据。 + AudioBuffer buffer; + buffer.mData = NULL; + buffer.mDataByteSize = 0; + // 采集的时候设置了数据格式是 kAudioFormatLinearPCM,即声道交错格式,所以即使是双声道这里也设置 mNumberChannels 为 1。 + // 对于双声道的数据,会按照采样位深 16 bit 每组,一组接一组地进行两个声道数据的交错拼装。 + buffer.mNumberChannels = 1; + AudioBufferList buffers; + buffers.mNumberBuffers = 1; + buffers.mBuffers[0] = buffer; + + // 2、获取音频 PCM 数据,存储到 AudioBufferList 中。 + // 这里有几个问题要说明清楚: + // 1)每次回调会过来多少数据? + // 按照上面采集音频参数的设置:PCM 为声道交错格式、每帧的声道数为 2、采样位深为 16 bit。这样每帧的字节数是 4 字节(左右声道各 2 字节)。 + // 返回数据的帧数是 inNumberFrames。这样一次回调回来的数据字节数是多少就是:mBytesPerFrame(4) * inNumberFrames。 + // 2)这个数据回调的频率跟音频采样率有关系吗? + // 这个数据回调的频率与音频采样率(上面设置的 mSampleRate 44100)是没关系的。声道数、采样位深、采样率共同决定了设备单位时间里采样数据的大小,这些数据是会缓冲起来,然后一块一块的通过这个数据回调给我们,这个回调的频率是底层一块一块给我们数据的速度,跟采样率无关。 + // 3)这个数据回调的频率是多少? + // 这个数据回调的间隔是 [AVAudioSession sharedInstance].preferredIOBufferDuration,频率即该值的倒数。我们可以通过 [[AVAudioSession sharedInstance] setPreferredIOBufferDuration:1 error:nil] 设置这个值来控制回调频率。 + OSStatus status = AudioUnitRender(capture.audioCaptureInstance, + ioActionFlags, + inTimeStamp, + inBusNumber, + inNumberFrames, + &buffers); + + // 3、数据封装及回调。 + if (status == noErr) { + // 使用工具方法将数据封装为 CMSampleBuffer。 + CMSampleBufferRef sampleBuffer = [AiMouseAudioRecord sampleBufferFromAudioBufferList:buffers inTimeStamp:inTimeStamp inNumberFrames:inNumberFrames description:capture.audioFormat]; + // 回调数据。 + if (capture.sampleBufferOutputCallBack) { + capture.sampleBufferOutputCallBack(sampleBuffer); + } + if (sampleBuffer) { + CFRelease(sampleBuffer); + } + } + + return status; + } +} + +@end diff --git a/MouseSdk/AiMouseSdk/AiMouseConfig.h b/MouseSdk/AiMouseSdk/AiMouseConfig.h new file mode 100644 index 0000000..3e68f0a --- /dev/null +++ b/MouseSdk/AiMouseSdk/AiMouseConfig.h @@ -0,0 +1,45 @@ +// +// AiMouseConfig.h +// AiMouseSdk +// +// Created by incar on 2024/3/27. +// + +#import + +#ifdef DEBUG //调试 + +// 不带方法名 +#define NSLog(FORMAT, ...) {\ +NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];\ +[dateFormatter setDateStyle:NSDateFormatterMediumStyle];\ +[dateFormatter setTimeStyle:NSDateFormatterShortStyle];\ +[dateFormatter setDateFormat:@"HH:mm:ss:SSS"]; \ +NSString *str = [dateFormatter stringFromDate:[NSDate date]];\ +fprintf(stderr," %s ~ %s\n",[str UTF8String], [[NSString stringWithFormat:FORMAT, ##__VA_ARGS__] UTF8String]);\ +} + +//// 带方法名 +//#define NSLog(FORMAT, ...) {\ +//NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];\ +//[dateFormatter setDateStyle:NSDateFormatterMediumStyle];\ +//[dateFormatter setTimeStyle:NSDateFormatterShortStyle];\ +//[dateFormatter setDateFormat:@"HH:mm:ss:SSSSSS"]; \ +//NSString *str = [dateFormatter stringFromDate:[NSDate date]];\ +//fprintf(stderr," %s %s %d %s~ 打印 = %s\n",[str UTF8String],[[[NSString stringWithUTF8String:__FILE__] lastPathComponent] UTF8String], __LINE__,__func__, [[NSString stringWithFormat:FORMAT, ##__VA_ARGS__] UTF8String]);\ +//} + + +#else + +#define NSLog(FORMAT, ...) nil + +#endif + +NS_ASSUME_NONNULL_BEGIN + +@interface AiMouseConfig : NSObject + +@end + +NS_ASSUME_NONNULL_END diff --git a/MouseSdk/AiMouseSdk/AiMouseConfig.m b/MouseSdk/AiMouseSdk/AiMouseConfig.m new file mode 100644 index 0000000..13f55cf --- /dev/null +++ b/MouseSdk/AiMouseSdk/AiMouseConfig.m @@ -0,0 +1,12 @@ +// +// AiMouseConfig.m +// AiMouseSdk +// +// Created by incar on 2024/3/27. +// + +#import "AiMouseConfig.h" + +@implementation AiMouseConfig + +@end diff --git a/MouseSdk/AiMouseSdk/AiMouseRecordManager.h b/MouseSdk/AiMouseSdk/AiMouseRecordManager.h new file mode 100644 index 0000000..d09866e --- /dev/null +++ b/MouseSdk/AiMouseSdk/AiMouseRecordManager.h @@ -0,0 +1,18 @@ +// +// AiMouseRecordManager.h +// AVDemo +// +// Created by incar on 2024/3/27. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface AiMouseRecordManager : NSObject +- (void)start; + +- (void)stop; +@end + +NS_ASSUME_NONNULL_END diff --git a/MouseSdk/AiMouseSdk/AiMouseRecordManager.m b/MouseSdk/AiMouseSdk/AiMouseRecordManager.m new file mode 100644 index 0000000..bb55a74 --- /dev/null +++ b/MouseSdk/AiMouseSdk/AiMouseRecordManager.m @@ -0,0 +1,115 @@ +// +// AiMouseRecordManager.m +// AVDemo +// +// Created by incar on 2024/3/27. +// + +#import "AiMouseRecordManager.h" +#import +#import "AiMouseAudioRecord.h" + +@interface AiMouseRecordManager() +@property (nonatomic, strong) AiMouseAudioConfig *audioConfig; +@property (nonatomic, strong) AiMouseAudioRecord *audioCapture; +@property (nonatomic, strong) NSFileHandle *fileHandle; +@end + +@implementation AiMouseRecordManager +#pragma mark - Property +- (AiMouseAudioConfig *)audioConfig { + if (!_audioConfig) { + _audioConfig = [AiMouseAudioConfig defaultConfig]; + } + + return _audioConfig; +} + +- (AiMouseAudioRecord *)audioCapture { + if (!_audioCapture) { + __weak typeof(self) weakSelf = self; + _audioCapture = [[AiMouseAudioRecord alloc] initWithConfig:self.audioConfig]; + _audioCapture.errorCallBack = ^(NSError* error) { + NSLog(@"KFAudioCapture error: %zi %@", error.code, error.localizedDescription); + }; + // 音频采集数据回调。在这里将 PCM 数据写入文件。 + _audioCapture.sampleBufferOutputCallBack = ^(CMSampleBufferRef sampleBuffer) { + if (sampleBuffer) { + // 1、获取 CMBlockBuffer,这里面封装着 PCM 数据。 + CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer); + size_t lengthAtOffsetOutput, totalLengthOutput; + char *dataPointer; + + // 2、从 CMBlockBuffer 中获取 PCM 数据存储到文件中。 + CMBlockBufferGetDataPointer(blockBuffer, 0, &lengthAtOffsetOutput, &totalLengthOutput, &dataPointer); + [weakSelf.fileHandle writeData:[NSData dataWithBytes:dataPointer length:totalLengthOutput]]; + } + }; + } + + return _audioCapture; +} + +- (NSFileHandle *)fileHandle { + if (!_fileHandle) { + NSString *audioPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"test.pcm"]; + NSLog(@"PCM file path: %@", audioPath); + [[NSFileManager defaultManager] removeItemAtPath:audioPath error:nil]; + [[NSFileManager defaultManager] createFileAtPath:audioPath contents:nil attributes:nil]; + _fileHandle = [NSFileHandle fileHandleForWritingAtPath:audioPath]; + } + + return _fileHandle; +} +- (void)dealloc { + if (_fileHandle) { + [_fileHandle closeFile]; + } +} +-(instancetype)init{ + if (self = [super init]) { + [self setupAudioSession]; + } + return self; +} + +#pragma mark - Action +- (void)start { + [self.audioCapture startRunning]; +} + +- (void)stop { + [self.audioCapture stopRunning]; +} + +- (void)setupAudioSession { + NSError *error = nil; + +// // 1、获取音频会话实例。 +// AVAudioSession *session = [AVAudioSession sharedInstance]; +// +// // 2、设置分类和选项。 +// [session setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionMixWithOthers | AVAudioSessionCategoryOptionDefaultToSpeaker error:&error]; +// if (error) { +// NSLog(@"AVAudioSession setCategory error."); +// error = nil; +// return; +// } +// +// // 3、设置模式。 +// [session setMode:AVAudioSessionModeVideoRecording error:&error]; +// if (error) { +// NSLog(@"AVAudioSession setMode error."); +// error = nil; +// return; +// } +// +// // 4、激活会话。 +// [session setActive:YES error:&error]; +// if (error) { +// NSLog(@"AVAudioSession setActive error."); +// error = nil; +// return; +// } +} +@end diff --git a/MouseSdk/AiMouseSdk/AiMouseSdk.h b/MouseSdk/AiMouseSdk/AiMouseSdk.h index cf5694d..68989f5 100644 --- a/MouseSdk/AiMouseSdk/AiMouseSdk.h +++ b/MouseSdk/AiMouseSdk/AiMouseSdk.h @@ -17,3 +17,6 @@ #import #import + + + diff --git a/MouseSdk/AiMouseSdk/AiMouseTools.m b/MouseSdk/AiMouseSdk/AiMouseTools.m index 32618f4..33648a7 100644 --- a/MouseSdk/AiMouseSdk/AiMouseTools.m +++ b/MouseSdk/AiMouseSdk/AiMouseTools.m @@ -22,6 +22,9 @@ } + (NSDictionary *)stringToDictionary:(NSString *)jsonString{ NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding]; + if (jsonData == nil) { + return nil; + } NSError *error; NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&error]; return dict; @@ -112,3 +115,4 @@ return @""; } @end + diff --git a/MouseSdk/AiMouseSdk/AiMouseTools2.h b/MouseSdk/AiMouseSdk/AiMouseTools2.h new file mode 100644 index 0000000..99393e4 --- /dev/null +++ b/MouseSdk/AiMouseSdk/AiMouseTools2.h @@ -0,0 +1,17 @@ +// +// AiMouseTools2.h +// AiMouseSdk +// +// Created by incar on 2024/3/27. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface AiMouseTools2 : NSObject + ++ (NSString *)fixDeviceData:(NSString *)str; +@end + +NS_ASSUME_NONNULL_END diff --git a/MouseSdk/AiMouseSdk/AiMouseTools2.m b/MouseSdk/AiMouseSdk/AiMouseTools2.m new file mode 100644 index 0000000..1c37eaa --- /dev/null +++ b/MouseSdk/AiMouseSdk/AiMouseTools2.m @@ -0,0 +1,22 @@ +// +// AiMouseTools2.m +// AiMouseSdk +// +// Created by incar on 2024/3/27. +// + +#import "AiMouseTools2.h" +#import "AiMouseTools.h" + +@implementation AiMouseTools2 + ++ (NSString *)fixDeviceData:(NSString *)str{ + NSRange range = [str rangeOfString:@"}"]; + + if(range.location != NSNotFound){ + NSString *sub = [str substringToIndex:range.location + 1]; + return sub; + } + return str; +} +@end diff --git a/MouseSdk/AiMouseSdk/AiRecordManager.h b/MouseSdk/AiMouseSdk/AiRecordManager.h new file mode 100644 index 0000000..846505f --- /dev/null +++ b/MouseSdk/AiMouseSdk/AiRecordManager.h @@ -0,0 +1,22 @@ +// +// AiRecordManager.h +// AudioRecorder +// +// Created by incar on 2024/3/29. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface AiRecordManager : NSObject +@property (nonatomic,strong)void (^didRecordData)(NSData *data); + +- (void)checkPermissions; + +- (void)start; + +- (void)stop; +@end + +NS_ASSUME_NONNULL_END diff --git a/MouseSdk/AiMouseSdk/AiRecordManager.m b/MouseSdk/AiMouseSdk/AiRecordManager.m new file mode 100644 index 0000000..5a6b216 --- /dev/null +++ b/MouseSdk/AiMouseSdk/AiRecordManager.m @@ -0,0 +1,192 @@ +// +// AiRecordManager.m +// AudioRecorder +// +// Created by incar on 2024/3/29. +// + +#import "AiRecordManager.h" +#import + +@interface AiRecordManager() +@property (nonatomic,assign)BOOL isAllowRecord; +@property (nonatomic,strong)AVAudioEngine *audioEngine; +@property (nonatomic,strong)AVAudioFile *outputFile; + +@end + +@implementation AiRecordManager +//+(AiRecordManager *)shared{ +// static AiRecordManager *manager = nil; +// static dispatch_once_t oncePredicate; +// dispatch_once(&oncePredicate, ^{ +// manager = [[AiRecordManager alloc]init]; +// }); +// return manager; +//} + +-(instancetype)init{ + if (self = [super init ]) { + self.isAllowRecord = false; +// [self checkPermissions]; + } + return self; +} + +- (void)start{ + if (self.isAllowRecord == false) { + return; + } + // let audioSession = AVAudioSession.sharedInstance() + // do { + // try audioSession.setCategory(.playAndRecord, options: .defaultToSpeaker) + // try audioSession.setActive(true, options: .notifyOthersOnDeactivation) + // catch let error as NSError { + // print("ERROR:", error) + // } + + + self.audioEngine = [[AVAudioEngine alloc] init]; + AVAudioInputNode *input = self.audioEngine.inputNode; + AVAudioNodeBus bus = 0; + AVAudioFormat *inputFormat = [input inputFormatForBus:bus]; + // NSString *fullPath = [NSString stringWithFormat:@"%@1.pcm",NSTemporaryDirectory()]; + // NSURL *fileURL = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask].firstObject URLByAppendingPathComponent:@"record.caf"]; + // [self showLog:fileURL.absoluteString]; + // NSError *err0; + // self.outputFile = [[AVAudioFile alloc] initForWriting:fileURL settings:inputFormat.settings commonFormat:inputFormat.commonFormat interleaved:inputFormat.isInterleaved error:&err0]; + // if (err0) { + // [self showLog:[NSString stringWithFormat:@"设置写入路径 %@", err0.description]]; + // } + // else{ + // [self showLog:@"写入路径设置成功"]; + // } + // self.recordData = [[NSMutableData alloc] init]; + __weak typeof(self) ws = self; + [input installTapOnBus:bus bufferSize:32 format:inputFormat block:^(AVAudioPCMBuffer * _Nonnull buffer, AVAudioTime * _Nonnull when) { + NSData *data = [ws pcm_downsample:buffer]; + if (ws.didRecordData) { + ws.didRecordData(data); + } +// NSLog(@"%@",data); +// NSError *err0; + // [ws.outputFile writeFromBuffer:buffer error:&err0]; + // if (err0) { + // [self showLog:[NSString stringWithFormat:@"写入失败 %@", err0.description]]; + // } + // else{ + // [self showLog:@"写入成功"]; + // } + }]; + + NSError *err1; + [self.audioEngine prepare]; + [self.audioEngine startAndReturnError:&err1]; + if (err1) { + [self showLog:[NSString stringWithFormat:@"启动录音失败 %@", err1.description]]; + } + else{ + [self showLog:@"启动成功"]; + } +} + +- (void)stop{ + [self.audioEngine stop]; + // [self finishRecored:self.recordData]; +} + +- (void)checkPermissions{ + switch ([AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio]) { + case AVAuthorizationStatusAuthorized: + self.isAllowRecord = true; + [self showLog:@"已经授权"]; + break; + case AVAuthorizationStatusNotDetermined: + { + [self showLog:@"未授权"]; + [AVCaptureDevice requestAccessForMediaType:AVMediaTypeAudio completionHandler:^(BOOL granted) { + if (granted) { + self.isAllowRecord = true; + [self showLog:@"已经授权"]; + } + else{ + [self showLog:@"已拒绝"]; + } + }]; + } + break; + case AVAuthorizationStatusDenied: + [self showLog:@"已拒绝"]; + [self openSettings]; + break; + default: + [self showLog:@"已拒绝"]; + [self openSettings]; + break; + } +} + +- (void)openSettings{ + NSURL *url = [NSURL URLWithString:@""]; +} + +- (void)showLog:(NSString *)log{ + NSLog(@"%@",log); +} + +//将录音数据保存 +- (void)finishRecored:(NSData *)data{ + NSString *doc = NSTemporaryDirectory(); + NSString *fullPath = [NSString stringWithFormat:@"%@1.pcm",doc]; + NSError *err; + [data writeToFile:fullPath options:NSDataWritingAtomic error:&err]; + if (err) { + [self showLog:[NSString stringWithFormat:@"录音保存失败 %@",err.description]]; + } + else{ + [self showLog:[NSString stringWithFormat:@"录音保存成功 path = %@",fullPath]]; + } +} + +-(NSData*) pcm_downsample:(AVAudioPCMBuffer*)buffer { + //位深转换 float32 转 int16_t + NSData* pData = [[NSData alloc] initWithBytes:buffer.floatChannelData[0] length:buffer.frameLength * 4]; + Byte*dataByte = (Byte*)[pData bytes]; + NSUInteger srcSize = pData.length/4; + Float32 vf32[srcSize]; + memcpy(vf32, &dataByte[0], pData.length); + int16_t bArr[srcSize]; + int r; + for(size_t i =0; i < srcSize; ++i) { + float x = vf32[i]; + float c; + c = ((x < -1) ? -1: ((x >1) ?1: x)); + c = c +1; + r = (int)(c *32767.5f); + r = r -32768; + bArr[i] = (short)r; + } + //码率转换 44100 转 16000 + uint32_t last_pos = (uint32_t)(srcSize -1); + uint32_t dstSize = (srcSize/44100.0)*16000; + int16_t bArr16[dstSize]; + for(uint32_t idx =0; idx < dstSize; idx++) { + float index = ((float) idx *44100.0) / (16000.0); + uint32_t p1 = (uint32_t) index; + float coef = index - p1; + uint32_t p2 = (p1 == last_pos) ? last_pos : p1 +1; + bArr16[idx] = (int16_t) ((1.0f- coef) * bArr[p1] + coef * bArr[p2]); + } + NSMutableData *data1 = [[NSMutableData alloc] init]; + for(NSUInteger i =0; i < dstSize; i++) { + unsigned char valChar[2]; + valChar[0] =0xff& bArr16[i]; + valChar[1] = (0xff00& bArr16[i]) >>8; + [data1 appendBytes:valChar length:2]; + } + return data1; +} + + + +@end diff --git a/MouseSdk/MouseSdk.xcodeproj/project.pbxproj b/MouseSdk/MouseSdk.xcodeproj/project.pbxproj index 1bbbde2..d012860 100644 --- a/MouseSdk/MouseSdk.xcodeproj/project.pbxproj +++ b/MouseSdk/MouseSdk.xcodeproj/project.pbxproj @@ -7,6 +7,13 @@ objects = { /* Begin PBXBuildFile section */ + 182B54802BB6A0D7009F53B6 /* AiRecordManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 182B547E2BB6A0D7009F53B6 /* AiRecordManager.m */; }; + 182B54812BB6A0D7009F53B6 /* AiRecordManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 182B547F2BB6A0D7009F53B6 /* AiRecordManager.h */; }; + 1867E5A92BB2706F008FC5FB /* HidManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1867E5A82BB2706F008FC5FB /* HidManager.m */; }; + 1877F3E32BB4049600D3220A /* AiMouseConfig.h in Headers */ = {isa = PBXBuildFile; fileRef = 1877F3E12BB4049600D3220A /* AiMouseConfig.h */; }; + 1877F3E42BB4049600D3220A /* AiMouseConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = 1877F3E22BB4049600D3220A /* AiMouseConfig.m */; }; + 1877F3E82BB4090E00D3220A /* AiMouseTools2.h in Headers */ = {isa = PBXBuildFile; fileRef = 1877F3E62BB4090E00D3220A /* AiMouseTools2.h */; }; + 1877F3E92BB4090E00D3220A /* AiMouseTools2.m in Sources */ = {isa = PBXBuildFile; fileRef = 1877F3E72BB4090E00D3220A /* AiMouseTools2.m */; }; 18AE9CE02B99C72000743515 /* AiMouse.h in Headers */ = {isa = PBXBuildFile; fileRef = 18AE9CDE2B99C71F00743515 /* AiMouse.h */; settings = {ATTRIBUTES = (Public, ); }; }; 18AE9CE12B99C72000743515 /* AiMouse.m in Sources */ = {isa = PBXBuildFile; fileRef = 18AE9CDF2B99C71F00743515 /* AiMouse.m */; }; 18BF1DCF2B81AB7C00B8D90F /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 18BF1DCE2B81AB7C00B8D90F /* AppDelegate.m */; }; @@ -230,6 +237,14 @@ /* Begin PBXFileReference section */ 0311DF3AD67ED792549B96BD /* libPods-MouseSdk.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-MouseSdk.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 182B547E2BB6A0D7009F53B6 /* AiRecordManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AiRecordManager.m; sourceTree = ""; }; + 182B547F2BB6A0D7009F53B6 /* AiRecordManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AiRecordManager.h; sourceTree = ""; }; + 1867E5A72BB2706F008FC5FB /* HidManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HidManager.h; sourceTree = ""; }; + 1867E5A82BB2706F008FC5FB /* HidManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HidManager.m; sourceTree = ""; }; + 1877F3E12BB4049600D3220A /* AiMouseConfig.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AiMouseConfig.h; sourceTree = ""; }; + 1877F3E22BB4049600D3220A /* AiMouseConfig.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AiMouseConfig.m; sourceTree = ""; }; + 1877F3E62BB4090E00D3220A /* AiMouseTools2.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AiMouseTools2.h; sourceTree = ""; }; + 1877F3E72BB4090E00D3220A /* AiMouseTools2.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AiMouseTools2.m; sourceTree = ""; }; 18AE9CDE2B99C71F00743515 /* AiMouse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AiMouse.h; sourceTree = ""; }; 18AE9CDF2B99C71F00743515 /* AiMouse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AiMouse.m; sourceTree = ""; }; 18BF1DCA2B81AB7C00B8D90F /* MouseSdk.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MouseSdk.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -495,6 +510,8 @@ children = ( 18BF1E202B83066200B8D90F /* BleManager.h */, 18BF1E212B83066200B8D90F /* BleManager.m */, + 1867E5A72BB2706F008FC5FB /* HidManager.h */, + 1867E5A82BB2706F008FC5FB /* HidManager.m */, 18BF1DCD2B81AB7C00B8D90F /* AppDelegate.h */, 18BF1DCE2B81AB7C00B8D90F /* AppDelegate.m */, 18BF1DD02B81AB7C00B8D90F /* ViewController.h */, @@ -538,20 +555,26 @@ 18BF1E892B8362D300B8D90F /* AiMouseSdk.h */, 18AE9CDE2B99C71F00743515 /* AiMouse.h */, 18AE9CDF2B99C71F00743515 /* AiMouse.m */, + 1877F3E12BB4049600D3220A /* AiMouseConfig.h */, + 1877F3E22BB4049600D3220A /* AiMouseConfig.m */, 18BF8F4C2BA9A7A4008F6933 /* AiMouseDeviceInfo.h */, 18BF8F4D2BA9A7A4008F6933 /* AiMouseDeviceInfo.m */, 18F328DC2B9950E000CABA2E /* AiFrameManager.h */, 18F328DD2B9950E000CABA2E /* AiFrameManager.m */, 18F323932B8F525C00CABA2E /* AiMouseTools.h */, 18F323942B8F525C00CABA2E /* AiMouseTools.m */, - 18F3238C2B8F4F7300CABA2E /* AiAudioManager.h */, - 18F3238D2B8F4F7300CABA2E /* AiAudioManager.m */, + 1877F3E62BB4090E00D3220A /* AiMouseTools2.h */, + 1877F3E72BB4090E00D3220A /* AiMouseTools2.m */, 18F323822B8F330100CABA2E /* AiAuthManager.h */, 18F323832B8F330100CABA2E /* AiAuthManager.m */, 18F323A62B9095D300CABA2E /* AiAudioConverter.h */, 18F323A72B9095D300CABA2E /* AiAudioConverter.m */, 18F323C22B93352000CABA2E /* AiAudioDecoder.h */, 18F323C32B93352000CABA2E /* AiAudioDecoder.m */, + 18F3238C2B8F4F7300CABA2E /* AiAudioManager.h */, + 18F3238D2B8F4F7300CABA2E /* AiAudioManager.m */, + 182B547F2BB6A0D7009F53B6 /* AiRecordManager.h */, + 182B547E2BB6A0D7009F53B6 /* AiRecordManager.m */, ); path = AiMouseSdk; sourceTree = ""; @@ -814,6 +837,7 @@ 18CB08B32BA40282005F5854 /* lfg.h in Headers */, 18CB09122BA40283005F5854 /* ac3_parser.h in Headers */, 18CB08ED2BA40283005F5854 /* pixfmt.h in Headers */, + 182B54812BB6A0D7009F53B6 /* AiRecordManager.h in Headers */, 18F323A82B9095D300CABA2E /* AiAudioConverter.h in Headers */, 18CB08A72BA40282005F5854 /* hwcontext_mediacodec.h in Headers */, 18CB08AA2BA40282005F5854 /* murmur3.h in Headers */, @@ -835,6 +859,7 @@ 18CB08A32BA40282005F5854 /* error.h in Headers */, 18CB08DC2BA40283005F5854 /* rc4.h in Headers */, 18F323C42B93352000CABA2E /* AiAudioDecoder.h in Headers */, + 1877F3E32BB4049600D3220A /* AiMouseConfig.h in Headers */, 18CB08A02BA40282005F5854 /* time.h in Headers */, 18CB08B62BA40282005F5854 /* xtea.h in Headers */, 18CB08C02BA40283005F5854 /* hwcontext_opencl.h in Headers */, @@ -857,6 +882,7 @@ 18CB08F02BA40283005F5854 /* downmix_info.h in Headers */, 18CB08C12BA40283005F5854 /* audio_fifo.h in Headers */, 18CB08CF2BA40283005F5854 /* pixelutils.h in Headers */, + 1877F3E82BB4090E00D3220A /* AiMouseTools2.h in Headers */, 18CB09102BA40283005F5854 /* dirac.h in Headers */, 18CB08F12BA40283005F5854 /* avassert.h in Headers */, 18CB08E92BA40283005F5854 /* ripemd.h in Headers */, @@ -1044,6 +1070,7 @@ }; 18BF1E862B8362D300B8D90F = { CreatedOnToolsVersion = 15.0.1; + LastSwiftMigration = 1500; }; }; }; @@ -1154,6 +1181,7 @@ 18BF1E762B832D2E00B8D90F /* ScanVC.m in Sources */, 18BF1E222B83066200B8D90F /* BleManager.m in Sources */, 18BF1DCF2B81AB7C00B8D90F /* AppDelegate.m in Sources */, + 1867E5A92BB2706F008FC5FB /* HidManager.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1179,8 +1207,11 @@ buildActionMask = 2147483647; files = ( 18F3238F2B8F4F7300CABA2E /* AiAudioManager.m in Sources */, + 1877F3E42BB4049600D3220A /* AiMouseConfig.m in Sources */, + 1877F3E92BB4090E00D3220A /* AiMouseTools2.m in Sources */, 18F323C52B93352000CABA2E /* AiAudioDecoder.m in Sources */, 18F323A92B9095D300CABA2E /* AiAudioConverter.m in Sources */, + 182B54802BB6A0D7009F53B6 /* AiRecordManager.m in Sources */, 18F328DF2B9950E000CABA2E /* AiFrameManager.m in Sources */, 18F323962B8F525C00CABA2E /* AiMouseTools.m in Sources */, 18BF8F4F2BA9A7A4008F6933 /* AiMouseDeviceInfo.m in Sources */, @@ -1340,6 +1371,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 811936622867478806C1A8BB /* Pods-MouseSdk.debug.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ENABLE_MODULES = YES; @@ -1352,9 +1384,10 @@ "$(PROJECT_DIR)/framework", ); GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_NSBluetoothAlwaysUsageDescription = "要打开蓝牙功能哦,才能继续使用APP"; + INFOPLIST_KEY_NSBluetoothAlwaysUsageDescription = "要打开蓝牙才能继续使用APP哦"; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INFOPLIST_KEY_NSMainStoryboardFile = Main; + INFOPLIST_KEY_NSMicrophoneUsageDescription = "要打开麦克风才能继续使用APP哦"; INFOPLIST_KEY_NSPrincipalClass = NSApplication; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -1375,6 +1408,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 51DE1118FCBA78EBEE6FE2CD /* Pods-MouseSdk.release.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ENABLE_MODULES = YES; @@ -1387,9 +1421,10 @@ "$(PROJECT_DIR)/framework", ); GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_NSBluetoothAlwaysUsageDescription = "要打开蓝牙功能哦,才能继续使用APP"; + INFOPLIST_KEY_NSBluetoothAlwaysUsageDescription = "要打开蓝牙才能继续使用APP哦"; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INFOPLIST_KEY_NSMainStoryboardFile = Main; + INFOPLIST_KEY_NSMicrophoneUsageDescription = "要打开麦克风才能继续使用APP哦"; INFOPLIST_KEY_NSPrincipalClass = NSApplication; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -1468,6 +1503,7 @@ 18BF1E902B8362D300B8D90F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; @@ -1496,6 +1532,8 @@ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -1504,6 +1542,7 @@ 18BF1E912B8362D300B8D90F /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; @@ -1532,6 +1571,7 @@ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; diff --git a/MouseSdk/MouseSdk.xcworkspace/xcuserdata/apple.xcuserdatad/UserInterfaceState.xcuserstate b/MouseSdk/MouseSdk.xcworkspace/xcuserdata/apple.xcuserdatad/UserInterfaceState.xcuserstate index 89650fd..80d43fd 100644 Binary files a/MouseSdk/MouseSdk.xcworkspace/xcuserdata/apple.xcuserdatad/UserInterfaceState.xcuserstate and b/MouseSdk/MouseSdk.xcworkspace/xcuserdata/apple.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/MouseSdk/MouseSdk.xcworkspace/xcuserdata/apple.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/MouseSdk/MouseSdk.xcworkspace/xcuserdata/apple.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist index 72091c3..6486004 100644 --- a/MouseSdk/MouseSdk.xcworkspace/xcuserdata/apple.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ b/MouseSdk/MouseSdk.xcworkspace/xcuserdata/apple.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -3,4 +3,82 @@ uuid = "96107861-D4C9-42B2-9605-61F7DD48CC72" type = "0" version = "2.0"> + + + + + + + + + + + + + + + + + + + + diff --git a/MouseSdk/MouseSdk/BleManager.m b/MouseSdk/MouseSdk/BleManager.m index 215ac45..9d08625 100644 --- a/MouseSdk/MouseSdk/BleManager.m +++ b/MouseSdk/MouseSdk/BleManager.m @@ -41,7 +41,7 @@ self = [super init]; if (self) { self.centralManager = [[CBCentralManager alloc]initWithDelegate:self queue:nil]; - [AiMouse shared].delegate = self; + } return self; } @@ -112,6 +112,10 @@ //鉴权特征 self.authCha = characteristic; [peripheral setNotifyValue:YES forCharacteristic:characteristic]; + //切换代理 + [AiMouse shared].delegate = self; + //切换当前连接类型 + [[AiMouse shared] switchConnect:AiMouseConnectType_bluetooth]; //找到鉴权特征后,一定要调用鉴权接口开始鉴权。只有鉴权通过了,sdk相关功能才可以使用 [[AiMouse shared] startAuthentication:^(AiMouseAuthState state) { [self showLog:[NSString stringWithFormat:@"鉴权结果 = %@",@(state)]]; @@ -168,41 +172,25 @@ /// 鼠标按键指令回调 /// - Parameter data: 数据。 -/// M 键 短按0x42;长按按下(开始录音)0x25, 长按抬起(结束录音) 0x26; -/// AI键 短按0x43 ,长按按下(开始录音)0x27, 长按抬起(结束录音) 0x28; -/// 语音键 按下(开始录音) 0x31,弹起(结束录音)0x32; +/// 前 第一次单击31(开始录音), 第二次单击32(结束录音) , 按下21(开始录音)抬起22(结束录音) +/// 中 单击42, 按下25(开始录音)抬起26(结束录音) +/// 后 单击43,按下27(开始录音)抬起28(结束录音) /// 其他未定义 - (void)aiMouseSendCmdData:(NSData *)data{ uint8 cmd; [data getBytes:&cmd length:1]; - if(cmd == 0x25){ - [self showLog:@"M键开始录音"]; - self.recordData = [[NSMutableData alloc] init]; - } - else if(cmd == 0x26){ - [self showLog:@"M键结束录音"]; - [self finishRecored:self.recordData]; - } - else if(cmd == 0x27){ - [self showLog:@"AI键开始录音"]; - self.recordData = [[NSMutableData alloc] init]; - } - else if(cmd == 0x28){ - [self showLog:@"AI键结束录音"]; - [self finishRecored:self.recordData]; - } - else if(cmd == 0x31){ - [self showLog:@"语音键开始录音"]; + [self showLog:[NSString stringWithFormat:@"收到指令 = %X",cmd]]; + + if(cmd == 0x31 || cmd == 0x21 || cmd == 0x25 ||cmd == 0x27){ + [self showLog:@"开始录音"]; self.recordData = [[NSMutableData alloc] init]; } - else if(cmd == 0x32){ - [self showLog:@"语音键结束录音"]; + else if(cmd == 0x32 || cmd == 0x22 || cmd == 0x26 ||cmd == 0x28){ + [self showLog:@"结束录音"]; [self finishRecored:self.recordData]; } - else{ - [self showLog:@"未定义指令!!!!!!!!"]; - } } + /// 录音回调 /// - Parameter data: pcm格式语音数据 - (void)aiMouseSendRecordData:(NSData *)data{ diff --git a/MouseSdk/MouseSdk/HidManager.h b/MouseSdk/MouseSdk/HidManager.h new file mode 100644 index 0000000..826614b --- /dev/null +++ b/MouseSdk/MouseSdk/HidManager.h @@ -0,0 +1,23 @@ +// +// HidManager.h +// MouseSdk +// +// Created by incar on 2024/3/26. +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface HidManager : NSObject +@property (nonatomic,strong)void (^connectStateDidChange)(BOOL isConnnect);/// 设备是否连上 +@property (nonatomic,strong)void (^authCallBack)(BOOL isSuccess);/// 鉴权回调 +///单利 ++(HidManager *)shared; + +/// 获取设备信息 +- (void)getDeviceInfo:(void (^)(AiMouseDeviceInfo *deviceInfo,NSError *error))completation; +@end + +NS_ASSUME_NONNULL_END diff --git a/MouseSdk/MouseSdk/HidManager.m b/MouseSdk/MouseSdk/HidManager.m new file mode 100644 index 0000000..c15857a --- /dev/null +++ b/MouseSdk/MouseSdk/HidManager.m @@ -0,0 +1,231 @@ +// +// HidManager.m +// MouseSdk +// +// Created by incar on 2024/3/26. +// + +#import "HidManager.h" +#include +#include + +#import +#import "Config.h" + +@interface HidManager () + +//IOHIDDeviceRef deviceRef; +@property(nonatomic, assign) IOHIDManagerRef hidManager;///hid管理 +@property(nonatomic, assign) IOHIDDeviceRef authCha;//鉴权特征 +@property(nonatomic, assign) IOHIDDeviceRef aiCha;//ai特征 +@property(nonatomic, assign) IOHIDDeviceRef deviceInfoCha;//设备信息特征 +@property (nonatomic,strong) NSMutableData *recordData;//录音的数据 +@end + +@implementation HidManager ++(HidManager *)shared{ + static HidManager *manager = nil; + static dispatch_once_t oncePredicate; + dispatch_once(&oncePredicate, ^{ + manager = [[HidManager alloc]init]; + }); + return manager; +} + +- (instancetype)init{ + self = [super init]; + if (self) { + [self initHid]; + } + return self; +} + +- (void)initHid{ + self.hidManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); + IOHIDManagerScheduleWithRunLoop(self.hidManager, CFRunLoopGetMain(), kCFRunLoopDefaultMode); + IOReturn ret = IOHIDManagerOpen(self.hidManager, 0L); + if (ret != kIOReturnSuccess) { + NSLog(@"打开设备失败!"); + }else{ + NSLog(@"打开设备成功!"); + } + + const long vendorID = 0x248a; + const long productID = 0xca08; + NSMutableDictionary* dict= [NSMutableDictionary dictionary]; + [dict setValue:[NSNumber numberWithLong:productID] forKey:[NSString stringWithCString:kIOHIDProductIDKey encoding:NSUTF8StringEncoding]]; + [dict setValue:[NSNumber numberWithLong:vendorID] forKey:[NSString stringWithCString:kIOHIDVendorIDKey encoding:NSUTF8StringEncoding]]; + IOHIDManagerSetDeviceMatching(self.hidManager, (__bridge CFMutableDictionaryRef)dict); + + IOHIDManagerRegisterDeviceMatchingCallback(self.hidManager, &hidAppearDevice, NULL); + IOHIDManagerRegisterDeviceRemovalCallback(self.hidManager, &hidDisAppearDevice, NULL); +} + +#pragma mark - Hid CallBack +static void hidAppearDevice(void *inContext,IOReturn inResult,void *inSender,IOHIDDeviceRef inIOHIDDeviceRef) { + uint32_t vid = IOHIDDevGetUInt32Property(inIOHIDDeviceRef, CFSTR(kIOHIDVendorIDKey)); + uint32_t pid = IOHIDDevGetUInt32Property(inIOHIDDeviceRef, CFSTR(kIOHIDProductIDKey)); + uint32_t PrimaryUsagePage = IOHIDDevGetUInt32Property(inIOHIDDeviceRef, CFSTR(kIOHIDPrimaryUsagePageKey)); + uint32_t PrimaryUsage = IOHIDDevGetUInt32Property(inIOHIDDeviceRef, CFSTR(kIOHIDPrimaryUsageKey)); + NSLog(@"vid = %u, pid = %u, PrimaryUsagePage = %u, PrimaryUsage = %u,",vid,pid,PrimaryUsagePage,PrimaryUsage); + NSLog(@"发现设备 %@\n ",inIOHIDDeviceRef); + + // PrimaryUsagePage=1 PrimaryUsage=0,设备信息 + // PrimaryUsagePage=1 PrimaryUsage=6,鉴权 + // PrimaryUsagePage=65283 PrimaryUsage=0,ai + if(PrimaryUsagePage == 1 && PrimaryUsage == 0){ + [HidManager shared].deviceInfoCha = inIOHIDDeviceRef; + } + else if(PrimaryUsagePage == 1 && PrimaryUsage == 6){ + [HidManager shared].authCha = inIOHIDDeviceRef; + [[HidManager shared] startAuthentication]; + } + else if(PrimaryUsagePage == 65283 && PrimaryUsage == 0){ + [HidManager shared].aiCha = inIOHIDDeviceRef; + } + + unsigned char InputBuffer[128]; + IOHIDDeviceRegisterInputReportCallback(inIOHIDDeviceRef, InputBuffer, sizeof(InputBuffer), hidReceiveData, NULL); + // CFRelease(ref); +} + +static void hidDisAppearDevice(void *inContext,IOReturn inResult,void *inSender,IOHIDDeviceRef inIOHIDDeviceRef) { + NSLog(@"拔出设备 %@ ",inIOHIDDeviceRef); + uint32_t PrimaryUsagePage = IOHIDDevGetUInt32Property(inSender, CFSTR(kIOHIDPrimaryUsagePageKey)); + uint32_t PrimaryUsage = IOHIDDevGetUInt32Property(inSender, CFSTR(kIOHIDPrimaryUsageKey)); + + if(PrimaryUsagePage == 1 && PrimaryUsage == 0){ + [HidManager shared].connectStateDidChange(false); + } +} + +static void hidReceiveData(void* context, IOReturn result, void* sender, IOHIDReportType type, uint32_t reportID, uint8_t *report,CFIndex reportLength){ + NSInteger len = get_data_len(report, reportLength); + NSData *data = [[NSData alloc] initWithBytes:report length:len]; + [[HidManager shared] showLog:[NSString stringWithFormat:@"device = %@ \n [receive] = %@",sender,[AiMouseTools dataToHexString:data]]]; + + uint32_t PrimaryUsagePage = IOHIDDevGetUInt32Property(sender, CFSTR(kIOHIDPrimaryUsagePageKey)); + uint32_t PrimaryUsage = IOHIDDevGetUInt32Property(sender, CFSTR(kIOHIDPrimaryUsageKey)); + + if(PrimaryUsagePage == 1 && PrimaryUsage == 0){ + [[HidManager shared] showLog:@"device数据"]; + [[AiMouse shared] sendDeviceDataToSdk:data]; + } + else if(PrimaryUsagePage == 1 && PrimaryUsage == 6){ + [[HidManager shared] showLog:@"鉴权数据"]; + [[AiMouse shared] sendAuthDataToSdk:data]; + } + else if(PrimaryUsagePage == 65283 && PrimaryUsage == 0){ + [[HidManager shared] showLog:@"AI数据"]; + [[AiMouse shared] sendAiDataToSdk:data]; + } +} + +static uint32_t IOHIDDevGetUInt32Property(IOHIDDeviceRef inIOHIDDeviceRef, CFStringRef inKey) { + uint32_t outValue = 0; + if (inIOHIDDeviceRef != NULL) { + CFTypeRef tCFTypeRef = IOHIDDeviceGetProperty(inIOHIDDeviceRef, inKey); + if (tCFTypeRef != NULL && (CFNumberGetTypeID() == CFGetTypeID(tCFTypeRef)) ) { + Boolean result = CFNumberGetValue((CFNumberRef) tCFTypeRef, kCFNumberSInt32Type, &outValue); + if (!result) { + NSLog(@"get %@ failed.", inKey); + } + } + } + + return outValue; +} +#pragma mark - AiDelegate +/// 鉴权数据回调,需要将此数据转发给蓝牙设备 +/// - Parameter data: 数据 +- (void)aiMouseSendAuthData:(NSData *)data{ + [self sendData:data toDevice:self.authCha]; +} + + +/// 设备信息数据回调,需要将此数据通过设备信息特征转发给蓝牙设备 +/// - Parameter data: 数据 +-(void)aiMouseSendDeviceInfoData:(NSData *)data{ + [self sendData:data toDevice:self.deviceInfoCha]; +} + +/// 鼠标按键指令回调 +/// - Parameter data: 数据。 +/// 前 第一次单击31(开始录音), 第二次单击32(结束录音) , 按下21(开始录音)抬起22(结束录音) +/// 中 单击42, 按下25(开始录音)抬起26(结束录音) +/// 后 单击43,按下27(开始录音)抬起28(结束录音) +/// 其他未定义 +- (void)aiMouseSendCmdData:(NSData *)data{ + uint8 cmd; + [data getBytes:&cmd length:1]; + [self showLog:[NSString stringWithFormat:@"收到指令 = %X",cmd]]; + + if(cmd == 0x31 || cmd == 0x21 || cmd == 0x25 ||cmd == 0x27){ + [self showLog:@"开始录音"]; + self.recordData = [[NSMutableData alloc] init]; + } + else if(cmd == 0x32 || cmd == 0x22 || cmd == 0x26 ||cmd == 0x28){ + [self showLog:@"结束录音"]; + [self finishRecored:self.recordData]; + } +} +/// 录音回调 +/// - Parameter data: pcm格式语音数据 +- (void)aiMouseSendRecordData:(NSData *)data{ + [self.recordData appendData:data]; +} + +#pragma mark - public +-(void)startAuthentication{ + [HidManager shared].connectStateDidChange(true); + [AiMouse shared].delegate = self; + //切换当前连接类型 + [[AiMouse shared] switchConnect:AiMouseConnectType_usb]; + //找到鉴权特征后,一定要调用鉴权接口开始鉴权。只有鉴权通过了,sdk相关功能才可以使用 + [self showLog:@"开始鉴权"]; + [[AiMouse shared] startAuthentication:^(AiMouseAuthState state) { + [self showLog:[NSString stringWithFormat:@"鉴权结果 = %@",@(state)]]; + if(self.authCallBack){ + self.authCallBack(state == AiMouseAuthState_Success); + } + }]; +} +/// 获取设备信息 +- (void)getDeviceInfo:(void (^)(AiMouseDeviceInfo * _Nonnull, NSError * _Nonnull))completation{ + [[AiMouse shared] getDeviceInfo:completation]; +} + +-(void)sendData:(NSData *)data toDevice:(IOHIDDeviceRef)device{ + [self showLog:[NSString stringWithFormat:@"开始发送 %@\n [send] %@",device,[AiMouseTools dataToHexString:data]]]; + IOReturn ret = IOHIDDeviceSetReport(device, kIOHIDReportTypeOutput, 0, data.bytes, data.length); + if (ret != kIOReturnSuccess) { + [self showLog:@"发送失败!"]; + }else{ + [self showLog:@"发送成功!"]; + } +} +//将录音数据保存 +- (void)finishRecored:(NSData *)data{ + NSString *doc = NSTemporaryDirectory(); + NSString *fullPath = [NSString stringWithFormat:@"%@1.pcm",doc]; + NSError *err; + [data writeToFile:fullPath options:NSDataWritingAtomic error:&err]; + if (err) { + [self showLog:[NSString stringWithFormat:@"录音保存失败 %@",err.description]]; + } + else{ + [self showLog:[NSString stringWithFormat:@"录音保存成功 path = %@",fullPath]]; + } +} +- (void)showLog:(NSString *)log{ + NSLog(@"%@",log); +} + +static inline int get_data_len(uint8_t *buf, u_long len) { + if (len <= 0 ) return 0; + + int url_len = 0; + while (buf[url_len] != 0x00) url_len++; + return url_len; +} +@end diff --git a/MouseSdk/MouseSdk/MouseSdk.entitlements b/MouseSdk/MouseSdk/MouseSdk.entitlements index 7ff8a92..6fe1208 100644 --- a/MouseSdk/MouseSdk/MouseSdk.entitlements +++ b/MouseSdk/MouseSdk/MouseSdk.entitlements @@ -4,8 +4,12 @@ com.apple.security.app-sandbox + com.apple.security.device.audio-input + com.apple.security.device.bluetooth + com.apple.security.device.usb + com.apple.security.files.user-selected.read-only com.apple.security.network.client diff --git a/MouseSdk/MouseSdk/ViewController.m b/MouseSdk/MouseSdk/ViewController.m index 63bd155..6bf7f72 100644 --- a/MouseSdk/MouseSdk/ViewController.m +++ b/MouseSdk/MouseSdk/ViewController.m @@ -8,12 +8,13 @@ #import "ViewController.h" #import "ScanVC.h" #import "BleManager.h" - +#import "HidManager.h" @interface ViewController () - -@property(nonatomic,strong)NSTextField *connectLabel; -@property(nonatomic,strong)NSTextField *autoLabel; +@property(nonatomic,strong)NSTextField *bleLabel;///蓝牙连接状态 +@property(nonatomic,strong)NSTextField *usbLabel;///usb连接状态 +@property(nonatomic,strong)NSTextField *authLabel;///鉴权状态 +@property(nonatomic,strong)NSTextField *deviceInfoLabel;///设备信息 @end @implementation ViewController @@ -25,6 +26,7 @@ [self setupUI]; [self setupNotification]; [self setupBleManger]; + [self setupHidManger]; } - (void)setRepresentedObject:(id)representedObject { @@ -37,42 +39,71 @@ self.title = @"AI SDK"; { - NSButton *btn = [[NSButton alloc]initWithFrame:NSMakeRect(100, self.view.bounds.size.height - 100, 100, 45)]; + NSButton *btn = [[NSButton alloc]initWithFrame:NSMakeRect(50, self.view.bounds.size.height - 100, 100, 45)]; btn.bordered = true; - btn.title = @"扫描"; + btn.title = @"蓝牙扫描"; [btn setTarget:self]; [btn setAction:@selector(scanAction)]; btn.state = 1; [self.view addSubview:btn]; } { - NSTextField *label = [[NSTextField alloc] initWithFrame:CGRectMake(230, self.view.bounds.size.height - 120, 200, 35)]; + NSButton *btn = [[NSButton alloc]initWithFrame:NSMakeRect(50, self.view.bounds.size.height - 200, 100, 45)]; + btn.bordered = true; + btn.title = @"蓝牙获取设备信息"; + [btn setTarget:self]; + [btn setAction:@selector(bleGetDeviceInfoAction)]; + btn.state = 1; + [self.view addSubview:btn]; + } + { + NSButton *btn = [[NSButton alloc]initWithFrame:NSMakeRect(50, self.view.bounds.size.height - 260, 100, 45)]; + btn.bordered = true; + btn.title = @"2.4g获取设备信息"; + [btn setTarget:self]; + [btn setAction:@selector(hidGetDeviceInfoAction)]; + btn.state = 1; + [self.view addSubview:btn]; + } + { + NSTextField *label = [[NSTextField alloc] initWithFrame:CGRectMake(180, self.view.bounds.size.height - 80, 300, 35)]; label.editable = NO; label.bordered = NO; - label.stringValue = @"连接状态:未连接"; + label.stringValue = @"连蓝牙接状态:未连接"; label.backgroundColor = NSColor.clearColor; - self.connectLabel = label; + self.bleLabel = label; [self.view addSubview:label]; } { - NSTextField *label = [[NSTextField alloc] initWithFrame:CGRectMake(230, self.view.bounds.size.height - 200, 200, 35)]; + NSTextField *label = [[NSTextField alloc] initWithFrame:CGRectMake(180, self.view.bounds.size.height - 120, 300, 35)]; + label.editable = NO; + label.bordered = NO; + label.stringValue = @"2.4G连接状态:未连接"; + label.backgroundColor = NSColor.clearColor; + self.usbLabel = label; + [self.view addSubview:label]; + } + { + NSTextField *label = [[NSTextField alloc] initWithFrame:CGRectMake(180, self.view.bounds.size.height - 160, 300, 35)]; label.editable = NO; label.bordered = NO; label.stringValue = @"鉴权状态:unknow"; label.backgroundColor = NSColor.clearColor; - self.autoLabel = label; + self.authLabel = label; [self.view addSubview:label]; } { - NSButton *btn = [[NSButton alloc]initWithFrame:NSMakeRect(100, self.view.bounds.size.height - 200, 100, 45)]; - btn.bordered = true; - btn.title = @"获取设备信息"; - [btn setTarget:self]; - [btn setAction:@selector(getDeviceInfoAction)]; - btn.state = 1; - [self.view addSubview:btn]; + NSTextField *label = [[NSTextField alloc] initWithFrame:CGRectMake(180, self.view.bounds.size.height - 260, 300, 100)]; + label.editable = NO; + label.bordered = NO; + label.stringValue = @""; + label.backgroundColor = NSColor.clearColor; + self.deviceInfoLabel = label; + [self.view addSubview:label]; } } + +#pragma mark - ble - (void)setupBleManger{ BleManager *manager = [BleManager shared]; __weak typeof(self) ws = self; @@ -80,28 +111,65 @@ [ws showAuthResult:isSuccess]; }; } - - (void)scanAction{ ScanVC *vc = [[ScanVC alloc] initWithNibName:nil bundle:nil]; [self presentViewControllerAsModalWindow:vc]; } -- (void)getDeviceInfoAction{ +- (void)bleGetDeviceInfoAction{ [[BleManager shared] getDeviceInfo:^(AiMouseDeviceInfo *deviceInfo, NSError *error) { if (deviceInfo) { - NSLog(@"battery_power = %@,CPI = %@,CPI_list = %@,version = %@,brand = %@,sn = %@,mac = %@,",deviceInfo.battery_power,deviceInfo.CPI,deviceInfo.CPI_list,deviceInfo.version,deviceInfo.brand,deviceInfo.sn,deviceInfo.mac); + NSString *info = [NSString stringWithFormat:@"battery_power = %@,CPI = %@,CPI_list = %@,version = %@,brand = %@,sn = %@,mac = %@",deviceInfo.battery_power,deviceInfo.CPI,deviceInfo.CPI_list,deviceInfo.version,deviceInfo.brand,deviceInfo.sn,deviceInfo.mac]; + NSLog(@"%@",info); + + dispatch_async(dispatch_get_main_queue(), ^{ + self.deviceInfoLabel.stringValue = info; + }); } else{ - NSLog(@"get deivce info error == %@",error.description); + NSString *info = [NSString stringWithFormat:@"error == %@",error.description]; + NSLog(@"%@",info); + dispatch_async(dispatch_get_main_queue(), ^{ + self.deviceInfoLabel.stringValue = info; + }); } }]; } - - (void)showAuthResult:(BOOL)isSuccess{ dispatch_async(dispatch_get_main_queue(), ^{ - self.autoLabel.stringValue = isSuccess?@"鉴权状态:鉴权成功":@"鉴权状态:鉴权失败"; + self.authLabel.stringValue = isSuccess?@"鉴权状态:鉴权成功":@"鉴权状态:鉴权失败"; }); } +#pragma mark - hid +- (void)setupHidManger{ + HidManager *manager = [HidManager shared]; + __weak typeof(self) ws = self; + manager.authCallBack = ^(BOOL isSuccess) { + [ws showAuthResult:isSuccess]; + }; + manager.connectStateDidChange = ^(BOOL isConnnect) { + ws.usbLabel.stringValue = isConnnect? @"2.4g连接状态:已连接":@"2.4g连接状态:未连接"; + }; +} +- (void)hidGetDeviceInfoAction{ + [[HidManager shared] getDeviceInfo:^(AiMouseDeviceInfo *deviceInfo, NSError *error) { + if (deviceInfo) { + NSString *info = [NSString stringWithFormat:@"battery_power = %@,CPI = %@,CPI_list = %@,version = %@,brand = %@,sn = %@,mac = %@",deviceInfo.battery_power,deviceInfo.CPI,deviceInfo.CPI_list,deviceInfo.version,deviceInfo.brand,deviceInfo.sn,deviceInfo.mac]; + NSLog(@"%@",info); + + dispatch_async(dispatch_get_main_queue(), ^{ + self.deviceInfoLabel.stringValue = info; + }); + } + else{ + NSString *info = [NSString stringWithFormat:@"error == %@",error.description]; + NSLog(@"%@",info); + dispatch_async(dispatch_get_main_queue(), ^{ + self.deviceInfoLabel.stringValue = info; + }); + } + }]; +} #pragma mark - notication - (void)setupNotification{ @@ -110,9 +178,11 @@ } - (void)bleDidConnect:(NSNotification *)notification{ - self.connectLabel.stringValue = @"连接状态:已连接"; + self.bleLabel.stringValue = @"蓝牙连接状态:已连接"; } + - (void)bleDidDisconnect:(NSNotification *)notification{ - self.connectLabel.stringValue = @"连接状态:未连接"; + self.bleLabel.stringValue = @"蓝牙连接状态:未连接"; } + @end diff --git a/MouseSdk_Mac_V1.0.1/MouseSdkDemo/MouseSdkDemo.xcodeproj/project.pbxproj b/MouseSdk_Mac_V1.0.1/MouseSdkDemo/MouseSdkDemo.xcodeproj/project.pbxproj index bbae49d..5313cc8 100644 --- a/MouseSdk_Mac_V1.0.1/MouseSdkDemo/MouseSdkDemo.xcodeproj/project.pbxproj +++ b/MouseSdk_Mac_V1.0.1/MouseSdkDemo/MouseSdkDemo.xcodeproj/project.pbxproj @@ -489,6 +489,7 @@ CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = M33X987285; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/framework", @@ -519,6 +520,7 @@ CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = M33X987285; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/framework", diff --git a/MouseSdk_Mac_V1.0.1/MouseSdkDemo/MouseSdkDemo.xcodeproj/project.xcworkspace/xcuserdata/apple.xcuserdatad/UserInterfaceState.xcuserstate b/MouseSdk_Mac_V1.0.1/MouseSdkDemo/MouseSdkDemo.xcodeproj/project.xcworkspace/xcuserdata/apple.xcuserdatad/UserInterfaceState.xcuserstate index f676776..4c9a236 100644 Binary files a/MouseSdk_Mac_V1.0.1/MouseSdkDemo/MouseSdkDemo.xcodeproj/project.xcworkspace/xcuserdata/apple.xcuserdatad/UserInterfaceState.xcuserstate and b/MouseSdk_Mac_V1.0.1/MouseSdkDemo/MouseSdkDemo.xcodeproj/project.xcworkspace/xcuserdata/apple.xcuserdatad/UserInterfaceState.xcuserstate differ