会議のスクリーンキャスト:スクリーンキャストデータストリーム取得スキーム
目次
-異なるシステムバージョンの進化
ここではiosを例に挙げるだけで、後で他のプラットフォームでの実現方法を別々に書く時間があります.アップルはこの方面でずっと消極的で緩やかな進化状態にあり、いつも開発者に少し巧みな婦人がやりにくい感じを与えている.与えられた実装スキームであっても,種々の不安定性のため,実際の呼び出し中に安定して実行できない.後で一つ一つ説明します.
iosにおける比較的正規の実装方式はExtensionで実現され,ios 9から限られた実装が提供される.ios 9以前にもプロトコルを解読することによって実現されていましたが、本稿ではExtension拡張方式で実現していますが、拡張をどのように追加するかは一つ一つ説明しません.
1. RPScreenRecorder
RPScreenRecorderはアップルが最初に提案したスクリーン録画案で、直接コードの一部をアップロードします.
/*
* , , , 。
*/
#pragma mark- start record
-(IBAction)onStart:(id)sender{
if([[RPScreenRecorder sharedRecorder] isAvailable] == YES){
[[RPScreenRecorder sharedRecorder] startRecordingWithMicrophoneEnabled:YES handler:^(NSError * _Nullable error) {
}];
}
}
#pragma mark- stop record
- (IBAction)onStop:(id)sender{
if([[RPScreenRecorder sharedRecorder] isAvailable] == YES){
[[RPScreenRecorder sharedRecorder]stopRecordingWithHandler:^(RPPreviewViewController * _Nullable previewViewController, NSError * _Nullable error) {
[self presentViewController:previewViewController animated:YES completion:^{
}];
}];
}
}
上記の方法では、クリックが完了すると、システムが提供するプレビューインタフェースがモードで開きます.インタフェースでは共有を保存するなどの方法を選択できます.この方式はストリーミングのリアルタイムリターンをサポートしていないことを示しており、生放送を行うのは明らかに適切ではなく、主に後期に完全なファイルを生成して再アップロードする方法に用いられる.
そこでios 11では、アップルがこれに基づいて一定のリアルタイムストリームを返す能力を提供している場合がありますが、実際にはこの場合、この方法で実現する必要はありません.この場合、呼び出し方法を提供します.
//
if (@available(iOS 11.0, *)) {
[[RPScreenRecorder sharedRecorder] startCaptureWithHandler:^(CMSampleBufferRef _Nonnull sampleBuffer, RPSampleBufferType bufferType, NSError * _Nullable error) {
NSLog(@"--- [ ] %@", sampleBuffer);
} completionHandler:^(NSError * _Nullable error) {
NSLog(@"Recording started error %@",error);
}];
}
//
if (@available(iOS 11.0, *)) {
[[RPScreenRecorder sharedRecorder] stopCaptureWithHandler:^(NSError * _Nullable error) {
}];
}
この場合、リアルタイムストリームデータの戻り問題は解決されたが、アプリケーション外のコンテンツは常に録画できない.実はこの需要を実現するには、すでに別の実現方式がある.
2.Extensionはリアルタイムストリームデータの返却を実現する
Extensionの追加プロセスは上図ではありません.target->add->Broadcast Upload Extension->Next->ポイント選択include UI Extensionオプション->追加完了
このとき、システムには2つのフォルダが追加され、1つは録画前のuiディスプレイを実現するために使用される(ios 12以降、新しい起動方式に基づいて、もうコールバックされなくなった).通常、このインタフェースでいくつかの設定や過剰なインタフェースとして示すことができます.
@implementation BroadcastSetupViewController
// Call this method when the user has finished interacting with the view controller and a broadcast stream can start
- (void)userDidFinishSetup {
NSURL *broadcastURL = [NSURL URLWithString:@"http://apple.com/broadcast/streamID"];
NSDictionary *setupInfo = @{ @"broadcastName" : @"example" };
[self.extensionContext completeRequestWithBroadcastURL:broadcastURL setupInfo:setupInfo];
}
- (void)userDidCancelSetup {
[self.extensionContext cancelRequestWithError:[NSError errorWithDomain:@"YourAppDomain" code:-1 userInfo:nil]];
}
@end
注記から分かるように、**userDidFinishSetup**を呼び出し、**userDidCancelSetup**を呼び出してスクリーンを録画する前に終了する.
上記のプロセスが完了すると、システムは録画画面に入り、リアルタイムストリームのリターンフェーズに戻ります.コールバック・コードは次のとおりです.
#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.
}
- (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.
}
- (void)broadcastFinished {
// User has requested to finish the broadcast.
}
- (void)processSampleBuffer:(CMSampleBufferRef)sampleBuffer withType:(RPSampleBufferType)sampleBufferType {
switch (sampleBufferType) {
case RPSampleBufferTypeVideo:
// Handle video sample buffer
break;
case RPSampleBufferTypeAudioApp:
// Handle audio sample buffer for app audio
break;
case RPSampleBufferTypeAudioMic:
// Handle audio sample buffer for mic audio
break;
default:
break;
}
}
@end
Extension呼び出しにどのように触れますか?
ios 10では、ユーザが自発的にExtensionを選択する第1の方法が提供される.// ,
[RPBroadcastActivityViewController loadBroadcastActivityViewControllerWithHandler:^(RPBroadcastActivityViewController * _Nullable broadcastActivityViewController, NSError * _Nullable error) {
[self presentViewController:broadcastActivityViewController animated:YES completion:nil];
}];
ios 11の場合、システムはさらに直接的なトリガを提供します.[RPBroadcastActivityViewController loadBroadcastActivityViewControllerWithPreferredExtension:@"com.ReplayKitDemo.TestSetupUI" handler:^(RPBroadcastActivityViewController * _Nullable controller, NSError * _Nullable error) {
controller.delegate = self;
[self presentViewController:controller animated:YES completion:nil];
}];
コードの「com.ReplayKitDemo.TestSetup UI」は、Setup UIのBoundIDに対応します.その後、アップルは上の2つの方法が直接的ではないと思っていましたが、ios 12で提案された3つ目の方法がありました.if (@available(iOS 12.0, *)) {
RPSystemBroadcastPickerView *pickView = [[RPSystemBroadcastPickerView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
pickView.preferredExtension = @"com.awesome.SRUtil.SRExtUpload";
[self.view addSubview:pickView];
}
RPSystemBroadcastPickerViewは、直接呼び出し機能を提供するシステムが提供するコントロールです.このように呼び出されると、Setup UIを呼び出して表示するのではなく、SampleHandlerが存在する拡張プロジェクトを直接呼び出す.「com.awesome.SRUtil.SRExtUpload」は、SampleHandlerプロジェクトのBoundIDです.しかし、ios 13が発売されるまでは、この呼び出し方式はまだ安定していない.
-注意事項
1.Extension 50 Mメモリ制限
プロジェクトの実践では、この50 Mのメモリ制限に対して、プッシュフロースキームで多くの方法を試みました.Tcpからマルチエンドへの同時プッシュ、UDPマルチキャスト送信まで、ほとんどのプロセスがメモリ制限をめぐって展開されます.
TPLineのハードな指標要求のため,プッシュフローはサーバを経由して中継することができず,PTPの直接送達を実現する.従って、早期にTcpで同時マルチエンドプッシュを実現する過程で、最初の案を修正し、コールバックデータの蓄積速度がTcpマルチエンドプッシュストリームより速いという問題を解決するために、送信データキューの慣用的な実現方式を断固として放棄した.これは後のブログで一つ一つ説明します.
後でUDP+マルチキャスト方式で送信する場合,マルチエンド同時プッシュフローの問題は確かに解決されるが,UDPに合わせて多様なエラー制御を実現する案が必要である.これは後のブログで一つ一つ説明します.
プッシュストリームがサーバを経由して中継できないことに制限され、ローカルエリアネットワーク内の会議スクリーンシステムに使用され、UDP+マルチキャスト方式を採用することが最良の実践方式であることが実証された.
2.Extension論理処理の時間制限
拡張debugを行う過程で、最初は次のブレークポイントに慣れてデータのデバッグを行います.しかし、すぐに拡張がすぐに終了し、終了したことに気づきます.
システムは拡張サポートを提供すると同時に、システムのスムーズさを維持するために最大の稼働時間を制限しています.符号化を含むすべての論理は、この最大実行時間制限の下で実行される.しかし,実際の応用では,符号化動作を行う時間に十分であることが示されている.
だから論理設計を行う時、“processsSampleBuffer”の方法でコールバックした後の処理は多くのスレッドの処理をしなければなりません.
3.同期スレッドキュー実行符号化
//
// SampleHandler.m
// TPUpload
//
// Created by adam on 2019/5/22.
// Copyright © 2019 lcs. All rights reserved.
//
- (void)processSampleBuffer:(CMSampleBufferRef)sampleBuffer withType:(RPSampleBufferType)sampleBufferType {
switch (sampleBufferType) {
case RPSampleBufferTypeVideo:
[[H264Encoder shareInstance] startH264EncodeWithSampleBuffer:sampleBuffer andReturnData:^(NSData *data) {
[[UDPServerMain shareInstance] pushVideo:data];
}];
break;
case RPSampleBufferTypeAudioApp:
break;
case RPSampleBufferTypeAudioMic:
break;
default:
break;
}
}
//
// H264Encoder.m
// Awesome
//
// Created by adam on 2019/1/8.
// Copyright © 2019 common. All rights reserved.
//
#pragma mark- startH264EncodeWithSampleBuffer
-(void)startH264EncodeWithSampleBuffer:(CMSampleBufferRef)sampleBuffer andReturnData:(ReturnDataBlock)block{
self.returnDataBlock = block;
dispatch_sync(m_EncodeQueue, ^{
[self encode:sampleBuffer];
});
}
上記のコードを例にとると、H 264 Encoderはハードコーディングを行い、「startH 264 EncodeWithSampleBuffer」メソッドでは、コーディング操作を行う際にdispatch_async非同期方式で呼び出されます.
4. RPSystemBroadcastPickerView
/*
* , , , 。
*/
#pragma mark- start record
-(IBAction)onStart:(id)sender{
if([[RPScreenRecorder sharedRecorder] isAvailable] == YES){
[[RPScreenRecorder sharedRecorder] startRecordingWithMicrophoneEnabled:YES handler:^(NSError * _Nullable error) {
}];
}
}
#pragma mark- stop record
- (IBAction)onStop:(id)sender{
if([[RPScreenRecorder sharedRecorder] isAvailable] == YES){
[[RPScreenRecorder sharedRecorder]stopRecordingWithHandler:^(RPPreviewViewController * _Nullable previewViewController, NSError * _Nullable error) {
[self presentViewController:previewViewController animated:YES completion:^{
}];
}];
}
}
//
if (@available(iOS 11.0, *)) {
[[RPScreenRecorder sharedRecorder] startCaptureWithHandler:^(CMSampleBufferRef _Nonnull sampleBuffer, RPSampleBufferType bufferType, NSError * _Nullable error) {
NSLog(@"--- [ ] %@", sampleBuffer);
} completionHandler:^(NSError * _Nullable error) {
NSLog(@"Recording started error %@",error);
}];
}
//
if (@available(iOS 11.0, *)) {
[[RPScreenRecorder sharedRecorder] stopCaptureWithHandler:^(NSError * _Nullable error) {
}];
}
@implementation BroadcastSetupViewController
// Call this method when the user has finished interacting with the view controller and a broadcast stream can start
- (void)userDidFinishSetup {
NSURL *broadcastURL = [NSURL URLWithString:@"http://apple.com/broadcast/streamID"];
NSDictionary *setupInfo = @{ @"broadcastName" : @"example" };
[self.extensionContext completeRequestWithBroadcastURL:broadcastURL setupInfo:setupInfo];
}
- (void)userDidCancelSetup {
[self.extensionContext cancelRequestWithError:[NSError errorWithDomain:@"YourAppDomain" code:-1 userInfo:nil]];
}
@end
#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.
}
- (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.
}
- (void)broadcastFinished {
// User has requested to finish the broadcast.
}
- (void)processSampleBuffer:(CMSampleBufferRef)sampleBuffer withType:(RPSampleBufferType)sampleBufferType {
switch (sampleBufferType) {
case RPSampleBufferTypeVideo:
// Handle video sample buffer
break;
case RPSampleBufferTypeAudioApp:
// Handle audio sample buffer for app audio
break;
case RPSampleBufferTypeAudioMic:
// Handle audio sample buffer for mic audio
break;
default:
break;
}
}
@end
// ,
[RPBroadcastActivityViewController loadBroadcastActivityViewControllerWithHandler:^(RPBroadcastActivityViewController * _Nullable broadcastActivityViewController, NSError * _Nullable error) {
[self presentViewController:broadcastActivityViewController animated:YES completion:nil];
}];
[RPBroadcastActivityViewController loadBroadcastActivityViewControllerWithPreferredExtension:@"com.ReplayKitDemo.TestSetupUI" handler:^(RPBroadcastActivityViewController * _Nullable controller, NSError * _Nullable error) {
controller.delegate = self;
[self presentViewController:controller animated:YES completion:nil];
}];
if (@available(iOS 12.0, *)) {
RPSystemBroadcastPickerView *pickView = [[RPSystemBroadcastPickerView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
pickView.preferredExtension = @"com.awesome.SRUtil.SRExtUpload";
[self.view addSubview:pickView];
}
1.Extension 50 Mメモリ制限
プロジェクトの実践では、この50 Mのメモリ制限に対して、プッシュフロースキームで多くの方法を試みました.Tcpからマルチエンドへの同時プッシュ、UDPマルチキャスト送信まで、ほとんどのプロセスがメモリ制限をめぐって展開されます.
TPLineのハードな指標要求のため,プッシュフローはサーバを経由して中継することができず,PTPの直接送達を実現する.従って、早期にTcpで同時マルチエンドプッシュを実現する過程で、最初の案を修正し、コールバックデータの蓄積速度がTcpマルチエンドプッシュストリームより速いという問題を解決するために、送信データキューの慣用的な実現方式を断固として放棄した.これは後のブログで一つ一つ説明します.
後でUDP+マルチキャスト方式で送信する場合,マルチエンド同時プッシュフローの問題は確かに解決されるが,UDPに合わせて多様なエラー制御を実現する案が必要である.これは後のブログで一つ一つ説明します.
プッシュストリームがサーバを経由して中継できないことに制限され、ローカルエリアネットワーク内の会議スクリーンシステムに使用され、UDP+マルチキャスト方式を採用することが最良の実践方式であることが実証された.
2.Extension論理処理の時間制限
拡張debugを行う過程で、最初は次のブレークポイントに慣れてデータのデバッグを行います.しかし、すぐに拡張がすぐに終了し、終了したことに気づきます.
システムは拡張サポートを提供すると同時に、システムのスムーズさを維持するために最大の稼働時間を制限しています.符号化を含むすべての論理は、この最大実行時間制限の下で実行される.しかし,実際の応用では,符号化動作を行う時間に十分であることが示されている.
だから論理設計を行う時、“processsSampleBuffer”の方法でコールバックした後の処理は多くのスレッドの処理をしなければなりません.
3.同期スレッドキュー実行符号化
//
// SampleHandler.m
// TPUpload
//
// Created by adam on 2019/5/22.
// Copyright © 2019 lcs. All rights reserved.
//
- (void)processSampleBuffer:(CMSampleBufferRef)sampleBuffer withType:(RPSampleBufferType)sampleBufferType {
switch (sampleBufferType) {
case RPSampleBufferTypeVideo:
[[H264Encoder shareInstance] startH264EncodeWithSampleBuffer:sampleBuffer andReturnData:^(NSData *data) {
[[UDPServerMain shareInstance] pushVideo:data];
}];
break;
case RPSampleBufferTypeAudioApp:
break;
case RPSampleBufferTypeAudioMic:
break;
default:
break;
}
}
//
// H264Encoder.m
// Awesome
//
// Created by adam on 2019/1/8.
// Copyright © 2019 common. All rights reserved.
//
#pragma mark- startH264EncodeWithSampleBuffer
-(void)startH264EncodeWithSampleBuffer:(CMSampleBufferRef)sampleBuffer andReturnData:(ReturnDataBlock)block{
self.returnDataBlock = block;
dispatch_sync(m_EncodeQueue, ^{
[self encode:sampleBuffer];
});
}
上記のコードを例にとると、H 264 Encoderはハードコーディングを行い、「startH 264 EncodeWithSampleBuffer」メソッドでは、コーディング操作を行う際にdispatch_async非同期方式で呼び出されます.
4. RPSystemBroadcastPickerView
RPSystemBroadcastPickerView
を開発とユーザーに開放し、ついにアプリケーション内でシステムを起動する録画機能を実現することができたが、実際には、呼び出されたpickerView
が、preferredExtension
を指定した場合、対応するExtension
に表示されない場合があることが分かった.RPSystemBroadcastPickerView
の外観をカスタマイズすることができず、外観をカスタマイズすることができず、システムのスタイルをデフォルトで表示するしかなく、インタフェースが高度にカスタマイズされている場合、調和のとれた共存を実現することは難しい.そこで一般的には
の方式を用いて,RPSystemBroadcastPickerView
をページに追加してから非表示にする.RPSystemBroadcastPickerView
は他のボタンによってトリガされ、参照コードは以下の通りである:for (UIView *item in self.pickView.subviews) {
if ([item isKindOfClass:UIButton.class] == YES) {
actionButton = (UIButton*)item;
}
}
dispatch_async(dispatch_get_main_queue(), ^{
[self->actionButton sendActionsForControlEvents:UIControlEventTouchDown];
});
-開発の経験
1.Extensionのデバッグ方法
簡単に言えば、Extensionをデバッグする過程で、まず実行主プロジェクトをインストールし、SampleHandlerプロジェクトtargetを選択してスケジューリング実行し、開いているアプリケーションリストであなたのプロジェクトを選択します.
コードが変更された後も、上記の手順を繰り返して、最新のコードが実行されていることを確認したほうがいいです.
2.奇妙な現象をどう扱うか
多くの奇妙な現象はいくつかの主要な要素に由来しています.
3.SampleHandlerは、異なるインタフェースで、返されるストリームの状況が異なります。
SampleHandler
のprocessSampleBuffer
メソッドを、リスニング画面に変化があるかどうかによってコールバックする.テストでは、携帯電話によってアプリケーションによってインタフェースに表示されるprocessSampleBuffer
のコールバック状況が異なることが分かった.