webrtc之摄像头的基本使用

263 阅读7分钟

上一章节简单介绍了webrtc的几个组成部分。那么从这章节开始,将逐个介绍每个部分的使用和作用。本小结就是介绍媒体流的基础功能之使用摄像头。

得益于webrtc,我们可以用三行简单的代码就可以在浏览器中调用麦克风和摄像头,像下面这样:

// 使用的vue项目
const sream = navigator.mediaDevices.getUserMedia()
videoRef.value.srcObject = newStream;
audioRef.value.srcObject = newStream;

就是这么简单,调用一个api就可以轻松拿到媒体流,然后赋值给video标签或者audio标签即可。当然,前提是你的有麦克风、摄像头或者虚拟的也行。

那么,现在就来介绍下navigator.mediaDevices.getUserMedia()。这里是webrtc的官方文档介绍,英文能力强的也可以直接从这里看。

getUserMedia

它在官方文档中的定义如下:

partial interface Navigator {
  [SecureContext] undefined getUserMedia(MediaStreamConstraints constraints,
                                    NavigatorUserMediaSuccessCallback successCallback,
                                    NavigatorUserMediaErrorCallback errorCallback);
};

可以看到,它可以接收三个参数,分别是约束参数、成功后回调参数和失败回调参数。所以,它也可以使用Promise形式去调用。

在使用时,你可以什么参数都不传递,它也会自动去获取媒体流,如果获取成功则返回媒体流;获取失败,比如用户拒绝了浏览器访问媒体设备(摄像头和麦克风)的请求,或者是当前设备没有可用的媒体设备,则会抛出 PermissionDeniedError 或 NotFoundError 等错误。

相对来说,它的成功和失败回调都使用的比较少,一般都会选择使用async await方式来调用,简单便捷。

简单调用即可,没有什么其他要注意的。但是,如果说你需要使用某个特定的摄像头,比如在移动端视频时想要使用后置摄像头,或者在浏览器中同时进行多个视频会议,此时需要使用不同的摄像头。那么就需要在调用这个API时就需要进行传参了,就是它的第一个参数constraints

constraints

它在官方文档中的定义如下:

dictionary MediaStreamConstraints {
  (boolean or MediaTrackConstraints) video = false;
  (boolean or MediaTrackConstraints) audio = false;
};

可以看到,在这个参数中可以同时指定视频和音频。也就是说,想要那个就可以指定那个,也可以都要:

// 只要视频
const sream = navigator.mediaDevices.getUserMedia({
    video: true
})

// 两个都要
const sream = navigator.mediaDevices.getUserMedia({
    video: true,
    audio: true
})

这是比较通用的做法,但实际情况下不会这么去写。因为还要考虑到很多种情况,比如你的网速、你的分辨率还有多个摄像头情况下选取特定的摄像头等,这些都是在写代码时就需要提前预想到的情况。而constraints参数也是想到了这点,它也支持你去定制videoaudio

try {
  const stream = await navigator.mediaDevices.getUserMedia({
    video: {
      width: {min: 640, ideal: 1280},
      height: {min: 480, ideal: 720},
      frameRate: {min: 30},
      advanced: [
        {width: 1920, height: 1280},
        {aspectRatio: 4/3},
        {frameRate: {min: 50}},
        {frameRate: {min: 40}}
      ]
    }
  });
} catch (error) {
  if (error.name != "OverconstrainedError") {
    throw error;
  }
}

在这个例子中,width: {min: 640, ideal: 1280} height: {min: 480, ideal: 720},表示最低配的分辨率为640 x 480,但理想的情况下应该是1280 x 720。就是说min表示托底的,是最后没办法情况下才使用的;而ideal是理想情况下,即优先考虑的,有较高的权重。frameRate: {min: 30}也是同样的,最低使用30fps,越高越好。

advanced则有点不同。有三个不同点:

  1. 是相对于没有写的advanced中的约束条件,写在advanced中的具有较高的优先级。比如width: {min: 640, ideal: 1280},height: {min: 480, ideal: 720}advanced: [{width: 1920, height: 1280}]一起出现时,表示最低配置分辨率是640 x 480,理想情况下是1280 x 720。然后,在同时满足这两个情况下,最想要的配置是1920 x 1280。如果可以满足这个最想要的配置,那么则优先使用。
  2. advanced中的约束条件也是有优先级的,最前面的最优先考虑,如果不满足,则会依次往下考虑。
      advanced: [
        {width: 1920, height: 1280},
        {aspectRatio: 4/3},
        {frameRate: {min: 50}},
        {frameRate: {min: 40}}
      ]

分辨率和宽高比是无法同时满足的,只能满足其一。这种情况下,在advanced中同时设置了分辨率和宽高比,那么谁在前面就会优先使用谁。在本例中,就是优先使用分辨率为1920 x 1280的约束条件。但是假如满足不了这个约束条件,那么就会使用宽高比为4 : 3的约束条件。如果还不满足,则会去使用advanced之外的约束条件。

  1. advanced数组中的每条约束条件要么同时满足要么同时忽略。即 advanced: [ {width: 1920, height: 1280}]中的widthheight必须同时满足,一个不满足都会将整个约束条件忽略,进而考虑下一个约束条件。

当然,还有很多其他参数,在本小节后面会列出来。

在实际环境下,约束条件也是变化的。比如在一般会议中,应该优先考虑画面的流畅性,此时分辨率低一点也没有太大影响;而在共享屏幕时,则应该反过来,优先考虑高分辨率,这样对方能看的清楚一点,此时流畅性稍微差一点也能接受。要注意结合实际场景去修改约束条件。

代码实现

代码很简单,如下所示:

// 获取指定媒体设备id对应的媒体流
const getTargetDeviceMedia = async () => {
  const constraints = {
    video: {
      deviceId: { exact: form.videoId },
    },
    audio: {
      deviceId: { exact: form.audioInId },
    },
    width: form.width,
    height: form.height,
    frameRate: {
      ideal: form.frameRate,
      max: 24,
    },
  };
  return await navigator.mediaDevices
    .getUserMedia(constraints)
    .catch(handleError);
};

Snipaste_2024-04-16_20-27-08.png

拿到对应的摄像头和麦克风之后,就可以将其赋值给video标签就可以正常显示了:


 <video ref="videoRef" autoplay controls muted class="demo1"></video>

const handleClick = async () => {
  const stream = videoRef.value.srcObject;
  // 如果之前已经存在流 则停止
  if (stream) {
    stream.getAudioTracks().forEach((e) => {
      stream.removeTrack(e);
    });
    stream.getVideoTracks().forEach((e) => {
      stream.removeTrack(e);
    });
  }
  // 获取新的流 并赋值
  const newStream = await getTargetDeviceMedia();
  videoRef.value.srcObject = newStream;
};

注意这是里赋值给srcObject,而不是src,不要搞错了。muted表示静音,controls表示视频控件(一般设置为false),autoplay表示自动播放。autoplay 会导致分配给元素的新流自动播放。playsinline 属性允许视频在某些移动浏览器中以内嵌方式播放,而不是仅以全屏模式播放。

image.png

补充constraints

这里在额外的补充一些约束条件(具体在官方文档的4.3.8小节),以供学习参考:

image.png

参数含义
width视频宽度
height视频高度
aspectRatio视频宽高比
frameRate帧率
facingMode使用的摄像头(前置后置),有多个参数
resizeMode是否允许调整图像大小
sampleRate音频采样率
sampleSize音频采样率大小
echoCancellation开启回音消除
autoGainControl开启自动增益
noiseSupperssion开启降噪
labency延迟
channelCount声道数
deviceId设备ID
groupId设置组ID

总结

本小节介绍了如何使用getUserMedia这个API,它是必须要掌握的核心。通过传入不同的约束条件,来实现不同的功能,适应各种各样的场景需求。当然,使用起来还是非常简单的,没什么难度。难的是如何满足不同的场景,这就很考验基本功了。要非常熟悉各种约束条件的使用,多多练习。

现在会使用摄像头、麦克风了,但你怎么知道你的电脑是否支持webrtc呢?这就需要用到MediaDevices这个API了。下一小节将会介绍媒体流的设备检测。

项目地址

前端:gitee.com/yoboom/webr…

后端:gitee.com/yoboom/webr…

项目功能如下:

image.png

感兴趣的可以直接去体验一下,欢迎star和提pr