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