msgpackとOpenCVを使って、iMACなどのiSightからブラウザに画像をリアルタイム配信
base64encodedなdataURIより、createObjectURLのが速かったのでメモ
まぁ、HD画像のdataURIとか長すぎなので当然といえば当然なのですが、Blob経由のimg貼り付けという記事をあまり見つけられなかったので。
準備
msgpackのbinary dataをWebSocketを利用して配信する為に以下のコードを利用します。
1.opencvをインストール
$ brew install opencv
2.linear-cpp/linear-jsをclone, makeなど
\## C++
$ git clone --recursive https://github.com/linear-rpc/linear-cpp
\## linear-cpp/sampleのws_server_sample.cppを下記サンプルコードで上書き
$ cp ws_server_sample.cpp linear-cpp/sample
$ cd linear-cpp; ./bootstrap && configure --with-sample && make
$ git clone --recursive https://github.com/linear-rpc/linear-js
\## linear-js/src/linear.debug.jsを下記サンプルコード index.htmlと同じ場所に配置
サンプルコード
送信側(C++)
ws_server_sample.cpp
#include "opencv2/opencv.hpp"
#include "linear/ws_server.h"
#include "linear/group.h"
int main() {
linear::shared_ptr<linear::Handler> dummy;
linear::WSServer server(dummy);
// open cam
cv::VideoCapture cap(0);
if(!cap.isOpened()) {
return -1;
}
// starts on INADDR_ANY:37800
server.Start("0.0.0.0", 37800);
while (true) {
// capture image
cv::Mat frame, resized;
cap >> frame;
cv::resize(frame, resized, cv::Size(), 0.5, 0.5); // iMAC, orig: 1280x1024 -> 640x512
// show image on local window
cv::imshow("window", resized);
// jpeg compression
std::vector<unsigned char> jpeg;
std::vector<int> param = std::vector<int>(2);
param[0] = CV_IMWRITE_JPEG_QUALITY;
param[1] = 95; // 0-100
cv::imencode(".jpg", resized, jpeg, param);
// send jpeg data
linear::Notify notify("image", linear::type::binary(reinterpret_cast<char*>(jpeg.data()), jpeg.size()));
notify.Send(LINEAR_BROADCAST_GROUP);
// press 'q' to exit
int key = cv::waitKey(16); // wait 16ms => 60fps
if(key == 113) {
break;
}
}
cv::destroyAllWindows();
server.Stop();
return 0;
}
受信側(HTML + JavaScript)
index.html
<!DOCTYPE html>
<html>
<head>
<title>Jpeg Streaming</title>
<meta charset='utf-8'/>
<script type='text/javascript' src='linear.debug.js'></script>
<script>
window.onload = function() {
var transport = {
type: 'websocket',
host: '127.0.0.1',
port: 37800,
channel: 'linear',
};
var client = new linear.client({transports: [transport]});
client.onnotify = function(notification) {
var blob = new Blob([linear.tobinary(notification.data)], {type: "image/jpeg"});
document.getElementById('view').src = URL.createObjectURL(blob);
};
client.connect();
};
</script>
</head>
<body id='body'>
<img id='view'></img>
</body>
</html>
確認
## ターミナルを開いて、サーバ起動
$ /path/to/ws_server_sample
## 同じPC上のbrowserでindex.htmlを開く。(違うPCからアクセスする場合は、index.htmlのhost部分をサーバを起動させているPCのアドレスやFQDNに書き換え)
$ open /path/to/index.html
ポイント
- ネットワークから受信したjpegのbinary dataを直接ブラウザのimgタグに貼り付けるには、Blobが有効。(私の環境では、base64encodedなdataを送信して、imgタグにdataURIとして貼り付けるより10倍くらい速かったです: img.src書き換え前後にconsole.time入れて計測)
- サーバ・クライアント合計70行くらいで実装できて多分、簡単。
- file:: schemeだろうが何だろうが直接CORS可能。(良し悪しですが)
- 今回の場合、msgpackを用いるメリットはないですが、jpegデータとその他情報などを混在させて送受信するようなアプリケーションでは有効なはず。
- WebAudio使えば音声なども送受信&&デコード出来ると思いますが、P2Pでは無いですし、TCPですし、リップシンクとかも大変なので、ちゃんとしたリアルタイムコミュニケーションにはWebRTCのがよいとおもいまうす。