cvl-robot's diary

研究ノート メモメモ https://github.com/dotchang/

ofxGstreamerとofxGstRTPを使ってWindowsでh264ストリーミング配信、と、openCVで受信

Windowsで動画のストリーミング配信をするプログラムを自前で用意するのは、結構大変だ、という話をちょっと前にしました。

1.生の連番画像をOSCや自前のプロトコルを使って送信する方法、

2.HTTP経由でMotionJPGを配信する方法、

3.ffserverやGStreamerを使ってRTP経由でh264動画フォーマットで配信する方法

1,2は以前やっているので、今日は3をします。流量制御などが出来るようになるので、少しお洒落です。

1.ofxGStreamerとofxGstRTPのインストール

1.1 addonのインストール

openFrameworksのデベロッパーarturocさんの作った、ofxGstreamer[1]とofxGstRTP[2]並びに関連ライブラリを使います。いつものように、openFrameworksのaddonフォルダに追加します。

1. https://github.com/arturoc/ofxGStreamer/archive/master.zip

2. https://github.com/arturoc/ofxGstRTP/archive/master.zip

3. https://github.com/arturoc/ofxXMPP/archive/master.zip

4. https://github.com/arturoc/ofxNice/archive/master.zip

5. https://github.com/arturoc/ofxDepthStreamCompression/archive/master.zip

6. https://github.com/arturoc/ofxSnappy/archive/master.zip

7. https://github.com/arturoc/ofxEchoCancel/archive/master.zip

ダウンロードしてきて解凍して、フォルダ名から-masterを削るように改名して、openframeworksのaddonフォルダの中に移動します。7は、書いてあったからダウンロードしてきているだけで、windowsではソースコードの修正が必要でビルドがすんなり通らないので、無くても構いません。

1.2 GStreamerのインストール

addonの他に、GStreamer本体のインストールも必要です。[1]で説明があるように、通常版と開発版の両方を、[完全]指定でインストールします。そうしないとx264関連のコーデックが使えないそうです。

1. http://gstreamer.freedesktop.org/data/pkg/windows/1.2.2/gstreamer-1.0-x86-1.2.2.msi

2. http://gstreamer.freedesktop.org/data/pkg/windows/1.2.2/gstreamer-1.0-devel-x86-1.2.2.msi

環境変数のPATHの指定は、必要なDLLを自前のexeファイルのあるフォルダにコピーしてしまえば良いので、必須ではありません。

 1.3 projectGeneratorで空プロジェクトを生成し、exampleを上書き

プロジェクトジェネレータで下図のようにaddonを指定して空のプロジェクトを作ります。今回入れたものの他に指定しているのは、ofxGuiとofxOscです。また、ofxEchoCancelは使いません。

f:id:cvl-robot:20141128170949p:plain

 今回ベースにするexampleは、ofxGstRTPの中のexample_video_audio_LANです。exampleからsrcとbinフォルダをコピーして、乱暴ですが、空プロジェクトに上書きします。

slnファイルを開いて、やみくもにビルドを掛けると、いくつかのコンパイルエラーが出てきます。エラーの数は多いのですが、すべてofxNiceAgent.h内でuintの型の定義が見つからない、という物なので、型の定義を追加します。

ofxNiceAgent.h

/*
* ofxNiceAgent.h
*
* Created on: Aug 27, 2013
* Author: arturo
*/

#ifndef OFXNICEAGENT_H_
#define OFXNICEAGENT_H_

#include <agent.h>
#include <map>
#include "ofConstants.h"
#include "ofTypes.h"

#ifndef uint
typedef unsigned int uint;
#endif

class ofxNiceStream;

 

 ビルドが通り、いざ実行しようとすると、次はgstreamer関連のdllがみつからないというシステムエラーが表示されます。環境変数でPATHを通すか、関連dllをすべてローカルにコピーして持ってきてexeと同じフォルダに置きます。

E:\gstreamer\1.0\x86\bin

のフォルダの中にあるdllファイルを(選別するのが面倒くさいので)全部持ってきます。

1.4 デモの実行

USBカメラが必要なので適当に用意しておいてください。また、本格的に実験してみたいならPC+カメラが2組あると便利です。

このデモプログラムは、本来2台のPC間でテレビ電話のようにカメラ映像と音声を双方向に配信しよう、という物です。音声は、標準の録音デバイスが使われます。

f:id:cvl-robot:20141128173704g:plain

右下の小枠の表示が、サーバー側、大きな枠の表示が、クライアント側です。

今は、一台のPC内で同じポートを指定して通信していますので、同じものが表示されています。

ofApp.cpp内で送受信先の指定が直書きされています。

client.setup("127.0.0.1",0);
client.addVideoChannel(5000);
client.addAudioChannel(6000);

server.setup("127.0.0.1");
server.addVideoChannel(5000,640,480,30);
server.addAudioChannel(6000);

 ソースコード内のコメントにあるように、2台のPCで通信したい時は、互いにクロスして通信するように設定を変えた2つのexeファイルを用意します。もしくは、スマートに外部ファイル等で指定できるようにします。

1台目

client.setup("127.0.0.1",0);
client.addVideoChannel(5000);
client.addAudioChannel(6000);

server.setup("127.0.0.1");
server.addVideoChannel(7000,640,480,30);
server.addAudioChannel(8000);

 2台目

client.setup("127.0.0.1",0);
client.addVideoChannel(7000);
client.addAudioChannel(8000);

server.setup("127.0.0.1");
server.addVideoChannel(5000,640,480,30);
server.addAudioChannel(6000);

 これでテレビ電話風になります。2台別のPCで通信する場合は、IPも変更してください。

この動画がカクカクしているのはデスクトップキャプチャが重いせいですが、ディレイがあるのはGStreamerのせいです。とくに、音声の負荷が重いようですので、不要なら画像だけを配信するようにすると、多少改善します。

 

2.双方向で通信なんてしなくていいから、受信だけOpenCVだけでやりたい場合

ofxGStreamerは機能比でみればとてもお手軽ですが、通信させたいコンピュータすべてにGStreamerをいちいちセットアップするのはおっくうです。なので、OpenCVだけで画像の受信をしたいと思います。参考ソースコードはこちら。

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;

#ifdef _DEBUG
#pragma comment(lib, "E:\\opencv300\\build\\x86\\vc11\\lib\\opencv_world300d.lib")
#else
#pragma comment(lib, "E:\\opencv300\\build\\x86\\vc11\\lib\\opencv_world300.lib")
#endif

int main(int argc, char* argv[])
{
  Mat frame;
  namedWindow("video", 1);
  //VideoCapture cap("http://127.0.0.1:7890/ipvideo.mjpg");
  //VideoCapture cap("rtp://127.0.0.1:7000/");
  VideoCapture cap("E:\\workspace\\of_v0.8.4_vs_release\\apps\\myApps\\cvMjpegReceiver\\stream.sdp");
  while ( cap.isOpened() )
  {
    cap >> frame;
    if(frame.empty()){
    cout << "empty" << endl;
    break;
  }

  //cvtColor(frame, frame, cv::COLOR_RGB2BGR);
  imshow("video", frame);
  if(waitKey(30) >= 0) break;
  }

  return 0;
}

 openCVには動画ストリーム受信のための便利なクラスVideoCaptureが用意されており、これを使います。実態は、ffmpegやgstreamerなどの外部ライブラリのラッパーです。

単純にソースを指定してやればいいのですが、URLを直に次のように書くと、

VideoCapture cap("rtp://127.0.0.1:7000/");

エラーが出て上手く受信することが出来ません。

[rtp @ 006c1a60] Unable to receive RTP payload type 96 without an SDP file describing it

h264ストリームを読ませるためには、sdpファイルというのが必要なようです[5]。 

stream.sdp

v=0
c=IN IP4 127.0.0.1/7000
m=video 7000 RTP/AVP 96
b=AS:6000
a=rtpmap:96 H264/90000

単にテキストで、通信先IPとポート番号を記述したものです。これを用意して、VideoCaptureに読ませます。

IP 127.0.0.1

PORT 7000

h264で決められたPayloadの型の指定 96

ボーレート 6000kbps

動画フォーマット H264/90000 (90000の意味は分かりません)

 sdpフォーマットは他にもオプションが数多くあるようですので、ここを調整すればもっとうまく通信できるようになるかもしれません。

f:id:cvl-robot:20141128181311g:plain

左は、1台目のポート設定のサーバーです。受信ストリームに何も入ってこないので、受信画面が化けています。右がopenCVのimshowの結果です。時間遅れやばらつきが気になりますが、大半はデスクトップキャプチャの影響によるものです。

2.2 USBカメラの映像でなく、自前で用意した動画や画像を送るには?

ユーザーがやるべきことは、とても簡単です。各フレームごとに画像をofPixels形式にして渡してやればいいので、なんでも送ることが出来ます。難しいことは、gstreamer任せです。

ofApp.cpp内のupdate関数を書き換えます。

//--------------------------------------------------------------
void ofApp::update(){
  grabber.update();
  if(grabber.isFrameNew()){
    server.newFrame(grabber.getPixelsRef());
  }

  client.update();
  if(client.isFrameNewVideo()){
    remoteVideo.loadData(client.getPixelsVideo());
  }
}

 grabberで撮ってきたカメラ画像を渡す代わりに、自前で用意した適当な画像を渡してやればいいわけです。

これで、新RICOH THETAなどの全天球動画配信も自前でできますね!

 

マルチキャストのやり方調査中[3][4]・・・わからん。

暗号化は使わないので、調べていない。

 

2015年10月3日追記

開発環境をMSVC2015に変更すると、XMPP__imp____iob_funcがリンクできないと言うエラーが出てきます。この解決のために、main.cppのグローバルに見えるところに次のダミーを書きます。

参考: c++ - unresolved external symbol __imp__fprintf and __imp____iob_func, SDL2 - Stack Overflow

FILE _iob[] = {*stdin, *stdout, *stderr};

extern "C" FILE * __cdecl __iob_func(void)
{
    return _iob;
}

 

[1] arturoc/ofxGStreamer · GitHub

[2] arturoc/ofxGstRTP · GitHub

[3] Documentation/Maemo 5 Developer Guide/Using Multimedia Components/Camera API Usage - maemo.org wiki

[4] SDP‚Æ‚Í -- KeyFŽGŠwŽ–“T

[5] sip - Minimum SDP for making a H264 RTP stream? - Stack Overflow

gstreamerの使い方の説明が分かりやすい。

[6] THETA S + Raspberry pi 2でライブストリーミングしてみた(続)