windows客户端中使用了基于webrtc的音视频sdk,同时界面使用的是cef。
当webrtc解码出大尺寸视频时,需要通过某些技术将数据投递给javascript进行绘制。
##一、使用canvas绘制bmp数据
webrtc解码图像后,将数据转换为BMP图像,保存在内存中,
javascript通过自定义scheme进行图像的获取,比如请求zhangpengtest://image001.bmp
拿到数据后通过canvas绘制bmp。
这种方法cpu占用率很高,因为canvas使用cpu绘制,并且需要进行图像的缩放。
c++通过以下代码通知js有图像准备好:
std::stringstream ss;ss << "zhangpengtest://" << sid << pic_id++ << ".bmp";std::string url = ss.str(); CefRefPtr<CefFrame> frame = browser->GetMainFrame();char buffer[256];sprintf(buffer, "onVideoData('%s');", url.c_str());frame->ExecuteJavaScript(buffer, frame->GetURL(), 0);
javascript可以通过设置img标签的src来加载图像
##二、通过webgl绘制
通过webgl绘制可以降低cpu,如何获取javascript的arraybuffer数据?
可以通过自定义scheme,结合XMLHttpRequest的Get请求,向browser进程的webrtc请求数据。
可以参考文章 cef中javascript和c++交换二进制数据(arraybuffer)的方法
我为什么选择使用方案三,是因为当时还没有发现能拿到arraybuffer数据的方法。
目前感觉可以使用此方案替换方案三。
##三、修改cef源码,添加虚拟摄像头,使用video标签预览摄像头
###基础知识
cef中本身也继承了一个webrtc,在javascript中使用getUserMedia()可以获取视频流stream。
通过window.navigator.mediaDevices.enumerateDevices()可以获取摄像头列表。
通过window.navigator.mediaDevices.getUserMedia可以获取对应于某个videoId的stream。
当使用video.srcObject = stream时,即可在video标签中预览摄像头。
###添加虚拟的摄像头
通过为设备列表添加一个虚拟的id,通过这个id的stream,使video标签打开我们自己实现的摄像头。
在该摄像头的实现代码中,为摄像头获取webrtc sdk解码的YUV格式的视频图像。
(1)修改media\capture\video\win\video_capture_device_factory_win.cc
void VideoCaptureDeviceFactoryWin::GetDeviceDescriptors(VideoCaptureDeviceDescriptors* device_descriptors) {DCHECK(thread_checker_.CalledOnValidThread());VideoCaptureApi api = VideoCaptureApi::WIN_DIRECT_SHOW;if (use_media_foundation_) {GetDeviceDescriptorsMediaFoundation(device_descriptors);api = VideoCaptureApi::WIN_MEDIA_FOUNDATION;}else {GetDeviceDescriptorsDirectShow(device_descriptors);}device_descriptors->emplace_back("zhangpengtest", "085db6c9-3c95-43a9-96de-2f015f2ac17c", "zhangpengtest:0", api);
}
在获取摄像头列表后,添加一个虚拟摄像头zhangpengtest,字符串的字段都可以任意填写,不要和已存在的设备重名即可。
std::unique_ptr<VideoCaptureDevice> VideoCaptureDeviceFactoryWin::CreateDevice(const Descriptor& device_descriptor) {DCHECK(thread_checker_.CalledOnValidThread());std::unique_ptr<VideoCaptureDevice> device;if (device_descriptor.display_name.find("zhangpengtest") != std::string::npos) {device.reset(new VideoCaptureDeviceZhangpeng(device_descriptor));DVLOG(1) << " DirectShow Device: " << device_descriptor.display_name;if (!static_cast<VideoCaptureDeviceFenbi*>(device.get())->Init())device.reset();return device;}//省略了部分代码return device;
}
以上代码,在打开摄像头时,发现是虚拟摄像头,就返回了VideoCaptureDeviceZhangpeng对象。
(2)实现摄像头类VideoCaptureDeviceZhangpeng
//继承于VideoCaptureDevice
class VideoCaptureDeviceZhangpeng : public VideoCaptureDevice {bool Init();
}
具体的实现细节,参考内置摄像头的实现方法即可。
重点细节在于在Init中启动一个线程,该线程负责定时获取图像数据。
获取图像数据后,回调给上层逻辑。
client_->OnIncomingCapturedData(data);
这样video标签就拿到了视频图像,会自动绘制出来。
(3)如何获取图像数据呢?
在windows系统中,可以通过共享内存来解决。
webrtc sdk解码视频后,写入共享内存。
虚拟摄像头中的线程,定时读取共享内存即可。