目標(biāo)
實(shí)現(xiàn)web端遠(yuǎn)程展示和控制iOS真機(jī)
Part1 實(shí)時(shí)展示屏幕
通過(guò)簡(jiǎn)單調(diào)研盏檐,發(fā)現(xiàn)大家都在使用ios-minicap复斥,下來(lái)了看看。
ios-minicap通過(guò)使用私有接口開(kāi)啟ios視頻流疗疟,將視頻流編碼為jpg圖片署鸡,通過(guò)websocket發(fā)送到前端,前端實(shí)時(shí)展示圖片绊诲,達(dá)到實(shí)時(shí)展示界面的效果送粱。
實(shí)時(shí)展示圖片對(duì)流量有一定負(fù)擔(dān),這里考慮通過(guò)h264編碼的視頻流方式傳遞給前端實(shí)時(shí)展示驯镊。
目前還有對(duì)圖片做diff然后決定是否更新的方案葫督,但是在同樣的流量限制下竭鞍,fps是不如視頻流的, 就不適合播放視頻或游戲場(chǎng)景。
Web端播放視頻流
目前可選不多橄镜,廣泛使用基于Broadway這個(gè)h264解碼庫(kù).
這里選擇一個(gè)封裝了Broadway的npm庫(kù):
h264-live-player (https://github.com/131/h264-live-player)
只需要提供視頻流的websocket地址偎快,就可以打開(kāi)demo頁(yè)面,播放視頻了洽胶。略過(guò)晒夹。
iOS端生成視頻流
新建mac cmdline項(xiàng)目,參照minicap姊氓,首先開(kāi)啟iOS設(shè)備錄制的功能
-(void)EnableDALDevices{
CMIOObjectPropertyAddress prop = {
kCMIOHardwarePropertyAllowScreenCaptureDevices,
kCMIOObjectPropertyScopeGlobal,
kCMIOObjectPropertyElementMaster
};
UInt32allow =1;
CMIOObjectSetPropertyData(kCMIOObjectSystemObject,
&prop,0,NULL,
sizeof(allow), &allow );
}
接下來(lái)我們需要找到連接的設(shè)備id
-(NSString*)getConnectedDeviceId{
NSArray* devs = [AVCaptureDevice devicesWithMediaType:AVMediaTypeMuxed];
NSString *deviceId;
for(AVCaptureDevice* d in devs) {
//如果通過(guò)命令行參數(shù)指定了連接的設(shè)備id丐怯,則只判斷這個(gè)設(shè)備id
if(self.targetDeviceId){
if( [self.targetDeviceId isEqualToString:d.uniqueID]){
deviceId = self.targetDeviceId;
NSLog(@"found connected device %@",d.localizedName);
break;
}
}else if([d.modelID isEqualToString:@"iOS Device"]){
deviceId = d.uniqueID;
NSLog(@"found connected device %@",d.localizedName);
break;
}
}
return deviceId;
}
這里順便支持下命令行啟動(dòng)時(shí)連接指定設(shè)備。
在調(diào)用EnableDALDevices方法后翔横,立即調(diào)用getConnectedDeviceId方法并不會(huì)拿到想要的設(shè)備id读跷,有一定的延遲。
stackoverflow上有監(jiān)聽(tīng)設(shè)備連接的通知方法禾唁,但是試了下不work效览。這里偷懶就用個(gè)timer輪詢吧。
設(shè)備斷開(kāi)連接的檢測(cè)同樣可以用timer搞定荡短,但是要注意丐枉,一旦開(kāi)始了視頻連接,再次調(diào)用getConnectedDeviceId是可能拿到空值的掘托。
視頻流處理
偷懶找了個(gè)github項(xiàng)目瘦锹,項(xiàng)目將攝像頭采集的視頻流通過(guò)VideoToolbox庫(kù)轉(zhuǎn)換為h264視頻流。
稍微更改下VideoCapture中的代碼:
- (instancetype)initWithCaptureParam:(VCVideoCapturerParam *)param error:(NSError *__autoreleasing _Nullable * _Nullable)error;
...
// 通過(guò)傳入的device id闪盔,創(chuàng)建AVCaptureDevice
AVCaptureDevice *mDevice = [AVCaptureDevice deviceWithUniqueID: param.deviceID];
self.captureDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:mDevice error:&errorMessage];
self.captureVideoDataOutput = [[AVCaptureVideoDataOutput alloc] init];
...
//web端解碼器是YpCbCr顏色編碼弯院,這里需要設(shè)置下。
NSDictionary *videoSetting = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8Planar], kCVPixelBufferPixelFormatTypeKey,
AVVideoScalingModeResizeAspect, (id)AVVideoScalingModeKey,nil];
[self.captureVideoDataOutput setVideoSettings:videoSetting];
...
//設(shè)置fps
if(self.captureConnection.supportsVideoMinFrameDuration){
self.captureConnection.videoMinFrameDuration = CMTimeMake(1, param.frameRate);
}
...
}
編碼器VideoEncoder代碼:
- (instancetype)initWithParam:(VEVideoEncoderParam *)param
...
//h264的profile泪掀,web端只支持Baseline
profileRef = kVTProfileLevel_H264_Baseline_3_0;
...
}
搞定了視頻編碼抽兆,下面將數(shù)據(jù)傳輸給前端
視頻流傳輸
隨便找了個(gè)mac端的socket庫(kù) MBWebSocket
這庫(kù)默認(rèn)都按utf8字符發(fā)送,所以添加了個(gè)sendData方法族淮,發(fā)送data類型的數(shù)據(jù)辫红。
初始化之,端口由上層代碼傳入
self.socket = [[MBWebSocketServer alloc] initWithPort:self.port delegate:self];
在編碼收到的回調(diào)中發(fā)送包
//got NAL unit from encoder
- (void)videoEncodeOutputDataCallback:(NSData *)data isKeyFrame:(BOOL)isKeyFrame
{
[self.socket sendData:data];
}
另外由于web端的h264庫(kù)比較笨祝辣,需要預(yù)先知道播放視頻的寬高贴妻,我們需要在發(fā)送第一幀數(shù)據(jù)前發(fā)送編碼后的視頻寬高數(shù)據(jù),略過(guò)蝙斜。
命令行參數(shù)
命令行參數(shù)需要支持fps名惩、bitrate、resolution孕荠、指定設(shè)備id等設(shè)置娩鹉。
也可以通過(guò)websocket傳入視頻參數(shù)動(dòng)態(tài)更改攻谁。
詳略。
注意:更改分辨率寬高應(yīng)該設(shè)置為16的倍數(shù)弯予,否則系統(tǒng)會(huì)自動(dòng)優(yōu)化為16倍數(shù)戚宦。
Next
控制流