WebRTC音视频开发读书笔记(三)

当采集音频或视频时,设备会源源不断地产生媒体数据,这些数据就是媒体流,从Canvas,桌面,摄像头捕获的流为视频流,从麦克风捕获的的流称为音频流,媒体流中混入的可能是多种数据 ,因此WebRTC又将其划分成多个轨道,每个轨道对应于具体的设备。

五、媒体流与轨道

本文涉及的API如表所示:

1、媒体流

通过MediaStream接口处理媒体流,一个流包含多个视频流轨道和音频轨道,有两种方法可以输出 媒体流,其一,可以将输出显示为视频或者音频元素; 其二,可以将输出发送到RTCPeerConection对象,然后将其发送到远程计算机。媒体流可以通过摄像头、麦克风、屏幕、画布、视频源、远端流等方式获取。下面简要介绍属性、事件、方法。

(1)属性

active: 流的活动状态,活动为true ,否则为false

ended: 结束状态,结束为true,否则为false

id: 唯一标识符

(2)事件

onactive: 当MediaStream对象变为活动状态时触发活动状态。

onended: 当媒体流终止时触发此事件。

oninactive: 当流变为非活动状态时,触发此事件。

onremovetrack: 从它移除MediaStreamTrack 对象时触发此事件。

(3)方法

addTrack :增加MediaStreamTrack

clone: 使用新id 返回MediaStream对象的克隆。

getAudioTracks: 获取音频轨道列表

getTrackById : 通过id返回媒体流轨道。

2、媒体轨道

MediaStreamTrack表示一段媒体源,是WebRTC的基本媒体单元,每一个轨道都有一个源与之关联。媒体流有两个输出渠道,一是video标签,二是通过RTCPeerConnection发送到远端。

(1)属性

enabled: 布尔值, 轨道有效性

kind: 字符值,audio为音频,video为视频。

muted: 布尔值 :静音状态,true为静音。

readyState: 布尔值,轨道当前状态。

remote: 布尔值,数据通过RTCPeerConnection提供状态,true是远端传输数据。

(2) 事件

started: 轨道开始事件

ended: l轨道结束事件

(3) 方法

getConstraints : 获取轨道采用的约束条件。

applyConstraints: 应用约束条件至轨道。

3、流与轨道API测试示例

此示例用于测试音视频轨道的获取和删除等方法,主要有以下步骤:

(1)设置约束条件,启用音频和视频。

(2)使用 getUserMedia访问设备并获取到MediaStream对象,然后添加操作流的方法,如下所示:

获取音频轨道列表、根据id获取轨道列表、删除音频轨道、获取所有轨道、获取视频轨道列表、删除轨道列表。

(3)添加每个方法的测试按钮。

完整代码如下所示:

javascript 复制代码
import React from "react";
import { Button } from "antd";

//MediaStream对象
let stream;
/**
 * 摄像头使用示例
 */
class MediaStreamAPI extends React.Component {
    constructor() {
        super();
    }

    componentDidMount() {
        this.openDevice();
    }

    //打开音视频设备
    openDevice = async () => {
        try {
            //根据约束条件获取媒体
            stream = await navigator.mediaDevices.getUserMedia({
                //启用音频
                audio: true,
                //启用视频
                video: true
            });
            let video = document.getElementById("myVideo");
            video.srcObject = stream;
        } catch (e) {
            console.log(`getUserMedia错误:` + error);
        }
    }

    //获取音频轨道列表
    btnGetAudioTracks = () => {
        console.log("getAudioTracks");
        //返回一个数据
        console.log(stream.getAudioTracks());
    }

    //根据Id获取音频轨道
    btnGetTrackById = () => {
        console.log("getTrackById");
        console.log(stream.getTrackById(stream.getAudioTracks()[0].id));
    }

    //删除音频轨道
    btnRemoveAudioTrack = () => {
        console.log("removeAudioTrack()");
        stream.removeTrack(stream.getAudioTracks()[0]);
    }

    //获取所有轨道,包括音频及视频
    btnGetTracks = () => {
        console.log("getTracks()");
        console.log(stream.getTracks());
    }

    //获取视频轨道列表
    btnGetVideoTracks = () => {
        console.log("getVideoTracks()");
        console.log(stream.getVideoTracks());
    }

    //删除视频轨道
    btnRemoveVideoTrack = () => {
        console.log("removeVideoTrack()");
        stream.removeTrack(stream.getVideoTracks()[0]);
    }

    render() {
        return (
            <div className="container">
                <h1>
                    <span>MediaStreamAPI测试</span>
                </h1>
                <video className="video" id="myVideo" autoPlay playsInline></video>
                <Button onClick={this.btnGetTracks} style={{width:'120px'}}>获取所有轨道</Button>
                <Button onClick={this.btnGetAudioTracks} style={{width:'120px'}}>获取音频轨道</Button>
                <Button onClick={this.btnGetTrackById} style={{width:'200px'}}>根据Id获取音频轨道</Button>
                <Button onClick={this.btnRemoveAudioTrack} style={{width:'120px'}}>删除音频轨道</Button>
                <Button onClick={this.btnGetVideoTracks} style={{width:'120px'}}>获取视频轨道</Button>
                <Button onClick={this.btnRemoveVideoTrack} style={{width:'120px'}}>删除视频轨道</Button>
            </div>
        );
    }
}
//导出组件
export default MediaStreamAPI;

六、媒体录制

1、MediaRecorder

影像及声音保存在某些场景下是必要的,目的是便于日后回放,在W3C制定的标准中,MediaRecorder是控制媒体录制的API,它给我们的网页赋予了录制音频和视频的能力,使得web可以脱离服务器,客户端辅助,独立进行媒体的录制。

MediaRecorder的语法如下所示:

javascript 复制代码
var mediaRecorder=new MediaRecorder(stream,options)

参数 stream是媒体流数据源,可以从 getUserMedia获取,也可以从<video>、<audio>、<canvas>标签获取。

参数options是限制选项,表示一个字典对象,包含下列属性:

mineType: 指定录制的媒体类型,音频还是视频,编码方式。

audioBitsPerSound: 指定音频的比特率

videoBitsPerSound: 指定视频的比特率

bitsPerSecond: 指定音频和视频比特率

注意: 默认视频比特率为: 2.5Mbps, 音频的比特率为自适应.

mineType指定录制容器的MIME类型,在应用中通过调用MediaRecorder.isTypeSuupported()检查浏览器是否支持此种mineType. mineType的类型及说明如图所示:

mineType示例代码如下所示:

javascript 复制代码
var options={mineType:  'video/webm;codecs=vp8'}

此代码表示:设置成webm格式视频,编码格式为vp8。

常用API如下所示:

start 开始录制媒体

stop: 停止录制媒体

ondataavailable: 数据有效时触发此事件。

onerror: 当有错误时触发此事件。

2、录制音频示例

此示例主要有以下步骤

(1)定义状态和变量

(2)获取音频数据

(3)录制音频

(4) 播放音频

完整示例代码如下所示:

javascript 复制代码
import React from "react";
import { Button, } from "antd";


//录制对象
let mediaRecorder;
//录制数据
let recordedBlobs;
//音频播放对象
let audioPlayer;
/**
 * 录制音频示例
 */
class RecordAudio extends React.Component {
  constructor() {
    super();
    //初始操作状态
    this.state = {
      status: 'start',
    }
  }

  componentDidMount() {
    //获取音频播放器
    audioPlayer =document.getElementById('audioPlayer');
  }

  //点击打开麦克风按钮
  startClickHandler = async (e) => {
    try {
      //获取音频数据流
      const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
      console.log('获取音频stream:', stream);
      //将stream与window.stream绑定
      window.stream = stream;

      //设置当前状态为startRecord
      this.setState({
        status: 'startRecord',
      });

    } catch (e) {
      //发生错误
      console.error('navigator.getUserMedia error:', e);
    }
  }

  //开始录制
  startRecordButtonClickHandler = (e) => {
    recordedBlobs = [];
    //媒体类型
    let options = { mineType: 'audio/ogg;' };
    try {
      //初始化MediaRecorder对象,传入音频流及媒体类型
      mediaRecorder = new MediaRecorder(window.stream, options);
    } catch (e) {
      console.error('MediaRecorder创建失败:', e);
      return;
    }

    //录制停止事件回调
    mediaRecorder.onstop = (event) => {
      console.log('Recorder stopped: ', event);
      console.log('Recorded Blobs: ', recordedBlobs);
    };
    //当数据有效时触发的事件,可以把数据存储到缓存区里
    mediaRecorder.ondataavailable = this.handleDataAvailable;
    //录制10秒
    mediaRecorder.start(10);
    console.log('MediaRecorder started', mediaRecorder);

    //设置当前状态为stopRecord
    this.setState({
      status: 'stopRecord',
    });
  }

  //停止录制
  stopRecordButtonClickHandler = (e) => {
    mediaRecorder.stop();
    //设置当前状态为play
    this.setState({
      status: 'play',
    });
  }

  //播放录制数据
  playButtonClickHandler = (e) => {
    //生成blob文件,类型为audio/ogg
    const blob = new Blob(recordedBlobs, { type: 'audio/ogg' });

    audioPlayer.src = null;
    //根据blob文件生成播放器的数据源
    audioPlayer.src = window.URL.createObjectURL(blob);
    //播放声音
    audioPlayer.play();
    //设置当前状态为download
    this.setState({
      status: 'download',
    });
  }

  //下载录制文件
  downloadButtonClickHandler = (e) => {
    //生成blob文件,类型为audio/ogg
    const blob = new Blob(recordedBlobs, { type: 'audio/ogg' });
    //URL.createObjectURL()方法会根据传入的参数创建一个指向该参数对象的URL
    const url = window.URL.createObjectURL(blob);
    //创建a标签
    const a = document.createElement('a');
    a.style.display = 'none';
    a.href = url;
    //设置下载文件
    a.download = 'test.ogg';
    //将a标签添加至网页上去
    document.body.appendChild(a);
    a.click();
    setTimeout(() => {
      document.body.removeChild(a);
      //URL.revokeObjectURL()方法会释放一个通过URL.createObjectURL()创建的对象URL.
      window.URL.revokeObjectURL(url);
    }, 100);
    //设置当前状态为start
    this.setState({
      status: 'start',
    });
  }

  //录制数据回调事件
  handleDataAvailable = (event) => {
    console.log('handleDataAvailable', event);
    //判断是否有数据
    if (event.data && event.data.size > 0) {
      //将数据记录起来
      recordedBlobs.push(event.data);
    }
  }

  render() {

    return (
      <div className="container">
        <h1>
          <span>音频录制</span>
        </h1>

        {/* 音频播放器,播放录制音频 */}
        <audio id="audioPlayer" controls autoPlay></audio>

        <div>
          <Button
            className="button"
            onClick={this.startClickHandler}
            disabled={this.state.status != 'start'}>
            打开麦克风
            </Button>
          <Button
            className="button"
            disabled={this.state.status != 'startRecord'}
            onClick={this.startRecordButtonClickHandler}>
            开始录制
          </Button>
          <Button
            className="button"
            disabled={this.state.status != 'stopRecord'}
            onClick={this.stopRecordButtonClickHandler}>
            停止录制
          </Button>
          <Button
            className="button"
            disabled={this.state.status != 'play'}
            onClick={this.playButtonClickHandler}>
            播放
          </Button>
          <Button
            className="button"
            disabled={this.state.status != 'download'}
            onClick={this.downloadButtonClickHandler}>
            下载
            </Button>
        </div>
      </div>
    );
  }
}
//导出组件
export default RecordAudio;

3、其它媒体的录制

(1)视频的录制

获取媒体流的区别

javascript 复制代码
//约束条件
    let constraints = {
      //开启音频
      audio: true,
      //设置视频分辨率为1280*720
      video: {
        width: 1280, height: 720
      }


 //获取音视频流
      const stream = await navigator.mediaDevices.getUserMedia(constraints);

播放器的区别

javascript 复制代码
  {/* 视频预览 muted表示默认静音 */}
        <video className="small-video" id="videoPreview" playsInline autoPlay muted></video>
         {/* 视频回放 loop表示循环播放 */}
        <video className="small-video" id="videoPlayer" playsInline loop></video>

mineType区别

javascript 复制代码
let options = { mimeType: 'video/webm;codecs=vp9' };
(2)录制屏幕

获取媒体流的区别

javascript 复制代码
//调用getDisplayMedia方法,约束设置成{video:true}即可
      stream = await navigator.mediaDevices.getDisplayMedia({
        //设置屏幕分辨率
        video: {
          width: 2880, height: 1800
        }
      });

播放器的区别

javascript 复制代码
 {/* 捕获屏幕数据渲染 */}
        <video className="video" ref="myVideo" autoPlay playsInline></video>

mineType区别

javascript 复制代码
 //创建MediaRecorder对象,准备录制
      mediaRecorder = new MediaRecorder(window.stream, { mimeType: 'video/webm' });
(3)录制canvas

获取媒体流的区别

javascript 复制代码
stream = canvas.captureStream(10);

播放器的区别

javascript 复制代码
 {/* 画布Canvas容器 */}
                    <div className="small-canvas">
                        {/* Canvas不设置样式 */}
                        <canvas ref='canvas'></canvas>
                    </div>
                    <video className="small-video" ref='video' playsInline autoPlay></video>

mineType区别

javascript 复制代码
//创建MediaRecorder对象,准备录制
            mediaRecorder = new MediaRecorder(window.stream, { mimeType: 'video/webm' });
相关推荐
安步当歌1 小时前
【WebRTC】视频发送链路中类的简单分析(下)
网络·音视频·webrtc·视频编解码·video-codec
从后端到QT2 小时前
WebRTC API分析
webrtc
lqj_本人2 小时前
鸿蒙next版开发:音频并发策略扩展(ArkTS)
音视频
红米饭配南瓜汤16 小时前
WebRTC视频 04 - 视频采集类 VideoCaptureDS 中篇
音视频·webrtc·媒体
杨武博19 小时前
音频格式转换
android·音视频
Learning改变世界20 小时前
ubuntu24.04播放语音视频
音视频
棱角~~21 小时前
10款PDF合并工具的使用体验与推荐!!
经验分享·其他·音视频·实时音视频·学习方法
红米饭配南瓜汤21 小时前
WebRTC视频 02 - 视频采集类 VideoCaptureModule
音视频·webrtc·媒体
gomogomono21 小时前
HDR视频技术之二:光电转换与 HDR 图像显示
音视频·hdr
cuijiecheng201821 小时前
音视频入门基础:MPEG2-TS专题(4)——使用工具分析MPEG2-TS传输流
音视频