Android プラットフォームでの GB28181 履歴ビデオおよびオーディオ ファイルのダウンロード仕様の議論と技術的実装

技術的背景

前回のブログでは、 Android。ファイル取得後、GB28181 プラットフォーム側はファイル リストに対して再生またはダウンロード操作を実行できます。この記事では主に説明しますビデオおよびオーディオ ファイルのダウンロード。

標準的な解釈

ビデオおよびオーディオ ファイルをダウンロードするための基本要件

メディア受信側から送信されたビデオおよびオーディオ ファイルのダウンロード要求を受信した後、SIP サーバーはメディア ファイル ダウンロード コマンドをメディア ストリーム送信側に送信します。メディア ストリーム送信側は RTP を使用してビデオ ストリームをメディア ストリーム受信側に送信し、メディアストリーム レシーバーはビデオ ストリームをメディア ストリーム レシーバーに直接送信し、メディア ファイルとして保存します。

メディア ストリームの受信者はユーザー クライアントまたはネットワーク システムであり、メディア ストリームの送信者はメディア デバイスまたはネットワーク システムです。メディア ストリーム受信者または SIP サーバーは、構成クエリなどを通じてメディア ストリーム送信者によってサポートされているダウンロードおよび送信速度を取得し、要求された SDP メッセージ本文で指定されたダウンロード速度を伝えることができます。

メディア ストリーム送信者は、Invite リクエストに対応する 200 0K 応答 SDP メッセージ本文でダウンロードされたファイルのサイズ パラメーターを拡張できるため、メディア ストリーム受信者はダウンロードの進行状況を計算できます。 、メディア ストリーム レシーバー ダウンローダーは、コード ストリームから取得した時間に基づいてダウンロードの進行状況の計算をサポートする必要があります。ビデオおよびオーディオ ファイルのダウンロードは、メディア ストリーム キープアライブ メカニズムをサポートする必要があります。

コマンド処理

このうち、シグナリング 1、8、9、10、11、12 は、SIP サーバーがクライアントの通話要求を受信した後、B2BUA プロキシ モードを通じてメディア ストリーム受信者とメディア サーバーの間にメディア リンクを確立するシグナリング プロセスです。

シグナリング 2 ~ 7 は、SIP サーバーが三者通話制御を通じてメディア サーバーとメディア ストリーム間のメディア リンクを確立するためのシグナリング プロセスです。

シグナリング 13 ~ 16 は、メディア ストリーム送信者がダウンロードをファイルの最後まで再生し、ダウンロード完了通知メッセージをメディア受信者に送信する処理です。

シグナリング 17 ~ 20 は、メディア ストリーム受信機がメディア サーバーとのメディア リンクを切断するためのシグナリング プロセスです。

シグナリング 21 ~ 24 は、SIP サーバーがメディア サーバーとメディア ストリーム送信側の間のメディア リンクを切断するためのシグナリング プロセスです。

コマンドの処理は次のように説明されます。

  1. メディア ストリーム受信者は SIP サーバーに Invite メッセージを送信します。メッセージ ヘッダー フィールドには、オンデマンド ビデオ ソース ID、送信者のメディア ストリーム シリアル番号、メディア ストリーム受信者 ID、受信側のメディア ストリーム シリアルを示す Subject フィールドが含まれます。 SDP メッセージ本文の s フィールドはファイルのダウンロードを表す「Download」、u フィールドはダウンロード チャネル ID とダウンロード タイプ、フィールドはダウンロード期間を表します。a フィールドは拡張できます。このダウンロードのデバイスのダウンロード速度を指定するダウンロード速度パラメータを伝達します。これが伝達されない場合、デフォルトは 1x 速度です。
  2. Invite リクエストを受信した後、SIP サーバーは、三者通話制御を通じてメディア サーバーとメディア ストリーム送信者の間にメディア接続を確立します。Invite メッセージをメディア サーバーに送信します。このメッセージには SDP メッセージ本文は含まれません。
  3. SIP サーバーから Invite リクエストを受信した後、メディア サーバーは、メディア サーバーがメディア ストリームを受信するための IP ポート、メディア フォーマットなどを記述する SDP メッセージ本文を含む 200 0K 応答を返します。
  4. メディア サーバーから返された 200 OK 応答を受信した後、SIP サーバーはメディア ストリーム送信者に Invite 要求を送信します。この要求には、メディア サーバーからメッセージ 3 で返信された 200 OK 応答メッセージ本文が含まれます。s フィールド「Download」はファイルのダウンロードを表し、u フィールドはダウンロード チャネル ID とダウンロード タイプを表し、t フィールドはダウンロード期間を表します。y フィールドは SSRC 値を記述するために追加され、f フィールドはメディア パラメータを記述します。また、a フィールドは、ダウンロード速度を 2 倍にするために拡張できます。パラメータはデバイスに渡されます。
  5. SIP サーバーから Invite リクエストを受信した後、メディア ストリーム送信者は、SDP メッセージ本文を含む 200 OK 応答で応答します。メッセージ本文には、IP、ポート、メディア フォーマット、SSRC フィールド、および送信されたメディア ストリームのその他の内容が記述されます。 a フィールドはファイル サイズ パラメータを持ちます。
  6. メディア ストリーム送信者から 200 OK 応答を受信した後、SIP サーバーはメディア サーバーに ACK 要求を送信します。この要求には、メディア ストリーム送信者がメッセージ 5 で返信した 200 OK 応答メッセージ本文が含まれており、Invite セッション確立プロセスを完了します。メディアサーバー。
  7. メディア ストリーム送信者から 200 OK 応答を受信した後、SIP サーバーはメディア ストリーム送信者に ACK 要求を送信します。この要求にはメッセージ本文は含まれず、メディア ストリーム送信者との Invite セッション確立プロセスが完了します。
  8. 三者通話制御が完了すると、SIP サーバーは B2BUA プロキシ方式を通じてメディア ストリーム レシーバーとメディア サーバーの間にメディア接続を確立します。SSRC 値をメッセージ 1 に追加し、メディア サーバーに転送します。
  9. メディア サーバーは Invite リクエストを受信し、メディア サーバーが送信するメディア ストリームの IP、ポート、メディア フォーマット、SSRC 値などが記述された SDP メッセージ本文を含む 200 OK 応答を返します。
  10. SIP サーバーはメッセージ 9 をメディア ストリーム受信者に転送し、拡張可能なフィールドにはファイル サイズ パラメーターが含まれます。
  11. 200 OK 応答を受信した後、メディア ストリーム受信機は ACK メッセージで応答し、SIP サーバーとの Invite セッション確立プロセスを完了します。
  12. SIP サーバーはメッセージ 11 をメディア サーバーに転送し、メディア サーバーとの Invite セッション確立プロセスを完了します。
  13. ファイルのダウンロードが完了すると、メディア ストリーマーはセッション内メッセージを送信します。
  14. SIP サーバーはメッセージ 17 を受信し、それをメディア ストリーム レシーバーに転送します。
  15. メッセージ 18 を受信した後、メディア ストリーム受信側は 200 OK 応答を返し、リンク切断プロセスを実行します。
  16. SIP サーバーはメッセージ 19 をメディア ストリーム送信者に転送します。
  17. メディア ストリーム レシーバーは、SIP サーバーに BYE メッセージを送信し、メッセージ 1、10、および 11 でメディア ストリーム レシーバーと確立された Invite セッションを切断します。
  18. BYE メッセージを受信した後、SIP サーバーは 200OK 応答で応答し、セッションは切断されます。
  19. BYE メッセージを受信した後、SIP サーバーはメディア サーバーに BYE メッセージを送信し、メッセージ 8、9、および 12 によって確立されたメディア サーバーとの Invite セッションを切断します。
  20. BYE メッセージを受信した後、メディア サーバーは 200 OK 応答で応答し、セッションは切断されます。
  21. SIP サーバーはメディア サーバーに BYE メッセージを送信し、メッセージ 2、3、および 6 によって確立されたメディア サーバーとの Invite セッションを切断します。
  22. BYE メッセージを受信した後、メディア サーバーは 200 OK 応答で応答し、セッションは切断されます。
  23. SIP サーバーは、メディア ストリーム送信者に BYE メッセージを送信し、メッセージ 4、5、および 7 によって確立されたメディア ストリーム送信者との Invite セッションを切断します。
  24. BYE メッセージを受信した後、メディア ストリーム送信者は 200 OK 応答で応答し、セッションは切断されます。

技術的な実現

この記事では、Daniu Live SDK が開発した Android プラットフォーム GB28181 デバイスを例として、サイドビュー オーディオ履歴ファイルの取得とダウンロードにアクセスし (この記事ではダウンロードに重点を置いています)、関連する設計アイデアを紹介します。

 Android デバイスのアクセス端末は、国家標準プラットフォームから送信された INVITE SDP を受信します。

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

上記の SDP では、s=Download はダウンロードを意味し、a=downloadspeed:4 は 4 倍速のダウンロードを意味し、SSRC は: 1200000001 (SSRC の最初のビットは履歴またはリアルタイムのメディア ストリームの識別ビット、0 はリアルタイム ビデオ)とオーディオ、1 は過去のビデオとオーディオです)。

Android デバイスのアクセス端末は次のように応答します。

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 は、ビデオ ファイル サイズが 15611511 バイトであることを示し、ファイル サイズ パラメーターを伝えます。これは、メディア ストリーム レシーバーがダウンロードの進行状況を計算するのに便利です (a=filesize は、メディア コンテナー全体のサイズと、メディア コンテナーの合計数です)実際に送信されるオーディオおよびビデオ フレームのバイト数)。

国家標準プラットフォームは Ack を送信した後、ビデオとオーディオ データのダウンロードを開始します。ダウンロード プロセス中に、ダウンロード速度は SIP-INFO メッセージと MANSRTSP プロトコルを通じて調整できます。

PLAY RTSP/1.0
CSeq: 31129
Scale: 0.25

Android GB28181 デバイスのアクセス側がオーディオ フレームとビデオ フレームの送信を完了すると、通知イベント タイプ「121」が送信され、履歴メディア ファイルの送信が完了したことを示し、セッション内メッセージが次のように送信されます。

<?xml version="1.0" encoding="GB2312"?>
<Notify>
<CmdType>MediaStatus</CmdType>
<SN>213466963</SN>
<DeviceID>34020000001380000001</DeviceID>
<NotifyType>121</NotifyType>
</Notify>
インターフェース設計

シグナリング インターフェイスの設計:


/**
* 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);
 
}

過去のビデオとオーディオのダウンロード リスナーのデザイン:

/**
* 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);
}

基礎となる jni インターフェース設計:


/**
* 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);
 
}
最終処理ロジック

RecordDownloadListenerImpl は次のように実装されます。

/**
* 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));
    }
}

ファイル送信に関する処理コードは以下のとおりです。

/**
* 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() {
            /***
			*相关代码
			**/
        }
    }
}

要約する

GB28181 の過去のビデオおよびオーディオ ファイルのダウンロードは論理的に複雑であるように見えますが、実際には単純ではありません。ファイルのダウンロードは、それぞれシグナリング、RTP データのパッケージ化、および送信の観点から、ビデオおよび過去のビデオおよびオーディオ ファイルの取得を完了することに基づいています。など、ビデオ ファイルの整合性を考慮して、過去のビデオ ファイルとオーディオ ファイルをダウンロードする設計目標は、フレーム損失とパケット損失を減らすことです。RTP over TCP モードを使用することをお勧めします。GB28181 機器のアクセス側としては、標準仕様に沿って実装するよう最善を尽くしておりますが、実際のドッキング国家標準プラットフォームメーカーにより多少の差異が生じる場合がございますので、詳細は現場の実情に応じて対応させていただきます。

おすすめ

転載: blog.csdn.net/renhui1112/article/details/132708066
おすすめ