iOS屏幕录制实现

博客 分享
0 345
张三
张三 2022-02-16 12:55:24
悬赏:0 积分 收藏

iOS 屏幕录制实现

iOS 屏幕录制实现

目录
  • iOS 屏幕录制实现
    • 录屏API版本变化
    • App内部录制屏幕
      • 录音麦克风声音
    • App内部录屏直播
      • Bonjour
      • APP广播端实现
      • 广播端App(直播平台)的实现
    • iOS12可在app里手动触发录屏
    • 录屏文件数据的共享
    • iOS14
    • 保存视频到相册

录屏API版本变化

  • 主要使用iOS系统的Airplay功能和ReplayKit库实现屏幕录制
  • iOS9开始,苹果新增了 ReplayKit 框架,使用该框架中的API进行录屏,该功能只能录制应用内屏幕,且无法操作视频/音频流,最终只能在预览页面进行“保存”、“拷贝”、“分享”等操作。
  • 从iOS 10开始,苹果新增了录制系统屏幕的API,即应用即使退出前台也能持续录制,以下称为“系统屏幕录制”,区分于“应用屏幕录制”。
  • iOS 11官方开放了应用内录屏的流数据处理API,即可直接操作视频流、音频流,而不是只能预览、保存、分享。
  • 对于录制系统内容,iOS11不允许开发直接调用api来启动系统界别的录制,必须是用户通过手动启动.用户点击进入手机设置页面-> 控制中心-> 自定义 , 找到屏幕录制的功能按钮,将其添加到上方:添加成功
  • 在iOS 12.0+上出现了一个新的UI控件RPSystemBroadcastPickerView,用于展示用户启动系统录屏的指定视图.可以在App界面手动出发录屏

App内部录制屏幕

  • 从App内部录制屏幕,不支持系统界面。只能录制App。
  • 关键类 RPScreenRecorder

录音麦克风声音

  • 首先开启麦克风权限,添加相关配置plist
////  ViewController//////  Created by song on 2022/01/13.//  Copyright ? 2022 song. All rights reserved.#import "MainViewController.h"#import <ReplayKit/ReplayKit.h>#import <AVFoundation/AVFoundation.h>#import "SystemScreenRecordController.h"@interface MainViewController ()<RPScreenRecorderDelegate,RPPreviewViewControllerDelegate>@end@implementation MainViewController-(void)viewDidLoad{    [super viewDidLoad];    self.view.backgroundColor = [UIColor whiteColor];    [self setupUI];    [self setupScreen];}- (void)setupScreen{    AVAuthorizationStatus microPhoneStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio];      switch (microPhoneStatus) {          case AVAuthorizationStatusDenied:          case AVAuthorizationStatusRestricted:          {              // 被拒绝              [self goMicroPhoneSet];          }              break;          case AVAuthorizationStatusNotDetermined:          {              // 没弹窗              [self requestMicroPhoneAuth];          }              break;          case AVAuthorizationStatusAuthorized:          {              // 有授权          }              break;          default:              break;      }    }-(void) goMicroPhoneSet{    UIAlertController * alert = [UIAlertController alertControllerWithTitle:@"您还没有允许麦克风权限" message:@"去设置一下吧" preferredStyle:UIAlertControllerStyleAlert];    UIAlertAction * cancelAction = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {    }];    UIAlertAction * setAction = [UIAlertAction actionWithTitle:@"去设置" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {        dispatch_async(dispatch_get_main_queue(), ^{            NSURL * url = [NSURL URLWithString:UIApplicationOpenSettingsURLString];            [UIApplication.sharedApplication openURL:url options:nil completionHandler:^(BOOL success) {            }];        });    }];    [alert addAction:cancelAction];    [alert addAction:setAction];    [self presentViewController:alert animated:YES completion:nil];}-(void) requestMicroPhoneAuth{    [AVCaptureDevice requestAccessForMediaType:AVMediaTypeAudio completionHandler:^(BOOL granted) {    }];}- (void)setupUI{    self.title= @"录屏Demo";    self.navigationController.navigationBar.tintColor=[UIColor whiteColor];    self.navigationController.navigationBar.barTintColor = [UIColor greenColor];    self.navigationController.navigationBar.barStyle = UIBarStyleBlack;    [self.navigationController.navigationBar setTitleTextAttributes:@{NSForegroundColorAttributeName:[UIColor whiteColor],NSFontAttributeName:[UIFont systemFontOfSize:25]}];        UIBarButtonItem *leftBar = [[UIBarButtonItem alloc ] initWithTitle:@"开始录屏" style:UIBarButtonItemStylePlain target:self action:@selector(start)];    UIBarButtonItem *playBtn = [[UIBarButtonItem alloc] initWithTitle:@"结束录屏" style:UIBarButtonItemStylePlain target:self action:@selector(stop)];        self.navigationItem.rightBarButtonItem = playBtn;        self.navigationItem.leftBarButtonItem = leftBar;        UIButton *btn1 =  [UIButton buttonWithType:UIButtonTypeSystem];    btn1.frame = CGRectMake(110, 100, 100, 33);    btn1.backgroundColor = [UIColor redColor];    [btn1 setTitle:@"点我啊" forState:UIControlStateNormal];    [btn1 addTarget:self action:@selector(systemBtnClick) forControlEvents:UIControlEventTouchUpInside];    [self.view addSubview:btn1];}- (void)systemBtnClick{    SystemScreenRecordController *vc = [[SystemScreenRecordController alloc] init];    vc.hidesBottomBarWhenPushed = YES;    [self.navigationController pushViewController:vc animated:YES];}- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{    NSLog(@"keyPath:%@,change:%@",keyPath,change);    if ([keyPath isEqualToString:@"available"] && [change[@"new"] integerValue] == 1) {        [self start];    }}- (void)checkout{        if (@available(iOS 9.0, *)) {        if ([RPScreenRecorder sharedRecorder].available) {            NSLog(@"可以录屏");            [self start];                    }else{            NSLog(@"未授权");            [[RPScreenRecorder sharedRecorder] addObserver:self forKeyPath:@"available" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];        }    } else {        NSLog(@"不支持录屏");    }}- (void)start{    if ([RPScreenRecorder sharedRecorder].recording) {        NSLog(@"录制中...");    }else{        NSLog(@"1---[RPScreenRecorder sharedRecorder].microphoneEnabled:%d",[RPScreenRecorder sharedRecorder].microphoneEnabled);        if(![RPScreenRecorder sharedRecorder].microphoneEnabled){            [[RPScreenRecorder sharedRecorder] setMicrophoneEnabled:YES];        }        NSLog(@"2---[RPScreenRecorder sharedRecorder].microphoneEnabled:%d",[RPScreenRecorder sharedRecorder].microphoneEnabled);        [RPScreenRecorder sharedRecorder].delegate = self;        if (@available(iOS 11.0, *)) {            [[RPScreenRecorder sharedRecorder] startCaptureWithHandler:^(CMSampleBufferRef  _Nonnull sampleBuffer, RPSampleBufferType bufferType, NSError * _Nullable error) {                NSLog(@"拿到流,可以直播推流");                switch (bufferType) {                    case RPSampleBufferTypeAudioApp:                        NSLog(@"内部音频流");                        break;                    case RPSampleBufferTypeVideo:                        NSLog(@"内部视频流");                        break;                    case RPSampleBufferTypeAudioMic:                        NSLog(@"麦克风音频");                        break;                    default:                        break;                }            } completionHandler:^(NSError * _Nullable error) {                NSLog(@"startCaptureWithHandler completionHandler");                if (error) {                                    }else{                                    }            }];        }        else if (@available(iOS 10.0, *)) {            [[RPScreenRecorder sharedRecorder] startRecordingWithHandler:^(NSError * _Nullable error) {                NSLog(@"startRecordingWithHandler:%@",error);            }];        } else if(@available(iOS 9.0, *))  {            [[RPScreenRecorder sharedRecorder] startRecordingWithMicrophoneEnabled:YES handler:^(NSError * _Nullable error) {                NSLog(@"startRecordingWithMicrophoneEnabled:%@",error);            }];        }        }        }- (void)stop{    if ([RPScreenRecorder sharedRecorder].recording) {        [[RPScreenRecorder sharedRecorder] stopRecordingWithHandler:^(RPPreviewViewController * _Nullable previewViewController, NSError * _Nullable error) {            NSLog(@"stopRecordingWithHandler");            if (!error) {                previewViewController.previewControllerDelegate = self;                [self presentViewController:previewViewController animated:YES completion:nil];            }        }];    }}#pragma mark - RPScreenRecorderDelegate- (void)screenRecorder:(RPScreenRecorder *)screenRecorder didStopRecordingWithPreviewViewController:(RPPreviewViewController *)previewViewController error:(NSError *)error /*API_AVAILABLE(ios(11.0)*/{        if(@available(iOS 11.0,*)){        NSLog(@"didStopRecordingWithPreviewViewController: %@",error);    }}-(void)screenRecorderDidChangeAvailability:(RPScreenRecorder *)screenRecorder{    NSLog(@"screenRecorderDidChangeAvailability:%@",screenRecorder);}- (void)screenRecorder:(RPScreenRecorder *)screenRecorder didStopRecordingWithError:(NSError *)error previewViewController:(RPPreviewViewController *)previewViewController{    if(@available(iOS 9.0,*)){        NSLog(@"didStopRecordingWithError :%@",error);    }}#pragma mark - RPPreviewViewControllerDelegate- (void)previewControllerDidFinish:(RPPreviewViewController *)previewController{    NSLog(@"previewControllerDidFinish");    [previewController dismissViewControllerAnimated:YES completion:nil];    }- (void)previewController:(RPPreviewViewController *)previewController didFinishWithActivityTypes:(NSSet<NSString *> *)activityTypes{    NSLog(@"didFinishWithActivityTypes:%@",activityTypes);}@end

App内部录屏直播

Bonjour

  • Bonjour 是 Apple 基于标准的网络技术,旨在帮助设备和服务在同一网络上发现彼此。例如,iPhone 和 iPad 设备使用 Bonjour 发现兼容“隔空打印”的打印机,iPhone 和 iPad 设备以及 Mac 电脑使用 Bonjour 发现兼容“隔空播放”的设备(如 Apple TV).

  • Bonjour

  • 由于bonjour服务是开源的,且iOS系统提供底层API库:DNS-SD,去实现此功能。

  • Bonjour服务一般用于发布服务全局广播,但如果服务不想被其它机器知道,只有制定机器知道,如何实现:

    • 1、客户端与服务器通信,等到服务器的服务ip地址,端口号
    • 2、客户端本地创建服务结点,并连接
  • 参考

  • 参考

APP广播端实现

- 被录制端需要在原有功能的基础上,增加一个唤起广播的入口。- 点击直播会出现直播App选择(实现了ReplayKit Live的APP)- ![](https://tva1.sinaimg.cn/large/008i3skNgy1gs7adt8fqij30u01szkjl.jpg)
////  SystemScreenRecordController.m//  SLQDemo////  Created by song on 2022/01/6.//  Copyright ? 2022 了. All rights reserved.//#import "SystemScreenRecordController.h"#import <ReplayKit/ReplayKit.h>@interface SystemScreenRecordController ()<RPBroadcastActivityViewControllerDelegate,RPBroadcastControllerDelegate>@end@implementation SystemScreenRecordController- (void)viewDidLoad {    [super viewDidLoad];    // Do any additional setup after loading the view.    self.view.backgroundColor = [UIColor greenColor];    UIButton *btn1 =  [UIButton buttonWithType:UIButtonTypeSystem];    btn1.frame = CGRectMake(110, 100, 100, 33);    btn1.backgroundColor = [UIColor redColor];    [btn1 setTitle:@"点我啊" forState:UIControlStateNormal];    [btn1 addTarget:self action:@selector(systemBtnClick) forControlEvents:UIControlEventTouchUpInside];    [self.view addSubview:btn1];}- (void)systemBtnClick {    [self setupUI];}- (void)setupUI {        if (@available(iOS 10.0, *)) {        [RPBroadcastActivityViewController loadBroadcastActivityViewControllerWithHandler:^(RPBroadcastActivityViewController * _Nullable broadcastActivityViewController, NSError * _Nullable error) {            if (error) {                NSLog(@"loadBroadcastActivityViewControllerWithHandler:%@",error);            }else{                broadcastActivityViewController.delegate = self;                broadcastActivityViewController.modalPresentationStyle = UIModalPresentationPopover;                [self presentViewController:broadcastActivityViewController animated:YES completion:nil];            }        }];    } else {        NSLog(@"不支持录制系统屏幕");    } }#pragma mark - RPBroadcastActivityViewControllerDelegate- (void)broadcastActivityViewController:(RPBroadcastActivityViewController *)broadcastActivityViewController didFinishWithBroadcastController:(RPBroadcastController *)broadcastController error:(NSError *)error{    NSLog(@"broadcastActivityViewController: didFinishWithBroadcastController:");    dispatch_async(dispatch_get_main_queue(), ^{        [broadcastActivityViewController dismissViewControllerAnimated:YES completion:nil];    });        NSLog(@"Boundle id :%@",broadcastController.broadcastURL);        if (error) {        NSLog(@"BAC: %@ didFinishWBC: %@, err: %@",                   broadcastActivityViewController,                   broadcastController,                   error);             return;    }    [broadcastController startBroadcastWithHandler:^(NSError * _Nullable error) {        if (error) {            NSLog(@"startBroadcastWithHandler:%@",error);        }else{            NSLog(@"startBroadcast success");        }    }];}- (void)broadcastController:(RPBroadcastController *)broadcastController didUpdateServiceInfo:(NSDictionary<NSString *,NSObject<NSCoding> *> *)serviceInfo{    NSLog(@"didUpdateServiceInfo:%@",serviceInfo);}@end

广播端App(直播平台)的实现

  • 新增对 ReplayKit Live 的支持,只需要创建两个扩展的 target,分别是 Broadcast UI Extension 和 Broadcast Upload Extension
////  SampleHandler.m//  broadcast////  Created by song on 2022/01/6.//  Copyright ? 2022 了. All rights reserved.//#import "SampleHandler.h"@implementation SampleHandler- (void)broadcastStartedWithSetupInfo:(NSDictionary<NSString *,NSObject *> *)setupInfo {    // User has requested to start the broadcast. Setup info from the UI extension can be supplied but optional.    NSLog(@"启动广播");    }- (void)broadcastPaused {    // User has requested to pause the broadcast. Samples will stop being delivered.    NSLog(@"暂停广播");}- (void)broadcastResumed {    // User has requested to resume the broadcast. Samples delivery will resume.    NSLog(@"恢复广播");}- (void)broadcastFinished {    // User has requested to finish the broadcast.    NSLog(@"完成广播");}- (void)processSampleBuffer:(CMSampleBufferRef)sampleBuffer withType:(RPSampleBufferType)sampleBufferType {        switch (sampleBufferType) {        case RPSampleBufferTypeVideo:            // Handle video sample buffer            // 得到YUV数据            NSLog(@"视频流");            break;        case RPSampleBufferTypeAudioApp:            // Handle audio sample buffer for app audio            // 处理app音频            NSLog(@"App音频流");            break;        case RPSampleBufferTypeAudioMic:            // Handle audio sample buffer for mic audio            // 处理麦克风音频            NSLog(@"麦克风音频流");            break;                    default:            break;    }}@end
  • 实现录屏信息的界面,可以设置一下标题什么的
////  BroadcastSetupViewController.m//  broadcastSetupUI////  Created by song on 2022/01/07.//  Copyright ? 2022 了. All rights reserved.//#import "BroadcastSetupViewController.h"@implementation BroadcastSetupViewController- (void)viewDidLoad{    [super viewDidLoad];    NSLog(@"BroadcastSetupViewController");    self.view.backgroundColor = [UIColor redColor];    UIButton *btn1 =  [UIButton buttonWithType:UIButtonTypeSystem];    btn1.frame = CGRectMake(110, 100, 200, 33);    btn1.backgroundColor = [UIColor redColor];    [btn1 setTitle:@"点我开始直播" forState:UIControlStateNormal];    [btn1 addTarget:self action:@selector(systemBtnClick) forControlEvents:UIControlEventTouchUpInside];    [self.view addSubview:btn1];        UIButton *btn2 =  [UIButton buttonWithType:UIButtonTypeSystem];    btn2.frame = CGRectMake(110, 200, 200, 33);    btn2.backgroundColor = [UIColor redColor];    [btn2 setTitle:@"取消直播" forState:UIControlStateNormal];    [btn2 addTarget:self action:@selector(stop) forControlEvents:UIControlEventTouchUpInside];    [self.view addSubview:btn2];}- (void)systemBtnClick {    NSLog(@"开始直播");    [self userDidFinishSetup];}- (void)stop {    [self userDidCancelSetup];}// Call this method when the user has finished interacting with the view controller and a broadcast stream can start- (void)userDidFinishSetup {    NSLog(@"userDidFinishSetup");    // URL of the resource where broadcast can be viewed that will be returned to the application    NSURL *broadcastURL = [NSURL URLWithString:@"http://apple.com/broadcast/test1"];        // Dictionary with setup information that will be provided to broadcast extension when broadcast is started    NSDictionary *setupInfo = @{ @"broadcastName" : @"App live" };        // Tell ReplayKit that the extension is finished setting up and can begin broadcasting    [self.extensionContext completeRequestWithBroadcastURL:broadcastURL setupInfo:setupInfo];}- (void)userDidCancelSetup {    // Tell ReplayKit that the extension was cancelled by the user    NSLog(@"userDidCancelSetup");    [self.extensionContext cancelRequestWithError:[NSError errorWithDomain:@"YourAppDomain" code:-1 userInfo:nil]];}@end
  • 注意
iOS10只支持app内容录制,所以当app切到后台,录制内容将停止;手机锁屏时,录制进程将停止;这几个方法中的代码不能阻塞(例如写文件等慢操作),否则导致录制进程停止;

iOS12可在app里手动触发录屏

  • 在iOS 12.0+上出现了一个新的UI控件RPSystemBroadcastPickerView,用于展示用户启动系统录屏的指定视图.
  if (@available(iOS 12.0, *)) {        self.broadPickerView = [[RPSystemBroadcastPickerView alloc] initWithFrame:CGRectMake(110, 100, 100, 100)];        self.broadPickerView.preferredExtension = @"com.ask.answer.live.boradcastr";// nil的话列出所有可录屏的App        [self.view addSubview:self.broadPickerView];    }
  • 添加以上代码后,就会多出一个黑色按钮,点击就会弹出录制界面

录屏文件数据的共享

  • 每个Extension都需要一个宿主App,并且有自己的沙盒,当我们把录屏文件保存到沙盒中时宿主App是无法获取到的,那么只有采用共享的方式才能让宿主App拿到录屏文件。
  • App Group Share帮我们解决了这个问题,通过设置组间共享的模式,使得同一个Group下面的App可以共享资源,解决了沙盒的限制。

iOS14

  • 新增录制视频保存之URL的API,可直接保存到相册,保存到沙盒等
- (void)saveVideoWithUrl:(NSURL *)url {    PHPhotoLibrary *photoLibrary = [PHPhotoLibrary sharedPhotoLibrary];    [photoLibrary performChanges:^{        [PHAssetChangeRequest creationRequestForAssetFromVideoAtFileURL:url];            } completionHandler:^(BOOL success, NSError * _Nullable error) {        if (success) {            NSLog(@"已将视频保存至相册");        } else {            NSLog(@"未能保存视频到相册");        }    }];}- (void)stop{    if ([RPScreenRecorder sharedRecorder].recording) {                if (@available(iOS 14.0, *)) {            __weak typeof(self) weakSelf = self;            NSString *cachesDir = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory,NSUserDomainMask,YES) firstObject];            NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@/test.mp4",cachesDir]];            [[RPScreenRecorder sharedRecorder] stopRecordingWithOutputURL:url  completionHandler:^(NSError * _Nullable error) {                NSLog(@"stopRecordingWithOutputURL:%@",url);                [weakSelf saveVideoWithUrl:url];                           }];        } else {            [[RPScreenRecorder sharedRecorder] stopRecordingWithHandler:^(RPPreviewViewController * _Nullable previewViewController, NSError * _Nullable error) {                NSLog(@"stopRecordingWithHandler");                if (!error) {                    previewViewController.previewControllerDelegate = self;                    [self presentViewController:previewViewController animated:YES completion:nil];                }            }];        }      }}

保存视频到相册

  • 预览视频可通过AVPlayerViewController预览视频

  • 也可以直接保存到相册

  • SampleHandler数据流回调里处理视频

  • 通过AppGroup和宿主app共享数据

////  SampleHandler.m//  broadcast////  Created by song on 2022/01/6.//  Copyright ? 2022 了. All rights reserved.//#import "SampleHandler.h"#import <AVFoundation/AVFoundation.h>@interface NSDate (Timestamp)+ (NSString *)timestamp;@end @implementation NSDate (Timestamp)+ (NSString *)timestamp {    long long timeinterval = (long long)([NSDate timeIntervalSinceReferenceDate] * 1000);    return [NSString stringWithFormat:@"%lld", timeinterval];}@end@interface SampleHandler()@property (nonatomic,strong) AVAssetWriter *assetWriter;@property (nonatomic,strong) AVAssetWriterInput *videoInput;@property (nonatomic,strong) AVAssetWriterInput *audioInput;@end@implementation SampleHandler- (void)broadcastStartedWithSetupInfo:(NSDictionary<NSString *,NSObject *> *)setupInfo {    // User has requested to start the broadcast. Setup info from the UI extension can be supplied but optional.    NSLog(@"启动广播:%@",setupInfo);    [self initData];}- (NSString *)getDocumentPath {        static NSString *replaysPath;    if (!replaysPath) {        NSFileManager *fileManager = [NSFileManager defaultManager];        NSURL *documentRootPath = [fileManager containerURLForSecurityApplicationGroupIdentifier:@"group.com.ask.answer.live"];        replaysPath = [documentRootPath.path stringByAppendingPathComponent:@"Replays"];        if (![fileManager fileExistsAtPath:replaysPath]) {            NSError *error_createPath = nil;            BOOL success_createPath = [fileManager createDirectoryAtPath:replaysPath withIntermediateDirectories:true attributes:@{} error:&error_createPath];            if (success_createPath && !error_createPath) {                NSLog(@"%@路径创建成功!", replaysPath);            } else {                NSLog(@"%@路径创建失败:%@", replaysPath, error_createPath);            }        }else{            NSLog(@"%@路径已存在!", replaysPath);        }    }    return replaysPath;}- (NSURL *)getFilePathUrl {    NSString *time = [NSDate timestamp];    NSString *fileName = [time stringByAppendingPathExtension:@"mp4"];    NSString *fullPath = [[self getDocumentPath] stringByAppendingPathComponent:fileName];    return [NSURL fileURLWithPath:fullPath];}- (NSArray <NSURL *> *)fetechAllResource {    NSFileManager *fileManager = [NSFileManager defaultManager];        NSString *documentPath = [self getDocumentPath];    NSURL *documentURL = [NSURL fileURLWithPath:documentPath];    NSError *error = nil;    NSArray<NSURL *> *allResource  =  [fileManager contentsOfDirectoryAtURL:documentURL includingPropertiesForKeys:@[] options:(NSDirectoryEnumerationSkipsSubdirectoryDescendants) error:&error];    return allResource;    }- (void)initData {    if ([self.assetWriter canAddInput:self.videoInput]) {        [self.assetWriter addInput:self.videoInput];    }else{        NSLog(@"添加input失败");    }}- (AVAssetWriter *)assetWriter{    if (!_assetWriter) {        NSError *error = nil;        _assetWriter = [[AVAssetWriter alloc] initWithURL:[self getFilePathUrl] fileType:(AVFileTypeMPEG4) error:&error];        NSAssert(!error, @"_assetWriter 初始化失败");    }    return _assetWriter;}-(AVAssetWriterInput *)audioInput{    if (!_audioInput) {        // 音频参数        NSDictionary *audioCompressionSettings = @{            AVEncoderBitRatePerChannelKey:@(28000),            AVFormatIDKey:@(kAudioFormatMPEG4AAC),            AVNumberOfChannelsKey:@(1),            AVSampleRateKey:@(22050)        };        _audioInput  = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio outputSettings:audioCompressionSettings];    }    return _audioInput;}-(AVAssetWriterInput *)videoInput{    if (!_videoInput) {                CGSize size = [UIScreen mainScreen].bounds.size;        // 视频大小        NSInteger numPixels = size.width * size.height;        // 像素比        CGFloat bitsPerPixel = 7.5;        NSInteger bitsPerSecond = numPixels * bitsPerPixel;        // 码率和帧率设置        NSDictionary *videoCompressionSettings = @{            AVVideoAverageBitRateKey:@(bitsPerSecond),//码率            AVVideoExpectedSourceFrameRateKey:@(25),// 帧率            AVVideoMaxKeyFrameIntervalKey:@(15),// 关键帧最大间隔            AVVideoProfileLevelKey:AVVideoProfileLevelH264BaselineAutoLevel,            AVVideoPixelAspectRatioKey:@{                    AVVideoPixelAspectRatioVerticalSpacingKey:@(1),                    AVVideoPixelAspectRatioHorizontalSpacingKey:@(1)            }        };        CGFloat scale = [UIScreen mainScreen].scale;                // 视频参数        NSDictionary *videoOutputSettings = @{            AVVideoCodecKey:AVVideoCodecTypeH264,            AVVideoScalingModeKey:AVVideoScalingModeResizeAspectFill,            AVVideoWidthKey:@(size.width*scale),            AVVideoHeightKey:@(size.height*scale),            AVVideoCompressionPropertiesKey:videoCompressionSettings        };                _videoInput  = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:videoOutputSettings];        _videoInput.expectsMediaDataInRealTime = true;    }    return _videoInput;}- (void)broadcastPaused {    // User has requested to pause the broadcast. Samples will stop being delivered.    NSLog(@"暂停广播");    [self stopRecording];}- (void)broadcastResumed {    // User has requested to resume the broadcast. Samples delivery will resume.    NSLog(@"恢复广播");    [self stopRecording];}- (void)broadcastFinished {    // User has requested to finish the broadcast.    NSLog(@"完成广播");    [self stopRecording];}- (void)processSampleBuffer:(CMSampleBufferRef)sampleBuffer withType:(RPSampleBufferType)sampleBufferType {    switch (sampleBufferType) {        case RPSampleBufferTypeVideo:            // Handle video sample buffer            // 得到YUV数据            NSLog(@"视频流");            AVAssetWriterStatus status = self.assetWriter.status;            if (status == AVAssetWriterStatusFailed || status == AVAssetWriterStatusCompleted || status == AVAssetWriterStatusCancelled) {                return;            }            if (status == AVAssetWriterStatusUnknown) {                [self.assetWriter startWriting];                CMTime time = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);                [self.assetWriter startSessionAtSourceTime:time];                            }            if (status == AVAssetWriterStatusWriting ) {                if (self.videoInput.isReadyForMoreMediaData) {                    BOOL success = [self.videoInput appendSampleBuffer:sampleBuffer];                    if (!success) {                        [self stopRecording];                    }                }            }            break;        case RPSampleBufferTypeAudioApp:            // Handle audio sample buffer for app audio            // 处理app音频            NSLog(@"App音频流");            break;        case RPSampleBufferTypeAudioMic:            // Handle audio sample buffer for mic audio            // 处理麦克风音频            NSLog(@"麦克风音频流");            if (self.audioInput.isReadyForMoreMediaData) {                BOOL success = [self.audioInput appendSampleBuffer:sampleBuffer];                if (!success) {                    [self stopRecording];                }            }            break;                    default:            break;    }}- (void)stopRecording {//    if (self.assetWriter.status == AVAssetWriterStatusWriting) {        [self.assetWriter finishWritingWithCompletionHandler:^{            NSLog(@"结束写入数据");        }];//        [self.audioInput markAsFinished];//    }}@end
  • 预览视频
- (void)watchRecord:(UIButton *)sender {    NSLog(@"watchRecord");    NSArray<NSURL *> *allResource = [[self fetechAllResource] sortedArrayUsingComparator:^NSComparisonResult(NSURL *  _Nonnull obj1, NSURL * _Nonnull obj2) {        //排序,每次都查看最新录制的视频        return [obj2.path compare:obj1.path options:(NSCaseInsensitiveSearch)];    }];    AVPlayerViewController *playerViewController;    playerViewController = [[AVPlayerViewController alloc] init];    NSLog(@"url%@:",allResource);////    for (NSURL *url in allResource) {//        [self saveVideoWithUrl:url];//    }    playerViewController.player = [AVPlayer playerWithURL:allResource.firstObject];    //    playerViewController.delegate = self;    [self presentViewController:playerViewController animated:YES completion:^{        [playerViewController.player play];        NSLog(@"error == %@", playerViewController.player.error);    }];    }- (NSString *)getDocumentPath {        static NSString *replaysPath;    if (!replaysPath) {        NSFileManager *fileManager = [NSFileManager defaultManager];        NSURL *documentRootPath = [fileManager containerURLForSecurityApplicationGroupIdentifier:@"group.com.ask.answer.live"];        replaysPath = [documentRootPath.path stringByAppendingPathComponent:@"Replays"];        if (![fileManager fileExistsAtPath:replaysPath]) {            NSError *error_createPath = nil;            BOOL success_createPath = [fileManager createDirectoryAtPath:replaysPath withIntermediateDirectories:true attributes:@{} error:&error_createPath];            if (success_createPath && !error_createPath) {                NSLog(@"%@路径创建成功!", replaysPath);            } else {                NSLog(@"%@路径创建失败:%@", replaysPath, error_createPath);            }        }    }    return replaysPath;}- (NSArray <NSURL *> *)fetechAllResource {    NSFileManager *fileManager = [NSFileManager defaultManager];        NSString *documentPath = [self getDocumentPath];    NSURL *documentURL = [NSURL fileURLWithPath:documentPath];    NSError *error = nil;    NSArray<NSURL *> *allResource  =  [fileManager contentsOfDirectoryAtURL:documentURL includingPropertiesForKeys:@[] options:(NSDirectoryEnumerationSkipsSubdirectoryDescendants) error:&error];    return allResource;    }- (void)saveVideoWithUrl:(NSURL *)url {    PHPhotoLibrary *photoLibrary = [PHPhotoLibrary sharedPhotoLibrary];    [photoLibrary performChanges:^{        [PHAssetChangeRequest creationRequestForAssetFromVideoAtFileURL:url];            } completionHandler:^(BOOL success, NSError * _Nullable error) {        if (success) {            NSLog(@"已将视频保存至相册");        } else {            NSLog(@"未能保存视频到相册");        }    }];}
  • 预览视频经常失败,重启手机后恢复正常,也许是API的问题。

  • 参考1

  • 参考2

  • 参考3

  • AppGroup

本文来自博客园,作者:struggle_time,转载请注明原文链接:https://www.cnblogs.com/songliquan/p/15891392.html

posted @ 2022-02-16 12:36 struggle_time 阅读(0) 评论(0) 编辑 收藏 举报
回帖
    张三

    张三 (王者 段位)

    821 积分 (2)粉丝 (41)源码

     

    温馨提示

    亦奇源码

    最新会员