14
2020
05

在英特尔开源WebRTC开发套件OWT Android版中加入第三方美颜

在英特尔开源WebRTC开发套件OWT Android版中加入第三方美颜

英特尔开源WebRTC协同通信开发套件(Intel® Collaboration Suite for WebRTC)。

WebRTC协同通信开发套件是一款端到端媒体服务器软件,客户端包含JavaScript,Android,iOS,Linux,Windows C++ SDK,服务端支持SFU和MCU模式,可实现实时音视频分析功能。

WebRTC协同通信开发套件开源后命名为Open WebRTC Toolkit(OWT),采用商业友好的Apache 2.0 License软件许可。

据Mark介绍,OWT保持了原有的Intel® Collaboration Suite for WebRTC的基本框架,并计划集成一系列新技术,如Scalable Video Technology(SVT)。此外,OWT开源社区还得到了阿里云、爱奇艺、Zealcomm、Huddl等企业支持。

OWT的代码已经在GitHub公开:https://github.com/open-webrtc-toolkit同时,Intel® Collaboration Suite for WebRTC项目webrtc.intel.com仍然会被保留,并作为英特尔官方的OWT分发版继续定期对外发布,当前最新版本是v4.2.1(2019年6月4日发布)。


在OWT提供的Android SDK中,并无美颜接口,但预留了可以设置VideoProcessor的接口。

public interface VideoProcessor extends CapturerObserver {
    default void onFrameCaptured(VideoFrame frame, VideoProcessor.FrameAdaptationParameters parameters) {
        VideoFrame adaptedFrame = applyFrameAdaptationParameters(frame, parameters);
        if (adaptedFrame != null) {
            this.onFrameCaptured(adaptedFrame);
            adaptedFrame.release();
        }
    }
    void setSink(@Nullable VideoSink var1);
    @Nullable
    static VideoFrame applyFrameAdaptationParameters(VideoFrame frame, VideoProcessor.FrameAdaptationParameters parameters) {
        if (parameters.drop) {
            LogUtils.v("VideoProcessor", "parameters.drop");
            return null;
        } else {
            LogUtils.v("VideoProcessor", "parameters: "+parameters.toString());
            Buffer adaptedBuffer = frame.getBuffer().cropAndScale(parameters.cropX, parameters.cropY, parameters.cropWidth, parameters.cropHeight, parameters.scaleWidth, parameters.scaleHeight);
            return new VideoFrame(adaptedBuffer, frame.getRotation(), parameters.timestampNs);
        }
    }
    public static class FrameAdaptationParameters {
        public final int cropX;
        public final int cropY;
        public final int cropWidth;
        public final int cropHeight;
        public final int scaleWidth;
        public final int scaleHeight;
        public final long timestampNs;
        public final boolean drop;
        public FrameAdaptationParameters(int cropX, int cropY, int cropWidth, int cropHeight, int scaleWidth, int scaleHeight, long timestampNs, boolean drop) {
            this.cropX = cropX;
            this.cropY = cropY;
            this.cropWidth = cropWidth;
            this.cropHeight = cropHeight;
            this.scaleWidth = scaleWidth;
            this.scaleHeight = scaleHeight;
            this.timestampNs = timestampNs;
            this.drop = drop;
        }
        @Override
        public String toString() {
            return "FrameAdaptationParameters{" +
                    "cropX=" + cropX +
                    ", cropY=" + cropY +
                    ", cropWidth=" + cropWidth +
                    ", cropHeight=" + cropHeight +
                    ", scaleWidth=" + scaleWidth +
                    ", scaleHeight=" + scaleHeight +
                    ", timestampNs=" + timestampNs +
                    ", drop=" + drop +
                    '}';
        }
    }
}

我们要做的就是把实现这个VideoProcessor接口,把美颜的功能加入进去。现在以相芯的美颜接入做为示例

首先,我们新建个XXVideoProcessor类,implements VideoProcessor接口

//视频处理模块,在需要使用第三方美颜、或打水印等操作时,可调用此模块
public class XXVideoProcessor implements VideoProcessor {
    VideoSink mVideoSink;
    FURenderer mFuRenderer;
    public void setFuRenderer(FURenderer fuRenderer) {
        mFuRenderer = fuRenderer;
    }
    @Override
    public void setSink(VideoSink videoSink) {
        LogUtils.d("setSink");
        mVideoSink = videoSink;
    }
    @Override
    public void onCapturerStarted(boolean b) {
        LogUtils.d("onCapturerStarted : ");
    }
    @Override
    public void onCapturerStopped() {
        LogUtils.d("onCapturerStopped");
    }
    @Override
    public void onFrameCaptured(VideoFrame videoFrame) {
        VideoFrame frame = videoFrame;
        try {
            int bufferWidth = frame.getBuffer().getWidth();
            int bufferHeight = frame.getBuffer().getHeight();
            //从报像头采集的videoFrame里获取NV21数据,FU美颜需要,如需要其他格式,可自行改写
            byte[] data = createNV21Data(frame.getBuffer().toI420());
            if (null != mFuRenderer) {
                // videoFrame.getRotatedWidth(), videoFrame.getRotatedHeight()
                //美颜
                mFuRenderer.onDrawFrame(data, bufferWidth, bufferHeight, data, bufferWidth, bufferHeight);
            }
            /*
            //视需求翻转
            if (videoFrame.getRotation() == 270) {
                NV21_mirror(data, bufferWidth, bufferHeight);
            }
            */
            //转回VideoFrame格式
            frame = new VideoFrame(new NV21Buffer(data, bufferWidth, bufferHeight, null), videoFrame.getRotation(), videoFrame.getTimestampNs());
        } catch (Exception e) {
            e.printStackTrace();
        }
        //回写到Rtc模块里
        mVideoSink.onFrame(frame);
    }
    private byte[] createNV21Data(VideoFrame.I420Buffer i420Buffer) {
        final int width = i420Buffer.getWidth();
        final int height = i420Buffer.getHeight();
        final int chromaWidth = (width + 1) / 2;
        final int chromaHeight = (height + 1) / 2;
        final int ySize = width * height;
        LogUtils.v("MJH","width = "+width + " height: " +height);
        @SuppressWarnings("ByteBufferBackingArray") final ByteBuffer nv21Buffer = ByteBuffer.allocateDirect(ySize + width * chromaHeight);
        final byte[] nv21Data = nv21Buffer.array();
        int ni420BufferWidth = i420Buffer.getStrideY();
        int nOffset = 0;
        int nOffset2 = 0;
        ByteBuffer bufferY = i420Buffer.getDataY();
        ByteBuffer bufferU = i420Buffer.getDataU();
        ByteBuffer bufferV = i420Buffer.getDataV();
        for (int y = 0; y < height; ++y) {
            for (int x = 0; x < width; ++x) {
                nv21Data[nOffset2 + x] = bufferY.get(nOffset + x);
            }
            nOffset += ni420BufferWidth;
            nOffset2 += width;
        }
        ni420BufferWidth = i420Buffer.getStrideU();
        nOffset = 0;
        nOffset2 = 0;
        for (int y = 0; y < chromaHeight; ++y) {
            for (int x = 0; x < chromaWidth; ++x) {
                nv21Data[ySize + nOffset2 + (x<<1)] = bufferV.get(nOffset + x);
                nv21Data[ySize + nOffset2 + (x<<1) + 1] = bufferU.get(nOffset + x);
            }
            nOffset += ni420BufferWidth;
            nOffset2 += width;
        }
        i420Buffer.release();
        return nv21Data;
    }

首先,需要实现setSink方法,把videoSink保存到成员变量里,这个是处理完的数据的出口。

在重载的onFrameCaptured方法里,获取到VideoFrame,通过createNV21Data方法获取到第三方美颜需要的数据格式,如需要其他格式,可自行改写。在处理完后要调用mVideoSink.onFrame(frame) 回写到Rtc模块里。



然后,通过改写接口方法,将XXVideoProcessor对像传入到MediaStreamFactory中,在MediaStreamFactory 的 createMediaStream方法里,调用 videoSource.setVideoProcessor(xxvideoProcessor)即可。


如有问题,欢迎留言探讨。


« 上一篇 下一篇 »

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。