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

# 概要

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の３つがやりとりできれば十分なので、コンストラクタで入れて、参照できるようにしただけ。

## 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の通信動作ができました。