规范解读
GB/T28181-2022相对2016版,除了对H.265、AAC有了明确的说明外,快照也有了具体的要求。源设备向目标设备发送图像抓拍配置命令,携带传输路径、会话ID等信息。目标设备完成图像传输后,发送图像抓拍传输完成通知命令,采用IETF RFC 3428中的MESSAGE方法实现,图像格式宜使用JPEG,图像分辨率宜采用与主码流相同的分辨率。
具体流程如下图:
需要注意的是,MESSAGE消息头Content-type头域要求Content-type:Application/MANSCDP+xml。图像传输方式宜采用http,图像抓拍传输完成通知命令采用MANSCDP协议格式定义。
技术实现
本文以大牛直播SDK实现的Android平台GB28181设备接入模块为例,介绍下快照实现逻辑,需要注意的是,哪怕设备侧不回传,也需要具备快照能力,并保存为jpeg格式,然后按照gb28181规范要求,把采集到的图片,上传到国标平台侧。
编辑
先说外部驱动快照:
/* * MainActivity.java * Author: daniusdk.com */ class ButtonCaptureImageListener implements View.OnClickListener { public void onClick(View v) { if (null == snap_shot_impl_) { snap_shot_impl_ = new SnapShotImpl(image_path_, context_, handler_, libPublisher, snap_shot_publisher_); snap_shot_impl_.start(); } startLayerPostThread(); snap_shot_impl_.set_layer_post_thread(layer_post_thread_); snap_shot_impl_.capture(); } }
SnapShotImpl实现如下:
public SnapShotImpl(String dir, Context context, android.os.Handler handler, SmartPublisherJniV2 lib_sdk, LibPublisherWrapper publisher) { this.dir_ = dir; if (context != null) this.context_ = new WeakReference<>(context); if (handler != null) this.os_handler_ = new WeakReference<>(handler); this.lib_sdk_ = lib_sdk; this.publisher_ = publisher; }
调用capture()的时候,处理逻辑如下:
protected boolean capture(String file_name, boolean is_update_layers, String user_data) { if (is_null_or_empty(file_name)) { Log.e(TAG, "capture file name is null"); return false; } if (publisher_.empty()) { if (!init_sdk_instance()) { Log.e(TAG, "init_sdk_instance failed"); return false; } } if (is_update_layers && layer_post_thread_ != null) layer_post_thread_.update_layers(); boolean ret = publisher_.CaptureImage(0, 100, file_name, user_data); if (ret) update_sdk_instance_release_time(); return ret; }
其中init_sdk_instance()实现如下:
protected boolean init_sdk_instance() { if (!publisher_.empty()) return true; Context context = application_context(); if (null == context) return false; if (null == lib_sdk_) return false; long handle = lib_sdk_.SmartPublisherOpen(context, 0, 3, 1920, 1080); if (0 == handle) { Log.e(TAG, "init_sdk_instance sdk open failed!"); return false; } lib_sdk_.SetSmartPublisherEventCallbackV2(handle, new SDKEventHandler(this)); lib_sdk_.SmartPublisherSetFPS(handle, 4); publisher_.set(lib_sdk_, handle); update_sdk_instance_release_time(); Log.i(TAG, "init_sdk_instance ok handle:" + handle); return true; }
对接gb28181平台部分,快照状态定义:
public final static int INITIALIZATION_STATUS = 0; // 初始状态 public final static int CAPTURING_STATUS = 1; // 抓拍中 public final static int CAPTURE_COMPLETION_STATUS = 2; // 抓拍完成状态 public final static int UPLOADING_STATUS = 3; // 图像上传中状态 public final static int UPLOAD_COMPLETION_STATUS = 4; // 图像上传完成状态 public final static int ERROR_STATUS = 5; // 错误状态
快照完成后,告知国标平台:
public void notify_server(List<String> notified_files) { ArrayList<String> snap_shot_list = new ArrayList(items_.size()); for (SnapItem i : items_) { if (SnapItem.UPLOAD_COMPLETION_STATUS == i.status()) snap_shot_list.add(i.gb_snap_shot_file_id()); if (notified_files != null) notified_files.add(i.file_name()); } if (null == agent_) return; GBSIPAgent agent = agent_.get(); if (null == agent) return; agent.notifyUploadSnapShotFinished(from_user_name_, from_user_name_at_domain_, device_id_, this.session_id(), snap_shot_list); }
技术总结
GB28181-2022快照实现,最好是单独开个实例,帧率根据实际快照间隔或者要求,不需要全帧率投递,在不录像不实时回传的时候,也能实现快照逻辑,然后按照规范要求和国标平台侧的客制化诉求,把快照后的文件上传到国标平台。