ホームページ >ウェブフロントエンド >htmlチュートリアル >websocket は音声と画像の機能を探索します_html/css_WEB-ITnose

websocket は音声と画像の機能を探索します_html/css_WEB-ITnose

WBOY
WBOYオリジナル
2016-06-24 11:27:341140ブラウズ

WebSocket の音声と画像の機能を探ります

WebSocket について言えば、馴染みのない方でも、一言で言うと問題ありません

「WebSocket プロトコルは HTML5 の新しいプロトコルです。」ブラウジング サーバー間全二重通信を実装します

WebSocket は、従来のサーバー プッシュ テクノロジーよりもはるかに優れています。彗星やロング ポーリング テクノロジーに別れを告げることができます。幸いなことに、私たちは HTML5 の時代に生きています~

この記事では、WebSocket を 3 つのパートに分けて説明します

1 つ目は WebSocket の一般的な使用方法、2 つ目はサーバー側の WebSocket を自分で完全に構築する方法、そして最後に、WebSocket を使用して作成された 2 つのデモ、写真の送信、オンライン送信に焦点を当てます。音声チャット ルーム、行きましょう

1. websocket の一般的な使用法

ここでは私が考える 3 つの一般的な Websocket 実装を紹介します... (注: この記事はノード コンテキストに基づいています)

1.

まずはデモをしてみましょう

var http = require('http');

var io = require('socket.io');

var server = http.createServer(function(req, res) ) {

res.writeHeader(200, {'content-type': 'text/html;charset="utf-8"'});

res.end();

}).listen(8888) ;

varソケット =.io.listen (サーバー);

socket.sockets.on('connection', function(socket) {

socket.emit('xxx', {options});

ソケット。 on('xxx', function(data) {

// do someting

});

});

WebSocket を知っている学生が Socket.io を知らないということはあり得ないと思います。ソケット.ioはとても有名で素晴らしいもので、タイムアウトやハンドシェイクなどの処理に多大な影響を与えています。これは、WebSocket を実装するために最も一般的に使用される方法でもあると思います。 Socket.io の最も優れた点は、ブラウザが WebSocket をサポートしていない場合、内部的に長いポーリングに正常にデグレードされるため、ユーザーと開発者は特定の実装を気にする必要がなく、非常に便利です。

しかし、Socket.io にはその包括性ゆえに落とし穴もあります。最も重要なことは、そのカプセル化によってデータの通信の冗長性が高まり、その利点も徐々に低下するということです。ブラウザの標準化の進歩でその輝きを失いました

Chrome

バージョン4以降でサポート

Firefox

バージョン4以降でサポート

Internet Explorer

バージョン 10+ でサポート

Opera

バージョン 10+ でサポート

Safari

バージョン5以降でサポート

ここで私は非難しているわけではありません良くないので削除されたsocket.ioですが、時には他の実装も検討してみましょう~

2. http module

socket.ioは肥大化していると言いましたので、今度は便利さについて話しましょう、最初のデモ

var http = require('http');

var server = http.createServer();

server.on('upgrade', function(req) {

console. log(req.headers);

});

server.listen(8888);

実際、socket.io もこの方法で WebSocket を実装しますが、それをお手伝いします。これは、いくつかのハンドル処理をカプセル化するもので、socket.io に 2 つのソース コードの画像を追加することもできます。

後で使用する例があります。ここで説明しますので、後ほど詳しく見てみましょう~

2番目に、自分でサーバー側WebSocketのセットを実装します

3つの一般的なWebSocket実装方法について説明しましたが、開発者向けに考えてみましょう

従来のWebSocketとの比較http データ対話モード、websocket サーバーによってプッシュされたイベントが追加され、クライアントがイベントを受信して​​それに応じて処理すると述べましたが、開発における違いはそれほど大きくありません

それは、これらのモジュールがすでに

データの解析に役立っているためです。

フレーム

を作成し、ここですべての落とし穴を埋めました。 2 番目の部分では、単純なサーバー側 WebSocket モジュールを自分で構築してみます

研究に協力してくれた亜炭酸コバルトに感謝し、ここではこの部分について簡単に説明します。興味があり、興味がある場合は、Baidu [Web 技術研究] をご覧ください。

サーバー側 WebSocket を自分で完成させるには、主に 2 つのポイントがあります。1 つは、ネット モジュールを使用してデータ ストリームを受信することです。公式のフレーム構造図に従ってデータを解析するためのこれら 2 つの部分が完了すると、基礎となる作業がすべて完了しました

まず、WebSocket ハンドシェイク メッセージのキャプチャ コンテンツをクライアントに送信します

クライアント コードは非常にシンプルです

ws = 新しい WebSocket("ws://127.0.0.1:8888");

サーバー側でこのキーを検証するには、キーに特定の文字列を追加して sha1 操作を実行し、結果を Base64 に変換して送り返すことを意味します

var crypto = require(' crypto' );

var WS = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';

require('net').createServer(function(o) {

var key;

o.on('data ', function(e) {

if(!key) {

// 送信された KEY を取得

key = e.toString().match(/Sec-WebSocket-Key: (.+)/)[1 ] ;

// 文字列 WS を接続し、sha1 操作を実行し、最後に Base64 に変換します

key = crypto.createHash('sha1').update(key+WS).digest('base64');

// クライアントに返されたデータを出力します。これらのフィールドは必須です

o.write('HTTP/1.1 101 Switching Protocolsrn');

o.write('Upgrade: websocketrn');

o.write ( 'Connection: Upgradern');

// このフィールドにはサーバーによって処理された KEY が含まれます

o.write('Sec-WebSocket-Accept: '+key+'rn');

// 空行を出力します。 HTTPヘッダーの終了

o.write('rn');

}

});

}).listen(8888);

このようにしてハンドシェイク部分が完了しました。データフレームによる分析と生成が生きています

まずは公式のフレーム構造図を見てみましょう

簡単な紹介

FINは終了したかどうかの指標です

RSVは予約されたスペース、0です

opcode は、データタイプ、分割されたスライスであるかどうか、バイナリ解析であるかどうか、ハートビートパケットなどを識別します。

opcode に対応するマップを提供します

MASK がマスクを使用するかどうか

Payload len と次の extend payload lengthはデータの長さを表し、これが最も厄介です

PayloadLenはわずか7ビットで、符号なし整数型に変換すると0から127までの値しか持ちません。 もちろん、そのような小さな値ではより大きなデータを記述することはできませんが、そのため、データ長が 125 以下の場合にのみデータ長の記述として使用できると規定されています。この値が 126 の場合、次の 2 バイトはデータ長を格納するために使用されます。 127 の場合、次の 8 バイトはデータ長の格納に使用されます

マスキングキーマスク

データフレームを解析するためのコードは以下に掲載されています

function decodeDataFrame(e) {

var i = 0,

j,s,

frame = {

FIN: e[i] >> 7,

オペコード: e[i++ ] & 15,

マスク: e[i] > > 7,

PayloadLength: e[i++] & 0x7F

};

if(frame.PayloadLength === 126) {

frame.PayloadLength = (e[i++] << 8) + e [i++];

}

if(frame.PayloadLength === 127) {

i += 4;

frame.PayloadLength = (e[ i++] << 24) + (e[i++] << 16) + (e[i++] << 8) + e[i++];

}

if(frame.Mask) {

frame.MaskingKey = [e[i++], e[ i++], e[i++], e[i++]];

for(j = 0, s = []; j

s.push(e[i+j] ^ Frame.MaskingKey[j%4]);

}

} else {

s = e.slice(i, i+frame.PayloadLength);

}

s = 新しいバッファー;

if(frame.Opcode === 1) {

s = s.toString();

}

frame.PayloadData = s;

return Frame ;

}

その後、データフレームは次のようになります生成された

function encodeDataFrame(e) {

var s = [],

o = new Buffer(e.PayloadData),

l = o.length;

s.push((e .FIN << 7) + e.Opcode);

if(l < 126) {

s.push(l);

} else if(l < 0x10000) {

s.push (126, (l&0xFF00) >> 8, l&0xFF);

} else {

s.push(127, 0, 0, 0, 0, (l&0xFF000000) >> 24, (l&0xFF0000) > > 16, (l&0xFF00) >> 8, l&0xFF);

}

return Buffer.concat([new Buffer(s), o]) ;

}

これらはすべてフレーム構造図に従って処理されます。ここでは詳しく説明しません。興味のある方は、Web Technology Research Institute にアクセスしてください。 3. WebSocket の写真と WebSocket の音声チャット ルームの転送

この記事のメイン部分は、WebSocket の使用シナリオをいくつか示すことです

1. 写真を転送する手順について考えてみましょう

まず、サーバーはクライアントのリクエストを受信し、それを読み取り、バイナリ データをクライアントに転送します。クライアントはそれをどのように処理しますか?もちろん、FileReader オブジェクトを使用します

最初にクライアント コードを指定してください

var ws = new WebSocket("ws://xxx.xxx.xxx.xxx:8888");ws.onopen = function (){ サーバー側のコードに移動します

console.log("ハンドシェイクが成功しました");

};

ws.onmessage = function(e) {

var Reader = new FileReader();

reader.onload = function(event ) {

var content = events.target.result;

var a = new Image();

a.src = content;

document.body.appendChild(a);

}

reader.readAsDataURL ( e.data);

};

メッセージを受信し、次に readAsDataURL を実行し、画像 Base64 をページに直接追加します

fs.readdir( "スカイランド" 、 function(err, files) {if(err) {s.push((1 << 7) + 2) この文は、オペコードを 2 に直接書き込むのと同じです。バイナリ フレームの場合、クライアントはデータを受信するときに toString を実行しようとしません。そうでない場合は、エラーが報告されます~

throw err;

}

for(var i = 0; i < files.length; i++) {

fs.readFile ('skyland /' + files[i], function(err, data) {

if(err) {

throw err;

}

o.write(encodeImgFrame(data));

});

}

});

function encodeImgFrame(buf) {

var s = [],

l = buf.length,

ret = [];

s.push((1 <<< ; 7) + 2);

if(l < 126) {

s.push(l);

} else if(l < 0x10000) {

s.push(126, (l&0xFF00) >) ;> 8, l&0xFF);

} else {

s.push(127, 0, 0, 0, 0, (l&0xFF000000) >> 24, (l&0xFF0000) >> 16, (l&0xFF00) >> ; 8, l&0xFF);

}

return Buffer.concat([new Buffer(s), buf]);

}

コードは非常に単純です。ここで共有します WebSocket による写真の転送速度を見てみましょう

多くの写真、合計 824 万枚をテストしました

通常の静的リソース サーバーは約 20 秒かかります (サーバーが遠い)

cdn約 2.8 秒かかります

WebSocket メソッドについてはどうでしょうか? ? !

答えは、これも約 20 秒かかります。 とてもがっかりしませんか? 送信速度が遅いのは、サーバーがローカル マシン上の同じ画像リソースを読み込むことができるということではありません。約1秒...データフローができないようです 距離制限を突破して通信速度を上げることはできません

WebSocketの別の使い方を見てみましょう~

WebSocketを使用して音声チャットルームを構築します

まずボイスチャットルームの機能を説明します

ユーザーがチャンネルに入った後、マイクから音声を入力すると、それがバックグラウンドに送信されてチャンネル内の他の人に転送され、他の人がメッセージを受信して​​再生します

それ問題は 2 か所にあるようです。1 つ目はオーディオ入力で、2 つ目は再生用のデータ ストリームの受信です

まず話しましょうオーディオ入力については、ここでは HTML5 の getUserMedia メソッドが使用されていますが、注意してください。このメソッドはオンラインになると大きな落とし穴になります

最後に、最初にコードを投稿します

if (navigator.getUserMedia) {navigator .getUserMedia(コードがローカルで実行されている場合、ブラウザはマイク入力を有効にするかどうかを尋ねるメッセージを表示します。確認すると、コードが起動します

{ audio: true },

function ( stream) {

var rec = new SRecorder(stream);

recorder = rec;

})

}

first パラメータは {audio: true} で、オーディオのみを有効にしてから、 SRecorder オブジェクト。以降の操作は基本的にこのオブジェクトに対して実行されます。この時点で、

次に、SRecorder コンストラクターとは何かを見て、重要な部分を説明します。

var SRecorder = function(stream) {……

var context = new AudioContext();

var audioInput = context.createMediaStreamSource(stream);

var Recorder = context.createScriptProcessor ( 4096、1、1);

AudioContext はオーディオ コンテキスト オブジェクトです。サウンド フィルタリングを行ったことがある学生は、「オーディオの一部が再生のためにスピーカーに到達する前に、途中でインターセプトされるため、オーディオ データを取得します。このインターセプトは window.AudioContext によって実行されます」ということを知っているはずです。これを行うには、オーディオに対するすべての操作がこのオブジェクトに基づいています。」 AudioContext を通じてさまざまな AudioNode ノードを作成し、フィルターを追加して特別なサウンドを再生できます

録音原理は同じですが、AudioContext を使用する必要もありますが、マイクのオーディオ入力を受信するには、通常のように ajax を使用してオーディオ ArrayBuffer オブジェクトを要求し、それをデコードすることでオーディオを処理する代わりに、マイクは createMediaStreamSource メソッドを使用する必要があります。このパラメーターは 2 番目のパラメーターであることに注意してください。 getUserMedia メソッドのパラメータ

createScriptProcessor メソッドについて話しましょう。その公式の説明は次のとおりです:

JavaScript 経由で直接オーディオ処理に使用できる ScriptProcessorNode を作成します。

——————

要約すると、このメソッドは次のようになります。 JavaScript を使用して音声収集操作を処理することです

いよいよ音声を収集します。勝利は目前です!

次にマイク入力をオーディオキャプチャに接続しましょう

audioInput.connect(recorder);

recorder.connect(context.destination);

context.destination説明は以下の通り

AudioContext インターフェイスの destination プロパティは、コンテキスト内のすべてのオーディオの最終的な宛先を表す AudioDestinationNode を返します。

——————

context.destination は、環境内のオーディオを表す最終的な宛先を返します。

この時点では、オーディオの収集を監視するイベントがまだ必要です

audioData はインターネットで見つけたオブジェクトです。メインの encodeWAV メソッドは何度も実行されています。これは最後に完全なコードと一緒に掲載されます

recorder.onaudioprocess = function (e) {

audioData.input(e.inputBuffer.getChannelData(0));

}

この時点で、

ユーザー全体がチャンネルに入った後、マイクから音声を入力する

ことが完了しました。 次に、音声ストリームをサーバーに送信します。これは少し面倒ですが、先ほど述べたように、WebSocket はさまざまなオペコードを通じて返されたデータがテキストであるかバイナリ データであるかを示すことができ、onaudioprocess への入力は配列であり、サウンドを再生するために最終的に必要なものは Blob です。 、 {type: 'audio/wav'} のオブジェクトなので、送信する前に配列を WAV blob に変換する必要があります。このとき、上記の encodeWAV メソッドが使用されます サーバーは非常に単純であるようです。転送してください

ローカルテストは確かに可能です

ただし、シンクホールは発生します わかりました!

サーバー上でプログラムを実行するときに getUserMedia メソッドを呼び出すと、安全な環境にある必要があること、つまり https が必要であることを求めるプロンプトが表示されます。つまり、ws も wss に置き換える必要があります。つまり、サーバー コードは独自のカプセル化されたハンドシェイク、解析、エンコードを使用します。コードは次のとおりです

var https = require('https');

コードはまだ非常に単純です。https モジュールを使用し、最初に説明した ws モジュールはシミュレートされたチャネルであり、転送のコア機能のみを実装します。

ws モジュールは実装が非常に便利なので使用されます。 wss と https はロジック コード 0 と競合します

https の確立については、主に秘密キー、CSR 証明書の署名、および証明書ファイルが必要です。興味のある学生はそれについて学ぶことができます。現在のネットワーク環境では getUserMedia を使用できません ……)

以下は完全なフロントエンド コードです

​​

var fs = require('fs');

var ws = require ('ws');

var userMap = Object.create(null);

var options = {

key: fs.readFileSync('./privatekey.pem'),

cert: fs.readFileSync(' ./certificate.pem')

} ;

var server = https.createServer(options, function(req, res) {

res.writeHead({

'Content-Type' : 'text/html'

)

});

fs.readFile( './testaudio.html', function(err, data) {

if(err) {

return ;

}

res.end(data);

});

});

var wss = new ws.Server({server: サーバー});

wss.on('接続', function(o) {

o.on('メッセージ', function(message) {

if(message .indexOf('user') === 0) {

var user = message.split(':')[1];

userMap[user] = o;

} else {

for(var u in userMap) {

userMap[u].send(message);

}

}

});

});

server.listen(8888) ;

var a = document.getElementById('a');この記事では、まず WebSocket の将来について展望します。仕様に従って自分たちでデータ フレームを解析して生成することを試み、WebSocket についてより深く理解しました

var b = document .getElementById('b');

var c = ドキュメント .getElementById('c');

navigator.getUserMedia = navigator.webkitGetUserMedia ||

var gRecorder = null;

var audio = ドキュメント.querySelector('audio');

var ドア = false ;

var ws = null;

b.onclick = function() {

if(a.value === '') {

alert( 'ユーザー名を入力してください');

return false;

}

if(!navigator.getUserMedia) {

alert('申し訳ありませんが、お使いのデバイスはボイスチャットできません');

return false;

}

SRecorder .get(function (rec) {

gRecorder = rec;

});

ws = new WebSocket("wss://x.x.x.x:8888");

ws.onopen = function() {

console .log('ハンドシェイク成功');

ws .send('user:' + a.value);

};

ws.onmessage = function(e) {

receive(e.data);

};

document.onkeydown = function(e ) {

if(e.keyCode === 65) {

if(!door) {

gRecorder.start();

door = true;

}

}

};

document .onkeyup = function(e) {

if(e.keyCode === 65) {

if(door) {

ws.send(gRecorder.getBlob( ));

gRecorder.clear();

gRecorder.stop();

door = false;

}

}

}

}

c.onclick = function() {

if (ws) {

ws.close();

}

}

var SRecorder = function(stream) {

config = {};

config.sampleBits = config.smapleBits || 8;

config.sampleRate = config.sampleRate || (44100 / 6);

var context = new AudioContext();

var audioInput = context.createMediaStreamSource(stream);

var Recorder = context.createScriptProcessor(4096, 1, 1);

var audioData = {

size : 0 // 録音ファイルの長さ

, バッファ: [] // 録音バッファ

, inputSampleRate: context.sampleRate // 入力サンプリング レート

, inputSampleBits: 16 to to config.sampleRate //出力サンプリング レート

, oututSampleBits: config.sampleBits //出力サンプル ビット 8、16

, clear: function() {

this.buffer = [];

this.size = 0 ;

}

、入力: 関数 (データ) {

this.buffer.push(new Float32Array(data));

this.size += data.length;

}

、圧縮: 関数 ( ) { //マージと圧縮

//マージ

var data = new Float32Array(this.size);

var offset = 0;

for (var i = 0; i

data.set(this.buffer[i], offset);

offset += this.buffer[i].length;

}

//Compression

var Compression = parseInt(this. inputSampleRate / this.outputSampleRate);

var length = data.length / COMPLEX;

var result = new Float32Array(length);

var Index = 0, j = 0;

while (index < length) {

result[index] = data[j];

j += 圧縮;

index++;

}

return result;

}

, encodeWAV: function () {

var sampleRate = Math. min( this.inputSampleRate, this.outputSampleRate);

var sampleBits = Math.min(this.inputSampleBits, this.oututSampleBits);

var bytes = this.compress();

var dataLength = bytes.length * (サンプルビット / 8);

var バッファ = new ArrayBuffer(44 + dataLength);

var data = new DataView(buffer);

var channelCount = 1;//モノラル

var offset = 0;

var writeString = function (str) {

for (var i = 0; i

data.setUint8(offset + i, str.charCodeAt(i));

}

};

// リソース交換ファイル識別子

writeString('RIFF'); offset += 4;

// 次のアドレスからファイルの終わりまでの合計バイト数、つまりファイルサイズ -8

data.setUint32( offset, 36 + dataLength, true); offset += 4;

//WAV ファイル フラグ

writeString('WAVE');

//Wave フォーマット フラグ

writeString('fmt ') ; offset += 4;

//フィルターバイト、通常は 0x10 = 16

data.setUint32(offset, 16, true);

//フォーマットカテゴリ (PCM フォーマット)サンプリングデータ)

data.setUint16(offset, 1, true); offset += 2;

// チャンネル数

data.setUint16(offset, channelCount, true);

//サンプリング レート、1 秒あたりのサンプル数は、各チャンネルの再生速度を表します

data.setUint32(offset,sampleRate, true); offset += 4;

//波形データ転送速度 (平均バイト数) 2 番目の数値) モノラル チャンネル モノラル チャンネルごとに 1 つのサンプルが占めるバイト数 × サンプルごとのデータ ビット数を調整します/8

data.setUint16(offset, channelCount * (sampleBits / 8), offset += 2);

// サンプルごとのデータビット数

data.setUint16(offset, sampleBits, true); offset += 2;

//データ識別子

writeString('data'); //サンプルデータの総数、つまりデータの総サイズ -44

data.setUint32(offset, dataLength, true); offset += 4;

// サンプルデータを書き込む

if (sampleBits == = 8) {

for (var i = 0; i

var s = Math.max(-1, Math.min(1, bytes[i]));

var = s

val = parseInt(255 / (65535 / (val + 32768)));

data.setInt8(offset, val, true);

}

} else {

for (var i = 0; i

var s = Math.max(-1, Math.min(1, bytes[i]));

data.setInt16(offset, s

}

}

return new Blob([data], { type: 'オーディオ/wav' });

}

};

this.start = function () {

audioInput.connect(recorder);

recorder.connect(context.destination);

}

this.stop = function () {

recorder .disconnect();

}

this.getBlob = function () {

return audioData.encodeWAV();

}

this.clear = function() {

audioData.clear();

}

recorder.onaudioprocess = function (e) {

audioData.input(e.inputBuffer.getChannelData(0));

}

};

SRecorder。 get = function (コールバック) {

if (コールバック) {

if (navigator.getUserMedia) {

navigator.getUserMedia(

{ audio: true },

function (stream) {

var rec = new SRecorder(stream);

callback(rec) ;

})

}

}

}

function accept(e) {

audio.src = window.URL.createObjectURL(e);

}

注: ライブで a キーを押して話し、a キーを放して送信します

ボタンを押さずにリアルタイムで会話しようとして、setInterval を通して送信しましたが、ノイズがこれには、encodeWAV カプセル化の別の層と、環境ノイズを除去するためのより多くの機能が必要です

最後に、2 つのデモを見ていきます。WebSocket の可能性に関して言えば、音声チャット ルームのデモは幅広い範囲をカバーしています。 AudioContext オブジェクトに慣れていない学生は、まず AudioContext について理解する必要があります

この記事はここで終わります~ アイデアや質問があれば、一緒に議論したり探索したりすることを歓迎します ~

著者: TAT に感謝します。 vorshen

原文: http://www.alloyteam.com/2015/12/websockets-ability-to-explore-it-with-voice-pictures/

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。