WebRTC Data Channel Sample for Android を作ってみた

ここんところ、WebRTCのアプリを作ろうとして、四苦八苦していました。

やっと、データチャンネルで送受信するアプリができたので、ここに公開します。

開発環境ですが
Android Studio 1.2.1.1 build 141.19032520
を使って開発しています。

普通にプロジェクトを作成します。

まず、WebRTCのライブラリをインポートする為の設定を行います。

app配下にあるbuild.gradleのdependenciesに下記の一行を入れるだけ

compile 'io.pristine:libjingle:9357@aar'

これでなんと、ネイティブのsoライブラリまで持ってきてくれます(なんと便利な!)

150610-0006

次に、AndroidManifest.xmlにパーミッションを追加します

<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-feature android:glEsVersion="0x00020000" android:required="true" />

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />

150610-0003

これで、準備OK、ここからは、MainActivity.javaのソースをそのまま掲載しちゃいます。

package jp.eguchi.android.datachannelsample;

// WebRTC Data Channel Sample Program
// Programed by Kazuyuki Eguchi

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;

import org.json.JSONObject;
import org.webrtc.DataChannel;
import org.webrtc.IceCandidate;
import org.webrtc.MediaConstraints;
import org.webrtc.MediaStream;
import org.webrtc.PeerConnection;
import org.webrtc.PeerConnectionFactory;
import org.webrtc.SdpObserver;
import org.webrtc.SessionDescription;

import java.nio.ByteBuffer;
import java.util.LinkedList;
import java.util.List;


public class MainActivity extends Activity {

    final private String TAG = "sample";

    PeerConnectionFactory factory = null;

    PeerConnection pc1 = null;
    PeerConnection pc2 = null;

    DataChannel dc1 = null;
    DataChannel dc2 = null;

    // pc1用のObserver
    PeerConnection.Observer pcob1 = new PeerConnection.Observer() {
        @Override
        public void onSignalingChange(PeerConnection.SignalingState signalingState) {
            Log.d(TAG, "pcob1 onSignalingChange() " + signalingState.name());
        }

        @Override
        public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) {
            Log.d(TAG, "pcob1 onIceConnectionChange() " + iceConnectionState.name());
        }

        @Override
        public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) {
            Log.d(TAG, "pcob1 onIceGatheringChange() " + iceGatheringState.name());
        }

        @Override
        public void onIceCandidate(IceCandidate iceCandidate) {
            Log.d(TAG, "pcob1 onIceCandidate()");

            JSONObject json = new JSONObject();

            String mes = null;

            try {
                json.put("type", "candidate");
                json.put("sdpMLineIndex", iceCandidate.sdpMLineIndex);
                json.put("sdpMid", iceCandidate.sdpMid);
                json.put("candidate", iceCandidate.sdp);

                mes = json.toString();

                Log.d(TAG, mes);

                // ここで、 WebSocket等で相手側に mesを送る
                // 受信側では

                JSONObject json2 = new JSONObject(mes);
                IceCandidate candidate = new IceCandidate(json2.getString("sdpMid"), json2.getInt("sdpMLineIndex"), json2.getString("candidate"));
                pc2.addIceCandidate(candidate);

            } catch (org.json.JSONException ex) {
                Log.d(TAG, ex.toString());
            }
        }

        @Override
        public void onAddStream(MediaStream mediaStream) {
            Log.d(TAG, "pcob1 onAddStream()");
        }

        @Override
        public void onRemoveStream(MediaStream mediaStream) {
            Log.d(TAG, "pcob1 onRemoveStream");
        }

        @Override
        public void onDataChannel(DataChannel dataChannel) {
            Log.d(TAG, "pcob1 onDataChannel()");
        }

        @Override
        public void onRenegotiationNeeded() {
            Log.d(TAG, "pcob1 onRenegotiationNeeded()");
        }
    };

    // pc2用のObserver
    PeerConnection.Observer pcob2 = new PeerConnection.Observer() {
        @Override
        public void onSignalingChange(PeerConnection.SignalingState signalingState) {
            Log.d(TAG, "pcob2 onSignalingChange() " + signalingState.name());
        }

        @Override
        public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) {
            Log.d(TAG, "pcob2 onIceConnectionChange() " + iceConnectionState.name());
        }

        @Override
        public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) {
            Log.d(TAG, "pcob2 onIceGatheringChange() " + iceGatheringState.name());
        }

        @Override
        public void onIceCandidate(IceCandidate iceCandidate) {
            Log.d(TAG, "pcob2 onIceCandidate()");

            JSONObject json = new JSONObject();

            String mes = null;

            try {
                json.put("type", "candidate");
                json.put("sdpMLineIndex", iceCandidate.sdpMLineIndex);
                json.put("sdpMid", iceCandidate.sdpMid);
                json.put("candidate", iceCandidate.sdp);

                mes = json.toString();

                Log.d(TAG, mes);

                // ここで、 WebSocket等で相手側に mesを送る
                // 受信側では

                JSONObject json2 = new JSONObject(mes);
                IceCandidate candidate = new IceCandidate(json2.getString("sdpMid"), json2.getInt("sdpMLineIndex"), json2.getString("candidate"));
                pc1.addIceCandidate(candidate);

            } catch (org.json.JSONException ex) {
                Log.d(TAG, ex.toString());
            }
        }

        @Override
        public void onAddStream(MediaStream mediaStream) {
            Log.d(TAG, "pcob2 onAddStream()");
        }

        @Override
        public void onRemoveStream(MediaStream mediaStream) {
            Log.d(TAG, "pcob2 onRemoveStream");
        }

        @Override
        public void onDataChannel(DataChannel dataChannel) {
            Log.d(TAG, "pcob2 onDataChannel()");
            dc2 = dataChannel;
            dc2.registerObserver(dc2o);
        }

        @Override
        public void onRenegotiationNeeded() {
            Log.d(TAG, "pcob2 onRenegotiationNeeded()");
        }
    };

    SdpObserver so1 = new SdpObserver() {
        @Override
        public void onCreateSuccess(SessionDescription sessionDescription) {
            Log.d(TAG, "so1 onCreateSuccess()");

            pc1.setLocalDescription(so1, sessionDescription);

            JSONObject json = new JSONObject();

            String mes = null;

            try {
                json.put("type", sessionDescription.type.toString().toLowerCase());
                json.put("sdp", sessionDescription.description);

                mes = json.toString();

                Log.d(TAG, mes);

                // ここで、 WebSocket等で相手側に mesを送る
                // 受信側では

                JSONObject json2 = new JSONObject(mes);
                String type = json2.getString("type");
                String sdp = json2.getString("sdp");

                SessionDescription sdp2 = new SessionDescription(SessionDescription.Type.fromCanonicalForm(type), sdp);

                pc2.setRemoteDescription(so2, sdp2);
                MediaConstraints constraints = new MediaConstraints();
                pc2.createAnswer(so2, constraints);

            } catch (org.json.JSONException ex) {
                Log.d(TAG, ex.toString());
            }
        }

        @Override
        public void onSetSuccess() {
            Log.d(TAG, "so1 onCreateSuccess()");
        }

        @Override
        public void onCreateFailure(String s) {
            Log.d(TAG, "so1 onCreateFailure() " + s);
        }

        @Override
        public void onSetFailure(String s) {
            Log.d(TAG, "so1 onSetFailure() " + s);
        }
    };

    SdpObserver so2 = new SdpObserver() {
        @Override
        public void onCreateSuccess(SessionDescription sessionDescription) {
            Log.d(TAG, "so2 onCreateSuccess()");
            pc2.setLocalDescription(so2, sessionDescription);

            JSONObject json = new JSONObject();

            String mes = null;

            try {
                json.put("type", sessionDescription.type.toString().toLowerCase());
                json.put("sdp", sessionDescription.description);

                mes = json.toString();

                Log.d(TAG, mes);

                // ここで、 WebSocket等で相手側に mesを送る
                // 受信側では

                JSONObject json2 = new JSONObject(mes);
                String type = json2.getString("type");
                String sdp = json2.getString("sdp");

                SessionDescription sdp2 = new SessionDescription(SessionDescription.Type.fromCanonicalForm(type), sdp);

                pc1.setRemoteDescription(so1, sdp2);

            } catch (org.json.JSONException ex) {
                Log.d(TAG, ex.toString());
            }
        }

        @Override
        public void onSetSuccess() {
            Log.d(TAG, "so2 onCreateSuccess()");
        }

        @Override
        public void onCreateFailure(String s) {
            Log.d(TAG, "so2 onCreateFailure() " + s);
        }

        @Override
        public void onSetFailure(String s) {
            Log.d(TAG, "so2 onSetFailure() " + s);
        }
    };

    DataChannel.Observer dc1o = new DataChannel.Observer() {
        @Override
        public void onStateChange() {
            Log.d(TAG, "da1o onStateChange() " + dc1.state().name());

            if (dc1.state() == DataChannel.State.OPEN) {
                String data = "from dc1";
                ByteBuffer buffer = ByteBuffer.wrap(data.getBytes());
                dc1.send(new DataChannel.Buffer(buffer, false));
            }
        }

        @Override
        public void onMessage(DataChannel.Buffer buffer) {
            Log.d(TAG, "da1o onMessage()");

            if (buffer.binary == false) {
                int limit = buffer.data.limit();
                byte[] datas = new byte[limit];
                buffer.data.get(datas);
                String tmp = new String(datas);
                Log.d(TAG, tmp);
            }
        }
    };

    DataChannel.Observer dc2o = new DataChannel.Observer() {
        @Override
        public void onStateChange() {
            Log.d(TAG, "da2o onStateChange() " + dc2.state().name());

            if (dc2.state() == DataChannel.State.OPEN) {
                String data = "from dc2";
                ByteBuffer buffer = ByteBuffer.wrap(data.getBytes());
                dc2.send(new DataChannel.Buffer(buffer, false));
            }

        }

        @Override
        public void onMessage(DataChannel.Buffer buffer) {
            Log.d(TAG, "da2o onMessage()");

            if (buffer.binary == false) {
                int limit = buffer.data.limit();
                byte[] datas = new byte[limit];
                buffer.data.get(datas);
                String tmp = new String(datas);
                Log.d(TAG, tmp);
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Log.d(TAG, "onCreate()");

        PeerConnectionFactory.initializeAndroidGlobals(getApplicationContext(), true, false, false, null);
        factory = new PeerConnectionFactory();
    }

    @Override
    protected void onResume() {
        super.onResume();

        Log.d(TAG, "onResume()");

        // STRNサーバは Google様のを使わせてもらう。
        List<PeerConnection.IceServer> iceServers = new LinkedList<PeerConnection.IceServer>();
        iceServers.add(new PeerConnection.IceServer("stun:stun.l.google.com:19302"));

        MediaConstraints constraints = new MediaConstraints();

        pc1 = factory.createPeerConnection(iceServers, constraints, pcob1);
        pc2 = factory.createPeerConnection(iceServers, constraints, pcob2);

        // offerする前にデータチャネルを作成する
        dc1 = pc1.createDataChannel("RTCDataChannel", new DataChannel.Init());
        dc1.registerObserver(dc1o);

        // Offerする
        pc1.createOffer(so1, constraints);
    }

    @Override
    protected void onPause() {
        super.onPause();

        Log.d(TAG, "onPause()");

        if (dc1 != null) {
            dc1.close();
            dc1.unregisterObserver();
        }

        if (dc2 != null) {
            dc2.close();
            dc2.unregisterObserver();
        }

        if (pc1 != null) {
            pc1.close();
        }

        if (pc2 != null) {
            pc2.close();
        }
    }
}

実行した際のLogcatのログ

06-10 17:44:58.719    4149-4149/jp.eguchi.android.datachannelsample D/sample﹕ onCreate()
06-10 17:44:58.723   4149-11577/jp.eguchi.android.datachannelsample D/JVM﹕ AttachCurrentThreadIfNeeded::ctor@[tid=11577]
06-10 17:44:58.723   4149-11577/jp.eguchi.android.datachannelsample D/JVM﹕ Attaching thread to JVM
06-10 17:44:58.724   4149-11577/jp.eguchi.android.datachannelsample D/JVM﹕ JVM::environment@[tid=11577]
06-10 17:44:58.724   4149-11577/jp.eguchi.android.datachannelsample D/JVM﹕ JNIEnvironment::ctor@[tid=11577]
06-10 17:44:58.724   4149-11577/jp.eguchi.android.datachannelsample D/AudioManager﹕ ctor@[tid=11577]
06-10 17:44:58.724   4149-11577/jp.eguchi.android.datachannelsample D/JVM﹕ JNIEnvironment::RegisterNatives(org/webrtc/voiceengine/WebRtcAudioManager)
06-10 17:44:58.724   4149-11577/jp.eguchi.android.datachannelsample D/JVM﹕ NativeRegistration::ctor@[tid=11577]
06-10 17:44:58.724   4149-11577/jp.eguchi.android.datachannelsample D/JVM﹕ NativeRegistration::NewObject@[tid=11577]
06-10 17:44:58.724   4149-11577/jp.eguchi.android.datachannelsample D/WebRtcAudioManager﹕ ctor@[name=Thread-1038, id=1038]
06-10 17:44:58.724   4149-11577/jp.eguchi.android.datachannelsample D/WebRtcAudioManager﹕ Nexus 5 is blacklisted for HW AEC usage!
06-10 17:44:58.726   4149-11577/jp.eguchi.android.datachannelsample D/AudioManager﹕ OnCacheAudioParameters@[tid=11577]
06-10 17:44:58.726   4149-11577/jp.eguchi.android.datachannelsample D/AudioManager﹕ hardware_aec: 0
06-10 17:44:58.726   4149-11577/jp.eguchi.android.datachannelsample D/AudioManager﹕ low_latency_output: 1
06-10 17:44:58.726   4149-11577/jp.eguchi.android.datachannelsample D/AudioManager﹕ sample_rate: 48000
06-10 17:44:58.726   4149-11577/jp.eguchi.android.datachannelsample D/AudioManager﹕ channels: 1
06-10 17:44:58.726   4149-11577/jp.eguchi.android.datachannelsample D/AudioManager﹕ output_buffer_size: 240
06-10 17:44:58.726   4149-11577/jp.eguchi.android.datachannelsample D/AudioManager﹕ input_buffer_size: 1920
06-10 17:44:58.726   4149-11577/jp.eguchi.android.datachannelsample D/JVM﹕ GlobalRef::ctor@[tid=11577]
06-10 17:44:58.726   4149-11577/jp.eguchi.android.datachannelsample D/AudioManager﹕ JavaAudioManager::ctor@[tid=11577]
06-10 17:44:58.726   4149-11577/jp.eguchi.android.datachannelsample D/AudioManager﹕ IsLowLatencyPlayoutSupported()
06-10 17:44:58.726   4149-11577/jp.eguchi.android.datachannelsample W/AudioManager﹕ NOTE: OpenSL ES output is currently disabled!
06-10 17:44:58.726   4149-11577/jp.eguchi.android.datachannelsample D/JVM﹕ AttachCurrentThreadIfNeeded::ctor@[tid=11577]
06-10 17:44:58.726   4149-11577/jp.eguchi.android.datachannelsample D/JVM﹕ JVM::environment@[tid=11577]
06-10 17:44:58.726   4149-11577/jp.eguchi.android.datachannelsample D/JVM﹕ JNIEnvironment::ctor@[tid=11577]
06-10 17:44:58.726   4149-11577/jp.eguchi.android.datachannelsample D/AudioTrackJni﹕ ctor@[tid=11577]
06-10 17:44:58.726   4149-11577/jp.eguchi.android.datachannelsample D/JVM﹕ JNIEnvironment::RegisterNatives(org/webrtc/voiceengine/WebRtcAudioTrack)
06-10 17:44:58.726   4149-11577/jp.eguchi.android.datachannelsample D/JVM﹕ NativeRegistration::ctor@[tid=11577]
06-10 17:44:58.726   4149-11577/jp.eguchi.android.datachannelsample D/JVM﹕ NativeRegistration::NewObject@[tid=11577]
06-10 17:44:58.726   4149-11577/jp.eguchi.android.datachannelsample D/WebRtcAudioTrack﹕ ctor@[name=Thread-1038, id=1038]
06-10 17:44:58.726   4149-11577/jp.eguchi.android.datachannelsample D/WebRtcAudioTrack﹕ Android SDK: 22, Release: M, Brand: google, Device: hammerhead, Id: MPZ44Q, Hardware: hammerhead, Manufacturer: LGE, Model: Nexus 5, Product: hammerhead
06-10 17:44:58.726   4149-11577/jp.eguchi.android.datachannelsample D/JVM﹕ GlobalRef::ctor@[tid=11577]
06-10 17:44:58.726   4149-11577/jp.eguchi.android.datachannelsample D/JVM﹕ AttachCurrentThreadIfNeeded::ctor@[tid=11577]
06-10 17:44:58.726   4149-11577/jp.eguchi.android.datachannelsample D/JVM﹕ JVM::environment@[tid=11577]
06-10 17:44:58.726   4149-11577/jp.eguchi.android.datachannelsample D/JVM﹕ JNIEnvironment::ctor@[tid=11577]
06-10 17:44:58.726   4149-11577/jp.eguchi.android.datachannelsample D/AudioRecordJni﹕ ctor@[tid=11577]
06-10 17:44:58.726   4149-11577/jp.eguchi.android.datachannelsample D/JVM﹕ JNIEnvironment::RegisterNatives(org/webrtc/voiceengine/WebRtcAudioRecord)
06-10 17:44:58.726   4149-11577/jp.eguchi.android.datachannelsample D/JVM﹕ NativeRegistration::ctor@[tid=11577]
06-10 17:44:58.726   4149-11577/jp.eguchi.android.datachannelsample D/JVM﹕ NativeRegistration::NewObject@[tid=11577]
06-10 17:44:58.726   4149-11577/jp.eguchi.android.datachannelsample D/WebRtcAudioRecord﹕ ctor@[name=Thread-1038, id=1038]
06-10 17:44:58.726   4149-11577/jp.eguchi.android.datachannelsample D/JVM﹕ GlobalRef::ctor@[tid=11577]
06-10 17:44:58.727   4149-11577/jp.eguchi.android.datachannelsample D/AudioManager﹕ SetActiveAudioLayer(5)@[tid=11577]
06-10 17:44:58.727   4149-11577/jp.eguchi.android.datachannelsample D/AudioManager﹕ delay_estimate_in_milliseconds: 150
06-10 17:44:58.727   4149-11577/jp.eguchi.android.datachannelsample D/AudioTrackJni﹕ AttachAudioBuffer@[tid=11577]
06-10 17:44:58.727   4149-11577/jp.eguchi.android.datachannelsample D/AudioTrackJni﹕ SetPlayoutSampleRate(48000)
06-10 17:44:58.727   4149-11577/jp.eguchi.android.datachannelsample D/AudioTrackJni﹕ SetPlayoutChannels(1)
06-10 17:44:58.727   4149-11577/jp.eguchi.android.datachannelsample D/AudioRecordJni﹕ AttachAudioBuffer
06-10 17:44:58.727   4149-11577/jp.eguchi.android.datachannelsample D/AudioRecordJni﹕ SetRecordingSampleRate(48000)
06-10 17:44:58.727   4149-11577/jp.eguchi.android.datachannelsample D/AudioRecordJni﹕ SetRecordingChannels(1)
06-10 17:44:58.727   4149-11577/jp.eguchi.android.datachannelsample D/AudioRecordJni﹕ total_delay_in_milliseconds: 150
06-10 17:44:58.727   4149-11577/jp.eguchi.android.datachannelsample D/AudioManager﹕ Init@[tid=11577]
06-10 17:44:58.727   4149-11577/jp.eguchi.android.datachannelsample D/WebRtcAudioManager﹕ init@[name=Thread-1038, id=1038]
06-10 17:44:58.731    4149-4149/jp.eguchi.android.datachannelsample D/sample﹕ onResume()
06-10 17:44:59.157   4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ pcob1 onRenegotiationNeeded()
06-10 17:44:59.158   4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ so1 onCreateSuccess()
06-10 17:44:59.395   4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ pcob1 onSignalingChange() HAVE_LOCAL_OFFER
06-10 17:44:59.396   4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ {"type":"offer","sdp":"v=0\r\no=- 113918549713234061 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=msid-semantic: WMS\r\nm=application 9 DTLS\/SCTP 5000\r\nc=IN IP4 0.0.0.0\r\na=ice-ufrag:lw3\/LpT+Giz61KGg\r\na=ice-pwd:QxRiyQgLEFnfWEsJ0p5+6G3s\r\na=fingerprint:sha-256 B8:AB:2D:09:BB:01:23:04:44:64:81:1E:F6:65:24:25:74:A5:6C:F0:62:8B:96:3E:86:83:E2:ED:A9:33:E6:FA\r\na=setup:actpass\r\na=mid:data\r\na=sctpmap:5000 webrtc-datachannel 1024\r\n"}
06-10 17:44:59.398   4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ pcob2 onSignalingChange() HAVE_REMOTE_OFFER
06-10 17:44:59.398   4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ pcob1 onIceGatheringChange() GATHERING
06-10 17:44:59.398   4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ so1 onCreateSuccess()
06-10 17:44:59.398   4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ so2 onCreateSuccess()
06-10 17:44:59.399   4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ so2 onCreateSuccess()
06-10 17:44:59.404   4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ pcob2 onSignalingChange() STABLE
06-10 17:44:59.406   4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ {"type":"answer","sdp":"v=0\r\no=- 3088308566766388678 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=msid-semantic: WMS\r\nm=application 9 DTLS\/SCTP 5000\r\nc=IN IP4 0.0.0.0\r\nb=AS:30\r\na=ice-ufrag:4gco7NuR3wheyF6s\r\na=ice-pwd:Fv95\/TF5FvTbGMCcISNSKNf3\r\na=fingerprint:sha-256 31:EE:E9:19:85:5B:02:5F:CC:80:A3:FC:F7:64:EC:71:B6:01:4E:D0:35:1B:74:8E:D1:30:05:43:49:0D:52:55\r\na=setup:active\r\na=mid:data\r\na=sctpmap:5000 webrtc-datachannel 1024\r\n"}
06-10 17:44:59.410   4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ pcob1 onSignalingChange() STABLE
06-10 17:44:59.412   4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ pcob1 onIceConnectionChange() CHECKING
06-10 17:44:59.412   4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ pcob1 onIceCandidate()
06-10 17:44:59.412   4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ {"type":"candidate","sdpMLineIndex":0,"sdpMid":"data","candidate":"candidate:3209757399 1 udp 2122260223 10.49.120.146 39965 typ host generation 0"}
06-10 17:44:59.415   4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ pcob2 onIceConnectionChange() CHECKING
06-10 17:44:59.415   4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ pcob2 onIceGatheringChange() GATHERING
06-10 17:44:59.415   4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ so2 onCreateSuccess()
06-10 17:44:59.415   4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ so1 onCreateSuccess()
06-10 17:44:59.416   4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ pcob2 onIceCandidate()
06-10 17:44:59.416   4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ {"type":"candidate","sdpMLineIndex":0,"sdpMid":"data","candidate":"candidate:3209757399 1 udp 2122260223 10.49.120.146 41101 typ host generation 0"}
06-10 17:44:59.444   4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ pcob1 onIceGatheringChange() COMPLETE
06-10 17:44:59.453   4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ pcob2 onIceGatheringChange() COMPLETE
06-10 17:44:59.486   4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ pcob1 onIceConnectionChange() CONNECTED
06-10 17:44:59.487   4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ pcob1 onIceConnectionChange() COMPLETED
06-10 17:44:59.488   4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ da1o onStateChange() OPEN
06-10 17:44:59.489   4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ pcob2 onIceConnectionChange() CONNECTED
06-10 17:44:59.493   4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ pcob2 onDataChannel()
06-10 17:44:59.494   4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ da2o onStateChange() OPEN
06-10 17:44:59.495   4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ da2o onMessage()
06-10 17:44:59.495   4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ from dc1
06-10 17:44:59.496   4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ da1o onMessage()
06-10 17:44:59.496   4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ from dc2
06-10 17:45:03.231    4149-4149/jp.eguchi.android.datachannelsample D/sample﹕ onPause()
06-10 17:45:03.231   4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ da1o onStateChange() CLOSING
06-10 17:45:03.233   4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ da1o onStateChange() CLOSED
06-10 17:45:03.233   4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ da2o onStateChange() CLOSING
06-10 17:45:03.234   4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ da2o onStateChange() CLOSED
06-10 17:45:03.236   4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ pcob1 onIceConnectionChange() CLOSED
06-10 17:45:03.236   4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ pcob1 onSignalingChange() CLOSED
06-10 17:45:03.239   4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ pcob2 onIceConnectionChange() CLOSED
06-10 17:45:03.240   4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ pcob2 onSignalingChange() CLOSED

以上、ご参考までに

これで、WebRTCのアプリが増えるといいなあ〜