SOP TRTC SDK 仪表盘 Android TRTC 发送自定义采集的视频数据 Android TRTC 发送自定义采集音频解决方案 Android TRTC 实现横屏视频通话 iOS端 TRTC 发送自定义采集视频解决方案 iOS端 TRTC 发送自定义采集音频解决方案 APNS推送 脏字过滤 TXLiteAVSDK中使用 AVAudioSession 问题总结 AndroidStudio编译SDK报错 RoomService部署验证 Xcode编译SDK报错 iOS编译库冲突问题 iOS端移动直播自定义采集实现 iOS端TXLiteAVSDK与IMSDK 3.x集成冲突报错问题 Android端TXLiteAVSDK与IMSDK 3.x集成冲突报错问题 Android端LiteIM sdk升级IM4.x版本报错问题 移动直播iOS 12兼容问题 如何实现好的画质 如何计算PCM音量大小 使用播放器播放视频有黑边 直播拉流播放失败 直播拉流端卡顿现象 短视频上传失败 移动直播SDK对接第三方美颜库 移动直播连麦解决方案 Android移动直播推自定义采集的视频数据 Android移动直播推自定义采集的声音数据 Android直播播放如何获取YUV数据 Android直播播放如何自定义渲染 实时音视频画面黑屏 实时音视频订阅流显示 iOS 12默认新编译系统下文件名冲突问题 TXLiteAVSDK指标监控 进阶:小程序实时音视频参数透传 移动直播 Android 9.0 无法拉流问题 移动直播推流事件回调 移动直播拉流事件回调 短视频实现视频缩略图列表转GIF功能 roomService加入群组时报错invalid group id NTP时间戳转换 提示Role not exists 角色不存在 如何播放背景音乐 iOS端短视频添音频相关问题总结 Web同步终端离线推送TIMOfflinePushInfo说明文档 web端自定义消息发送 web端同步终端的已读回执 web端对群组内用户禁言操作 TRTC v2混流接口setMixTranscodingConfig使用指引

Android移动直播推自定义采集的视频数据

发布日期:2018年8月31日 更新日期:2018年10月15日 贡献者:yyuanchen chaoli

常见场景

目前腾讯视频云移动直播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种分辨率

results matching ""

    No results matching ""