文档反馈
文档反馈

音视频前处理

本章节主要介绍网易云信提供的各种音视频前处理功能。前处理介于采集和编码之间,按照数据类型分类,可以分为音频前处理和视频前处理,音频前处理包括降噪回音消除人声检测自动增益耳返等等,视频前处理包括美颜磨皮设置对比度镜像水印等。网易云信移动端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开启视频数据处理,根据情况选择开启新的或者老的数据回调,推荐使用新的数据回调方式。

parameters.setBoolean(AVChatParameters.KEY_VIDEO_FRAME_FILTER, on);//开启老的视频数据回调
parameters.setBoolean(AVChatParameters.KEY_VIDEO_FRAME_FILTER_NEW, on);//开启新的视频数据回调
/**
     * 视频数据外部处理接口, 此接口需要同步执行. 操作运行在视频数据发送线程上,处理速度过慢会导致帧率过低
     *
     * @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)) {}
/**
 * 视频数据外部处理接口, 此接口需要同步执行. 操作运行在视频数据发送线程上,处理速度过慢会导致帧率过低
 * @param input          待处理数据
 * @param outputFrames   {@link com.netease.nrtc.sdk.video.VideoFrame[0]} 处理后的数据,{@link com.netease.nrtc.sdk.video.VideoFrame[1]} 处理后的镜像数据。
 *                       在实际使用过程中,用户需要根据自己需求来决定是否真正需要输入镜像数据,一般在使用到水印等外部处理时才会需要真正输入两路数据,其他情况可以忽略此参数。
 * @param filterParameter 待处理数据的参数
 * @return 返回true成功
 */
boolean onVideoFrameFilter(final com.netease.nrtc.sdk.video.VideoFrame input, com.netease.nrtc.sdk.video.VideoFrame[] outputFrames, VideoFilterParameter filterParameter);
参数 说明
input 待处理数据,统一用VideoFrame封装,可能是I420/NV/Texture格式
outputFrames outputFrames[0]处理后的数据,outputFrames[1]处理后的镜像数据
filterParameter 待处理参数 ,参考 VideoFilterParameter
@Override
boolean onVideoFrameFilter(final VideoFrame input, VideoFrame[] outputFrames, VideoFilterParameter filterParameter){
    ...
        if(format == VideoFrameFormat.kVideoTexture) {
            innerVideoEffect.init(getApplicationContext(), true, true);
        }else{
            innerVideoEffect.init(getApplicationContext(), true, false);
        }
    ...
         if (frame.getBuffer().getFormat() == VideoFrameFormat.kVideoTexture) {
            return  filterTextureFrame(frame,outputFrames,filterParameter);
        } else {
            return filterByteBufferFrame(frame, outputFrames, filterParameter);
        }
    ...
}

滤镜模块初始化

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

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

滤镜模块可以通过gradle进行依赖,如

dependencies {
    implementation 'com.netease.nrtc:videoeffect:1.0.3'
}

如果在API < 18工程中依赖,需要在AndroidManifest.xml中申明此库支持低版本依赖,运行时需增加API >= 18才使用滤镜库逻辑

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <!--添加以下内容-->
    <uses-sdk tools:overrideLibrary="com.netease.nrtc.libvideoeffect"/>

    <application>...<application/>
</manifest>

接入时,需要在采集视频数据的回调方法中自定义滤镜功能,该回调需要设置对应参数才会触发。

    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);

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

/**
 * 采用GPU对Camera采集的OES Texture进行滤镜处理 (必须在具有EGLContext的线程上进行调用)
 *
 * @param texture OES Texture
 * @param width   采集图像宽
 * @param height  采集图像高
 * @return 2D texture纹理,可以对这个texture做后续操作,如硬编、预览
*/
public abstract int filterTexture(int texture, int width, int height);
参数 说明
texture 数据类型。
height 图像宽。
height 图像高。
 int filtedTextureId = mVideoEffect.filterTexture(textureId, width, 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);

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

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

    /**
     * 手动水印
     * @param data - 纹理格式的数据,包括纹理ID,纹理的byte[]数据,纹理长,纹理宽等。
     * @param needI420Data - 是否需要I420数据。
     * @param waterCount - 需要添加水印的纹理数。比如镜像过和未镜像过的两个纹理。
     * @return TextureData - 包括纹理ID,纹理的byte[]数据,纹理长,纹理宽等。
     */
    public TextureData effectWater(TextureData data,boolean needI420Data, int waterCount){}
参数 说明
data 纹理格式的数据,包括纹理ID,纹理长,纹理宽等
needI420Data 是否需要I420数据。
waterCount 需要添加水印的纹理数。比如镜像过和未镜像过的两个纹理。
     //////给未镜像数据加水印
VideoEffect.TextureData textureDataW = mVideoEffect.effectWater(textureData, false,1);
  /**
     * 原始纹理数据进行旋转
     * @param textureType 输入纹理类型 GLES11Ext.GL_TEXTURE_EXTERNAL_OES、GLES20.GL_TEXTURE_2D
     * @param textureId 输入纹理ID
     * @param srcWidth 输入宽
     * @param srcHeight 输入高
     * @param rotation  对纹理的旋转角度
     * @return 旋转后的纹理信息
     */
    public TextureData rotateTexture(int textureType,int textureId, int srcWidth, int srcHeight, int rotation){}
参数 说明
textureType 输入纹理类型 GLES11Ext.GL_TEXTURE_EXTERNAL_OES、GLES20.GL_TEXTURE_2D
textureId 输入纹理ID
srcWidth 输入宽
srcHeight 输入高
rotation 对纹理的旋转角度
 VideoEffect.TextureData textureDataR = mVideoEffect.rotateTexture(GLES20.GL_TEXTURE_2D, filtedTextureId, width, height, rotation);
    /**
     * 原始纹理数据镜像处理
     *
     * @param textureType 输入纹理类型 GLES11Ext.GL_TEXTURE_EXTERNAL_OES、GLES20.GL_TEXTURE_2D
     * @param textureId 输入纹理ID
     * @param srcWidth 输入宽
     * @param srcHeight 输入高
     * @return 镜像后的纹理信息
     */
    public TextureData mirrorTexture(int textureType,int textureId, int srcWidth, int srcHeight){}
参数 说明
textureType 输入纹理类型 GLES11Ext.GL_TEXTURE_EXTERNAL_OES、GLES20.GL_TEXTURE_2D
textureId 输入纹理ID
srcWidth 输入宽
srcHeight 输入高
VideoEffect.TextureDatatextureData = mVideoEffect.mirrorTexture(GLES20.GL_TEXTURE_2D, textureDataR.textureId, textureDataR.width, textureDataR.height);

销毁滤镜模块

销毁滤镜模块,需要在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);
×

反馈成功

非常感谢您的反馈,我们会继续努力做得更好。