webrtc 服务器 janus android使用教程1:Echo Test

WebRtc Android端无人机自定义视频流接入方案总结 WebRtc Android端自定义视频流接入方案 最近在基于WebRtc接入无人机视频流方案,网上找了很多资料都不是很完整,现在功能已经接入完成,为了方便同行开发,将自己的心得及遇到的问题总结下。 1、先从网站上下载webrtc Android端demo代码,下载地址https://github.com/benwtrent/janus-gateway-android.git。 2、确保使用的设备是支持相机功能的,不然是无法推拉流到web网站的。 3、从demo代码及d... 阅读详情

1.编译带ws接口的janus v0.7.6

官方文档地址  https://github.com/meetecho/janus-gateway  

由于官方提供的andorid demo 是基于http c++的实现(https://github.com/meetecho/janus-mobile-sdk)对于android 过于复杂和有bug  所有使用ws 要简单的多

ws 安装后有以下日志会输出为成功

[WARN] libwebsockets has been built without IPv6 support, will bind to IPv4 only
libwebsockets logging: 0
WebSockets server started (port 8188)...
 

2.android studio 导入webrtc和websoket库 

implementation 'org.webrtc:google-webrtc:1.0.26131' 可以用最新的
implementation 'com.github.dfqin:grantor:2.5'
implementation "org.java-websocket:Java-WebSocket:1.3.8"

如果不熟悉android webrtc的开发的可以参考https://www.jianshu.com/p/8c10146afd6c

3.实现webscoket

主要对webscoket 的消息进行发送和接收  并把sdp信息给webrtc peerconntion

package com.liiln.janus5.echo;

import android.util.Log;


import com.liiln.janus5.janus.MyTools;

import org.java_websocket.client.WebSocketClient;
import org.java_websocket.drafts.Draft;
import org.java_websocket.drafts.Draft_6455;
import org.java_websocket.extensions.IExtension;
import org.java_websocket.handshake.ServerHandshake;
import org.java_websocket.protocols.IProtocol;
import org.java_websocket.protocols.Protocol;
import org.json.JSONException;
import org.json.JSONObject;
import org.webrtc.IceCandidate;
import org.webrtc.SessionDescription;

import java.math.BigInteger;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.ConcurrentHashMap;


public class EchoWebSocketChannel  extends WebSocketClient {
    private ConcurrentHashMap<BigInteger, PlugHandle> attachedPlugins = new ConcurrentHashMap();
    private ConcurrentHashMap<String, ITransaction> transactions = new ConcurrentHashMap();
    private BigInteger sessionId = null;
    private Delegate _delegate = null;
    public EchoWebSocketChannel(URI serverUri, Draft protocolDraft) {
        super(serverUri, protocolDraft);
    }

    @Override
    public void onOpen(ServerHandshake handshakedata) {
        //
        createSession();
    }

    @Override
    public void onMessage(String message) {
        Log.e("提示收到消息》》》",message);
        //
        try {
            JSONObject msg  = new JSONObject(message);
            String janus = msg.getString("janus");
            if(janus.equals("success")){
                String tid = msg.getString("transaction");
                ITransaction transaction = transactions.get(tid);
                if(transaction!=null){
                    transactions.remove(tid);
                    transaction.onSuccess(msg);
                }
            }else if(janus.equals("error")){
                String tid = msg.getString("transaction");
                ITransaction transaction = transactions.get(tid);
                if(transaction!=null){
                    transactions.remove(tid);
                    transaction.onError();
                }
            }else if(janus.equals("ack")){
            }else{
                if(msg.has("sender")) {
                    BigInteger sender = new BigInteger(msg.getString("sender"));
                    PlugHandle handle = attachedPlugins.get(sender);
                    if (handle != null) {
                        if (janus.equals("event")) {
                            JSONObject data = msg.getJSONObject("plugindata").getJSONObject("data");
                            JSONObject jsep = null;
                            if(msg.has("jsep")){
                                jsep = msg.getJSONObject("jsep");
                            }
                            _delegate.onMessage(sender, data, jsep); //
                        } else if (janus.equals("detached")) {
                            _delegate.onLeaving(sender);//
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }



    }

    @Override
    public void onClose(int code, String reason, boolean remote) {

    }

    @Override
    public void onError(Exception ex) {

    }

    public interface Delegate{
        void onMessage(BigInteger handleId, JSONObject msg, JSONObject jsep);
        void onLeaving(BigInteger handleId);
        void onAttached(BigInteger handleId);
    }
    public void setDelegate(Delegate delegate){
        _delegate =delegate;
    }

    public static EchoWebSocketChannel init(String url) {
        EchoWebSocketChannel websocket = null;
        try {
            ArrayList<IProtocol> protocols = new ArrayList<IProtocol>();
            protocols.add(new Protocol("janus-protocol"));
            Draft_6455 proto_janus = new Draft_6455(Collections.<IExtension>emptyList(), protocols);
            websocket = new EchoWebSocketChannel(new URI(url),proto_janus);
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }
        websocket.connect();
        return websocket;
    }

    public void disconnect(){
        stopKeepAliveTimer();
        super.close();
    }
    public void sendmessage(String message){
        Log.e("提示","send==>>"+message);
        send(message);
    }

    private volatile Thread keep_alive = null;
    boolean connected = false;
    private void startKeepAliveTimer(){
        connected = true;
        keep_alive = new Thread(new Runnable() {
            @Override
            public void run() {
                Thread thisThread = Thread.currentThread();
                while (keep_alive == thisThread) {
                    try {
                        thisThread.sleep(25000);
                    } catch (InterruptedException ex) {
                    }
                    if (!connected) {
                        return;
                    }
                    JSONObject obj = new JSONObject();
                    try {
                        obj.put("janus", "keepalive");
                        obj.put("session_id", sessionId);
                        obj.put("transaction", MyTools.randomString(12));
                        sendmessage(obj.toString());
                    } catch (JSONException e) {
                        connected = false;
                        return;
                    }
                }
            }
        }, "KeepAlive");
        Log.e("提示","socket end");
        keep_alive.start();
    }
    private void stopKeepAliveTimer(){
        keep_alive = null;
        connected = false;
    }
    private void createSession(){
        String tid = MyTools.randomString(12);
        transactions.put(tid, new ITransaction(tid) {
            @Override
            public void onSuccess(JSONObject msg) throws Exception{
                JSONObject data = msg.getJSONObject("data");
                sessionId = new BigInteger(data.getString("id"));
                Log.e("===>","收到sessionId"+sessionId);
                startKeepAliveTimer();
                createHandle();
                /**
                 JSONObject objx = new JSONObject();
                 objx.put("janus", "info");
                 objx.put("transaction",  stringGenerator.randomString(12));
                 sendmessage(objx.toString());
                 **/
            }
        });
        try{
            JSONObject obj = new JSONObject();
            obj.put("janus", "create");
            obj.put("transaction", tid);
            sendmessage(obj.toString());
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    private void createHandle(){
        String tid = MyTools.randomString(12);
        transactions.put(tid, new ITransaction(tid) {
            @Override
            public void onSuccess(JSONObject msg) throws Exception{
                JSONObject data = msg.getJSONObject("data");
                BigInteger handleId = new BigInteger(data.getString("id"));
                Log.e("===>","handleId="+handleId);
                _delegate.onAttached(handleId);
                PlugHandle handle = new PlugHandle(handleId);
                attachedPlugins.put(handleId,handle);
                //registerUsrname(handleId);
            }
        });

        try{
            JSONObject obj = new JSONObject();
            obj.put("janus", "attach");
            obj.put("transaction", tid);
            obj.put("plugin", "janus.plugin.echotest");
            obj.put("session_id",sessionId);
            sendmessage(obj.toString());
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public void publisherCreateOffer(final BigInteger handleId, final SessionDescription sdp) {
        JSONObject publish = new JSONObject();
        JSONObject jsep = new JSONObject();
        JSONObject message = new JSONObject();
        try {
            publish.putOpt("request", "configure");
            publish.putOpt("audio", true);
            publish.putOpt("video", true);

            jsep.putOpt("type", sdp.type);
            jsep.putOpt("sdp", sdp.description);

            message.putOpt("janus", "message");
            message.putOpt("body", publish);
            message.putOpt("jsep", jsep);
            message.putOpt("transaction", MyTools.randomString(12)  );
            message.putOpt("session_id", sessionId);
            message.putOpt("handle_id", handleId);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        sendmessage(message.toString());
    }


    public void trickleCandidate(final BigInteger handleId, final IceCandidate iceCandidate) {
        JSONObject candidate = new JSONObject();
        JSONObject message = new JSONObject();
        try {
            candidate.putOpt("candidate", iceCandidate.sdp);
            candidate.putOpt("sdpMid", iceCandidate.sdpMid);
            candidate.putOpt("sdpMLineIndex", iceCandidate.sdpMLineIndex);

            message.putOpt("janus", "trickle");
            message.putOpt("candidate", candidate);
            message.putOpt("transaction", MyTools.randomString(12));
            message.putOpt("session_id", sessionId);
            message.putOpt("handle_id", handleId);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        sendmessage(message.toString());
    }

    public void trickleCandidateComplete(final BigInteger handleId) {
        JSONObject candidate = new JSONObject();
        JSONObject message = new JSONObject();
        try {
            candidate.putOpt("completed", true);

            message.putOpt("janus", "trickle");
            message.putOpt("candidate", candidate);
            message.putOpt("transaction", MyTools.randomString(12));
            message.putOpt("session_id", sessionId);
            message.putOpt("handle_id", handleId);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        sendmessage(message.toString());
    }
 

}

//额外类

public abstract class ITransaction {
    public String _tid = null;
    public ITransaction(String tid){
        _tid = tid;
    }
    public void onError(){};
    public void onSuccess(JSONObject data)throws Exception{};
}
public class PlugHandle {
    private BigInteger _handleId = null;
    public PlugHandle(BigInteger handleId){
        _handleId = handleId;
    }
}

3 、结合webrtc库使用 参考上面的android webrtc 连接

1.创建PeerConnectionFactory 、PeerConnection

  2.peerConnection.addStream(mediaStream);

  3.peerConnection.createOffer 成功后将sdp 发送

_websocket.publisherCreateOffer(handleId,sdp);

 4.发送后webscoket会收到anwer的sdp信息设置给peerConnection

peerConnection.setRemoteDescription

5.设置后peerConnection 的onAddStream会返回 video数据 用webrtc.SurfaceViewRenderer显示就行了

VideoTrack remoteVideoTrack = mediaStream.videoTracks.get(0);
runOnUiThread(() -> {
    if(remoteVideoTrack!=null) remoteVideoTrack.addSink(remoteView);
});

6.ice的调用

trickleCandidate(handleId, candidate);和trickleCandidateComplete(handleId); 发送

4、测试

  

其他demo 比如会议、对话等可以参考博客列表

如果你觉得麻烦可以到https://item.taobao.com/item.htm?id=615521243728 获取支持 

交流群261074724 

 

 

 

 

 

 

JanusWebrtc VideoRoom插件 Android使用总结 janus VideoRoom插件官方文档 实例Demo GitHub地址 JanusWebrtc详见:Janus(一)开源服务器介绍 - 简书janus-gateway Janus 是由Meetecho设计和开发的开源、通用的基于SFU架构的WebRTC流媒体服务器,它支持在Linux的服务器或MacOS上的机器进...https://www.jianshu.com/p/78f2689158dd在安卓上使用时,音视频编码录音传输全由Webrtc自行处理,调用时 1、确保权限获取正常,麦克. 阅读详情

相关推荐

关于WebRTC视频Android端实现的原理解读

进入公司之后做了第一个项目就是关于视频的,因为用的是别人提供的sdk,所以说很容易就能实现其中的功能,那么项目结尾的时候就想着不能光会用,起码得知道原理过程吧!那么下面就讲解一下本人对关于WebRTC的视频连接过程的一些讲解:关于WebRTC这个库。

yinshipin007的博客 1214

WebRTC视频会议Janus+Android

讲解如何搭建Janus,并运行Janus WebDemo。实现Android端使用Janus流媒体服务器与Web端进行多人的实时音视频互动课程升级支持janus0.9.2,配置文档已经上传在 第7课时 《janus-启动配置》附件中

webRTC RTCPeerConnection连接建立过程 ---- 基于一个简易demo来分析

前言 本文尝试梳理webRTC连接建立的过程,但是不会一上来就给你个高大上的图,而是基于一个简单的demo运行来展开。 webRTC的demo和服务器部署,我已经介绍了两套: 一个是google官方的: webRTC Android源码拉取与编译与运行 一个是Janus: webRTC服务器搭建(基于Janus)与Demo运行 这两种都是可以改一改,直接商业化的。 如果是纯粹学习用,那这里推荐个github找到的简单的demo: RTCStartupDemo。 比起前两个,这个demo只适合学习用,因为非常

newchenxf的专栏 3903

WebRTC gateway janus入门教程:从配置到插件编写

文章链接: http://webrtc.org.cn/webrtc-janus-180426/ 本文介绍了janus的环境搭建,janus的设计思想,和插件的编写方法。 阅读本文后,你能够对janus的使用有基本的认识。 具体的细节可以通过阅读源码的方式继续深入了解。 ...

我是榜样的博客 3567

JanusAndroid:在 Android 上运行 Janus 服务器的 Android 应用程序

杰纳斯安卓 Janus Android 是一个 Android 应用程序,用于在 Android 上运行 Janus 服务器。 此应用程序使用原始 Janus 项目 ( ) 的 Android 移植,位于 https://github.com/tpiotrow/janusproject-Android 杰纳斯计划 Janus 是一个完全用 Java 1.7 实现的开源多代理平台。 Janus 使开发人员能够快速创建基于 Web、企业和桌面多代理的应用程序。 它提供了一套全面的功能来开发、运行、显示和监控基于多代理的应用程序。 基于 Janus 的应用程序可以分布在网络上。 Janus 可以用作面向代理的平台、组织平台和/或完整平台。 它还本地管理递归代理和完全子的概念。 Janus 由 Laboratoire Systèmes et Transports 和 Grupo de Inv

【无机纳米材料科研制图——OriginLab 0205】Origin对BET吸附/脱附曲线及孔径分布制图...

此篇,我们来分享如何对BET数据进行处理和制图。 一、等温吸脱附曲线绘制 1)找到等温吸脱附文件。 一半来说,对一个样品测一次BET,会产生很多数据,具体因仪器而异。我这里的数据主要有两个文件夹,一个脱附,一个吸附,吸附文件夹中多了一个等温吸脱附的文件夹。而这...

XD_Yangf的博客 803

基于Janus的WebRTC服务器部署与实时通信Demo实战

本文详细介绍了基于Janus网关搭建WebRTC服务器的完整实战流程。从环境准备、核心依赖库编译安装,到Janus服务的配置、SSL证书生成与启动,最后通过网页端和Android客户端Demo进行音视频互通测试,帮助开发者快速部署并理解实时通信服务器的核心架构与调试方法。

weixin_29212429的博客 255

Arduino-Pico:用Arduino生态开发树莓派RP2040双核与PIO实战

在嵌入式开发领域,微控制器编程是连接硬件与软件的核心技能。其原理在于通过编写代码直接控制处理器与外设,实现特定功能。掌握这项技术能极大提升开发效率,降低硬件操作门槛,广泛应用于物联网、智能硬件和自动化控制等场景。本文聚焦于如何利用Arduino这一广受欢迎的生态,来开发树莓派RP2040微控制器。RP2040以其双核Cortex-M0+架构和独特的可编程IO(PIO)硬件而著称,性能强大。通过第三方Arduino核心(arduino-pico),开发者可以沿用熟悉的Arduino语法和库,快速调用双核并行处

weixin_26905423的博客 381

5步构建现代视频流转换系统:go2rtc技术架构深度解析

在实时流媒体技术领域,协议兼容性和低延迟传输一直是技术实现的难点。go2rtc作为一个零依赖、零延迟、零成本的视频流转换工具,通过创新的技术架构解决了传统RTSP转WebRTC的复杂性问题,为开发者提供了完整的多协议视频流转发解决方案。 ## 技术架构与核心设计理念 go2rtc采用模块化设计,将视频流处理分为输入、输出和双向音频三个核心组件。这种架构设计使得系统能够灵活支持数十种不同的视频流

gitblog_00080的博客 348

Mediapipe手势识别实战——基于关节角度计算实现动态手势分类

本文详细介绍了如何利用Mediapipe进行手势识别,通过计算手指关节弯曲角度实现动态手势分类。文章从基础原理出发,讲解了关节角度计算的数学方法,并提供了Python实现代码。同时,还分享了数字手势和OK手势的识别实战经验,以及性能优化和工程实践中的关键技巧,帮助开发者快速掌握实时手势识别技术。

weixin_26891599的博客 402

用ADSP-21569+SigmaStudio打造音频处理系统:从硬件连接到实时调参实战

本文详细介绍了如何利用ADSP-21569 DSP芯片和SigmaStudio图形化编程工具构建高性能音频处理系统。从硬件接口配置、信号流设计到实时参数调试,提供工业级开发中的实用技巧,包括低延迟优化、动态参数插值等关键技术,帮助开发者快速实现专业音频处理方案。

9o8p7i6u5y的博客 979

Ubuntu 20.04下Nginx-RTMP生产级部署与三协议共存实战

RTMP服务器是低延迟直播的基础架构组件,其核心在于协议解析、流状态管理与HTTP服务集成。Nginx-RTMP模块将RTMP协议栈嵌入Nginx事件模型,实现连接复用、实时流监控(/stat接口)及HLS/DASH自动切片等关键能力。相比FFmpeg单进程推拉流或SRS/Janus等全功能服务器,它以极简配置达成高稳定性与可运维性,特别适配Ubuntu 20.04这类LTS系统——但需规避其IPv6默认禁用、systemd服务类型误配、ufw防火墙静默拦截1935端口等隐性陷阱。本文聚焦Nginx-RTM

weixin_30325971的博客 291

深入浅出:拆解RK3588 DVP摄像头驱动框架,从DTS配置到V4L2子系统

本文深入解析RK3588 DVP摄像头驱动框架,从DTS配置到V4L2子系统的完整实现。详细介绍了瑞芯微RK3588的多媒体子系统架构、设备树配置逻辑、rkcif驱动与V4L2框架的交互机制,以及用户空间调试与性能优化技巧,帮助开发者解决DVP摄像头驱动中的常见问题。

weixin_42525632的博客 265

webrtc服务器janus android客户端编译学习三

https://blog.youkuaiyun.com/bvngh3247/article/details/80908529

liuweihui521的专栏 470

Windows下MMdetection3D保姆级安装指南(含CUDA和PyTorch版本避坑)

本文提供Windows平台下MMdetection3D的完整安装指南,重点解决CUDA与PyTorch版本匹配问题。从环境准备到关键组件安装,详细介绍了CUDA Toolkit选择策略、PyTorch精准安装步骤,以及MM系列组件的安装顺序和常见错误解决方案,帮助用户顺利完成3D目标检测环境的配置。

weixin_42628941的博客 490

SpringCloud最新最全面试题

在服务注册与发现中,有一个注册中心。当服务器启动的时候,会把当前自己服务器的信息比如服务地址通讯地址等以别名方式注册到注册中心上。另一方(消费者服务提供者),以该别名的方式去注册中心上获取到实际的服务通讯地址,然后再实现本地RPC调用RPC远程调用框架核心设计思想:在于注册中心,因为使用注册中心管理每个服务与服务之间的一个依赖关系(服务治理概念)。在任何RPC远程框架中,都会有一个注册中心存放服务地址相关信息(接口地址)。

qq_38196449的博客 4328

WebRTC janus安装编译教程

13.安装完成后,直接执行janus运行程序。3.安装Meson and Ninja。10.安装libnanomsg-dev。8.安装libwebsockets。系统 ubuntu 22.04。11.安装rabbitmq-c。4.设置python优先级。5.安装libnice。6.安装libsrtp。7.安装usrsctp。12.安装janus。

纵横四海的博客 1023

智能办公助手——OpenClaw办公自动化集成(2026办公版)

OpenClaw智能办公助手是一款集成任务管理、文档处理等功能的办公自动化系统。该系统采用Python实现,核心功能包括:1)任务管理系统,支持任务增删改查、状态跟踪和统计报表生成;2)文档智能助手,提供文档处理能力。任务管理模块具备优先级设置、状态变更、时间记录等功能,并能生成包含完成率等指标的任务报告。该系统旨在通过自动化办公流程提升工作效率,减少重复性工作。开发者可利用这些模块构建完整的智能办公解决方案。

一键难忘的博客 349

前端janus的使用

其实janus源码内部调用webrtc进一步封装好了接口createOffer,读者不用关心复杂的媒体协商peerconnection这些复杂的概念(当时本人在这里花费很长时间去理解,读者如果感兴趣,可建议先去了解webrtc的常用api,janus源码有使用到流的获取,以及对peerconnection的处理),你只要知道你的浏览器获取本地音视频等流信息通过createOffer这个接口就可以实现。通过传不同个参数,每new一个janus就是一个新的会议室,这里我们只考虑一个会议室的情况。

菜鸟的灵魂 1375

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

Janus源码分析(5)——echotest分析

1、运行效果图 Echo测试演示的是发送给服务器网关的音频和视频,服务器会回传给你,效果如下图所示: 2、代码分析 2.1 代码结构 2.2 源码分析 2.2.1 创建线程 在janus = new Janus()时,调用Janus(gatewayCallbacks)在其中有函数createSession,并且传入下面的回调函数: createSession创建请求,成功建立一次httpAP...

点点滴滴 2706

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

视频教程-WebRTC视频会议Janus+Android-Android

WebRTC视频会议Janus+Android 从2012年开始从事移动互联...

weixin_34019484的博客 963

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

Android 音视频开发实践系列-04-Android WebRTC推流到SRS服务器实现直播功能

目录 前言 了解WebRTC 部署SRS服务器 下载源码并运行 可能遇到的问题 create session : create session : add publisher : publish negotiate : no found valid H.264 payload type RTC error code=5020 : create session : stream /live/livestream busy 播放卡住的问题 参考资料 前言 最近项目用到WebRTC来

农贤钢的专栏 1万+

janus-Gateway的服务端部署

需求是前后端的webRTC推拉流,但是后端用的是c++,于是使用了这个库做视频流的推送和拉取,记录踩坑过程。如果你也需要自己部署janus的服务端并在前端拉流测试,希望对你有所帮助。服务端的搭建本人并不熟悉,参考的是newchenxf大佬的这篇,由于版本更新,有些报错的地方会在下文指正。

Raccon_的博客 4102

janus搭建

Janus 官网:https://janus.conf.meetecho.com/index.html 参考文档:https://github.com/meetecho/janus-gateway Janus是一个开源的WebRTC网关,可用于构建实时通信应用程序。它提供了音频、视频和数据传输的功能,可以与WebRTC兼容的浏览器、移动应用程序和服务器进行通信。 他是一个轻量级的网关,具有灵活的架构和模块化的设计。它采用插件方式实现不同的功能,如媒体流处理、会话管理、NAT穿透等。这使得开发者可以根据自己的

littleboy_IT 2196

janus demo运行说明

本文简要说明了下janus的demo运行时的注意事项

tusong86的博客 1829
上一篇: pjsip windows demo编译运行 (pjsip 2.9 qt环境)
下一篇: Java Pjsip (Pjsua2 api ) 2.10 windows sip语音呼叫教程
java_lilin
博客等级 码龄14年 364粉丝 124原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值