美文网首页ios专题
iOS的录屏功能

iOS的录屏功能

作者: 黑暗森林的歌者 | 来源:发表于2020-01-19 17:11 被阅读0次

    iOS的录屏功能

    苹果提供了ReplayKit对屏幕进行录制,支持音频、视频,还可以对视频进行剪辑、分享。

    需要引用头文件<ReplayKit/ReplayKit.h>,通过RPScreenRecorder控制录屏。

    RPScreenRecorder

    [[RPScreenRecorder sharedRecorder] startRecordingWithHandler:^(NSError * _Nullable error) {
            
    }];
        
    [[RPScreenRecorder sharedRecorder] setMicrophoneEnabled:YES];
    

    开始录制,会请求摄像头和麦克风权限,如果用户拒绝,则无法进行录制。

    录制内容无法直接查看,需要通过RPPreviewViewController才能查看、分享和保存到相册。

    [[RPScreenRecorder sharedRecorder] stopRecordingWithHandler:^(RPPreviewViewController * _Nullable previewViewController, NSError * _Nullable error) {
            
     }];
    

    也可以自己处理数据流:

    [RPScreenRecorder sharedRecorder].delegate = self;// 设置代理
    [RPScreenRecorder sharedRecorder].microphoneEnabled = YES;// 麦克风
    [RPScreenRecorder sharedRecorder].cameraEnabled = YES; // 摄像头
    
    [[RPScreenRecorder sharedRecorder] startCaptureWithHandler:^(CMSampleBufferRef  _Nonnull sampleBuffer, RPSampleBufferType bufferType, NSError * _Nullable error) {
            previewViewController.previewControllerDelegate = self;
    } completionHandler:^(NSError * _Nullable error) {
            
    }];
    
    /* 接收到的 buffer类型,可以使用 AVAssetWriter  进行数据写入
    API_AVAILABLE(ios(10.0),tvos(10.0))
    typedef NS_ENUM(NSInteger, RPSampleBufferType) {
        RPSampleBufferTypeVideo = 1,
        RPSampleBufferTypeAudioApp,
        RPSampleBufferTypeAudioMic,
    };
    */
    

    RPScreenRecorderDelegate

    停止的时候也可以通过代理方法使用 RPPreviewViewController

    - (void)screenRecorder:(RPScreenRecorder *)screenRecorder didStopRecordingWithError:(NSError *)error previewViewController:(nullable RPPreviewViewController *)previewViewController API_DEPRECATED("No longer supported", ios(9.0, 10.0), tvos(10.0,10.0));
    - (void)screenRecorder:(RPScreenRecorder *)screenRecorder didStopRecordingWithPreviewViewController:(nullable RPPreviewViewController *)previewViewController error:(nullable NSError *)error API_AVAILABLE(ios(11.0), tvos(11.0));
    

    需要设置 RPPreviewViewController的代理。

    previewViewController.previewControllerDelegate = self;
    

    RPPreviewViewController

    - (void)previewController:(RPPreviewViewController *)previewController didFinishWithActivityTypes:(NSSet<NSString *> *)activityTypes {
        if ([activityTypes containsObject: UIActivityTypeSaveToCameraRoll]) {
            NSLog(@"保存到相册");
        }
    }
    // 根据activityType 判断选择的操作
    /*
    UIKIT_EXTERN UIActivityType const UIActivityTypePostToFacebook     API_AVAILABLE(ios(6.0)) __TVOS_PROHIBITED;
    UIKIT_EXTERN UIActivityType const UIActivityTypePostToTwitter      API_AVAILABLE(ios(6.0)) __TVOS_PROHIBITED;
    UIKIT_EXTERN UIActivityType const UIActivityTypePostToWeibo        API_AVAILABLE(ios(6.0)) __TVOS_PROHIBITED;    // SinaWeibo
    UIKIT_EXTERN UIActivityType const UIActivityTypeMessage            API_AVAILABLE(ios(6.0)) __TVOS_PROHIBITED;
    UIKIT_EXTERN UIActivityType const UIActivityTypeMail               API_AVAILABLE(ios(6.0)) __TVOS_PROHIBITED;
    UIKIT_EXTERN UIActivityType const UIActivityTypePrint              API_AVAILABLE(ios(6.0)) __TVOS_PROHIBITED;
    UIKIT_EXTERN UIActivityType const UIActivityTypeCopyToPasteboard   API_AVAILABLE(ios(6.0)) __TVOS_PROHIBITED;
    UIKIT_EXTERN UIActivityType const UIActivityTypeAssignToContact    API_AVAILABLE(ios(6.0)) __TVOS_PROHIBITED;
    UIKIT_EXTERN UIActivityType const UIActivityTypeSaveToCameraRoll   API_AVAILABLE(ios(6.0)) __TVOS_PROHIBITED;
    UIKIT_EXTERN UIActivityType const UIActivityTypeAddToReadingList   API_AVAILABLE(ios(7.0)) __TVOS_PROHIBITED;
    UIKIT_EXTERN UIActivityType const UIActivityTypePostToFlickr       API_AVAILABLE(ios(7.0)) __TVOS_PROHIBITED;
    UIKIT_EXTERN UIActivityType const UIActivityTypePostToVimeo        API_AVAILABLE(ios(7.0)) __TVOS_PROHIBITED;
    UIKIT_EXTERN UIActivityType const UIActivityTypePostToTencentWeibo API_AVAILABLE(ios(7.0)) __TVOS_PROHIBITED;
    UIKIT_EXTERN UIActivityType const UIActivityTypeAirDrop            API_AVAILABLE(ios(7.0)) __TVOS_PROHIBITED;
    UIKIT_EXTERN UIActivityType const UIActivityTypeOpenInIBooks       API_AVAILABLE(ios(9.0)) __TVOS_PROHIBITED;
    UIKIT_EXTERN UIActivityType const UIActivityTypeMarkupAsPDF        API_AVAILABLE(ios(11.0)) __TVOS_PROHIBITED;
    *
    

    extension方式录制视频

    通过Xcode的file -> new -> target创建extension

    如果录屏或直播屏幕前需要用户输入一些内容或其他操作,需要同时添加 UI Extension

    创建完成后就出现Extension的代码

    QQ20200119-153801@2x 15.54.39.png

    具体的录制功能在SampleHandler中实现,由于iOS沙盒机制的存在,extension的数据并不能直接被App使用,可以使用AppGroup进行数据共享。从而将录制的视频保存到App中。

    RPSystemBroadcastPickerView 按钮

    需要通过replaykit提供的RPSystemBroadcastPickerView 启动录屏,把RPSystemBroadcastPickerView放到需要的view上,通过点击调起Extension

    RPSystemBroadcastPickerView *broadcastPickerView = [[RPSystemBroadcastPickerView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
    broadcastPickerView.preferredExtension = kBroadcastExtensionBundleId;  // extension的id
    [self.view addSubview:broadcastPickerView];
    broadcastPickerView.center = self.view.center;
    

    SampleHandler中的视频写入操作

    @property (nonatomic, strong) AVAssetWriter *assetWriter;
    @property (nonatomic, strong) AVAssetWriterInput *assetWriterInput;
    @property (nonatomic, strong) AVAssetWriterInput *assetAudioInput;
    @property (nonatomic, strong) NSString *videoOutPath; // 当前沙盒保存视频的路径
    
    @property (nonatomic, assign) BOOL  isFirstSample; // 是否第一帧数据
    
    - (void)broadcastStartedWithSetupInfo:(NSDictionary<NSString *,NSObject *> *)setupInfo {
        // 视频开始录制时调用
      // info中可以是从 UI 中传递过来的参数
    }
    
     // 定义 AVAssetWriter
    NSNumber *width= [NSNumber numberWithFloat:[[UIScreen mainScreen] bounds].size.width];
    NSNumber *height = [NSNumber numberWithFloat:[[UIScreen mainScreen] bounds].size.height];
    // 视频参数
    
    NSDictionary *compressionProperties =
        @{AVVideoProfileLevelKey         : AVVideoProfileLevelH264HighAutoLevel,
          AVVideoH264EntropyModeKey      : AVVideoH264EntropyModeCABAC,
          AVVideoAverageBitRateKey       : @(1920 * 1080 * 11.4),
          AVVideoMaxKeyFrameIntervalKey  : @30,
          AVVideoAllowFrameReorderingKey : @NO};
    
    NSDictionary *videoSettings =
        @{
            AVVideoCompressionPropertiesKey : compressionProperties,
            AVVideoCodecKey                 : AVVideoCodecTypeH264,
            AVVideoWidthKey                 : @(width),
            AVVideoHeightKey                : @(height)
        };
    // 音频参数
    AudioChannelLayout acl;
        bzero( &acl, sizeof(acl));
        acl.mChannelLayoutTag = kAudioChannelLayoutTag_Mono;
    
    NSDictionary *audioOutputSettings = [NSDictionary dictionaryWithObjectsAndKeys:
                                             [NSNumber numberWithInt: kAudioFormatMPEG4AAC ], AVFormatIDKey,
                                             [NSNumber numberWithInt: 1 ], AVNumberOfChannelsKey,
                                             [NSNumber numberWithFloat: 44100.0 ], AVSampleRateKey,
                                             [NSNumber numberWithInt: 64000 ], AVEncoderBitRateKey,
                                             [NSData dataWithBytes: &acl length: sizeof( acl ) ], AVChannelLayoutKey,
                                             nil];
    // 定义 writer
    self.assetWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:videoSettings];
    self.assetWriterInput.expectsMediaDataInRealTime = YES;
    
    self.assetAudioInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio outputSettings:audioOutputSettings];
    self.assetAudioInput.expectsMediaDataInRealTime = YES;
    
    self.assetWriter = [AVAssetWriter assetWriterWithURL:[NSURL fileURLWithPath:self.videoOutPath] fileType:AVFileTypeMPEG4 error:nil];    
    [self.assetWriter addInput:self.assetWriterInput];    
    [self.assetWriter addInput:self.assetAudioInput];
    [self.assetWriter setMovieTimeScale:60];
    //  开始写入视频
    [self.assetWriter startWriting];
    
    // 暂停和继续录屏
    - (void)broadcastPaused {
        // User has requested to pause the broadcast. Samples will stop being delivered.
        
    }
    
    - (void)broadcastResumed {
        // User has requested to resume the broadcast. Samples delivery will resume.
        
    }
    
    // 收到录屏的 buffer
    - (void)processSampleBuffer:(CMSampleBufferRef)sampleBuffer withType:(RPSampleBufferType)sampleBufferType {
        
        switch (sampleBufferType) {
            case RPSampleBufferTypeVideo:
                // Handle video sample buffer for app audio
            {
              // 如果是第一帧数据,判断数据类型,如果不是 video,则废弃,否则会出现视频开头是黑屏
                if (self.isFirstSample) {
                    
                    [self.assetWriter startSessionAtSourceTime:CMSampleBufferGetPresentationTimeStamp(sampleBuffer)];
                    
                    self.isFirstSample = NO;
                }
                
                if (CMSampleBufferIsValid(sampleBuffer) && self.assetWriterInput.readyForMoreMediaData) {
                    [self.assetWriterInput appendSampleBuffer:sampleBuffer];
                }
            }
                break;
            case RPSampleBufferTypeAudioApp:
                // Handle audio sample buffer for app audio
                
                break;
            case RPSampleBufferTypeAudioMic:
                // Handle audio sample buffer for mic audio
            {
                if (CMSampleBufferIsValid(sampleBuffer) && self.assetAudioInput.readyForMoreMediaData) {
                    [self.assetAudioInput appendSampleBuffer:sampleBuffer];
                }
            }
                break;
            default:
                break;
        }
    }
    
    
    // 完成录屏
    - (void)broadcastFinished {
        // User has requested to finish the broadcast.
        // 定义一个标志,标记 assetWriter 是否完成了写入
        // 也可以使用 dispatchgroup
        __block BOOL finish = NO;    
        [self.assetWriterInput markAsFinished];
        [self.assetAudioInput markAsFinished];
        [self.assetWriter finishWritingWithCompletionHandler:^{
            self.assetWriterInput = nil;
            self.assetAudioInput = nil;
            self.assetWriter = nil;
            
            // [self moveVideo]; // 把视频数据保存到appgroup共享
            // [self sendLocalNotification]; // 发送 一个本地通知
            
            finish = YES;
        }];
        
        while (finish == NO) {
        }
    }
    

    AppGroup共享数据

    - (void)moveVideo {
        NSURL *groupURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"定义的 appgroup 的 id"];
        NSURL *fileURL = [groupURL URLByAppendingPathComponent:@"record.mp4"];
        [[NSFileManager defaultManager] removeItemAtURL:fileURL error:nil];
        
        [[NSFileManager defaultManager] moveItemAtPath:self.videoOutPath toPath:[fileURL path] error:nil];    
    }
    

    通过通知回调到app

    //  identifier 是通知到id,在app的代码中,正常监听这个id 的通知就行
    - (void)sendNotificationForMessageWithIdentifier:(nullable NSString *)identifier userInfo:(NSDictionary *)info {
        CFNotificationCenterRef const center = CFNotificationCenterGetDarwinNotifyCenter();
        CFDictionaryRef userInfo = (__bridge CFDictionaryRef)info;
        BOOL const deliverImmediately = YES;
        CFStringRef identifierRef = (__bridge CFStringRef)identifier;
        CFNotificationCenterPostNotification(center, identifierRef, NULL, userInfo, deliverImmediately);
    }
    

    接收录制完成的通知

    定义CFNotification回调

    void NotificationCallback(CFNotificationCenterRef center,
                                    void * observer,
                                    CFStringRef name,
                                    void const * object,
                                    CFDictionaryRef userInfo) {
        NSString *identifier = (__bridge NSString *)name;
        NSObject *sender = (__bridge NSObject *)observer;
        
        NSDictionary *notionUserInfo = @{@"identifier":identifier};
        [[NSNotificationCenter defaultCenter] postNotificationName:@"ScreenNotificationName"
                                                            object:sender
                                                          userInfo:notionUserInfo];
    }
    

    注册通知监听

    CFNotificationCenterRef const center = CFNotificationCenterGetDarwinNotifyCenter();
        CFStringRef str = (__bridge CFStringRef)identifier;
        CFNotificationCenterAddObserver(center,
                                        (__bridge const void *)(self),
                                        NotificationCallback,
                                        str,
                                        NULL,
                                        CFNotificationSuspensionBehaviorDeliverImmediately);
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(broadcastInfo:) name:@"ScreenNotificationName" object:nil];
    

    收到通知后

    - (void)broadcastInfo:(NSNotification *)notion {
        NSDictionary *userInfo = notion.userInfo;
        NSString *identifier = userInfo[@"identifier"];
      // 从 appgroup 中获取保存的视频地址
        NSURL *groupURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:appGroupId];
        NSURL *fileURL = [groupURL URLByAppendingPathComponent:@"record.mp4"];
    }
    

    注意

    系统提供录制进程的内存空间约为50M,超过50M进程将会被停止

    采集到数据结构中的yuv的缓存空间,不能占用

    使用 AppDelegate 的代理判断是否锁屏

    相关文章

      网友评论

        本文标题:iOS的录屏功能

        本文链接:https://www.haomeiwen.com/subject/wonrzctx.html