开发者中心

名称: 上海璟梦信息科技有限公司 音视频 SDK 集成文档
版本: V1.0.1
日期: 2022-04-19

一、集成指南

1.1 SDK 集成

1.1.1 申请 AppId

1.1.2 导入 jar 包

  1. 将 SDK 的 jar 包添加到工程 libs 目录
    2) 将 okHttp 的 jar 包添加到工程 libs 目录,增加gradle 依赖
在跟目录下的build.gradle

allprojects {
    repositories {
        google()
        mavenCentral()
        jcenter() // Warning: this repository is going to shut down soon
        maven {
            url "https://nexus-sdk.cm253.com/repository/maven-public/"
        }
    }
}

在app目录下的buil.gradle

implementation 'com.chuanglan.sdk:rtc:1.2.0.2@jar'

1.1.3 导入 so 库

  1. 选择您的 so 库
ndk{
    abiFilters "armeabi-v7a", "arm64-v8a"
}

1.1.4 配置 AndroidManifest.xml

1)添加 SDK 相关权限

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.CAMERA"/>

1.1.5 添加混淆配置

# IM
-keep class com.chuanglan.** { *;}
-keep class com.network.okhttp.** { *;}
-keep class com.network.okio.** { *;}
-keep class org.apache.mina.** { *;}
-keep class com.google.protobuf.** { *;}
-keep class org.slf4j.** { *;}

# 实时音视频
-keep class com.chuanglan.** { *;}
-keep class com.cloud.audio.** { *;}
-keep class com.lib.commonlib.** { *;}
-keep class com.medialib.audio.** { *;}
-keep class com.protocol.tlv.** { *;}
-keep class com.smart.audio.** { *;}
-keep class com.third.google.gson** { *;}

二、接口调用

2.1 语音模块

2.1.1 初始化(App 用户为未登录状态时调用)

在代码中调用 SDK 初始化方法:

private VoipInitInfo info = new VoipInitInfo();
info.setAppId(xxx);
info.setRoomId(xxx);
info.setUserId(xxx);
//BusinessDataStorage.getInstance().getAppToken(); IM登录成功后这样获取token
info.setAppToken(xxx);
info.setRoomSecret(xxx);

CLVideoLib.Builder videoBuilder = CLVideoLib.create();
videoBuilder.builder(context, "您的APPID");
// 设置 IP 地址 私有化部署可以设置
// CLVoiceLib.getInstance().setTcpHost("xxx.xxx.xxx.xxx");
// 设置端口号
// CLVoiceLib.getInstance().setTcpPort(xxxx);
// 语音模块初始化
CLVideoLib.getInstance().init(videoBuilder);
// 初始化房间信息
CLVideoLib.getInstance().initRoomParam(info);
VoipListener voipListener = new VoipListener(){

    @Override
    public void initSuccess() {
        // 初始化成功
    }

    @Override
    public void initError(String msg) {
        // 初始化失败
    }

    @Override
    public void messageError(int errorCode, Object errorInfo) {
        // 错误信息
    }

    @Override
    public void micAction(boolean isUp) {
        // 上下麦
    }

    @Override
    public void loginAction(boolean success, String msg) {
        // 登录动作回调
    }

    @Override
    public void logoutAction(boolean success, String msg) {
        // 登出动作回调
    }

    @Override
    public void loginRoomNotify(LoginNotify loginRoomNotify, String msg) {
        // 用户进入房间回调
    }

    @Override
    public void logoutRoomNotify(LogoutNotify logoutRoomNotify, String msg) {
        // 用户离开房间回调
    }

    @Override
    public void onReceive(VideoStreamInfo videoStreamInfo) {

    }

    @Override
    public void onSend(boolean status, int msgCode, Object msg) {

    }

    @Override
    public void onMicList(List<MicInfo> userMicList) {
        // 查询麦上所有人回调
    }
};
// 观察 Voip 监听器
CLVideoLib.getInstance().observeVoipListener(voipListener, true);
CLVideoLib.getInstance().login(true,false);

销毁时移除监听回调

// 移除 Voip 监听器
CLVideoLib.getInstance().observeVoipListener(voipListener, false);
CLVideoLib.getInstance().onDestroy();

2.1.2 用户登录(App 用户为已初始化状态时调用)

// 设置您是否为呼出方 callOut == true 为呼出方, callOut == false 则为接收方
// 如果是游戏房间创建者 callOut = true 其他用户进入房间的 callOut = false
// Param2 是否是实时视频
CLVideoLib.getInstance().login(callOut, false);

2.1.3 用户登出

CLVideoLib.getInstance().logout();

2.1.4 音量操作

设置音量监听

// 初始化之后,设置此方法才有效的
// 音量等级监听
CLVoiceLib.getInstance().setVolumeCallback(new VolumeCallback() {
    @Override
    public void onVolumeLevel(String userId, int level) {
        
    }
});
// 音量大小监听
CLVoiceLib.getInstance().setVolumeSizeCallback(new VolumeSizeCallback() {
    @Override
    public void onVolumeLevel(String userId, float volume) {
		
    }
});

音频流操作接口(静音,闭麦操作)

// 开始拉取音频流接口
CLVoiceLib.getInstance().startPull();

// 开始推送音频流接口
CLVoiceLib.getInstance().startPush();

// 停止拉取音频流接口
CLVoiceLib.getInstance().stoptPull();

// 停止拉取某人音频流接口
CLVoiceLib.getInstance().stopPull(userId);

// 停止推送音频流接口
CLVoiceLib.getInstance().stopPush();

调整收听音量

// @param userId 您要调整的麦上人的接收的声音
// @param volume 声音大小,最大值为 1, 当设置 0 是则为静音
CLVoiceLib.getInstance().adjustUserVolume(userId, volume);

2.1.5 上下麦操作

// 上麦接口
CLVideoLib.getInstance().micActionUp();
// 下麦接口
CLVideoLib.getInstance().micActionDown();

2.1.6 查询当前麦上所有人

CLVideoLib.getInstance().queryMicList();


// 设置查询麦上所有人监听
@Override
public void onMicList(List<MicInfo> userMicList) {
    StringBuilder micUp = new StringBuilder();
    for (MicInfo userMicInfo : userMicList) {
        if (VoiceConstant.MIC_TYPE_UP == userMicInfo.getMicStatus()) {
            micUp.append(userMicInfo.getUserId());
            micUp.append(":");

            if (!userList.contains(userMicInfo.getUserId())) {
                userList.add(userMicInfo.getUserId());
            }
        }
    }
    if (micUp.toString().length() > 0) {
        micUp.append("在麦上");
        MLog.i(micUp.toString());
        print(micUp.toString());
    } else {
        print("无人上麦");
    }
}

注:可以配合调整收听音量修改所有人的接收音量

2.1.7 录音操作

String audioPath = "";
// 当前 Activity 布局文件的 rootView
ConstraintLayout rootLayout = findViewById(R.id.root_view);
// 录音按钮
Button audioButton = findViewById(R.id.btn_record);
// 用于显示录音音频路径
TextView tvAudioPath = findViewById(R.id.tv_audio_path);
// 录音功能初始化
AudioRecorderPanel audioRecorderPanel= new AudioRecorderPanel(context);
// 设置录音最大时长
audioRecorderPanel.setMaxDuration(60);//60秒
// 将 AudioorderPanel 附加到 button 上
audioRecorderPanel.attach(rootLayout, audioButton);
audioRecorderPanel.setRecordListener(new AudioRecorderPanel.OnRecordListener(){
    @Override
    public boolean onEnableRecord() {
        return true;
    }

    // audioFile 音频文件路径
    // duration 音频时长
    @Override
    public void onRecordSuccess(String audioFile, int duration) {
        
        audioPath = s;
        tvAudioPath.setText(audioPath);
    }

    @Override
    public void onRecordFail(String s) {

    }
    
    // 开始录音 START
    // 录音中 RECORDING
    // 用户准备取消 TO_CANCEL,
    // 最长录音时间快到 TO_TIMEOUT,
    @Override
    public void onRecordStateChanged(AudioRecorderPanel.RecordState recordState) {
        
    }
});

2.1.8 播放录音操作

// 播放录音按钮
Button playAudioButton = findViewById(R.id.btn_play_record);
playAudioButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // Param1:context 上下文
        // Param2:audioPath 录音操作时保存的音频路径
        AudioPlayManager.getInstance().startPlay(context, audioPath, new IAudioPlayListener(){
            @Override
            public void onStart(String s) {

            }

            @Override
            public void onStop(String s) {

            }

            @Override
            public void onComplete(String s) {

            }
        });
    }
});

2.2 视频通话模块

2.2.1 初始化

// 视频初始化信息
private VoipInitInfo info = new VoipInitInfo();
info.setAppId(xxx);
info.setRoomId(xxx);
info.setUserId(xxx);
//BusinessDataStorage.getInstance().getAppToken(); IM登录成功后这样获取token
info.setAppToken(xxx);
info.setRoomSecret(xxx);

// 视频模块构造器
CLVideoLib.Builder videoBuilder = CLVideoLib.create();
videoBuilder.builder(context, "您的APPID");
// 设置 IP 地址 私有化部署可以需要设置
// CLVoiceLib.getInstance().setTcpHost("xxx.xxx.xxx.xxx");
// 设置端口号
// CLVoiceLib.getInstance().setTcpPort(xxxx);
// 初始化视频模块
CLVideoLib.getInstance().init(videoBuilder);
// 初始化房间信息
CLVideoLib.getInstance().initRoomParam(info);
VoipListener voipListener = new VoipListener(){
    @Override
    public void initSuccess() {

    }

    @Override
    public void initError(String msg) {

    }

    @Override
    public void messageError(int errorCode, Object errorInfo) {

    }

    @Override
    public void micAction(boolean isUp) {

    }

    @Override
    public void loginAction(boolean success, String msg) {

    }

    @Override
    public void logoutAction(boolean success, String msg) {

    }

    @Override
    public void loginRoomNotify(LoginNotify loginRoomNotify, String msg) {

    }

    @Override
    public void logoutRoomNotify(LogoutNotify logoutRoomNotify, String msg) {

    }

    @Override
    public void onReceive(VideoStreamInfo videoStreamInfo) {
        if (videoStreamInfo.getData() != null) {
            Log.d("VideoActivity", "onReceive length=" + videoStreamInfo.getData().length);
            // 此处将数据流转码后更新到您自己的组件即可,详情请看视频流解码
            PlayerSurfaceView.decodeH264(videoStreamInfo.getData());
        }
    }

    @Override
    public void onSend(boolean status, int msgCode, Object msg) {

    }

    @Override
    public void onMicList(List<MicInfo> userMicList) {

    }
};
CLVideoLib.getInstance().observeVoipListener(voipListener, true)
// RecordSurfaceView 是我们提供给您的, 若您不想使用我们提供的可以自定义 SurfaceView
RecordSurfaceView.initCamera(context, null, true, CLVideoProfile.VIDEO_PROFILE_HD1080P);

销毁时移除监听回调:

CLVideoLib.getInstance().observeVoipListener(voipListener, false)
CLVideoLib.getInstance().onDestroy();

2.2.2 用户登录(App 用户为已初始化状态时调用)

// 设置您是否为呼出方 callOut == true 为呼出方, callOut == false 则为接收方
// Param2 是否是实时视频
CLVideoLib.getInstance().login(callOut, true);

2.2.3 用户登出

CLVideoLib.getInstance().logout();


2.2.4 视频视频质量

VIDEO_PROFILE_LOWEST(0), //分辨率 160x90 码率 120k
VIDEO_PROFILE_LOW(1), //分辨率 480x270 码率 525k
VIDEO_PROFILE_STANDARD(2), //分辨率 640x360 码率 900k
VIDEO_PROFILE_HD720P(3), //分辨率 1280x720 码率 1300k

2.2.5 视频流编码(推流)

public class YourSurfaceView extends SurfaceView implements SurfaceHolder.Callback,
        H264Encoder.H264DataListener{
    private H264Encoder h264Encoder = null;
    private int frameCount = 0;

    private Camera.PreviewCallback previewCallback = new Camera.PreviewCallback() {
        @Override
        public void onPreviewFrame(byte[] data, Camera camera) {
            // 此处为判断您的相机是否打开
            if (isCameraOpen) {
                handlePreviewFrame(data);
            }
        }
    };

    private void handlePreviewFrame(byte[] data) {
        if (h264Encoder != null) {
            frameCount++;
            h264Encoder.onFrame(data);
        }
    }
            
    private void init(Context context){
    	SurfaceHolder holder = this.getHolder();
        if (holder != null) {
            holder.addCallback(this);
        }
    }

    public YourSurfaceView(@NonNull Context context) {
        super(context);
    }

    public YourSurfaceView(@NonNull Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public YourSurfaceView(@NonNull Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public void surfaceCreated(@NonNull SurfaceHolder holder) {
        h264Encoder = new H264Encoder(this);
    }

    @Override
    public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {

    }

    @Override
    public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
        if (h264Encoder != null) {
            h264Encoder.close();
            h264Encoder = null;
        }
    }

    @Override
    public void h264Data(byte[] buffer, int length) {
        // 此处为视频推流接口
        CLVideoLib.getInstance().sendVideoStream(buffer, frameCount, 0);
    }
}

2.2.6 视频流解码

public class PlayerSurfaceView extends SurfaceView implements  SurfaceHolder.Callback {
    public PlayerSurfaceView(Context context) {
        super(context);
        init(context);
    }

    public PlayerSurfaceView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    private H264Decoder h264Decoder;

    private void init(Context context){
        this.getHolder().addCallback(this);
    }

    public void decodeH264(byte[] h264) {
        if (h264 == null || h264Decoder == null) {
            return;
        }
        h264Decoder.decodeH264(h264);
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        h264Decoder = new H264Decoder(this.getWidth(), this.getHeight(), this.getHolder());
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        if (h264Decoder!=null){
            h264Decoder.stopCodec();
        }
    }
}

七、历史版本

发布日期 发布版本 更新说明
2021-02-18 V1.0.0 初版发布