草庐IT

javascript - MediaStream 同时捕获 Canvas 和音频

coder 2024-05-07 原文

我正在从事一个项目,我想:

  1. 加载视频 js 并将其显示在 Canvas 上。
  2. 使用滤镜改变 Canvas (以及视频)的外观。
  3. 使用 MediaStream captureStream() 方法和 MediaRecorder 对象记录 Canvas 表面和原始视频的音频。
  4. 在 HTML 视频元素中播放 Canvas 和音频流。

通过调整以下 WebRTC 演示代码,我已经能够在视频元素中显示 Canvas 记录:https://webrtc.github.io/samples/src/content/capture/canvas-record/

就是说,我不知道如何在 Canvas 旁边录制视频的音频。是否可以创建一个包含来自两个不同源/元素的 MediaStreamTrack 实例的 MediaStream?

根据 MediaStream API 的规范,理论上应该有一些方法可以实现这一点: https://w3c.github.io/mediacapture-main/#introduction

“MediaStream API 中的两个主要组件是 MediaStreamTrack 和 MediaStream 接口(interface)。MediaStreamTrack 对象表示源自用户代理中一个媒体源的单一类型的媒体,例如网络摄像头生成的视频。 MediaStream 用于将多个 MediaStreamTrack 对象分组到一个单元中,该单元可以在媒体元素中记录或呈现。”

最佳答案

Is it possible to create a MediaStream containing MediaStreamTrack instances from two different sources/elements?

是的,您可以使用 MediaStream.addTrack() 来做到这一点方法,或 new MediaStream([track1, track2]) .


OP 已经知道如何获取所有这些,但这里提醒 future 的读者:

  • <canvas> 获取视频流轨道,您可以调用 canvas.captureStream(framerate) 方法。

  • <video> 获取音频流轨道您可以使用 Web Audio API 元素,它是 createMediaStreamDestination 方法。 这将返回 MediaStreamAudioDestinationNode节点 ( dest ) 包含我们的音频流。然后你必须连接一个 MediaElementAudioSourceNode 从您的 <video> 创建元素,到这个dest . 如果您需要向此流添加更多音轨,您应该将所有这些源连接到 dest .

现在我们有两个流,一个用于 <canvas>视频和音频,我们可以在初始化记录器之前将音轨添加到 Canvas 流中:

canvasStream.addTrack(audioStream.getAudioTracks()[0]);
const recorder = new MediaRecorder(canvasStream)

或者我们可以创建第三个 MediaStream来自这两个轨道的对象:

const [videoTrack] = canvasStream.getVideoTracks();
const [audioTrack] = audioStream.getAudioTracks();
const recordedStream = new MediaStream(videoTrack, audioTrack)
const recorder = new MediaRecorder(recordedStream);

这是一个完整的例子:

var
  btn = document.querySelector("button"),
  canvas,
  cStream,
  aStream,
  vid,
  recorder,
  analyser,
  dataArray,
  bufferLength,
  chunks = [];

function clickHandler() {

  btn.textContent = 'stop recording';
  
  if (!aStream) {
    initAudioStream();
  }
  
  cStream = canvas.captureStream(30);
  cStream.addTrack(aStream.getAudioTracks()[0]);

  recorder = new MediaRecorder(cStream);
  recorder.start();

  recorder.ondataavailable = saveChunks;

  recorder.onstop = exportStream;

  btn.onclick = stopRecording;

};

function exportStream(e) {

  if (chunks.length) {

    var blob = new Blob(chunks, { type: chunks[0].type });
    var vidURL = URL.createObjectURL(blob);
    var vid = document.createElement('video');
    vid.controls = true;
    vid.src = vidURL;
    vid.onend = function() {
      URL.revokeObjectURL(vidURL);
    }
    document.body.insertBefore(vid, canvas);

  } else {

    document.body.insertBefore(document.createTextNode('no data saved'), canvas);

  }
}

function saveChunks(e) {

  e.data.size && chunks.push(e.data);

}

function stopRecording() {

  vid.pause();
  btn.remove();
  recorder.stop();

}

function initAudioStream() {

  var audioCtx = new AudioContext();
  // create a stream from our AudioContext
  var dest = audioCtx.createMediaStreamDestination();
  aStream = dest.stream;
  // connect our video element's output to the stream
  var sourceNode = audioCtx.createMediaElementSource(vid);
  sourceNode.connect(dest)
  // start the video
  vid.play();

  // just for the fancy canvas drawings
  analyser = audioCtx.createAnalyser();
  sourceNode.connect(analyser);

  analyser.fftSize = 2048;
  bufferLength = analyser.frequencyBinCount;
  dataArray = new Uint8Array(bufferLength);
  analyser.getByteTimeDomainData(dataArray);

  // output to our headphones
  sourceNode.connect(audioCtx.destination)

  startCanvasAnim();
  
}
function enableButton() {

  vid.oncanplay = null;
  btn.onclick = clickHandler;
  btn.disabled = false;

};

var loadVideo = function() {

  vid = document.createElement('video');
  vid.crossOrigin = 'anonymous';
  vid.oncanplay = enableButton;
  vid.src = 'https://dl.dropboxusercontent.com/s/bch2j17v6ny4ako/movie720p.mp4';

}

function startCanvasAnim() {
  // from MDN https://developer.mozilla.org/en/docs/Web/API/AnalyserNode#Examples
  canvas = Object.assign(document.createElement("canvas"), { width: 500, height: 200});
  document.body.prepend(canvas);
  var canvasCtx = canvas.getContext('2d');

  canvasCtx.fillStyle = 'rgb(200, 200, 200)';
  canvasCtx.lineWidth = 2;
  canvasCtx.strokeStyle = 'rgb(0, 0, 0)';

  var draw = function() {

    var drawVisual = requestAnimationFrame(draw);

    analyser.getByteTimeDomainData(dataArray);

    canvasCtx.fillRect(0, 0, canvas.width, canvas.height);
    canvasCtx.beginPath();

    var sliceWidth = canvas.width * 1.0 / bufferLength;
    var x = 0;

    for (var i = 0; i < bufferLength; i++) {

      var v = dataArray[i] / 128.0;
      var y = v * canvas.height / 2;

      if (i === 0) {
        canvasCtx.moveTo(x, y);
      } else {
        canvasCtx.lineTo(x, y);
      }

      x += sliceWidth;
    }

    canvasCtx.lineTo(canvas.width, canvas.height / 2);
    canvasCtx.stroke();

  };

  draw();

}

loadVideo();
button { vertical-align: top }
<button disabled>record</button>

关于javascript - MediaStream 同时捕获 Canvas 和音频,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39302814/

有关javascript - MediaStream 同时捕获 Canvas 和音频的更多相关文章

  1. ruby - 如何让Ruby捕获线程中的语法错误 - 2

    我正在尝试使用ruby​​编写一个双线程客户端,一个线程从套接字读取数据并将其打印出来,另一个线程读取本地数据并将其发送到远程服务器。我发现的问题是Ruby似乎无法捕获线程内的错误,这是一个示例:#!/usr/bin/rubyThread.new{loop{$stdout.puts"hi"abc.putsefsleep1}}loop{sleep1}显然,如果我在线程外键入abc.putsef,代码将永远不会运行,因为Ruby将报告“undefinedvariableabc”。但是,如果它在一个线程内,则没有错误报告。我的问题是,如何让Ruby捕获这样的错误?或者至少,报告线程中的错误?

  2. ruby-on-rails - 无法在 Rails 助手中捕获 block 的输出 - 2

    我在使用自定义RailsFormBuilder时遇到了问题,从昨天晚上开始我就发疯了。基本上我想对我的构建器方法之一有一个可选block,以便我可以在我的主要content_tag中显示其他内容。:defform_field(method,&block)content_tag(:div,class:'field')doconcatlabel(method,"Label#{method}")concattext_field(method)capture(&block)ifblock_given?endend当我在我的一个Slim模板中调用该方法时,如下所示:=f.form_field:e

  3. ruby-on-rails - 使用 javascript 更改数据方法不会更改 ajax 调用用户的什么方法? - 2

    我遇到了一个非常奇怪的问题,我很难解决。在我看来,我有一个与data-remote="true"和data-method="delete"的链接。当我单击该链接时,我可以看到对我的Rails服务器的DELETE请求。返回的JS代码会更改此链接的属性,其中包括href和data-method。再次单击此链接后,我的服务器收到了对新href的请求,但使用的是旧的data-method,即使我已将其从DELETE到POST(它仍然发送一个DELETE请求)。但是,如果我刷新页面,HTML与"new"HTML相同(随返回的JS发生变化),但它实际上发送了正确的请求类型。这就是这个问题令我困惑的

  4. ruby - 在 ruby​​ 中生成一个进程,捕获 stdout,stderr,获取退出状态 - 2

    我想从ruby​​rake脚本运行一个可执行文件,比如foo.exe我希望将foo.exe的STDOUT和STDERR输出直接写入我正在运行rake任务的控制台.当进程完成时,我想将退出代码捕获到一个变量中。我如何实现这一目标?我一直在玩backticks、process.spawn、system但我无法获得我想要的所有行为,只有部分更新:我在Windows上,在标准命令提示符下,而不是cygwin 最佳答案 system获取您想要的STDOUT行为。它还返回true作为零退出代码,这可能很有用。$?填充了有关最后一次system调

  5. ruby - 捕获 Ruby Logger 输出以进行测试 - 2

    我有一个像这样的ruby​​类:require'logger'classTdefdo_somethinglog=Logger.new(STDERR)log.info("Hereisaninfomessage")endend测试脚本行如下:#!/usr/bin/envrubygem"minitest"require'minitest/autorun'require_relative't'classTestMailProcessorClasses当我运行这个测试时,out和err都是空字符串。我看到消息打印在stderr上(在终端上)。有没有办法让Logger和capture_io一起玩得

  6. ruby - Capistrano 中的执行、测试和捕获命令有什么区别? - 2

    关于SSHkit-Github它说:Allbackendssupporttheexecute(*args),test(*args)&capture(*args)来自SSHkit-Rubydoc,我明白execute实际上是test的别名?test之间有什么区别?,execute,capture在Capistrano/SSHKit中我应该什么时候使用? 最佳答案 执行只是执行命令。使用非0退出引发错误。测试方法的行为与execute完全相同,但是它返回bool值(true如果命令以0退出,而false否则)。它通常用于控制任务中的流程

  7. ruby - 如何捕获 ruby​​ 中的所有异常? - 2

    我们如何捕获或/和处理ruby​​中所有未处理的异常?例如,这样做的动机可能是将某种异常记录到不同的文件或发送电子邮件给系统管理。在Java中我们会做Thread.setDefaultUncaughtExceptionHandler(UncaughtExceptionHandlerex);在Node.js中process.on('uncaughtException',function(error){/*code*/});在PHP中register_shutdown_function('errorHandler');functionerrorHandler(){$error=error_

  8. ruby - 正则表达式 - 保存重复捕获的组 - 2

    这就是我做的a="%span.rockets#diamonds.ribbons.forever"a=a.match(/(^\%\w+)([\.|\#]\w+)+/)putsa.inspect这是我得到的#这就是我想要的#帮助?我尝试过但失败了:( 最佳答案 通常,您不能获得任意数量的捕获组,但如果您使用扫描,您可以为您想要捕获的每个标记获得一个匹配:a="%span.rockets#diamonds.ribbons.forever"a=a.scan(/^%\w+|\G[.|#]\w+/)putsa.inspect["%span","

  9. ruby - 如何捕获所有 HTTP 流量(本地代理) - 2

    我希望访问我机器上的所有HTTP流量(我的Windows机器-不是服务器)。据我了解,拥有一个本地代理是所有流量路线的必经之路。我一直在谷歌搜索但未能找到任何资源(关于Ruby)来帮助我。非常感谢任何提示或链接。 最佳答案 WEBrick中有一个HTTP代理(Rubystdlib的一部分)和here's一个实现示例。如果你喜欢生活在边缘,还有em-proxy伊利亚·格里戈里克。这postIlya暗示它似乎确实需要一些调整来解决您的问题。 关于ruby-如何捕获所有HTTP流量(本地代理)

  10. ruby - 使用 ruby​​ gem net-ssh-multi 同时在多个服务器上执行 sudo 命令 - 2

    在previousquestion中我想出了如何在多个服务器上启动经过密码验证的sshsession来运行单个命令。现在我需要能够执行“sudo”命令。问题是,net-ssh-multi没有分配sudo需要运行的伪终端(pty),导致以下错误:[127.0.0.1:stderr]sudo:sorry,youmusthaveattytorunsudo根据documentation,可以通过调用channel对象的方法来分配伪终端,但是,以下代码不起作用:它会生成上面的“notty”错误:require'net/ssh'require'net/ssh/multi'Net::SSH::Mul

随机推荐