> 文档中心 > linphone基于sipserver实现音视频对讲

linphone基于sipserver实现音视频对讲

一、技术背景

 

因业务需求需要基于sipserver实现音视频对讲,开源代码linephone即可实现。文末附Demo实现及源码,对讲的实现也基于服务端实现后,提供ip地址才行。

开源库地址:https://github.com/topics/linphone

二、基于安卓客户端实现

添加build.gradle依赖

repositories {    maven { // Replace snapshots by releases for releases ! url "http://linphone.org/releases/maven_repository/"    }}implementation "org.linphone:linphone-sdk-android:4.3.1"

主要代码示例

package org.linphone.sample;import android.app.Service;import android.content.Intent;import android.content.pm.PackageInfo;import android.content.pm.PackageManager;import android.os.Build;import android.os.Handler;import android.os.IBinder;import android.widget.Toast;import androidx.annotation.Nullable;import org.linphone.core.Call;import org.linphone.core.CallParams;import org.linphone.core.Core;import org.linphone.core.CoreListenerStub;import org.linphone.core.Factory;import org.linphone.core.LogCollectionState;import org.linphone.core.tools.Log;import org.linphone.mediastream.Version;import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import java.util.Timer;import java.util.TimerTask;public class LinphoneService extends Service {    private static final String START_LINPHONE_LOGS = " ==== Device information dump ====";    // Keep a static reference to the Service so we can access it from anywhere in the app    private static LinphoneService sInstance;    private Handler mHandler;    private Timer mTimer;    private Core mCore;    private CoreListenerStub mCoreListener;    public static boolean isReady() { return sInstance != null;    }    public static LinphoneService getInstance() { return sInstance;    }    public static Core getCore() { return sInstance.mCore;    }    @Nullable    @Override    public IBinder onBind(Intent intent) { return null;    }    @Override    public void onCreate() { super.onCreate(); // The first call to liblinphone SDK MUST BE to a Factory method // So let's enable the library debug logs & log collection String basePath = getFilesDir().getAbsolutePath(); Factory.instance().setLogCollectionPath(basePath); Factory.instance().enableLogCollection(LogCollectionState.Enabled); Factory.instance().setDebugMode(true, getString(R.string.app_name)); // Dump some useful information about the device we're running on Log.i(START_LINPHONE_LOGS); dumpDeviceInformation(); dumpInstalledLinphoneInformation(); mHandler = new Handler(); // This will be our main Core listener, it will change activities depending on events mCoreListener = new CoreListenerStub() {     @Override     public void onCallStateChanged(Core core, Call call, Call.State state, String message) {  Toast.makeText(LinphoneService.this, message, Toast.LENGTH_SHORT).show();  if (state == Call.State.IncomingReceived) {      Toast.makeText(LinphoneService.this, "Incoming call received, answering it automatically", Toast.LENGTH_LONG).show();      // For this sample we will automatically answer incoming calls      CallParams params = getCore().createCallParams(call);      params.enableVideo(true);      call.acceptWithParams(params);  } else if (state == Call.State.Connected) {      // This stats means the call has been established, let's start the call activity      Intent intent = new Intent(LinphoneService.this, CallActivity.class);      // As it is the Service that is starting the activity, we have to give this flag      intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);      startActivity(intent);  }     } }; try {     // Let's copy some RAW resources to the device     // The default config file must only be installed once (the first time)     copyIfNotExist(R.raw.linphonerc_default, basePath + "/.linphonerc");     // The factory config is used to override any other setting, let's copy it each time     copyFromPackage(R.raw.linphonerc_factory, "linphonerc"); } catch (IOException ioe) {     Log.e(ioe); } // Create the Core and add our listener mCore = Factory.instance()  .createCore(basePath + "/.linphonerc", basePath + "/linphonerc", this); mCore.addListener(mCoreListener); // Core is ready to be configured configureCore();    }    @Override    public int onStartCommand(Intent intent, int flags, int startId) { super.onStartCommand(intent, flags, startId); // If our Service is already running, no need to continue if (sInstance != null) {     return START_STICKY; } // Our Service has been started, we can keep our reference on it // From now one the Launcher will be able to call onServiceReady() sInstance = this; // Core must be started after being created and configured mCore.start(); // We also MUST call the iterate() method of the Core on a regular basis TimerTask lTask =  new TimerTask() {      @Override      public void run() {   mHandler.post(    new Runnable() { @Override public void run() {     if (mCore != null) {  mCore.iterate();     } }    });      }  }; mTimer = new Timer("Linphone scheduler"); mTimer.schedule(lTask, 0, 20); return START_STICKY;    }    @Override    public void onDestroy() { mCore.removeListener(mCoreListener); mTimer.cancel(); mCore.stop(); // A stopped Core can be started again // To ensure resources are freed, we must ensure it will be garbage collected mCore = null; // Don't forget to free the singleton as well sInstance = null; super.onDestroy();    }    @Override    public void onTaskRemoved(Intent rootIntent) { // For this sample we will kill the Service at the same time we kill the app stopSelf(); super.onTaskRemoved(rootIntent);    }    private void configureCore() { // We will create a directory for user signed certificates if needed String basePath = getFilesDir().getAbsolutePath(); String userCerts = basePath + "/user-certs"; File f = new File(userCerts); if (!f.exists()) {     if (!f.mkdir()) {  Log.e(userCerts + " can't be created.");     } } mCore.setUserCertificatesPath(userCerts);    }    private void dumpDeviceInformation() { StringBuilder sb = new StringBuilder(); sb.append("DEVICE=").append(Build.DEVICE).append("\n"); sb.append("MODEL=").append(Build.MODEL).append("\n"); sb.append("MANUFACTURER=").append(Build.MANUFACTURER).append("\n"); sb.append("SDK=").append(Build.VERSION.SDK_INT).append("\n"); sb.append("Supported ABIs="); for (String abi : Version.getCpuAbis()) {     sb.append(abi).append(", "); } sb.append("\n"); Log.i(sb.toString());    }    private void dumpInstalledLinphoneInformation() { PackageInfo info = null; try {     info = getPackageManager().getPackageInfo(getPackageName(), 0); } catch (PackageManager.NameNotFoundException nnfe) {     Log.e(nnfe); } if (info != null) {     Log.i(      "[Service] Linphone version is ",      info.versionName + " (" + info.versionCode + ")"); } else {     Log.i("[Service] Linphone version is unknown"); }    }    private void copyIfNotExist(int ressourceId, String target) throws IOException { File lFileToCopy = new File(target); if (!lFileToCopy.exists()) {     copyFromPackage(ressourceId, lFileToCopy.getName()); }    }    private void copyFromPackage(int ressourceId, String target) throws IOException { FileOutputStream lOutputStream = openFileOutput(target, 0); InputStream lInputStream = getResources().openRawResource(ressourceId); int readByte; byte[] buff = new byte[8048]; while ((readByte = lInputStream.read(buff)) != -1) {     lOutputStream.write(buff, 0, readByte); } lOutputStream.flush(); lOutputStream.close(); lInputStream.close();    }}

 CallActivity

package org.linphone.sample;import android.annotation.TargetApi;import android.app.Activity;import android.content.pm.PackageManager;import android.content.res.Configuration;import android.os.Bundle;import android.util.DisplayMetrics;import android.view.TextureView;import android.view.View;import android.widget.RelativeLayout;import androidx.annotation.Nullable;import org.linphone.core.Call;import org.linphone.core.Core;import org.linphone.core.CoreListenerStub;import org.linphone.core.VideoDefinition;import org.linphone.core.tools.Log;import org.linphone.mediastream.Version;public class CallActivity extends Activity {    // We use 2 TextureView, one for remote video and one for local camera preview    private TextureView mVideoView;    private TextureView mCaptureView;    private CoreListenerStub mCoreListener;    @Override    protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.call); mVideoView = findViewById(R.id.videoSurface); mCaptureView = findViewById(R.id.videoCaptureSurface); Core core = LinphoneService.getCore(); // We need to tell the core in which to display what core.setNativeVideoWindowId(mVideoView); core.setNativePreviewWindowId(mCaptureView); // Listen for call state changes mCoreListener = new CoreListenerStub() {     @Override     public void onCallStateChanged(Core core, Call call, Call.State state, String message) {  if (state == Call.State.End || state == Call.State.Released) {      // Once call is finished (end state), terminate the activity      // We also check for released state (called a few seconds later) just in case      // we missed the first one      finish();  }     } }; findViewById(R.id.terminate_call).setOnClickListener(new View.OnClickListener() {     @Override     public void onClick(View v) {  Core core = LinphoneService.getCore();  if (core.getCallsNb() > 0) {      Call call = core.getCurrentCall();      if (call == null) {   // Current call can be null if paused for example   call = core.getCalls()[0];      }      call.terminate();  }     } });    }    @Override    protected void onStart() { super.onStart();    }    @Override    protected void onResume() { super.onResume(); LinphoneService.getCore().addListener(mCoreListener); resizePreview();    }    @Override    protected void onPause() { LinphoneService.getCore().removeListener(mCoreListener); super.onPause();    }    @Override    protected void onDestroy() { super.onDestroy();    }    @TargetApi(24)    @Override    public void onUserLeaveHint() { // If the device supports Picture in Picture let's use it boolean supportsPip =  getPackageManager()   .hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE); Log.i("[Call] Is picture in picture supported: " + supportsPip); if (supportsPip && Version.sdkAboveOrEqual(24)) {     enterPictureInPictureMode(); }    }    @Override    public void onPictureInPictureModeChanged(     boolean isInPictureInPictureMode, Configuration newConfig) { if (isInPictureInPictureMode) {     // Currently nothing to do has we only display video     // But if we had controls or other UI elements we should hide them } else {     // If we did hide something, let's make them visible again }    }    private void resizePreview() { Core core = LinphoneService.getCore(); if (core.getCallsNb() > 0) {     Call call = core.getCurrentCall();     if (call == null) {  call = core.getCalls()[0];     }     if (call == null) return;     DisplayMetrics metrics = new DisplayMetrics();     getWindowManager().getDefaultDisplay().getMetrics(metrics);     int screenHeight = metrics.heightPixels;     int maxHeight =      screenHeight / 4; // Let's take at most 1/4 of the screen for the camera preview     VideoDefinition videoSize =      call.getCurrentParams().getSentVideoDefinition(); // It already takes care of rotation     if (videoSize.getWidth() == 0 || videoSize.getHeight() == 0) {  Log.w(   "[Video] Couldn't get sent video definition, using default video definition");  videoSize = core.getPreferredVideoDefinition();     }     int width = videoSize.getWidth();     int height = videoSize.getHeight();     Log.d("[Video] Video height is " + height + ", width is " + width);     width = width * maxHeight / height;     height = maxHeight;     if (mCaptureView == null) {  Log.e("[Video] mCaptureView is null !");  return;     }     RelativeLayout.LayoutParams newLp = new RelativeLayout.LayoutParams(width, height);     newLp.addRule(      RelativeLayout.ALIGN_PARENT_BOTTOM,      1); // Clears the rule, as there is no removeRule until API 17.     newLp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, 1);     mCaptureView.setLayoutParams(newLp);     Log.d("[Video] Video preview size set to " + width + "x" + height); }    }}

基于TextureView进行视频流渲染

​​​​​​​ 

demo实现及下载 linephone基于sipserver实现安卓音视频对讲功能-Android文档类资源-CSDN下载基于开源库linephone基于sipserver实现安卓音视频对讲功能更多下载资源、学习资料请访问CSDN下载频道.https://download.csdn.net/download/FrancisBingo/68213457