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

experiencia técnica

En el último blog, mencionamos la discusión y la implementación técnica de la especificación de recuperación de archivos históricos de video y audio GB28181 en la plataforma Android . Después de la recuperación de archivos, el lado de la plataforma GB28181 puede realizar operaciones de reproducción o descarga en la lista de archivos. Este artículo analiza principalmente la descarga de archivos de vídeo y audio.

Interpretación estándar

Requisitos básicos para descargar archivos de video y audio.

Después de recibir la solicitud de descarga de archivos de video y audio enviada por el receptor de medios, el servidor SIP envía un comando de descarga de archivos de medios al remitente del flujo de medios. El remitente del flujo de medios utiliza RTP para transmitir el flujo de video al receptor del flujo de medios y el medio El receptor de transmisión transmite directamente la transmisión de video al receptor de transmisión de medios. Guárdelo como archivo multimedia.

El receptor del flujo de medios puede ser un cliente de usuario o un sistema en red, y el remitente del flujo de medios puede ser un dispositivo de medios o un sistema en red. El receptor de flujo de medios o el servidor SIP puede obtener la velocidad de descarga y envío admitida por el remitente del flujo de medios a través de una consulta de configuración, etc., y transportar la velocidad de descarga especificada en el cuerpo del mensaje SDP solicitado.

El remitente del flujo de medios puede expandir el parámetro de tamaño del archivo descargado en el cuerpo del mensaje SDP de respuesta de 200 0K correspondiente a la solicitud de invitación, de modo que el receptor del flujo de medios pueda calcular el progreso de la descarga. Cuando el remitente del flujo de medios no puede proporcionar el parámetro de tamaño del archivo , el receptor de flujo de medios El descargador debe admitir el cálculo del progreso de la descarga en función del tiempo obtenido del flujo de código. Las descargas de archivos de vídeo y audio deben admitir el mecanismo de mantenimiento de transmisión de medios.

Proceso de comando

Entre ellos, la señalización 1, 8, 9, 10, 11, 12 es el proceso de señalización de establecer un enlace de medios entre el destinatario del flujo de medios y el servidor de medios a través del modo proxy B2BUA después de que el servidor SIP recibe la solicitud de llamada del cliente.

La señalización 2 a 7 es el proceso de señalización para que el servidor SIP establezca el enlace de medios entre el servidor de medios y el flujo de medios a través del control de llamadas de tres partes.

La señalización 13 a 16 es el proceso para que el remitente del flujo de medios reproduzca la descarga hasta el final del archivo y envíe un mensaje de notificación de finalización de la descarga al receptor de medios.

La señalización 17 ~ 20 es el proceso de señalización para que el receptor de flujo de medios desconecte el enlace de medios con el servidor de medios.

La señalización 21 ~ 24 es el proceso de señalización para que el servidor SIP desconecte el enlace de medios entre el servidor de medios y el remitente del flujo de medios.

El proceso de comando se describe a continuación:

  1. El receptor del flujo de medios envía un mensaje de invitación al servidor SIP. El campo del encabezado del mensaje incluye el campo Asunto, que indica la ID de la fuente de video bajo demanda, el número de serie del flujo de medios del remitente, el ID del receptor del flujo de medios y el número de serie del flujo de medios del extremo receptor. identificación del número y otros parámetros. SDP El campo s en el cuerpo del mensaje es "Descargar" para representar la descarga del archivo, el campo u representa el ID del canal de descarga y el tipo de descarga, y el campo representa el período de tiempo de descarga. El campo a se puede ampliar para llevar el parámetro de velocidad de descarga, que especifica la velocidad de descarga del dispositivo para esta descarga. Si no se lleva, la velocidad predeterminada es 1x.
  2. Después de recibir la solicitud de invitación, el servidor SIP establece una conexión de medios entre el servidor de medios y el remitente del flujo de medios a través del control de llamadas de tres partes. Envíe un mensaje de invitación al servidor de medios. Este mensaje no lleva un cuerpo de mensaje SDP.
  3. Después de recibir la solicitud de invitación del servidor SIP, el servidor de medios responde con una respuesta de 200 0K, que lleva el cuerpo del mensaje SDP, que describe el puerto IP, el formato de medios, etc., para que el servidor de medios reciba el flujo de medios.
  4. Después de recibir la respuesta 200 OK devuelta por el servidor de medios, el servidor SIP envía una solicitud de invitación al remitente del flujo de medios. La solicitud lleva el cuerpo del mensaje de respuesta 200 OK respondido por el servidor de medios en el mensaje 3. El campo s "Descargar" representa la descarga de archivos, el campo u representa el ID del canal de descarga y el tipo de descarga, el campo t representa el período de tiempo de descarga, el campo y se agrega para describir el valor SSRC, el campo f describe los parámetros de medios, y el campo a se puede expandir para llevar la velocidad de descarga múltiple, que se duplicará.Los parámetros pasados ​​al dispositivo.
  5. Después de recibir la solicitud de invitación del servidor SIP, el remitente del flujo de medios responde con una respuesta 200 OK, que lleva el cuerpo del mensaje SDP, que describe la IP, el puerto, el formato de medios, el campo SSRC y otros contenidos del flujo de medios enviado por el remitente del flujo de medios y es extensible. El campo a lleva el parámetro de tamaño de archivo.
  6. Después de recibir la respuesta 200 OK del remitente del flujo de medios, el servidor SIP envía una solicitud ACK al servidor de medios. La solicitud lleva el cuerpo del mensaje de respuesta 200 OK respondido por el remitente del flujo de medios en el mensaje 5 para completar el proceso de establecimiento de sesión de invitación con el servidor de medios.
  7. Después de recibir la respuesta 200 OK del remitente del flujo de medios, el servidor SIP envía una solicitud ACK al remitente del flujo de medios. La solicitud no lleva el cuerpo del mensaje y completa el proceso de establecimiento de sesión de invitación con el remitente del flujo de medios.
  8. Después de completar el control de llamadas tripartitas, el servidor SIP establece una conexión de medios entre el receptor de flujo de medios y el servidor de medios a través del método proxy B2BUA. Agregue el valor SSRC al mensaje 1 y reenvíelo al servidor de medios.
  9. El servidor de medios recibe la solicitud de invitación y responde con una respuesta 200 OK, que lleva el cuerpo del mensaje SDP, que describe la IP, el puerto, el formato de medios, el valor SSRC, etc., del flujo de medios enviado por el servidor de medios.
  10. El servidor SIP reenvía el mensaje 9 al receptor de flujo de medios y el campo a extensible lleva el parámetro de tamaño de archivo.
  11. Después de recibir la respuesta 200 OK, el receptor de flujo de medios responde con un mensaje ACK para completar el proceso de establecimiento de sesión de invitación con el servidor SIP.
  12. El servidor SIP reenvía el mensaje 11 al servidor de medios para completar el proceso de establecimiento de sesión de invitación con el servidor de medios.
  13. El transmisor de medios envía un mensaje durante la sesión una vez completada la descarga del archivo.
  14. El servidor SIP recibe el mensaje 17 y lo reenvía al receptor de flujo de medios.
  15. Después de recibir el mensaje 18, el receptor del flujo de medios responde con una respuesta 200 OK y realiza el proceso de desconexión del enlace.
  16. El servidor SIP reenvía el mensaje 19 al remitente del flujo de medios.
  17. El receptor de flujo de medios envía un mensaje BYE al servidor SIP para desconectar la sesión de invitación establecida con los mensajes 1, 10 y 11 con el receptor de flujo de medios.
  18. Después de recibir el mensaje BYE, el servidor SIP responde con una respuesta 200OK y la sesión se desconecta.
  19. Después de recibir el mensaje BYE, el servidor SIP envía un mensaje BYE al servidor de medios y desconecta la sesión de invitación con el servidor de medios establecida por los mensajes 8, 9 y 12.
  20. Después de recibir el mensaje BYE, el servidor de medios responde con una respuesta 200 OK y la sesión se desconecta.
  21. El servidor SIP envía un mensaje BYE al servidor de medios y desconecta la sesión de invitación con el servidor de medios establecida por los mensajes 2, 3 y 6.
  22. Después de recibir el mensaje BYE, el servidor de medios responde con una respuesta 200 OK y la sesión se desconecta.
  23. El servidor SIP envía un mensaje BYE al remitente del flujo de medios y desconecta la sesión de invitación con el remitente del flujo de medios establecida por los mensajes 4, 5 y 7.
  24. Después de recibir el mensaje BYE, el remitente del flujo de medios responde con una respuesta 200 OK y la sesión se desconecta.

Realización técnica

Este artículo toma como ejemplo el dispositivo GB28181 con plataforma Android desarrollado por Daniu Live SDK para acceder a la recuperación y descarga de archivos de historial de audio de vista lateral (este artículo se centra en la descarga) y presenta las ideas de diseño relevantes:

 El terminal de acceso del dispositivo Android recibe el INVITE SDP enviado desde la plataforma estándar nacional:

v=0
o=34020000001380000001 0 0 IN IP4 192.168.2.154
s=Download
u=34020000001380000001:0
c=IN IP4 192.168.2.154
t=1693796426 1693796703
m=video 30002 RTP/AVP 96 97 98
a=recvonly
a=rtpmap:96 PS/90000
a=rtpmap:97 MPEG4/90000
a=rtpmap:98 H264/90000
a=downloadspeed:4
y=1200000001

En el SDP anterior, s=Descarga significa descarga, a=downloadspeed:4 significa descarga de velocidad 4x, SSRC es: 1200000001 (el primer bit de SSRC es el bit de identificación del flujo de medios histórico o en tiempo real, 0 es video en tiempo real y audio, 1 es vídeo y audio históricos).

El terminal de acceso al dispositivo Android responde:

v=0
o=34020000011310000039 0 0 IN IP4 192.168.2.212
s=Download
c=IN IP4 192.168.2.212
t=0 0
m=video 36576 RTP/AVP 96
a=rtpmap:96 PS/90000
a=filesize:15611511
a=sendonly
y=1200000001

a=filesize:15611511 indica que el tamaño del archivo de video es 15611511Byte y lleva el parámetro de tamaño de archivo, lo cual es conveniente para que el receptor de transmisión de medios calcule el progreso de la descarga (a=filesize es el tamaño de todo el contenedor de medios y el número total de bytes de los fotogramas de audio y vídeo realmente enviados).

Después de que la plataforma estándar nacional envía un Ack, comienza a descargar los datos de video y audio. Durante el proceso de descarga, la velocidad de descarga se puede ajustar a través del mensaje SIP-INFO y el protocolo MANSRTSP:

PLAY RTSP/1.0
CSeq: 31129
Scale: 0.25

Una vez que el lado de acceso del dispositivo Android GB28181 completa el envío de fotogramas de audio y video, se envía el evento de notificación tipo "121", que indica que se completa el envío de archivos multimedia históricos, y el mensaje dentro de la sesión se envía de la siguiente manera:

<?xml version="1.0" encoding="GB2312"?>
<Notify>
<CmdType>MediaStatus</CmdType>
<SN>213466963</SN>
<DeviceID>34020000001380000001</DeviceID>
<NotifyType>121</NotifyType>
</Notify>
Diseño de interfaz

Diseño de interfaz de señalización:


/**
* Author: daniusdk.com
*/
package com.gb.ntsignalling;
 
public interface GBSIPAgent {
    void addDownloadListener(GBSIPAgentDownloadListener downloadListener);
 
    void removeDownloadListener(GBSIPAgentDownloadListener removeListener);
 
    /*
    *响应Invite Download 200 OK
    */
    boolean respondDownloadInviteOK(long id, String deviceId, String startTime, String stopTime, MediaSessionDescription localMediaDescription);
 
    /*
    *响应Invite Download 其他状态码
    */
    boolean respondDownloadInvite(int statusCode, long id, String deviceId, String startTime, String stopTime);
 
    /*
    * 媒体流发送者在文件下载结束后发Message消息通知SIP服务器回文件已发送完成
    * notifyType 必须是"121“
     */
    boolean notifyDownloadMediaStatus(long id, String deviceId, String startTime, String stopTime, String notifyType);
 
    /*
     *终止Download会话
     */
    void terminateDownload(long id, String deviceId, String startTime, String stopTime, boolean isSendBYE);
 
    /*
     *终止所有Download会话
     */
    void terminateAllDownloads(boolean isSendBYE);
 
}

Diseño de escucha de descarga de audio y video histórico:

/**
* Author: daniusdk.com
*/
package com.gb.ntsignalling;
 
public interface GBSIPAgentDownloadListener {
    /*
     *收到s=Download的文件下载Invite
     */
    void ntsOnInviteDownload(long id, String deviceId, SessionDescription sessionDescription);
 
    /*
     *发送Download invite response 异常
     */
    void ntsOnDownloadInviteResponseException(long id, String deviceId, String startTime, String stopTime, int statusCode, String errorInfo);
 
    /*
     * 收到CANCEL Download INVITE请求
     */
    void ntsOnCancelDownload(long id, String deviceId, String startTime, String stopTime);
 
    /*
     * 收到Ack
     */
    void ntsOnAckDownload(long id, String deviceId, String startTime, String stopTime);
 
    /*
    * 更改下载速度
     */
    void ntsOnDownloadMANSRTSPScaleCommand(long id, String deviceId, String startTime, String stopTime, double scale);
 
    /*
     * 收到Bye
     */
    void ntsOnByeDownload(long id, String deviceId, String startTime, String stopTime);
 
    /*
     * 不是在收到BYE Message情况下, 终止Download
     */
    void ntsOnTerminateDownload(long id, String deviceId, String startTime, String stopTime);
 
    /*
     * Download会话对应的对话终止, 一般不会触发这个回调,目前只有在响应了200K, 但在64*T1时间后还没收到ACK,才可能会出发
    收到这个, 请做相关清理处理
    */
    void ntsOnDownloadDialogTerminated(long id, String deviceId, String startTime, String stopTime);
}

Diseño de interfaz jni subyacente:


/**
* SmartPublisherJniV2.java
* Author: daniusdk.com
*/
package com.daniulive.smartpublisher;
 
public class SmartPublisherJniV2 {
 
     /**
	 * Open publisher(启动推送实例)
	 *
	 * @param ctx: get by this.getApplicationContext()
	 * 
	 * @param audio_opt:
	 * if 0: 不推送音频
	 * if 1: 推送编码前音频(PCM)
	 * if 2: 推送编码后音频(aac/pcma/pcmu/speex).
	 * 
	 * @param video_opt:
	 * if 0: 不推送视频
	 * if 1: 推送编码前视频(NV12/I420/RGBA8888等格式)
	 * if 2: 推送编码后视频(AVC/HEVC)
	 * if 3: 层叠加模式
	 *
	 * <pre>This function must be called firstly.</pre>
	 *
	 * @return the handle of publisher instance
	 */
    public native long SmartPublisherOpen(Object ctx, int audio_opt, int video_opt,  int width, int height);
 
    
     /**
	 * 设置流类型
	 * @param type: 0:表示 live 流, 1:表示 on-demand 流, SDK默认为0(live流)
	 * 注意: 流类型设置当前仅对GB28181媒体流有效
	 * @return {0} if successful
	 */
    public native int SetStreamType(long handle, int type);
 
 
    /**
	 * 投递视频 on demand包, 当前只用于GB28181推送, 注意ByteBuffer对象必须是DirectBuffer
	 *
	 * @param codec_id: 编码id, 当前支持H264和H265, 1:H264, 2:H265
	 *
	 * @param packet: 视频数据, 包格式请参考H264/H265 Annex B Byte stream format, 例如:
	 *                0x00000001 nal_unit 0x00000001 ...
	 *                H264 IDR: 0x00000001 sps 0x00000001 pps 0x00000001 IDR_nal_unit .... 或 0x00000001 IDR_nal_unit ....
	 *                H265 IDR: 0x00000001 vps 0x00000001 sps 0x00000001 pps 0x00000001 IDR_nal_unit .... 或 0x00000001 IDR_nal_unit ....
	 *
	 * @param offset: 偏移量
	 * @param size: packet size
	 * @param pts_us: 时间戳, 单位微秒
	 * @param is_pts_discontinuity: 是否时间戳间断,0:未间断,1:间断
	 * @param is_key: 是否是关键帧, 0:非关键帧, 1:关键帧
	 * @param codec_specific_data: 可选参数,可传null, 对于H264关键帧包, 如果packet不含sps和pps, 可传0x00000001 sps 0x00000001 pps
	 *                    ,对于H265关键帧包, 如果packet不含vps,sps和pps, 可传0x00000001 vps 0x00000001 sps 0x00000001 pps
	 * @param codec_specific_data_size: codec_specific_data size
	 * @param width: 图像宽, 可传0
	 * @param height: 图像高, 可传0
	 *
	 * @return {0} if successful
	 */
	public native int PostVideoOnDemandPacketByteBuffer(long handle, int codec_id,
														ByteBuffer packet, int offset, int size, long pts_us, int is_pts_discontinuity, int is_key,
														byte[] codec_specific_data, int codec_specific_data_size,
									  					int width, int height);
 
	
     /**
	 * 投递音频on demand包, 当前只用于GB28181推送, 注意ByteBuffer对象必须是DirectBuffer
	 *
	 * @param codec_id: 编码id, 当前支持PCMA和AAC, 65536:PCMA, 65538:AAC
	 * @param packet: 音频数据
	 * @param offset:packet偏移量
	 * @param size: packet size
	 * @param pts_us: 时间戳, 单位微秒
	 * @param is_pts_discontinuity: 是否时间戳间断,0:未间断,1:间断
	 * @param codec_specific_data: 如果是AAC的话,需要传 Audio Specific Configuration
	 * @param codec_specific_data_size: codec_specific_data size
	 * @param sample_rate: 采样率
	 * @param channels: 通道数
	 *
	 * @return {0} if successful
	 */
	public native int PostAudioOnDemandPacketByteBuffer(long handle, int codec_id,
														ByteBuffer packet, int offset, int size, long pts_us, int is_pts_discontinuity,
														byte[] codec_specific_data, int codec_specific_data_size,
														int sample_rate, int channels);
 
	/**
	 * 启动 GB28181 媒体流
	 *
	 * @return {0} if successful
	 */
	public native int StartGB28181MediaStream(long handle);
 
 
    /**
	 * 停止 GB28181 媒体流
	 *
	 * @return {0} if successful
	 */
	public native int StopGB28181MediaStream(long handle);
 
    
	/**
     * 关闭推送实例,结束时必须调用close接口释放资源
	 *
	 * @return {0} if successful
	 */
    public native int SmartPublisherClose(long handle);
 
}
Lógica del último procesamiento

RecordDownloadListenerImpl se implementa de la siguiente manera:

/**
* RecordDownloadListenerImpl.java
* Author: daniusdk.com
*/
package com.daniulive.smartpublisher;
 
public class RecordDownloadListenerImpl implements com.gb.ntsignalling.GBSIPAgentDownloadListener {
 
    /*
     *收到s=Download的文件下载Invite
     */
    @Override
    public void ntsOnInviteDownload(long id, String deviceId, SessionDescription sdp) {
        if (!post_task(new OnInviteTask(this.context_, this.is_exit_, this.senders_map_, deviceId, sdp, id))) {
            Log.e(TAG, "ntsOnInviteDownload post_task failed, " + RecordSender.make_print_tuple(id, deviceId, sdp.getTime().getStartTime(),  sdp.getTime().getStopTime()));
 
            // 这里不发488, 等待事务超时也可以的
            GBSIPAgent agent = this.context_.get_agent();
            if (agent != null)
                agent.respondDownloadInvite(488, id, deviceId, sdp.getTime().getStartTime(), sdp.getTime().getStopTime());
        }
    }
 
    /*
     * 收到CANCEL Download INVITE请求
     */
    @Override
    public void ntsOnCancelDownload(long id, String deviceId, String startTime, String stopTime) {
        Log.i(TAG, "ntsOnCancelDownload, " + RecordSender.make_print_tuple(id, deviceId, startTime, stopTime));
 
        RecordSender sender = senders_map_.remove(id);
        if (null == sender)
            return;
 
        StopDisposeTask task = new StopDisposeTask(sender);
        if (!post_task(task))
            task.run();
    }
 
    /*
     * 收到Ack
     */
    @Override
    public void ntsOnAckDownload(long id, String deviceId, String startTime, String stopTime) {
        Log.i(TAG, "ntsOnAckDownload, "+ RecordSender.make_print_tuple(id, deviceId, startTime, stopTime));
 
        RecordSender sender = senders_map_.get(id);
        if (null == sender) {
            Log.e(TAG, "ntsOnAckDownload get sender is null, " + RecordSender.make_print_tuple(id, deviceId, startTime, stopTime));
 
            GBSIPAgent agent = this.context_.get_agent();
            if (agent != null)
                agent.terminateDownload(id, deviceId, startTime, stopTime, false);
 
            return;
        }
 
        StartTask task = new StartTask(sender, this.senders_map_);
        if (!post_task(task))
            task.run();
    }
 
    /*
     * 收到Bye
     */
    @Override
    public void ntsOnByeDownload(long id, String deviceId, String startTime, String stopTime) {
        Log.i(TAG, "ntsOnByeDownload, "+ RecordSender.make_print_tuple(id, deviceId, startTime, stopTime));
 
        RecordSender sender = this.senders_map_.remove(id);
        if (null == sender)
            return;
 
        StopDisposeTask task = new StopDisposeTask(sender);
        if (!post_task(task))
            task.run();
    }
 
    /*
     * 更改下载速度
     */
    @Override
    public void ntsOnDownloadMANSRTSPScaleCommand(long id, String deviceId, String startTime, String stopTime, double scale) {
        if (scale < 0.01) {
            Log.e(TAG, "ntsOnDownloadMANSRTSPScaleCommand invalid scale:" + scale  + " " + RecordSender.make_print_tuple(id, deviceId, startTime, stopTime));
            return;
        }
 
        RecordSender sender = this.senders_map_.get(id);
        if (null == sender) {
            Log.e(TAG, "ntsOnDownloadMANSRTSPScaleCommand can not get sender, scale:" + scale  + " " + RecordSender.make_print_tuple(id, deviceId, startTime, stopTime));
            return;
        }
 
        sender.set_speed(scale);
 
        Log.i(TAG, "ntsOnDownloadMANSRTSPScaleCommand, scale:" + scale + " " + RecordSender.make_print_tuple(id, deviceId, startTime, stopTime));
    }
}

El código de procesamiento relacionado con el envío de archivos es el siguiente:

/**
* RecordSender.java
* Author: daniusdk.com
*/
package com.daniulive.smartpublisher;
 
public class RecordSender {
    public void set_speed(double speed) {
        int percent_speed = (int)(speed*100);
        this.percent_speed_.set(percent_speed);
    }
 
    public void set_file_description(RecordFileDescription desc) {
        this.file_description_ = desc;
    }
 
    public static String make_print_tuple(long id, String device_id, String start_time, String stop_time) {
        StringBuilder sb = new StringBuilder(96);
        sb.append("[id:").append(id);
        sb.append(", device:" + device_id);
        sb.append(", t=").append(start_time).append(" ").append(start_time);
        sb.append("]");
        return sb.toString();
    }
 
    public boolean start() {
        SendThread current_thread = thread_.get();
        if (current_thread != null) {
            if (current_thread.is_exit()) {
                Log.e(TAG, "start, the thread already exists and has exited, return false, " + get_print_tuple());
                return false;
            }
 
            Log.i(TAG, "start, the thread already exists and has exited, return true, " + get_print_tuple());
            return true;
        }
 
        SendThread thread = new SendThread();
        if (!thread_.compareAndSet(null, thread)) {
            Log.i(TAG, "start, call compareAndSet return false, the thread already exists, return true, " + get_print_tuple());
            return true;
        }
 
        try {
            Log.i(TAG, "start thread, " + get_print_tuple());
            thread.start();
        }catch (Exception e) {
            thread_.compareAndSet(thread, null);
            Log.e(TAG, "start e:", e);
            return false;
        }
 
        return true;
    }
 
    public void stop() {
        SendThread current_thread = thread_.get();
        if (current_thread != null && !current_thread.is_exit()) {
            current_thread.exit();
            Log.i(TAG, "stop, exit thread " + get_print_tuple());
        }
    }
 
    private boolean init_native_sender(StackDisposable disposables) {
        if(native_handle_ !=0) {
            Log.e(TAG, "init_native_sender, native_handle_ is not 0, " + get_print_tuple());
            return false;
        }
 
        if (null == this.media_info_ || !this.media_info_.is_has_track() ) {
            Log.e(TAG, "init_native_sender, there is no track, " + get_print_tuple());
            return false;
        }
 
        if (0 == rtp_handle_) {
            Log.e(TAG, "init_native_sender, rtp_handle_ is 0, " + get_print_tuple());
            return false;
        }
 
        if (null == lib_publisher_){
            Log.e(TAG, "init_native_sender, lib_publisher_ is null, " + get_print_tuple());
            return false;
        }
 
        Context context = this.context_.get_context();
        if (null == context) {
            Log.e(TAG, "init_native_sender, context is null, " + get_print_tuple());
            return false;
        }
 
        long handle = lib_publisher_.SmartPublisherOpen(context, media_info_.is_has_audio_track()?2:0, media_info_.is_has_video_track()?2:0, 0, 0);
        if (0 == handle) {
            Log.e(TAG, "init_native_sender, call SmartPublisherOpen failed, " + get_print_tuple());
            return false;
        }
 
        NativeSenderDisposable native_disposable = new NativeSenderDisposable(lib_publisher_, handle);
 
        lib_publisher_.SetStreamType(handle, 1);
 
        List<MediaTrack> tracks = media_info_.get_tracks();
        for (MediaTrack i : tracks) {
            if (i.is_video())
                lib_publisher_.SetEncodedVideoCodecId(handle, i.codec_id(), i.csd_set(), i.csd_set() != null? i.csd_set().length : 0);
            else if(i.is_audio())
                lib_publisher_.SetEncodedAudioCodecId(handle, i.codec_id(), i.csd_set(), i.csd_set() != null? i.csd_set().length : 0);
        }
 
        lib_publisher_.SetGB28181RTPSender(handle, rtp_handle_, rtp_payload_type_, rtp_encoding_name_);
 
        int ret = lib_publisher_.StartGB28181MediaStream(handle);
        if (ret != 0) {
            Log.e(TAG, "init_native_sender, call StartGB28181MediaStream failed, " + get_print_tuple());
            native_disposable.dispose();
            return false;
        }
 
        native_disposable.is_need_call_stop(true);
        disposables.push(native_disposable);
 
        native_handle_ = handle;
 
        return true;
    }
 
    private boolean post_media_packet(MediaPacket packet) {
        /*Log.i(TAG, "post "+ MediaTrack.get_media_type_string(packet.media_type()) + " " +
                MediaTrack.get_codec_id_string(packet.codec_id()) + " packet, pts:" + out_point_3(packet.pts_us()/1000.0) +"ms, key:"
                + (packet.is_key()?1:0) + ", size:" + packet.size()); */
 
        if (null == lib_publisher_ || 0 == native_handle_ || !packet.is_has_data())
            return false;
 
        if (packet.is_audio()) {
            if (packet.is_aac()) {
                if (packet.is_has_codec_specific_data_set())
                    return 0 == lib_publisher_.PostAudioOnDemandPacketByteBuffer(native_handle_, packet.codec_id(), packet.data(), 0, packet.size(),
                            packet.pts_us(), 0, packet.codec_specific_data_set(), packet.codec_specific_data_set_size(), 0, 0);
            }
        }else if (packet.is_video()) {
            if (packet.is_avc() || packet.is_hevc()) {
                return 0 == lib_publisher_.PostVideoOnDemandPacketByteBuffer(native_handle_, packet.codec_id(), packet.data(),
                        0, packet.size(), packet.pts_us(), 0, packet.is_key()?1:0,
                        packet.codec_specific_data_set(), packet.codec_specific_data_set_size(), 0, 0);
            }
        }
 
        return false;
    }
 
  
    private void release_packets(Deque<MediaPacket> packets) {
        while (!packets.isEmpty())
            packets.removeFirst().release_buffer();
    }
 
    private static String out_point_3(double v) { return String.format("%.3f", v); }
 
    public static String to_mega_bytes_string(long bytes) {
        double mb = bytes/(1024*1024.0);
        return out_point_3(mb);
    }
 
    private class SendThread extends Thread {
	
        @Override
        public void run() {
            /***
			*相关代码
			**/
        }
    }
}

Resumir

La descarga de archivos de audio y video históricos GB28181 parece lógicamente compleja, pero en realidad no es simple. La descarga de archivos se basa en completar la recuperación de archivos de video y audio y video históricos, respectivamente, desde las perspectivas de señalización, empaquetado y envío de datos RTP. , etc., teniendo en cuenta la integridad del archivo de vídeo, el objetivo del diseño de descargar archivos históricos de vídeo y audio es reducir la pérdida de fotogramas y de paquetes. Se recomienda utilizar el modo RTP sobre TCP. Aunque como lado de acceso del equipo GB28181, Hacemos todo lo posible para implementarlo de acuerdo con las especificaciones estándar. El fabricante de la plataforma estándar nacional de acoplamiento real tendrá algunas diferencias y los detalles se abordarán de acuerdo con la situación real en el sitio.

Supongo que te gusta

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