MediaStream 同时捕获画布和音频
Posted
技术标签:
【中文标题】MediaStream 同时捕获画布和音频【英文标题】:MediaStream Capture Canvas and Audio Simultaneously 【发布时间】:2017-01-11 04:25:59 【问题描述】:我正在从事一个我想从事的项目:
-
加载一个视频js并显示在画布上。
使用过滤器来改变画布(以及视频)的外观。
使用 MediaStream captureStream() 方法和 MediaRecorder 对象记录画布表面和原始视频的音频。
在 html 视频元素中播放画布流和音频流。
通过调整这个 WebRTC 演示代码,我已经能够在视频元素中显示画布记录:https://webrtc.github.io/samples/src/content/capture/canvas-record/
也就是说,我不知道如何在画布旁边录制视频的音频。是否可以从两个不同的源/元素创建包含 MediaStreamTrack 实例的 MediaStream?
根据 MediaStream API 的规范,理论上应该有一些方法可以做到这一点: https://w3c.github.io/mediacapture-main/#introduction
“MediaStream API 中的两个主要组件是 MediaStreamTrack 和 MediaStream 接口。MediaStreamTrack 对象表示源自用户代理中的一个媒体源的单一类型的媒体,例如由网络摄像机生成的视频。 MediaStream 用于将多个 MediaStreamTrack 对象组合成一个单元,可以在一个媒体元素中记录或呈现。”
【问题讨论】:
由于接受的答案并非专门针对 p5.js,您介意我将其从您问题的标题和正文中删除吗? 一点也不,去吧。 【参考方案1】:是否可以创建包含来自两个不同源/元素的 MediaStreamTrack 实例的 MediaStream?
是的,您可以使用MediaStream.addTrack()
方法来实现。
但在this bug 被修复之前,Firefox 只会使用初始流的轨道进入记录器。
OP 已经知道如何获取所有内容,但这里提醒未来的读者:
要从画布中获取videoStream轨道,可以调用canvas.captureStream(framerate)
方法。
要从视频元素中获取音频 streamTrack,您可以使用 WebAudio API 和 createMediaStreamDestination
方法。
这将返回一个包含我们的音频流的 MediaStreamDestination 节点 (dest
)。然后,您必须将从您的视频元素创建的MediaElementSource 连接到此dest
。
如果您需要向此流中添加更多音轨,则应将所有这些源连接到 dest
。
现在我们有两个流,一个用于画布视频,一个用于音频,我们可以在初始化 new MediaRecorder(canvasStream)
之前使用 canvasStream.addTrack(audiostream.getAudioTracks()[0])
。
这是一个完整的例子,它现在只能在 chrome 中使用,并且可能很快会在 Firefox 中使用,届时他们将修复错误:
var cStream,
aStream,
vid,
recorder,
analyser,
dataArray,
bufferLength,
chunks = [];
function clickHandler()
this.textContent = 'stop recording';
cStream = canvas.captureStream(30);
cStream.addTrack(aStream.getAudioTracks()[0]);
recorder = new MediaRecorder(cStream);
recorder.start();
recorder.ondataavailable = saveChunks;
recorder.onstop = exportStream;
this.onclick = stopRecording;
;
function exportStream(e)
if (chunks.length)
var blob = new Blob(chunks)
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();
this.parentNode.removeChild(this);
recorder.stop();
function initAudioStream(evt)
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(this);
sourceNode.connect(dest)
// start the video
this.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();
rec.onclick = clickHandler;
rec.disabled = false;
;
var loadVideo = function()
vid = document.createElement('video');
vid.crossOrigin = 'anonymous';
vid.oncanplay = initAudioStream;
vid.src = 'https://dl.dropboxusercontent.com/s/bch2j17v6ny4ako/movie720p.mp4';
function startCanvasAnim()
// from MDN https://developer.mozilla.org/en/docs/Web/API/AnalyserNode#Examples
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();
<canvas id="canvas" ></canvas>
<button id="rec" disabled>record</button>
Ps:由于 FF 团队似乎需要一些时间来修复该错误,因此这里有一个快速修复,使其也适用于 FF。
您还可以使用new MediaStream([track1, track2])
混合两个曲目。
但是,chrome 目前是这个构造函数的前缀,但由于它确实支持addTrack
,所以它并不是真正需要的,我们可以提供像
var mixedStream = 'MediaStream' in window ?
new MediaStream([cStream.getVideoTracks()[0], aStream.getAudioTracks()[0]]) :
cStream;
recorder = new MediaRecorder(mixedStream);
Working fiddle for both FF and chrome.
【讨论】:
感谢您的快速响应和代码示例!很高兴知道一旦错误得到解决,混合画布/音频流将是可行的(至少在 Firefox 中)。 @dsing7,其实chrome没有bug,我打错了(忘了把sourceNode连接到MediaStreamDestination...)修复了代码,现在可以在Chrome中使用了。 很高兴 Chrome 按预期工作。感谢您的提醒! 如何向我的 MediaStream 添加第二个独特的音频元素?这将是来自 mp3 的音频,而不是视频。我应该创建一个额外的媒体流目标、目标流和媒体元素源吗?连接它们,然后使用 addTrack() 将新的目标流添加到我的 MediaStream?我刚试过这个,导出的视频没有我的 mp3 中的音频。 这里是我的代码的相关部分:pastebin.com/f4RwH7MD【参考方案2】:Kaiido 的演示很棒。对于那些只是寻找 tl;dr 代码以将音频流添加到现有画布流的人:
let videoOrAudioElement = /* your audio source element */;
// get the audio track:
let ctx = new AudioContext();
let dest = ctx.createMediaStreamDestination();
let sourceNode = ctx.createMediaElementSource(videoOrAudioElement);
sourceNode.connect(dest);
sourceNode.connect(ctx.destination);
let audioTrack = dest.stream.getAudioTracks()[0];
// add it to your canvas stream:
canvasStream.addTrack(audioTrack);
// use your canvas stream like you would normally:
let recorder = new MediaRecorder(canvasStream);
// ...
【讨论】:
@Kaiido,你通过对内容投反对票而不留下理由让社区变得更糟。我把它做成了一个社区维基,所以如果需要你可以改进它。当人们只需要原始逻辑时,此代码 sn-p 可防止人们不得不解析您的完整演示。我已将其设为社区 wiki,因此如果您因答案错误而投反对票,您可以对其进行改进。 是否可以用麦克风的音频和网页的另一个音频录制画布?同一录音中是否可以有 2 个或更多音频?我有一个游戏,我想录制并使用麦克风,但还要录制我录制时正在播放的任何其他音频。以上是关于MediaStream 同时捕获画布和音频的主要内容,如果未能解决你的问题,请参考以下文章
WebRTC音视频采集和播放示例及MediaStream媒体流解析