概要
unityとwebrtcを連携させたい。
とりあえず手元ではMacOS、Android、iOSでできたので、情報を共有しておきたい
というわけでMacOSでの話
https://qiita.com/taktod/items/a8c87068265fff951c0f
これの続き
この記事では接続してchromeのwebrtcと音声の共有したいと思います。
webrtcのコードのメモ
Nativeの場合はこんな具合になる。
- PeerConnectionFactoryを作る
- localStreamをfactoryから作る(映像や音声を共有しないならいらないけど)
- シグナリングサーバーから接続相手の情報をもらう。
- PeerConnectionをfactoryから作る
- 作ったconnectionにlocalStreamつける
- DataChannel贈りたかったら、connectionから作る
- connectionからoffer作る
- offerのsdpできたらlocalDescriptionとして登録 signaling経由で相手に送る
- remoteのsdpもらったらremoteDescriptionとしてconnectionに登録
- あとはcandidateの情報を互いに投げて登録する。
- あとはイベントにしたがって色々やる。
といった具合
ちなみに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をつくってみた。
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の通信動作ができました。