The short video SDK provides the AliyunIMixRecorder class for its duet recording feature. This feature allows you to combine an existing video with live footage from the camera and record them together in a specific track layout, such as side-by-side, top-and-bottom, or picture-in-picture. Duet recording is an enhancement of the basic recording feature and adds a new local video track to the composition.
Supported editions
Edition | Supported |
Professional | Yes |
Standard | Yes |
Basic | No |
Key concepts
To better understand the feature, review the definitions of duet recording, track, and track layout.
Related classes
Class | Description |
The core class for duet recording. It provides methods for recording, preview, effects, and callbacks. | |
A factory class that creates duet recording instances. | |
A class for configuring duet recording parameters, such as the track layout and output path. | |
A class for configuring duet recording tracks, including layout information and layering. | |
A class for configuring recording parameters, such as video dimensions, encoder type, and capture frame rate. | |
An interface for recording callbacks, including completion, progress, and error callbacks. | |
An interface for camera data capture callbacks, including callbacks for preview resolution selection, captured frames, and camera startup failures. | |
An interface for receiving audio data callbacks in PCM format. | |
An interface for managing recorded video clips, including deleting clips and setting the recording duration. |
Procedure
To use duet recording, you must grant camera and microphone permissions to your application. Otherwise, recording will fail.
The procedure for duet recording is similar to basic recording. The main difference is in the configuration of input and output parameters and the preview view.
Configuration | Step | Description | Sample code |
Basic configurations | 1 | Create, configure, and release recording instances. | |
2 | Set up callbacks. | ||
3 | Set up the preview view and start the preview. | ||
4 | Start, stop, and cancel the recording of video clips. | ||
5 | Finish recording. | ||
Advanced configurations | 6 | Configure camera controls (such as camera type and flash mode) and video clip management (such as maximum/minimum duration and clip deletion). These are optional. | |
7 | Configure recording effects such as retouching and filters. These are optional. | ||
8 | Configure photo taking or facial recognition. |
Initialize and configure parameters
Initialize the AliyunIMixRecorder class, create a recording instance, and configure recording parameters. For more information about the parameters used in the code, see Related classes.
Initialize
// Create a recording instance.
AliyunIMixRecorder recorder = AliyunMixRecorderCreator.createAlivcMixRecorderInstance(context);
// Release the recording instance.
// Ensure you release the instance when it is no longer needed or before your application exits.
// Do not release it while it is in use.
AliyunIMixRecorder.release();Configure recording parameters
// Set the quality of the recorded video.
AliyunIMixRecorder.setVideoQuality(quality);
// Set the bitrate of the recorded video.
AliyunIMixRecorder.setVideoBitrate(int bitrate); // Unit: kbps
// Set the output parameters for the recorded video.
// `inputMediaInfo` specifies parameters for the composite video, such as track layout and the output video.
// `outputInfo` specifies information about the output video.
AliyunIMixRecorder.setMixMediaInfo(AliyunMixMediaInfoParam inputMediaInfo, MediaInfo outputInfo); // For parameter details, see the AliyunMixMediaInfoParam and MediaInfo API references.
// Set the output path for the recorded video.
AliyunIMixRecorder.setOutputPath(String path);
// Set the Group of Pictures (GOP) size for the output video.
AliyunIMixRecorder.setGop(int gop); // Unit: number of framesSet up callbacks
Set up callbacks to receive real-time updates on the progress and status of audio and video processing. For more information about the parameters used in the code, see Related classes.
// Set the recording callback.
AliyunIMixRecorder.setRecordCallBack(RecordCallback callBack);
// Set the callback for captured video frames.
AliyunIMixRecorder.setOnFrameCallback(OnFrameCallBack callback);
// Set the callback for captured audio data.
AliyunIMixRecorder.setOnAudioCallback(OnAudioCallBack callback);Enable preview
To enable preview, you must provide a SurfaceView container. Call startPreview() in the onResume() method of your Activity/Fragment and stopPreview() in the onPause() method. For more information about the parameters used in the code, see Related classes.
// Set the preview views.
// `cameraView` is the view for the camera feed. `videoView` is the view for the local video.
AliyunIMixRecorder.setDisplayView(SurfaceView cameraView, Surface videoView);
// Start the preview.
AliyunIMixRecorder.startPreview();
// Stop the preview.
// Call stopPreview() in the onPause() method of your Activity/Fragment.
AliyunIMixRecorder.stopPreview();Start recording
You often need to start, stop, and restart recording multiple times to create the final video. Stopping the recording generates a video clip, while canceling the recording discards the current clip. For more information about the parameters used in the code, see Related classes.
Start recording
// Start recording.
AliyunIMixRecorder.startRecording();Record clips
// Start recording the first clip.
AliyunIMixRecorder.startRecording();
// Stop recording to generate a video clip.
AliyunIMixRecorder.stopRecording();
// Start recording another clip.
AliyunIMixRecorder.startRecording();
// Cancel recording and discard the current clip.
AliyunIMixRecorder.cancelRecording();
// Continue to record the next video clip.
AliyunIMixRecorder.startRecording();
AliyunIMixRecorder.stopRecording();Finish recording
When you finish recording, you can either merge all clips into a single video file or generate a configuration file containing information about the clips. For more information about the parameters used in the code, see Related classes.
// Finish recording and merge all recorded clips into a single video file.
AliyunIMixRecorder.finishRecording();
// Finish recording and generate a configuration file for the clips without merging them.
AliyunIMixRecorder.finishRecordingForEdit();Control recording and manage clips
This section describes how to configure optional parameters for camera control (such as camera type and flash mode) and video clip management (such as setting recording duration, deleting clips, and getting the clip count). For more information about the parameters used in the code, see Related classes.
Camera controls
// Get the number of available cameras.
AliyunIMixRecorder.getCameraCount();
// Set the camera type.
AliyunIMixRecorder.setCamera(cameraType);
// Mute the recording.
AliyunIMixRecorder.setMute(boolean isMute);
// Set the sensor rotation angle.
// Important: Read the API documentation for this parameter carefully.
AliyunIMixRecorder.setRotation(int rotation);
// Set the recording rotation angle.
// Important: Read the API documentation for this parameter carefully.
AliyunIMixRecorder.setRecordRotation(int rotation);
// Set camera preview parameters such as flash, focus mode, zoom, and exposure.
// You can also set these parameters individually using the methods below.
AliyunIMixRecorder.setCameraParam(CameraParam cameraParam);
// Switch between the front and rear cameras.
AliyunIMixRecorder.switchCamera();
// Set the flash mode.
AliyunIMixRecorder.setLight(FlashType flashType);
// Set the zoom level.
AliyunIMixRecorder.setZoom(float rate);
// Set the exposure compensation ratio.
AliyunIMixRecorder.setExposureCompensationRatio(float value);
// Set the focus mode.
AliyunIMixRecorder.setFocusMode(int mode);
// Trigger manual focus at a specific point.
AliyunIMixRecorder.setFocus(float xRatio, float yRatio);Clip management
// Get the clip manager instance.
AliyunIClipManager manager = AliyunIRecorder.getClipManager();
// Set the maximum total recording duration in milliseconds (not for a single clip).
manager.setMaxDuration(int maxDurationMs);
// Set the minimum total recording duration in milliseconds (not for a single clip).
manager.setMinDuration(int minDurationMs);
// Delete the last recorded clip.
manager.deletePart();
// Delete a clip at a specific index.
manager.deletePart(int index);
// Delete all recorded clips.
manager.deleteAllPart();
// Get the total duration of all clips.
manager.getDuration();
// Get the total number of clips.
manager.getPartCount();
// Get a list of file paths for all clips.
manager.getVideoPathList();Configure effects
This section describes how to configure optional recording effects such as retouching and filters. For more information about the parameters used in the code, see Related classes.
Filters
You can create custom filters. For more information, see Filters and transitions.
// Apply a filter.
AliyunIMixRecorder.applyFilter(effectFilter);
// Remove the filter.
// To remove the filter, pass a new EffectFilter with a null path.
AliyunIMixRecorder.applyFilter(new EffectFilter(null));Animated filters
// Apply an animated filter.
AliyunIMixRecorder.applyAnimationFilter(effectFilter);
// Remove an animated filter.
AliyunIMixRecorder.removeAnimationFilter(effctFilter);Recording speed
// Set the recording speed.
AliyunIMixRecorder.setRate(float rate);Static stickers and watermarks
// Add a static sticker or watermark.
AliyunIMixRecorder.addImage(effctImage);
// Remove a static sticker or watermark.
AliyunIMixRecorder.removeImage(effctImage);
// Update the position of a static sticker or watermark.
AliyunIMixRecorder.setEffectView(float xRatio,float yRatio,float widthRatio,float heightRatio,EffectBase effectBase);Animated stickers
You can create custom animated stickers. For more information, see Animated images.
// Add an animated sticker.
AliyunIMixRecorder.addPaster(effectPaster,float sx,float sy,float sw,float sh,float rotation,boolean flip);
// Remove an animated sticker.
AliyunIMixRecorder.removePaster(effectPaster);
// Update the position of an animated sticker.
AliyunIMixRecorder.setEffectView(float xRatio,float yRatio,float widthRatio,float heightRatio,effectBase);Advanced retouching
The video recording module provides basic built-in retouching features and also supports third-party retouching SDKs, such as the Alibaba Cloud retouching effects SDK and FaceUnity. The built-in retouching feature is simple and only allows you to set the retouching level. Third-party retouching SDKs typically offer a richer set of features, including advanced retouching, face shaping, makeup effects, filters, and stickers.
Built-in retouching
// Enable or disable retouching. AliyunIMixRecorder.setBeautyStatus(boolean on); // Set the retouching level. AliyunIMixRecorder.setBeautyLevel(int level);Third-party retouching SDKs
To use effects from a third-party retouching SDK, you must first obtain the necessary permissions and integrate the SDK with the short video SDK.
For information on integrating the Alibaba Cloud retouching effects SDK, see retouching effects SDK. For code examples on how to configure effects, see Usage examples.
For information on purchasing, integrating, and using the FaceUnity SDK, see FaceUnity.
Third-party retouching SDKs require two types of data to function: the camera texture ID and the raw camera frame data. The following code shows how to obtain this data.
Get camera texture ID data
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() { // To destroy GL resources for custom (third-party) rendering: // In SDK versions earlier than 3.7.8, you can use GLSurfaceView.queueEvent. // In SDK versions 3.7.8 and later, we recommend destroying GL resources within this callback. if (mBeautyInterface != null) { mBeautyInterface.release(); mBeautyInterface = null; } } });Get raw camera frame data
AliyunIMixRecorder.setOnFrameCallback(new OnFrameCallBack() { @Override public void onFrameBack(byte[] bytes, int width, int height, Camera.CameraInfo info) { // The raw data callback is in NV21 format. This raw data is primarily used // by third-party SDKs like FaceUnity for advanced retouching. 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() { } });
Other features
The SDK supports photo taking and facial recognition during video recording. For more information about the parameters used in the code, see Related classes.
Photo taking
You can take photos with or without effects. Photos taken without effects use the system's camera functionality. The photo data is returned through the RecordCallback.onPictureBack(Bitmap) or RecordCallback.onPictureDataBack(byte[]) callback.
// Take a photo with effects applied.
AliyunIMixRecorder.takePhoto(boolean needBitmap);
// Take a photo using the system camera (no effects).
AliyunIMixRecorder.takePicture(boolean needBitmap);
// Set the photo size for system photos. This is not supported for photos with effects.
AliyunIMixRecorder.setPictureSize(Camera.Size size);Facial recognition
To use facial recognition, you must integrate your app's own facial recognition model file. For an example, see the facial recognition model file demo.
// Enable or disable facial recognition.
AliyunIMixRecorder.needFaceTrackInternal(boolean need);
// Set the path to the facial recognition model file.
AliyunIMixRecorder.setFaceTrackInternalModelPath(String path);
// Set the face detection rotation angle.
// Important: Read the API documentation for this parameter carefully.
AliyunIMixRecorder.setFaceDetectRotation(int rotation);
// Set the maximum number of faces to detect.
// The maximum value for the built-in face tracking is 3.
AliyunIMixRecorder.setFaceTrackInternalMaxFaceCount(int maxFaceCount);
// Add an animated sticker that tracks a face.
AliyunIMixRecorder.addPaster(EffectPaster effectPaster);Duet recording sample code
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
/**
* Video recording 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)
// Recording button
mRecordBtn.setOnClickListener {
if(mRecordStatus == RecordStatus.Recording) {
mAliyunRecord.finishRecording()
Toast.makeText(this@MixRecordActivity, "Stop recording", 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, "Start recording", 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) // Set to 0L to automatically use the full duration of the video.
.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 configuration example
<?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>