Android平台GB28181设计开发的时候,有个功能必不可少的:实时快照,特别是用于执法记录仪等场景下,用于图像留底或分析等考量。
实时快照的实现并不难,目前实现有两种方式,一种是拿到数据(比如摄像头数据)后,直接上层编码,存取快照;另一种模式是,数据投递到JNI层,底层针对投递过来的编码前数据,做png编码,并保存到设定的目录下,今天我们主要探讨第二种实现模式。
我们在publisher实例初始化的时候,设置是否启用快照:
private void InitAndSetConfig() { int audio_opt = 1; int fps = 18; int gop = fps * 2; Log.i(TAG, "InitAndSetConfig video_width: " + video_width_ + " cur_video_height" + video_height_ + " imageRotationDegree:" + cameraImageRotationDegree_); publisherHandle = libPublisher.SmartPublisherOpen(context_, audio_opt, 3, video_width_, video_height_); if (publisherHandle == 0) { Log.e(TAG, "sdk open failed!"); return; } Log.i(TAG, "publisherHandle=" + publisherHandle); if(videoEncodeType == 1) { int h264HWKbps = setHardwareEncoderKbps(true, video_width_, video_height_); h264HWKbps = h264HWKbps*fps/25; Log.i(TAG, "h264HWKbps: " + h264HWKbps); int isSupportH264HWEncoder = libPublisher .SetSmartPublisherVideoHWEncoder(publisherHandle, h264HWKbps); if (isSupportH264HWEncoder == 0) { libPublisher.SetNativeMediaNDK(publisherHandle, 1); Log.i(TAG, "Great, it supports h.264 hardware encoder!"); } } else if (videoEncodeType == 2) { int hevcHWKbps = setHardwareEncoderKbps(false, video_width_, video_height_); hevcHWKbps = hevcHWKbps*fps/25; Log.i(TAG, "hevcHWKbps: " + hevcHWKbps); int isSupportHevcHWEncoder = libPublisher .SetSmartPublisherVideoHevcHWEncoder(publisherHandle, hevcHWKbps); if (isSupportHevcHWEncoder == 0) { libPublisher.SetNativeMediaNDK(publisherHandle, 1); Log.i(TAG, "Great, it supports hevc hardware encoder!"); } } boolean is_sw_vbr_mode = true; if(is_sw_vbr_mode) //H.264 software encoder { int is_enable_vbr = 1; int video_quality = CalVideoQuality(video_width_, video_height_, true); int vbr_max_bitrate = CalVbrMaxKBitRate(video_width_, video_height_); libPublisher.SmartPublisherSetSwVBRMode(publisherHandle, is_enable_vbr, video_quality, vbr_max_bitrate); } if (is_pcma_) { libPublisher.SmartPublisherSetAudioCodecType(publisherHandle, 3); } else { libPublisher.SmartPublisherSetAudioCodecType(publisherHandle, 1); } libPublisher.SetSmartPublisherEventCallbackV2(publisherHandle, new EventHandePublisherV2()); libPublisher.SmartPublisherSetSWVideoEncoderProfile(publisherHandle, 3); libPublisher.SmartPublisherSetSWVideoEncoderSpeed(publisherHandle, 2); libPublisher.SmartPublisherSetGopInterval(publisherHandle, gop); libPublisher.SmartPublisherSetFPS(publisherHandle, fps); // libPublisher.SmartPublisherSetSWVideoBitRate(publisherHandle, 600, 1200); boolean is_noise_suppression = true; libPublisher.SmartPublisherSetNoiseSuppression(publisherHandle, is_noise_suppression ? 1 : 0); boolean is_agc = false; libPublisher.SmartPublisherSetAGC(publisherHandle, is_agc ? 1 : 0); int echo_cancel_delay = 0; libPublisher.SmartPublisherSetEchoCancellation(publisherHandle, 1, echo_cancel_delay); libPublisher.SmartPublisherSaveImageFlag(publisherHandle, 1); }
记得留意最后一句:libPublisher.SmartPublisherSaveImageFlag(publisherHandle, 1);
相关接口说明如下:
/** * Set if needs to save image during publishing stream(设置是否启用快照) * * @param is_save_image: if with 1, it will save current image via the interface of SmartPlayerSaveImage(), if with 0: does not it * * @return {0} if successful */ public native int SmartPublisherSaveImageFlag(long handle, int is_save_image);
设置后,页面有需要实时快照的数据时,点击“实时快照”按钮即可,需要注意的是,实时快照需要存储路径和保存下来的快照文件名称。具体实现如下:
class ButtonCaptureImageListener implements View.OnClickListener { @SuppressLint("SimpleDateFormat") public void onClick(View v) { if(isPushingRtmp || isRecording || isRTSPPublisherRunning || isGB28181StreamRunning) { String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); String imageFileName = "dn_" + timeStamp; //创建以时间命名的文件名称 String imagePath = imageSavePath + "/" + imageFileName + ".png"; Log.i(TAG, "imagePath:" + imagePath); libPublisher.SmartPublisherSaveCurImage(publisherHandle, imagePath); } else { Log.e(TAG, "快照失败,请确保在推送、录像、GB28181推送或内置RTSP服务发布状态.."); } } }
快照截取成功后,publisher实例会回调以下事件:
case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_CAPTURE_IMAGE: publisher_event = "快照: " + param1 + " 路径:" + param3; if (param1 == 0) { publisher_event = publisher_event + "截取快照成功.."; } else { publisher_event = publisher_event + "截取快照失败.."; } break;
由于实时快照,功能复杂度不高,而且也可以在上层实现,很容易被忽略,如果做的精细的话,还是需要开发者花费一定的心思。