webrtc/examples/unityplugin
Qiang Chen 51e2046dbc Bug Fix: WebRTC Unity Plugin Audio One Way
When audio_only is on for the webrtc unity plugin, there is a bug that
the audio from hologram cannot be heard at the remote side.

Actually we found the audio is transmitted to the remote side, but the
remote side wants video data also to playout everything. So without
video data, the remote side will drop all the audio data.

Thus, on the hologram (using webrtc unity plugin) side, we should not
hook up a dummy camera, but instead we should use media constraint to
request the remote side to send video data.

This CL fixes the bug.

Bug: webrtc:8555
Change-Id: I21ddda65185b645088aa4ac15f47b3f8ffad1873
Reviewed-on: https://webrtc-review.googlesource.com/24680
Commit-Queue: Qiang Chen <qiangchen@chromium.org>
Reviewed-by: George Zhou <gyzhou@chromium.org>
Cr-Commit-Position: refs/heads/master@{#21094}
2017-12-05 20:36:12 +00:00
..
java/src/org/webrtc Moving src/webrtc into src/. 2017-09-15 04:25:06 +00:00
ANDROID_INSTRUCTION Moving src/webrtc into src/. 2017-09-15 04:25:06 +00:00
classreferenceholder.cc Fixing WebRTC after moving from src/webrtc to src/ 2017-09-15 05:02:56 +00:00
classreferenceholder.h Fixing WebRTC after moving from src/webrtc to src/ 2017-09-15 05:02:56 +00:00
DEPS Fixing WebRTC after moving from src/webrtc to src/ 2017-09-15 05:02:56 +00:00
jni_onload.cc Fixing WebRTC after moving from src/webrtc to src/ 2017-09-15 05:02:56 +00:00
OWNERS Moving src/webrtc into src/. 2017-09-15 04:25:06 +00:00
README Moving src/webrtc into src/. 2017-09-15 04:25:06 +00:00
simple_peer_connection.cc Bug Fix: WebRTC Unity Plugin Audio One Way 2017-12-05 20:36:12 +00:00
simple_peer_connection.h Bug Fix: WebRTC Unity Plugin Audio One Way 2017-12-05 20:36:12 +00:00
unity_plugin_apis.cc Fixing WebRTC after moving from src/webrtc to src/ 2017-09-15 05:02:56 +00:00
unity_plugin_apis.h Fixing WebRTC after moving from src/webrtc to src/ 2017-09-15 05:02:56 +00:00
video_observer.cc Fixing WebRTC after moving from src/webrtc to src/ 2017-09-15 05:02:56 +00:00
video_observer.h Fixing WebRTC after moving from src/webrtc to src/ 2017-09-15 05:02:56 +00:00

This directory contains an example Unity native plugin for Windows OS and Android.

The APIs use Platform Invoke (P/Invoke) technology as required by Unity native plugin.
This plugin dll can also be used by Windows C# applications other than Unity.

For detailed build instruction on Android, see ANDROID_INSTRUCTION

An example of wrapping native plugin into a C# managed class in Unity is given as following:

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;

namespace SimplePeerConnectionM {
  // A class for ice candidate.
  public class IceCandidate {
    public IceCandidate(string candidate, int sdpMlineIndex, string sdpMid) {
      mCandidate = candidate;
      mSdpMlineIndex = sdpMlineIndex;
      mSdpMid = sdpMid;
    }
    string mCandidate;
    int mSdpMlineIndex;
    string mSdpMid;

    public string Candidate {
      get { return mCandidate; }
      set { mCandidate = value; }
    }

    public int SdpMlineIndex {
      get { return mSdpMlineIndex; }
      set { mSdpMlineIndex = value; }
    }

    public string SdpMid {
      get { return mSdpMid; }
      set { mSdpMid = value; }
    }
  }

  // A managed wrapper up class for the native c style peer connection APIs.
  public class PeerConnectionM {
    private const string dllPath = "webrtc_unity_plugin";

    //create a peerconnection with turn servers
    [DllImport(dllPath, CallingConvention = CallingConvention.Cdecl)]
    private static extern int CreatePeerConnection(string[] turnUrls, int noOfUrls,
        string username, string credential);

    [DllImport(dllPath, CallingConvention = CallingConvention.Cdecl)]
    private static extern bool ClosePeerConnection(int peerConnectionId);

    [DllImport(dllPath, CallingConvention = CallingConvention.Cdecl)]
    private static extern bool AddStream(int peerConnectionId, bool audioOnly);

    [DllImport(dllPath, CallingConvention = CallingConvention.Cdecl)]
    private static extern bool AddDataChannel(int peerConnectionId);

    [DllImport(dllPath, CallingConvention = CallingConvention.Cdecl)]
    private static extern bool CreateOffer(int peerConnectionId);

    [DllImport(dllPath, CallingConvention = CallingConvention.Cdecl)]
    private static extern bool CreateAnswer(int peerConnectionId);

    [DllImport(dllPath, CallingConvention = CallingConvention.Cdecl)]
    private static extern bool SendDataViaDataChannel(int peerConnectionId, string data);

    [DllImport(dllPath, CallingConvention = CallingConvention.Cdecl)]
    private static extern bool SetAudioControl(int peerConnectionId, bool isMute, bool isRecord);

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate void LocalDataChannelReadyInternalDelegate();
    public delegate void LocalDataChannelReadyDelegate(int id);
    [DllImport(dllPath, CallingConvention = CallingConvention.Cdecl)]
    private static extern bool RegisterOnLocalDataChannelReady(
        int peerConnectionId, LocalDataChannelReadyInternalDelegate callback);

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate void DataFromDataChannelReadyInternalDelegate(string s);
    public delegate void DataFromDataChannelReadyDelegate(int id, string s);
    [DllImport(dllPath, CallingConvention = CallingConvention.Cdecl)]
    private static extern bool RegisterOnDataFromDataChannelReady(
        int peerConnectionId, DataFromDataChannelReadyInternalDelegate callback);

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate void FailureMessageInternalDelegate(string msg);
    public delegate void FailureMessageDelegate(int id, string msg);
    [DllImport(dllPath, CallingConvention = CallingConvention.Cdecl)]
    private static extern bool RegisterOnFailure(int peerConnectionId,
        FailureMessageInternalDelegate callback);

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate void AudioBusReadyInternalDelegate(IntPtr data, int bitsPerSample,
        int sampleRate, int numberOfChannels, int numberOfFrames);
    public delegate void AudioBusReadyDelegate(int id, IntPtr data, int bitsPerSample,
        int sampleRate, int numberOfChannels, int numberOfFrames);
    [DllImport(dllPath, CallingConvention = CallingConvention.Cdecl)]
    private static extern bool RegisterOnAudioBusReady(int peerConnectionId,
        AudioBusReadyInternalDelegate callback);

    // Video callbacks.
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate void I420FrameReadyInternalDelegate(
        IntPtr dataY, IntPtr dataU, IntPtr dataV,
        int strideY, int strideU, int strideV,
        uint width, uint height);
    public delegate void I420FrameReadyDelegate(int id,
        IntPtr dataY, IntPtr dataU, IntPtr dataV,
        int strideY, int strideU, int strideV,
        uint width, uint height);
    [DllImport(dllPath, CallingConvention = CallingConvention.Cdecl)]
    private static extern bool RegisterOnLocalI420FrameReady(int peerConnectionId,
        I420FrameReadyInternalDelegate callback);
    [DllImport(dllPath, CallingConvention = CallingConvention.Cdecl)]
    private static extern bool RegisterOnRemoteI420FrameReady(int peerConnectionId,
        I420FrameReadyInternalDelegate callback);

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate void LocalSdpReadytoSendInternalDelegate(string type, string sdp);
    public delegate void LocalSdpReadytoSendDelegate(int id, string type, string sdp);
    [DllImport(dllPath, CallingConvention = CallingConvention.Cdecl)]
    private static extern bool RegisterOnLocalSdpReadytoSend(int peerConnectionId,
        LocalSdpReadytoSendInternalDelegate callback);

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate void IceCandiateReadytoSendInternalDelegate(
        string candidate, int sdpMlineIndex, string sdpMid);
    public delegate void IceCandiateReadytoSendDelegate(
        int id, string candidate, int sdpMlineIndex, string sdpMid);
    [DllImport(dllPath, CallingConvention = CallingConvention.Cdecl)]
    private static extern bool RegisterOnIceCandiateReadytoSend(
        int peerConnectionId, IceCandiateReadytoSendInternalDelegate callback);

    [DllImport(dllPath, CallingConvention = CallingConvention.Cdecl)]
    private static extern bool SetRemoteDescription(int peerConnectionId, string type, string sdp);

    [DllImport(dllPath, CallingConvention = CallingConvention.Cdecl)]
    private static extern bool AddIceCandidate(int peerConnectionId, string sdp,
      int sdpMlineindex, string sdpMid);

    public PeerConnectionM(List<string> turnUrls, string username, string credential) {
      string[] urls = turnUrls != null ? turnUrls.ToArray() : null;
      int length = turnUrls != null ? turnUrls.Count : 0;
      mPeerConnectionId = CreatePeerConnection(urls, length, username, credential);
      RegisterCallbacks();
    }

    public void ClosePeerConnection() {
      ClosePeerConnection(mPeerConnectionId);
      mPeerConnectionId = -1;
    }

    // Return -1 if Peerconnection is not available.
    public int GetUniqueId() {
      return mPeerConnectionId;
    }

    public void AddStream(bool audioOnly) {
      AddStream(mPeerConnectionId, audioOnly);
    }

    public void AddDataChannel() {
      AddDataChannel(mPeerConnectionId);
    }

    public void CreateOffer() {
      CreateOffer(mPeerConnectionId);
    }

    public void CreateAnswer() {
      CreateAnswer(mPeerConnectionId);
    }

    public void SendDataViaDataChannel(string data) {
      SendDataViaDataChannel(mPeerConnectionId, data);
    }

    public void SetAudioControl(bool isMute, bool isRecord) {
      SetAudioControl(mPeerConnectionId, isMute, isRecord);
    }

    public void SetRemoteDescription(string type, string sdp) {
      SetRemoteDescription(mPeerConnectionId, type, sdp);
    }

    public void AddIceCandidate(string candidate, int sdpMlineindex, string sdpMid) {
      AddIceCandidate(mPeerConnectionId, candidate, sdpMlineindex, sdpMid);
    }

    private void RegisterCallbacks() {
      localDataChannelReadyDelegate = new LocalDataChannelReadyInternalDelegate(
          RaiseLocalDataChannelReady);
      RegisterOnLocalDataChannelReady(mPeerConnectionId, localDataChannelReadyDelegate);

      dataFromDataChannelReadyDelegate = new DataFromDataChannelReadyInternalDelegate(
          RaiseDataFromDataChannelReady);
      RegisterOnDataFromDataChannelReady(mPeerConnectionId, dataFromDataChannelReadyDelegate);

      failureMessageDelegate = new FailureMessageInternalDelegate(RaiseFailureMessage);
      RegisterOnFailure(mPeerConnectionId, failureMessageDelegate);

      audioBusReadyDelegate = new AudioBusReadyInternalDelegate(RaiseAudioBusReady);
      RegisterOnAudioBusReady(mPeerConnectionId, audioBusReadyDelegate);

      localI420FrameReadyDelegate = new I420FrameReadyInternalDelegate(
        RaiseLocalVideoFrameReady);
      RegisterOnLocalI420FrameReady(mPeerConnectionId, localI420FrameReadyDelegate);

      remoteI420FrameReadyDelegate = new I420FrameReadyInternalDelegate(
        RaiseRemoteVideoFrameReady);
      RegisterOnRemoteI420FrameReady(mPeerConnectionId, remoteI420FrameReadyDelegate);

      localSdpReadytoSendDelegate = new LocalSdpReadytoSendInternalDelegate(
        RaiseLocalSdpReadytoSend);
      RegisterOnLocalSdpReadytoSend(mPeerConnectionId, localSdpReadytoSendDelegate);

      iceCandiateReadytoSendDelegate =
          new IceCandiateReadytoSendInternalDelegate(RaiseIceCandiateReadytoSend);
      RegisterOnIceCandiateReadytoSend(
          mPeerConnectionId, iceCandiateReadytoSendDelegate);
    }

    private void RaiseLocalDataChannelReady() {
      if (OnLocalDataChannelReady != null)
        OnLocalDataChannelReady(mPeerConnectionId);
    }

    private void RaiseDataFromDataChannelReady(string data) {
      if (OnDataFromDataChannelReady != null)
        OnDataFromDataChannelReady(mPeerConnectionId, data);
    }

    private void RaiseFailureMessage(string msg) {
      if (OnFailureMessage != null)
        OnFailureMessage(mPeerConnectionId, msg);
    }

    private void RaiseAudioBusReady(IntPtr data, int bitsPerSample,
      int sampleRate, int numberOfChannels, int numberOfFrames) {
      if (OnAudioBusReady != null)
        OnAudioBusReady(mPeerConnectionId, data, bitsPerSample, sampleRate,
            numberOfChannels, numberOfFrames);
    }

    private void RaiseLocalVideoFrameReady(
        IntPtr dataY, IntPtr dataU, IntPtr dataV,
        int strideY, int strideU, int strideV,
        uint width, uint height) {
      if (OnLocalVideoFrameReady != null)
        OnLocalVideoFrameReady(mPeerConnectionId, dataY, dataU, dataV, strideY, strideU, strideV,
          width, height);
    }

    private void RaiseRemoteVideoFrameReady(
       IntPtr dataY, IntPtr dataU, IntPtr dataV,
       int strideY, int strideU, int strideV,
       uint width, uint height) {
      if (OnRemoteVideoFrameReady != null)
        OnRemoteVideoFrameReady(mPeerConnectionId, dataY, dataU, dataV, strideY, strideU, strideV,
          width, height);
    }


    private void RaiseLocalSdpReadytoSend(string type, string sdp) {
      if (OnLocalSdpReadytoSend != null)
        OnLocalSdpReadytoSend(mPeerConnectionId, type, sdp);
    }

    private void RaiseIceCandiateReadytoSend(string candidate, int sdpMlineIndex, string sdpMid) {
      if (OnIceCandiateReadytoSend != null)
        OnIceCandiateReadytoSend(mPeerConnectionId, candidate, sdpMlineIndex, sdpMid);
    }

    public void AddQueuedIceCandidate(List<IceCandidate> iceCandidateQueue) {
      if (iceCandidateQueue != null) {
        foreach (IceCandidate ic in iceCandidateQueue) {
          AddIceCandidate(mPeerConnectionId, ic.Candidate, ic.SdpMlineIndex, ic.SdpMid);
        }
      }
    }

    private LocalDataChannelReadyInternalDelegate localDataChannelReadyDelegate = null;
    public event LocalDataChannelReadyDelegate OnLocalDataChannelReady;

    private DataFromDataChannelReadyInternalDelegate dataFromDataChannelReadyDelegate = null;
    public event DataFromDataChannelReadyDelegate OnDataFromDataChannelReady;

    private FailureMessageInternalDelegate failureMessageDelegate = null;
    public event FailureMessageDelegate OnFailureMessage;

    private AudioBusReadyInternalDelegate audioBusReadyDelegate = null;
    public event AudioBusReadyDelegate OnAudioBusReady;

    private I420FrameReadyInternalDelegate localI420FrameReadyDelegate = null;
    public event I420FrameReadyDelegate OnLocalVideoFrameReady;

    private I420FrameReadyInternalDelegate remoteI420FrameReadyDelegate = null;
    public event I420FrameReadyDelegate OnRemoteVideoFrameReady;

    private LocalSdpReadytoSendInternalDelegate localSdpReadytoSendDelegate = null;
    public event LocalSdpReadytoSendDelegate OnLocalSdpReadytoSend;

    private IceCandiateReadytoSendInternalDelegate iceCandiateReadytoSendDelegate = null;
    public event IceCandiateReadytoSendDelegate OnIceCandiateReadytoSend;

    private int mPeerConnectionId = -1;
  }
}