Discusión e implementación técnica de la especificación de recuperación de archivos de audio y video históricos GB28181 en la plataforma Android

experiencia técnica

Cuando trabajamos en el módulo lateral de acceso al dispositivo GB28181 en la plataforma Android, especialmente para grabadoras policiales o escenarios similares, el sistema no solo tiene requisitos para la grabación de video convencional, sino que también debe poder interactuar con el lado de la plataforma GB28181. como realizar la recuperación, descarga o reproducción de archivos de audio de la vista lateral del dispositivo. Este artículo supone que la grabadora o el equipo relacionado ha completado la grabación y analiza principalmente la recuperación de archivos de video y audio en el equipo.

Interpretación estándar

Primero revise los requisitos básicos para la recuperación de archivos de audio y video GB/T28181-2016:

La recuperación de archivos utiliza principalmente área, equipo, período de tiempo de grabación, ubicación de grabación y contenido de la grabación como condiciones para la consulta. Los mensajes de mensaje se utilizan para enviar solicitudes de recuperación y devolver resultados de la consulta. Se pueden enviar múltiples mensajes de mensaje para transmitir los resultados. Apéndice N multi -Se deben soportar mensajes de respuesta.Requisitos de transmisión. Los comandos de solicitud y respuesta de recuperación de archivos se definen en el formato de protocolo MANSCDP.

Proceso de comando:

El proceso de señalización se describe a continuación:

  1. La dirección de recuperación del directorio envía un mensaje de solicitud de consulta de directorio al propietario del directorio, y el cuerpo del mensaje contiene las condiciones de recuperación de archivos de video y audio;
  2. El propietario del directorio envía 200 OK, sin cuerpo de mensaje, al buscador del directorio;
  3. El propietario del directorio envía los resultados de la consulta al buscador del directorio. El cuerpo del mensaje contiene el directorio de archivos. Cuando un mensaje de mensaje no puede transmitir todos los resultados de la consulta, se utilizan varios mensajes para transmitirlos;
  4. La dirección de recuperación del directorio envía 200 OK al propietario del directorio, sin cuerpo de mensaje.

Ejemplos de resultados sin consulta son los siguientes:

<?xml version="1.0" encoding="GB2312"?>
<Query>
  <CmdType>RecordInfo</CmdType>
  <SN>405331641</SN>
  <DeviceID>34020000001380000001</DeviceID>
  <StartTime>2023-09-04T00:00:00</StartTime>
  <EndTime>2023-09-04T06:00:00</EndTime>
  <Type>all</Type>
</Query>

Si no se encuentra ningún vídeo, el dispositivo responderá de la siguiente manera: Si no se encuentra ningún archivo, el contenido del elemento <SumNum> se rellenará con "0" y el elemento <RecordList> no se incluirá:

<?xml version="1.0" encoding="GB2312"?>
<Response>
<CmdType>RecordInfo</CmdType>
<SN>405331641</SN>
<DeviceID>34020000001380000001</DeviceID>
<Name>DaniuSDK</Name>
<SumNum>0</SumNum>
</Response>

Hay resultados de la consulta:

<Query>
  <CmdType>RecordInfo</CmdType>
  <SN>68331900</SN>
  <DeviceID>34020000001380000001</DeviceID>
  <StartTime>2023-09-04T06:00:00</StartTime>
  <EndTime>2023-09-04T12:00:00</EndTime>
  <Type>all</Type>
</Query>

La respuesta del lado del dispositivo es la siguiente:

<Response>
<CmdType>RecordInfo</CmdType>
<SN>68331900</SN>
<DeviceID>34020000001380000001</DeviceID>
<Name>DaniuSDK</Name>
<SumNum>6</SumNum>
<RecordList Num="3">
<Item>
<DeviceID>34020000001380000001</DeviceID>
<Name>DaniuSDK</Name>
<StartTime>2023-09-04T10:11:56</StartTime>
<EndTime>2023-09-04T10:12:58</EndTime>
<Secrecy>0</Secrecy>
</Item>
<Item>
<DeviceID>34020000001380000001</DeviceID>
<Name>DaniuSDK</Name>
<StartTime>2023-09-04T10:13:07</StartTime>
<EndTime>2023-09-04T10:15:33</EndTime>
<Secrecy>0</Secrecy>
</Item>
<Item>
<DeviceID>34020000001380000001</DeviceID>
<Name>DaniuSDK</Name>
<StartTime>2023-09-04T10:15:37</StartTime>
<EndTime>2023-09-04T10:16:32</EndTime>
<Secrecy>0</Secrecy>
</Item>
</RecordList>
</Response>

Cabe señalar que el tamaño de la solicitud de MENSAJE SIP fuera de la sesión no puede exceder los 1300 bytes.

Realización técnica

Tomando como ejemplo el lado de acceso al dispositivo GB28181 de la plataforma Android de Daniu Live SDK, la lógica de la interfaz de diseño es la siguiente:

package com.gb.ntsignalling;
 
public interface GBSIPAgent {
    void addListener(GBSIPAgentListener listener);
 
    void addPlayListener(GBSIPAgentPlayListener playListener);
 
    void removePlayListener(GBSIPAgentPlayListener playListener);
 
    void addDownloadListener(GBSIPAgentDownloadListener downloadListener);
 
    void removeDownloadListener(GBSIPAgentDownloadListener removeListener);
 
    void addTalkListener(GBSIPAgentTalkListener talkListener);
 
    void removeTalkListener(GBSIPAgentTalkListener talkListener);
 
    void addAudioBroadcastListener(GBSIPAgentAudioBroadcastListener audioBroadcastListener);
 
    void addDeviceControlListener(GBSIPAgentDeviceControlListener deviceControlListener);
 
    void addQueryCommandListener(GBSIPAgentQueryCommandListener queryCommandListener);
 
    void addQueryRecordInfoListener(GBSIPAgentQueryRecordInfoListener queryRecordInfoListener);
 
    /*
    历史视音频文件检索应答
     */
    boolean respondRecordInfoQueryCommand(String fromUserName, String fromUserNameAtDomain, String toUserName,String deviceName, RecordQueryInfo queryInfo,
                                          java.util.List<RecordFileInfo> recordList);
}

RecordQueryInfo está diseñado de la siguiente manera:

//GBSIPAgentQueryRecordInfoListener
//Author: daniusdk.com

package com.gb.ntsignalling;
 
public interface GBSIPAgentQueryRecordInfoListener {
 
    void ntsOnQueryRecordInfoCommand(String fromUserName, String fromUserNameAtDomain,
                                     String toUserName,
                                     RecordQueryInfo recordQueryInfo);
}
 
 
package com.gb.ntsignalling;
public interface RecordQueryInfo {
 
    /*
     *命令序列号(必选)
     */
    String getSN();
 
    /*
     * 目录设备/视频监控联网系统/区域编码(必选)
     */
    String getDeviceID();
 
    /*
     * 录像起始时间(必选)
     */
    String getStartTime();
 
    /*
     * 录像终止时间(必选)
     */
    String getEndTime();
 
    /*
     * 文件路径名 (可选)
     */
    String getFilePath();
 
    /*
     * 录像地址(可选 支持不完全查询)
     */
    String getAddress();
 
    /*
     * 保密属性(可选)缺省为0;0:不涉密,1:涉密
     */
    String getSecrecy();
 
    /*
     * 录像产生类型(可选)time或alarm 或 manual或all
     */
    String getType();
 
    /*
     * 录像触发者ID(可选)
     */
    String getRecorderID();
 
    /*
     *录像模糊查询属性(可选)缺省为0;0:不进行模糊查询,此时根据 SIP 消息中 To头域
     *URI中的ID值确定查询录像位置,若ID值为本域系统ID 则进行中心历史记录检索,若为前
     *端设备ID则进行前端设备历史记录检索;1:进行模糊查询,此时设备所在域应同时进行中心
     *检索和前端检索并将结果统一返回.
     */
    String getIndistinctQuery();
}

RecordFileInfo está diseñado de la siguiente manera:

//RecordFileInfo.java
//Author: daniusdk.com

package com.gb.ntsignalling;
 
public class RecordFileInfo {
 
    /* 设备/区域编码(必选) */
    private String mDeviceID;
 
    /* 设备/区域名称(必选) */
    private String mName;
 
    /*文件路径名 (可选)*/
    private String mFilePath;
 
    /*录像地址(可选)*/
    private String mAddress;
 
    /*录像开始时间(可选)*/
    private String mStartTime;
 
    /*录像结束时间(可选)*/
    private String mEndTime;
 
    /*保密属性(必选)缺省为0;0:不涉密,1:涉密*/
    private String mSecrecy = "0";
 
    /*录像产生类型(可选)time或alarm 或 manual*/
    private String mType;
 
    /*录像触发者ID(可选)*/
    private String mRecorderID;
 
    /*录像文件大小,单位:Byte(可选)*/
    private String mFileSize;
 
    public RecordFileInfo() { }
 
    public RecordFileInfo(String deviceID) {
        this.setDeviceID(deviceID);
    }
 
    public RecordFileInfo(String deviceID, String name) {
        this.setDeviceID(deviceID);
        this.setName(name);
    }
 
    public String getDeviceID() {
        return mDeviceID;
    }
 
    public void setDeviceID(String deviceID) {
        this.mDeviceID = deviceID;
    }
 
    public String getName() {
        return mName;
    }
 
    public void setName(String name) {
        this.mName = name;
    }
 
    public String getFilePath() {
        return mFilePath;
    }
 
    public void setFilePath(String filePath) {
        this.mFilePath = filePath;
    }
 
    public String getAddress() {
        return mAddress;
    }
 
    public void setAddress(String address) {
        this.mAddress = address;
    }
 
    public String getStartTime() {
        return mStartTime;
    }
 
    public void setStartTime(String startTime) {
        this.mStartTime = startTime;
    }
 
    public String getEndTime() {
        return mEndTime;
    }
 
    public void setEndTime(String endTime) {
        this.mEndTime = endTime;
    }
 
    public String getSecrecy() {
        return mSecrecy;
    }
 
    public void setSecrecy(String secrecy) {
        this.mSecrecy = secrecy;
    }
 
    public String getType() {
        return mType;
    }
 
    public void setType(String type) {
        this.mType = type;
    }
 
    public String getRecorderID() {
        return mRecorderID;
    }
 
    public void setRecorderID(String recorderID) {
        this.mRecorderID = recorderID;
    }
 
    public String getFileSize() {
        return mFileSize;
    }
 
    public void setFileSize(String fileSize) {
        this.mFileSize = fileSize;
    }
}

La lógica de llamada es la siguiente:

package com.mydemo;
	
import com.gb.ntsignalling.GBSIPAgentQueryRecordInfoListener;
	
public class AndroidG8181DemoImpl implements GBSIPAgentQueryRecordInfoListener {
 
    private static class QueryRecordInfoTask extends RecordExecutorService.CancelableTask {
        @Override
        public void run() {
            RecordBaseQuery base_query = new RecordBaseQuery(get_canceler(), rec_dir_);
            java.util.Date start_time_lower =  base_query.parser_xml_date_time(record_query_info_.getStartTime());
            java.util.Date start_time_upper = base_query.parser_xml_date_time(record_query_info_.getEndTime());
            if (null == start_time_lower || null == start_time_upper) {
                Log.e(TAG, "start_time_lower:" + start_time_lower + " or start_time_upper:" + start_time_upper + " is null");
                return;
            }
 
            base_query.set_start_time_lower(start_time_lower);
            base_query.set_start_time_upper(start_time_upper);
 
            List<RecordFileDescription> file_list =  base_query.execute();
            if (is_cancel())
                return;
 
            file_list =  base_query.sort_by_start_time_asc(file_list);
            if (is_cancel())
                return;
 
            List<com.gb.ntsignalling.RecordFileInfo> list = base_query.to_record_file_info_list(file_list, record_query_info_.getDeviceID(), null);
            if (is_cancel())
                return;
 
            if (file_list != null) {
                for (RecordFileDescription i : file_list)
                    Log.i(TAG, i.toString(base_query.get_print_begin_date_time_format(), base_query.get_print_end_date_time_format()));
            }
 
            if (is_cancel() ||null == handler_ || null == sip_agent_)
                return;
 
            Handler handler = handler_.get();
            GBSIPAgent sip_agent = sip_agent_.get();
            if (null == handler || null == sip_agent)
                return;
 
            handler.post(new Runnable() {
                @Override
                public void run() {
                    if (null == this.sip_agent_)
                        return;
 
                    GBSIPAgent sip_agent = this.sip_agent_.get();
                    if (null == sip_agent)
                        return;
 
                    if (this.canceler_ != null && this.canceler_.get())
                        return;
 
                    String device_name = null;
                    sip_agent.respondRecordInfoQueryCommand(from_user_name_, from_user_name_at_domain_,
                            to_user_name_, device_name, this.record_query_info_, this.record_list_);
                }
 
                private WeakReference<GBSIPAgent> sip_agent_;
                private AtomicBoolean canceler_;
                private String from_user_name_;
                private String from_user_name_at_domain_;
                private String to_user_name_;
                private RecordQueryInfo record_query_info_;
                private List<RecordFileInfo> record_list_;
 
                public Runnable set(GBSIPAgent sip_agent, AtomicBoolean canceler, String from_user_name, String from_user_name_at_domain, String to_user_name,
                                    RecordQueryInfo record_query_info, List<RecordFileInfo> record_list) {
                    this.sip_agent_ = new WeakReference<>(sip_agent);
                    this.canceler_ = canceler;
                    this.from_user_name_ = from_user_name;
                    this.from_user_name_at_domain_ = from_user_name_at_domain;
                    this.to_user_name_ = to_user_name;
                    this.record_query_info_ = record_query_info;
                    this.record_list_ = record_list;
                    return this;
                }
            }.set(sip_agent, get_canceler(), this.from_user_name_, this.from_user_name_at_domain_, this.to_user_name_,
                    this.record_query_info_, list));
        }
 
        public QueryRecordInfoTask set(Handler handler, GBSIPAgent sip_agent, String rec_dir,
                                       String from_user_name, String from_user_name_at_domain,
                                       String to_user_name, RecordQueryInfo query_info) {
            this.handler_ = new WeakReference<>(handler);
            this.sip_agent_ = new WeakReference<>(sip_agent);
            this.rec_dir_ = rec_dir;
            this.from_user_name_ = from_user_name;
            this.from_user_name_at_domain_ = from_user_name_at_domain;
            this.to_user_name_ = to_user_name;
            this.record_query_info_ = query_info;
            return this;
        }
 
        private WeakReference<Handler> handler_;
        private WeakReference<GBSIPAgent> sip_agent_;
        private String rec_dir_;
        private String from_user_name_;
        private String from_user_name_at_domain_;
        private String to_user_name_;
        private RecordQueryInfo record_query_info_;
    }
 
 @Override
    public void ntsOnQueryRecordInfoCommand(String fromUserName, String fromUserNameAtDomain, final String toUserName,
                                            RecordQueryInfo recordQueryInfo) {
        handler_.post(new Runnable() {
            @Override
            public void run() {
                Log.i(TAG, "ntsOnQueryRecordInfoCommand from_user_name:" + from_user_name_ + ", to_user_name:" + to_user_name_
                        + ", sn:" + record_query_info_.getSN()  + ", device_id:" + record_query_info_.getDeviceID() +
                         ", start_time:" + record_query_info_.getStartTime() + ", end_time:" + record_query_info_.getEndTime());
 
                    QueryRecordInfoTask query_task = new QueryRecordInfoTask();
                    query_task.set(handler_, gb28181_agent_, recDir, from_user_name_, from_user_name_at_domain_, to_user_name_, record_query_info_);
                    if (!record_executor_.submit(query_task))
                        Log.e(TAG, "ntsOnQueryRecordInfoCommand call record_executor_.submit failed");
            }
 
            private String from_user_name_;
            private String from_user_name_at_domain_;
            private String to_user_name_;
            private RecordQueryInfo record_query_info_;
 
            public Runnable set(String from_user_name, String from_user_name_at_domain, String to_user_name, RecordQueryInfo record_query_info) {
                this.from_user_name_ = from_user_name;
                this.from_user_name_at_domain_ = from_user_name_at_domain;
                this.to_user_name_ = to_user_name;
                this.record_query_info_ = record_query_info;
                return this;
            }
 
        }.set(fromUserName, fromUserNameAtDomain, toUserName, recordQueryInfo));
    }
}

Resumir

Consultar los archivos del historial de audio de vista lateral del dispositivo GB28181 no parece difícil, pero de hecho hay mucha lógica que necesita ser procesada. Los desarrolladores interesados ​​pueden discutirlo conmigo a través de un mensaje privado a través de la plataforma.

Supongo que te gusta

Origin blog.csdn.net/renhui1112/article/details/132684382
Recomendado
Clasificación