技术背景
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的预期就是专注、极致、智慧、比快更快,做音视频行业的基石,帮助更多的行业,更少的精力实现音视频接入能力,任何行业,持续进步,才会有更大的收获。从另一个角度来说,看似每一次的精进,对我们技术从业者来说,都是持续的乐趣。