[代码重构](master): 使用浏览器给自己拍照

2022年11月28日13:11:50
master
土豆兄弟 2 years ago
parent 742fb4335f
commit 961786790e

@ -338,9 +338,199 @@ navigator.mediaDevices.enumerateDevices()
- 第二次,如果用户视频检测通过了,再次调用 getUserMedia API 时,则只采集音频数据。由于音频数据不能直接展示,所以需要使用 JavaScript 中的 AudioContext 对象,将采集到的音频计算后,
再将其绘制到页面上。这样,当用户看到音频数值的变化后,说明音频设备也是有效的。
### 2.3 使用浏览器给自己拍照
- 在正式讲解如何进行拍照之前,你需要先了解**非编码帧(解码帧)和编码帧**这两个知识点,这会有利于你对后面拍照实现内容的理解。
- 非编码帧
- 当你要播放某个视频文件时,播放器会按照一定的时间间隔连续地播放从音视频文件中解码后的视频帧,这样视频就动起来了。同理,播放从摄像头获取的视频帧也是如此,只不过从摄像头获取得本来就是**非编码视频帧**,所以就不需要解码了。
- 通过上面的描述,你应该能得到以下两点信息:
- 播放的视频帧之间的时间间隔是非常小的。如按每秒钟 20 帧的帧率计算,每帧之间的间隔是 50ms。
- 播放器播的是**非编码帧(解码后的帧)**,这些非编码帧就是一幅幅独立的图像。
- 从摄像头里采集的帧或通过解码器解码后的帧都是**非编码帧**。非编码帧的格式一般是 YUV 格式或是 RGB 格式。
- 编码帧
- 相对于非编码帧,通过编码器(如 H264/H265、VP8/VP9压缩后的帧称为**编码帧**。这里我们以 H264 为例,经过 H264 编码的帧包括以下三种类 型。
- I 帧:关键帧。压缩率低,可以单独解码成一幅完整的图像。
- P 帧:参考帧。压缩率较高,解码时依赖于前面已解码的数据。
- B 帧:前后参考帧。压缩率最高,解码时不光依赖前面已经解码的帧,而且还依赖它后面的 P 帧。换句话说就是,**B 帧后面的 P 帧要优先于它进行解码,然后才能将 B 帧解码**。
- 通过上面的介绍,现在你应该已经清楚地知道了:**从播放器里获取的视频帧一定是非编码帧。也就是说,拍照的过程其实是从连续播放的一幅幅画面中抽取正在显示的那张画面**。
- 如何获取视频流
- 在获得照片之前,你首先要通过浏览器的 API 获取视频流,并通过 HTML5 的 <video> 标签将视频播放出来。
```html
<html>
<head>
<title>WebRTC take picture</title>
</head>
<body>
<video autoplay playsinline id="player">
<script src="./js/client.js"></script>
</body>
</html>
```
- 上面这段代码很简单,就是定义了一个 video 标签,用于播放从摄像头获取到的视频流。另外,它还引入了一段 JavaScript 脚本:
```js
'use strict'
// 获取 HTML 页面中的 video 标签
var videoplay = document.querySelector('video#player');
// 播放视频流
function gotMediaStream(stream){
videoplay.srcObject = stream;
}
function handleError(err){
console.log('getUserMedia error:', err);
}
// 对采集的数据做一些限制
var constraints = {
video : {
width: 1280,
height: 720,
frameRate:15,
},
audio : false
}
// 采集音视频数据流
navigator.mediaDevices.getUserMedia(constraints)
.then(gotMediaStream)
.catch(handleError);
```
- 在这段脚本中,我们调用了之前所讲的**getUserMedia**方法,该方法会打开摄像头,并通过它采集音视频流。然后再将采集到的视频流赋值给
HTML 中定义的**video标签的srcObject字段**这样video标签就可以从摄像头源源不断地获得视频帧并将它播放出来了。
- 如何拍照
- 实际上,浏览器提供了一个非常强大的对象,称为 **Canvas**。你可以把它想像成一块画布,既可以在这块画布上画上点、线或各种图形,也可以将一幅画直接绘制到上面。
- 在浏览器中Canvas 的功能非常强大,可以处理很多图表方面的事情
- 首先,在 HTML 中增加以下代码:
```html
...
<button id="TakePhoto">Take</button>
...
<canvas id="picture"></canvas>
...
```
- 上面的 HTML 代码段,包括一个 <canvas> 标签和一个 <button> 标签。我们的设想是,当点击拍照按钮时,就可以从视频流中获取到一张当时正在显示的图片了。
- 显然,光有 HTML 部分肯定是不行的,还需要下面的 JavaScript 脚本进行控制。增加 JavaScript 代码如下:
```js
...
var picture = document.querySelector('canvas#picture');
picture.width = 640;
picture.height = 480;
...
picture.getContext('2d').drawImage(videoplay, 0, 0, picture.width, picture.height);
...
```
- 在上面的 JavaScript 代码中,首先获得 HTML 中的 Canvas 标签,并设置了 Canvas 的宽高; 然后调用 Canvas 上下文的 drawImage 方法,这样就可以从视频流中抓取当时正在显示的图片了。
- 这里最关键的点就是 **drawImage** 方法,其方法格式如下:
```js
void ctx.drawImage(image, dx, dy, dWidth, dHeight);
```
- image可以是一幅图片或 HTMLVideoElement。
- dx, dy图片起点的 x、y 坐标。
- dWidth图片的宽度。
- dHeight图片的高度。
- 该方法的第一个参数特别重要,它既可以是一幅图片,也可以是一个 Video 元素。而 HTML 中的 <video> 标签就是一个 video 元素,所以它可以当作是 drawImage 方法的第一个参数。
这样就可以通过 Canvas 获取到照片了。
- 如何保存照片
- 照片拍好后,如何将它保存到本地文件系统中呢? 浏览器同样给我们提供了非常方便的方法,让我们来看一下具体代码吧。
- HTML 要先增加如下代码:
```html
...
<div>
<button id="save"> 保存 </button>
</div>
...
```
- 也就是当你点击保存这个 <button> 的时候,就可以将前面 Canvas 抓取的图片保存下来。不过,<button>只是触发一个事件,真正做事儿的是下面的 JavaScript 代码。具体逻辑如下:
```js
...
function downLoad(url){
var oA = document.createElement("a");
oA.download = 'photo';// 设置下载的文件名,默认是'下载'
oA.href = url;
document.body.appendChild(oA);
oA.click();
oA.remove(); // 下载之后把创建的元素删除
}
...
document.querySelector("button#save").onclick = function (){
downLoad(canvas.toDataURL("image/jpeg"););
}
....
```
- 在上面的代码中,当用户点击保存按钮时,会调用一个匿名函数。该函数的逻辑如下:
- 首先,通过 Canvas 的 toDataURL 方法获得图片的 URL 地址;
- 然后,将该 URL 地址当作参数传给 downLoad 函数;
- 最后downLoad 函数做的事儿比较简单,就是创建一个<a>标签,当用户点击时就将图片下载下来。
- 通过上面的代码,你就可以通过浏览器为自己拍照,并同时将拍下来的照片保存到文件系统中了。
- 如何实现滤镜
- 从视频流中获取到照片后,你还可以通过滤镜为照片增加点特效,这样会让你的照片更加特别。
- 在浏览器中对于图片的滤镜处理是通过 CSS 来控制的。像前面一样,首先在 HTML 中增加 CSS 的滤镜代码如下:
```html
...
<head>
<style>
.none {
-webkit-filter: none;
}
.blur {
-webkit-filter: blur(3px);
}
.grayscale {
-webkit-filter: grayscale(1);
}
.invert {
-webkit-filter: invert(1);
}
.sepia {
-webkit-filter: sepia(1);
}
</style>
</head>
<body>
...
<select id="filter">
<option value="none">None</option>
<option value="blur">blur</option>
<option value="grayscale">Grayscale</option>
<option value="invert">Invert</option>
<option value="sepia">sepia</option>
</select>
...
</body>
```
- 上面的 HTML 代码中定义了以下四种 CSS 滤镜。
- blur模糊度
- grayscale灰度黑白
- invert反转
- sepia深褐色
- 并增加了一个 <select> 标签,以便让用户选择使用不同的滤镜。但最终的控制还是由下面的 JavaScript 脚本来做的JavaScript 代码如下:
```js
...
picture.className = filtersSelect.value;
...
```
- 没错,只需要这样简单的一行代码,你就可以将不同的滤镜应用于获取的照片上

Loading…
Cancel
Save