webRTC 및 서버 기반 피어 연결을 사용하여 웹캠 및 오디오를 녹음하는 방법


90

사용자 웹캠과 오디오를 녹음하여 서버의 파일에 저장하고 싶습니다. 이 파일은 다른 사용자에게 제공 될 수 있습니다.

재생에는 문제가 없지만 녹화 할 콘텐츠를 가져 오는 데 문제가 있습니다.

내 이해는 getUserMedia .record()함수가 아직 작성되지 않았다는 것입니다. 지금까지 제안이 작성되었습니다.

PeerConnectionAPI를 사용하여 내 서버에서 피어 연결을 만들고 싶습니다. 나는 이것이 약간 해키하다는 것을 이해하지만 서버에서 피어를 만들고 클라이언트-피어가 보내는 것을 기록하는 것이 가능해야한다고 생각합니다.

이것이 가능하다면이 데이터를 flv 또는 다른 비디오 형식으로 저장할 수 있어야합니다.

내가 선호하는 것은 실제로 웹캠 + 오디오 클라이언트 측을 녹화하여 클라이언트가 업로드하기 전에 첫 번째 시도가 마음에 들지 않으면 비디오를 다시 녹화 할 수 있도록하는 것입니다. 이것은 또한 네트워크 연결의 중단을 허용합니다. 캔버스에 데이터를 전송하여 웹캠에서 개별 '이미지'를 기록 할 수있는 코드를 보았습니다. 멋지지만 오디오도 필요합니다.

지금까지 가지고있는 클라이언트 측 코드는 다음과 같습니다.

  <video autoplay></video>

<script language="javascript" type="text/javascript">
function onVideoFail(e) {
    console.log('webcam fail!', e);
  };

function hasGetUserMedia() {
  // Note: Opera is unprefixed.
  return !!(navigator.getUserMedia || navigator.webkitGetUserMedia ||
            navigator.mozGetUserMedia || navigator.msGetUserMedia);
}

if (hasGetUserMedia()) {
  // Good to go!
} else {
  alert('getUserMedia() is not supported in your browser');
}

window.URL = window.URL || window.webkitURL;
navigator.getUserMedia  = navigator.getUserMedia || navigator.webkitGetUserMedia ||
                          navigator.mozGetUserMedia || navigator.msGetUserMedia;

var video = document.querySelector('video');
var streamRecorder;
var webcamstream;

if (navigator.getUserMedia) {
  navigator.getUserMedia({audio: true, video: true}, function(stream) {
    video.src = window.URL.createObjectURL(stream);
    webcamstream = stream;
//  streamrecorder = webcamstream.record();
  }, onVideoFail);
} else {
    alert ('failed');
}

function startRecording() {
    streamRecorder = webcamstream.record();
    setTimeout(stopRecording, 10000);
}
function stopRecording() {
    streamRecorder.getRecordedData(postVideoToServer);
}
function postVideoToServer(videoblob) {
/*  var x = new XMLHttpRequest();
    x.open('POST', 'uploadMessage');
    x.send(videoblob);
*/
    var data = {};
    data.video = videoblob;
    data.metadata = 'test metadata';
    data.action = "upload_video";
    jQuery.post("http://www.foundthru.co.uk/uploadvideo.php", data, onUploadSuccess);
}
function onUploadSuccess() {
    alert ('video uploaded');
}

</script>

<div id="webcamcontrols">
    <a class="recordbutton" href="javascript:startRecording();">RECORD</a>
</div>

나는 같은 문제가 있습니다. getRecordedData () 메소드가 작동합니까? 새로 업데이트 된 브라우저에 없습니다.
Firas

아니요- 'Google Canary'도 시도했습니다.
Dave Hilditch 2013 년

예, 나는 그것에 대해 면밀히 주시하고 있습니다-적절한 해결책이있을 때이 스레드를 업데이트 할 것입니다.
Dave Hilditch 2013 년

2
당신은 나와 함께 감사 위의 질문하시기 바랍니다 공유의 솔루션을 가지고있는 경우
무하마드

2
누군가 서버 측 RTC 마법을 ​​통해 MediaStream 바이트를 얻을 수 있었습니까?
Vinay

답변:


44

당신은 확실히 Kurento봐야 합니다. WebRTC 피드 등에서 기록 할 수있는 WebRTC 서버 인프라를 제공합니다. 여기에서 계획중인 응용 프로그램에 대한 몇 가지 예를 찾을 수도 있습니다 . 데모에 레코딩 기능을 추가하고 미디어 파일을 URI (로컬 디스크 또는 모든 위치)에 저장하는 것은 정말 쉽습니다.

이 프로젝트는 LGPL Apache 2.0에 따라 사용이 허가되었습니다.


1 편집

이 게시물 이후로 몇 가지 시나리오에서 레코더를 추가하는 방법을 보여주는 새 자습서를 추가했습니다.

면책 조항 : 저는 Kurento를 개발하는 팀의 일원입니다.


2
@Redtopia 최근로드 테스트에서 i5 / 16GB RAM에서 150 개의 webrtc one2one 연결을 얻을 수있었습니다. 이 수치가 앞으로 더 나아질 것이라고 기대할 수 있지만 기적을 기대하지는 마십시오. SRTP에 대해 많은 암호화가 진행되고 있으며 이는 요구 사항입니다. 우리는 하드웨어 가속 암호화 / 복호화를 조사하고 있으며 수치는 더 높아질 것입니다. 더 철저히 테스트 할 때까지 얼마나 나아질 지 확신 할 수는 없지만 3 배 개선 될 것으로 예상합니다
igracia

2
@ user344146 아마 내가 대답했을 것입니다. 해당 게시물에 대한 링크를 공유 하시겠습니까? 그 대답을 받았다면 아마도 이미 거기에 있거나 목록에있는 것을 물었 기 때문일 것입니다. SNAPSHOT 버전을 컴파일하려고 한 것 같습니다. 이러한 아티팩트는 중앙에 게시되지 않으므로 자습서 릴리스를 확인하거나 내부 개발 저장소를 사용합니다. 이것은 목록에서 여러 번 답변을 받았으며, 개발 버전 작업에 대한 문서 항목이 있습니다 ... 우리는 시간을 들여 작성 했으므로 시간을내어 읽으면 좋을 것입니다.
igracia

2
그런 녹음을하기 위해 Kurento를 사용하고 있습니다. 나는 복잡하지는 않지만 개념을 이해하는 데 약간의 시간이 필요합니다. 문서의 일부는 정말 의미가 있기 때문에 kurento에 보낼 수있는 내용이나 이벤트 설명 등을 찾는 것은 때때로 정말 실망 스러울 수 있습니다. 그러나 어쨌든-이와 같은 공개 프로젝트는 정말 대단한 일이며 사용할 가치가 있습니다. Kurento는 Linux에서만 작동합니다 (Windows 버전은 공식이 아니며 전체 기능으로 작동하지 않습니다).
Krystian 2016

1
위의 질문에 대한 답변을 찾았습니다 (다른 사람을 위해 여기에 게시), Kurento는 현재 JDK 7.0을 지원합니다. Ubuntu 14.04에 의존 할 필요가 없으며 이후 버전도 지원해야하지만 Kurento는 다른 버전의 Ubuntu에서 공식적으로 테스트되지 않았습니다. / 다른 리눅스 버전. 또한 Kurento는 쉽게 설치 가능한 64 비트 버전을 출시하지만 32 비트 버전의 서버를 설치할 수 있지만 먼저 빌드해야합니다.
Bilbo Baggins

1
불행히도 내 대답에서 언급했듯이 Kurento의 개발은 Twilio 인수 후 심각하게 느려졌습니다. 대신 Janus를 사용하는 것이 좋습니다.
jamix

18

RecordRTC를 확인하십시오

RecordRTC는 github에서 MIT 라이센스를 받았습니다 .


2
그것은 꽤 굉장합니다. 제 질문입니다. 비디오와 오디오를 함께 녹화 할 수 있습니까? (두 개의 별개의 것이 아닌 실제 비디오를 생중계 할 수 있습니까?)
Brian Dear

동의 함-굉장하지만 데이터를 별도로 기록하는 것처럼 보입니다.
Dave Hilditch

3
@BrianDear 하나의 RecordRTC- 함께
Mifeng

3
이 접근 방식은 Chrome의 Whammy.js를 통해 작동합니다. Chrome의 MediaStreamRecorder 부족에 대해 Whammy가 제공하는 에뮬레이션보다 품질이 훨씬 낮아지는 경향이 있기 때문에 문제가됩니다. 본질적으로 WhammyRecorder는 MediaStream 개체 URL에 비디오 태그를 지정한 다음 특정 프레임 속도로 캔버스 요소의 webp 스냅 샷을 찍습니다. 그런 다음 Whammy를 사용하여 모든 프레임을 webm 비디오에 통합합니다.
Vinay

15

특히 Chrome이 v25 이후 v47 및 Firefox에서 MediaRecorder API를 지원 한다는 사실을 고려할 때 비디오 녹화를 위해 kurento 또는 기타 MCU를 사용하는 것은 약간 과잉이라고 생각합니다 . 따라서이 교차점에서 작업을 수행하기 위해 외부 js 라이브러리가 필요하지 않을 수도 있습니다. MediaRecorder를 사용하여 비디오 / 오디오를 녹화하기 위해 만든이 데모를 시도해보십시오.

데모 -크롬 및 파이어 폭스에서 작동합니다 (의도적으로 blob을 서버 코드로 푸시하지 않음)

Github 코드 소스

파이어 폭스를 실행하는 경우 여기에서 테스트 할 수 있습니다 (크롬 필요 https).

'use strict'

let log = console.log.bind(console),
  id = val => document.getElementById(val),
  ul = id('ul'),
  gUMbtn = id('gUMbtn'),
  start = id('start'),
  stop = id('stop'),
  stream,
  recorder,
  counter = 1,
  chunks,
  media;


gUMbtn.onclick = e => {
  let mv = id('mediaVideo'),
    mediaOptions = {
      video: {
        tag: 'video',
        type: 'video/webm',
        ext: '.mp4',
        gUM: {
          video: true,
          audio: true
        }
      },
      audio: {
        tag: 'audio',
        type: 'audio/ogg',
        ext: '.ogg',
        gUM: {
          audio: true
        }
      }
    };
  media = mv.checked ? mediaOptions.video : mediaOptions.audio;
  navigator.mediaDevices.getUserMedia(media.gUM).then(_stream => {
    stream = _stream;
    id('gUMArea').style.display = 'none';
    id('btns').style.display = 'inherit';
    start.removeAttribute('disabled');
    recorder = new MediaRecorder(stream);
    recorder.ondataavailable = e => {
      chunks.push(e.data);
      if (recorder.state == 'inactive') makeLink();
    };
    log('got media successfully');
  }).catch(log);
}

start.onclick = e => {
  start.disabled = true;
  stop.removeAttribute('disabled');
  chunks = [];
  recorder.start();
}


stop.onclick = e => {
  stop.disabled = true;
  recorder.stop();
  start.removeAttribute('disabled');
}



function makeLink() {
  let blob = new Blob(chunks, {
      type: media.type
    }),
    url = URL.createObjectURL(blob),
    li = document.createElement('li'),
    mt = document.createElement(media.tag),
    hf = document.createElement('a');
  mt.controls = true;
  mt.src = url;
  hf.href = url;
  hf.download = `${counter++}${media.ext}`;
  hf.innerHTML = `donwload ${hf.download}`;
  li.appendChild(mt);
  li.appendChild(hf);
  ul.appendChild(li);
}
      button {
        margin: 10px 5px;
      }
      li {
        margin: 10px;
      }
      body {
        width: 90%;
        max-width: 960px;
        margin: 0px auto;
      }
      #btns {
        display: none;
      }
      h1 {
        margin-bottom: 100px;
      }
<link type="text/css" rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
<h1> MediaRecorder API example</h1>

<p>For now it is supported only in Firefox(v25+) and Chrome(v47+)</p>
<div id='gUMArea'>
  <div>
    Record:
    <input type="radio" name="media" value="video" checked id='mediaVideo'>Video
    <input type="radio" name="media" value="audio">audio
  </div>
  <button class="btn btn-default" id='gUMbtn'>Request Stream</button>
</div>
<div id='btns'>
  <button class="btn btn-default" id='start'>Start</button>
  <button class="btn btn-default" id='stop'>Stop</button>
</div>
<div>
  <ul class="list-unstyled" id='ul'></ul>
</div>
<script src="https://code.jquery.com/jquery-2.2.0.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>


Chrome 49는 플래그없이 MediaRecorder API를 지원하는 최초의 제품입니다.
Octavian Naicu 2016

7

예, 이해했듯이 MediaStreamRecorder는 현재 구현되지 않았습니다.

MediaStreamRecorder는 getUserMedia () 스트림을 기록하기위한 WebRTC API입니다. 웹 앱이 라이브 오디오 / 비디오 세션에서 파일을 생성 할 수 있습니다.

또는 http://ericbidelman.tumblr.com/post/31486670538/creating-webm-video-from-getusermedia 와 같이 할 수 있지만 오디오가 누락되었습니다.


1
예, 오디오 파일을 캡처하여 서버로 전송 한 다음 결합하여 서버 측에서 실제 비디오 파일을 만들 수 있습니다. 그러나이 솔루션은 컴퓨터 구성에 따라 클라이언트 측에서 매우 느릴 수 있습니다. 캔버스를 사용하여 이미지 파일을 만들고 오디오를 캡처해야하고이 모든 것이 RAM에 있습니다. Btw, firefox 팀이 작업 중입니다. , 그래서 그들은 곧 출시 할 것입니다.
라스

4

RecordRTC를 기반으로하는 RecordRTC-together를 사용할 수 있습니다 .

비디오와 오디오를 별도의 파일로 함께 녹화 할 수 있습니다. ffmpeg서버에서 두 개의 파일을 하나로 병합하는 것과 같은 도구가 필요 합니다.


2
이것은 서버 측이 아닌 브라우저 솔루션입니다.
Brad

2

Web Call Server 4 는 WebRTC 오디오 및 비디오를 WebM 컨테이너에 녹음 할 수 있습니다. 녹음은 오디오 용 Vorbis 코덱과 비디오 용 VP8 코덱을 사용하여 수행됩니다. 초기 WebRTC 코덱은 Opus 또는 G.711 및 VP8입니다. 따라서 서버 측 레코딩에는 다른 컨테이너 (예 : AVI)를 사용해야하는 경우 Opus / G.711에서 Vorbis 서버 측 트랜스 코딩 또는 VP8-H.264 트랜스 코딩이 필요합니다.


이게 상업용 물건인가요?
Stepan Yakovenko

0

기록상 저도 이것에 대한 충분한 지식이 없습니다.

하지만 나는 이것을 Git 허브에서 찾았습니다.

<!DOCTYPE html>
 <html>
<head>
  <title>XSockets.WebRTC Client example</title>
  <meta charset="utf-8" />


<style>
body {

  }
.localvideo {
position: absolute;
right: 10px;
top: 10px;
}

.localvideo video {
max-width: 240px;
width:100%;
margin-right:auto;
margin-left:auto;
border: 2px solid #333;

 }
 .remotevideos {
height:120px;
background:#dadada;
padding:10px; 
}

.remotevideos video{
max-height:120px;
float:left;
 }
</style>
</head>
<body>
<h1>XSockets.WebRTC Client example </h1>
<div class="localvideo">
    <video autoplay></video>
</div>

<h2>Remote videos</h2>
<div class="remotevideos">

</div>
<h2>Recordings  ( Click on your camera stream to start record)</h2>
<ul></ul>


<h2>Trace</h2>
<div id="immediate"></div>
<script src="XSockets.latest.js"></script>
<script src="adapter.js"></script>
<script src="bobBinder.js"></script>
<script src="xsocketWebRTC.js"></script>
<script>
    var $ = function (selector, el) {
        if (!el) el = document;
        return el.querySelector(selector);
    }
    var trace = function (what, obj) {
        var pre = document.createElement("pre");
        pre.textContent = JSON.stringify(what) + " - " + JSON.stringify(obj || "");
        $("#immediate").appendChild(pre);
    };
    var main = (function () {
        var broker;
        var rtc;
        trace("Ready");
        trace("Try connect the connectionBroker");
        var ws = new XSockets.WebSocket("wss://rtcplaygrouund.azurewebsites.net:443", ["connectionbroker"], {
            ctx: '23fbc61c-541a-4c0d-b46e-1a1f6473720a'
        });
        var onError = function (err) {
            trace("error", arguments);
        };
        var recordMediaStream = function (stream) {
            if ("MediaRecorder" in window === false) {
                trace("Recorder not started MediaRecorder not available in this browser. ");
                return;
            }
            var recorder = new XSockets.MediaRecorder(stream);
            recorder.start();
            trace("Recorder started.. ");
            recorder.oncompleted = function (blob, blobUrl) {
                trace("Recorder completed.. ");
                var li = document.createElement("li");
                var download = document.createElement("a");
                download.textContent = new Date();
                download.setAttribute("download", XSockets.Utils.randomString(8) + ".webm");
                download.setAttribute("href", blobUrl);
                li.appendChild(download);
                $("ul").appendChild(li);
            };
        };
        var addRemoteVideo = function (peerId, mediaStream) {
            var remoteVideo = document.createElement("video");
            remoteVideo.setAttribute("autoplay", "autoplay");
            remoteVideo.setAttribute("rel", peerId);
            attachMediaStream(remoteVideo, mediaStream);
            $(".remotevideos").appendChild(remoteVideo);
        };
        var onConnectionLost = function (remotePeer) {
            trace("onconnectionlost", arguments);
            var peerId = remotePeer.PeerId;
            var videoToRemove = $("video[rel='" + peerId + "']");
            $(".remotevideos").removeChild(videoToRemove);
        };
        var oncConnectionCreated = function () {
            console.log(arguments, rtc);
            trace("oncconnectioncreated", arguments);
        };
        var onGetUerMedia = function (stream) {
            trace("Successfully got some userMedia , hopefully a goat will appear..");
            rtc.connectToContext(); // connect to the current context?
        };
        var onRemoteStream = function (remotePeer) {
            addRemoteVideo(remotePeer.PeerId, remotePeer.stream);
            trace("Opps, we got a remote stream. lets see if its a goat..");
        };
        var onLocalStream = function (mediaStream) {
            trace("Got a localStream", mediaStream.id);
            attachMediaStream($(".localvideo video "), mediaStream);
            // if user click, video , call the recorder
            $(".localvideo video ").addEventListener("click", function () {
                recordMediaStream(rtc.getLocalStreams()[0]);
            });
        };
        var onContextCreated = function (ctx) {
            trace("RTC object created, and a context is created - ", ctx);
            rtc.getUserMedia(rtc.userMediaConstraints.hd(false), onGetUerMedia, onError);
        };
        var onOpen = function () {
            trace("Connected to the brokerController - 'connectionBroker'");
            rtc = new XSockets.WebRTC(this);
            rtc.onlocalstream = onLocalStream;
            rtc.oncontextcreated = onContextCreated;
            rtc.onconnectioncreated = oncConnectionCreated;
            rtc.onconnectionlost = onConnectionLost;
            rtc.onremotestream = onRemoteStream;
            rtc.onanswer = function (event) {
            };
            rtc.onoffer = function (event) {
            };
        };
        var onConnected = function () {
            trace("connection to the 'broker' server is established");
            trace("Try get the broker controller form server..");
            broker = ws.controller("connectionbroker");
            broker.onopen = onOpen;
        };
        ws.onconnected = onConnected;
    });
    document.addEventListener("DOMContentLoaded", main);
</script>

필자의 경우 89 번 줄에서 OnrecordComplete는 실제로 레코더 파일의 링크를 추가합니다. 해당 링크를 클릭하면 다운로드가 시작되고 해당 경로를 서버에 파일로 저장할 수 있습니다.

녹음 코드는 다음과 같습니다.

recorder.oncompleted = function (blob, blobUrl) {
                trace("Recorder completed.. ");
                var li = document.createElement("li");
                var download = document.createElement("a");
                download.textContent = new Date();
                download.setAttribute("download", XSockets.Utils.randomString(8) + ".webm");
                download.setAttribute("href", blobUrl);
                li.appendChild(download);
                $("ul").appendChild(li);
            };

blobUrl은 경로를 보유합니다. 나는 이것으로 내 문제를 해결했고 누군가가 이것을 유용하게 생각하기를 바랍니다.


-4

기술적으로 백엔드에서 FFMPEG를 사용하여 비디오와 오디오를 혼합 할 수 있습니다.


7
예,하지만 어떻게 거기에 가져 오나요?
Eddie Monge Jr.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.