79 changed files with 1411 additions and 108 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1 @@ |
|||
Versions/Current/Headers |
|||
@ -0,0 +1 @@ |
|||
Versions/Current/JLBmpConvertKit |
|||
@ -0,0 +1 @@ |
|||
Versions/Current/Modules |
|||
@ -0,0 +1 @@ |
|||
Versions/Current/Resources |
|||
@ -0,0 +1,95 @@ |
|||
//
|
|||
// JLBmpCovert.h
|
|||
// JLBmpCovertKit
|
|||
//
|
|||
// Created by EzioChan on 2024/9/9.
|
|||
// Copyright © 2024 www.zh-jieli.com. All rights reserved.
|
|||
//
|
|||
|
|||
#import <Foundation/Foundation.h> |
|||
#import <JLBmpConvertKit/JLBmpConvertKit.h> |
|||
#if TARGET_OS_IOS || TARGET_OS_MACCATALYST |
|||
#import <UIKit/UIKit.h> |
|||
#elif TARGET_OS_OSX |
|||
#import <AppKit/AppKit.h> |
|||
#endif |
|||
|
|||
|
|||
NS_ASSUME_NONNULL_BEGIN |
|||
@class JLImageConvertResult; |
|||
|
|||
typedef void(^JLBmpCovertCallBack)(NSString *inFilePath,NSString *__nullable outFilePath,NSError *__nullable error) __deprecated_msg("This interface is deprecated. Please use convert:inFilePath:outFilePath: or convert:Image: instead."); |
|||
|
|||
typedef void(^JLBmpCovertCallBack2)(NSData *__nullable outFileData,NSError *__nullable error) __deprecated_msg("This interface is deprecated. Please use convert:inFilePath:outFilePath: or convert:Image: instead."); |
|||
|
|||
|
|||
/// 图像转换
|
|||
@interface JLBmpConvert : NSObject |
|||
#if TARGET_OS_IOS || TARGET_OS_MACCATALYST |
|||
|
|||
/// 图片转换
|
|||
/// - Parameters:
|
|||
/// - type: 转换类型
|
|||
/// - inFilePath: 输入文件
|
|||
/// - outFilePath: 输出文件
|
|||
/// - completion: 回调
|
|||
+(void)covert:(JLBmpConvertType)type |
|||
inFilePath:(NSString *_Nonnull)inFilePath |
|||
outFilePath:(NSString * __nullable)outFilePath |
|||
completion:(JLBmpCovertCallBack _Nonnull)completion __deprecated_msg("This interface is deprecated. Please use convert:inFilePath:outFilePath: or convert:Image: instead."); |
|||
|
|||
/// 图片转换
|
|||
/// - Parameters:
|
|||
/// - type: 转换类型
|
|||
/// - inImage: 输入图片
|
|||
/// - completion: 回调
|
|||
+(void)covert:(JLBmpConvertType)type |
|||
Image:(UIImage *)inImage |
|||
completion:(JLBmpCovertCallBack2 _Nonnull)completion __deprecated_msg("This interface is deprecated. Please use convert:inFilePath:outFilePath: or convert:Image: instead."); |
|||
|
|||
/// 图片转换
|
|||
/// - Parameters:
|
|||
/// - option: 类型
|
|||
/// 当前仅仅 707N 系列芯片可指定图像格式
|
|||
/// - inFilePath: 输入文件
|
|||
/// - outFilePath: 输出文件
|
|||
/// 文件路径转换入口
|
|||
+ (JLImageConvertResult *)convert:(JLBmpConvertOption *)option |
|||
inFilePath:(NSString *)inFilePath |
|||
outFilePath:(NSString * __nullable)outFilePath; |
|||
|
|||
/// UIImage 转换
|
|||
/// 当前仅仅 707N 系列芯片可指定图像格式
|
|||
/// - Parameters:
|
|||
/// - option: 类型
|
|||
/// - imgData: 图片的原始数据
|
|||
+(JLImageConvertResult *)convert:(JLBmpConvertOption *)option ImageData:(NSData *)imgData; |
|||
|
|||
|
|||
/// UIImage 转换成 BGRA
|
|||
/// - Parameter image: 图片
|
|||
+(NSData*)convertImageToBGRABytes:(UIImage *)image; |
|||
|
|||
/// UIImage 转换成 RGBA
|
|||
/// - Parameter image: 图片
|
|||
+(NSData*)convertImageToRGBABytes:(UIImage *)image; |
|||
|
|||
|
|||
/// 图片缩放
|
|||
/// - Parameters:
|
|||
/// - image: 图片
|
|||
/// - newSize: 缩放大小
|
|||
/// - Returns: 缩放后的图片
|
|||
+(NSData *)resizeImage:(UIImage*)image andResizeTo:(CGSize)newSize; |
|||
|
|||
#elif TARGET_OS_OSX |
|||
/// 图片转换
|
|||
/// - Parameters:
|
|||
/// - option: 转换类型
|
|||
/// - inImage: 输入图片
|
|||
+(JLImageConvertResult *)convert:(JLBmpConvertOption *)option Image:(NSImage *)image; |
|||
|
|||
#endif |
|||
@end |
|||
|
|||
NS_ASSUME_NONNULL_END |
|||
@ -0,0 +1,24 @@ |
|||
//
|
|||
// JLBmpConvertKit.h
|
|||
// JLBmpConvertKit
|
|||
//
|
|||
// Created by EzioChan on 2024/9/9.
|
|||
// Copyright © 2024 www.zh-jieli.com. All rights reserved.
|
|||
//
|
|||
|
|||
#import <Foundation/Foundation.h> |
|||
|
|||
//! Project version number for JLBmpConvertKit.
|
|||
FOUNDATION_EXPORT double JLBmpConvertKitVersionNumber; |
|||
|
|||
//! Project version string for JLBmpConvertKit.
|
|||
FOUNDATION_EXPORT const unsigned char JLBmpConvertKitVersionString[]; |
|||
|
|||
// In this header, you should import all the public headers of your framework using statements like #import <JLBmpConvertKit/PublicHeader.h>
|
|||
|
|||
|
|||
#import <JLBmpConvertKit/JLBmpConvertOption.h> |
|||
#import <JLBmpConvertKit/JLBmpConvert.h> |
|||
#import <JLBmpConvertKit/JLGifBin.h> |
|||
#import <JLBmpConvertKit/JLImageConvertResult.h> |
|||
|
|||
@ -0,0 +1,89 @@ |
|||
//
|
|||
// JLBmpConvertOption.h
|
|||
// JLBmpConvertKit
|
|||
//
|
|||
// Created by EzioChan on 2025/5/23.
|
|||
// Copyright © 2025 www.zh-jieli.com. All rights reserved.
|
|||
//
|
|||
|
|||
#import <Foundation/Foundation.h> |
|||
|
|||
NS_ASSUME_NONNULL_BEGIN |
|||
|
|||
/// 图像转换类型
|
|||
typedef NS_ENUM(NSUInteger, JLBmpConvertType) { |
|||
/// 695N 图像转换 RGB 转换成 695N 芯片对应的图像资源
|
|||
/// 对应原 BR23 转换算法
|
|||
JLBmpConvertType695N_RBG = 0, |
|||
/// 701N 图像转换 RGB 转换成 701N 芯片对应的图像资源
|
|||
/// 对应原 BR28 转换的算法
|
|||
JLBmpConvertType701N_RBG = 1, |
|||
/// 701N 图像转换 ARGB 转换成 701N 芯片对应的图像资源
|
|||
/// 对应原 BR28_ARBG 转换的算法
|
|||
JLBmpConvertType701N_ARBG = 2, |
|||
/// 701N 图像转换 RGB 转换成 701N 芯片对应的图像资源(不打包封装)
|
|||
/// 对应原 BR28_RGB_NO_PACK 转换的算法(不打包封装)
|
|||
JLBmpConvertType701N_RBG_NO_PACK = 3, |
|||
/// 701N 图像转换 ARGB 转换成 701N 芯片对应的图像资源(不打包封装)
|
|||
/// 对应原 BR28_ARGB_NO_PACK 转换的算法(不打包封装)
|
|||
JLBmpConvertType701N_ARGB_NO_PACK = 4, |
|||
/// 707N 图像转换 RGB 转换成 707N 芯片对应的图像资源
|
|||
/// 对应 BR35 转换的算法
|
|||
JLBmpConvertType707N_RBG = 5, |
|||
/// 707N 图像转换 ARGB 转换成 707N 芯片对应的图像资源
|
|||
/// 对应 BR35_ARGB 转换的算法
|
|||
JLBmpConvertType707N_ARGB = 6, |
|||
/// 707N 图像转换 RGB 转换成 707N 芯片对应的图像资源(不打包封装)
|
|||
/// 对应 BR35_RGB_NO_PACK 转换的算法(不打包封装)
|
|||
JLBmpConvertType707N_ARGB_NO_PACK = 7, |
|||
/// 707N 图像转换 RGB 转换成 707N 芯片对应的图像资源(不打包封装)
|
|||
/// 对应 BR35_RGB_NO_PACK 转换的算法(不打包封装)
|
|||
JLBmpConvertType707N_RBG_NO_PACK = 8, |
|||
/// 701N 图像转换 JPEG
|
|||
/// 仅仅支持 JPEG 类型
|
|||
JLBmpConvertType701N_JPEG = 9 |
|||
}; |
|||
|
|||
/// 图像格式
|
|||
/// 仅(707N 系列芯片支持)
|
|||
typedef NS_ENUM(NSUInteger, JLBmpPixelformat) { |
|||
/// ARBG8888/RBG888 格式
|
|||
JLBmpPixelformat_888, |
|||
/// ARBG8565/RBG565 格式
|
|||
JLBmpPixelformat_565, |
|||
/// 自动选择最小的格式
|
|||
JLBmpPixelformat_Auto, |
|||
}; |
|||
|
|||
/// LVGL 打包格式
|
|||
typedef NS_ENUM(NSUInteger, JLBmpPacketFormat) { |
|||
JLBmpPacketFormatNone = 0, |
|||
JLBmpPacketFormatJLUI = 1, |
|||
JLBmpPacketFormatLVGL = 2 |
|||
}; |
|||
|
|||
/// 图像转换选项
|
|||
@interface JLBmpConvertOption : NSObject |
|||
|
|||
/// 图像转换类型
|
|||
@property (nonatomic, assign) JLBmpConvertType convertType; |
|||
|
|||
/// 图像格式
|
|||
/// 默认为 auto
|
|||
/// 仅(707N 系列芯片支持)
|
|||
@property (nonatomic, assign) JLBmpPixelformat pixelformat; |
|||
|
|||
/// LVGL 打包格式
|
|||
/// 默认为 JLUI
|
|||
/// 仅(707N 系列芯片支持)
|
|||
@property (nonatomic, assign) JLBmpPacketFormat packetFormat; |
|||
|
|||
/// 是否转换成 BGRA
|
|||
/// 当使用的是 JL 的 UI 框架时需要使用
|
|||
/// 使用其他第三方框架时,需要根据固件端框架进行调整
|
|||
/// default YES
|
|||
@property (nonatomic, assign) BOOL convertToBGRA; |
|||
|
|||
@end |
|||
|
|||
NS_ASSUME_NONNULL_END |
|||
@ -0,0 +1,77 @@ |
|||
//
|
|||
// JLGifBin.h
|
|||
// JLGifLib
|
|||
//
|
|||
// Created by EzioChan on 2024/1/18.
|
|||
// Copyright © 2024 www.zh-jieli.com. All rights reserved.
|
|||
//
|
|||
|
|||
#import <Foundation/Foundation.h> |
|||
|
|||
NS_ASSUME_NONNULL_BEGIN |
|||
|
|||
/// Gif 转 Bin 支持的芯片类型
|
|||
typedef NS_ENUM(NSUInteger, JLGIFBinChipType) { |
|||
/// 701N
|
|||
JLGIFBinChipJL_701N, |
|||
/// 707N
|
|||
JLGIFBinChipJL_707N, |
|||
}; |
|||
|
|||
///packet 是否打包
|
|||
typedef NS_ENUM(NSInteger, JLGIFBinPackageType) { |
|||
/// 不做其他打包使用源数据,适用 LVGL 的公版UI框架
|
|||
JLGIFBinPackageTypeLVGL = 0, |
|||
/// 杰理打包格式,适用 JLVGL 的杰理定制版 UI 框架
|
|||
JLGIFBinPackageTypeJLVGL = 1, |
|||
}; |
|||
|
|||
typedef void(^JLGif2BinBlock)(int code,NSData *_Nullable binData); |
|||
|
|||
/// Gif 转 Bin
|
|||
@interface JLGifBin : NSObject |
|||
|
|||
/// 打印当前SDK的版本
|
|||
+(void)sdkVersion; |
|||
|
|||
/// 根据 Gif 创建 Bin
|
|||
/// - Parameters:
|
|||
/// - gifData: Gif 图片数据
|
|||
/// - level: 等级
|
|||
/// 1: 低码率
|
|||
/// 2: 中码率
|
|||
/// 3: 高码率
|
|||
/// - block: 回调内容
|
|||
+(void)makeDataToBin:(NSData *)gifData Level:(int)level Result:(JLGif2BinBlock)block __attribute__((deprecated("Use makeDataToBin:Level:ChipType:Result: instead."))); |
|||
|
|||
|
|||
/// 根据 Gif 创建 Bin
|
|||
/// - Parameters:
|
|||
/// - gifData: Gif 图片数据
|
|||
/// - level: 等级
|
|||
/// 1: 低码率
|
|||
/// 2: 中码率 (仅 JLGIFBinChipJL_701N 支持)
|
|||
/// 3: 高码率 (仅 JLGIFBinChipJL_701N 支持)
|
|||
/// - chip: 芯片类型
|
|||
/// - block: 回调内容
|
|||
+(void)makeDataToBin:(NSData *)gifData Level:(int)level ChipType:(JLGIFBinChipType)chip Result:(JLGif2BinBlock)block __attribute__((deprecated("Use makeDataToBin:Level:ChipType:PackageType:Result: instead."))); |
|||
|
|||
|
|||
/// 根据 Gif 创建 Bin
|
|||
/// - Parameters:
|
|||
/// - gifData: Gif 图片数据
|
|||
/// - level: 等级
|
|||
/// 1: 低码率
|
|||
/// 2: 中码率 (仅 JLGIFBinChipJL_701N 支持)
|
|||
/// 3: 高码率 (仅 JLGIFBinChipJL_701N 支持)
|
|||
/// - chip: 芯片类型
|
|||
/// - packageType: 打包类型
|
|||
/// 1: JLUI 杰理 UI 格式
|
|||
/// 0: 无 不打包
|
|||
/// - block: 回调内容
|
|||
+(void)makeDataToBin:(NSData *)gifData Level:(int)level ChipType:(JLGIFBinChipType)chip PackageType:(JLGIFBinPackageType)packageType Result:(JLGif2BinBlock)block; |
|||
|
|||
|
|||
@end |
|||
|
|||
NS_ASSUME_NONNULL_END |
|||
@ -0,0 +1,34 @@ |
|||
//
|
|||
// JLImageConvertResult.h
|
|||
// JLBmpConvertKit
|
|||
//
|
|||
// Created by EzioChan on 2025/3/6.
|
|||
// Copyright © 2025 www.zh-jieli.com. All rights reserved.
|
|||
//
|
|||
|
|||
#import <Foundation/Foundation.h> |
|||
|
|||
NS_ASSUME_NONNULL_BEGIN |
|||
|
|||
/// 图像转换结果
|
|||
@interface JLImageConvertResult : NSObject |
|||
/// 结果码
|
|||
@property (nonatomic, assign) int result; |
|||
/// 数据大小
|
|||
@property (nonatomic, assign) int buf_size; |
|||
/// 图像格式
|
|||
@property (nonatomic, assign) int pixel_format; |
|||
/// 压缩模式
|
|||
@property (nonatomic, assign) int compress_mode; |
|||
|
|||
#if TARGET_OS_IOS || TARGET_OS_MACCATALYST |
|||
/// 输出文件路径
|
|||
@property (nullable, nonatomic, copy) NSString *outFilePath; |
|||
#endif |
|||
|
|||
/// 输出文件内容
|
|||
@property (nullable, nonatomic, copy) NSData *outFileData; |
|||
|
|||
@end |
|||
|
|||
NS_ASSUME_NONNULL_END |
|||
Binary file not shown.
@ -0,0 +1,6 @@ |
|||
framework module JLBmpConvertKit { |
|||
umbrella header "JLBmpConvertKit.h" |
|||
export * |
|||
|
|||
module * { export * } |
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
|||
<plist version="1.0"> |
|||
<dict> |
|||
<key>BuildMachineOSBuild</key> |
|||
<string>25B78</string> |
|||
<key>CFBundleDevelopmentRegion</key> |
|||
<string>en</string> |
|||
<key>CFBundleExecutable</key> |
|||
<string>JLBmpConvertKit</string> |
|||
<key>CFBundleIdentifier</key> |
|||
<string>com.zh-jieli.JLBmpConvertKit</string> |
|||
<key>CFBundleInfoDictionaryVersion</key> |
|||
<string>6.0</string> |
|||
<key>CFBundleName</key> |
|||
<string>JLBmpConvertKit</string> |
|||
<key>CFBundlePackageType</key> |
|||
<string>FMWK</string> |
|||
<key>CFBundleShortVersionString</key> |
|||
<string>1.5.0</string> |
|||
<key>CFBundleSupportedPlatforms</key> |
|||
<array> |
|||
<string>MacOSX</string> |
|||
</array> |
|||
<key>CFBundleVersion</key> |
|||
<string>1</string> |
|||
<key>DTCompiler</key> |
|||
<string>com.apple.compilers.llvm.clang.1_0</string> |
|||
<key>DTPlatformBuild</key> |
|||
<string>25C57</string> |
|||
<key>DTPlatformName</key> |
|||
<string>macosx</string> |
|||
<key>DTPlatformVersion</key> |
|||
<string>26.2</string> |
|||
<key>DTSDKBuild</key> |
|||
<string>25C57</string> |
|||
<key>DTSDKName</key> |
|||
<string>macosx26.2</string> |
|||
<key>DTXcode</key> |
|||
<string>2620</string> |
|||
<key>DTXcodeBuild</key> |
|||
<string>17C52</string> |
|||
<key>LSMinimumSystemVersion</key> |
|||
<string>10.13</string> |
|||
<key>NSHumanReadableCopyright</key> |
|||
<string>Copyright © 2024 www.zh-jieli.com. All rights reserved.</string> |
|||
</dict> |
|||
</plist> |
|||
@ -0,0 +1 @@ |
|||
A |
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1 @@ |
|||
Versions/Current/Headers |
|||
@ -0,0 +1 @@ |
|||
Versions/Current/JLLogHelper |
|||
@ -0,0 +1 @@ |
|||
Versions/Current/Modules |
|||
@ -0,0 +1 @@ |
|||
Versions/Current/Resources |
|||
@ -0,0 +1,19 @@ |
|||
//
|
|||
// JLLogHelper.h
|
|||
// JLLogHelper
|
|||
//
|
|||
// Created by EzioChan on 2024/5/8.
|
|||
// Copyright © 2024 www.zh-jieli.com. All rights reserved.
|
|||
//
|
|||
|
|||
#import <Foundation/Foundation.h> |
|||
|
|||
//! Project version number for JLLogHelper.
|
|||
FOUNDATION_EXPORT double JLLogHelperVersionNumber; |
|||
|
|||
//! Project version string for JLLogHelper.
|
|||
FOUNDATION_EXPORT const unsigned char JLLogHelperVersionString[]; |
|||
|
|||
// In this header, you should import all the public headers of your framework using statements like #import <JLLogHelper/PublicHeader.h>
|
|||
|
|||
#import <JLLogHelper/JLLogManager.h> |
|||
@ -0,0 +1,88 @@ |
|||
//
|
|||
// JLLogManager.h
|
|||
// JLLogHelper
|
|||
//
|
|||
// Created by EzioChan on 2024/5/8.
|
|||
// Copyright © 2024 www.zh-jieli.com. All rights reserved.
|
|||
//
|
|||
|
|||
#import <Foundation/Foundation.h> |
|||
|
|||
NS_ASSUME_NONNULL_BEGIN |
|||
|
|||
/// 打印宏
|
|||
#define kJLLog(level,fmt...) [JLLogManager logLevel:level funcName:__FUNCTION__ line:__LINE__ format:fmt] |
|||
|
|||
typedef NS_ENUM(NSInteger, JLLOG_LEVEL) { |
|||
JLLOG_COMPLETE = 0, |
|||
JLLOG_DEBUG = 1, |
|||
JLLOG_INFO = 2, |
|||
JLLOG_WARN = 3, |
|||
JLLOG_ERROR = 4, |
|||
}; |
|||
|
|||
/// 日志打印
|
|||
@interface JLLogManager : NSObject |
|||
|
|||
/// 打印SDK版本
|
|||
+(void)sdkVersion; |
|||
|
|||
///清理已存储的打印
|
|||
+(void)clearLog; |
|||
|
|||
/**
|
|||
* 打印文本 |
|||
*/ |
|||
+(void)openLogTextFile; |
|||
|
|||
///重定向保存日志文件路径设置
|
|||
/// @param path 路径
|
|||
+(void)redirectLogPath:(NSString *)path; |
|||
|
|||
/// 收集日志回调接口
|
|||
/// @param block 回调
|
|||
+(void)collectLog:(void(^)(NSString *log))block; |
|||
|
|||
/// 打印时间
|
|||
/// @param isTimeStamp 是否打印 default is false
|
|||
+(void)logWithTimestamp:(BOOL)isTimeStamp; |
|||
|
|||
/// 把日志保存存储起来
|
|||
/// @param isSave 是否保存 default is yes
|
|||
+(void)saveLogAsFile:(BOOL)isSave; |
|||
|
|||
///LOG使能与等级,默认开启且debug等级。
|
|||
/// @param enable LOG使能
|
|||
/// @param isMore 是否打印【函数名&行号】
|
|||
/// @param level LOG等级
|
|||
+(void)setLog:(BOOL)enable IsMore:(BOOL)isMore Level:(JLLOG_LEVEL)level; |
|||
|
|||
/// 打印函数
|
|||
/// @param level LOG等级
|
|||
/// @param func 函数名
|
|||
/// @param line 行号
|
|||
/// @param format 内容
|
|||
+(void)logLevel:(JLLOG_LEVEL)level funcName:(const char* _Nullable)func line:(const int)line format:(NSString * _Nonnull)format,...; |
|||
|
|||
/// 打印
|
|||
/// @param something 任意字符串内容
|
|||
+(void)logSomething:(NSString *)something; |
|||
|
|||
/// 打印对象
|
|||
/// @param object 任意对象
|
|||
+(void)logObject:(id)object; |
|||
|
|||
|
|||
|
|||
/// 打印
|
|||
/// @param level LOG等级
|
|||
/// @param content 内容
|
|||
+(void)logLevel:(JLLOG_LEVEL)level content:(NSString *)content; |
|||
|
|||
/// 保存日志信息到文件中
|
|||
/// @param logContent 日志内容
|
|||
+(void)saveLog:(NSString *)logContent; |
|||
|
|||
@end |
|||
|
|||
NS_ASSUME_NONNULL_END |
|||
Binary file not shown.
@ -0,0 +1,6 @@ |
|||
framework module JLLogHelper { |
|||
umbrella header "JLLogHelper.h" |
|||
export * |
|||
|
|||
module * { export * } |
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
|||
<plist version="1.0"> |
|||
<dict> |
|||
<key>BuildMachineOSBuild</key> |
|||
<string>25B78</string> |
|||
<key>CFBundleDevelopmentRegion</key> |
|||
<string>en</string> |
|||
<key>CFBundleExecutable</key> |
|||
<string>JLLogHelper</string> |
|||
<key>CFBundleIdentifier</key> |
|||
<string>com.zh-jieli.JLLogHelper</string> |
|||
<key>CFBundleInfoDictionaryVersion</key> |
|||
<string>6.0</string> |
|||
<key>CFBundleName</key> |
|||
<string>JLLogHelper</string> |
|||
<key>CFBundlePackageType</key> |
|||
<string>FMWK</string> |
|||
<key>CFBundleShortVersionString</key> |
|||
<string>1.2</string> |
|||
<key>CFBundleSupportedPlatforms</key> |
|||
<array> |
|||
<string>MacOSX</string> |
|||
</array> |
|||
<key>CFBundleVersion</key> |
|||
<string>1</string> |
|||
<key>DTCompiler</key> |
|||
<string>com.apple.compilers.llvm.clang.1_0</string> |
|||
<key>DTPlatformBuild</key> |
|||
<string>25C57</string> |
|||
<key>DTPlatformName</key> |
|||
<string>macosx</string> |
|||
<key>DTPlatformVersion</key> |
|||
<string>26.2</string> |
|||
<key>DTSDKBuild</key> |
|||
<string>25C57</string> |
|||
<key>DTSDKName</key> |
|||
<string>macosx26.2</string> |
|||
<key>DTXcode</key> |
|||
<string>2620</string> |
|||
<key>DTXcodeBuild</key> |
|||
<string>17C52</string> |
|||
<key>LSMinimumSystemVersion</key> |
|||
<string>26.2</string> |
|||
<key>NSHumanReadableCopyright</key> |
|||
<string>Copyright © 2024 www.zh-jieli.com. All rights reserved.</string> |
|||
</dict> |
|||
</plist> |
|||
@ -0,0 +1 @@ |
|||
A |
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,191 @@ |
|||
import Foundation |
|||
import JL_BLEKit |
|||
|
|||
/// MODE_CALL_RECORD(mode=7)—— 通话录音(设备采集,无 TTS 回写)。 |
|||
/// |
|||
/// 与 Android `JieliDeviceRecordPort` 对齐: |
|||
/// - 下发 `mode=7 / audioType=OPUS / channel=2 / sampleRate=16k / recordtype=byDevice` |
|||
/// - 耳机持续上推 `SOURCE_E_SCO_MIX` 双声道交织 OPUS(L=本端 / R=对端) |
|||
/// - `OpusStreamDecoder(channels=2, dataSize=80)` 解码成 16k/16bit/stereo 交织 PCM |
|||
/// - 以单路 `in.stereo` 向 Dart 抛 PCM;**无 output 流**,不接 TTS 回写 |
|||
/// |
|||
/// 与 [StereoCallTranslationModeHandler](mode=6)的区别: |
|||
/// - mode=6 是立体声"通话翻译":耳机把 mix 上推、APP 可把译文回写给耳机 |
|||
/// - mode=7 是专门的"通话录音":只采集,不回写,固件通路与通话翻译互斥 |
|||
/// |
|||
/// 注:SDK 的 `JLTranslateSetModeType` 枚举没有 0x07,但它是 `NS_ENUM(NSUInteger, …)`, |
|||
/// 可以用 `JLTranslateSetModeType(rawValue: 7)` 构造未命名 case 下发到设备。 |
|||
public final class CallRecordModeHandler: BaseTranslationModeHandler { |
|||
|
|||
private var decoder: OpusStreamDecoder? |
|||
private var sampleRate = 16000 |
|||
private var audioType: JL_SpeakDataType = .OPUS |
|||
private var rxFrameCount: Int64 = 0 |
|||
private var rxBytes: Int64 = 0 |
|||
private var heartbeatTimer: DispatchSourceTimer? |
|||
private let heartbeatQueue = DispatchQueue(label: "callrecord.heartbeat", qos: .utility) |
|||
|
|||
public init(server: JieliHomeServer) { |
|||
super.init( |
|||
server: server, |
|||
modeId: TranslationModeIds.MODE_CALL_RECORD, |
|||
inputs: [TranslationStreams.IN_STEREO], |
|||
outputs: [] |
|||
) |
|||
} |
|||
|
|||
public override func start(args: [String: Any], completion: @escaping (Bool, String?) -> Void) { |
|||
print("[CallRecord] start ENTRY args=\(args)") |
|||
guard let server = server else { |
|||
completion(false, "server uninitialized"); return |
|||
} |
|||
if working { |
|||
print("[CallRecord] start already working, returning ok") |
|||
completion(true, nil); return |
|||
} |
|||
|
|||
let address = args["address"] as? String |
|||
let session: TranslationSession? = { |
|||
if let addr = address { return server.translationSession(for: addr) } |
|||
return server.currentTranslationSession() |
|||
}() |
|||
guard let s = session else { |
|||
emitError(-200, "no connected device; pass args.address or connect first") |
|||
completion(false, "no connected device"); return |
|||
} |
|||
|
|||
let sr = (args["sampleRate"] as? NSNumber)?.intValue ?? 16000 |
|||
let aType = Self.parseAudioType(args["audioType"]) ?? .OPUS |
|||
// 通话录音固件通路强制由耳机侧采集;`byPhone` 不适用,这里默认并只允许 byDevice。 |
|||
let recordType = Self.parseRecordType(args["strategy"], default: .byDevice) |
|||
print("[CallRecord] start params sr=\(sr) audioType=\(aType.rawValue) recordType=\(recordType.rawValue)") |
|||
|
|||
sampleRate = sr |
|||
audioType = aType |
|||
self.session = s |
|||
|
|||
// 立体声 OPUS 解码:单帧 80 B(16k/16bit/stereo/20ms)。 |
|||
if aType == .OPUS { |
|||
decoder = OpusStreamDecoder(channels: 2, dataSize: 80, sampleRate: sr) { [weak self] stereoPcm in |
|||
self?.emitStereo(stereoPcm) |
|||
} onError: { [weak self] code, msg in |
|||
self?.emitError(code, "opus stereo decode: \(msg ?? "")") |
|||
} |
|||
} |
|||
|
|||
s.acquire(owner: self) { [weak self] tm, errMsg in |
|||
guard let self = self else { return } |
|||
guard let tm = tm else { |
|||
print("[CallRecord] acquire FAIL err=\(errMsg ?? "nil")") |
|||
self.emitError(-201, errMsg) |
|||
completion(false, errMsg); return |
|||
} |
|||
print("[CallRecord] acquire OK isSupportTranslate=\(tm.trIsSupportTranslate()) " + |
|||
"isPlayWithA2dp=\(tm.trIsPlayWithA2dp()) isWorking=\(tm.trIsWorking()) isCalling=\(tm.isCalling)") |
|||
|
|||
tm.recordtype = recordType |
|||
// JLTranslateSetModeType 枚举只显式定义到 0x06,0x07 不是合法 enum case: |
|||
// - `JLTranslateSetModeType(rawValue: 7)` 在 NS_ENUM 严格模式下会返回 nil |
|||
// - 这里直接用 KVC 写 modeType 属性,绕过 Swift 枚举校验,底层 Obj-C |
|||
// 仅存个 NSUInteger,enterMode 单测是按原值透传给设备的 |
|||
let mode = JLTranslateSetMode() |
|||
mode.dataType = aType |
|||
mode.channel = 2 |
|||
mode.sampleRate = sr |
|||
mode.setValue(NSNumber(value: 7), forKey: "modeType") |
|||
self.emitLog("CallRecord start audioType=\(aType.rawValue) sr=\(sr) recordType=\(recordType.rawValue) ch=2") |
|||
print("[CallRecord] calling trStartTranslate mode=MODE_CALL_RECORD(0x07) dataType=\(aType.rawValue) ch=2 sr=\(sr)") |
|||
tm.trStartTranslate(mode) { [weak self] status, err in |
|||
guard let self = self else { return } |
|||
print("[CallRecord] trStartTranslate completion status=\(status.rawValue) err=\(err?.localizedDescription ?? "nil")") |
|||
if status == .success || status == .sameMode { |
|||
self.working = true |
|||
NSLog("[CallRecord] ✅ 通话录音启动成功 (status=\(status.rawValue) mode=7 sr=\(sr) recordType=\(recordType.rawValue)) — 等待耳机 SCO_MIX 上行...") |
|||
self.startHeartbeat() |
|||
completion(true, nil) |
|||
} else { |
|||
NSLog("[CallRecord] ❌ 通话录音启动失败 status=\(status.rawValue) err=\(err?.localizedDescription ?? "nil")") |
|||
self.emitError(Int(status.rawValue), err?.localizedDescription ?? "trStartTranslate failed") |
|||
self.stop() |
|||
completion(false, "tm status=\(status.rawValue)") |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
public override func stop() { |
|||
stopHeartbeat() |
|||
guard let s = session, working else { working = false; return } |
|||
working = false |
|||
s.release(owner: self) |
|||
decoder?.release(); decoder = nil |
|||
NSLog("[CallRecord] 🛑 CallRecord stop (totalRxFrames=\(rxFrameCount) bytes=\(rxBytes))") |
|||
emitLog("CallRecord stop") |
|||
} |
|||
|
|||
// MARK: - Audio upstream (SDK → APP) |
|||
|
|||
public override func onReceiveAudio(_ audio: JLTranslateAudio) { |
|||
rxFrameCount &+= 1 |
|||
if rxFrameCount <= 5 || rxFrameCount % 100 == 1 { |
|||
let msg = "[CallRecord/onRx] #\(rxFrameCount) working=\(working) " + |
|||
"source=\(audio.sourceType.rawValue) audioType=\(audio.audioType.rawValue) " + |
|||
"len=\(audio.data.count) count=\(audio.count)" |
|||
NSLog("%@", msg) |
|||
emitLog(msg) |
|||
} |
|||
guard working else { return } |
|||
// mode=7 与 mode=6 一致:SDK 把上行帧 source 标成 .typeESCOMax (SOURCE_E_SCO_MIX) |
|||
guard audio.sourceType == .typeESCOMax else { |
|||
if rxFrameCount <= 5 || rxFrameCount % 100 == 1 { |
|||
emitLog("[CallRecord/onRx] drop unhandled sourceType=\(audio.sourceType.rawValue)") |
|||
} |
|||
return |
|||
} |
|||
rxBytes &+= Int64(audio.data.count) |
|||
switch audio.audioType { |
|||
case .OPUS: decoder?.feed(audio.data) |
|||
case .PCM: emitStereo(audio.data) |
|||
default: |
|||
// 其它编码暂不支持(通话录音固件只会推 OPUS/PCM),丢弃并打日志 |
|||
if rxFrameCount <= 5 || rxFrameCount % 100 == 1 { |
|||
emitLog("[CallRecord/onRx] drop unsupported audioType=\(audio.audioType.rawValue)") |
|||
} |
|||
} |
|||
} |
|||
|
|||
// MARK: - Heartbeat |
|||
|
|||
private func startHeartbeat() { |
|||
stopHeartbeat() |
|||
rxFrameCount = 0 |
|||
rxBytes = 0 |
|||
let t = DispatchSource.makeTimerSource(queue: heartbeatQueue) |
|||
t.schedule(deadline: .now() + 2, repeating: 2.0) |
|||
t.setEventHandler { [weak self] in |
|||
guard let self = self, self.working else { return } |
|||
if self.rxFrameCount == 0 { |
|||
NSLog("[CallRecord/Heartbeat] ⚠️ 通话录音运行中,但耳机上行 0 帧 — 请确认设备是否处于真实通话状态(SCO_MIX 仅在通话期间上推)。") |
|||
} else { |
|||
NSLog("[CallRecord/Heartbeat] ✓ rx=\(self.rxFrameCount) 帧 bytes=\(self.rxBytes)") |
|||
} |
|||
} |
|||
heartbeatTimer = t |
|||
t.resume() |
|||
} |
|||
|
|||
private func stopHeartbeat() { |
|||
heartbeatTimer?.cancel() |
|||
heartbeatTimer = nil |
|||
} |
|||
|
|||
// MARK: - Helpers |
|||
|
|||
private func emitStereo(_ pcm: Data) { |
|||
pushFrame( |
|||
streamId: TranslationStreams.IN_STEREO, |
|||
pcm: pcm, |
|||
format: AudioFormat(sampleRate: sampleRate, channels: 2, bitsPerSample: 16) |
|||
) |
|||
} |
|||
} |
|||
Loading…
Reference in new issue