短视频SDK提供视频合拍功能AliyunIMixRecorder。使用一个已有视频作为样本视频,与摄像头采集的数据按照特定的布局方式(例如左右分屏,上下分屏,画中画等)进行合拍录制。视频合拍是基础录制的功能升级,相比基础录制,视频合拍增加了一个新的本地视频轨道。

版本支持

版本 是否支持
专业版 支持
标准版 支持
基础版 不支持

概念介绍

在以下文档介绍中将提及一些特殊概念,为方便开发者理解,可预先对视频合拍轨道轨道布局的概念做相关了解。

相关类功能

类名 功能
AliyunIMixRecorder 录制功能核心类,包括录制、设置预览、设置特效、设置回调等视频合拍的核心录制功能。
AliyunMixRecorderCreator 工厂类,用于创建合拍录制实例。
AliyunMixMediaInfoParam 合拍参数配置类,包括设置合拍视频的轨道排列及输出路径等参数。
AliyunMixRecorderDisplayParam 合拍轨道配置类,包括设置轨道的布局信息及布局层级等参数。
MediaInfo 录制参数配置类,包括设置视频的宽高、编码器类型、录制采集帧率等录制参数。
RecordCallback 录制回调接口,包括设置录制完成回调、录制进度回调及录制错误回调等。
OnFrameCallBack 相机采集数据回调接口,包括选择预览分辨率的回调、采集帧回调及摄像头开启失败的回调。
OnAudioCallBack 音频回调接口,设置音频pcm数据回调。
AliyunIClipManager 视频录制片段管理接口,包括删除片段、设置录制时长等。

合拍流程

说明 合拍录制功能需要获取摄像头和麦克风权限,否则无法录制。

视频合拍流程与基础录制流程基本相同,主要差别在于配置录制参数时设置输入输出参数以及设置预览View上的差别。

阶段 流程 说明 示例代码
基础 1 创建及销毁录制接口,并配置录制参数。 初始化及参数配置
2 回调设置。 回调设置
3 设置预览View并开启预览。 开启预览
4 创建开始录制片段、取消录制片段、停止录制片段。 开始录制
5 创建结束录制相关信息。 结束录制
进阶 6 配置录制相机控制相关参数(设置摄像头类型、设置闪光灯模式等)及录制片段管理相关参数(配置录制最大或最小时长、删除录制片段、获取录制片段数量等),可按需配置。 录制控制及管理
7 配置美颜、滤镜等录制特效,可按需配置。 设置特效
8 设置拍照或人脸识别。 其他功能

初始化及参数配置

初始化AliyunIMixRecorder类,创建录制接口,并配置录制参数。代码中需要使用的参数详情,请参考接口文档。接口链接请参见相关类功能

初始化
//创建录制接口
AliyunIMixRecorder recorder = AliyunMixRecorderCreator.createAlivcMixRecorderInstance(context);

//销毁录制接口
//不再使用SDK或者程序退出前销毁录制接口,请务必保证不要中途销毁
AliyunIMixRecorder.release();
配置录制参数
//设置录制视频的质量
AliyunIMixRecorder.setVideoQuality(quality);

//设置录制视频的码率
AliyunIMixRecorder.setVideoBitrate(int bitrate);//单位:Kbps

//设置录制视频输出参数
//inputMediaInfo为合成视频的参数(轨道排列及输出视频),outputInfo为输出视频的信息
AliyunIMixRecorder.setMixMediaInfo(AliyunMixMediaInfoParam inputMediaInfo, MediaInfo outputInfo);//相关参数描述请参考AliyunMixMediaInfoParam和MediaInfo接口文档

//设置录制视频的输出路径
AliyunIMixRecorder.setOutputPath(String path);

//设置录制视频的输出GOP
AliyunIMixRecorder.setGop(int gop);//单位:帧数

回调设置

通过设置回调,及时获取音视频处理的进展和状态。代码中需要使用的参数详情,请参考接口文档。接口链接请参见相关类功能

//设置录制回调
AliyunIMixRecorder.setRecordCallBack(RecordCallback callBack);

//设置视频帧采集回调
AliyunIMixRecorder.setOnFrameCallback(OnFrameCallBack callback);

//设置音频采集数据的回调
AliyunIMixRecorder.setOnAudioCallback(OnAudioCallBack callback);

开启预览

预览时,需要设置相对应的SurfaceView容器,同时,在Activity/Fragment的onResume()时进行startPreview,在onPause()进行stopPreview。代码中需要使用的参数详情,请参考接口文档。接口链接请参见相关类功能

//设置预览View
//cameraView参数是相机的视图,videoView参数是本地视频的视图
AliyunIMixRecorder.setDisplayView(SurfaceView cameraView, Surface videoView);

//开始预览
AliyunIMixRecorder.startPreview();

//结束预览
//在Activity/Fragment的onPause()时进行stopPreview
AliyunIMixRecorder.stopPreview();

开始录制

在实际录制过程中,常常不能一次性就能录制完成所需视频,而总是不断的停止、取消和重新录制。停止录制时会生成一个视频片段,而取消录制则不会保留当前录制的视频片段。代码中需要使用的参数详情,请参考接口文档。接口链接请参见相关类功能

开始录制
//开始录制
AliyunIMixRecorder.startRecording();
录制片段
//开始录制
AliyunIMixRecorder.startRecording();

//停止录制,生成一个视频片段
AliyunIMixRecorder.stopRecording();
AliyunIMixRecorder.startRecording();
// 取消录制,当前的视频片段不会保存
AliyunIMixRecorder.cancelRecording();

//继续录制下一个视频片段
AliyunIMixRecorder.startRecording();
AliyunIMixRecorder.stopRecording();

结束录制

结束录制时可生成一个片段拼接的视频或只生成片段视频的配置信息。代码中需要使用的参数详情,请参考接口文档。接口链接请参见相关类功能

//结束录制,并且将录制片段视频拼接成一个视频
AliyunIMixRecorder.finishRecording();

//结束录制,生成片段视频的配置信息(不拼接片段)
AliyunIMixRecorder.finishRecordingForEdit();

录制控制及管理

该模块用于配置录制相机控制相关参数(设置摄像头类型、设置闪光灯模式等)及录制片段管理相关参数(配置录制最大或最小时长、删除录制片段、获取录制片段数量等),可按需配置。代码中需要使用的参数详情,请参考接口文档。接口链接请参见相关类功能

录制摄像头控制
//获取摄像头数量
AliyunIMixRecorder.getCameraCount();

//设置摄像头类型
AliyunIMixRecorder.setCamera(cameraType);

//设置静音录制
AliyunIMixRecorder.setMute(boolean isMute);

//设置传感器角度值
//(非常重要,建议仔细阅读接口文档) 
AliyunIMixRecorder.setRotation(int rotation);

//设置录制视频的角度
//(非常重要,建议仔细阅读接口文档) 
AliyunIMixRecorder.setRecordRotation(int rotation);

//设置摄像头预览参数(闪光灯、对焦模式、Zoom、曝光度),也可参考下面的接口单独设置各预览参数
AliyunIMixRecorder.setCameraParam(CameraParam cameraParam);

//切换摄像头
AliyunIMixRecorder.switchCamera();

//设置闪光灯模式
AliyunIMixRecorder.setLight(FlashType flashType);

//设置Zoom 
AliyunIMixRecorder.setZoom(float rate);

//设置曝光度 
AliyunIMixRecorder.setExposureCompensationRatio(float value);

//设置对焦模式 
AliyunIMixRecorder.setFocusMode(int mode);

//手动对焦 
AliyunIMixRecorder.setFocus(float xRatio, float yRatio);
录制片段管理
//获取片段管理器
AliyunIClipManager manager = AliyunIRecorder.getClipManager();

//设置最大录制时长(总录制时长,非单个片段的最大时长)
manager.setMaxDuration(int maxDurationMs);

//设置最小录制时长(总录制时长,非单个片段的最小时长)
manager.setMinDuration(int minDurationMs);

//删除最后一段片段
manager.deletePart();

//删除指定的片段
manager.deletePart(int index);

//删除所有片段
manager.deleteAllPart();

//获取片段总时长
manager.getDuration();

//获取总的片段数量
manager.getPartCount();

//获取片段路径列表
manager.getVideoPathList();

设置特效

该模块用于配置美颜、滤镜等录制特效,可按需配置。代码中需要使用的参数详情,请参考接口文档。接口链接请参见相关类功能

滤镜

支持自定义滤镜,滤镜的制作方法请参见滤镜及转场

//设置滤镜
AliyunIMixRecorder.applyFilter(effectFilter);

//移除滤镜
//参数路径设置为null表示移除滤镜效果
AliyunIMixRecorder.applyFilter(new EffectFilter(null));
动效滤镜
//设置动效滤镜
AliyunIMixRecorder.applyAnimationFilter(effectFilter);

//移除动效滤镜
AliyunIMixRecorder.removeAnimationFilter(effctFilter);
变速
//设置录制速度
AliyunIMixRecorder.setRate(float rate);
静态贴纸/水印
//添加静态贴纸/水印
AliyunIMixRecorder.addImage(effctImage);

//移除静态贴纸/水印
AliyunIMixRecorder.removeImage(effctImage);

//更新静态贴纸/水印位置
AliyunIMixRecorder.setEffectView(float xRatio,float yRatio,float widthRatio,float heightRatio,EffectBase effectBase);

动态贴纸

支持自定义动态贴纸,动态贴纸的制作方法请参见动图
//添加动态贴纸
AliyunIMixRecorder.addPaster(effectPaster,float sx,float sy,float sw,float sh,float rotation,boolean flip);

//移除动态贴纸
AliyunIMixRecorder.removePaster(effectPaster);

//更新动态贴纸位置
AliyunIMixRecorder.setEffectView(float xRatio,float yRatio,float widthRatio,float heightRatio,effectBase);

高级美颜

视频录制模块,提供了基础的内置美颜功能,同时也支持使用外置的美颜SDK功能,如阿里云美颜特效SDK、相芯科技(FaceUnity)等美颜SDK。内置美颜功能相对比较简单,仅能设置不同的美颜等级;外置美颜SDK通常提供了更为丰富的美颜、美型、美妆美化、滤镜贴纸等功能。
  • 内置美颜
    //设置美颜开关
    AliyunIMixRecorder.setBeautyStatus(boolean on);
    
    //设置美颜程度
    AliyunIMixRecorder.setBeautyLevel(int level);
  • 外置美颜SDK
    想要在短视频SDK中使用外置美颜SDK所提供的特效,则需要提前获取相应外置美颜SDK的权限并将外置美颜SDK集成接入到短视频SDK中。
    • 阿里云美颜特效SDK的集成接入等相关操作请参见美颜特效SDK。特效设置代码示例请参见使用示例
    • FaceUnity的购买、集成、使用等相关操作,请参见FaceUnity
    外置美颜SDK需要获取两个数据用于实现美颜功能:相机纹理ID和相机原始帧数据。以下代码展示如何获取相机纹理ID和相机原始帧数据。
    • 获取相机纹理ID数据
      AliyunIMixRecorder.setOnTextureIdCallback(new OnTextureIdCallBack() {
                  @Override
                  public int onTextureIdBack(int textureId, int textureWidth, int textureHeight, float[] matrix) {
                      if (mBeautyInterface != null) {
                          return mBeautyInterface.onTextureIdBack(textureId, textureWidth, textureHeight, matrix, mControlView.getCameraType().getType());
                      }
                      return textureId;
                  }
      
                  @Override
                  public int onScaledIdBack(int scaledId, int textureWidth, int textureHeight, float[] matrix) {
      
                      return scaledId;
                  }
      
                  @Override
                  public void onTextureDestroyed() {
                      //关于自定义渲染(第三方渲染)销毁gl资源,SDK3.7.8之前版本,GLSurfaceView时可以通过GLSurfaceView.queueEvent来销毁gl资源;SDK3.7.8及之后版本开始,推荐在此回调中统一销毁gl资源
                      if (mBeautyInterface != null) {
                          mBeautyInterface.release();
                          mBeautyInterface = null;
                      }
                  }
              });
    • 获取相机原始帧数据
      AliyunIMixRecorder.setOnFrameCallback(new OnFrameCallBack() {
                  @Override
                  public void onFrameBack(byte[] bytes, int width, int height, Camera.CameraInfo info) {
                      //原始数据回调NV21,此处获取原始数据主要是提供给FaceUnity高级美颜使用
                      if (mBeautyInterface != null) {
                          mBeautyInterface.onFrameBack(bytes, width, height, info);
                      }
                  }
      
                  @Override
                  public Camera.Size onChoosePreviewSize(List<Camera.Size> supportedPreviewSizes,
                                                         Camera.Size preferredPreviewSizeForVideo) {
                      return null;
                  }
      
                  @Override
                  public void openFailed() {
                      
                  }
              });

其他功能

支持录制视频时拍照及人脸识别。代码中需要使用的参数详情,请参考接口文档。接口链接请参见相关类功能

拍照

拍照分为带特效拍照及系统拍照(不带特效),拍照后会通过RecordCallback.onPictureBack(Bitmap)或者RecordCallback.onPictureDataBack(byte[]) 返回数据。
//带特效拍照
AliyunIMixRecorder.takePhoto(boolean needBitmap);

//系统拍照(不带特效)
AliyunIMixRecorder.takePicture(boolean needBitmap);

//设置系统拍照的照片大小(仅系统拍照支持,带特效拍照不支持)
AliyunIMixRecorder.setPictureSize(Camera.Size size);

人脸识别

人脸识别时,需要接入APP自行内置的人脸识别模型文件 ,可参考人脸识别模型文件Demo来设置。
//开启人脸识别
AliyunIMixRecorder.needFaceTrackInternal(boolean need);

//设置人脸识别模型文件
AliyunIMixRecorder.setFaceTrackInternalModelPath(String path);

//设置人脸识别角度
//(非常重要,建议仔细阅读接口文档) 
AliyunIMixRecorder.setFaceDetectRotation(int rotation);

//设置人脸识别数量
//设置内置人脸识别的最大识别数,最大为3个
AliyunIMixRecorder.setFaceTrackInternalMaxFaceCount(int maxFaceCount);

//添加人脸动图
AliyunIMixRecorder.addPaster(EffectPaster effectPaster);

视频合拍代码示例

import android.graphics.Bitmap
import android.os.Bundle
import android.util.Log
import android.view.SurfaceView
import android.widget.ImageView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.aliyun.svideosdk.common.struct.common.VideoDisplayMode
import com.aliyun.svideosdk.common.struct.encoder.VideoCodecs
import com.aliyun.svideosdk.common.struct.recorder.MediaInfo
import com.aliyun.svideosdk.mixrecorder.AliyunIMixRecorder
import com.aliyun.svideosdk.mixrecorder.AliyunMixMediaInfoParam
import com.aliyun.svideosdk.mixrecorder.AliyunMixRecorderDisplayParam
import com.aliyun.svideosdk.mixrecorder.AliyunMixTrackLayoutParam
import com.aliyun.svideosdk.mixrecorder.impl.AliyunMixRecorderCreator
import com.aliyun.svideosdk.recorder.RecordCallback


/**
 * 视频录制Example
 */
class MixRecordActivity : AppCompatActivity() {

    enum class RecordStatus {
        Idle,
        Recording
    }

    private lateinit var mAliyunRecord : AliyunIMixRecorder
    private lateinit var mVideoPreviewView : SurfaceView
    private lateinit var mCameraPreviewView : SurfaceView
    private lateinit var mRecordBtn : ImageView
    private var mRecordStatus = RecordStatus.Idle

    companion object {
        const val TAG = "MixRecordActivity"
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_mixrecord)

        mVideoPreviewView = findViewById(R.id.videoPreviewView)
        mCameraPreviewView = findViewById(R.id.cameraPreviewView)
        mRecordBtn = findViewById(R.id.btnRecordControl)
        //录制按钮
        mRecordBtn.setOnClickListener {
            if(mRecordStatus == RecordStatus.Recording) {
                mAliyunRecord.finishRecording()
                Toast.makeText(this@MixRecordActivity, "结束录制", Toast.LENGTH_SHORT).show()
                updateRecordStatus(RecordStatus.Idle)
            } else {
                val curTime = System.currentTimeMillis()
                mAliyunRecord.setOutputPath("/storage/emulated/0/DCIM/Camera/svideo_mixrecord_video_$curTime.mp4")
                mAliyunRecord.startRecording()
                Toast.makeText(this@MixRecordActivity, "开始录制", Toast.LENGTH_SHORT).show()
                updateRecordStatus(RecordStatus.Recording)
            }

        }

        mAliyunRecord = AliyunMixRecorderCreator.createAlivcMixRecorderInstance(this)


        val videoDisplayParam = AliyunMixRecorderDisplayParam.Builder()
                .displayMode(VideoDisplayMode.FILL)
                .layoutParam(
                        AliyunMixTrackLayoutParam.Builder()
                                .centerX(0.25f)
                                .centerY(0.5f)
                                .widthRatio(0.5f)
                                .heightRatio(1.0f)
                                .build()
                )
                .build()

        val cameraDisplayParam = AliyunMixRecorderDisplayParam.Builder()
                .displayMode(VideoDisplayMode.FILL)
                .layoutParam(AliyunMixTrackLayoutParam.Builder()
                        .centerX(0.75f)
                        .centerY(0.5f)
                        .widthRatio(0.5f)
                        .heightRatio(1.0f)
                        .build())
                .build()

        val mixMediaParam = AliyunMixMediaInfoParam.Builder()
                .streamStartTimeMills(0L)
                .streamEndTimeMills(0L) //设置为0L就会自动使用视频的时长
                .mixVideoFilePath("/storage/emulated/0/DCIM/Camera/VID_20210317_174802.mp4")
                .mixDisplayParam(videoDisplayParam)
                .recordDisplayParam(cameraDisplayParam)
                .build()

        val mediaInfo = MediaInfo()
        mediaInfo.fps = 30
        mediaInfo.crf = 6
        mediaInfo.videoWidth = 720
        mediaInfo.videoHeight = 1080
        mediaInfo.videoCodec = VideoCodecs.H264_SOFT_OPENH264

        mAliyunRecord.setMixMediaInfo(mixMediaParam, mediaInfo)
        mAliyunRecord.setDisplayView(mCameraPreviewView, mVideoPreviewView)

        mAliyunRecord.setRecordCallback(object : RecordCallback {
            override fun onComplete(validClip: Boolean, clipDuration: Long) {
                Log.i(TAG, "onComplete")
            }

            override fun onFinish(outputPath: String?) {
                Log.i(TAG, "onFinish path : $outputPath")
                mAliyunRecord.clipManager.deleteAllPart()
            }

            override fun onProgress(progress: Long) {
                Log.i(TAG, "onProgress  : $progress")
            }

            override fun onMaxDuration() {
                Log.i(TAG, "onMaxDuration")
            }

            override fun onError(errorCode: Int) {
                Log.i(TAG, "onError : $errorCode")
            }

            override fun onInitReady() {
                Log.i(TAG, "onInitReady")
            }

            override fun onDrawReady() {
                Log.i(TAG, "onDrawReady")
            }

            override fun onPictureBack(bitmap: Bitmap?) {
                Log.i(TAG, "onPictureBack")
            }

            override fun onPictureDataBack(p0: ByteArray?) {
                Log.i(TAG, "onPictureDataBack")
            }

        })

    }

    private fun updateRecordStatus(recordStatus: RecordStatus)
    {
        mRecordStatus = recordStatus
        when(recordStatus) {
            RecordStatus.Idle -> {
                mRecordBtn.setImageResource(R.mipmap.alivc_svideo_bg_record_start)
            }
            RecordStatus.Recording -> {
                mRecordBtn.setImageResource(R.mipmap.alivc_svideo_bg_record_storp)
            }
        }
    }

    override fun onResume() {
        super.onResume()
        mAliyunRecord.startPreview()
    }

    override fun onPause() {
        super.onPause()
        mAliyunRecord.stopPreview()
    }

    override fun onDestroy() {
        super.onDestroy()
        mAliyunRecord.release()
    }
}

XML文件配置示例

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline3"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.5" />


    <SurfaceView
        android:id="@+id/videoPreviewView"
        android:layout_width="0dp"
        android:layout_height="400dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/guideline3"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        tools:layout_editor_absoluteY="0dp" />

    <SurfaceView
        android:id="@+id/cameraPreviewView"
        android:layout_width="0dp"
        android:layout_height="400dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="@+id/guideline3"
        app:layout_constraintEnd_toEndOf="parent" />

    <ImageView
        android:id="@+id/btnRecordControl"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:layout_marginBottom="50dp"
        android:src="@mipmap/alivc_svideo_bg_record_start"
        android:visibility="visible"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent">

    </ImageView>

</androidx.constraintlayout.widget.ConstraintLayout>