目标
实现web端远程展示和控制iOS真机
Part1 实时展示屏幕
通过简单调研,发现大家都在使用ios-minicap,下来了看看。
ios-minicap通过使用私有接口开启ios视频流,将视频流编码为jpg图片,通过websocket发送到前端,前端实时展示图片,达到实时展示界面的效果。
实时展示图片对流量有一定负担,这里考虑通过h264编码的视频流方式传递给前端实时展示。
目前还有对图片做diff然后决定是否更新的方案,但是在同样的流量限制下,fps是不如视频流的, 就不适合播放视频或游戏场景。
Web端播放视频流
目前可选不多,广泛使用基于Broadway这个h264解码库.
这里选择一个封装了Broadway的npm库:
h264-live-player (https://github.com/131/h264-live-player)
只需要提供视频流的websocket地址,就可以打开demo页面,播放视频了。略过。
iOS端生成视频流
新建mac cmdline项目,参照minicap,首先开启iOS设备录制的功能
-(void)EnableDALDevices{
CMIOObjectPropertyAddress prop = {
kCMIOHardwarePropertyAllowScreenCaptureDevices,
kCMIOObjectPropertyScopeGlobal,
kCMIOObjectPropertyElementMaster
};
UInt32allow =1;
CMIOObjectSetPropertyData(kCMIOObjectSystemObject,
&prop,0,NULL,
sizeof(allow), &allow );
}
接下来我们需要找到连接的设备id
-(NSString*)getConnectedDeviceId{
NSArray* devs = [AVCaptureDevice devicesWithMediaType:AVMediaTypeMuxed];
NSString *deviceId;
for(AVCaptureDevice* d in devs) {
//如果通过命令行参数指定了连接的设备id,则只判断这个设备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;
}
这里顺便支持下命令行启动时连接指定设备。
在调用EnableDALDevices方法后,立即调用getConnectedDeviceId方法并不会拿到想要的设备id,有一定的延迟。
stackoverflow上有监听设备连接的通知方法,但是试了下不work。这里偷懒就用个timer轮询吧。
设备断开连接的检测同样可以用timer搞定,但是要注意,一旦开始了视频连接,再次调用getConnectedDeviceId是可能拿到空值的。
视频流处理
偷懒找了个github项目,项目将摄像头采集的视频流通过VideoToolbox库转换为h264视频流。
稍微更改下VideoCapture中的代码:
- (instancetype)initWithCaptureParam:(VCVideoCapturerParam *)param error:(NSError *__autoreleasing _Nullable * _Nullable)error;
...
// 通过传入的device id,创建AVCaptureDevice
AVCaptureDevice *mDevice = [AVCaptureDevice deviceWithUniqueID: param.deviceID];
self.captureDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:mDevice error:&errorMessage];
self.captureVideoDataOutput = [[AVCaptureVideoDataOutput alloc] init];
...
//web端解码器是YpCbCr颜色编码,这里需要设置下。
NSDictionary *videoSetting = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8Planar], kCVPixelBufferPixelFormatTypeKey,
AVVideoScalingModeResizeAspect, (id)AVVideoScalingModeKey,nil];
[self.captureVideoDataOutput setVideoSettings:videoSetting];
...
//设置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;
...
}
搞定了视频编码,下面将数据传输给前端
视频流传输
随便找了个mac端的socket库 MBWebSocket
这库默认都按utf8字符发送,所以添加了个sendData方法,发送data类型的数据。
初始化之,端口由上层代码传入
self.socket = [[MBWebSocketServer alloc] initWithPort:self.port delegate:self];
在编码收到的回调中发送包
//got NAL unit from encoder
- (void)videoEncodeOutputDataCallback:(NSData *)data isKeyFrame:(BOOL)isKeyFrame
{
[self.socket sendData:data];
}
另外由于web端的h264库比较笨,需要预先知道播放视频的宽高,我们需要在发送第一帧数据前发送编码后的视频宽高数据,略过。
命令行参数
命令行参数需要支持fps、bitrate、resolution、指定设备id等设置。
也可以通过websocket传入视频参数动态更改。
详略。
注意:更改分辨率宽高应该设置为16的倍数,否则系统会自动优化为16倍数。
Next
控制流