autobahn|python を使用して websocket サーバーを実行しています。サーバー側では、「appsink」を使用して webm フレームをキャプチャするために使用している gstreamer パイプラインも実行しています。実装されている gstreamer パイプラインは次のとおりです。
gst-launch-1.0 v4l2src ! video/x-raw,width=640,height=480 ! videoconvert ! vp8enc ! webmmux ! appsink name="sink"
毎回、appsink でバッファを受信し、sendMessage を使用してバイナリの「メッセージ」として Websocket 経由で送信します。
def on_new_buffer(appsink):
global once
gstsample = appsink.emit('pull-sample')
gstbuffer = gstsample.get_buffer()
frame_data = gstbuffer.extract_dup(0,gstbuffer.get_size())
for c in global_clients:
c.sendMessage(frame_data,True)
print("Directly sent: {0} bytes".format(len(frame_data)))
return False
クライアント側では、受信した frame_data blob の複雑なフローがあります。FileReader、MediaSource、およびソース バッファがあります。frame_data が受信されるたびに、filereader を使用してバッファとして読み取られます。ファイルリーダーが前の frame_data の読み取りでビジー状態の場合、それを「buffer_pool」に追加します。frame_data がバッファとして読み取られると、「sourceBuffer」に追加されます。「sourceBuffer」がまだ前のチャンクを更新している場合、それは「sourceBufferpool」に追加されます。
<script>
var video = document.getElementById('v');
var playButton = document.getElementById('playbutton');
var mediaSource;
var sourceBuffer;
var buffer_pool = [];
var sourceBufferpool = [];
function setupVideo() {
window.MediaSource = window.MediaSource || window.WebKitMediaSource;
if (!!!window.MediaSource) {
alert('MediaSource API is not available');
}
mediaSource = new MediaSource();
video.src = window.URL.createObjectURL(mediaSource);
mediaSource.addEventListener('sourceopen', function (e) {
try {
sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs="vp8"');
} catch(e) {
console.log('Exception calling addSourceBuffer for video', e);
return;
}
//sourceBuffer.addEventListener('updatestart', function(e) { console.log('updatestart: ' + e.target + mediaSource.readyState); });
//sourceBuffer.addEventListener('updateend', function(e) { console.log('updateend: ' + e.target + mediaSource.readyState); });
sourceBuffer.addEventListener('error', function(e) { console.log('error: ' + e.target + mediaSource.readyState); });
sourceBuffer.addEventListener('abort', function(e) { console.log('abort: ' + e.target + mediaSource.readyState); });
sourceBuffer.addEventListener('update', function() {
if (sourceBufferpool.length > 0 && !sourceBuffer.updating) {
try {
sourceBuffer.appendBuffer(sourceBufferpool.shift());
console.log('update: pooled buffer appended ' + sourceBufferpool.length + mediaSource.readyState);
}catch(e){
console.log('Exception calling appendBuffer for video ', e);
return;
}
}
},false)
if (video.paused) {
video.play()
}
startWSStreaming();
},false)
mediaSource.addEventListener('sourceended', function(e) { console.log('sourceended: ' + mediaSource.readyState); });
mediaSource.addEventListener('sourceclose', function(e) { console.log('sourceclose: ' + mediaSource.readyState); });
mediaSource.addEventListener('error', function(e) { console.log('error: ' + mediaSource.readyState); });
}
function startWSStreaming() {
var reader = new FileReader();
reader.onload = function (evt) {
if (sourceBuffer.updating || sourceBufferpool.length > 0){
sourceBufferpool.push(new Uint8Array(evt.target.result));
console.log('update: pooled buffer appended ' + sourceBufferpool.length + mediaSource.readyState);
}else{
sourceBuffer.appendBuffer(new Uint8Array(evt.target.result));
console.log('update: direct buffer appended ' + sourceBufferpool.length + mediaSource.readyState);
}
}
reader.onloadend = function (evt) {
if (buffer_pool.length > 0) {
var chunk = new Blob([buffer_pool.shift()], {type: 'video/webm'});
evt.target.readAsArrayBuffer(chunk);
console.log('Processed buffer pool: current size ' + buffer_pool.length);
}
}
ws = new WebSocket("ws://localhost:9000/");
ws.onopen = function () {
document.getElementById("MSG1").innerHTML = 'Websocket opened <br>';
}
ws.onmessage = function(e) {
myBuffer = e.data;
if (reader.readyState == 1 || buffer_pool.length > 0) {
buffer_pool.push(myBuffer);
console.log('Received buffer pooled: current size ' + buffer_pool.length);
}else{
var chunk = new Blob([myBuffer], {type: 'video/webm'});
reader.readAsArrayBuffer(chunk);
console.log('First buffer processed');
}
}
}
</script>
最終結果は、ブラウザ ウィンドウに 1 つのフレームしか表示されず、ビデオがフリーズするというものです。chrome://media-internals/ を確認すると、次の手がかりが得られます。
Timestamp Property Value
00:00:00 00 pipeline_state kCreated
00:00:00 00 EVENT PIPELINE_CREATED
00:00:00 00 EVENT WEBMEDIAPLAYER_CREATED
00:00:00 00 url blob:http%3A//localhost%3A8080/09060a78-9759-4fcd-97a2-997121ba6122
00:00:00 00 pipeline_state kInitDemuxer
00:00:01 668 duration unknown
00:00:01 669 pipeline_state kInitVideoRenderer
00:00:01 685 pipeline_state kPlaying
00:00:03 820 EVENT PLAY
00:00:04 191 error Got a block with a timecode before the previous block.
00:00:04 191 pipeline_error pipeline: decode error
00:00:04 191 pipeline_state kStopping
00:00:04 192 pipeline_state kStopped
00:00:28 483 EVENT WEBMEDIAPLAYER_DESTROYED
ふぅ、かなり長い説明!!! この行までたどり着いたことを願っています。さて、本当の質問:
- 1 フレームだけ表示した後にビデオがフリーズするのはなぜですか?
- webmチャンクを個別のメッセージとして扱っているのに対し、これは「sendMessageFrameData」として処理する必要があるため、websocketの「sendMessage」メソッドが原因ですか?
- 送信された順序で受信されるように、到着した frame_data の順序付けが必要ですか?
- または私のアプローチ全体が間違っていますか?
助けてください!