技术背景
Android平台推流模块,添加文字或png水印,不是一件稀奇的事儿,常规的做法也非常多,本文,我们主要是以大牛直播SDK水印迭代,谈谈音视频行业的精进和工匠精神。
第一代:不可动态改变的文字、png水印
2015年,我们在做Android平台RTMP推送模块和轻量级RTSP服务模块的时候,有这样的场景诉求,应急指挥、智慧巡检或安防类,都有文字或png水印的技术诉求,针对这种情况,我们当时做了如下的接口设计:
/* * 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%的技术诉求:
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层,减少了一次拷贝,特别是在频繁水印处理时,提高了数据处理效率。
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; }
对应封装设计:
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的预期就是专注、极致、智慧、比快更快,做音视频行业的基石,帮助更多的行业,更少的精力实现音视频接入能力,任何行业,持续进步,才会有更大的收获。从另一个角度来说,看似每一次的精进,对我们技术从业者来说,都是持续的乐趣。