Integration guide

Windows

Library distributive

Siprix SDK for Windows distributed as single .zip archive. It contains following folders/files:

  • include:

    • Siprix.h. Header file. Using in the include setting when integrating library into C++ applications.

  • lib:

    • siprix.lib. Library file. Using in the linking settings when integrating library into C++ applications.

    • siprix.dll. Binary file with API and signalling implementation for x86_64.

    • siprixMedia.dll. Binary file with media implementation for x86_64.

Deploy to users computers

Library doesn’t require any specific steps for deploying (like register it as ActiveX, etc).

Just copy siprix.dll and siprixMedia.dll along with application’s executable file(s).

Integration into C++ application

  1. Modify project’s include settings with path to library’s folder include

  2. Modify project’s link settings with path to library’s lib\siprix.lib

  3. Modify code:

    • Add library initialization code;

    • Implement adding accounts and making calls;

    • Add own model implementation;

    • See also example application SiprixUA

Integration into C# application

  1. Copy binary files.

    These 2 files: siprix.dll and siprixMedia.dll has to be present in the folder with compiled application. Possible ways of adding them:

    • Copy manually to the output folder (required for both Debug and Release).

    • Add copy command to the post-build event: Copy Siprix binary files in post-build event.

    • Add these files to project and set property Copy if newer: Properties of siprix dll in MSVS project.

  2. Copy source files.

    Copy API and Model files SiprixAPI.cs, SiprixModels.cs from example application SampleWPF

  3. Modify code:

    • If library is adding to WinForms project comment first line in the models file #define WPF_PROJECT

    • Redesign Siprix.ObjModel::loadSavedAccounts and Siprix.ObjModel::postSaveAccountsChanges with own property or own code of saving/loading accounts state;

    • Redesign LogsModel, member variable Siprix.ObjModel.logsModel_.

      • If logging not required - don’t create instance of it, just set to null.

      • If it’s nice to have - replace implementation of the LogsModel::Print with own code.

      • If application needs to display logs on UI - use as is.

    • Add library initialization code when main window loaded (or when dialer required)

      • invoke SiprixModel.Instance.Initialize(App.Current.Dispatcher); in WPF prject.

      • invoke SiprixModel.Instance.Initialize(control); - in WinForms project, where control argument means any control or main window.

    • Add library cleanup code when main window closed (or dialer not required)

      • invoke SiprixModel.Instance.UnInitialize();.

    • Add other code which invokes AddAccount, Invite, Accept, etc., and handles model changes.

Render video in Windows application

Library renders video on the window, specified by its HWND handle. When it’s WinForms application implementation is straightforward as each control has property .Handle, which can be used as argument of method Call_SetVideoWindow().

In case of WPF application implementation is a bit more complicated and requires to use HwndHost or other approach of hosting Win32 control.

If using Win32 controls is not an option library provides ability to add own renderer, which can handle raw frames, see Call_SetVideoRenderer(). It’s planning to add support of WriteableBitmap to the example application: //TODO

If you have more questions/ideas contact: support@siprix-voip.com.


Android

Library distributive

Siprix SDK for Android distributed as single .aar file. It contains compiled java code and native binaries for platforms: arm64-v8a, armeabi-v7a, x86, x86_64.

Integration into existing project

  1. Copy aar file to the folder: <Project>/app/libs

  2. Modify build.gradle

    ...
    dependencies {
       ...
       implementation(files("libs/siprix_voip_sdk.aar"))
       ...
    }
    
  3. Add the following permission to manifest

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    
    <uses-feature android:name="android.hardware.camera" android:required="false" />
    <uses-permission android:name="android.permission.CAMERA" android:required="false"/>
    
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
    <uses-permission android:name="android.permission.VIBRATE" />
    
    <!-- Required if application will start foreground service  -->
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL" />
    <uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    
    <!-- Required if application will received incoming calls in background -->
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
    <uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT"/>
    
  4. Add code which asks runtime permissions:

    • RECORD_AUDIO - required always.

    • CAMERA - required only if application needs to send video from local camera.

    • POST_NOTIFICATIONS - required when application will displays notifications about received incoming calls.

  5. Copy model files

    • Copy folder model from example application SampleJava.

    • Set actual package name package <...>.model; in each copied file.

    • If required push notifications support set kFcmPushNotifEnabled = true in the ObjModel.java:44 and also check code of sending push token in method appendPushTokenToAccount in the AccountsModel.java:69.

    • If required - convert copied files to kotlin.

  6. Add service implementation

    • Copy CallNotifService.java from example application SampleJava.

    • Set actual package name package <...>;

    • Set actual import import <...>.model.ObjModel;

    • Review code of creating notifications, modify it if required.

    • Declare service in the manifest as shown below. Highlighted line (along with FOREGROUND* permissions, shown on step [3] above) required only if application will start this service in foreground mode.

    <service
      android:name=".CallNotifService"
      android:exported="false"
      android:stopWithTask="false"
      android:foregroundServiceType="phoneCall" />
    
  7. Update MainActivity

    Copy from example application SampleJava:

    • Members: ObjModel objModel_ and CallNotifService bgService_.

    • Method startAndBindBgService and invoke it from onCreate.

    • Implementation ServiceConnection serviceConnection_ = ....

    • LaunchMode of the activity android:launchMode="singleTop" from the manifest. This launch mode required to prevent creating few instances of same activity when it started by user/service.

  8. Add own implementation

    As tutorial could be used the following approach:

    • Add to the activity 2 buttons and 2 labels

    • Copy methods from code fragment below. It adds account/call, assigns model observers and displays update of the states on the text labels.

    • Assign copied methods as click event handlers of the added buttons. [!] Don’t forget to review this code and update hardcoded SIP account credentials and phone numbers.

    For final test:

    • Build app, run on device or emulator.

    • Click [AddAccount] - it should add account and display registration status.

    • Click [AddCall] - it should make outgoing call using account, from previous step.

    public void onAddCallButtonClick() {
        try {
            if(objModel_.accounts_.size()==0) return;
    
            DestModel dest = new DestModel();
            dest.setToExt("1012");
            dest.setSrcAccId(objModel_.accounts_.get(0).getAccId());
            objModel_.calls_.invite(dest);
    
            objModel_.calls_.setObserver(new ModelObserver() {
                @Override
                public void onModelChanged() {
                    if(objModel_.calls_.size() > 0) {
                        CallModel call = objModel_.calls_.get(0);
                        callStateLabel.setText(call.getState().toString());
                    }else{
                        callStateLabel.setText("No calls");
                    }
                }
            });
    
        }catch (Exception e) {
            Toast.makeText(this, e.getMessage(),Toast.LENGTH_SHORT).show();
        }
    }
    
    public void onAddAccountButtonClick() {
        try{
            AccountModel acc = new AccountModel();
            acc.setSipServer("192.168.0.122");
            acc.setSipExtension("1019");
            acc.setSipPassword("12345");
            acc.setExpireTime(300);
    
            objModel_.accounts_.add(acc);
    
            objModel_.accounts_.setObserver(new ModelObserver() {
                @Override
                public void onModelChanged() {
                    AccountModel acc = objModel_.accounts_.get(0);
                    accStateLabel.setText(acc.getRegText());
                }
            });
        }catch(Exception e) {
            Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
        }
    }
    

    Note

    When application will start next time it restores account, saved by the model in the previous session. Final implementation should take it into account and assign model observer just after start.

Render video in Android application

Library provides its own class SiprixVideoRenderer inherited from SurfaceView. Application should create instance of this class and assign it for selected call by invoking callSetVideoRenderer(callId, renderer); Here is example of declaration renderer in the layout resource file:

<com.siprix.SiprixVideoRenderer
    android:id="@+id/remote_renderer"
    android:layout_width="130dp"
    android:layout_height="100dp"
    android:elevation="-1dp"
    android:visibility="visible"/>

See also: Working in background.


MacOS

Library distributive

Siprix SDK for MacOS distributed as 2 frameworks: siprix.framework and siprixMedia.framework. They contain binaries for platforms: x86_64 and arm64. Also siprix.framework contains headers:

  • Siprix.h. ObjC header file. Using for integrating library into ObjC/Swift applications.

  • SiprixCpp.h. C++ header file. Using for integrating library into C/C++ applications.

Integration into existing project

  1. Copy siprix.framework and siprixMedia.framework to project’s folder.

  2. Set embed settings

    In Xcode, select build target, then select “General” tab, scroll down to “Framework, Libraries, and Embedded Content”, select “+”, then browse and select copied frameworks.

    Embed Siprix frameworks.

  3. Request authorization for audio/video capture and network.

    In Xcode, select build target, then select Info tab, add NSMicrophoneUsageDescription key, if required video calls add also NSCameraUsageDescription key.

    Then select Signing & Capabilities tab, in the App Sandbox section check Network, Camera, Audio Input.

    Mic/cam usage description plist. Enable mic/cam/network entitlements.

  4. Copy model file

    • Copy SiprixModels.swift from example application SampleSwiftUI.

    • Add copied file to XCode project by clicking menu Add Files to <Project>.

    • If LogsModel not required set it nil on line 818: logs = nil;//LogsModel() of the copied model file.

  5. Initialize/uninitialize library

    Add following initialization code to the App class.

    @main
    struct VoipTestApp: App {
    ...
       class AppDelegate: NSObject, NSApplicationDelegate {
          func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool{
              SiprixModel.shared.uninitialize()
              return true
          }
       }
    
       init() {
          ...
          SiprixModel.shared.initialize()
          ...
       }
    
       @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
       var body: some Scene {
          WindowGroup {
              ContentView()
          }
       }
    
  6. Add own implementation

    As tutorial could be used code below, which builds SwiftUI View with ability to add account, make call, display status. Screenshot of the MacOS tutorial app

    struct ContentView: View {
       @StateObject var accList = SiprixModel.shared.accountsListModel
       @StateObject var callsList = SiprixModel.shared.callsListModel
       @State private var addAlert = false
       @State private var addErr = ""
    
       var body: some View {
          HStack {
             VStack {
                Button(action: addAcc) {
                   Text("Add account")
                   Image(systemName: "text.badge.plus").font(.body)
                }
    
                ScrollView {
                   ForEach(accList.accounts) {
                      acc in HStack {
                         Text(acc.name)
                         regStateImg(acc)
                      }
                   }
                }.border(.gray)
             }
    
             VStack {
                Button(action: addCall) {
                   Text("Add call")
                   Image(systemName: "phone.badge.plus.fill").font(.body)
                }
    
                ScrollView{
                   ForEach(callsList.calls) {
                      call in HStack {
                         Text(call.remoteSide)
                         Text(call.stateStr)
                         Button(action: { call.bye() }) {
                            Text("Hangup")
                         }
                      }
                   }
                }.border(.gray)
             }
          }
          .alert("Can't add", isPresented: $addAlert) {}
          message: { Text(addErr) }
       }
    
       private func addAcc() {
          let accData = SiprixAccData()
          accData.sipServer = "192.168.0.122"
          accData.sipExtension = "1018"
          accData.sipPassword = "12345"
          accData.expireTime = 300
    
          let errCode = accList.add(accData)
          if(errCode != kErrorCodeEOK) {
             addErr = SiprixModel.shared.getErrorText(errCode)
             addAlert = true
          }
       }
    
       private func addCall() {
          let dest = SiprixDestData()
          dest.toExt = "1012"
          dest.fromAccId = Int32(accList.selectedAccId)
    
          let errCode = callsList.invite(dest)
          if(errCode != kErrorCodeEOK) {
             addErr = SiprixModel.shared.getErrorText(errCode)
             addAlert = true
          }
       }
    
       func regStateImg(_ acc: AccountModel) -> some View {
          switch acc.regState {
             case .success: Image(systemName: "checkmark.icloud")
             case .failed:  Image(systemName: "xmark.icloud")
             case .removed: Image(systemName: "checkmark")
             default:       Image(systemName: "arrow.clockwise.icloud")
          }
       }
    }
    

Render video in MacOS application

Library has special method: -(NSView* _Nonnull)createVideoWindow; for creating video windows. Application should invoke this method, move created window to proper location and assign it to selected call by invoking callSetVideoWindow(callId, wnd).

In case of using SwiftUI - create UIViewRepresentable as shown below

struct SiprixVideoView: UIViewRepresentable {
   private var call : CallModel
   private let isPreview : Bool

   init(_ call: CallModel, isPreview : Bool) {
      self.call = call
      self.isPreview = isPreview
   }

   func makeUIView(context: Context) -> UIView {
      let view = SiprixModel.shared.createVideoView()
      call.setVideoView(view, isPreview:isPreview)
      return view
   }
   func updateUIView(_ uiView: UIView, context: Context) { }
}
...
var body: some View {
   ZStack(alignment: .bottomTrailing) {
      if(call.withVideo) {
         SiprixVideoView(call, isPreview:false)
         SiprixVideoView(call, isPreview:true)
         ...
      }
   ...

See more: UIViewRepresentable.


iOS

Library distributive

Siprix SDK for iOS distributed as 2 frameworks: siprix.xcframework and siprixMedia.xcframework. They contain binaries for device (arm64) and also simulator (x86_64 and arm64).

Integration into existing project

  1. Copy siprix.xcframework and siprixMedia.xcframework to project’s folder.

  2. Set embed settings

    In Xcode, select build target, then select “General” tab, scroll down to “Framework, Libraries, and Embedded Content”, select “+”, then browse and select copied frameworks.

    Embed Siprix xcframeworks

  3. Request authorization for audio/video capture.

    In Xcode, select build target, then select Info tab, add NSMicrophoneUsageDescription key, if required video calls add also NSCameraUsageDescription key.

    Mic/cam usage description plist.

    See more: Requesting authorization to capture and save media.

  4. Select checkbox Voice over IP in the signing and capabilities

    In Xcode, select build target, then select Signing & Capabilities tab, add select checkbox Voice over IP.

    Select voice over IP.

  5. Copy model file

    • Copy SiprixModels.swift from example application SampleSwiftUI.

    • Add copied file to XCode project by clicking menu Add Files to <Project>.

    • If LogsModel not required set it nil on line 818: logs = nil;//LogsModel() of the copied model file.

  6. Initialize library

    Add following initialization code to the App class.

     init() {
        ...
        SiprixModel.shared.initialize()
        ...
     }
    
  7. Add own implementation

    As tutorial could be used code below (same as for MacOS), which builds SwiftUI View with ability to add account, make call, display status.

    Screenshot of the iOS tutorial app

    struct ContentView: View {
       @StateObject var accList = SiprixModel.shared.accountsListModel
       @StateObject var callsList = SiprixModel.shared.callsListModel
       @State private var addAlert = false
       @State private var addErr = ""
    
       var body: some View {
          HStack {
             VStack {
                Button(action: addAcc) {
                   Text("Add account")
                   Image(systemName: "text.badge.plus").font(.body)
                }
    
                ScrollView {
                   ForEach(accList.accounts) {
                      acc in HStack {
                         Text(acc.name)
                         regStateImg(acc)
                      }
                   }
                }.border(.gray)
             }
    
             VStack {
                Button(action: addCall) {
                   Text("Add call")
                   Image(systemName: "phone.badge.plus.fill").font(.body)
                }
    
                ScrollView{
                   ForEach(callsList.calls) {
                      call in HStack {
                         Text(call.remoteSide)
                         Text(call.stateStr)
                         Button(action: { call.bye() }) {
                            Text("Hangup")
                         }
                      }
                   }
                }.border(.gray)
             }
          }
          .alert("Can't add", isPresented: $addAlert) {}
          message: { Text(addErr) }
       }
    
       private func addAcc() {
          let accData = SiprixAccData()
          accData.sipServer = "192.168.0.122"
          accData.sipExtension = "1018"
          accData.sipPassword = "12345"
          accData.expireTime = 300
    
          let errCode = accList.add(accData)
          if(errCode != kErrorCodeEOK) {
             addErr = SiprixModel.shared.getErrorText(errCode)
             addAlert = true
          }
       }
    
       private func addCall() {
          let dest = SiprixDestData()
          dest.toExt = "1012"
          dest.fromAccId = Int32(accList.selectedAccId)
    
          let errCode = callsList.invite(dest)
          if(errCode != kErrorCodeEOK) {
             addErr = SiprixModel.shared.getErrorText(errCode)
             addAlert = true
          }
       }
    
       func regStateImg(_ acc: AccountModel) -> some View {
          switch acc.regState {
             case .success: Image(systemName: "checkmark.icloud")
             case .failed:  Image(systemName: "xmark.icloud")
             case .removed: Image(systemName: "checkmark")
             default:       Image(systemName: "arrow.clockwise.icloud")
          }
       }
    }
    

Render video in iOS application

Library has special method: -(UIView* _Nonnull)createVideoWindow; for creating video windows. Approach is the same as explained above: Render video in MacOS application.

CallKit integration

Siprix provides source code of the model with ready to use CallKit implementation. It’s available in SiprixModels.swift as part of example application SampleSwiftUI.

Model hides internal details and application can simply invoke methods like call.hold() or call.sendDtmf() or call.bye(). If CallKit enabled - these methods will trigger CX*Action’s, process them and perform SIP actions.

See also: CallKit-PushKit iOS.


Linux

Library distributive

Siprix SDK for Linux distributed as single .zip archive.

It contains following folders/files:

  • include:

    • Siprix.h. Header file. Using in the include setting when integrating library into C++ applications.

  • lib:

    • libsiprix.so. Binary file with API and signalling implementation for x86_64.

    • libsiprixMedia.so. Binary file with media implementation for x86_64.

Integration into C++ application

  1. Modify project’s include settings with path to library’s folder include

  2. Modify project’s link settings with path to library’s folder lib

  3. Modify code:

    • Add library initialization code;

    • Implement adding accounts and making calls;

    • Add own model implementation;

    • See also example application SiprixUA.