视频常用操作和配置

本文将为您介绍如何通过调用API设置视频编码属性。

功能介绍

在进行视频通话或直播时,开发者可以根据需要设置视频相关配置,如视频采集、编码分辨率、视频帧率、码率、镜像模式、视图模式等。

  • 分辨率:

    • 采集分辨率:指摄像头等采集设备提供的画面分辨率。

    • 编码分辨率指经过编码处理的画面的分辨率。

  • 码率:指每秒传输的比特(bit)数,单位为 bps(bit per second)。

  • 帧率:单位时间内视频显示帧数的量度单位,测量单位为“每秒显示帧数”(Frame Per Second,fps)。

设置合适的视频分辨率、帧率和码率可以在音视频场景中为用户提供更好的使用体验。选择合适镜像模式与视图模式则可以让开发者提供个性化的视频显示模式。

示例代码

Android端视频常用操作和配置Android/ARTCExample/BasicUsage/src/main/java/com/aliyun/artc/api/basicusage/VideoBasicUsage/VideoBasicUsageActivity.java

iOS端视频常用操作和配置iOS/ARTCExample/BasicUsage/VideoBasicUsage/VideoBasicUsageVC.swift

前提条件

在设置视频配置之前,请确保达成以下条件:

功能实现

1.设置相机采集配置

ARTC 提供setCameraCapturerConfiguration方法设置相机采集配置,包括相机方向、采集帧率。

说明
  • 需要在启动相机前调用,例如在 startPreview 启动预览或者 joinChannel 加入频道(自动启动相机)前。

  • 调用 enableLocalVideo(false) 关闭摄像头采集会释放摄像头资源,此时可以重新设置。

image

接口定义如下:

/**
 * @brief 采集偏好设置
 * @param cameraCapturerConfiguration 偏好设置
 *      - preference:
 *        - {@link AliRtcCaptureOutputPreference#ALIRTC_CAPTURER_OUTPUT_PREFERENCE_PREVIEW} 高清预览,采集优先保证视频预览质量
 *        - {@link AliRtcCaptureOutputPreference#ALIRTC_CAPTURER_OUTPUT_PREFERENCE_PERFORMANCE} 采集选择最接近推流的分辨率,优先保证设备性能
 *        - {@link AliRtcCaptureOutputPreference#ALIRTC_CAPTURER_OUTPUT_PREFERENCE_AUTO} 自动调整采集分辨率
 *      - cameraDirection: 设置采集方向,前置或后置摄像头
 * @return
 * - 0: 成功
 * - 非0: 表示失败
 * @note 必须在打开摄像头之前设置,如{@link #startPreview},{@link #joinChannel}之前设置
 */
public abstract int setCameraCapturerConfiguration(AliEngineCameraCapturerConfiguration cameraCapturerConfiguration);

相关配置如下:

参数

类型

描述

preference

AliRtcCaptureOutputPreference

采集偏好。

  • CAPTURER_OUTPUT_PREFERENCE_AUTO:SDK 自动调整。

  • CAPTURER_OUTPUT_PREFERENCE_PERFORMANCE:性能优先,根据编码参数设置最接近的配置。

  • CAPTURER_OUTPUT_PREFERENCE_PREVIEW:优先保证预览,选择较高的摄像头输入参数。

cameraDirection

AliRtcCameraDirection

相机方向(前置/后置)。

fps

int

采集帧率。

默认值为-1,表示使用编码配置的 fps,SDK 内部为 15。

cameraCaptureProfile

AliRtcCameraCaptureProfile

指定视频采集的特定分辨率。

  • 默认,跟随编码器设置。

  • 1080p。

textureEncode

int

(仅 Android)是否使用纹理编码。

cameraTextureCapture

int

(仅 Android)摄像头是否开启纹理采集。

接口调用示例如下:

Android

AliRtcEngine.AliEngineCameraCapturerConfiguration config = new AliRtcEngine.AliEngineCameraCapturerConfiguration();
config.preference = AliRtcEngine.AliRtcCaptureOutputPreference.ALIRTC_CAPTURER_OUTPUT_PREFERENCE_AUTO;
config.cameraCaptureProfile = AliRtcEngine.AliRtcCameraCaptureProfile.ALIRTC_CAMERA_CAPTURER_PROFILE_DEFAULT;
config.cameraDirection = AliRtcEngine.AliRtcCameraDirection.CAMERA_FRONT;
config.fps = 30;
mAliRtcEngine.setCameraCapturerConfiguration(config);

iOS

let cameraCaptureConfig: AliRtcCameraCapturerConfiguration = AliRtcCameraCapturerConfiguration()
cameraCaptureConfig.preference = .auto
cameraCaptureConfig.cameraCaptureProfile = .profileDefault
cameraCaptureConfig.cameraDirection = .front
cameraCaptureConfig.fps = 30
engine.setCameraCapturerConfiguration(cameraCaptureConfig)

2.设置视频编码配置

image

ARTC 提供setVideoEncoderConfiguration接口用于配置视频编码属性,包含视频分辨率、帧率、码率、关键帧间隔等影响视频质量的参数设置。开发者可以通过设置视频编码属性,控制视频流在不同网络条件下的展示方式。

说明

setVideoEncoderConfiguration在加入频道前后均可设置,如果在一次通话过程中仅需要设置一次,推荐在加入频道前设置。

相关配置如下:

参数名

类型

描述

dimensions

AliRtcVideoDimensions

视频分辨率,视频分辨率,默认值640x480,最大值1920x1080。

frameRate

int

视频编码帧率,默认值15, 最大值30。

bitrate

int

视频编码码率(kbps),默认值为 512。设置为0表示由SDK内部根据视频分辨率和码率计算合适的编码码率。

码率设置应根据分辨率和帧率有对应的合理范围,该值设置在合理范围内有效,否则SDK会自动调节码率到有效值。码率与分辨率、帧率对应关系请参考代码注释。

keyFrameInterval

int

关键帧间隔,单位毫秒。默认值0,表示SDK内部控制关键帧间隔。

说明

互动直播场景、需要和小程序互通场景必须设置。

forceStrictKeyFrameInterval

boolean

是否强制编码器严格按照设置的关键帧间隔产生关键帧,默认值false。

  • false:表示编码器会响应他人入会等关键帧请求,关键帧间隔和设置的值不严格匹配。

  • true:表示编码器不响应其他关键帧请求,严格按照设置的值产生关键帧。可能会造成订阅者首帧变慢。

mirrorMode

AliRtcVideoEncoderMirrorMode

编码视频镜像模式,控制推流视频是否镜像。

说明

设置镜像可以使用setVideoMirrorMode接口。

orientationMode

AliRtcVideoEncoderOrientationMode

编码视频旋转模式。

  • Adaptive:自适应,与采集视频保持一致。

  • FixedLandscapre:固定横屏。

  • FixedPortrait:固定竖屏。

rotationMode

AliRtcRotationMode

视频旋转角度(0/90/180/270)。

codecType

AliRtcVideoCodecType

编解码器类型。

  • default:使用设备默认设置。

  • software:软件编码。

  • hardware:硬件解码。

  • hardwareTexture:硬件纹理编码。

encodeCodecType

AliRtcVideoEncodeCodecType

视频编码格式(系统默认/ H264/H265)。

seiForceFrontIFrame

int

SEI发送前强制I帧。

-1表示使用默认值,0表示不强制,1表示强制(默认值)。

接口调用示例如下:

Android

AliRtcEngine.AliRtcVideoEncoderConfiguration aliRtcVideoEncoderConfiguration = new AliRtcEngine.AliRtcVideoEncoderConfiguration();
aliRtcVideoEncoderConfiguration.dimensions = new AliRtcEngine.AliRtcVideoDimensions(720, 1280);
aliRtcVideoEncoderConfiguration.frameRate = 20;
aliRtcVideoEncoderConfiguration.bitrate = 1200;
aliRtcVideoEncoderConfiguration.keyFrameInterval = 2000;
aliRtcVideoEncoderConfiguration.orientationMode = AliRtcVideoEncoderOrientationModeAdaptive;
mAliRtcEngine.setVideoEncoderConfiguration(aliRtcVideoEncoderConfiguration);

iOS

let config = AliRtcVideoEncoderConfiguration()
config.dimensions = CGSize(width: 720, height: 1280)
config.frameRate = 20
config.bitrate = 1200
config.keyFrameInterval = 2000
config.orientationMode = AliRtcVideoEncoderOrientationMode.adaptive
engine.setVideoEncoderConfiguration(config)

3.切换相机方向

Android 和 iOS 设备通常具有前置和后置摄像头。SDK 默认使用前置摄像头,如果需要修改,请调用setCameraCaptureConfiguration接口在开启摄像头前进行设置。如果已经开启摄像头,需要切换摄像头则可以调用switchCamera接口。

/**
 * @brief 切换前后摄像头
 * @return
 * - 0: 成功
 * - 非0: 失败
 * @note 只有iOS和android提供这个接口
 */
public abstract int switchCamera();

接口调用示例如下:

Android

mSwitchCameraBtn.setOnClickListener(v -> {
    if(mAliRtcEngine != null) {
        mAliRtcEngine.switchCamera();
    }
});

iOS

@IBAction func onCameraDirectionChanged(_ sender: UISegmentedControl) {
    rtcEngine?.switchCamera()
}

4.开关摄像头

在音视频通话中实现摄像头开关功能,ARTC 提供两个接口,muteLocalCamreaenableLocalVideo

接口

muteLocalCamera

enableLocalVideo

实现原理

发送数据替换为黑帧。

直接停止摄像头硬件采集,释放相关资源。

关闭摄像头现象

本地预览正常显示,远端用户黑屏。

本地预览和远端均停留在最后一帧。

特点

  • 保持视频流连接:接收端不会感知到视频中断。

  • 快速切换:无需频繁启停摄像头硬件,响应延迟低。适合需要动态启停摄像头场景。

  • 释放摄像头资源,

  • 切换较慢:需要开启/关闭摄像头,响应有延时。 适合长期关闭摄像头场景,例如纯音频场景。

4.1. 停止或恢复视频数据发送

image

ARTC 提供muteLocalCamera接口实现禁视频功能,在保留视频采集/编码/传输通道运行的前提下,向远端发送全黑视频帧(本地预览保持正常画面)。接口定义如下:

/**
 * 是否将停止本地视频数据发送
 * @param mute     true表示视频数据发送黑帧;false表示恢复正常
 * @param track    只支持{@link AliRtcVideoTrack#AliRtcVideoTrackCamera}
 * @return
 * - 0: 成功
 * - 非0: 失败
 * @note 发送黑色的视频帧。本地正常预览。采集、编码、发送模块仍然工作,只是视频内容被替换为黑色帧
 */
public abstract int muteLocalCamera(boolean mute, AliRtcVideoTrack track);
/**
 * @brief 对端用户发送视频黑帧数据发送通知
 * @param uid 执行muteVideo的用户ID
 * @param isMute
 * - true: 推流黑帧
 * - false: 正常推流
 * @note 该接口用于对端用户发送视频黑帧数据时的回调
 */
public void onUserVideoMuted(String uid ,boolean isMute){}

代码示例如下:

Android

开关摄像头采集:

if(!isMutedCamera) {
    mAliRtcEngine.muteLocalCamera(true, AliRtcEngine.AliRtcVideoTrack.AliRtcVideoTrackCamera);
    mPublishVideoBtn.setText(R.string.resume_pub_video);
    isMutedCamera = true;
} else {
    mAliRtcEngine.muteLocalCamera(false, AliRtcEngine.AliRtcVideoTrack.AliRtcVideoTrackCamera);
    mPublishVideoBtn.setText(R.string.stop_pub_video);
    isMutedCamera = false;
}

远端监听回调:

@Override
public void onUserVideoMuted(String uid ,boolean isMute){
    handler.post(new Runnable() {
        @Override
        public void run() {
            ToastHelper.showToast(VideoBasicUsageActivity.this, "remote user uid:" + uid + " camera mute:" + isMute, Toast.LENGTH_SHORT);
        }
    });
}

iOS

开关摄像头采集:

@IBAction func onVideoMuteSwitched(_ sender: UISwitch) {
    if sender.isOn {
        // 黑帧
        rtcEngine?.muteLocalCamera(false, for: AliRtcVideoTrack.camera)
    } else {
        rtcEngine?.muteLocalCamera(true, for: AliRtcVideoTrack.camera)
    }
}

远端监听回调

extension VideoBasicUsageVC: AliRtcEngineDelegate {
    func onUserVideoMuted(_ uid: String, videoMuted isMute: Bool) {
        "onUserVideoMuted: user id \(uid) video muted: \(isMute)".printLog()
    }
}

4.2. 开关相机采集

image

通过 enableLocalVideo 接口实现全局性控制本地视频采集设备(如摄像头)的启停状态,直接影响视频数据流的生成与传输。

/**
 * @brief 禁用或启用本地视频采集
 * @param enabled
 * - true : 启用本地视频采集
 * - false : 禁用本地视频采集
 * @return
 * - 0 : 成功
 * - < 0 : 失败
 * @note 默认为开启状态, 通过监听 {@link AliRtcEngineNotify#onUserVideoEnabled} 获取用户是否禁用或启用本地视频采集状态。
 */
public abstract int enableLocalVideo(boolean enabled);
/**
 * @brief 对端用户关闭相机流采集发送通知
 * @param uid 执行EnableLocalVideo的用户ID
 * @param isEnable
 * - true: 打开相机流采集
 * - false: 关闭相机流采集
 * @note 该接口用于对端用户关闭相机流采集时的回调
 */
public void onUserVideoEnabled(String uid, boolean isEnable){}

代码调用示例:

Android

开关相机

mCameraSwitchBtn = findViewById(R.id.camera_control_btn);
mCameraSwitchBtn.setOnClickListener(v -> {
    if(mAliRtcEngine != null) {
        if(isEnableCamera) {
            mAliRtcEngine.enableLocalVideo(false);
            isEnableCamera = false;
            mCameraSwitchBtn.setText(R.string.camera_on);
        } else {
            mAliRtcEngine.enableLocalVideo(true);
            isEnableCamera = true;
            mCameraSwitchBtn.setText(R.string.camera_off);
        }
    }
});

远端监听回调

@Override
public void onUserVideoEnabled(String uid, boolean isEnable) {
    handler.post(new Runnable() {
        @Override
        public void run() {
            ToastHelper.showToast(VideoBasicUsageActivity.this, "remote user uid:" + uid + " camera enable:" + isEnable, Toast.LENGTH_SHORT);
        }
    });
}

iOS

开关相机采集

@IBAction func onCameraSwitch(_ sender: UISwitch) {
    if sender.isOn {
        rtcEngine?.enableLocalVideo(true)
    } else {
        rtcEngine?.enableLocalVideo(false)
    }
    updateCaptureUIVisibility()
}

远端监听回调

extension VideoBasicUsageVC: AliRtcEngineDelegate {
    func onUserVideoEnabled(_ uid: String?, videoEnabled isEnable: Bool) {
        "onUserVideoEnabled: user id \(uid ?? "invalid uid") video enable: \(isEnable)".printLog()
    }
}

5. 开启或关闭预览

ARTC 提供startPreviewstopPreview接口来控制本地预览的启停。

注意:

  • 预览前需要调用setLocalViewConfig为本地预览画面设置渲染视图。

  • SDK 加入频道默认会开启预览,如果需要在入会前开启预览可以提前调用startPreview

  • 调用关闭预览后,本端预览画面会停留在最后一帧。

开启预览。

image

代码示例如下:

Android

启动预览:

private void startPreview() {
    if (mAliRtcEngine != null) {
        ViewGroup.LayoutParams layoutParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);

        // 设置本地视图
        if (mLocalVideoCanvas == null) {
            mLocalVideoCanvas = new AliRtcEngine.AliRtcVideoCanvas();
            SurfaceView localSurfaceView = mAliRtcEngine.createRenderSurfaceView(VideoBasicUsageActivity.this);
            if (localSurfaceView != null) {
                localSurfaceView.setZOrderOnTop(true);
                localSurfaceView.setZOrderMediaOverlay(true);
                fl_local.addView(localSurfaceView, layoutParams);
                mLocalVideoCanvas.view = localSurfaceView;
                try {
                    mAliRtcEngine.setLocalViewConfig(mLocalVideoCanvas, AliRtcVideoTrackCamera);
                } catch (Exception e) {
                    e.printStackTrace(); // Handle potential exceptions
                }
            }
        }
        // 开始预览
        mAliRtcEngine.startPreview();
    }
}

关闭预览:

mAliRtcEngine.stopPreview();
mAliRtcEngine.setLocalViewConfig(null, AliRtcVideoTrackCamera);
mAliRtcEngine.leaveChannel();
mAliRtcEngine.destroy();
mAliRtcEngine = null;

iOS

启动预览:

func startPreview() {
    let seatView = self.createSeatView(uid: self.userId)
    
    let canvas = AliVideoCanvas()
    canvas.view = seatView.canvasView
    canvas.renderMode = .auto
    canvas.mirrorMode = .onlyFrontCameraPreviewEnabled
    canvas.rotationMode = ._0
    
    self.rtcEngine?.setLocalViewConfig(canvas, for: AliRtcVideoTrack.camera)
    self.rtcEngine?.startPreview()
}

关闭预览:

self.rtcEngine?.stopPreview()
self.rtcEngine?.leaveChannel()
AliRtcEngine.destroy()
self.rtcEngine = nil

6. 设置镜像模式

ARTC 提供setVideoMirrorMode接口控制本地视频的预览画面镜像与推流画面镜像行为,支持运行时动态调整,适用于音视频通话、直播等场景。

/**
 * @brief 设置预览和推流镜像能力
 * @param mirrorMode 设置镜像的模式
 * @return
 * - 0: 设置成功
 * - <0: 设置失败
 *  - AliRtcErrInner: SDK内部状态错误,需检查是否创建SDK实例成功
 *
 * @note
 * - 此接口在入会前和入会后均可以动态设置,SDK内部会记录状态,并在可以操作预览及编码的时候对视频进行操作;
 * - 使用此接口的优先级会高于setLocalViewConfig&setVideoEncoderConfiguration
 * - 此接口与setLocalViewConfiguration&setVideoEncoderConfiguration里面的mirror重合,建议只有一个方式
 */
public abstract int setVideoMirrorMode(AliRtcVideoPipelineMirrorMode mirrorMode);

镜像模式如下:

枚举值

描述

AliRtcVideoPipelineMirrorModeNoMirror

预览和编码均关闭镜像。

AliRtcVideoPipelineMirrorModeBothMirror

预览和编码均打开镜像(默认)。

AliRtcVideoPipelineMirrorModeOnlyPreviewMirror

仅预览打开镜像。

AliRtcVideoPipelineMirrorModeOnlyPublishMirror

仅推流打开镜像。

代码示例如下:

Android

mMirrorSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
    @Override
    public void onItemSelected(AdapterView<?> adapterView, View view, int position, long id) {
        if(mAliRtcEngine != null) {
            AliRtcEngine.AliRtcVideoPipelineMirrorMode mirrorMode = AliRtcEngine.AliRtcVideoPipelineMirrorMode.values()[position];
            mAliRtcEngine.setVideoMirrorMode(mirrorMode);
        }
    }

    @Override
    public void onNothingSelected(AdapterView<?> adapterView) {

    }
});

iOS

let mirrorMode: AliRtcVideoPipelineMirrorMode = {
    switch row {
    case 0: return .bothMirror
    case 1: return .noMirror
    case 2: return .onlyPreviewMirror
    case 3: return .onlyPublishMirror
    default: return .bothMirror
    }
}()
self.rtcEngine?.setVideoMirrorMode(mirrorMode)

7. 设置渲染视图

在显示本地预览和远端用户画面前,需要调用setLocalViewConfigsetRemoteViewConig 接口为待显示画面设置渲染视图。

// 设置本地预览显示视图
public abstract int setLocalViewConfig(AliRtcVideoCanvas viewConfig, AliRtcVideoTrack track);
// 设置远端置顶用户显示视图
public abstract int setRemoteViewConfig(AliRtcVideoCanvas canvas, String uid, AliRtcVideoTrack track);

AliRtcVideoCanvas 常用配置如下:

参数

类型

说明

view

View

显示视图(必需)。

renderMode

AliRtcRenderMode

渲染模式。

  • Auto:自动模式。

  • Stretch:拉伸平铺模式,如果外部输入的视频宽高比和推流设置的宽高比不一致时,将输入视频拉伸到推流设置的比例,画面会变形。

  • Fill:填充黑边模式,如果外部输入的视频宽高比和推流设置的宽高比不一致时,将输入视频上下或者左右填充黑边。

  • Clip:裁剪模式,如果外部输入的视频宽高比和推流设置的宽高比不一致时,将输入视频宽或者高进行裁剪,画面内容会丢失。

mirrorMode

AliRtcRenderMirrorMode

镜像模式。

  • 只有前置摄像头镜像。

  • 全部镜像。

  • 全部不镜像。

rotationMode

AliRtcRotationMode

旋转模式(0/90/180/270)。

backgroundColor

int

背景颜色,格式为RGBHex,例如0x000000。

textureId

int

(仅 Android)支持第三方OpenGL ES纹理显示,纹理ID。

textureWidth

int

(仅 Android)支持第三方OpenGL ES纹理显示,纹理宽。

textureHeight

int

(仅 Android)支持第三方OpenGL ES纹理显示,纹理高。

sharedContext

long

(仅 Android)支持第三方OpenGL ES纹理显示,纹理共享上下文。

代码示例如下:

7.1. 设置本地渲染视图

Android

mLocalVideoCanvas = new AliRtcEngine.AliRtcVideoCanvas();
// 获取并设置SurfaceView
SurfaceView localSurfaceView = mAliRtcEngine.createRenderSurfaceView(VideoChatActivity.this);
localSurfaceView.setZOrderOnTop(true);
localSurfaceView.setZOrderMediaOverlay(true);
FrameLayout fl_local = findViewById(R.id.fl_local);
fl_local.addView(localSurfaceView, layoutParams);
mLocalVideoCanvas.view = localSurfaceView;
// 设置本地预览视图
mAliRtcEngine.setLocalViewConfig(mLocalVideoCanvas, AliRtcVideoTrackCamera);
mAliRtcEngine.startPreview();

iOS

let videoView = self.createVideoView(uid: self.userId)

let canvas = AliVideoCanvas()
canvas.view = videoView.canvasView
canvas.renderMode = .auto
canvas.mirrorMode = .onlyFrontCameraPreviewEnabled
canvas.rotationMode = ._0

self.rtcEngine?.setLocalViewConfig(canvas, for: AliRtcVideoTrack.camera)
self.rtcEngine?.startPreview()

Windows

AliEngineVideoCanvas canvas;
/* windows 窗口句柄 */
canvas.view = mHWnd;
mAliRtcEngine.setLocalViewConfig(canvas, AliEngineVideoTrackCamera);

7.2. 设置远端渲染视图

Android

@Override
public void onRemoteTrackAvailableNotify(String uid, AliRtcEngine.AliRtcAudioTrack audioTrack, AliRtcEngine.AliRtcVideoTrack videoTrack){
    handler.post(new Runnable() {
        @Override
        public void run() {
            if(videoTrack == AliRtcVideoTrackCamera) {
                SurfaceView surfaceView = mAliRtcEngine.createRenderSurfaceView(VideoChatActivity.this);
                surfaceView.setZOrderMediaOverlay(true);
                FrameLayout fl_remote = findViewById(R.id.fl_remote);
                if (fl_remote == null) {
                    return;
                }
                fl_remote.addView(surfaceView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
                AliRtcEngine.AliRtcVideoCanvas remoteVideoCanvas = new AliRtcEngine.AliRtcVideoCanvas();
                remoteVideoCanvas.view = surfaceView;
                mAliRtcEngine.setRemoteViewConfig(remoteVideoCanvas, uid, AliRtcVideoTrackCamera);
            } else if(videoTrack == AliRtcVideoTrackNo) {
                FrameLayout fl_remote = findViewById(R.id.fl_remote);
                fl_remote.removeAllViews();
                mAliRtcEngine.setRemoteViewConfig(null, uid, AliRtcVideoTrackCamera);
            }
        }
    });
}

iOS

func onRemoteTrackAvailableNotify(_ uid: String, audioTrack: AliRtcAudioTrack, videoTrack: AliRtcVideoTrack) {
    "onRemoteTrackAvailableNotify uid: \(uid) audioTrack: \(audioTrack)  videoTrack: \(videoTrack)".printLog()
    // 远端用户的流状态
    if audioTrack != .no {
        let videoView = self.videoViewList.first { $0.uidLabel.text == uid }
        if videoView == nil {
            _ = self.createVideoView(uid: uid)
        }
    }
    if videoTrack != .no {
        var videoView = self.videoViewList.first { $0.uidLabel.text == uid }
        if videoView == nil {
            videoView = self.createVideoView(uid: uid)
        }

        let canvas = AliVideoCanvas()
        canvas.view = videoView!.canvasView
        canvas.renderMode = .auto
        canvas.mirrorMode = .onlyFrontCameraPreviewEnabled
        canvas.rotationMode = ._0
        self.rtcEngine?.setRemoteViewConfig(canvas, uid: uid, for: AliRtcVideoTrack.camera)
    }
    else {
        self.rtcEngine?.setRemoteViewConfig(nil, uid: uid, for: AliRtcVideoTrack.camera)
    }

    if audioTrack == .no && videoTrack == .no {
        self.removeVideoView(uid: uid)
        self.rtcEngine?.setRemoteViewConfig(nil, uid: uid, for: AliRtcVideoTrack.camera)
    }
}

Windows

virtual void OnRemoteTrackAvailableNotify(const char *uid, AliEngineAudioTrack audioTrack, AliEngineVideoTrack videoTrack) {
    AliEngineVideoCanvas remote_canvas;
    if (videoTrack == AliEngineVideoTrackCamera
        || videoTrack == AliEngineVideoTrackBoth) {
        RECT rect;
        ::GetWindowRect(mHWnd, &rect);
        remote_canvas.displayView = remoteView;
        remote_canvas.renderMode = AliEngineRenderModeAuto;
        mAliRtcEngine->SetRemoteViewConfig(remote_canvas,uid,AliEngineVideoTrackCamera);
    } else {
        mAliRtcEngine->SetRemoteViewConfig(remote_canvas, uid, AliEngineVideoTrackCamera);
    }
}