摄像实时直播

适用于实时直播推流场景。

初始化 LiveStreamSession

LiveConfig.Builder builder = new LiveConfig.Builder();
// 配置录制参数
builder.setCameraOrientation(90) // 设置相机旋转参数,通常横屏传0度,竖屏传90度
    .setCameraId(LiveConfig.CAMERA_FACING_FRONT) // 设置所使用的相机
    .setVideoWidth(videoWidth) // 设置输出视频的宽(像素个数)
    .setVideoHeight(videoHeight); // 设置输出视频的高(像素个数)
    .setVideoFPS(20) // 设置帧率
    .setInitVideoBitrate(400000) // 设置视频编码初始码率
    .setMinVideoBitrate(100000) // 设置视频编码最低码率(用于动态码率控制)
    .setMaxVideoBitrate(800000); // 设置视频编码最高码率(用于动态码率控制)
// 初始化录制Session
mSession = new LiveStreamSession(this, builder.build());
mSession.setRtmpEventListener(this);
// 绑定SurfaceHolder
mSession.setSurfaceHolder(surfaceView.getHolder());
// 设置美颜、色彩调整、风格等滤镜链
mSession.setGPUImageFilters(filterList);

创建与销毁

// 初始化录音与捕获视频的device. 在创建Session时仅调用一次;
mSession.setupDevice();
// 配置推流地址以及房间角色(默认为大主播)
mSession.configRtmpSession(pushUrl, mRole);

// 销毁,不再使用时调用
mSession.destroyRtmpSession();
mSession.releaseDevice();

开始、暂停、恢复、结束推流

以下操作LiveStreamSession可调用多次

// 开始推流
mLiveCaptureSession.startStreaming();

// 暂停推流
mLiveCaptureSession.pauseStreaming();

// 恢复推流
mLiveCaptureSession.resumeStreaming();

// 停止推流
mLiveCaptureSession.stopStreaming();

设置滤镜

// 美颜滤镜
beautyFilter = new GPUImageSoftenBeautyFilter();
// 颜色调整滤镜
colorAdjustFilter = new ColorAdjustFilter(context);
// 其他滤镜:如风格滤镜,自定义滤镜
customFilter = new GPUImageFilter();
filterList.add(beautyFilter); // 先加美颜,因为会检测皮肤
filterList.add(colorAdjustFilter);
filterList.add(customFilter);
// 设置滤镜
mSession.setGPUImageFilters(filterList);

具体滤镜参数、取值范围的使用方法详见选择滤镜参数

设置背景音乐

// 设置背景乐
mSession.configBackgroundMusic(boolean enableBGM, String bgmPath, boolean isLooping)
// 设置背景乐区间,单位为微妙(us)
mSession.configBackgroundMusicClip(long clipStartInUSec, long clipDurationInUSec)

设置音量增益

// 设置录音音量增益,取值[0f, 1f]
mSession.setRecordTrackGain(float gain);
// 设置背景音音量增益,取值[0f, 1f]
mSession.setBGMTrackGain(float gain);

摄像头相关

以下接口可在录制过程中,根据用户的点击来分别调用:

// 开启或关闭闪光灯
mSession.toggleFlash(boolean flag);
// 是否可以切换摄像头
mSession.canSwitchCamera();
// 切换摄像头
mSession.switchCamera(int cameraId);
// focus到具体位置
mSession.focusToPoint(int x, int y);
// 获取最大缩放因子
mSession.getMaxZoomFactor();
// 设置缩放因子
mSession.setZoomFactor(int factor);

更多详情,参见demo中的CaptureViewModel类。

双向 RTMP 互动直播

以下接口可以用来实现基于双向 RTMP 协议的双向互动直播:

  • 发起会话
// 发起会话请求
// 参数userStreamUrl为被叫的rtmp推流地址
// 参数userId为被叫的业务ID
mSession.startCallWith(userStreamUrl, userId);

当会话发起成功后,即大主播收到 onConversationStarted 回调事件后,就可以根据对方的userId拼出对方的拉流地址(参考demo做法,具体拉流地址生成规则用户可以自行定义),具体代码如下:

// 初始化预览播放器
// 参数pullUrl为呼叫者(小主播)拉流地址
// binding.remotePreview1 为 BDCloudVideoView 的实例,用于播放连麦小主播的推流画面
if (!binding.remotePreview1.isPlaying()) {
    preview1Playing.set(true);
    binding.remotePreview1.setVideoPath(pullUrl);
    // 开始播放
    binding.remotePreview1.start();
}
  • 结束(或取消)会话
// 结束(或取消)会话
// 参数userStreamUrl为呼叫者(小主播)的rtmp推流地址
// 参数userId为呼叫者(小主播)的业务ID
mSession.stopCallWith(userStreamUrl, userId);

会话正常结束后,还需要停止预览播放器,具体代码如下:

// 注意判断当前是否正在预览,否则会抛状态异常
if (binding.remotePreview1.isPlaying() && binding.remotePreview1.getCurrentPlayingUrl().equals(pullUrl)) {
    // 停止播放
    binding.remotePreview1.stopPlayback();
    preview1Playing.set(false);
}
  • 监听会话状态

在应用层实现OnSessionEventListener接口并注册给LiveStreamSession对象后可以监听会话相关事件。
具体代码如下:

mSession.setRtmpEventListener(this);

实现接口的各个方法:

@Override
public void onSessionConnected() {
    Log.d(TAG, "onSessionConnected: ");
    mSession.startStreaming();
}

@Override
public void onError(int errorCode) {
    Log.d(TAG, "onError: " + errorCode);
}

@Override
public boolean onConversationRequest(final String userId) {
    final Object tmpLock = new Object();
    final AtomicBoolean dlgRet = new AtomicBoolean(false);
    if (requestLocks.size() == 2 && !requestLocks.containsKey(userId)) {
        return dlgRet.get();
    }
    requestLocks.put(userId, tmpLock);
    requestRets.put(userId, dlgRet);
    activity.runOnUiThread(new Runnable() {
        @Override
        public void run() {
            requesting.set(true);
            callerAdapter.notifyDataSetChanged();
        }
    });
    synchronized (tmpLock) {
        try {
            tmpLock.wait();
        } catch (Exception e) {
            // nothing matters
        }
    }
    requestLocks.remove(userId);
    requestRets.remove(userId);
    activity.runOnUiThread(new Runnable() {
        @Override
        public void run() {
            if (requestLocks.size() == 0) {
                requesting.set(false);
            }
            callerAdapter.notifyDataSetChanged();
        }
    });
    return dlgRet.get();
}

// 计时组件,用于统计连麦的时长
private Disposable timeTask;

@Override
public void onConversationStarted(String userId) {
    Log.d(TAG, "onConversationStarted: " + userId);
    playUrl(pullUrlBase + userId);
    if (timeTask == null || timeTask.isDisposed()) {
        timeTask = Flowable.interval(1, TimeUnit.SECONDS)
                .subscribeOn(AndroidSchedulers.mainThread())
                .subscribe(new Consumer<Long>() {
                    @Override
                    public void accept(@NonNull Long aLong) throws Exception {
                        mConversationTime.set(valueToString(aLong));
                    }
                });
    }
}

@Override
public void onConversationFailed(final String userId, final FailureReason failReasonCode) {
    activity.runOnUiThread(new Runnable() {
        @Override
        public void run() {
            requestRets.get(userId).set(false);
            synchronized (requestLocks.get(userId)) {
                requestLocks.get(userId).notify();
            }
            Toast.makeText(activity, "Start conversation failed with reason " + failReasonCode
                    .toString(), Toast.LENGTH_SHORT).show();
        }
    });
}

@Override
public void onConversationEnded(final String userId) {
    stopPlayUrl(pullUrlBase + userId);
    activity.runOnUiThread(new Runnable() {
        @Override
        public void run() {
            // 如果在用户主动取消的时候主播还没有做出应答,则会调用该代码
            if (requestRets.containsKey(userId)) {
                requestRets.get(userId).set(false);
                synchronized (requestLocks.get(userId)) {
                    requestLocks.get(userId).notify();
                }
            }
            if (!preview1Playing.get() && !preview2Playing.get()) {
                if (timeTask != null && !timeTask.isDisposed()) {
                    timeTask.dispose();
                }
                timeShowing.set(false);
            }
            Toast.makeText(activity, "Conversation with user " + userId + " was ended.", Toast
                    .LENGTH_SHORT).show();
        }
    });
}

详细说明请参考OnSessionEventListener接口定义。