龙空技术网

WebRTC本地选择codec

端庄优雅青山NAl 105

前言:

现在看官们对“html选择本地文件”大致比较关切,你们都需要知道一些“html选择本地文件”的相关资讯。那么小编也在网摘上收集了一些对于“html选择本地文件””的相关知识,希望大家能喜欢,朋友们快快来了解一下吧!

codec:编码译码器,编解码器。它是一个程序,也可以是算法,或者设备,用于编码(encode)和解码(decode)数据流。

WebRTC能让两个web或者app之间建立音视频通信。通信过程中,数据流的格式必须被两边的设备支持。

WebRTC提供了接口查询支持的codec,并且可以设置要使用的codec。本文演示选择视频codec的过程。

示例

用户可以在发送视频流之前选择codec。把支持的codec类型列出来,用户自行选择。

开启视频后,建立连接前,我们可以选择设置codec。如上图蓝色区域所示。

html

先来准备页面。2个video控件分别显示收发视频。 按钮分别控制开始,呼叫(发起连接)和挂断。

select用来选择codec。获取支持的codec信息,放到下拉栏里让用户选择。

以下是index.html主要内容

<div id="container">    <h1><a href="; title="WebRTC示例,修改codec">WebRTC示例,修改codec</a>    </h1>    <video id="localVideo" playsinline autoplay muted></video>    <video id="remoteVideo" playsinline autoplay></video>    <div class="box">        <button id="startBtn">开始</button>        <button id="callBtn">呼叫</button>        <button id="hangupBtn">挂断</button>    </div>    <div class="box">        <span>选择Codec:</span>        <select id="codecPreferences" disabled>            <option selected value="">Default</option>        </select>        <div id="actualCodec"></div>    </div>    <p>可以在控制台观察 <code>MediaStream</code>, <code>localStream</code>, 和 <code>RTCPeerConnection</code></p></div><script src="../../src/js/adapter-2021.js"></script><script src="js/main.js" async></script>

adapter-2021.js是存放在本地的文件。要使用最新的adapter,按以下地址引入

<script src=";></script>

js

main.js控制主要逻辑。从开启摄像头开始。建立连接前可以选择codec。

建立连接的流程与「WebRTC模拟传输视频流,video通过本地节点peer传输视频流」类似。

获取可用codec

先判断浏览器是否有RTCRtpTransceiver,并且要能支持setCodecPreferences方法

const supportsSetCodecPreferences = window.RTCRtpTransceiver &&  'setCodecPreferences' in window.RTCRtpTransceiver.prototype;

通过RTCRtpSender.getCapabilities('video')获取可支持的codec。 然后把它们放进列表codecPreferences

if (supportsSetCodecPreferences) {    const { codecs } = RTCRtpSender.getCapabilities('video');    codecs.forEach(codec => {      if (['video/red', 'video/ulpfec', 'video/rtx'].includes(codec.mimeType)) {        return;      }      const option = document.createElement('option');      option.value = (codec.mimeType + ' ' + (codec.sdpFmtpLine || '')).trim();      option.innerText = option.value;      codecPreferences.appendChild(option);    });    codecPreferences.disabled = false;  }

相关学习资料推荐,点击下方链接免费报名,先码住不迷路~】

音视频免费学习地址:FFmpeg/WebRTC/RTMP/NDK/Android音视频流媒体高级开发

【免费分享】音视频学习资料包、大厂面试题、技术视频和学习路线图,资料包括(C/C++,Linux,FFmpeg webRTC rtmp hls rtsp ffplay srs 等等)有需要的可以点击788280672加群免费领取~

配置codec

呼叫之前,找到用户选择的codec。 调用transceiver.setCodecPreferences(codecs),把选中的codec交给transceiver

if (supportsSetCodecPreferences) {    // 获取选择的codec    const preferredCodec = codecPreferences.options[codecPreferences.selectedIndex];    if (preferredCodec.value !== '') {      const [mimeType, sdpFmtpLine] = preferredCodec.value.split(' ');      const { codecs } = RTCRtpSender.getCapabilities('video');      const selectedCodecIndex = codecs.findIndex(c => c.mimeType === mimeType && c.sdpFmtpLine === sdpFmtpLine);      const selectedCodec = codecs[selectedCodecIndex];      codecs.splice(selectedCodecIndex, 1);      codecs.unshift(selectedCodec);      console.log(codecs);      const transceiver = pc1.getTransceivers().find(t => t.sender && t.sender.track === localStream.getVideoTracks()[0]);      transceiver.setCodecPreferences(codecs);      console.log('选择的codec', selectedCodec);    }  }

main.js完整代码如下

'use strict';console.log('WebRTC示例,选择codec');// --------- ui准备 ---------const startBtn = document.getElementById('startBtn');const callBtn = document.getElementById('callBtn');const hangupBtn = document.getElementById('hangupBtn');const localVideo = document.getElementById('localVideo');const remoteVideo = document.getElementById('remoteVideo');callBtn.disabled = true;hangupBtn.disabled = true;startBtn.addEventListener('click', start);callBtn.addEventListener('click', call);hangupBtn.addEventListener('click', hangup);// ---------------------------// -------- codec 的配置 --------const codecPreferences = document.querySelector('#codecPreferences');const supportsSetCodecPreferences = window.RTCRtpTransceiver &&  'setCodecPreferences' in window.RTCRtpTransceiver.prototype;// -----------------------------let startTime;remoteVideo.addEventListener('resize', () => {  console.log(`Remote video size changed to ${remoteVideo.videoWidth}x${remoteVideo.videoHeight}`);  if (startTime) {    const elapsedTime = window.performance.now() - startTime;    console.log('视频流连接耗时: ' + elapsedTime.toFixed(3) + 'ms');    startTime = null;  }});let localStream;let pc1;let pc2;const offerOptions = {  offerToReceiveAudio: 1,  offerToReceiveVideo: 1};function getName(pc) {  return (pc === pc1) ? 'pc1' : 'pc2';}function getOtherPc(pc) {  return (pc === pc1) ? pc2 : pc1;}// 启动本地视频async function start() {  console.log('启动本地视频');  startBtn.disabled = true;  try {    const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true });    console.log('获取到本地视频');    localVideo.srcObject = stream;    localStream = stream;    callBtn.disabled = false;  } catch (e) {    alert(`getUserMedia() error: ${e.name}`);  }  if (supportsSetCodecPreferences) {    const { codecs } = RTCRtpSender.getCapabilities('video');    console.log('RTCRtpSender.getCapabilities(video):\n', codecs);    codecs.forEach(codec => {      if (['video/red', 'video/ulpfec', 'video/rtx'].includes(codec.mimeType)) {        return;      }      const option = document.createElement('option');      option.value = (codec.mimeType + ' ' + (codec.sdpFmtpLine || '')).trim();      option.innerText = option.value;      codecPreferences.appendChild(option);    });    codecPreferences.disabled = false;  } else {    console.warn('当前不支持更换codec');  }}// 呼叫并建立连接async function call() {  callBtn.disabled = true;  hangupBtn.disabled = false;  console.log('开始呼叫');  startTime = window.performance.now();  const videoTracks = localStream.getVideoTracks();  const audioTracks = localStream.getAudioTracks();  if (videoTracks.length > 0) {    console.log(`使用的摄像头: ${videoTracks[0].label}`);  }  if (audioTracks.length > 0) {    console.log(`使用的麦克风: ${audioTracks[0].label}`);  }  const configuration = {};  pc1 = new RTCPeerConnection(configuration);  pc1.addEventListener('icecandidate', e => onIceCandidate(pc1, e));  pc2 = new RTCPeerConnection(configuration);  pc2.addEventListener('icecandidate', e => onIceCandidate(pc2, e));  pc2.addEventListener('track', gotRemoteStream);  localStream.getTracks().forEach(track => pc1.addTrack(track, localStream));  if (supportsSetCodecPreferences) {    // 获取选择的codec    const preferredCodec = codecPreferences.options[codecPreferences.selectedIndex];    if (preferredCodec.value !== '') {      const [mimeType, sdpFmtpLine] = preferredCodec.value.split(' ');      const { codecs } = RTCRtpSender.getCapabilities('video');      const selectedCodecIndex = codecs.findIndex(c => c.mimeType === mimeType && c.sdpFmtpLine === sdpFmtpLine);      const selectedCodec = codecs[selectedCodecIndex];      codecs.splice(selectedCodecIndex, 1);      codecs.unshift(selectedCodec);      console.log(codecs);      const transceiver = pc1.getTransceivers().find(t => t.sender && t.sender.track === localStream.getVideoTracks()[0]);      transceiver.setCodecPreferences(codecs);      console.log('选择的codec', selectedCodec);    }  }  codecPreferences.disabled = true;  try {    const offer = await pc1.createOffer(offerOptions);    await onCreateOfferSuccess(offer);  } catch (e) {    console.log(`Failed, pc1 createOffer: ${e.toString()}`);  }}async function onCreateOfferSuccess(desc) {  try {    await pc1.setLocalDescription(desc);    console.log('pc1 setLocalDescription 成功');  } catch (e) {    console.error('pc1 setLocalDescription 出错', e);  }  try {    await pc2.setRemoteDescription(desc);    console.log('pc2 setRemoteDescription ok');  } catch (e) {    console.error('pc2 setRemoteDescription fail', e);  }  try {    const answer = await pc2.createAnswer();    await onCreateAnswerSuccess(answer);  } catch (e) {    console.log(`pc2 create answer fail: ${e.toString()}`);  }}function gotRemoteStream(e) {  if (remoteVideo.srcObject !== e.streams[0]) {    remoteVideo.srcObject = e.streams[0];    console.log('pc2 received remote stream');  }}// 应答(接收)成功async function onCreateAnswerSuccess(desc) {  console.log(`Answer from pc2:\n${desc.sdp}`);  console.log('pc2 setLocalDescription start');  try {    await pc2.setLocalDescription(desc);  } catch (e) {    console.error('pc2 set local d fail', e);  }  console.log('pc1 setRemoteDescription start');  try {    await pc1.setRemoteDescription(desc);    // Display the video codec that is actually used.    setTimeout(async () => {      const stats = await pc1.getStats();      stats.forEach(stat => {        if (!(stat.type === 'outbound-rtp' && stat.kind === 'video')) {          return;        }        const codec = stats.get(stat.codecId);        document.getElementById('actualCodec').innerText = 'Using ' + codec.mimeType +          ' ' + (codec.sdpFmtpLine ? codec.sdpFmtpLine + ' ' : '') +          ', payloadType=' + codec.payloadType + '. Encoder: ' + stat.encoderImplementation;      });    }, 1000);  } catch (e) {    console.error(e);  }}async function onIceCandidate(pc, event) {  try {    await (getOtherPc(pc).addIceCandidate(event.candidate));    onAddIceCandidateSuccess(pc);  } catch (e) {    onAddIceCandidateError(pc, e);  }  console.log(`${getName(pc)} ICE candidate:\n${event.candidate ? event.candidate.candidate : '(null)'}`);}function onAddIceCandidateSuccess(pc) {  console.log(`${getName(pc)} addIceCandidate success`);}function onAddIceCandidateError(pc, error) {  console.log(`${getName(pc)} failed to add ICE Candidate: ${error.toString()}`);}localVideo.addEventListener('loadedmetadata', function () {  console.log(`Local video videoWidth: ${this.videoWidth}px,  videoHeight: ${this.videoHeight}px`);});remoteVideo.addEventListener('loadedmetadata', function () {  console.log(`Remote video videoWidth: ${this.videoWidth}px,  videoHeight: ${this.videoHeight}px`);});// 挂断function hangup() {  console.log('挂断');  pc1.close();  pc2.close();  pc1 = null;  pc2 = null;  hangupBtn.disabled = true;  callBtn.disabled = false;  codecPreferences.disabled = false;}
codec信息说明

观察控制台,打印出了可用codec信息(Mac,97.0.4692.71(正式版本)x86_64)。主要关注下面3种

{clockRate: 90000, mimeType: 'video/VP8'}{clockRate: 90000, mimeType: 'video/VP9', sdpFmtpLine: 'profile-id=0'}{clockRate: 90000, mimeType: 'video/H264', sdpFmtpLine: 'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f'}

clockRate 是codec的时钟频率,单位hz

sdpFmtpLine 是codec的SDP里a=fmtp的参数信息

mimeType 里说的是视频编码类型,常见的有VP8和H264等等

支持WebRTC的浏览器,必须要支持视频codec VP8和H264

VP8与VP9

2010年5月Google收购了On2 Technologies,获得了VP8。 Opera,FireFox,Chrome和Chromium支持HTML5中的video播放VP8视频。

WebM作为一个容器格式,图像部分使用VP8,音频使用Vorbis和Opus。

VP9由Google开发,一个开放的无版权费的视频编码标准。开发初期曾用名“Next Gen Open Video”。VP9也被视为是VP8的下一代视频编码标准。

H264

H.264,又称为MPEG-4第10部分,高级视频编码是一种面向块,基于运动补偿的视频编码标准。 到2014年,它已经成为高精度视频录制、压缩和发布的最常用格式之一。

优势:

1)网络亲和性,即可适用于各种传输网络2)高的视频压缩比

目前我们用的比较多的还是H264。

效果预览

网页效果请参考 选择codec

扩展阅读WebRTC模拟传输视频流,video通过本地节点peer传输视频流H.264简介Codecs used by WebRTCAudio_codecs

原文 WebRTC本地选择codec - 掘金

标签: #html选择本地文件