LoginSignup
4
0

More than 5 years have passed since last update.

webrtc.orgのnative使ってMacOSのプログラム組む・続き

Posted at

概要

unityとwebrtcを連携させたい。
とりあえず手元ではMacOS、Android、iOSでできたので、情報を共有しておきたい

というわけでMacOSでの話

https://qiita.com/taktod/items/a8c87068265fff951c0f
これの続き

この記事では接続してchromeのwebrtcと音声の共有したいと思います。

webrtcのコードのメモ

Nativeの場合はこんな具合になる。

  1. PeerConnectionFactoryを作る
  2. localStreamをfactoryから作る(映像や音声を共有しないならいらないけど)
  3. シグナリングサーバーから接続相手の情報をもらう。
  4. PeerConnectionをfactoryから作る
  5. 作ったconnectionにlocalStreamつける
  6. DataChannel贈りたかったら、connectionから作る
  7. connectionからoffer作る
  8. offerのsdpできたらlocalDescriptionとして登録 signaling経由で相手に送る
  9. remoteのsdpもらったらremoteDescriptionとしてconnectionに登録
  10. あとはcandidateの情報を互いに投げて登録する。
  11. あとはイベントにしたがって色々やる。

といった具合

ちなみにsignalingはなんとかしてください。
socketRocketでsignalingを書く想定なのでobjective-c++で記述していきます。

とりあえずコードを作っていきます。

PeerConnectionFactory

https://github.com/taktod/UnityWebrtcPlugin/blob/master/UnityWebrtcPlugin/TakPeerConnectionFactory.hpp
nativeのオブジェクトをwrapしてるだけ
特になにもしてない。

IceServer

https://github.com/taktod/UnityWebrtcPlugin/blob/master/UnityWebrtcPlugin/TakIceServer.hpp
peerConnectionを作るときに利用するサーバーの設定
stunとかturnとかの設定入れるやつ
単にuri、username、passwordの3つがやりとりできれば十分なので、コンストラクタで入れて、参照できるようにしただけ。

Constraint

https://github.com/taktod/UnityWebrtcPlugin/blob/master/UnityWebrtcPlugin/TakConstraint.hpp
audio:true、video:trueとか入れるやつですね。

MediaStream

https://github.com/taktod/UnityWebrtcPlugin/blob/master/UnityWebrtcPlugin/TakMediaStream.hpp
とりあえずLocalStreamだけ実装
しかも映像をこちらから送ることは今の所しない。
RemoteStreamを実装してVideoSinkをあわせるとVideoSinkからI420データを取り出してそれをtextureとして書き出すとかできるようになる。

PeerConnection

https://github.com/taktod/UnityWebrtcPlugin/blob/master/UnityWebrtcPlugin/TakPeerConnection.hpp
コネクション動作
とりあえずラムダ式でつかえるような感じにしてあります。

とりあえず動作テストとしてPeerConnectionつくってofferをつくってみた。

Entry.mm
    void takWebrtc_make() {
        cout << "make is called" << endl;
        TakWebrtc::initialize();
        // とりあえずPeerConnectionでもつくってみようとおもう。
        TakIceServer server = TakIceServer("stun:stun.l.google.com:19302", "", "");
        TakIceServer *lps = &server;

        TakPeerConnection *conn = new TakPeerConnection(_factory, &lps, 1);
        conn->onIceCandidate = [](const IceCandidateInterface *candidate) {
            // candidateの応答
        };
        TakConstraint constraint;
        constraint.AddMandatory("OfferToReceiveAudio", "true");
        constraint.AddMandatory("OfferToReceiveVideo", "true");
        conn->createOffer(&constraint, [conn](SessionDescriptionInterface *sdp) {
            cout << "sdpができてる" << endl;
            string str;
            sdp->ToString(&str);
            cout << str << endl;
        });
    }

動作はこんなぐあい

$ mono test.exe
make is called
sdpができてる
v=0
o=- 7946598842490890441 2 IN IP4 127.0.0.1
s=-
t=0 0
...
a=fmtp:101 apt=100
a=rtpmap:127 ulpfec/90000

Websocket

やっぱりちゃんと動作するかまで試そうとおもう。
とりあえずsignalingしたいのでwebsocketがほしい。
https://github.com/taktod/UnityWebrtcPlugin/blob/master/UnityWebrtcPlugin/TakWebsocket.hpp
単純にwebsocketで通信する動作
ただし、c++でつかいたいのでsocketRocketをwrap

こんな感じで動作しました。

    void sendOffer(string targetId) {
        // peerConnectionをつくって相手におくる。
        TakIceServer server = TakIceServer("stun:stun.l.google.com:19302", "", "");
        TakIceServer *lps = &server;

        _conn = new TakPeerConnection(_factory, &lps, 1);
        _conn->onIceCandidate = [targetId](const IceCandidateInterface *candidate) {
            // candidateの応答
            string str;
            candidate->ToString(&str);
            NSDictionary *dic
             = @{
                 @"target":@(targetId.c_str()),
                 @"from":@(myId.c_str()),
                 @"type":@"candidate",
                 @"candidate":@(str.c_str()),
                 @"sdpMid":@(candidate->sdp_mid().c_str()),
                 @"sdpMLineIndex": @(candidate->sdp_mline_index())
                 };
            NSData *data = [NSJSONSerialization dataWithJSONObject:dic options:0 error:nil];
            NSString *message = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
            _socket->send([message UTF8String]);
        };
        _conn->addStream(_stream);
        TakConstraint constraint;
        constraint.AddMandatory("OfferToReceiveAudio", true);
        constraint.AddMandatory("OfferToReceiveVideo", false);
        _conn->createOffer(&constraint, [targetId](SessionDescriptionInterface *sdp) {
            _conn->setLocalDescription(sdp);
            string str;
            sdp->ToString(&str);
            NSDictionary *dic
             = @{
                 @"target" : @(targetId.c_str()),
                 @"from" : @(myId.c_str()),
                 @"type" : @"sdp",
                 @"value" : @{
                         @"type": @(sdp->type().c_str()),
                         @"sdp": @(str.c_str())
                         }
                 };
            NSData *data = [NSJSONSerialization dataWithJSONObject:dic options:0 error:nil];
            NSString *message = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
            _socket->send([message UTF8String]);
        });
    }
    void sendAnswer(string targetId, string type, string sdp) {
        TakIceServer server = TakIceServer("stun:stun.l.google.com:19302", "", "");
        TakIceServer *lps = &server;

        _conn = new TakPeerConnection(_factory, &lps, 1);
        _conn->onIceCandidate = [targetId](const IceCandidateInterface *candidate) {
            // candidateの応答
            string str;
            candidate->ToString(&str);
            NSDictionary *dic
            = @{
                @"target":@(targetId.c_str()),
                @"from":@(myId.c_str()),
                @"type":@"candidate",
                @"candidate":@(str.c_str()),
                @"sdpMid":@(candidate->sdp_mid().c_str()),
                @"sdpMLineIndex": @(candidate->sdp_mline_index())
                };
            NSData *data = [NSJSONSerialization dataWithJSONObject:dic options:0 error:nil];
            NSString *message = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
            _socket->send([message UTF8String]);
        };
        _conn->addStream(_stream);
        _conn->setRemoteDescription(type, sdp);
        TakConstraint constraint;
        constraint.AddMandatory("OfferToReceiveAudio", true);
        constraint.AddMandatory("OfferToReceiveVideo", false);
        _conn->createAnswer(&constraint, [targetId](SessionDescriptionInterface *sdp) {
            _conn->setLocalDescription(sdp);
            string str;
            sdp->ToString(&str);
            NSDictionary *dic
            = @{
                @"target" : @(targetId.c_str()),
                @"from" : @(myId.c_str()),
                @"type" : @"sdpAnswer",
                @"value" : @{
                        @"type": @(sdp->type().c_str()),
                        @"sdp": @(str.c_str())
                        }
                };
            NSData *data = [NSJSONSerialization dataWithJSONObject:dic options:0 error:nil];
            NSString *message = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
            _socket->send([message UTF8String]);
        });
    }
    void takWebrtc_make() {
        cout << "make is called" << endl;
        // 初期化
        TakWebrtc::initialize();
        // websocketでsignalingを実施する。
        /*
         自作signalingの仕様メモ
         接続したら{ids:[id, id...], self:id}がくる
         sdp: {target:id,from:id,type:"sdp",value:SdpSession情報}
         sdpAnswer: sdpのtypeがsdpAnswerになる
         candidate: {target:id,from:id,type:"candidate",candidate:...,"sdpMid":...,"sdpMLineIndex":...}
         という形でやりとりする形にしてある。
         */
        _stream = new TakMediaStream(_factory, "test", "", "audioId");
        _socket = new TakWebsocket("ws://192.168.0.18:8080/");
        _socket->onMessage = [](string message) {
            // 初接続時
            // [ids:array(), self:number];で応答がくる。
            // このidsのそれぞれにofferを出す必要がある。
            @try {
                string (^toString)(NSObject *) = ^(id target) {
                    string output = "";
                    if([[target className] isEqualToString:@"__NSCFNumber"]) {
                        output = string([[target stringValue] UTF8String]);
                    }
                    if([[target className] isEqualToString:@"NSTaggedPointerString"]) {
                        output = string([(NSString *)target UTF8String]);
                    }
                    return output;
                };
                NSData *data = [@(message.c_str()) dataUsingEncoding:NSUTF8StringEncoding];
                // ここか・・・c_strが途中できれてるようにみえるわけですか・・・なるほど・・・
                NSDictionary *jsonObj = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
                if(myId == "") {
                    // {ids:[], self:number}でデータがきてるはず
                    myId = toString(jsonObj[@"self"]);
                    if([jsonObj[@"ids"] count] > 0) {
                        // なんかあるので、そこにofferを出す
                        sendOffer(toString(jsonObj[@"ids"][0]));
                    }
                }
                else {
                    NSDictionary *dic
                     = @{
                         @"sdp": ^(){
                             cout << "sdp取得" << endl;
                             sendAnswer(toString(jsonObj[@"from"]), [jsonObj[@"value"][@"type"] UTF8String], [jsonObj[@"value"][@"sdp"] UTF8String]);
                         },
                         @"sdpAnswer": ^(){
                             cout << "sdpAnswer取得" << endl;
                             _conn->setRemoteDescription([jsonObj[@"value"][@"type"] UTF8String], [jsonObj[@"value"][@"sdp"] UTF8String]);
                         },
                         @"candidate": ^(){
                             cout << "candidate取得" << endl;
                             _conn->addIceCandidate(toString(jsonObj[@"candidate"]), toString(jsonObj[@"sdpMid"]), [jsonObj[@"sdpMLineIndex"] intValue]);
                         }
                         };
                    void (^exec)() = dic[jsonObj[@"type"]];
                    if(exec != nil) {
                        exec();
                    }
                }
            }@catch(NSException *e){
                NSLog(@"%@", e);
            }
        };
        _socket->connect();
    }

とりあえず、monoで動作するwebrtcの通信動作ができました。

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0