此次demo使用chrome49调试测试

前端在操作视频输入,音频输入,输出上一直是比较弱的,或者说很难进行相关的操作,经过我最近的一些研究发现,在PC上实际上是可以实现这一系列的功能的,其实现原理主要是得益于google的webRTC技术。

什么是webRTC

WebRTC,名称源自网页即时通讯(英语:Web Real-Time Communication)的缩写,是一个支持网页浏览器进行实时语音对话或视频对话的API。它于2011年6月1日开源并在Google、Mozilla、Opera支持下被纳入万维网联盟的W3C推荐标准[1][2][3]。(来自维基百科

也就是说webRTC是让网页浏览器进行实时语音对话或者视频对话的一系列的解决方案。官方demo:https://github.com/webrtc/samples

这个demo里面实际上功能非常多,关于调用摄像头我们主要关心的是这个demo,可是当大家把代码拉下来之后发现非常多东西,我在这里给大家简单的总结一下,并自己写上几个简单的demo,当然想要关心具体实现功能,或者代码的也可以看源码。

如何在网页中调用摄像头

首先我们需要实现的就是在网页中调用到摄像头进行录制,假如没有摄像头的同学也可以去下载一个繁星伴奏,进行模拟,下载地址:http://fanxing.kugou.com/ac/accompany

我们这里需要用到navigator.getUserMedia这个API,当然在chrome下需要使用兼容前缀即navigator.webkitGetUserMedia

代码如下:

<body>
<video width="400" height="" id="video" autoplay="">
<source src="myvideo.mp4" type="video/mp4"></source>
<source src="myvideo.ogv" type="video/ogg"></source>
<source src="myvideo.webm" type="video/webm"></source>
<object width="" height="" type="application/x-shockwave-flash" data="myvideo.swf">
<param name="movie" value="myvideo.swf" />
<param name="flashvars" value="autostart=true&amp;file=myvideo.swf" />
</object>
当前浏览器不支持 video直接播放,点击这里下载视频: <a href="myvideo.webm">下载视频</a>
</video>
</body>
<script type="text/javascript">
var constraints={video:true}; //设置参数LocalMediaStream
var video_element=document.getElementById("video"); //
if(navigator.getUserMedia){ //默认API
navigator.getUserMedia(constraints)
.then(function(stream) {
console.info(stream);
window.stream = stream;
video_element.srcObject = stream;
video_element.src=URL.createObjectURL(stream);
return navigator.mediaDevices.enumerateDevices();
})
.catch(errorCallback);
}else if(navigator.webkitGetUserMedia){ //chrome兼容
navigator.webkitGetUserMedia(constraints,function(stream){
//成功获取后回调
console.info(stream);
window.stream = stream;
video_element.srcObject = stream;
video_element.src=URL.createObjectURL(stream);
return navigator.mediaDevices.enumerateDevices();
},function(data){
//失败回调
console.info(data)
});
}
</script>

我们可以看下代码,HTML部分很简单 就是一个video标签,主要功能的实现在js中可以找到navigator.getUserMedia这个API,通过这个就可以调用当前摄像头,并且返回流信息。

关于navigator.getUserMedia

提示用户需要权限去使用像摄像头或麦克风之类的媒体设备,如果用户提供了这个权限。

语法

navigator.getUserMedia ( constraints, successCallback, errorCallback );

参数
  1. constraints : 
    successCallback中传入的 LocalMediaStream对象所支持的媒体类型。
  2. successCallback
    当应用中传递LocalMediaStream对象时触发的函数。
  3. errorCallback
    当调用媒体设备失败时触发的函数.

其中第一个和第二是都是必须的.

  • 第一个需要传入想要调用哪种媒体类型,具体就像代码中定义:

    var constraints={video:true};

    当然也可以写多个类型,例如

    var constraints={video:true, audio: true};

  • 第二个参数则是调用成功后的回调函数,回调函数本身也有一个参数data返回的则是相关的音频视频信息。

通过这个对整个API的使用,把获取到的流信息stream传给video标签的src中即可将视频信息在网页中显示出来。

多个设备的处理

假设现在用户有多个摄像设备,那我们需要让用户进行选择,又应该如何做呢?

首先我们需要给用户提供他自己的设备名称让他进行选择,在这里我们需要使用navigator.mediaDevices.enumerateDevices()这个API 因此我们可以这样写:

<html>
<head>
<meta charset="UTF-8">
<title></title>
<script src="js/jquery.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
<button id="showInfo">显示设备信息</button>
<div>
<h2>视频输入设备</h2>
<div id="videoinputInfoBox"> </div>
</div>
</body>
<script type="text/javascript">
$(function(){
init();
});
function init(){
$("#showInfo").on("click",function(){
//获取设备信息
navigator.mediaDevices.enumerateDevices().then(gotDevices);
});
}
//获取设备信息后的处理
function gotDevices(data){
var videoinputhtml="";
for (var i = 0; i < data.length; i++) {
console.info(data[i]);
if(data[i].kind=="videoinput"){
videoinputhtml+="<span data-id='"+data[i].deviceId+"' data-info='"+data[i].label+"' class='infoBtn'>"+data[i].label+"</span><br>";
}
}
$("#videoinputInfoBox").html(videoinputhtml);
}
</script>
</html>

我们在页面添加了一个按钮showInfo,当点击这个按钮的时候调用了navigator.mediaDevices.enumerateDevices()这个方法,这个方法会把当前的设备以多个对象组成数组的形式全部返回,而我们对整个数组进行循环分类,从而得到其相关信息,其中

  1. MediaDeviceInfo.label 
    设备名称
  2. MediaDeviceInfo.deviceId 
    设备ID
  3. MediaDeviceInfo.kind 
    设备类型,只会有三种类型“videoinput”,"audioinput" or "audiooutput"
  4. MediaDeviceInfo.groupId 
    设备系列ID(自己翻译的,理解就是同一个设备有不同的功能,比如麦克风和听筒,则公用同一个groupId 官方的说法是

    Returns a DOMString that is a group identifier. Two devices have the same group identifier if they belong to the same physical device; for example a monitor with both a built-in camera and microphone.

    如有错误请指正)

根据上面的这4个字段的解析我们就可以得到用户关于音视频设备的各种信息了。

那接着我们需要做最后一步就是让用户点击某个视频设备则调用相应的视频设备进行相关的视频流信息输出

大概思路就是给每个设备名称绑定点击事件,然后获取到当前设备的deviceId,通过前面提到的constraints参数将设备ID传到navigator.getUserMedia方法中从而获取相关的设备信息。

代码如下:

<!DOCTYPE html>
<html> <head>
<meta charset="UTF-8">
<title></title>
<script src="js/jquery.js" type="text/javascript" charset="utf-8"></script>
</head> <body>
<video width="400" height="" id="video" autoplay="">
<source src="myvideo.mp4" type="video/mp4"></source>
<source src="myvideo.ogv" type="video/ogg"></source>
<source src="myvideo.webm" type="video/webm"></source>
<object width="" height="" type="application/x-shockwave-flash" data="myvideo.swf">
<param name="movie" value="myvideo.swf" />
<param name="flashvars" value="autostart=true&amp;file=myvideo.swf" />
</object>
当前浏览器不支持 video直接播放,点击这里下载视频: <a href="myvideo.webm">下载视频</a>
</video>
<button id="showInfo">显示设备信息</button>
<div>
<h2>视频输入设备</h2>
<div id="videoinputInfoBox"> </div>
</div>
</body>
<script type="text/javascript">
$(function() {
init();
start();
}); function init() {
$("#showInfo").on("click", function() {
//获取设备信息
navigator.mediaDevices.enumerateDevices().then(gotDevices);
});
}
//获取设备信息后的处理
function gotDevices(data) {
var videoinputhtml = "";
for (var i = 0; i < data.length; i++) {
console.info(data[i]);
if (data[i].kind == "videoinput") {
videoinputhtml += "<span data-id='" + data[i].deviceId + "' data-info='" + data[i].label + "' class='infoBtn'>" + data[i].label + "</span><br>";
}
}
$("#videoinputInfoBox").html(videoinputhtml);
}
//点击选择设备
function start() {
$("#videoinputInfoBox").on("click", ".infoBtn", function() {
//判断当前是否有其他的流信息,如果有则停止
if (window.stream) {
window.stream.getTracks().forEach(function(track) {
track.stop();
});
}
var $this = $(this);
var id = $this.attr("data-id");
//将设备id传入constraints
var constraints = {
video: {
sourceId: id
}
};
var video_element = document.getElementById('video');
errorCallback = function(error) {
console.log("Video capture error: ", error.code);
};
if (navigator.getUserMedia) {
navigator.getUserMedia(constraints)
.then(function(stream) {
window.stream = stream;
video_element.srcObject = stream;
video_element.src = URL.createObjectURL(stream);
return navigator.mediaDevices.enumerateDevices();
})
.then(gotDevices)
.catch(errorCallback);
} else if (navigator.webkitGetUserMedia) {
//webkit特别处理,将标准中的constraints格式转换成webkit所支持的
constraints.video = constraintsToChrome(constraints.video);
navigator.webkitGetUserMedia(constraints, function(stream) {
window.stream = stream;
video_element.srcObject = stream;
video_element.src = URL.createObjectURL(stream);
return navigator.mediaDevices.enumerateDevices();
}, function(data) {
console.info(data)
});
}
})
}
// getUserMedia constraints shim. 官方源码提供的方法,把传入的sourceId转换成navigator.webkitGetUserMedia锁需要的格式
var constraintsToChrome = function(c) {
if (typeof c !== 'object' || c.mandatory || c.optional) {
return c;
}
var cc = {};
Object.keys(c).forEach(function(key) {
if (key === 'require' || key === 'advanced' || key === 'mediaSource') {
return;
}
var r = (typeof c[key] === 'object') ? c[key] : {
ideal: c[key]
};
if (r.exact !== undefined && typeof r.exact === 'number') {
r.min = r.max = r.exact;
}
var oldname = function(prefix, name) {
if (prefix) {
return prefix + name.charAt(0).toUpperCase() + name.slice(1);
}
return (name === 'deviceId') ? 'sourceId' : name;
};
if (r.ideal !== undefined) {
cc.optional = cc.optional || [];
var oc = {};
if (typeof r.ideal === 'number') {
oc[oldname('min', key)] = r.ideal;
cc.optional.push(oc);
oc = {};
oc[oldname('max', key)] = r.ideal;
cc.optional.push(oc);
} else {
oc[oldname('', key)] = r.ideal;
cc.optional.push(oc);
}
}
if (r.exact !== undefined && typeof r.exact !== 'number') {
cc.mandatory = cc.mandatory || {};
cc.mandatory[oldname('', key)] = r.exact;
} else {
['min', 'max'].forEach(function(mix) {
if (r[mix] !== undefined) {
cc.mandatory = cc.mandatory || {};
cc.mandatory[oldname(mix, key)] = r[mix];
}
});
}
});
if (c.advanced) {
cc.optional = (cc.optional || []).concat(c.advanced);
}
return cc;
};
</script> </html>

我们将前面两段代码进行了结合,添加了一些方法,从而实现点击切换视频源

分析下上面的代码,其实关键点都有备注:

if (window.stream) { window.stream.getTracks().forEach(function(track) { track.stop(); }); }

因为我们每次都会将流信息stream放到window下面,因此每次选择新的视频源的时候都需要将旧的清除

var constraints = { video: { sourceId: id } };

我们在设置constraints参数的时候不再是使用constraints={video:true}了,而是将当前选择的这个设备id传入

constraints.video = constraintsToChrome(constraints.video);

这一段主要是针对webkit内核浏览器的,也就是chrome。

constraintsToChrome这个方法是我在webRTC的demo源码中找到的,应该是对传入的constraints进行格式化处理的

处理之前

var constraints = {
video: {
sourceId: id
}
};

处理之后:

  var constraints = {
video: {
optional:[
{sourceId: id}
]
}
};

这里我们直接调用就好。

截取图片

进行了上面的这些调整之后,我们就可以实现多个设备之间的选择从而调用到相应的摄像头。

那获取的摄像数据后我们还可以在进行一些扩展,例如截图

我们在上面的代码中添加一个方法即可

function setPhoto(){
var video_element=document.getElementById('video');
//拍照按钮
var screenshotBtn=document.getElementById("screenshot");
//点击拍照
screenshotBtn.onclick=function(){
var canvas=document.createElement('canvas'); //动态创建画布对象
var ctx=canvas.getContext('2d');
var cw=$("#video").width();
var ch=$("#video").height();
canvas.width=cw;
canvas.height=ch;
ctx.fillStyle='#ffffff';
ctx.fillRect(0,0,cw,ch);
ctx.drawImage(video_element,0,0,cw,ch); //将video对象内指定的区域捕捉绘制到画布上指定的区域,可进行不等大不等位的绘制。
console.info(canvas);
//渲染canvas
$("body").append(canvas);
//canvas转换成base64位的数据的图片
var imageData=canvas.toDataURL();
//渲染图片
$("body").append('<img src="'+imageData+'" id="canvasImg">');
}
}

上面的方法基于canvas即可实现获取到屏幕中video部分的像素点生成一个canvas画布以及一张相关的图片。

关于webRTC的相关内容还有很多,这里只是简单的说明了PC端video部分的使用,另外还有audio部分的内容以及移动端的具体事件这里就不一一展开了,有兴趣的同学也可以进行研究。

关于webRTC中video的使用实践的更多相关文章

  1. Android IOS WebRTC 音视频开发总结(八十七)-- WebRTC中丢包重传NACK实现分析

    本文主要介绍WebRTC中丢包重传NACK的实现,作者:weizhenwei ,文章最早发表在编风网,微信ID:befoio 支持原创,转载必须注明出处,欢迎关注我的微信公众号blacker(微信ID ...

  2. Android IOS WebRTC 音视频开发总结(八十六)-- WebRTC中RTP/RTCP协议实现分析

    本文主要介绍WebRTC中的RTP/RTCP协议,作者:weizhenwei ,文章最早发表在编风网,微信ID:befoio 支持原创,转载必须注明出处,欢迎关注我的微信公众号blacker(微信ID ...

  3. WebRTC中的NetEQ

    NetEQ使得WebRTC语音引擎能够快速且高解析度地适应不断变化的网络环境,确保了音质优美且缓冲延迟最小,其集成了自适应抖动控制以及丢包隐藏算法. WebRTC和NetEQ概述 WebRTC Web ...

  4. webRTC中语音降噪模块ANS细节详解(一)

    ANS(adaptive noise suppression) 是webRTC中音频相关的核心模块之一,为众多公司所使用.从2015年开始,我在几个产品中使用了webRTC的3A(AEC/ANS/AG ...

  5. webrtc中APM(AudioProcessing module)的使用

    一,实例化和配置 AudioProcessing* apm = AudioProcessing::Create(0); //这里的0指的是channelID,只是一个标注那个通道的表示 apm-> ...

  6. Redis在WEB开发中的应用与实践

    Redis在WEB开发中的应用与实践 一.Redis概述: Redis是一个功能强大.性能高效的开源数据结构服务器,Redis最典型的应用是NoSQL.但事实上Redis除了作为NoSQL数据库使用之 ...

  7. webrtc中的带宽自适应算法

    转自:http://www.xuebuyuan.com/1248366.html webrtc中的带宽自适应算法分为两种: 1, 发端带宽控制, 原理是由rtcp中的丢包统计来动态的增加或减少带宽,在 ...

  8. NET中异常处理的最佳实践

    NET中异常处理的最佳实践 本文翻译自CodeProject上的一篇文章,原文地址. 目录 介绍 做最坏的打算 提前检查 不要信任外部数据 可信任的设备:摄像头.鼠标以及键盘 “写操作”同样可能失效 ...

  9. Lazy<T>在Entity Framework中的性能优化实践

    Lazy<T>在Entity Framework中的性能优化实践(附源码) 2013-10-27 18:12 by JustRun, 328 阅读, 4 评论, 收藏, 编辑 在使用EF的 ...

随机推荐

  1. [AngularJS] $scope.$watch

    /** * Created by Answer1215 on 11/13/2014. */ function MainCtrl($scope){ function isLongEnough (pwd) ...

  2. 理解JAVASCRIPT 中hasOwnProperty()和isPrototypeOf的作用

    hasOwnProperty:是用来判断一个对象是否有你给出名称的属性或对象.不过需要注意的是,此方法无法检查该对象的原型链中是否具有该属性,该属性必须是对象本身的一个成员.格式如下: 1. 示例一: ...

  3. UNIX网络编程读书笔记:名字与地址转换

    概述 在名字和数值地址间进行转换的函数: gethostbyname和gethostbyaddr:在主机名字与IPv4地址之间进行转换.仅仅支持IPv4. getservbyname和getservb ...

  4. Unity发布各平台路径

    #if UNITY_EDITOR string filepath = Application.dataPath + "/StreamingAssets"; #elif UNITY_ ...

  5. python2.7里的StringIO.StringIO与BytesIO有什么区别

    import StringIO与from io import BytesIO的区别 open()函数返回的文件对象取决于模式.当使用文本模式打开文件时,它返回一个TextIOBase的子类.当使用二进 ...

  6. JQuery 之CSS操作

    JQuery 之CSS操作 设置 <p> 元素的颜色: 将所有段落的颜色设为红色 $(".btn1").click(function(){ $("p" ...

  7. Android SDK 更新和下载慢怎么办?

    博客搬家:因为各种原因,我如今的博客将首发于blog.mojijs.com, 能够百度搜索 "姜哥的墨迹技术博客" , 或者 点击这里 本文地址 http://blog.mojij ...

  8. Linux常用命令整理(脑图)

  9. dom与jquery互相转换

    /*取得<input>标签中的value属性的内容[dom对象->jquery对象] var inputElement = document.getElementById(" ...

  10. EMQ 压测问题

    一.单台服务器最高只能跑2W多一点问题描述 一直使用benchmark测试单台EMQ都没有超过3W链接数 一个独立的外网IP只能提供最多6W的端口号,但每个TCP需要分配一个指定的端口号.所以理论上讲 ...