Android平台音视频RTMP推送|GB28181对接之动态水印设计

Posted 音视频牛哥

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android平台音视频RTMP推送|GB28181对接之动态水印设计相关的知识,希望对你有一定的参考价值。

技术背景

随着移动单兵、智能车载、智慧安防、智能家居、工业仿真、GB28281技术对接等行业的发展,现场已经不再限于采集到视频数据编码打包发送或对接到流媒体服务端,大多场景对视频水印的要求越来越高,从之前的固定位置静态文字水印、png水印等慢慢过渡到动态水印需求。

本文以android平台采集摄像头数据为例,通过类似于PhotoShop图层的形式,添加不同图层,编码实现动态水印的效果。

废话不多说,先上个效果图,Android采集端获取到摄像头数据后,分别展示了实时时间水印、文字水印、png水印、文字水印二,所有水印均支持动态设置,可满足传统行业如实时时间戳叠加、动态经纬度设定、png logo等场景的水印设定需求。

Android平台音视频RTMP推送|GB28181对接之动态水印设计_rtmp水印

技术实现

  1. 摄像头数据采集,不再赘述,获取到前后摄像头的数据数据后(具体参见onPreviewFrame()处理),通过PostLayerImageNV21ByteArray()把数据投递到jni层。
  int w = videoWidth, h = videoHeight;
int y_stride = videoWidth, uv_stride = videoWidth;
int y_offset = 0, uv_offset = videoWidth * videoHeight;
int is_vertical_flip = 0, is_horizontal_flip = 0;
int rotation_degree = 0;

// 镜像只用在前置摄像头场景下
if (is_mirror && FRONT == currentCameraType)
// 竖屏, (垂直翻转->顺时旋转270度)等价于(顺时旋转旋转270度->水平翻转)
if (PORTRAIT == currentOrigentation)
is_vertical_flip = 1;
else
is_horizontal_flip = 1;


if (PORTRAIT == currentOrigentation)
if (BACK == currentCameraType)
rotation_degree = 90;
else
rotation_degree = 270;
else if (LANDSCAPE_LEFT_HOME_KEY == currentOrigentation)
rotation_degree = 180;


int scale_w = 0, scale_h = 0, scale_filter_mode = 0;

// 缩放测试++
/*
if (w >= 1280 && h >= 720)
scale_w = align((int)(w * 0.8 + 0.5), 2);
scale_h = align((int)(h * 0.8 + 0.5), 2);
else
scale_w = align((int)(w * 1.5 + 0.5), 2);
scale_h = align((int)(h * 1.5 + 0.5), 2);


if(scale_w >0 && scale_h >0)
scale_filter_mode = 3;
Log.i(TAG, "onPreviewFrame w:" + w + ", h:" + h + " s_w:" + scale_w + ", s_h:" + scale_h);

*/
// 缩放测试---

libPublisher.PostLayerImageNV21ByteArray(publisherHandle, 0, 0, 0,
data, y_offset, y_stride, data, uv_offset, uv_stride, w, h,
is_vertical_flip, is_horizontal_flip, scale_w, scale_h, scale_filter_mode, rotation_degree);

大家可能好奇PostLayerImageNV21ByteBuffer()和PostLayerImageNV21ByteArray()设计,接口参数很强大,和我们之前针对camera2的接口一样,几乎是万能接口,拿到的原始数据,不仅可以做水平、垂直翻转,还可以缩放处理。

  /**
* 投递层NV21图像
*
* @param index: 层索引, 必须大于等于0
*
* @param left: 层叠加的左上角坐标, 对于第0层的话传0
*
* @param top: 层叠加的左上角坐标, 对于第0层的话传0
*
* @param y_plane: y平面图像数据
*
* @param y_offset: 图像偏移, 这个主要目的是用来做clip的,一般传0
*
* @param y_row_stride: stride information
*
* @param uv_plane: uv平面图像数据
*
* @param uv_offset: 图像偏移, 这个主要目的是用来做clip的,一般传0
*
* @param uv_row_stride: stride information
*
* @param width: width, 必须大于1, 且必须是偶数
*
* @param height: height, 必须大于1, 且必须是偶数
*
* @param is_vertical_flip: 是否垂直翻转, 0不翻转, 1翻转
*
* @param is_horizontal_flip:是否水平翻转, 0不翻转, 1翻转
*
* @param scale_width: 缩放宽,必须是偶数, 0或负数不缩放
*
* @param scale_height: 缩放高, 必须是偶数, 0或负数不缩放
*
* @param scale_filter_mode: 缩放质量, 传0使用默认速度,可选等级范围是:[1,3],值越大缩放质量越好, 但速度越慢
*
* @param rotation_degree: 顺时针旋转, 必须是0, 90, 180, 270, 注意:旋转是在缩放, 垂直/水品反转之后再做, 请留意顺序
*
* @return 0 if successful
*/
public native int PostLayerImageNV21ByteBuffer(long handle, int index, int left, int top,
ByteBuffer y_plane, int y_offset, int y_row_stride,
ByteBuffer uv_plane, int uv_offset, int uv_row_stride,
int width, int height, int is_vertical_flip, int is_horizontal_flip,
int scale_width, int scale_height, int scale_filter_mode,
int rotation_degree);


/**
* 投递层NV21图像, 详细说明请参考PostLayerImageNV21ByteBuffer
*
* @return 0 if successful
*/
public native int PostLayerImageNV21ByteArray(long handle, int index, int left, int top,
byte[] y_plane, int y_offset, int y_row_stride,
byte[] uv_plane, int uv_offset, int uv_row_stride,
int width, int height, int is_vertical_flip, int is_horizontal_flip,
int scale_width, int scale_height, int scale_filter_mode,
int rotation_degree);
  1. 动态时间水印

动态时间水印其实就是文字水印的扩展,通过生成TextBitmap,然后从bitmap里面拷贝获取到text_timestamp_buffer_,通过我们设计的PostLayerImageRGBA8888ByteBuffer()投递到jni层。

private int postTimestampLayer(int index, int left, int top) 

Bitmap text_bitmap = makeTextBitmap(makeTimestampString(), getFontSize(),
Color.argb(255, 0, 0, 0), true, Color.argb(255, 255, 255, 255),true);

if (null == text_bitmap)
return 0;

if ( text_timestamp_buffer_ != null)
text_timestamp_buffer_.rewind();

if ( text_timestamp_buffer_.remaining() < text_bitmap.getByteCount())
text_timestamp_buffer_ = null;


if (null == text_timestamp_buffer_ )
text_timestamp_buffer_ = ByteBuffer.allocateDirect(text_bitmap.getByteCount());

text_bitmap.copyPixelsToBuffer(text_timestamp_buffer_);

int scale_w = 0, scale_h = 0, scale_filter_mode = 0;
//scale_w = align((int)(bitmapWidth*1.5 + 0.5), 2);
//scale_h = align((int)(bitmapHeight*1.5 + 0.5),2);
//scale_filter_mode = 3;

/*
if ( scale_w > 0 && scale_h > 0)
简单高效易用Windows/Linux/ARM/Android/iOS平台实现RTMP推送组件EasyRTMP-Android MediaCodec硬编码流程介绍

简单高效易用的全平台(Windows/Linux/ARM/Android/iOS)web实现RTMP推送组件EasyRTMP-Android BUS传递消息过程介绍

Android平台实现系统内录(捕获播放的音频)并推送RTMP服务技术方案探究

Android平台GB28181接入端如何对接UVC摄像头?

实现手机直播推送屏幕推送及录像功能RTMP推流组件之EasyRTMP-Android使用H265编码流程

Android平台GB28181接入端如何对接UVC摄像头?