We provide a C# wrapper for LibLinphone that can be used in a Xamarin project.

Generating the C# wrapper

The C# wrapper is automatically generated from the documentation in the code (like the Python/C++ wrappers).

The output of is one file named LinphoneWrapper.cs that contains everything. Depending on your project structure, you must copy it to the right place:

  • If you use a Native Portable project (using Portable Class Library, PCL), LinphoneWrapper.cs can't go in it because DllImport ins't supported. This means you have to copy LinphoneWrapper.cs in each one of your target Projects (for example LinphoneXamarin.Droid, LinphoneXamarin.iOS, etc...) ;
  • If you use a Native Shared project, LinphoneWrapper.cs can go in the shared project as long as each target project references the shared one.

Xamarin Android

Setup

For your Xamarin.Android project to be able to use Linphone, you also need our Android SDK. You can download the nightly snapshot from here or the latest stable release from there.

In your Android project, create a folder for the Linphone libraries and copy into it the content of the libs directory from our SDK. Import the folders and their content inside your Visual Studio project.

Do not rename the folders that contains the libraries and keep their names as they are in Visual Studio so it uses the "Path Sniffing" feature to use the correct libraries depending on the device architecture.

For each library file (with .so extension), set the Build Action to AndroidNativeLibrary.

For more information about this, you can check out the official documentation.

The wrapper needs to know the name of the Linphone library, but in our Android SDK the name depends on the architecture, so you must rename each .so file to remove the ABI suffix.

You also need to copy the liblinphone.jar file from our SDK in your project (wherever you want, it doesn't matter) and set it's Build Action to AndroidJavaLibrary.

Finally, in the Android project, in the Build section, under General, add "ANDROID" in the Conditional compilation symbols to have access to LinphoneAndroid class in the wrapper.

Getting started

As a Java Android application that uses native libraries, you must load the library manually inside your application when it starts:

Java.Lang.JavaSystem.LoadLibrary("bctoolbox");
Java.Lang.JavaSystem.LoadLibrary("ortp");
Java.Lang.JavaSystem.LoadLibrary("mediastreamer_base");
Java.Lang.JavaSystem.LoadLibrary("mediastreamer_voip");
Java.Lang.JavaSystem.LoadLibrary("linphone");

For Android, there is one method you have to call before any other one (and after the LoadLibrary calls):

LinphoneAndroid.setAndroidContext(Android.Runtime.JNIEnv.Handle, this.Handle);

Optionnaly, you may want to add the Android native log handler so the LibLinphone logs are sent in logcat:

LinphoneAndroid.setNativeLogHandler();

If you don't find the LinphoneAndroid class, ensure you have added the ANDROID conditional compilation symbol in the Android project.

Sample code

Once these steps are done, you can use every class in the wrapper and build your application using LibLinphone.

Here's a simple example that creates and starts a LinphoneCore:

private Core core;
private Call call;
private CoreListener callListener;

private void LinphoneCoreIterate()
{
   while (true)
    {
        RunOnUiThread(() => core.Iterate());
        System.Threading.Thread.Sleep(50);
    }
}

private void OnGlobal(Core lc, GlobalState gstate, string message)
{
    Console.WriteLine("Global state changed: " + gstate);
}

private void OnRegistration(Core lc, ProxyConfig config, RegistrationState state, string message)
{
    Console.WriteLine("Registration state changed: " + state);
}

private void OnCall(Core lc, Call call, CallState state, string message)
{
    Console.WriteLine("Call state changed: " + state);
    CallStats audio_stats = call.GetStats(StreamType.Audio);
    Console.WriteLine("Audio stats: " + audio_stats.DownloadBandwidth + " kbits/s / " + audio_stats.UploadBandwidth + " kbits/s");

   if (state == CallState.End)
    {
        core.RemoveListener(callListener);
    }
}

private void OnStats(Core lc, Call call, CallStats stats)
{
    Console.WriteLine("Call stats: " + stats.DownloadBandwidth + " kbits/s / " + stats.UploadBandwidth + " kbits/s");
}

protected override void OnCreate(Bundle bundle)
{
   base.OnCreate(bundle);

   // Copy the default_rc from the Assets to the device
   // The same can be done for the factory_rc
   AssetManager assets = this.Assets;
   string path = this.FilesDir.AbsolutePath;
   using (var br = new BinaryReader(Application.Context.Assets.Open("linphonerc_default")))
    {
       using (var bw = new BinaryWriter(new FileStream(path + "/default_rc", FileMode.Create)))
        {
           byte[] buffer = new byte[2048];
           int length = 0;
           while ((length = br.Read(buffer, 0, buffer.Length)) > 0)
            {
                bw.Write(buffer, 0, length);
            }
        }
    }

   // Load the libraries
   Java.Lang.JavaSystem.LoadLibrary("bctoolbox");
    Java.Lang.JavaSystem.LoadLibrary("ortp");
    Java.Lang.JavaSystem.LoadLibrary("mediastreamer_base");
    Java.Lang.JavaSystem.LoadLibrary("mediastreamer_voip");
    Java.Lang.JavaSystem.LoadLibrary("linphone");

    SetContentView(Resource.Layout.Main);

    Button button = FindViewById<Button>(Resource.Id.myButton);

    button.Click += delegate
    {
       if (callListener == null)
        {
            callListener = Factory.Instance.CreateCoreListener();
            callListener.OnCallStatsUpdated = OnStats;
        }

       if (call != null)
        {
            core.TerminateCall(call);
            call = null;
        }
       else
        {
            core.AddListener(callListener);
            call = core.InviteAddress(Factory.Instance.CreateAddress("sip:cotcot@sip.linphone.org"));
        }
    };

   // This is mandatory for Android
   LinphoneAndroid.setAndroidContext(JNIEnv.Handle, this.Handle);
    LinphoneAndroid.setNativeLogHandler();

    CoreListener listener = Factory.Instance.CreateCoreListener();
    listener.OnGlobalStateChanged = OnGlobal;
    listener.OnRegistrationStateChanged = OnRegistration;
    listener.OnCallStateChanged = OnCall;
    core = Factory.Instance.CreateCore(listener, path + "/default_rc", null);

    Thread coreIterate = new Thread(LinphoneCoreIterate);
    coreIterate.IsBackground = false;
    coreIterate.Start();
}

Xamarin IOS

Setup

For your Xamarin.IOS project to be able to use Linphone, you also need our iOS SDK. You can download the nightly snapshot from here or the latest stable release from there.

In your iOS project, create a folder for the Linphone libraries and copy into it the content of the libs directory from our SDK. Import the folders and their content inside your Xamarin Studio project.

For each library file (with .a extension), set the Build Action to None.

For more information about this, you can check out the official documentation.

Add the following line as a mTouch argument in your project build options :

--registrar:static -cxx -gcc_flags "-L${ProjectDir}/Libs -lantlr3c -lavcodec -lbcg729 -lbzrtp -lbctoolbox-tester -lbcunit -lspeex -lcorec -lcunit -lebml2 -lgsm -llinphone -llinphonetester -lmbedcrypto -lmbedtls -lmbedx509 -lopencore-amrnb -lopencore-amrwb -lopenh264 -lopus -lspeexdsp -lsrtp -lswresample -lswscale -ltunnel -lturbojpeg -lvo-amrwbenc -lvpx -lx264 -lbelr -lbellesip -lbelcard  -lbv16 -lmatroska2 -lbctoolbox -lortp -lmediastreamer_base -lmediastreamer_voip -lxml2 -lsqlite3 -lresolv.9 -framework AVFoundation -framework AudioToolbox -framework CoreMedia -framework CoreVideo -framework VideoToolbox -force_load ${ProjectDir}/Libs/liblinphone.a"

Getting started

First you need to create a view. Here's a sample code to create a view with a button displaying the linphone version. The initialization of this view also initialize the linphone core.

When the button is clicked a call to a SIP address is started.

using System;
using System.Threading;
using CoreGraphics;
using UIKit;
using Linphone;
using Foundation;

namespace LinphoneXamarin.iOS
{
public class CustomViewController : UIViewController
 {
 public CustomViewController()
  {
  }

 private LinphoneCore core;
 private LinphoneCoreListener callListener;

 private void OnGlobal(LinphoneCore lc, LinphoneGlobalState gstate, string message)
  {
   Console.WriteLine("Global state changed " + gstate);
  }

 private void OnCall(LinphoneCore lc, LinphoneCall call, LinphoneCallState gstate, string message)
  {
   Console.WriteLine("Call state changed " + gstate);
  if (gstate == LinphoneCallState.End)
   {
    core.RemoveListener(callListener);
   }
  }

 public override void ViewDidLoad()
  {
  base.ViewDidLoad();

   View.BackgroundColor = UIColor.White;
   Title = "My Custom View Controller";

  var btn = UIButton.FromType(UIButtonType.System);
   btn.Frame = new CGRect(20, 200, 280, 44);
  string v = LinphoneCore.Version;
   Console.WriteLine("Version : " + v);
   btn.SetTitle("Version : " + v, UIControlState.Normal);

  var user = new UIViewController();
   user.View.BackgroundColor = UIColor.Magenta;

   btn.TouchUpInside += (sender, e) =>
   {
    callListener = LinphoneFactory.Instance.CreateCoreListener();
    callListener.OnCallStateChanged = OnCall;
    core.AddListener(callListener);

    LinphoneAddress addr = LinphoneFactory.Instance.CreateAddress("sip:my.sip.address@sip.my.domain");
    core.InviteAddress(addr);

   };

   View.AddSubview(btn);

   LinphoneCoreListener listener = LinphoneFactory.Instance.CreateCoreListener();
   listener.OnGlobalStateChanged = OnGlobal;
   core = LinphoneFactory.Instance.CreateCore(listener, "/Path/To/My/defaultrc", null);

   NSTimer mIterateTimer = NSTimer.CreateRepeatingScheduledTimer(0.02, (obj) => { core.Iterate(); });
   mIterateTimer.Fire();
  }
 }
}

And here is the code of the app delegate to launch the previsous view when the app is started :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Runtime.InteropServices;
using Foundation;
using UIKit;

namespace LinphoneXamarin.iOS
{

// The UIApplicationDelegate for the application. This class is responsible for launching the
// User Interface of the application, as well as listening (and optionally responding) to
// application events from iOS.
 [Register("AppDelegate")]
public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
 {
 //
 // This method is invoked when the application has loaded and is ready to run. In this
 // method you should instantiate the window, load the UI into it and then make the window
 // visible.
 //
 // You have 17 seconds to return from this method, or iOS will terminate your application.

 public override UIWindow Window
  {
  get;
  set;
  }

 public override bool FinishedLaunching(UIApplication appl, NSDictionary options)
  {
  // create a new window instance based on the screen size
  Window = new UIWindow(UIScreen.MainScreen.Bounds);
  var cvc = new CustomViewController();
  var navController = new UINavigationController(cvc);
   Window.RootViewController = navController;
  // make the window visible
  Window.MakeKeyAndVisible();

  return true;
  }
 }
}