技术背景
我们在Android平台实现GB28181设备接入,把摄像头和麦克风数据,采集过去,用于移动单兵、智能车载、智慧安防、智能家居、工业仿真等行业时,发现大多场景对视频水印的要求越来越高,从之前的固定位置静态文字水印、png水印等慢慢过渡到动态水印需求。
本文,我们要探讨的是,除了常规的时间、经纬度信息获png水印外,如何叠加电量和设备信号状态到视频view。
如何获取电量信息
在Android平台上获取电量信息可以使用以下三种方式:
- 通过BatteryManager类获取电量信息:
可以使用Context.getSystemService()方法获取BatteryManager实例,并使用该实例的getIntExtra()方法获取电量信息。具体代码如下:
BatteryManagerbatteryManager= (BatteryManager) getSystemService(Context.BATTERY_SERVICE); intbatteryLevel=batteryManager.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); intbatteryScale=batteryManager.getIntExtra(BatteryManager.EXTRA_SCALE, -1); intbatteryPlugType=batteryManager.getIntExtra(BatteryManager.EXTRA_PLUGED, -1); booleanisCharging=batteryManager.getIntExtra(BatteryManager.EXTRA_CHARGING, 0) ==1;
- 通过PowerManager类获取电量信息:
可以使用Context.getSystemService()方法获取PowerManager实例,并使用该实例的isDeviceIdle()方法和isPowerSaveMode()方法获取电量信息。具体代码如下:
PowerManagerpowerManager= (PowerManager) getSystemService(Context.POWER_SERVICE); booleanisDeviceIdle=powerManager.isDeviceIdle(); booleanisPowerSaveMode=powerManager.isPowerSaveMode();
- 通过UsageStatsManager类获取电量信息:
可以使用Context.getSystemService()方法获取UsageStatsManager实例,并使用该实例的queryStats()方法获取电量信息。具体代码如下:
UsageStatsManagerusageStatsManager= (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE); longtime=System.currentTimeMillis(); UsageStatsstats=usageStatsManager.queryStats(UsageStatsManager.INTERVAL_DAILY, time); longtotalTime=stats.getTotalTime(); longscreenTime=stats.getScreenTime(); floatbatteryLevel= (totalTime*100) /screenTime;
如何获取设备信号
要获取Android设备的信号强度,可以使用TelephonyManager类中的getSignalStrength()方法。以下是一个示例代码片段,演示如何获取设备的信号强度:
TelephonyManagertelephonyManager= (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); intsignalStrength=telephonyManager.getSignalStrength();
此代码将返回设备的信号强度,单位为dBm(分贝毫瓦)。如果设备没有电话卡,则返回值为int最小值(0)。
请注意,要使用TelephonyManager类,您需要在AndroidManifest.xml文件中添加以下权限:
<uses-permissionandroid:name="android.permission.READ_PHONE_STATE"/>
此权限允许您的应用程序访问设备的电话状态和信息。
如何把设备电量信息和设备信号状态叠加到view
叠加设备电量和设备实时信号状态,实际上,调用的是我们动态文字水印,通过生成TextBitmap,然后从bitmap里面拷贝获取到text_timestamp_buffer_,通过我们设计的PostLayerImageRGBA8888ByteBuffer()投递到jni层。
privateintpostTimestampLayer(intindex, intleft, inttop) { Bitmaptext_bitmap=makeTextBitmap(makeTimestampString(), getFontSize(), Color.argb(255, 0, 0, 0), true, Color.argb(255, 255, 255, 255),true); if (null==text_bitmap) return0; 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_); intscale_w=0, scale_h=0, scale_filter_mode=0; libPublisher.PostLayerImageRGBA8888ByteBuffer(handle_, index, left, top, text_timestamp_buffer_, 0, text_bitmap.getRowBytes(), text_bitmap.getWidth(), text_bitmap.getHeight(), 0, 0, scale_w, scale_h, scale_filter_mode,0); intret=scale_h>0?scale_h : text_bitmap.getHeight(); text_bitmap.recycle(); returnret; }
文字水印
文字水印不再赘述,主要注意的是文字的大小、颜色、位置。
privateintpostText1Layer(intindex, intleft, inttop) { Bitmaptext_bitmap=makeTextBitmap("文本水印一", getFontSize()+8, Color.argb(255, 200, 250, 0), false, 0,false); if (null==text_bitmap) return0; ByteBufferbuffer=ByteBuffer.allocateDirect(text_bitmap.getByteCount()); text_bitmap.copyPixelsToBuffer(buffer); libPublisher.PostLayerImageRGBA8888ByteBuffer(handle_, index, left, top, buffer, 0, text_bitmap.getRowBytes(), text_bitmap.getWidth(), text_bitmap.getHeight(), 0, 0, 0, 0, 0,0); intret=text_bitmap.getHeight(); text_bitmap.recycle(); returnret; }
最终投递接口设计如下,接口不再赘述,几乎你期望的针对图像的处理,都已覆盖:
/*** 投递层RGBA8888图像,如果不需要Aplpha通道的话, 请使用RGBX8888接口, 效率高** @param index: 层索引, 必须大于等于0, 注意:如果index是0的话,将忽略Alpha通道** @param left: 层叠加的左上角坐标, 对于第0层的话传0** @param top: 层叠加的左上角坐标, 对于第0层的话传0** @param rgba_plane: rgba 图像数据** @param offset: 图像偏移, 这个主要目的是用来做clip的, 一般传0** @param row_stride: stride information** @param width: width, 必须大于1, 如果是奇数, 将减1** @param height: height, 必须大于1, 如果是奇数, 将减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*/publicnativeintPostLayerImageRGBA8888ByteBuffer(longhandle, intindex, intleft, inttop, ByteBufferrgba_plane, intoffset, introw_stride, intwidth, intheight, intis_vertical_flip, intis_horizontal_flip, intscale_width, intscale_height, intscale_filter_mode, introtation_degree);
以上水印的显示控制,我们通过LayerPostThread封装处理:
/** LayerPostThread实现动态水印封装* Author: https://daniusdk.com*/classLayerPostThreadextendsThread{ privatefinalintupdate_interval=400; // 400 毫秒privatevolatilebooleanis_exit_=false; privatelonghandle_=0; privateintwidth_=0; privateintheight_=0; privatevolatilebooleanis_text_=false; privatevolatilebooleanis_picture_=false; privatevolatilebooleanclear_flag_=false; privatefinalinttimestamp_index_=1; privatefinalinttext1_index_=2; privatefinalinttext2_index_=3; privatefinalintpicture_index_=4; privatefinalintrectangle_index_=5; ByteBuffertext_timestamp_buffer_=null; ByteBufferrectangle_buffer_=null; publicvoidrun() { text_timestamp_buffer_=null; rectangle_buffer_=null; if (0==handle_) return; booleanis_posted_pitcure=false; booleanis_posted_text1=false; booleanis_posted_text2=false; intrectangle_aplha=0; while(!is_exit_) { longt=SystemClock.elapsedRealtime(); if (clear_flag_) { clear_flag_=false; is_posted_pitcure=false; is_posted_text1=false; is_posted_text2=false; if (!is_text_||!is_picture_) { rectangle_aplha=0; libPublisher.RemoveLayer(handle_, rectangle_index_); } } intcur_h=8; intret=0; if (!is_exit_&&is_text_) { ret=postTimestampLayer(timestamp_index_, 0, cur_h); if ( ret>0 ) cur_h=align(cur_h+ret+2, 2); } if(!is_exit_&&is_text_&&!is_posted_text1) { cur_h+=6; ret=postText1Layer(text1_index_, 0, cur_h); if ( ret>0 ) { is_posted_text1=true; cur_h=align(cur_h+ret+2, 2); } } if (!is_exit_&&is_picture_&&!is_posted_pitcure) { ret=postPictureLayer(picture_index_, 0, cur_h); if ( ret>0 ) { is_posted_pitcure=true; cur_h=align(cur_h+ret+2, 2); } } if(!is_exit_&&is_text_&&!is_posted_text2) { postText2Layer(text2_index_); is_posted_text2=true; } // 这个是演示一个矩形, 不需要可以屏蔽掉if (!is_exit_&&is_text_&&is_picture_) { postRGBRectangle(rectangle_index_, rectangle_aplha); rectangle_aplha+=8; if (rectangle_aplha>255) rectangle_aplha=0; } waitSleep((int)(SystemClock.elapsedRealtime() -t)); } text_timestamp_buffer_=null; rectangle_buffer_=null; }
实时文字水印可以通过控制显示还是隐藏:
publicvoidenableText(booleanis_text) { is_text_=is_text; clear_flag_=true; if (handle_!=0) { libPublisher.EnableLayer(handle_, timestamp_index_, is_text_?1:0); libPublisher.EnableLayer(handle_, text1_index_, is_text_?1:0); libPublisher.EnableLayer(handle_, text2_index_, is_text_?1:0); } }
如需移除图层,也可以调用RemoveLayer()接口,具体设计如下:
/*** 启用或者停用视频层, 这个接口必须在StartXXX之后调用.** @param index: 层索引, 必须大于0, 注意第0层不能停用** @param is_enable: 是否启用, 0停用, 1启用** @return {0} if successful*/publicnativeintEnableLayer(longhandle, intindex, intis_enable); /*** 移除视频层, 这个接口必须在StartXXX之后调用.** @param index: 层索引, 必须大于0, 注意第0层不能移除** @return {0} if successful*/publicnativeintRemoveLayer(longhandle, intindex);
针对启动水印类型等外层封装:
privateLayerPostThreadlayer_post_thread_=null; privatevoidstartLayerPostThread() { if (3==video_opt_) { if (null==layer_post_thread_) { layer_post_thread_=newLayerPostThread(); layer_post_thread_.startPost(publisherHandle, videoWidth, videoHeight, currentOrigentation, isHasTextWatermark(), isHasPictureWatermark()); } } } privatevoidstopLayerPostThread() { if (layer_post_thread_!=null) { layer_post_thread_.stopPost(); layer_post_thread_=null; } }
总结
Android平台获取设备电量信息和设备实时信号状态,然后叠加到视频view,投递出去,比如执法记录仪之类场景下,非常有价值,可以让GB28181平台侧,获取到更多需要的信息,扩展性极强。