技术背景
前几年,我们发布的了Android平台GB28181设备接入模块,实现了不具备国标音视频能力的 Android终端,通过平台注册接入到现有的GB/T28181---2016或GB/T28181---2022服务。
Android终端除支持常规的音视频数据接入外,还可以支持移动设备位置(MobilePosition)订阅和通知、图像抓拍、语音广播和语音对讲、历史视音频下载和回放,支持对接数据类型如下:
- 编码前数据(目前支持的有YV12/NV21/NV12/I420/RGB24/RGBA32/RGB565等数据类型),其中,Android平台前后摄像头数据,或者屏幕数据,或者Unity拿到的数据,均属编码前数据;
- 编码后数据(如无人机等264/HEVC数据,或者本地解析的MP4音视频数据);
- 拉取RTSP或RTMP流并接入至GB28181平台(比如其他IPC的RTSP流,可通过Android平台GB28181接入到国标平台)。
实现的主要功能如下:
- [视频格式]H.264/H.265(Android H.265硬编码);
- [音频格式]G.711 A律、AAC;
- [音量调节]Android平台采集端支持实时音量调节;
- [H.264硬编码]支持H.264特定机型硬编码;
- [H.265硬编码]支持H.265特定机型硬编码;
- [软硬编码参数配置]支持gop间隔、帧率、bit-rate设置;
- [软编码参数配置]支持软编码profile、软编码速度、可变码率设置;
- 支持横屏、竖屏推流;
- Android平台支持后台service推送屏幕(推送屏幕需要5.0+版本);
- 支持纯视频、音视频PS打包传输;
- 支持RTP OVER UDP和RTP OVER TCP被动模式(TCP媒体流传输客户端);
- 支持信令通道网络传输协议TCP/UDP设置;
- 支持注册、注销,支持注册刷新及注册有效期设置;
- 支持设备目录查询应答;
- 支持心跳机制,支持心跳间隔、心跳检测次数设置;
- 支持移动设备位置(MobilePosition)订阅和通知;
- 适用国家标准:GB/T 28181---2016、GB/T28181---2022;
- 支持语音广播;
- 支持语音对讲;
- 支持图像抓拍;
- 支持历史视音频文件检索;
- 支持历史视音频文件下载;
- 支持历史视音频文件回放;
- 支持云台控制和预置位查询;
- [实时水印]支持动态文字水印、png水印;
- [镜像]Android平台支持前置摄像头实时镜像功能;
- [实时静音]支持实时静音/取消静音;
- [实时快照]支持实时快照;
- [降噪]支持环境音、手机干扰等引起的噪音降噪处理、自动增益、VAD检测;
- [外部编码前视频数据对接]支持YUV数据对接;
- [外部编码前音频数据对接]支持PCM对接;
- [外部编码后视频数据对接]支持外部H.264数据对接;
- [外部编码后音频数据对接]外部AAC数据对接;
- [扩展录像功能]支持和录像SDK组合使用,录像相关功能。
Android平台的GB28181设备接入端模块,对水印要求比较高,除了传统的文字外,还需要图片、时间戳等,更重要的是,有动态水印的技术诉求。下面我们从水印创建、叠加、更新等维度介绍下动态水印的实现。
创建水印内容:
-
实时时间水印:获取当前系统时间,并将其格式化为字符串,以便添加到视频帧中作为水印。例如,使用 SimpleDateFormat 类来格式化时间:
private String makeTimestampString() { if (null == date_format_) date_format_ = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
javascriptreturn date_format_.format(new Date());
}
-
文本水印:定义你想要添加到视频中的特定文本信息,比如设备编号、用户信息等。这些文本信息可以在代码中预先定义好,也可以根据实际情况从外部获取,比如从设备的配置文件中读取或者通过网络请求获取。
-
图片水印:准备好要作为水印的图片文件,可以将其放置在项目的资源文件夹中,然后通过 BitmapFactory 类来加载图片:
private Bitmap getAssetsBitmap() { if (null == context_) return null;
iniContext context = context_.get(); if (null == context) return null; Bitmap bitmap = null; try { InputStream s = context.getAssets().open("tca.png"); bitmap = BitmapFactory.decodeStream(s); s.close(); } catch (Exception e) { e.printStackTrace(); } return bitmap;
}
创建水印图像:
- 根据水印内容创建一个图像对象,用于表示水印。如果是文本水印,可以使用 Canvas 和 Paint 在一个空白的 Bitmap 上绘制文本。如果是图片水印,则直
接使用之前加载的图片 Bitmap 作为水印图像。不过,可能需要根据实际需求对图片进行缩放、旋转等处理,以适应视频帧的大小和方向。
将水印图像叠加到视频帧上:
- 将水印图像的像素数据与视频帧的像素数据进行合并处理,从而实现水印的叠加。这一步通常需要对像素数据进行逐行或逐像素的操作,根据一定的算法将水印图像的像素值与视频帧的像素值进行混合。
- 可以使用类似于图像处理软件中的图层叠加方式,将水印图像作为一个图层叠加到视频帧上。具体的实现方式可能因使用的视频处理库或框架而有所不同,但一般需要获取视频帧的像素数据缓冲区,然后在缓冲区中进行数据的修改和叠加操作。
- 在叠加水印时,需要注意水印的位置、透明度和大小等参数,以确保水印不会遮挡视频的重要内容,同时又能够清晰地显示出来。可以根据实际需求在代码中设置这些参数,或者提供用户界面让用户可以自定义水印的参数。
更新动态水印:
如果水印内容是动态变化的(如实时时间),需要定期更新水印图像。可以使用定时器或 Handler 来周期性地获取新的水印内容,重新创建水印图像,并将其叠加到视频帧上。例如,使用 Handler 的 postDelayed() 方法来实现定时更新。
SmartGBD动态水印设计
本文以大牛直播SDK的Android平台GB28181设备接入模块(SmartGBD)的动态水印为例,探讨下我们的动态水印设计实现,我们提供了图片水印、文字水印、全部水印、不加水印几个选项。
ini
/* SmartGbdActivity.java
* Created by daniusdk.com
* WeChat: xinsheng120
*/
watermarkSelctor = (Spinner) findViewById(R.id.watermarkSelctor);
final String[] watermarks = new String[]{"图片水印", "全部水印", "文字水印", "不加水印"};
ArrayAdapter<String> adapterWatermark = new ArrayAdapter<String>(this,
android.R.layout.simple_spinner_item, watermarks);
adapterWatermark.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
watermarkSelctor.setAdapter(adapterWatermark);
watermarkSelctor.setSelection(3,true);
watemarkType = 3; //默认不加水印
watermarkSelctor.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view,
int position, long id) {
watemarkType = position;
Log.i(TAG, "[水印类型]Currently choosing: " + watermarks[position] + ", watemarkType: " + watemarkType);
if (layer_post_thread_ != null) {
layer_post_thread_.enableText(isHasTextWatermark());
layer_post_thread_.enablePicture(isHasPictureWatermark());
layer_post_thread_.update_layers();
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
以文字水印设计和数据投递为例:
java
private int postText1Layer(List<LibPublisherWrapper> publisher_list, int index, int left, int top, int video_w, int video_h) {
Bitmap text_bitmap = makeTextBitmap("文本水印一", getFontSize(video_w) + 8,
Color.argb(255, 200, 250, 0),
false, 0, false);
if (null == text_bitmap)
return 0;
for (LibPublisherWrapper i : publisher_list)
i.PostLayerBitmap(index, left, top, text_bitmap, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0);
int ret = text_bitmap.getHeight();
text_bitmap.recycle();
return ret;
}
对应封装设计:
csharp
public boolean PostLayerBitmap(int index, int left, int top,
android.graphics.Bitmap bitmap, int clip_left, int clip_top, int clip_width, int clip_height,
int is_vertical_flip, int is_horizontal_flip,
int scale_width, int scale_height, int scale_filter_mode,
int rotation_degree) {
if (!check_native_handle())
return false;
if (!read_lock_.tryLock())
return false;
try {
if (!check_native_handle())
return false;
return OK == lib_publisher_.PostLayerBitmap(get(), index, left, top,
bitmap, clip_left, clip_top, clip_width, clip_height, is_vertical_flip, is_horizontal_flip,
scale_width, scale_height, scale_filter_mode, rotation_degree);
} catch (Exception e) {
Log.e(TAG, "PostLayerBitmap Exception:", e);
return false;
} finally {
read_lock_.unlock();
}
}
总结
Android平台的GB28181设备接入模块,主要用在如执法记录仪、智能安全帽、智能监控、智慧零售、智慧教育、远程办公、明厨亮灶、智慧交通、智慧工地、雪亮工程、平安乡村、生产运输、车载终端等场景,这些场景,好多对动态水印的诉求都非常大,动态水印的设计和实现,一方面需要足够灵活,另一方面,还需要尽可能的资源占用低,以上是大概的技术实现,感兴趣的开发者,可以跟我单独沟通探讨。