常见场景

目前腾讯视频云移动直播SDK(LiteAVSDK)只回调摄像机预览画面的纹理数据。如果开发者集成第三方美颜库来实现美颜、滤镜等功能,但第三方库的美颜功能输入数据要求是camera的原始数据(YUV 数据)。开发者想实现该功能,需要采用自定义采集视频数据接口,然后复用 LiteAVSDK 的编码和推流功能。

解决方案

Android5.0以上,通过camera2采集YUV_420_888

  1. 不再调用 TXLivePusherstartCameraPreview 接口。这样 SDK 本身就不会再采集视频数据和音频数据,而只是启动预处理、编码、流控、推流等工作。
  2. 在摄像机的预览回调onImageAvailable()中,获取到 YUV_420_888 格式的视频数据,然后将 YUV_420_888 格式转码为 I420 格式,再使用 sendCustomVideoData 向SDK填充您采集和处理后的 Video 数据。

具体实例代码如下:

ImageReader mImageReader = ImageReader.newInstance(width, height, ImageFormat.YUV_420_888 ,1);

ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() {


        @RequiresApi(api = Build.VERSION_CODES.KITKAT)
        @Override
        public void onImageAvailable(ImageReader reader) {

            // 获取捕获的照片数据
            Image image = reader.acquireNextImage();
            int width2 = image.getWidth();
            int height2 = image.getHeight();
            if(isPushFlag){

                byte[] yuv420pbuf = camera2ImageToI420(image);

                //TODO setConfig()
                int result= mLivePusher.sendCustomVideoData(yuv420pbuf, TXLivePusher.YUV_420P, width2, height2);
            }
        }
}

@RequiresApi(api = Build.VERSION_CODES.KITKAT)
    public byte[] camera2ImageToI420(Image image){
        int width3 = image.getWidth();
        int height3 = image.getHeight();

        // 从image里获取三个plane
        Image.Plane[] planes = image.getPlanes();
//        for (int i = 0; i < planes.length; i++) {
//           ByteBuffer iBuffer = planes[i].getBuffer();
//            int iSize = iBuffer.remaining();
//            Log.i("TAG", "pixelStride  " + planes[i].getPixelStride());
//            Log.i("TAG", "rowStride   " + planes[i].getRowStride());
//            Log.i("TAG", "width  " + image.getWidth());
//            Log.i("TAG", "height  " + image.getHeight());
//            Log.i("TAG", "buffer size  " + iSize);
//            Log.i("TAG", "Finished reading data from plane  " + i);
//        }
        // Y-buffer
        ByteBuffer yBuffer = planes[0].getBuffer();
        int ySize = yBuffer.remaining();
        byte[] yBytes = new byte[ySize];
        yBuffer.get(yBytes);

        // U-buffer
        ByteBuffer uBuffer = planes[1].getBuffer();
        int uSize = uBuffer.remaining();
        byte[] uBytes = new byte[uSize];
        uBuffer.get(uBytes);

        // V-buffer
        ByteBuffer vBuffer = planes[2].getBuffer();
        int vSize = vBuffer.remaining();
        byte[] vBytes = new byte[vSize];
        vBuffer.get(vBytes);

        byte[] ret = new byte[width3 * height3 * 3/2];
        int yLength = yBytes.length;
        int uLength = uBytes.length+1;
        int vLength = vBytes.length+1;

        ByteBuffer bufferY = ByteBuffer.wrap(ret, 0, yLength);
        ByteBuffer bufferU = ByteBuffer.wrap(ret, yLength, uLength/ 2);
        ByteBuffer bufferV = ByteBuffer.wrap(ret, yLength+uLength/ 2, vLength/ 2);

        bufferY.put(yBytes,0,yLength);
        for(int i=0;i<uLength;i+=2){
            bufferU.put(uBytes[i]);
        }
        for(int i=0;i<vLength;i+=2){
            bufferV.put(vBytes[i]);
        }

        return ret;
    }

Android5.0以下,通过camera采集到NV21数据

  1. 不再调用 TXLivePusherstartCameraPreview 接口。这样 SDK 本身就不会再采集视频数据和音频数据,而只是启动预处理、编码、流控、推流等工作。
  2. 在摄像机的预览回调onPreviewFrame()中,获取到 NV21 格式的视频数据,然后将 NV21 格式转码为 I420 格式,再使用 sendCustomVideoData 向SDK填充您采集和处理后的 Video 数据。

具体实例代码如下:

//以下是简单的实例,获取摄像机预览回调的视频数据并推流
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
    // 假设摄像机获取的视频格式是 NV21, 预览画面大小为 1280X720
    // 即宽度 mPreviewWidth 值为1280,高度 mPreviewHeight 值为 720
    if (!isPush) {
    } else {
        // 开始自定义推流
        // 需要将视频格式转码为 I420
        byte[] buffer = new byte[data.length];
        buffer = nv21ToI420(data, mPreviewWidth, mPreviewHeight);

        int customModeType = 0;
        customModeType |= TXLiveConstants.CUSTOM_MODE_VIDEO_CAPTURE;
        // 只能分辨率的宽和高小于或者等于预览画面的宽和高的分辨率 
        // 还能选择 360x640 等,但不能选择 540x960。因指定分辨率的高(960) > 预览画面的高(720),编码器无法裁剪画面。
        mLivePushConfig.setVideoResolution(TXLiveConstants.VIDEO_RESOLUTION_TYPE_1280_720);
        mLivePushConfig.setAutoAdjustBitrate(false);
        mLivePushConfig.setVideoBitrate(1500);
        mLivePushConfig.setVideoEncodeGop(3);
        mLivePushConfig.setVideoFPS(18);
        mLivePushConfig.setCustomModeType(customModeType);
        mLivePusher.setConfig(mLivePushConfig);

        int result= mLivePusher.sendCustomVideoData(buffer, TXLivePusher.YUV_420P, mPreviewWidth, mPreviewHeight);
    }
}

/**
 * nv21转I420
 * @param data
 * @param width
 * @param height
 * @return
 */
public static byte[] nv21ToI420(byte[] data, int width, int height) {
    byte[] ret = new byte[data.length];
    int total = width * height;

    ByteBuffer bufferY = ByteBuffer.wrap(ret, 0, total);
    ByteBuffer bufferU = ByteBuffer.wrap(ret, total, total / 4);
    ByteBuffer bufferV = ByteBuffer.wrap(ret, total + total / 4, total / 4);

    bufferY.put(data, 0, total);
    for (int i=total; i<data.length; i+=2) {
        bufferV.put(data[i]);
        bufferU.put(data[i+1]);
    }
    return ret;
}

camera2完整的示例代码下载地址, 建议将代码复制到腾讯云开发者demo中

camera完整的示例代码下载地址, 建议将代码复制到腾讯云开发者demo中

原理

接口说明

int sendCustomVideoData(byte[] buffer, int bufferType, int w, int h)

该接口是向 SDK 传入开发者自定义采集和处理后的视频数据(美颜、滤镜等),目前支持 I420 格式。该接口适用场景是只想使用我们 SDK 来 来编码和推流。 调用该接口前提,是不再调用 TXLivePusherstartCameraPreview 接口。

  • 参数说明
参数 类型 说明
buffer byte[] 视频数据
bufferType int 视频格式.目前只支持 TXLivePusher.YUV_420P
w int 视频图像的宽度
h int 视频图像的高度
  • 返回结果说明:
结果 说明
>0 发送成功,但帧率过高,超过了TXLivePushConfig中设置的帧率,帧率过高会导致视频编码器输出的码率超过TXLivePushConfig中设置的码率,返回值表示当前YUV视频帧提前的毫秒数
0 发送成功
-1 视频分辨率非法
-2 YUV数据长度与设置的视频分辨率所要求的长度不一致
-3 视频格式非法
-4 视频图像长宽不符合要求,画面比要求的小了
-1000 SDK内部错误

自定义采集数据流程图

注意事项

  1. 目前sendCustomVideoData接口只支持 I420(TXLivePusher.YUV_420P)格式的视频数据。
  2. sendCustomVideoData 方法最后两个参数是摄像机预览画面的宽度和高度,必需保持一致,不然会报出 -4 的错误。camera2在获取摄像机预览宽高前,请先检测手机支持的分辨率,如果指定分辨率与支持的分辨率不一致,会获取到比指定分辨率小的画面,sendCustomVideoData时要以实际预览画面的宽高为准。
  3. 指定推流分辨率(setVideoResolution)的宽度(高度)一定要小于或者等于摄像机预览画面的宽度(高度)。例如预览分辨率是960x720,设置推流的分辨率可以 960x540。
  4. LivePushConfig 中的customModeType 设置为TXLiveConstants.CUSTOM_MODE_VIDEO_CAPTURE,SDK 还是会采集音频数据的。
  5. 使用LivePushConfig.setVideoResolution设置推流分辨率,目前 sendCustomVideoData 只支持推 640x360(360P)、360x640、960x540(540P)、540x960、1280x720(720P)、720x1280这6种分辨率

iOS移动直播,自定义采集视频数据推流

文章来源于腾讯云开发者社区,点击查看原文