@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);
__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];
}
_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;
}
#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,///鉴权成功
/// 鼠标按键指令回调
/// - 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;
@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: 鉴权结果回调
// 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);
-(instancetype)init{
if (self = [super init]) {
_authState = AiMouseAuthState_Unknow;
+ _connectType = AiMouseConnectType_bluetooth;
}
return self;
}
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
--- /dev/null
+//
+// 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
--- /dev/null
+//
+// 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
--- /dev/null
+//
+// 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
--- /dev/null
+//
+// AiMouseConfig.m
+// AiMouseSdk
+//
+// Created by incar on 2024/3/27.
+//
+
+#import "AiMouseConfig.h"
+
+@implementation AiMouseConfig
+
+@end
--- /dev/null
+//
+// 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
--- /dev/null
+//
+// 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
#import <AiMouseSdk/AiMouse.h>
#import <AiMouseSdk/AiMouseTools.h>
+
+
+
}
+ (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
+
--- /dev/null
+//
+// 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
--- /dev/null
+//
+// 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
--- /dev/null
+//
+// 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
--- /dev/null
+//
+// 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
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 = "";
};
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>
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{
--- /dev/null
+//
+// 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
--- /dev/null
+//
+// 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
<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>
#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
[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
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",