DingRTC的基本功能包含初始化SDK、加入频道、本地发布、订阅远端和离开频道等。通过阅读本文,您可以了解DingRTC的基本功能。
操作步骤
初始化SDK。
您需要创建RtcEngine实例,并注册回调。
// 设置日志路径 DingRtcEngine.setLogDirPath(logDir); // 实现并创建listener // 可以按需实现关注的回调,例如: // 入离会、推拉流是否成功可以根据对应回调判断 class MyListener extends DingRtcEngineListener { .... } MyListener listener = new MyListener(); // 创建engine DingRtcEngine engine = DingRtcEngine.createInstance(listener, ""); // 关闭订阅(无订阅需求时,减少资源消耗) engine.subscribeAllRemoteAudioStreams(false); engine.subscribeAllRemoteVideoStreams(false); // 开启推音视频流 // note:也可以在入会后调用 engine.publishLocalAudioStream(true); engine.publishLocalVideoStream(true);
加入频道。
入会需获取鉴权Token,通常由业务方服务端生成,具体方法请参考“使用Token鉴权”
public static class DingRtcAuthInfo { /*! 频道ID。 */ public String channelId; /*! 用户ID。 */ public String userId; /*! 应用ID。 */ public String appId; /*! 令牌。 */ public String token; /*! GSLB地址(建议传空串 - "")。 */ public String gslb; } // 获取鉴权信息,getFromAppServer由客户侧实现 DingRtcEngine.DingRtcAuthInfo authInfo = getAuthInfoFromAppServer(); // 加入频道 // 入会是否成功参见 listener 的 onJoinChannelResult 回调 engine.joinChannel(authInfo, "张三");
参数
描述
appId
应用ID,在控制台应用管理页面创建和查看。
channelId
频道ID。1~64位,由大小写字母、数字、下划线(_)、短划线(-)组成。
userId
用户ID。1~64位,由大小写字母、数字、下划线(_)、短划线(-)组成。
说明同一个用户ID在其他端登录,先入会的端会被后入会的端踢出频道。
token
频道鉴权令牌。
gslbServer
服务地址,可为空,默认值为:
"https://gslb.dingrtc.com"
,请您通过业务服务器下发到客户端SDK,不建议您将该地址固化在客户端代码。发布本地音频流。
入会之后,SDK默认不推流,需主动调用API开启推流(可在入会前调用)。
开启音频推流
// 开启推音频,前面调用过可不用再调用(支持入会前调用) engine.publishLocalAudioStream(true);
外部音频推流
如果是Linux服务器或没有麦克风声音采集设备,可使用外部音频源接口来推送音频数据。
// 设置推外部音频文件和音频参数 // pcmSampleRate:音频文件采样率,例如:48000(48k采样率) // pcmChannels: 音频文件通道数,例如:1或者2 (单声道或双声道) engine.setExternalAudioSource(true, pcmSampleRate, pcmChannels); // 生成Frame数据 DingRtcEngine.DingRtcAudioFrame frame = new DingRtcEngine.DingRtcAudioFrame(); frame.bytesPerSample = 2; frame.samplesPerSec = pcmSampleRate; frame.numChannels = pcmChannels; frame.buffer = buffer; frame.numSamples = numChannels; frame.timestamp = timestamp; // 推送Frame数据 engine.pushExternalAudioFrame(frame);
通常需要一个单独的线程来循环推送音频数据,同时控制数据保持一定的时间间隔均匀输入给SDK。可类比为麦克风设备,按固定周期采集音频数据输入给SDK。
static class RtcContext { // audio config String pcmFilePath; int pcmSampleRate = 16000; int pcmChannels = 1; int pcmReadFreq = 40; volatile boolean isPushAudioStarted = false; volatile boolean externalAudioFinished = false; Thread externalAudioThread; } private static void startPushAudio(RtcContext context) { if (context.isPushAudioStarted) { return; } context.isPushAudioStarted = true; if (context.engine != null) { // 开启推流 context.engine.publishLocalAudioStream(true); // 设置音频PCM推流 context.engine.setExternalAudioSource(true, context.pcmSampleRate, context.pcmChannels); } context.externalAudioThread = new Thread(() -> { DingRtcEngine.DingRtcAudioFrame frame = new DingRtcEngine.DingRtcAudioFrame(); try (RandomAccessFile file = new RandomAccessFile(context.pcmFilePath, "r")) { long sampleCount = 0; // 16-bit pcm, 2byte int bytesPerSample = 2; int samplesToRead = context.pcmSampleRate / (1000 / context.pcmReadFreq); int bufferSize = samplesToRead * context.pcmChannels * bytesPerSample; byte[] buffer = new byte[bufferSize]; long delay = 0; long startClock = System.currentTimeMillis(); long lastStatsClock = startClock; while (!context.quit) { int readBytes = file.read(buffer); if (readBytes != bufferSize) { // 读完文件,直接退出 // break; // 如果想循环读 file.seek(0); continue; } frame.bytesPerSample = bytesPerSample; frame.samplesPerSec = context.pcmSampleRate; frame.numChannels = context.pcmChannels; frame.buffer = buffer; frame.numSamples = readBytes / frame.bytesPerSample / frame.numChannels; frame.timestamp = sampleCount * 1000 / context.pcmSampleRate; delay = frame.timestamp; long elapsed = System.currentTimeMillis() - startClock; if (delay - elapsed > 5) { sleep(delay - elapsed); } if (frame.numSamples > 0) { context.engine.pushExternalAudioFrame(frame); sampleCount += frame.numSamples; } long elpasedStats = System.currentTimeMillis() - lastStatsClock; if (elpasedStats >= 2000) { log("[Demo] audioPushThread pushExternalAudioFrame sampleCount:", sampleCount); lastStatsClock = System.currentTimeMillis(); } } } catch (Exception e) { log("[Demo] pushAudio error:", e.toString()); } finally { context.externalAudioFinished = true; } }, "pub-audio"); context.externalAudioThread.start(); }
停止音频推流
// 关闭外部音频源 engine.setExternalAudioSource(false, pcmSampleRate, pcmChannels); // 关闭音频推流 engine.publishLocalAudioStream(false);
发布本地视频流。
入会之后,SDK默认不推流,需主动调用API开启推流(可在入会前调用)。
开启视频推流
// 开启推视频,前面调用过可不用再调用(支持入会前调用) engine.publishLocalVideoStream(true);
外部视频推流
如果是Linux服务器或没有摄像头视频采集设备,可使用外部视频源接口来推送视频数据。
// 设置推外部视频文件和流类型 // track 仅支持 DingRtcVideoTrackCamera 和 DingRtcVideoTrackScreen // 外部推流不支持 DingRtcVideoTrackBoth engine.setExternalVideoSource(true, DingRtcEngine.DingRtcVideoTrack.DingRtcVideoTrackCamera); // 生成Frame数据 DingRtcEngine.DingRtcVideoFrame frame = new DingRtcEngine.DingRtcVideoFrame(); frame.format = context.pixelFormat; frame.width = context.videoWidth; frame.height = context.videoHeight; frame.lineSize[0] = context.videoWidth; frame.lineSize[1] = context.videoWidth >> 1; frame.lineSize[2] = context.videoWidth >> 1; frame.rotation = context.videoRotation; frame.videoFrameLength = bufferSize; frame.buffer = buffer; // bufferSize // YUV(I420): buffer_size = width * height + half_width * half_height * 2; // RGBA: buffer_size = width * height * 4; // 推送Frame数据 engine.pushExternalAudioFrame(frame);
通常需要一个单独的线程来循环推送视频数据,同时控制数据保持一定的时间间隔均匀输入给SDK。可类比为摄像头设备,按固定周期采集音频数据输入给SDK。
static class RtcContext { // video config String videoYUVFilePath; DingRtcEngine.DingRtcVideoFormat videoPixelFormat = DingRtcEngine.DingRtcVideoFormat.DingRtcVideoI420; int videoWidth = 0; int videoHeight = 0; int videoRotation = 0; int videoFps = 25; volatile boolean isPushVideoStarted = false; volatile boolean externalVideoFinished = false; Thread externalVideoThread; } private static void startPushVideo(RtcContext context) { if (context.isPushVideoStarted) { return; } context.isPushVideoStarted = true; if (context.engine != null) { // 开启推流 context.engine.publishLocalVideoStream(true); // 设置音频PCM推流 context.engine.setExternalVideoSource(true, DingRtcEngine.DingRtcVideoTrack.DingRtcVideoTrackCamera); } context.externalVideoThread = new Thread(() -> { try (RandomAccessFile file = new RandomAccessFile(context.videoYUVFilePath, "r")) { long frameCount = 0; DingRtcEngine.DingRtcVideoFormat pixelFormat = context.videoPixelFormat; int bufferSize = calcBufferSize(pixelFormat, context.videoWidth, context.videoHeight); long delay = 0; long startClock = System.currentTimeMillis(); long lastStatsClock = startClock; byte[] buffer = new byte[bufferSize]; DingRtcEngine.DingRtcVideoFrame frame = new DingRtcEngine.DingRtcVideoFrame(); while (!context.quit) { long readStartClock = System.currentTimeMillis(); int readBytes = file.read(buffer); if (readBytes != bufferSize) { // 读完文件,直接退出 // break; // 如果想循环读 file.seek(0); continue; } frame.format = pixelFormat; frame.width = context.videoWidth; frame.height = context.videoHeight; frame.lineSize[0] = context.videoWidth; frame.lineSize[1] = context.videoWidth >> 1; frame.lineSize[2] = context.videoWidth >> 1; frame.rotation = context.videoRotation; frame.videoFrameLength = bufferSize; frame.buffer = buffer; frame.timestamp = frameCount * 1000 / context.videoFps; delay = frame.timestamp; long elapsed = System.currentTimeMillis() - startClock; if (delay - elapsed > 5) { sleep(delay - elapsed); } context.engine.pushExternalVideoFrame(frame, DingRtcEngine.DingRtcVideoTrack.DingRtcVideoTrackCamera); frameCount++; long elpasedStats = System.currentTimeMillis() - lastStatsClock; if (elpasedStats >= 5000) { log("[Demo] videoPushThread pushExternalVideoFrame frameCount:", frameCount); lastStatsClock = System.currentTimeMillis(); } } } catch (Exception e) { log("[Demo] pushVideo error:", e.toString()); } finally { context.externalVideoFinished = true; } }, "pub-video"); context.externalVideoThread.start(); }
停止视频推流
// 关闭外部视频源 engine.setExternalVideoSource(false, DingRtcEngine.DingRtcVideoTrack.DingRtcVideoTrackCamera); // 关闭音频推流 engine.publishLocalVideoStream(false);
订阅远端音频流。
SDK默认自动订阅远端音视频并拉流,如需关闭拉流请主动调用API关闭拉流(可在入会前调用)。
订阅音频流
SDK当前不支持按指定用户订阅音频流,仅支持订阅音频合流(即所有远端用户的声音合流)。
// 订阅音频流,可在JoinChannel前调用 engine.subscribeAllRemoteAudioStreams(true); // 取消订阅音频流,可在JoinChannel前调用 engine.subscribeAllRemoteAudioStreams(false);
监听音频帧数据
class MyAudioFrameObserver extends DingRtcAudioFrameObserver { ... public boolean onPlaybackAudioFrame(DingRtcAudioFrame frame) { // 远端用户混音后待播放PCM数据 return false; } } // 注册音频数据回调对象 engine.registerAudioFrameObserver(new MyAudioFrameObserver()); // 打开播放数据回调(远端用户声音) engine.enableAudioFrameObserver(true, DingRtcEngine.DingRtcAudioSource.DingRtcAudioSourcePlayback); // 取消音频数据回调 engine.enableAudioFrameObserver(false, DingRtcEngine.DingRtcAudioSource.DingRtcAudioSourcePlayback); engine.registerAudioFrameObserver(null);
监听发言人音量
class MyRtcListener extends DingRtcEngineListener { ... @Override public void onAudioVolumeIndication(DingRtcEngine.DingRtcAudioVolumeInfo[] speakers, int speakerNumber) { // remote user volume info } ... } // 开启音量回调 engine.enableAudioVolumeIndication(300, 3, 1); // 关闭音量回调 engine.enableAudioVolumeIndication(0, 3, 1);
订阅远端视频流。
订阅视频流
// 方式一:订阅音频流(支持在joinChannel前就调用) engine.subscribeAllRemoteAudioStreams(true); // 方式二:订阅某一个人视频流 engine.subscribeRemoteVideoStream(uid, track, true);
监听视频帧数据
class MyVideoFrameObserver extends DingRtcVideoFrameObserver { ... public boolean onCaptureVideoFrame(DingRtcVideoSample dingRtcVideoSample) { // 本地采集视频数据 return false; } public boolean onRemoteVideoFrame(String str, DingRtcVideoTrack dingRtcVideoTrack, DingRtcVideoSample dingRtcVideoSample) { // 远端用户视频数据 return false; } public boolean onPreEncodeVideoFrame(DingRtcVideoTrack dingRtcVideoTrack, DingRtcVideoSample dingRtcVideoSample) { // 本地编码前视频数据(对比本地采集视频数据,此处的数据经过了前处理) return false; } public DingRtcVideoFormat getVideoFormatPreference() { // 目前仅支持I420 return DingRtcVideoFormat.DingRtcVideoI420; } } // 注册视频数据回调对象 engine.registerVideoFrameObserver(new MyVideoFrameObserver()); // 打开视频数据回调(远端用户视频) engine.enableVideoFrameObserver(true, DingRtcEngine.DingRtcVideoObservePosition.DingRtcPositionPreRender); // 取消视频数据回调 engine.enableVideoFrameObserver(false, DingRtcEngine.DingRtcVideoObservePosition.DingRtcPositionPreRender); engine.registerVideoFrameObserver(null);
离开频道。
// 离会 engine.leaveChannel();
销毁引擎。
// 销毁SDK
engine.destroy();