智能小视频Smartsmallvideo

    摄像实时直播

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

    初始化 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接口定义。

    上一篇
    后台合成
    下一篇
    手机录屏直播