技术背景
Android平台推流模块,添加文字或png水印,不是一件稀奇的事儿,常规的做法也非常多,本文,我们主要是以大牛直播SDK水印迭代,谈谈音视频行业的精进和工匠精神。
第一代:不可动态改变的文字、png水印
2015年,我们在做Android平台RTMP推送模块和轻量级RTSP服务模块的时候,有这样的场景诉求,应急指挥、智慧巡检或安防类,都有文字或png水印的技术诉求,针对这种情况,我们当时做了如下的接口设计:
代码语言:java复制 /*
* SmartPublisherJniV2.java
* WebSite: https://daniusdk.com
*
* Created by DaniuLive on 2015/09/20.
*/
/**
* Set Text water-mark(设置文字水印)
*
* @param fontSize: it should be "MEDIUM", "SMALL", "BIG"
*
* @param waterPostion: it should be "TOPLEFT", "TOPRIGHT", "BOTTOMLEFT", "BOTTOMRIGHT".
*
* @param xPading, yPading: the distance of the original picture.
*
* <pre> The interface is only used for setting font water-mark when publishing stream. </pre>
*
* @return {0} if successful
*/
/*
* 已废弃, 请使用层模式加水印
*public native int SmartPublisherSetTextWatermark(long handle, String waterText, int isAppendTime, int fontSize, int waterPostion, int xPading, int yPading);
*/
/**
* Set Text water-mark font file name(设置文字水印字体路径)
*
* @param fontFileName: font full file name, e.g: /system/fonts/DroidSansFallback.ttf
*
* @return {0} if successful
*/
/* 已废弃, 请使用层模式加水印
* public native int SmartPublisherSetTextWatermarkFontFileName(long handle, String fontFileName);
*/
/**
* Set picture water-mark(设置png图片水印)
*
* @param picPath: the picture working path, e.g: /sdcard/logo.png
*
* @param waterPostion: it should be "TOPLEFT", "TOPRIGHT", "BOTTOMLEFT", "BOTTOMRIGHT".
*
* @param picWidth, picHeight: picture width & height
*
* @param xPading, yPading: the distance of the original picture.
*
* <pre> The interface is only used for setting picture(logo) water-mark when publishing stream, with "*.png" format </pre>
*
* @return {0} if successful
*/
/*
* 已废弃, 请使用层模式加水印
*public native int SmartPublisherSetPictureWatermark(long handle, String picPath, int waterPostion, int picWidth, int picHeight, int xPading, int yPading);
*/
第二代:实时动态文字、png水印
尽管上面的水印,已经可以满足大多技术场景的需求,但在我们内部,却被一直诟病,因为违背我们SDK设计和使用的smart策略。
随着Android平台GB28181设备接入模块的发布,基于GB28181设备接入模块,对水印提出来更高的要求,好多公司或开发者,需要实时更新水印内容(比如MobilePosition位置信息、实时时间、作业内容等),为此,我们想到的是,这一版,我们需要实现动态水印能力。
以文字水印为例,我们的实现和设计如下,通过bitmap获取到文字水印数据,然后通过PostLayerImageRGBA8888ByteBuffer()接口投递到jni,这种设计,几乎已经满足了100%的技术诉求:
代码语言: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;
ByteBuffer buffer = ByteBuffer.allocateDirect(text_bitmap.getByteCount());
text_bitmap.copyPixelsToBuffer(buffer);
for (LibPublisherWrapper i : publisher_list)
i.PostLayerImageRGBA8888ByteBuffer(index, left, top, buffer, 0,
text_bitmap.getRowBytes(), text_bitmap.getWidth(), text_bitmap.getHeight(),
0, 0, 0, 0, 0, 0);
int ret = text_bitmap.getHeight();
text_bitmap.recycle();
return ret;
}
第三代:Bitmap接口设计
尽管第二代水印设计,已经满足了技术层面的场景诉求,但从效率角度,我们认为还有进步的空间,为此,我们直接把生成的bitmap数据投递到jni层,减少了一次拷贝,特别是在频繁水印处理时,提高了数据处理效率。
代码语言: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;
}
对应封装设计:
代码语言:java复制 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();
}
}
总结
有人说,音视频行业最苦最没有意思、高投入低回报的就是做SDK。在我们看来,少一次拷贝、一次功能的迭代是进步,但大多数情况下,对于不了解细节的开发者看来,不深耕不细测很难看出端倪。大牛直播SDK的预期就是专注、极致、智慧、比快更快,做音视频行业的基石,帮助更多的行业,更少的精力实现音视频接入能力,任何行业,持续进步,才会有更大的收获。从另一个角度来说,看似每一次的精进,对我们技术从业者来说,都是持续的乐趣。