TRTC 视频旋转场景方案

2022-04-12 15:27:15 浏览数 (1)

场景介绍

在网络会议、双人视频通话等场景时,将手机横屏、竖屏放置场景下,实现本地和远端都可以看到正常的画面效果。

效果演示

当左边手机进行旋转时,即进行横屏推流,右边手机的小画面订阅到的远端流,动态调整view进行适配,避免出现黑边;

当两端手机都进行旋转时,两端都进行横屏推流,各自订阅的远端流画面进行动态调整view;

无论如何旋转手机,两端看到的画面都是正的。

(大画面:本地摄像头; 小画面:远端流)

视频旋转效果演示

实现逻辑

推流端

1)开启 SDK 重力感应,默认就是开启的,如果关闭了,请调用接口打开 SDK 重力感应

2)监听手机旋转角度

3)根据不同的旋转角度,设置视频编码参数,即横屏/竖屏编码

4)发送 SEI 消息,告知房间内其他用户,当前是横屏还是竖屏

5)根据不同的旋转角度,旋转自己订阅的远端流的画面

6)根据不同的旋转角度,来调整 activity 为横屏或竖屏

拉流端

1)收到远端用户的第一帧视频,根据宽高数据,调整渲染远端流的 view 宽高,避免小窗口出现黑边

2)当推流端旋转手机时,可以收到发送的 SEI 消息,根据传递过来的横屏或竖屏,调整渲染远端流的 view 宽高,避免出现黑边

代码逻辑

1)TRTC SDK 版本号

这里可以指定使用 9.5.11347 版本的 SDK,体验下效果。

SDK 发布日志历史:参考文档

代码语言:javascript复制
com.tencent.liteav:LiteAVSDK_TRTC:9.5.11347

2)修改清单文件

android:configChanges 避免重新启动 activity

代码语言:javascript复制
<activity 
    android:name="com.tencent.trtc.videocall.VideoCallingActivity"
    android:configChanges="orientation|keyboard|layoutDirection|screenSize"
    android:screenOrientation="portrait"
    />

3)开启重力感应

setGSensorMode 设置重力感应接口,默认开启,默认值为 TRTCGSensorMode_UIAutoLayout

代码语言:javascript复制
mTRTCCloud.setGSensorMode(TRTCCloudDef.TRTC_GSENSOR_MODE_UIAUTOLAYOUT);

4)监听手机的旋转

当手机旋转时,如:90度、180度、270度时,需要将 activity 调整设置对应的 横屏或竖屏

使用 OrientationEventListener 实时监听手机的旋转角度

代码语言:javascript复制
/**
 * 获取到手机旋转的角度,取四个方向:0、90、180、270度
 * @param orientation 0、90、180、270度
 */
@Override
public void onOrientationChanged(int orientation) {
    // 手机平放,获取不到旋转角度
    if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) {
        return;
    }

    // 获取手机旋转角度 : 0
    if (orientation > 350 || orientation < 10) {
        // 在当前旋转角度下,仅调用一次,切换角度后再去调用
        if (!isCall(0)) {
            // 根据旋转的角度,调整推流编码、发送SEI、调整渲染的view、activity方向
            adjustVideoEnc(0);
        }
    // 获取手机旋转角度 : 90
    } else if (orientation > 80 &amp;&amp; orientation < 100) {
        // 在当前旋转角度下,仅调用一次,切换角度后再去调用
        if (!isCall(90)) {
            // 根据旋转的角度,调整推流编码、发送SEI、调整渲染的view、activity方向
            adjustVideoEnc(90);
        }
    // 获取手机旋转角度 : 180
    } else if (orientation > 170 &amp;&amp; orientation < 190) {
        // 在当前旋转角度下,仅调用一次,切换角度后再去调用
        if (!isCall(180)) {
            // 根据旋转的角度,调整推流编码、发送SEI、调整渲染的view、activity方向
            adjustVideoEnc(180);
        }
    // 获取手机旋转角度 : 270
    } else if (orientation > 260 &amp;&amp; orientation < 280) {
        // 在当前旋转角度下,仅调用一次,切换角度后再去调用
        if (!isCall(270)) {
            // 根据旋转的角度,调整推流编码、发送SEI、调整渲染的view、activity方向
            adjustVideoEnc(270);
        }
    }
}

手机的旋转角度事件,非常频繁,避免重复调用,需要控制下请求:

(这里仅是示例,根据实际需要进行调整逻辑!)

代码语言:javascript复制
/**
 * 在指定旋转角度,是否已经操作了,避免连续在同一个旋转角度操作多次
 * @param mOrientation 0、90、180、270度
 * @return true: 之前一次已经操作过了,不要再次操作了;  false:之前一次没操作过,你去操作吧
 */
public boolean isCall(int mOrientation) {
    Boolean flag = mRotationCall.get(mOrientation);
    if (flag == null) {
        flag = false;
    }

    switch (mOrientation) {
        case 0:
            mRotationCall.put(0, true);
            mRotationCall.put(90, false);
            mRotationCall.put(180, false);
            mRotationCall.put(270, false);
            break;
        case 90:
            mRotationCall.put(0, false);
            mRotationCall.put(90, true);
            mRotationCall.put(180, false);
            mRotationCall.put(270, false);
            break;
        case 180:
            mRotationCall.put(0, false);
            mRotationCall.put(90, false);
            mRotationCall.put(180, true);
            mRotationCall.put(270, false);
            break;
        case 270:
            mRotationCall.put(0, false);
            mRotationCall.put(90, false);
            mRotationCall.put(180, false);
            mRotationCall.put(270, true);
            break;
        default:
            break;
    }
    return flag;
}

5)根据不同的旋转角度,调整编码参数、发送SEI消息、旋转远端用户的画面、activity横竖屏

代码语言:javascript复制
/**
 * 1)推到远端流的编码参数
 *    1.1)如果是竖屏,就设置竖屏的编码参数
 *    1.2)如果是横屏,就设置横屏的编码参数
 * 2)发送 SEI 消息
 *    1.1)如果当前是横屏推流,就告诉房间内其他人,我当前在推横屏
 *        1.1.1)房间内其他用户收到后,调整对应用户的 view 的宽高比,避免出现黑边
 *    1.2)如果当前是竖屏推流,就告诉房间内其他人,我当前在推竖屏
 *        1.1.1)房间内其他用户收到后,调整对应用户的 view 的宽高比,避免出现黑边
 * 3)旋转远端用户的画面
 *    3.1)我当前拉取到了其他人的流,我当前切换成 横屏/竖屏 ,需要旋转远端用户的画面,避免方向不一致
 * 4)设置当前 activity 横屏 或 竖屏
 * @param mOrientation 旋转的角度,如:0 90 180 270
 */
public void adjustVideoEnc(int mOrientation) {
    // 本地画面旋转角度设置 0
    TRTCCloudDef.TRTCRenderParams params = new TRTCCloudDef.TRTCRenderParams();
    params.rotation = TRTC_VIDEO_ROTATION_0;
    mTRTCCloud.setLocalRenderParams(params);

    switch (mOrientation) {
        case 0:
            Log.d(TAG, "旋转角度 0");
            // 1)设置推到远端的编码参数
            TRTCCloudDef.TRTCVideoEncParam param = new TRTCCloudDef.TRTCVideoEncParam();
            param.videoResolutionMode = TRTC_VIDEO_RESOLUTION_MODE_PORTRAIT; // 竖屏
            mTRTCCloud.setVideoEncoderParam(param);

            // 2)发送消息给房间内其它用户,说自己调整了横竖屏
            JSONObject buildInfoJson = new JSONObject();
            try {
                buildInfoJson.put("TRTC_VIDEO_RESOLUTION_MODE", "PORTRAIT");
            } catch (Exception e) {
            }
            mTRTCCloud.sendSEIMsg(buildInfoJson.toString().getBytes(), 1);

            // 3)旋转订阅的远端画面
            for (String uid : mRemoteUidList){
                TRTCCloudDef.TRTCRenderParams renderParams = new TRTCCloudDef.TRTCRenderParams();
                renderParams.rotation = TRTCCloudDef.TRTC_VIDEO_ROTATION_0;
                renderParams.fillMode = TRTCCloudDef.TRTC_VIDEO_RENDER_MODE_FIT;  // 默认值fill,这里手动指定fit
                mTRTCCloud.setRemoteRenderParams(uid, TRTC_VIDEO_STREAM_TYPE_BIG, renderParams);
            }

            // 4)设置 activity 竖屏
            VideoCallingActivity.this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
            break;
        case 90:
            Log.d(TAG, "旋转角度 90");
            // 1)设置推到远端的编码参数
            param = new TRTCCloudDef.TRTCVideoEncParam();
            param.videoResolutionMode = TRTC_VIDEO_RESOLUTION_MODE_LANDSCAPE; // 横屏
            mTRTCCloud.setVideoEncoderParam(param);

            // 2)发送消息给房间内其它用户,说自己调整了横竖屏
            buildInfoJson = new JSONObject();
            try {
                buildInfoJson.put("TRTC_VIDEO_RESOLUTION_MODE", "LANDSCAPE");
            } catch (Exception e) {
            }
            mTRTCCloud.sendSEIMsg(buildInfoJson.toString().getBytes(), 1);

            // 3)旋转订阅的远端画面
            for (String uid : mRemoteUidList){
                TRTCCloudDef.TRTCRenderParams renderParams = new TRTCCloudDef.TRTCRenderParams();
                renderParams.rotation = TRTCCloudDef.TRTC_VIDEO_ROTATION_180;
                renderParams.fillMode = TRTCCloudDef.TRTC_VIDEO_RENDER_MODE_FIT;  // 默认值fill,这里手动指定fit
                mTRTCCloud.setRemoteRenderParams(uid, TRTC_VIDEO_STREAM_TYPE_BIG, renderParams);
            }

            // 4)设置 activity 横屏
            VideoCallingActivity.this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
            break;
        case 180:
            Log.d(TAG, "旋转角度 180");
            // 1)设置推到远端的编码参数
            param = new TRTCCloudDef.TRTCVideoEncParam();
            param.videoResolutionMode = TRTC_VIDEO_RESOLUTION_MODE_PORTRAIT; // 竖屏
            mTRTCCloud.setVideoEncoderParam(param);

            // 2)发送消息给房间内其它用户,说自己调整了横竖屏
            buildInfoJson = new JSONObject();
            try {
                buildInfoJson.put("TRTC_VIDEO_RESOLUTION_MODE", "PORTRAIT");
            } catch (Exception e) {
            }
            mTRTCCloud.sendSEIMsg(buildInfoJson.toString().getBytes(), 1);

            // 3)旋转订阅的远端画面
            for (String uid : mRemoteUidList){
                TRTCCloudDef.TRTCRenderParams renderParams = new TRTCCloudDef.TRTCRenderParams();
                renderParams.rotation = TRTCCloudDef.TRTC_VIDEO_ROTATION_180;
                renderParams.fillMode = TRTCCloudDef.TRTC_VIDEO_RENDER_MODE_FIT;  // 默认值fill,这里手动指定fit
                mTRTCCloud.setRemoteRenderParams(uid, TRTC_VIDEO_STREAM_TYPE_BIG, renderParams);
            }

            // 4)设置 activity 竖屏
            VideoCallingActivity.this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
            break;
        case 270:
            Log.d(TAG, "旋转角度 270");
            // 1)设置推到远端的编码参数
            param = new TRTCCloudDef.TRTCVideoEncParam();
            param.videoResolutionMode = TRTC_VIDEO_RESOLUTION_MODE_LANDSCAPE; // 横屏
            mTRTCCloud.setVideoEncoderParam(param);

            // 2)发送消息给房间内其它用户,说自己调整了横竖屏
            buildInfoJson = new JSONObject();
            try {
                buildInfoJson.put("TRTC_VIDEO_RESOLUTION_MODE", "LANDSCAPE");
            } catch (Exception e) {
            }
            mTRTCCloud.sendSEIMsg(buildInfoJson.toString().getBytes(), 1);

            // 3)旋转订阅的远端画面
            for (String uid : mRemoteUidList){
                TRTCCloudDef.TRTCRenderParams renderParams = new TRTCCloudDef.TRTCRenderParams();
                renderParams.rotation = TRTCCloudDef.TRTC_VIDEO_ROTATION_0;
                renderParams.fillMode = TRTCCloudDef.TRTC_VIDEO_RENDER_MODE_FIT;  // 默认值fill,这里手动指定fit
                mTRTCCloud.setRemoteRenderParams(uid, TRTC_VIDEO_STREAM_TYPE_BIG, renderParams);
            }

            // 4)设置 activity 横屏
            VideoCallingActivity.this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
            break;
        default:
            break;
    }
}

6)收到SEI消息、首帧视频

代码语言:javascript复制
/***
 * 收到元旦用户告知,远端用户切换了横屏/竖屏,赶紧调整下 view 的窗口大小
 * @param userId 发送消息的用户
 * @param data 发送的数据
 */
@Override
public void onRecvSEIMsg(String userId, byte[] data) {
    // 解析数据
    Map<String, Object> objectMap = new Gson().fromJson(new String(data), Map.class);
    Object mode = objectMap.get("TRTC_VIDEO_RESOLUTION_MODE");
    if (mode instanceof String) {
        // 横屏
        if (mode.equals("LANDSCAPE")) {
            // 当前收到远端用户告知为横屏,调整该远端用户 view 的大小,以免出现黑边
            // 设置传入的宽高,仅表示 宽 > 高
            adjustRemoteViewResolutionMode(userId, 1, 0);
        // 竖屏
        } else if (mode.equals("PORTRAIT")) {
            // 当前收到远端用户告知为竖屏,调整该远端用户 view 的大小,以免出现黑边
            // 设置传入的宽高,仅表示 宽 < 高
            adjustRemoteViewResolutionMode(userId, 0, 1);
        }
    }
}

/***
 * 收到用户的第一帧视频画面
 * 如果 userId 为空值,代表 SDK 已经开始渲染自己本地的视频画面
 * 如果 userId 不为空,代表 SDK 已经开始渲染远端用户的视频画面
 * @param userId 哪个用户的视频首帧
 * @param streamType 流类型
 * @param width 宽
 * @param height 高
 */
@Override
public void onFirstVideoFrame(String userId, int streamType, int width, int height) {
    /**
     * 不为空,即为远端用户的,也仅仅调整远端用户的 view
     */
    if (userId != null) {
        // 根据首帧收到的宽高,来确定流的分辨率,以便调整 view 的宽高
        // 宽 > 高,说明要 view的 宽 > 高
        // 宽 < 高,说明要 view的 宽 < 高
        adjustRemoteViewResolutionMode(userId, width, height);
    }
}

代码语言:javascript复制
/**
 * 调整远端 view 的大小
 * 当传入的 宽 > 高,说明是要横着展示远端的流,那么 view 的 宽 > 高 来设置
 *    1)如果 view 的 宽 > 高 , 就不做处理
 *    2)如果 view 的 宽 < 高 , 那就把 宽和高 对调下大小
 * 当传入的 宽 < 高,说明是要横着展示远端的流,那么 view 的 宽 < 高 来设置
 *    1)如果 view 的 宽 < 高 , 就不做处理
 *    2)如果 view 的 宽 > 高 , 那就把 宽和高 对调下大小
 * @param userId 对应的远端用户
 * @param width 宽度
 * @param height 高度
 */
private void adjustRemoteViewResolutionMode(String userId, int width, int height) {
    // 获取对应 userId 的下标
    int index = mRemoteUidList.indexOf(userId);
    // index 不等于 -1,说明存在该 userId
    if (index != -1) {
        // 根据 index 获取对应用户的 view
        TXCloudVideoView videoView = mRemoteViewList.get(index);
        // 获取该 userId 对应 view 的 LayoutParams
        ViewGroup.LayoutParams layoutParams = videoView.getLayoutParams();
        // 如果传入过来要求的 宽 > 高,即 横着
        if (width > height) {
            // 如果已经给它的 宽 < 高,那么就要调整他,使它的 宽 > 高,宽高对调即可
            if (layoutParams.width < layoutParams.height) {
                int tempWidth = layoutParams.width;
                layoutParams.width = layoutParams.height;
                layoutParams.height = tempWidth;
                videoView.setLayoutParams(layoutParams);
            }
        // 如果传入过来要求的 宽 < 高,即 竖着
        } else if (width < height) {
            // 如果已经给它的 宽 > 高,那么就要调整他,使它的 宽 < 高,宽高对调即可
            if (layoutParams.width > layoutParams.height) {
                int tempWidth = layoutParams.width;
                layoutParams.width = layoutParams.height;
                layoutParams.height = tempWidth;
                videoView.setLayoutParams(layoutParams);
            }
        }
    }
}

代码示例

当运行demo时,记得修改 GenerateTestUserSig 文件中的 SDKAPPID、SECRETKEY,才能正常运行起来。

文件:DebugsrcmainjavacomtencenttrtcdebugGenerateTestUserSig.java

控制台上获取对应的应用信息控制台上获取对应的应用信息

演示示例代码:如附件

TRTC-API-Example.zip

参考文档

视频画面旋转和缩放:https://cloud.tencent.com/document/product/647/32237

0 人点赞