音视频前处理

本章节主要介绍网易云提供的各种音视频前处理功能。前处理介于采集和编码之间,按照数据类型分类,可以分为音频前处理和视频前处理,音频前处理包括降噪回音消除人声检测自动增益耳返等等,视频前处理包括美颜磨皮设置对比度镜像水印等。网易云移动端SDK中内置了基础款的美颜滤镜。 同时,网易云还提供了音视频采集数据的回调功能,所以除了SDK自带的一些音视频的前处理功能,开发者可以利用音视频采集数据的回调功能,实现自定义的音视频数据前处理,包括接入第三方变声、美颜算法。

音频前处理

音频采集数据回调与发送

当用户开始外部语音处理后,采集到的语音数据通过次回调通知。 用户可以对语音数据做相应的变声等不同的处理。需要通过setParameters开启语音数据处理。

    /**
     * 语音数据处理接口, 不要改变数据的长度. 需要设置参数 {@link AVChatParameters#KEY_AUDIO_FRAME_FILTER}.
     *
     * @param frame 语音帧
     * @return 返回false 失败
     * @see AVChatParameters#KEY_AUDIO_FRAME_FILTER
     * @see AVChatManager#setParameter(AVChatParameters.Key, Object)
     */
    boolean onAudioFrameFilter(AVChatAudioFrame frame);
参数 说明
frame 语音帧,参考AVChatAudioFrame
@Override
public boolean onAudioFrameFilter(AVChatAudioFrame frame) {}

音频耳返

当主播想要从耳机中听到自己的声音时,可以开启耳返。一般使用在主播开启伴音,主播戴上耳机,随着伴奏说话唱歌,可以从耳机中实时听到融合了音乐和自己人声的声音。

    /**
     * <p>开启耳返</p>
     * <p>
     * <p>在通话建立后可以打开耳返功能,打开后可以从耳机中实时听到自己的声音。</p>
     *
     * @return {@code true} 方法调用成功,{@code false} 方法调用失败
     */
    public abstract boolean startPlayCapturedAudio();


    /**
     * <p>关闭耳返</p>
     * <p>
     * <p>在成功开启耳返功能后, 可以随时关闭耳返效果。</p>
     *
     * @return {@code true} 方法调用成功,{@code false} 方法调用失败
     */
    public abstract boolean stopPlayCapturedAudio();


    /**
     * <p>设置耳返音量</p>
     * <p>
     * <p>在成功打开耳返功能后,可以实时调整耳返音量。</p>
     *
     * @param volume 播放耳返音量 [0.0f - 1.0f]
     * @return {@code true} 方法调用成功,{@code false} 方法调用失败
     */
    public abstract boolean setPlayCapturedAudioVolume(float volume);

setPlayCapturedAudioVolume参数说明:

参数 说明
volume 播放耳返数据音量[0.0f - 1.0f]

视频前处理

视频采集数据回调与发送

当用户开始外部视频处理后,采集到的视频数据通过次回调通知。 用户可以对视频数据做相应的美颜等不同的处理。 需要通过setParameters开启视频数据处理。

   /**
     * 视频数据外部处理接口, 此接口需要同步执行. 操作运行在视频数据发送线程上,处理速度过慢会导致帧率过低
     *
     * @param frame          待处理数据
     * @param maybeDualInput 如果为 {@code true} 则代表需要外部输入两路数据,
     *                       {@link AVChatVideoFrame#data} 处理后的原始数据,{@link AVChatVideoFrame#dataMirror} 处理后的镜像数据。
     *                       如果为  {@code false} 则代表仅需要外部输入一路数据,仅支持 {@link AVChatVideoFrame#data}。
     *                       在实际使用过程中,用户需要根据自己需求来决定是否真正需要输入镜像数据,一般在使用到水印等外部处理时才会需要真正输入两路数据,其他情况可以忽略此参数。
     * @return 返回true成功
     */
    boolean onVideoFrameFilter(AVChatVideoFrame frame, boolean maybeDualInput);
参数 说明
frame 待处理数据,参考AVChatVideoFrame
maybeDualInput 是否需要外部输入两路数据,如果为 true 则代表需要外部输入两路数据。
@Override
public boolean onVideoFrameFilter(AVChatVideoFrame frame, boolean maybeDualInput)) {}

滤镜模块初始化

SDK的发布包中打包有滤镜SDK,开发者可以根据需要接入滤镜功能,轻松实现磨皮、滤镜、静态水印和动态水印功能。滤镜模块的接口文档打开查阅API文档

滤镜模块需要运行在GLES 3.0及以上版本的系统中,对应API版本为API >= 18

滤镜的依赖包括video_effect.jarlibvideoeffect.so以及对应的一些assets资源,这些资源都位于sdk目录下。

接入时,首先需要将以上三个资源分别拷贝到对应的工程结构目录中,并在采集视频数据的回调方法中自定义滤镜功能,该回调需要设置对应参数才会触发。

    boolean onVideoFrameFilter(final AVChatVideoFrame frame, final boolean maybeDualInput);

滤镜模块的功能实现由VideoEffect类提供,使用前需要通过VideoEffectFactory.getVCloudEffect创建一个对应实例。

/**
 * 滤镜模块初始化前处理
 */
public void init(Context context, boolean useFilter, boolean hasGLContext);
参数 说明
context 应用上下文,建议使用ApplicationContext。
useFilter 是否使用滤镜功能。
hasGLContext 调用线程是否具有EGLContext。如果当前线程没有EGLContext,那么该接口会创建一个。后续所有的滤镜API必须保证在同一个具有GL Context的线程中调用。
mVideoEffect = VideoEffectFactory.getVCloudEffect();
mVideoEffect.init(getApplicationContext(), true, false);

对视频美颜

对视频美颜需要调用VideoEffect的设置滤镜类型和滤镜强度接口。

 //设置滤镜类型,可设置为`FilterType`枚举类中的一种
 public abstract void setFilterType(FilterType type);

 //设置滤镜强度,有效值为(0-1)
 public abstract void setFilterLevel(float level);
参数 说明
FilterType 滤镜类型。
level 滤镜强度,有效值为(0-1)。
mVideoEffect.setFilterType(VideoEffect.FilterType.nature);
mVideoEffect.setFilterLevel(0.5f);

设置磨皮强度

对视频进行磨皮需要调用VideoEffect的设置磨皮强度接口。

/** 设置磨皮强度
 * 参数:
 * level - (0-5)*/
public abstract void setBeautyLevel(int level)
参数 说明
level 磨皮强度,有效值为(0-5)。
mVideoEffect.setBeautyLevel(5);

设置静态水印

设置静态水印需要调用VideoEffect的设置静态水印接口。

/**添加水印(只需要调用一次)
 * 参数:
 * bitmap - 水印图片
 * rect - 水印具体位置(上下左右中四个基本位置)
 * x - 距离 rect 的 x 坐标
 * y - 距离 rect 的 y 坐标 */
public void addWaterMark(android.graphics.Bitmap bitmap,
                         VideoEffect.Rect rect,
                         int x,
                         int y)
参数 说明
bitmap 水印图片。
rect 水印具体位置(上下左右中四个基本位置)。
x 距离 rect 的 x 坐标。
y 距离 rect 的 y 坐标。
mVideoEffect.addWaterMark(mWaterMaskBitmapStatic, frame.width / 2, frame.height / 2);

设置动态水印

设置动态水印需要调用VideoEffect的设置动态水印接口。

/**添加水印(只需要调用一次)
 * 添加动态水印(只需要调用一次)
 * 参数:
 * bitmapArray - 水印图片
 * rect - 水印具体位置(上下左右中四个基本位置)
 * x - 距离 rect 的 x 坐标
 * y - 距离 rect 的 y 坐标
 * fps - 动态水印的帧率
 * cameraFps - 相机采集的帧率
 * looped - 是否循环播放 */
public void addDynamicWaterMark(android.graphics.Bitmap[] bitmapArray,
                                VideoEffect.Rect rect,
                                int x,
                                int y,
                                int fps,
                                int cameraFps,
                                boolean looped)
参数 说明
bitmapArray 水印图片。
rect 水印具体位置(上下左右中四个基本位置)。
x 距离 rect 的 x 坐标。
y 距离 rect 的 y 坐标。
fps 动态水印的帧率。
cameraFps 相机采集的帧率。
looped 是否循环播放。
      mVideoEffect.closeDynamicWaterMark(false);
      mVideoEffect.addDynamicWaterMark(null, frame.width / 2, frame.height / 2, 23, AVChatVideoFrameRate.FRAME_RATE_15, true);
      mVideoEffect.addDynamicWaterMark(mWaterMaskBitmapDynamic, frame.width / 2, frame.height / 2, 23, AVChatVideoFrameRate.FRAME_RATE_15, true);

滤镜处理

对图片原始数据进行滤镜处理,并返回RGBA格式的数据,如果开启了滤镜,则每一帧都需要调用该方法.采用GPU对buffer数据进行滤镜处理,必须在具有EGLContext的线程上进行调用。

/**采用GPU对buffer数据进行滤镜处理(必须在具有EGLContext的线程上进行调用)
 * 参数:
 * format - 数据类型
 * data - 原始数据
 * width - 图像宽
 * height - 图像高
 * 返回:
 * RGBA图像数组 */
public abstract byte[] filterBufferToRGBA(VideoEffect.DataFormat format,
                                          byte[] data,
                                          int width,
                                          int height)
参数 说明
format 数据类型。
data 原始数据。
width 图像宽。
height 图像高。
            byte[] intermediate = mVideoEffect.filterBufferToRGBA(format, frame.data, frame.width, frame.height);

水印和涂鸦处理

该方法将输入的原始视频数据转为YUV格式数据,同时会根据autoEffect配置是否为true自动进行水印、涂鸦、旋转等操作。

方法会返回两路数据,但只有第一路数据是始终有效的,并需要将之拷贝至AVChatVideoFrame.data中去。

仅当needMirrorDatatrue时,第二路数据有效,并需要将第二路数据拷贝至AVChatVideoFrame.dataMirror中,并设置AVChatVideoFrame.dualInputtrue

/**原始视频数据转YUV,同时会根据配置进行水印、涂鸦、旋转等操作,保证输出的是正的YUV数据
 * 参数:
 * src - 原始视频数据
 * dataFormat - 原始视频数据的色彩空间类型
 * inWidth - 输入视频宽
 * inHeight - 输入视频高
 * cameraRotation - 相机拍摄调度
 * displayOrientation - 计算后的显示角度
 * outWidth - 输出视频宽
 * outHeight - 输出视频高
 * needMirrorData - 是否需要镜像后的数据(主要用于前置摄像头下水印、涂鸦等本地镜像显示和编码数据的区别)
 * autoEffect - 是否自动添加上水印、涂鸦等
 * 返回:
 * 具有水印、涂鸦等正的YUV数据*/
public VideoEffect.YUVData[] TOYUV420(byte[] src,
                                      VideoEffect.DataFormat dataFormat,
                                      int inWidth,
                                      int inHeight,
                                      int cameraRotation,
                                      int displayOrientation,
                                      int outWidth,
                                      int outHeight,
                                      boolean needMirrorData,
                                      boolean autoEffect)
参数 说明
src 原始视频数据。
dataFormat 原始视频数据的色彩空间类型。
width 视频宽。
height 视频高。
cameraRotation 相机拍摄调度。
displayOrientation 计算后的显示角度。
outWidth 输出视频宽。
outHeight 输出视频高。
needMirrorData 是否需要镜像后的数据。
autoEffect 是否自动添加上水印、涂鸦等。
       result = mVideoEffect.TOYUV420(intermediate, VideoEffect.DataFormat.RGBA, frame.width, frame.height, frame.rotation, 90, frame.width, frame.height, needMirrorData, true);

销毁滤镜模块

销毁滤镜模块,需要在init方法调用的线程中调用

//销毁滤镜
public void unInit()
mVideoEffect.unInit();
mVideoEffect = null;

设置视频预览镜像

设置视频预览镜像是通过setParameters或者setParameter接口进行设置的,参数名称在AVChatParameters中的KEY_VIDEO_LOCAL_PREVIEW_MIRROR

    /**
     * 前置摄像头本地预览镜像
     *
     * >当使用前置摄像头时,本地预览画面是否镜像。 默认前置摄像头画面镜像处理。
     */
    public static final Key<Boolean> KEY_VIDEO_LOCAL_PREVIEW_MIRROR = new Key<>(RtcParameters.KEY_VIDEO_LOCAL_PREVIEW_MIRROR, Boolean.class);
参数 说明
value true 镜像,false 不镜像。
boolean mirror = AVChatManager.getInstance().getParameter(AVChatParameters.KEY_VIDEO_LOCAL_PREVIEW_MIRROR);
AVChatManager.getInstance().setParameter(AVChatParameters.KEY_VIDEO_LOCAL_PREVIEW_MIRROR, !mirror);

设置视频编码镜像

设置视频预览镜像是通过setParameters或者setParameter接口进行设置的,参数名称在AVChatParameters中的KEY_VIDEO_TRANSPORT_MIRROR

    /**
     * 前置摄像头发送数据预览镜像
     *
     * 当使用前置摄像头时,本地发送画面是否镜像。 默认前置摄像头发送画面不镜像处理。
     *
    public static final Key<Boolean> KEY_VIDEO_TRANSPORT_MIRROR = new Key<>(RtcParameters.KEY_VIDEO_TRANSPORT_MIRROR, Boolean.class);
参数 说明
value true 镜像,false 不镜像。
boolean mirror = AVChatManager.getInstance().getParameter(AVChatParameters.KEY_VIDEO_TRANSPORT_MIRROR);
AVChatManager.getInstance().setParameter(AVChatParameters.KEY_VIDEO_TRANSPORT_MIRROR, !mirror);