BeautyAPI.m 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. //
  2. // BeautyAPI.m
  3. // BeautyAPi
  4. //
  5. // Created by zhaoyongqiang on 2023/5/31.
  6. //
  7. #import "BeautyAPI.h"
  8. #import <AVFoundation/AVFoundation.h>
  9. static NSString *const beautyAPIVersion = @"1.0.4";
  10. @implementation BeautyStats
  11. @end
  12. @implementation CameraConfig
  13. @end
  14. @implementation BeautyConfig
  15. @end
  16. @interface BeautyAPI ()
  17. @property (nonatomic, strong) BeautyConfig *config;
  18. @property (nonatomic, assign) CFTimeInterval preTime;
  19. @property (nonatomic, strong) NSMutableArray *statsArray;
  20. @property (nonatomic, assign) AgoraVideoRenderMode renderMode;
  21. @end
  22. #if __has_include(<AgoraRtcKit/AgoraRtcKit.h>)
  23. @interface BeautyAPI ()<AgoraVideoFrameDelegate>
  24. @end
  25. #endif
  26. @implementation BeautyAPI
  27. - (instancetype)init {
  28. if (self == [super init]) {
  29. _isFrontCamera = YES;
  30. }
  31. return self;
  32. }
  33. - (NSMutableArray *)statsArray {
  34. if (_statsArray == nil) {
  35. _statsArray = [NSMutableArray new];
  36. }
  37. return _statsArray;
  38. }
  39. - (int)initialize:(BeautyConfig *)config {
  40. if (config.cameraConfig == nil) {
  41. CameraConfig *cameraConfig = [[CameraConfig alloc] init];
  42. cameraConfig.frontMirror = MirrorMode_LOCAL_REMOTE;
  43. cameraConfig.backMirror = MirrorMode_NONE;
  44. config.cameraConfig = cameraConfig;
  45. }
  46. [LogUtil log:[NSString stringWithFormat:@"RTC Version == %@", [AgoraRtcEngineKit getSdkVersion]]];
  47. [LogUtil log:[NSString stringWithFormat:@"BeautyAPI Version == %@", [self getVersion]]];
  48. self.config = config;
  49. if (self.config.statsDuration <= 0) {
  50. self.config.statsDuration = 1;
  51. }
  52. if (config == nil) {
  53. [LogUtil log:@"缺少配置信息" level:(LogLevelError)];
  54. return -1;
  55. }
  56. if (config.beautyRender == nil) {
  57. [LogUtil log:@"beautyRender 为空" level:(LogLevelError)];
  58. return -1;
  59. }
  60. [LogUtil log:[NSString stringWithFormat:@"beautyRender == %@", config.beautyRender.description]];
  61. self.beautyRender = config.beautyRender;
  62. if (config.captureMode == CaptureModeAgora) {
  63. #if __has_include(<AgoraRtcKit/AgoraRtcKit.h>)
  64. [LogUtil log:@"captureMode == Agora"];
  65. [config.rtcEngine setVideoFrameDelegate:self];
  66. NSDictionary *dict = @{
  67. @"rtcVersion": [AgoraRtcEngineKit getSdkVersion],
  68. @"beautyRender": config.beautyRender.description,
  69. @"captureMode": @(config.captureMode),
  70. @"cameraConfig": @{
  71. @"frontMirror": @(config.cameraConfig.frontMirror),
  72. @"backMirror": @(config.cameraConfig.backMirror)
  73. }
  74. };
  75. [self rtcReportWithEvent:@"initialize" label:dict];
  76. #else
  77. [LogUtil log:@"rtc 未导入" level:(LogLevelError)];
  78. return -1;
  79. #endif
  80. } else {
  81. [LogUtil log:@"captureMode == Custom"];
  82. }
  83. [self setupMirror];
  84. return 0;
  85. }
  86. - (int)switchCamera {
  87. _isFrontCamera = !_isFrontCamera;
  88. NSDictionary *dict = @{ @"cameraPosition": @(_isFrontCamera) };
  89. [self rtcReportWithEvent:@"cameraPosition" label:dict];
  90. int res = [self.config.rtcEngine switchCamera];
  91. [self setupMirror];
  92. return res;
  93. }
  94. - (AgoraVideoMirrorMode)setupMirror {
  95. AgoraVideoMirrorMode mode = AgoraVideoMirrorModeDisabled;
  96. if (self.isFrontCamera) {
  97. if (self.config.cameraConfig.frontMirror == MirrorMode_LOCAL_ONLY || self.config.cameraConfig.frontMirror == MirrorMode_REMOTE_ONLY) {
  98. mode = AgoraVideoMirrorModeEnabled;
  99. }
  100. } else {
  101. if (self.config.cameraConfig.backMirror == MirrorMode_REMOTE_ONLY || self.config.cameraConfig.backMirror == MirrorMode_LOCAL_ONLY) {
  102. mode = AgoraVideoMirrorModeEnabled;
  103. }
  104. }
  105. [self.config.rtcEngine setLocalRenderMode:self.renderMode mirror:mode];
  106. [LogUtil log:[NSString stringWithFormat:@"AgoraVideoMirrorMode == %ld isFrontCamera == %d", mode, self.isFrontCamera]];
  107. MOLogV(@"setupMirror mode: %ld, isFrontCamera: %d", mode, self.isFrontCamera);
  108. return mode;
  109. }
  110. - (int)updateCameraConfig:(CameraConfig *)cameraConfig {
  111. self.config.cameraConfig = cameraConfig;
  112. [self setupMirror];
  113. NSDictionary *dict = @{
  114. @"cameraConfig": @{
  115. @"frontMirror": @(cameraConfig.frontMirror),
  116. @"backMirror": @(cameraConfig.backMirror)
  117. }
  118. };
  119. [self rtcReportWithEvent:@"updateCameraConfig" label:dict];
  120. return 0;
  121. }
  122. - (int)enable:(BOOL)enable {
  123. _isEnable = enable;
  124. NSDictionary *dict = @{ @"enable": @(enable) };
  125. [self rtcReportWithEvent:@"enable" label:dict];
  126. return 0;
  127. }
  128. - (int)onFrame:(CVPixelBufferRef)pixelBuffer callback:(void (^)(CVPixelBufferRef _Nonnull))callback {
  129. if (self.config.captureMode == CaptureModeAgora) {
  130. return -1;
  131. }
  132. if (pixelBuffer == nil) {
  133. return -1;
  134. }
  135. if (self.isEnable == NO && callback) {
  136. callback(pixelBuffer);
  137. return -1;
  138. }
  139. CVPixelBufferRef pb = [self.config.beautyRender onCapture:pixelBuffer];
  140. if (pb == nil) {
  141. return -1;
  142. }
  143. if (callback) {
  144. callback(pb);
  145. }
  146. return 0;
  147. }
  148. #if __has_include(<AgoraRtcKit/AgoraRtcKit.h>)
  149. - (int)setupLocalVideo:(UIView *)view renderMode:(AgoraVideoRenderMode)renderMode {
  150. self.renderMode = renderMode;
  151. AgoraRtcVideoCanvas *localCanvas = [[AgoraRtcVideoCanvas alloc] init];
  152. localCanvas.mirrorMode = [self setupMirror];
  153. localCanvas.view = view;
  154. localCanvas.renderMode = renderMode;
  155. localCanvas.uid = 0;
  156. NSDictionary *dict = @{ @"renderMode": @(renderMode) };
  157. [self rtcReportWithEvent:@"setupLocalVideo" label:dict];
  158. [LogUtil log:@"setupLocalVideoCanvas"];
  159. return [self.config.rtcEngine setupLocalVideo:localCanvas];
  160. }
  161. #endif
  162. - (int)setBeautyPreset: (BeautyPresetMode)mode {
  163. if (self.config.beautyRender == nil) {
  164. return -1;
  165. }
  166. if (mode == BeautyPresetModeCustom) {
  167. return -1;
  168. }
  169. [self.config.beautyRender setBeautyPreset];
  170. return 0;
  171. }
  172. - (int)destroy {
  173. if (self.config == nil) {
  174. return -1;
  175. }
  176. #if __has_include(<AgoraRtcKit/AgoraRtcKit.h>)
  177. [self.config.rtcEngine setVideoFrameDelegate:nil];
  178. #endif
  179. [self.config.beautyRender destroy];
  180. self.config = nil;
  181. [LogUtil log:@"destroy"];
  182. return 0;
  183. }
  184. - (void)rtcReportWithEvent: (NSString *)event label: (NSDictionary *)label {
  185. if (self.config.rtcEngine == nil) {
  186. [LogUtil log:@"rtc 不能为空" level:(LogLevelError)];
  187. return;
  188. }
  189. NSString *jsonString = [self convertToJson:label];
  190. [self.config.rtcEngine sendCustomReportMessage:@"scenarioAPI"
  191. category:[NSString stringWithFormat:@"beauty_iOS_%@",[self getVersion]]
  192. event:event
  193. label:jsonString
  194. value:0];
  195. }
  196. - (NSString *)convertToJson: (NSDictionary *)object {
  197. NSError *error = nil;
  198. NSData *jsonData = [NSJSONSerialization dataWithJSONObject:object
  199. options:0
  200. error:&error];
  201. if (error) {
  202. // 转换失败
  203. MOLogV(@"Error: %@", error.localizedDescription);
  204. return nil;
  205. }
  206. NSString *jsonString = [[NSString alloc] initWithData:jsonData
  207. encoding:NSUTF8StringEncoding];
  208. return jsonString;
  209. }
  210. - (NSString *)getVersion {
  211. return beautyAPIVersion;
  212. }
  213. #pragma mark - VideoFrameDelegate
  214. #if __has_include(<AgoraRtcKit/AgoraRtcKit.h>)
  215. - (BOOL)onCaptureVideoFrame:(AgoraOutputVideoFrame *)videoFrame sourceType:(AgoraVideoSourceType)sourceType {
  216. if (!self.isEnable) { return YES; }
  217. CFTimeInterval startTime = CACurrentMediaTime();
  218. CVPixelBufferRef pixelBuffer = [self.config.beautyRender onCapture:videoFrame.pixelBuffer];
  219. CFTimeInterval endTime = CACurrentMediaTime();
  220. if (self.config.statsEnable) {
  221. [self.statsArray addObject:@(endTime - startTime)];
  222. }
  223. videoFrame.pixelBuffer = pixelBuffer;
  224. if (self.config.eventCallback && self.preTime > 0 && self.config.statsEnable) {
  225. CFTimeInterval time = startTime - self.preTime;
  226. if (time > self.config.statsDuration && self.statsArray.count > 0) {
  227. NSArray *sortArray = [self.statsArray sortedArrayUsingComparator:^NSComparisonResult(NSNumber * _Nonnull obj1, NSNumber * _Nonnull obj2) {
  228. return obj1.doubleValue > obj2.doubleValue;
  229. }];
  230. double totalValue = 0;
  231. for (NSNumber *value in sortArray) {
  232. totalValue += value.doubleValue;
  233. }
  234. BeautyStats *stats = [[BeautyStats alloc] init];
  235. stats.minCostMs = [sortArray.firstObject doubleValue];
  236. stats.maxCostMs = [sortArray.lastObject doubleValue];
  237. stats.averageCostMs = totalValue / sortArray.count;
  238. self.config.eventCallback(stats);
  239. [self.statsArray removeAllObjects];
  240. self.preTime = startTime;
  241. [LogUtil log:[NSString stringWithFormat:@"minCostMs == %f", stats.minCostMs] level:(LogLevelInfo)];
  242. [LogUtil log:[NSString stringWithFormat:@"maxCostMs == %f", stats.maxCostMs] level:(LogLevelInfo)];
  243. [LogUtil log:[NSString stringWithFormat:@"averageCostMs == %f", stats.averageCostMs] level:(LogLevelInfo)];
  244. }
  245. }
  246. if (self.preTime <= 0) {
  247. self.preTime = startTime;
  248. }
  249. return YES;
  250. }
  251. - (AgoraVideoFormat)getVideoFormatPreference{
  252. return [self.config.beautyRender getVideoFormatPreference];
  253. }
  254. - (AgoraVideoFrameProcessMode)getVideoFrameProcessMode{
  255. return AgoraVideoFrameProcessModeReadWrite;
  256. }
  257. - (BOOL)getMirrorApplied{
  258. if (self.isFrontCamera) {
  259. return self.config.cameraConfig.frontMirror == MirrorMode_REMOTE_ONLY || self.config.cameraConfig.frontMirror == MirrorMode_LOCAL_REMOTE;
  260. }
  261. return self.config.cameraConfig.backMirror == MirrorMode_REMOTE_ONLY || self.config.cameraConfig.backMirror == MirrorMode_LOCAL_REMOTE;
  262. }
  263. - (BOOL)getRotationApplied {
  264. return NO;
  265. }
  266. - (AgoraVideoFramePosition)getObservedFramePosition {
  267. return AgoraVideoFramePositionPostCapture;
  268. }
  269. #endif
  270. @end
  271. @implementation LogUtil
  272. + (NSString *)getCurrentTime {
  273. NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
  274. formatter.dateFormat = @"yyyy-MM-dd HH:mm:ss";
  275. return [formatter stringFromDate:[NSDate date]];
  276. }
  277. + (NSString *)getLogPrefixForLevel:(LogLevel)level {
  278. switch (level) {
  279. case LogLevelInfo:
  280. return @"[INFO]";
  281. case LogLevelError:
  282. return @"[ERROR]";
  283. case LogLevelDebug:
  284. return @"[DEBUG]";
  285. default:
  286. return @"";
  287. }
  288. }
  289. + (void)log:(NSString *)message {
  290. [self log:message level:(LogLevelDebug)];
  291. }
  292. + (void)log:(NSString *)message level:(LogLevel)level {
  293. NSString *logString = [NSString stringWithFormat:@"%@ %@ %@\n",
  294. [self getCurrentTime],
  295. [self getLogPrefixForLevel:level],
  296. message];
  297. // 写入文件
  298. NSString *logFile = [NSString stringWithFormat:@"%@/agora_beautyapi.log", NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject];
  299. [self checkLogFileSizeWithPath: logFile];
  300. NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:logFile];
  301. if (fileHandle) {
  302. dispatch_async(dispatch_get_global_queue(0, 0), ^{
  303. [fileHandle seekToEndOfFile];
  304. [fileHandle writeData:[logString dataUsingEncoding:NSUTF8StringEncoding]];
  305. [fileHandle closeFile];
  306. });
  307. } else {
  308. dispatch_async(dispatch_get_global_queue(0, 0), ^{
  309. [logString writeToFile:logFile atomically:YES encoding:NSUTF8StringEncoding error:nil];
  310. });
  311. }
  312. }
  313. + (void)checkLogFileSizeWithPath: (NSString *)filePath {
  314. NSFileManager *fileManager = [NSFileManager defaultManager];
  315. NSError *error = nil;
  316. NSDictionary *fileAttributes = [fileManager attributesOfItemAtPath:filePath error:&error];
  317. if (fileAttributes) {
  318. NSNumber *fileSizeNumber = [fileAttributes objectForKey:NSFileSize];
  319. long long fileSize = [fileSizeNumber longLongValue];
  320. if (fileSize > 1024 * 1024 * 2) { // 文件大于2M
  321. [fileManager removeItemAtPath:filePath error:&error];
  322. }
  323. }
  324. }
  325. @end