增加2.4g数据传输
authorliangzhenying <867174124@qq.com>
Sat, 30 Mar 2024 03:40:57 +0000 (11:40 +0800)
committerliangzhenying <867174124@qq.com>
Sat, 30 Mar 2024 03:40:57 +0000 (11:40 +0800)
27 files changed:
MouseSdk/AiMouseSdk/AiAudioManager.h
MouseSdk/AiMouseSdk/AiAudioManager.m
MouseSdk/AiMouseSdk/AiAuthManager.m
MouseSdk/AiMouseSdk/AiMouse.h
MouseSdk/AiMouseSdk/AiMouse.m
MouseSdk/AiMouseSdk/AiMouseAudioRecord.h [new file with mode: 0644]
MouseSdk/AiMouseSdk/AiMouseAudioRecord.m [new file with mode: 0644]
MouseSdk/AiMouseSdk/AiMouseConfig.h [new file with mode: 0644]
MouseSdk/AiMouseSdk/AiMouseConfig.m [new file with mode: 0644]
MouseSdk/AiMouseSdk/AiMouseRecordManager.h [new file with mode: 0644]
MouseSdk/AiMouseSdk/AiMouseRecordManager.m [new file with mode: 0644]
MouseSdk/AiMouseSdk/AiMouseSdk.h
MouseSdk/AiMouseSdk/AiMouseTools.m
MouseSdk/AiMouseSdk/AiMouseTools2.h [new file with mode: 0644]
MouseSdk/AiMouseSdk/AiMouseTools2.m [new file with mode: 0644]
MouseSdk/AiMouseSdk/AiRecordManager.h [new file with mode: 0644]
MouseSdk/AiMouseSdk/AiRecordManager.m [new file with mode: 0644]
MouseSdk/MouseSdk.xcodeproj/project.pbxproj
MouseSdk/MouseSdk.xcworkspace/xcuserdata/apple.xcuserdatad/UserInterfaceState.xcuserstate
MouseSdk/MouseSdk.xcworkspace/xcuserdata/apple.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
MouseSdk/MouseSdk/BleManager.m
MouseSdk/MouseSdk/HidManager.h [new file with mode: 0644]
MouseSdk/MouseSdk/HidManager.m [new file with mode: 0644]
MouseSdk/MouseSdk/MouseSdk.entitlements
MouseSdk/MouseSdk/ViewController.m
MouseSdk_Mac_V1.0.1/MouseSdkDemo/MouseSdkDemo.xcodeproj/project.pbxproj
MouseSdk_Mac_V1.0.1/MouseSdkDemo/MouseSdkDemo.xcodeproj/project.xcworkspace/xcuserdata/apple.xcuserdatad/UserInterfaceState.xcuserstate

index fe67d0c..d3e14dc 100644 (file)
@@ -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);
 
 
index 1b6d808..2e53feb 100644 (file)
@@ -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];
 }
index 0a9b99a..64b4276 100644 (file)
@@ -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;
 }
index fd49e55..9f3f9a2 100644 (file)
@@ -8,6 +8,11 @@
 #import <Foundation/Foundation.h>
 #import <AiMouseSdk/AiMouseDeviceInfo.h>
 
+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<AiMouseDelegate> 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: 鉴权结果回调
index b1e0860..897d041 100644 (file)
@@ -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) {
     }
     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{
 }
 
 - (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);
 }
 
 - (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{
     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 (file)
index 0000000..c54cf62
--- /dev/null
@@ -0,0 +1,36 @@
+//
+//  AiMouseAudioRecord.h
+//  AVDemo
+//
+//  Created by incar on 2024/3/27.
+//
+
+#import <Foundation/Foundation.h>
+#import <CoreMedia/CoreMedia.h>
+
+@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 (file)
index 0000000..2d1f250
--- /dev/null
@@ -0,0 +1,268 @@
+//
+//  AiMouseAudioRecord.m
+//  AVDemo
+//
+//  Created by incar on 2024/3/27.
+//
+
+#import "AiMouseAudioRecord.h"
+#import <AVFoundation/AVFoundation.h>
+#import <mach/mach_time.h>
+
+@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 (file)
index 0000000..3e68f0a
--- /dev/null
@@ -0,0 +1,45 @@
+//
+//  AiMouseConfig.h
+//  AiMouseSdk
+//
+//  Created by incar on 2024/3/27.
+//
+
+#import <Foundation/Foundation.h>
+
+#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 (file)
index 0000000..13f55cf
--- /dev/null
@@ -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 (file)
index 0000000..d09866e
--- /dev/null
@@ -0,0 +1,18 @@
+//
+//  AiMouseRecordManager.h
+//  AVDemo
+//
+//  Created by incar on 2024/3/27.
+//
+
+#import <Foundation/Foundation.h>
+
+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 (file)
index 0000000..bb55a74
--- /dev/null
@@ -0,0 +1,115 @@
+//
+//  AiMouseRecordManager.m
+//  AVDemo
+//
+//  Created by incar on 2024/3/27.
+//
+
+#import "AiMouseRecordManager.h"
+#import <AVFoundation/AVFoundation.h>
+#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
index cf5694d..68989f5 100644 (file)
@@ -17,3 +17,6 @@
 
 #import <AiMouseSdk/AiMouse.h>
 #import <AiMouseSdk/AiMouseTools.h>
+
+
+
index 32618f4..33648a7 100644 (file)
@@ -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;
     return @"";
 }
 @end
+
diff --git a/MouseSdk/AiMouseSdk/AiMouseTools2.h b/MouseSdk/AiMouseSdk/AiMouseTools2.h
new file mode 100644 (file)
index 0000000..99393e4
--- /dev/null
@@ -0,0 +1,17 @@
+//
+//  AiMouseTools2.h
+//  AiMouseSdk
+//
+//  Created by incar on 2024/3/27.
+//
+
+#import <Foundation/Foundation.h>
+
+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 (file)
index 0000000..1c37eaa
--- /dev/null
@@ -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 (file)
index 0000000..846505f
--- /dev/null
@@ -0,0 +1,22 @@
+//
+//  AiRecordManager.h
+//  AudioRecorder
+//
+//  Created by incar on 2024/3/29.
+//
+
+#import <Foundation/Foundation.h>
+
+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 (file)
index 0000000..5a6b216
--- /dev/null
@@ -0,0 +1,192 @@
+//
+//  AiRecordManager.m
+//  AudioRecorder
+//
+//  Created by incar on 2024/3/29.
+//
+
+#import "AiRecordManager.h"
+#import <AVFoundation/AVFoundation.h>
+
+@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
index 1bbbde2..d012860 100644 (file)
@@ -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 */; };
 
 /* 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 = "<group>"; };
+               182B547F2BB6A0D7009F53B6 /* AiRecordManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AiRecordManager.h; sourceTree = "<group>"; };
+               1867E5A72BB2706F008FC5FB /* HidManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HidManager.h; sourceTree = "<group>"; };
+               1867E5A82BB2706F008FC5FB /* HidManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HidManager.m; sourceTree = "<group>"; };
+               1877F3E12BB4049600D3220A /* AiMouseConfig.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AiMouseConfig.h; sourceTree = "<group>"; };
+               1877F3E22BB4049600D3220A /* AiMouseConfig.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AiMouseConfig.m; sourceTree = "<group>"; };
+               1877F3E62BB4090E00D3220A /* AiMouseTools2.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AiMouseTools2.h; sourceTree = "<group>"; };
+               1877F3E72BB4090E00D3220A /* AiMouseTools2.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AiMouseTools2.m; sourceTree = "<group>"; };
                18AE9CDE2B99C71F00743515 /* AiMouse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AiMouse.h; sourceTree = "<group>"; };
                18AE9CDF2B99C71F00743515 /* AiMouse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AiMouse.m; sourceTree = "<group>"; };
                18BF1DCA2B81AB7C00B8D90F /* MouseSdk.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MouseSdk.app; sourceTree = BUILT_PRODUCTS_DIR; };
                        children = (
                                18BF1E202B83066200B8D90F /* BleManager.h */,
                                18BF1E212B83066200B8D90F /* BleManager.m */,
+                               1867E5A72BB2706F008FC5FB /* HidManager.h */,
+                               1867E5A82BB2706F008FC5FB /* HidManager.m */,
                                18BF1DCD2B81AB7C00B8D90F /* AppDelegate.h */,
                                18BF1DCE2B81AB7C00B8D90F /* AppDelegate.m */,
                                18BF1DD02B81AB7C00B8D90F /* ViewController.h */,
                                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 = "<group>";
                                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 */,
                                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 */,
                                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 */,
                                        };
                                        18BF1E862B8362D300B8D90F = {
                                                CreatedOnToolsVersion = 15.0.1;
+                                               LastSwiftMigration = 1500;
                                        };
                                };
                        };
                                18BF1E762B832D2E00B8D90F /* ScanVC.m in Sources */,
                                18BF1E222B83066200B8D90F /* BleManager.m in Sources */,
                                18BF1DCF2B81AB7C00B8D90F /* AppDelegate.m in Sources */,
+                               1867E5A92BB2706F008FC5FB /* HidManager.m in Sources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
                        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 */,
                        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;
                                        "$(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)",
                        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;
                                        "$(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)",
                18BF1E902B8362D300B8D90F /* Debug */ = {
                        isa = XCBuildConfiguration;
                        buildSettings = {
+                               CLANG_ENABLE_MODULES = YES;
                                CODE_SIGN_STYLE = Automatic;
                                COMBINE_HIDPI_IMAGES = YES;
                                CURRENT_PROJECT_VERSION = 1;
                                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 = "";
                        };
                18BF1E912B8362D300B8D90F /* Release */ = {
                        isa = XCBuildConfiguration;
                        buildSettings = {
+                               CLANG_ENABLE_MODULES = YES;
                                CODE_SIGN_STYLE = Automatic;
                                COMBINE_HIDPI_IMAGES = YES;
                                CURRENT_PROJECT_VERSION = 1;
                                PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
                                SKIP_INSTALL = YES;
                                SWIFT_EMIT_LOC_STRINGS = YES;
+                               SWIFT_VERSION = 5.0;
                                VERSIONING_SYSTEM = "apple-generic";
                                VERSION_INFO_PREFIX = "";
                        };
index 89650fd..80d43fd 100644 (file)
Binary files a/MouseSdk/MouseSdk.xcworkspace/xcuserdata/apple.xcuserdatad/UserInterfaceState.xcuserstate and b/MouseSdk/MouseSdk.xcworkspace/xcuserdata/apple.xcuserdatad/UserInterfaceState.xcuserstate differ
index 72091c3..6486004 100644 (file)
@@ -3,4 +3,82 @@
    uuid = "96107861-D4C9-42B2-9605-61F7DD48CC72"
    type = "0"
    version = "2.0">
+   <Breakpoints>
+      <BreakpointProxy
+         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
+         <BreakpointContent
+            uuid = "019E5380-401C-4E19-9E7F-0F7DA329E81F"
+            shouldBeEnabled = "Yes"
+            ignoreCount = "0"
+            continueAfterRunningActions = "No"
+            filePath = "AiMouseSdk/AiMouseAudioRecord.m"
+            startingColumnNumber = "9223372036854775807"
+            endingColumnNumber = "9223372036854775807"
+            startingLineNumber = "220"
+            endingLineNumber = "220"
+            landmarkName = "audioBufferCallBack(inRefCon, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData)"
+            landmarkType = "9">
+         </BreakpointContent>
+      </BreakpointProxy>
+      <BreakpointProxy
+         BreakpointExtensionID = "Xcode.Breakpoint.ExceptionBreakpoint">
+         <BreakpointContent
+            uuid = "F738AAA8-B689-4FF7-AA28-A46920AE7879"
+            shouldBeEnabled = "Yes"
+            ignoreCount = "0"
+            continueAfterRunningActions = "No"
+            breakpointStackSelectionBehavior = "1"
+            scope = "1"
+            stopOnStyle = "0">
+         </BreakpointContent>
+      </BreakpointProxy>
+      <BreakpointProxy
+         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
+         <BreakpointContent
+            uuid = "FDA64E17-61A3-4439-9354-1E36554FE365"
+            shouldBeEnabled = "Yes"
+            ignoreCount = "0"
+            continueAfterRunningActions = "No"
+            filePath = "MouseSdk/BleManager.m"
+            startingColumnNumber = "9223372036854775807"
+            endingColumnNumber = "9223372036854775807"
+            startingLineNumber = "184"
+            endingLineNumber = "184"
+            landmarkName = "-aiMouseSendCmdData:"
+            landmarkType = "7">
+            <Locations>
+               <Location
+                  uuid = "FDA64E17-61A3-4439-9354-1E36554FE365 - 4643c942ae7f0b57"
+                  shouldBeEnabled = "Yes"
+                  ignoreCount = "0"
+                  continueAfterRunningActions = "No"
+                  symbolName = "-[BleManager aiMouseSendCmdData:]"
+                  moduleName = "MouseSdk"
+                  usesParentBreakpointCondition = "Yes"
+                  urlString = "file:///Users/apple/Desktop/code/MouseSdk-iOS/MouseSdk/MouseSdk/BleManager.m"
+                  startingColumnNumber = "9223372036854775807"
+                  endingColumnNumber = "9223372036854775807"
+                  startingLineNumber = "184"
+                  endingLineNumber = "184"
+                  offsetFromSymbolStart = "378">
+               </Location>
+               <Location
+                  uuid = "FDA64E17-61A3-4439-9354-1E36554FE365 - 4643c942ae7f0b57"
+                  shouldBeEnabled = "Yes"
+                  ignoreCount = "0"
+                  continueAfterRunningActions = "No"
+                  symbolName = "-[BleManager aiMouseSendCmdData:]"
+                  moduleName = "MouseSdk"
+                  usesParentBreakpointCondition = "Yes"
+                  urlString = "file:///Users/apple/Desktop/code/MouseSdk-iOS/MouseSdk/MouseSdk/BleManager.m"
+                  startingColumnNumber = "9223372036854775807"
+                  endingColumnNumber = "9223372036854775807"
+                  startingLineNumber = "184"
+                  endingLineNumber = "184"
+                  offsetFromSymbolStart = "171">
+               </Location>
+            </Locations>
+         </BreakpointContent>
+      </BreakpointProxy>
+   </Breakpoints>
 </Bucket>
index 215ac45..9d08625 100644 (file)
@@ -41,7 +41,7 @@
     self = [super init];
     if (self) {
         self.centralManager = [[CBCentralManager alloc]initWithDelegate:self queue:nil];
-        [AiMouse shared].delegate = self;
+        
     }
     return self;
 }
             //鉴权特征
             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)]];
 
 /// 鼠标按键指令回调
 /// - 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 (file)
index 0000000..826614b
--- /dev/null
@@ -0,0 +1,23 @@
+//
+//  HidManager.h
+//  MouseSdk
+//
+//  Created by incar on 2024/3/26.
+//
+
+#import <Foundation/Foundation.h>
+#import <AiMouseSdk/AiMouseDeviceInfo.h>
+
+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 (file)
index 0000000..c15857a
--- /dev/null
@@ -0,0 +1,231 @@
+//
+//  HidManager.m
+//  MouseSdk
+//
+//  Created by incar on 2024/3/26.
+//
+
+#import "HidManager.h"
+#include <IOKit/hid/IOHIDLib.h>
+#include <IOKit/hid/IOHIDKeys.h>
+
+#import <AiMouseSdk/AiMouseSdk.h>
+#import "Config.h"
+
+@interface HidManager ()<AiMouseDelegate>
+
+//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
index 7ff8a92..6fe1208 100644 (file)
@@ -4,8 +4,12 @@
 <dict>
        <key>com.apple.security.app-sandbox</key>
        <true/>
+       <key>com.apple.security.device.audio-input</key>
+       <true/>
        <key>com.apple.security.device.bluetooth</key>
        <true/>
+       <key>com.apple.security.device.usb</key>
+       <true/>
        <key>com.apple.security.files.user-selected.read-only</key>
        <true/>
        <key>com.apple.security.network.client</key>
index 63bd155..6bf7f72 100644 (file)
@@ -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 {
     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;
         [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{
 }
 
 - (void)bleDidConnect:(NSNotification *)notification{
-    self.connectLabel.stringValue = @"连接状态:已连接";
+    self.bleLabel.stringValue = @"蓝牙连接状态:已连接";
 }
+
 - (void)bleDidDisconnect:(NSNotification *)notification{
-    self.connectLabel.stringValue = @"连接状态:未连接";
+    self.bleLabel.stringValue = @"蓝牙连接状态:未连接";
 }
+
 @end
index bbae49d..5313cc8 100644 (file)
                                CODE_SIGN_STYLE = Automatic;
                                COMBINE_HIDPI_IMAGES = YES;
                                CURRENT_PROJECT_VERSION = 1;
+                               DEVELOPMENT_TEAM = M33X987285;
                                FRAMEWORK_SEARCH_PATHS = (
                                        "$(inherited)",
                                        "$(PROJECT_DIR)/framework",
                                CODE_SIGN_STYLE = Automatic;
                                COMBINE_HIDPI_IMAGES = YES;
                                CURRENT_PROJECT_VERSION = 1;
+                               DEVELOPMENT_TEAM = M33X987285;
                                FRAMEWORK_SEARCH_PATHS = (
                                        "$(inherited)",
                                        "$(PROJECT_DIR)/framework",
index f676776..4c9a236 100644 (file)
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