常见场景
目前腾讯视频云移动直播SDK(LiteAVSDK)只回调摄像机预览画面的纹理数据。如果开发者集成第三方美颜库来实现美颜、滤镜等功能,但第三方库的美颜功能输入数据要求是camera的原始数据(YUV 数据)。开发者想实现该功能,需要采用自定义采集视频数据接口,然后复用 LiteAVSDK 的编码和推流功能。
解决方案
Android5.0以上,通过camera2采集YUV_420_888
- 不再调用
TXLivePusher
的startCameraPreview
接口。这样 SDK 本身就不会再采集视频数据和音频数据,而只是启动预处理、编码、流控、推流等工作。 - 在摄像机的预览回调
onImageAvailable()
中,获取到 YUV_420_888 格式的视频数据,然后将 YUV_420_888 格式转码为 I420 格式,再使用sendCustomVideoData
向SDK填充您采集和处理后的 Video 数据。
具体实例代码如下:
代码语言:javascript复制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数据
- 不再调用
TXLivePusher
的startCameraPreview
接口。这样 SDK 本身就不会再采集视频数据和音频数据,而只是启动预处理、编码、流控、推流等工作。 - 在摄像机的预览回调
onPreviewFrame()
中,获取到 NV21 格式的视频数据,然后将 NV21 格式转码为 I420 格式,再使用sendCustomVideoData
向SDK填充您采集和处理后的 Video 数据。
具体实例代码如下:
代码语言:javascript复制//以下是简单的实例,获取摄像机预览回调的视频数据并推流
@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 来 来编码和推流。 调用该接口前提,是不再调用 TXLivePusher
的 startCameraPreview
接口。
- 参数说明
参数 | 类型 | 说明 |
---|---|---|
buffer | byte[] | 视频数据 |
bufferType | int | 视频格式.目前只支持 TXLivePusher.YUV_420P |
w | int | 视频图像的宽度 |
h | int | 视频图像的高度 |
- 返回结果说明:
结果 | 说明 |
---|---|
>0 | 发送成功,但帧率过高,超过了TXLivePushConfig中设置的帧率,帧率过高会导致视频编码器输出的码率超过TXLivePushConfig中设置的码率,返回值表示当前YUV视频帧提前的毫秒数 |
0 | 发送成功 |
-1 | 视频分辨率非法 |
-2 | YUV数据长度与设置的视频分辨率所要求的长度不一致 |
-3 | 视频格式非法 |
-4 | 视频图像长宽不符合要求,画面比要求的小了 |
-1000 | SDK内部错误 |
自定义采集数据流程图
注意事项
- 目前sendCustomVideoData接口只支持 I420(TXLivePusher.YUV_420P)格式的视频数据。
- sendCustomVideoData 方法最后两个参数是摄像机预览画面的宽度和高度,必需保持一致,不然会报出 -4 的错误。camera2在获取摄像机预览宽高前,请先检测手机支持的分辨率,如果指定分辨率与支持的分辨率不一致,会获取到比指定分辨率小的画面,sendCustomVideoData时要以实际预览画面的宽高为准。
- 指定推流分辨率(setVideoResolution)的宽度(高度)一定要小于或者等于摄像机预览画面的宽度(高度)。例如预览分辨率是960x720,设置推流的分辨率可以 960x540。
- LivePushConfig 中的customModeType 设置为TXLiveConstants.CUSTOM_MODE_VIDEO_CAPTURE,SDK 还是会采集音频数据的。
- 使用LivePushConfig.setVideoResolution设置推流分辨率,目前 sendCustomVideoData 只支持推 640x360(360P)、360x640、960x540(540P)、540x960、1280x720(720P)、720x1280这6种分辨率
iOS移动直播,自定义采集视频数据推流