Интерпретация и техническая реализация спецификаций воспроизведения исторических видео и аудио файлов GB28181 на платформе Android.
Интерпретация и техническая реализация спецификаций воспроизведения исторических видео и аудио файлов GB28181 на платформе Android.

Технический опыт

Прежде чем реализовать воспроизведение исторических видео- и аудиофайлов GB28181, мы завершили извлечение и загрузку исторических видео- и аудиофайлов. Воспроизведение исторических видео и аудио очень важно на платформе GB28181, например, на внешних устройствах, таких как записывающие устройства правоохранительных органов. Данные записи по умолчанию хранятся на стороне внешнего устройства. Если вам необходимо загрузить их на платформу для единого хранилища, вы можете не только скопировать их на рабочую станцию, но и загрузить их в командный центр через историческое видео. и аудиофайлы GB28181. Если командному центру необходимо напрямую просматривать исторические видео и аудио файлы, это также может быть достигнуто с помощью воспроизведения исторического видео и аудио GB28181.

GB28181 Основные требования для воспроизведения исторических видео- и аудиофайлов:

  • Для установления сеансового соединения необходимо использовать метод Invite в протоколе SIP;
  • Тело сообщения, использующее метод Info расширенного протокола SIP, содержит команды управления воспроизведением видео и аудио;
  • Используйте протокол RTP/RTCP для реализации передачи мультимедиа;
  • Команда управления воспроизведением мультимедиа относится к сообщениям запроса PlayPause и Teardown и ответным сообщениям в протоколе MANSRTSP;
  • Воспроизведение исторических видео и аудио должно поддерживать механизм поддержания активности медиапотока.

Основной процесс заключается в следующем:

В этой статье объединены сторона доступа к устройству GB28181 платформы Android и сторона платформы национального стандарта GB28181, чтобы объяснить основной процесс:

1. Платформа GB28181 отправляет приглашение на сторону доступа к устройству Android GB28181. Поле заголовка сообщения содержит поле «Тема», в котором указывается идентификатор источника видео по запросу, серийный номер медиапотока отправителя, идентификатор получателя медиапотока и т. д. флаги серийного номера медиапотока принимающей стороны и другие параметры. Сообщение содержит информацию SDP, поле s — «Воспроизведение» для представления исторического воспроизведения, поле u представляет идентификатор канала воспроизведения и тип воспроизведения, поле t представляет период времени воспроизведения, а поле y добавляется для описания значения SSRC. ;

2. После получения запроса на приглашение со стороны платформы национального стандарта сторона доступа устройства Android GB28181 отвечает 200OK и передает тело сообщения SDP. SDP описывает IP, порт, формат мультимедиа, поле SSRC и т. д. медиапоток, отправленный устройством Android;

3. После получения ответа 200 OK от стороны устройства Android национального стандарта сторона платформы национального стандарта отправляет запрос ACK на сторону устройства Android национального стандарта. Запрос не содержит тела сообщения и завершает процесс установления сеанса приглашения с помощью. сторона устройства Android национального стандарта;

4. Нажмите IP-адрес и информацию о порте, указанные в Invite SDP на устройстве Android GB28181, чтобы отправить пакет аудио и видео RTP (рекомендуется пакет PS RTP) на медиасервер;

5. В процессе воспроизведения проигрыватель осуществляет управление воспроизведением, отправляя внутрисеансовое сообщение Info+MANSRTSP на SIP-сервер (которое затем пересылается SIP-сервером на устройство Android), включая паузу видео, воспроизведение, быстрое воспроизведение, замедленное воспроизведение, случайное перетаскивание и т. д.;

6. Устройство Android GB28181 отправляет внутрисеансовое сообщение после завершения воспроизведения файла, чтобы уведомить SIP-сервер о завершении воспроизведения;

7. После получения сообщения мультимедийного уведомления сторона платформы национального стандарта обрабатывает его соответствующим образом, а затем сторона службы национального стандарта отправляет сообщение BYE на сторону устройства национального стандарта Android;

8. После получения сообщения BYE на стороне устройства Android GB28181 он отвечает 200 OK, отключает сеанс и освобождает соответствующие ресурсы.

Давайте поговорим о командах управления воспроизведением мультимедиа:

Команда управления воспроизведением мультимедиа завершается сообщением-запросом от клиента к серверу и ответным сообщением от сервера к клиенту. Запрос и ответ. Ответ относится к некоторым форматам сообщений запроса и ответа в протоколе RTSP (IETFRFC2326).

Команды воспроизведения мультимедиа:

Клиент отправляет сообщение запроса PLAY, чтобы запросить у сервера отправку мультимедиа. Должен поддерживаться заголовок Range, а в заголовке Range должен быть указан диапазон времени воспроизведения для воспроизведения мультимедиа в указанный период времени. Диапазон времени должен поддерживать диапазоны относительных меток времени npt и smpte.

Значение заголовка Range — «ntp=now-» и не содержит заголовка Scale, что означает, что воспроизведение возобновляется с позиции паузы с исходной скоростью.

PLAY RTSP/1.0 CSeq: 2 Range: npt= now-

Пример команды паузы воспроизведения:

PAUSERTSP/1.0 CSeq:1 PauseTime:now

Примеры команд быстрой и медленной перемотки вперед:

PLAYRTSP/1.0 CSeq:3 Scale:2.0

Пример команды случайного перетаскивания:

PLAYRTSP/1.0 CSeq:4 Range:npt=100-

Команда остановки:

Клиент отправляет сообщение запроса TEARDOWN, чтобы прекратить отправку указанного потока, завершить сеанс и освободить ресурсы.

Команда ответа:

Клиент и сервер должны поддерживать коды состояния 200, 4xx и 5xx команды ответа. См. IETFRFC2326.

Диапазон значений полей заголовка Scale и Range

Базовые значения, которые должен поддерживать заголовок Scale, — 0,25, 0,5, 1, 2 и 4.

Значение заголовка Range — это относительное значение начальной точки воспроизведения видео. Диапазон значений — от 0 до времени окончания воспроизведения видео. Параметр указывается в секундах и не может иметь отрицательное значение. Например, значение заголовка Range равно 0, что означает, что воспроизведение начинается с начальной точки; значение заголовка Range равно 100, что означает, что воспроизведение начинается через 100 секунд после начальной точки записи; Заголовок диапазона теперь отображается, что означает, что воспроизведение начинается с текущей позиции.

Техническая реализация

В этой статье в качестве примера рассматривается сторона доступа к устройству Daniu Live SDK на платформе Android GB28181. В настоящее время мы реализуем следующие функции:

  • [Формат видео] H.264/H.265 (жестко закодирован в Android H.265);
  • [Аудиоформат] G.711 A-law, AAC;
  • [Регулировка громкости] Терминал сбора данных на платформе Android поддерживает регулировку громкости в режиме реального времени;
  • [Жесткое кодирование H.264] Поддержка жесткого кодирования H.264 для определенных моделей;
  • [Жесткое кодирование H.265] Поддержка жесткого кодирования H.265 для определенных моделей;
  • [Конфигурация параметров программного и жесткого кодирования] Поддержка интервала кадров, частоты кадров, настроек скорости передачи данных;
  • [Конфигурация параметров программного кодирования] поддерживает профиль мягкого кодирования, скорость мягкого кодирования и настройки переменной скорости кодирования;
  • Поддерживает горизонтальную и вертикальную потоковую передачу на экране;
  • Платформа Android поддерживает push-экран фоновой службы (для push-экрана требуется версия 5.0+);
  • Поддержка чистой передачи видео, аудио и видео PS пакета;
  • Поддержка пассивного режима RTP OVER UDP и RTP OVER TCP (клиент потоковой передачи мультимедиа TCP);
  • Поддержка настроек протокола передачи сети канала сигнализации TCP/UDP;
  • Поддерживает регистрацию, отмену, обновление регистрации и настройку периода действия регистрации;
  • Поддержка ответа на запрос каталога устройства;
  • Поддержка механизма пульса, поддержка интервала пульса и настроек времени обнаружения пульса;
  • Поддержка подписки и уведомлений о местонахождении мобильного устройства (MobilePosition);
  • Применимые национальные стандарты: GB/T 28181-2016;
  • Поддержка голосового вещания;
  • Поддержка голосовой связи;
  • Поддержка поиска исторических видео и аудио файлов;
  • Поддерживает загрузку исторических видео и аудио файлов;
  • Поддержка воспроизведения исторических видео и аудио файлов;
  • Поддержка управления PTZ и запроса предустановленного положения;
  • [Водяной знак в реальном времени] поддерживает динамические текстовые водяные знаки и водяные знаки PNG;
  • [Зеркалирование] Платформа Android поддерживает функцию зеркального отображения передней камеры в реальном времени;
  • [Отключение звука в реальном времени] Поддерживает отключение/включение звука в реальном времени;
  • [Снимок в реальном времени] Поддержка моментального снимка в реальном времени;
  • [Шумоподавление] поддерживает обработку шумоподавления, автоматическое усиление и обнаружение VAD, вызванного звуками окружающей среды, помехами мобильного телефона и т. д.;
  • [Стыковка видеоданных перед внешним кодированием] поддерживает стыковку данных YUV;
  • [Стыковка аудиоданных перед внешним кодированием] поддерживает стыковку PCM;
  • [Стыковка внешних кодированных видеоданных] Поддерживает стыковку внешних данных H.264;
  • [Стыковка аудиоданных после внешнего кодирования] Стыковка внешних данных AAC;
  • [Расширенная функция записи] поддерживает использование в сочетании с модулем записи для обеспечения функций, связанных с записью.

Пример сигнального взаимодействия

На стороне устройства Android GB28181 осуществляется поиск видео:

Язык кода:javascript
копировать
    <?xml version="1.0" encoding="GB2312"?>
    <Query>
      <CmdType>RecordInfo</CmdType>
      <SN>564849544</SN>
      <DeviceID>34020000001380000001</DeviceID>
      <StartTime>2023-11-05T06:00:00</StartTime>
      <EndTime>2023-11-05T12:00:00</EndTime>
      <Type>all</Type>
    </Query>

В списке на боковой стороне платформы GB отображается информация о полученном видеофайле:

Сторона устройства платформы Android GB28181 получила приглашение от платформы национального стандарта:

Язык кода:javascript
копировать
    v=0
    o=34020000001380000001 0 0 IN IP4 192.168.0.108
    s=Playback
    u=34020000001380000001:0
    c=IN IP4 192.168.0.108
    t=1699159500 1699161303
    m=video 30014 RTP/AVP 96 97 98 99
    a=recvonly
    a=rtpmap:96 PS/90000
    a=rtpmap:97 MPEG4/90000
    a=rtpmap:98 H264/90000
    a=rtpmap:99 H265/90000
    y=1200000006

Сторона устройства платформы Android GB28181 отвечает на сторону платформы национального стандарта:

Язык кода:javascript
копировать
    v=0
    o=34020000011310000039 0 0 IN IP4 192.168.0.104
    s=Playback
    c=IN IP4 192.168.0.104
    t=0 0
    m=video 55584 RTP/AVP 96
    a=rtpmap:96 PS/90000
    a=filesize:199500000
    a=sendonly
    y=1200000006

Если вы хотите ускорить воспроизведение вперед на 2-кратной скорости:

Язык кода:javascript
копировать
    PLAY RTSP/1.0
    CSeq: 785427390
    Scale: 2.000000

Устройство Android GB28181 на платформе Android отправляет внутрисеансовое сообщение, а тип времени уведомления — «121», что указывает на окончание отправки исторических медиафайлов:

Язык кода:javascript
копировать
    <?xml version="1.0" encoding="GB2312"?>
    <Notify>
    <CmdType>MediaStatus</CmdType>
    <SN>433507779</SN>
    <DeviceID>34020000001380000001</DeviceID>
    <NotifyType>121</NotifyType>
    </Notify>

Реализация интерфейса

Язык кода:javascript
копировать
/*
* Author: daniusdk.com
*/

package com.gb.ntsignalling;
 
public interface GBSIPAgent {
    void addPlaybackListener(GBSIPAgentPlaybackListener playbackListener);
 
    void removePlaybackListener(GBSIPAgentPlaybackListener playbackListener);
 
    /*
     *Ответ на приглашение Playback 200 OK
     */
    boolean respondPlaybackInviteOK(long id, String deviceId, String startTime, String stopTime, MediaSessionDescription localMediaDescription);
 
    /*
     *Ответ на приглашение Playback Другие коды состояния
     */
    boolean respondPlaybackInvite(int statusCode, long id, String deviceId);
 
    /*
     * Отправитель медиапотока отправляет сообщение-сообщение после завершения воспроизведения, чтобы уведомить SIP-сервер о том, что файл воспроизведения был отправлен.
     * notifyType Должно быть "121"
     */
    boolean notifyPlaybackMediaStatus(long id, String deviceId, String notifyType);
 
    /*
     * Завершить сеанс воспроизведения
     */
    void terminatePlayback(long id, String deviceId, boolean isSendBYE);
 
    /*
     * Завершить все сеансы воспроизведения
     */
    void terminateAllPlaybacks(boolean isSendBYE);
}
 
 
/**
* Сигнализация Воспроизведение Listener
*/
package com.gb.ntsignalling;
 
public interface GBSIPAgentPlaybackListener {
    /*
     *Получено приглашение на историческое воспроизведение от s=Playback.
     */
    void ntsOnInvitePlayback(long id, String deviceId, SessionDescription sessionDescription);
 
    /*
     *Отправить воспроизведение invite response аномальный
     */
    void ntsOnPlaybackInviteResponseException(long id, String deviceId, int statusCode, String errorInfo);
 
    /*
     * ОТМЕНА получено Playback ПРИГЛАСИТЕ запрос
     */
    void ntsOnCancelPlayback(long id, String deviceId);
 
    /*
     * Подтверждение получено
     */
    void ntsOnAckPlayback(long id, String deviceId);
 
    /*
    * команда воспроизведения
     */
    void ntsOnPlaybackMANSRTSPPlayCommand(long id, String deviceId);
 
    /*
     * команда паузы
     */
    void ntsOnPlaybackMANSRTSPPauseCommand(long id, String deviceId);
 
    /*
     * Команды быстрой/медленной перемотки вперед
     */
    void ntsOnPlaybackMANSRTSPScaleCommand(long id, String deviceId, double scale);
 
    /*
     * Случайная команда перетаскивания
     */
    void ntsOnPlaybackMANSRTSPSeekCommand(long id, String deviceId, double position_sec);
 
    /*
     * команда остановки
     */
    void ntsOnPlaybackMANSRTSPTeardownCommand(long id, String deviceId);
 
    /*
     * Получено до свидания
     */
    void ntsOnByePlayback(long id, String deviceId);
 
    /*
     * Не получаю ПОКА В случае сообщения Прекратить воспроизведение
     */
    void ntsOnTerminatePlayback(long id, String deviceId);
 
    /*
     * Разговор, соответствующий сеансу воспроизведения, завершается, Обычно этот обратный вызов не запускается. В настоящее время он отвечает только на 200 КБ. Но если вы не получили ACK по истечении времени 64*T1, вы можете начинать.
    получил это, Пожалуйста, сделайте соответствующую очистку
    */
    void ntsOnPlaybackDialogTerminated(long id, String deviceId);
}
 
 
/**
* Часть интерфейса JNI, rtp ps C++ реализация упаковки и отправки кода
*/
 
public class SmartPublisherJniV2 {
 
     /**
	 * Open издатель (запуск экземпляра push)
	 *
	 * @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 (прямая трансляция).
	 * Уведомление: Настройка типа потока в настоящее время действительна только для медиапотока GB28181.
	 * @return {0} if successful
	 */
    public native int SetStreamType(long handle, int type);
 
 
    /**
	 * Видео доставки on пакет спроса, В настоящее время используется только для нажатия GB28181, Обратите внимание, что объект ByteBuffer должен быть DirectBuffer.
	 *
	 * @param codec_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: Необязательный параметр, можно передать ноль, Для пакета ключевых кадров H264: Если пакет не содержит sps и pps, Может передать 0x00000001 sps 0x00000001 pps
	 *                    , для пакета ключевых кадров H265, Если в пакете нет 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);
 
	
     /**
	 * Опубликовать аудио вкл. пакет спроса, В настоящее время используется только для нажатия GB28181, Обратите внимание, что объект ByteBuffer должен быть DirectBuffer.
	 *
	 * @param codec_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);
																											
	/**
	 * on demand После того, как источник завершит поиск, пожалуйста, позвони
	 * @return {0} if successful
	 */
	public native int OnSeekProcessed(long handle);
 
	/**
	 * запускать GB28181 медиапоток
	 *
	 * @return {0} if successful
	 */
	public native int StartGB28181MediaStream(long handle);
 
 
    /**
	 * останавливаться GB28181 медиапоток
	 *
	 * @return {0} if successful
	 */
	public native int StopGB28181MediaStream(long handle);
 
    
	/**
     * Закройте экземпляр push. В конце необходимо вызвать интерфейс закрытия для освобождения ресурсов.
	 *
	 * @return {0} if successful
	 */
    public native int SmartPublisherClose(long handle);
 
}
 
 
/**
* Код реализации части прослушивателя
*/
 
public class PlaybackListenerImpl implements com.gb.ntsignalling.GBSIPAgentPlaybackListener {
 
    /*
     *Получено приглашение на загрузку файла с помощью s=Playback.
     */
    @Override
    public void ntsOnInvitePlayback(long id, String deviceId, SessionDescription sdp) {
        if (!post_task(new PlaybackListenerImpl.OnInviteTask(this.context_, this.is_exit_, this.senders_map_, deviceId, sdp, id))) {
            Log.e(TAG, "ntsOnInvitePlayback 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.respondPlaybackInvite(488, id, deviceId);
        }
    }
 
    /*
     *Отправить воспроизведение invite response аномальный
     */
    @Override
    public void ntsOnPlaybackInviteResponseException(long id, String deviceId, int statusCode, String errorInfo) {
        Log.i(TAG, "ntsOnPlaybackInviteResponseException, status_code:" + statusCode + ", "
                + RecordSender.make_print_tuple(id, deviceId) + ",  error_info:" + errorInfo);
 
        RecordSender sender = senders_map_.remove(id);
        if (null == sender)
            return;
 
        PlaybackListenerImpl.StopDisposeTask task = new PlaybackListenerImpl.StopDisposeTask(sender);
        if (!post_task(task))
            task.run();
    }
 
    /*
     * ОТМЕНА получено Playback ПРИГЛАСИТЕ запрос
     */
    @Override
    public void ntsOnCancelPlayback(long id, String deviceId) {
        Log.i(TAG, "ntsOnCancelPlayback, " + RecordSender.make_print_tuple(id, deviceId));
 
        RecordSender sender = senders_map_.remove(id);
        if (null == sender)
            return;
 
        PlaybackListenerImpl.StopDisposeTask task = new PlaybackListenerImpl.StopDisposeTask(sender);
        if (!post_task(task))
            task.run();
    }
 
    /*
     * Подтверждение получено
     */
    @Override
    public void ntsOnAckPlayback(long id, String deviceId) {
        Log.i(TAG, "ntsOnAckPlayback, "+ RecordSender.make_print_tuple(id, deviceId));
 
        RecordSender sender = senders_map_.get(id);
        if (null == sender) {
            Log.e(TAG, "ntsOnAckPlayback get sender is null, " + RecordSender.make_print_tuple(id, deviceId));
 
            GBSIPAgent agent = this.context_.get_agent();
            if (agent != null)
                agent.terminatePlayback(id, deviceId, false);
 
            return;
        }
 
        PlaybackListenerImpl.StartTask task = new PlaybackListenerImpl.StartTask(sender, this.senders_map_);
        if (!post_task(task))
            task.run();
    }
 
    /*
     * Получено до свидания
     */
    @Override
    public void ntsOnByePlayback(long id, String deviceId) {
        Log.i(TAG, "ntsOnByePlayback, "+ RecordSender.make_print_tuple(id, deviceId));
 
        RecordSender sender = this.senders_map_.remove(id);
        if (null == sender)
            return;
 
        PlaybackListenerImpl.StopDisposeTask task = new PlaybackListenerImpl.StopDisposeTask(sender);
        if (!post_task(task))
            task.run();
    }
 
    /*
     * команда воспроизведения
     */
    @Override
    public void ntsOnPlaybackMANSRTSPPlayCommand(long id, String deviceId) {
        RecordSender sender = this.senders_map_.get(id);
        if (null == sender) {
            Log.e(TAG, "ntsOnPlaybackMANSRTSPPlayCommand can not get sender " + RecordSender.make_print_tuple(id, deviceId));
            return;
        }
 
        sender.post_play_command();
 
        Log.i(TAG, "ntsOnPlaybackMANSRTSPPlayCommand " + RecordSender.make_print_tuple(id, deviceId));
    }
 
    /*
     * команда паузы
     */
    @Override
    public void ntsOnPlaybackMANSRTSPPauseCommand(long id, String deviceId) {
        RecordSender sender = this.senders_map_.get(id);
        if (null == sender) {
            Log.e(TAG, "ntsOnPlaybackMANSRTSPPauseCommand can not get sender " + RecordSender.make_print_tuple(id, deviceId));
            return;
        }
 
        sender.post_pause_command();
 
        Log.i(TAG, "ntsOnPlaybackMANSRTSPPauseCommand " + RecordSender.make_print_tuple(id, deviceId));
    }
 
    /*
     * Команды быстрой/медленной перемотки вперед
     */
    @Override
    public void ntsOnPlaybackMANSRTSPScaleCommand(long id, String deviceId, double scale) {
        if (scale < 0.01) {
            Log.e(TAG, "ntsOnPlaybackMANSRTSPScaleCommand invalid scale:" + scale  + " " + RecordSender.make_print_tuple(id, deviceId));
            return;
        }
 
        RecordSender sender = this.senders_map_.get(id);
        if (null == sender) {
            Log.e(TAG, "ntsOnPlaybackMANSRTSPScaleCommand can not get sender, scale:" + scale  + " " + RecordSender.make_print_tuple(id, deviceId));
            return;
        }
 
        sender.post_scale_command(scale);
 
        Log.i(TAG, "ntsOnPlaybackMANSRTSPScaleCommand, scale:" + scale + " " + RecordSender.make_print_tuple(id, deviceId));
    }
 
    /*
     * Случайная команда перетаскивания
     */
    @Override
    public void ntsOnPlaybackMANSRTSPSeekCommand(long id, String device_id, double position_sec) {
        if (position_sec < 0.0) {
            Log.e(TAG, "ntsOnPlaybackMANSRTSPSeekCommand invalid seek pos:" + position_sec  + ", " + RecordSender.make_print_tuple(id, device_id));
            return;
        }
 
        RecordSender sender = this.senders_map_.get(id);
        if (null == sender) {
            Log.e(TAG, "ntsOnPlaybackMANSRTSPSeekCommand can not get sender " + RecordSender.make_print_tuple(id, device_id));
            return;
        }
 
        long offset_ms = sender.get_file_start_time_offset_ms();
        position_sec += (offset_ms/1000.0);
 
        sender.post_seek_command(position_sec);
 
        Log.i(TAG, "ntsOnPlaybackMANSRTSPSeekCommand seek pos:" + RecordSender.out_point_3(position_sec) + "s, " + RecordSender.make_print_tuple(id, device_id));
    }
 
    /*
     * команда остановки
     */
    @Override
    public void ntsOnPlaybackMANSRTSPTeardownCommand(long id, String device_id) {
        CallTerminatePlaybackTask call_terminate_task =  new CallTerminatePlaybackTask(this.context_, id, device_id, true);
        post_task(call_terminate_task);
 
        RecordSender sender = this.senders_map_.remove(id);
        if (null == sender) {
            Log.w(TAG, "ntsOnPlaybackMANSRTSPTeardownCommand can not remove sender " + RecordSender.make_print_tuple(id, device_id));
            return;
        }
 
        Log.i(TAG, "ntsOnPlaybackMANSRTSPTeardownCommand " + RecordSender.make_print_tuple(id, device_id));
 
        PlaybackListenerImpl.StopDisposeTask task = new PlaybackListenerImpl.StopDisposeTask(sender);
        if (!post_task(task))
            task.run();
    }
 
    /*
     * Не получаю ПОКА В случае сообщения Прекратить воспроизведение
     */
    @Override
    public void ntsOnTerminatePlayback(long id, String deviceId) {
        Log.i(TAG, "ntsOnTerminatePlayback, "+ RecordSender.make_print_tuple(id, deviceId));
 
        RecordSender sender = this.senders_map_.remove(id);
        if (null == sender)
            return;
 
        PlaybackListenerImpl.StopDisposeTask task = new PlaybackListenerImpl.StopDisposeTask(sender);
        if (!post_task(task))
            task.run();
    }
 
    /*
     * Разговор, соответствующий сеансу воспроизведения, завершается, Обычно этот обратный вызов не запускается. В настоящее время он отвечает только на 200 КБ. Но если вы не получили ACK по истечении времени 64*T1, вы можете начинать.
    получил это, Пожалуйста, сделайте соответствующую очистку
    */
    @Override
    public void ntsOnPlaybackDialogTerminated(long id, String deviceId) {
        Log.i(TAG, "ntsOnPlaybackDialogTerminated, "+ RecordSender.make_print_tuple(id, deviceId));
 
        RecordSender sender = this.senders_map_.remove(id);
        if (null == sender)
            return;
 
        PlaybackListenerImpl.StopDisposeTask task = new PlaybackListenerImpl.StopDisposeTask(sender);
        if (!post_task(task))
            task.run();
    }
}

Подвести итог

Платформа Android GB28181 воспроизводит исторические видео и аудиофайлы, помимо вышеуказанного взаимодействия сигнализации, также необходимо обрабатывать упаковку и отправку RTP и т. д. По сравнению с другими функциями реализация более сложна. Заинтересованные разработчики могут попробовать.

boy illustration
Неразрушающее увеличение изображений одним щелчком мыши, чтобы сделать их более четкими артефактами искусственного интеллекта, включая руководства по установке и использованию.
boy illustration
Копикодер: этот инструмент отлично работает с Cursor, Bolt и V0! Предоставьте более качественные подсказки для разработки интерфейса (создание навигационного веб-сайта с использованием искусственного интеллекта).
boy illustration
Новый бесплатный RooCline превосходит Cline v3.1? ! Быстрее, умнее и лучше вилка Cline! (Независимое программирование AI, порог 0)
boy illustration
Разработав более 10 проектов с помощью Cursor, я собрал 10 примеров и 60 подсказок.
boy illustration
Я потратил 72 часа на изучение курсорных агентов, и вот неоспоримые факты, которыми я должен поделиться!
boy illustration
Идеальная интеграция Cursor и DeepSeek API
boy illustration
DeepSeek V3 снижает затраты на обучение больших моделей
boy illustration
Артефакт, увеличивающий количество очков: на основе улучшения характеристик препятствия малым целям Yolov8 (SEAM, MultiSEAM).
boy illustration
DeepSeek V3 раскручивался уже три дня. Сегодня я попробовал самопровозглашенную модель «ChatGPT».
boy illustration
Open Devin — инженер-программист искусственного интеллекта с открытым исходным кодом, который меньше программирует и больше создает.
boy illustration
Эксклюзивное оригинальное улучшение YOLOv8: собственная разработка SPPF | SPPF сочетается с воспринимаемой большой сверткой ядра UniRepLK, а свертка с большим ядром + без расширения улучшает восприимчивое поле
boy illustration
Популярное и подробное объяснение DeepSeek-V3: от его появления до преимуществ и сравнения с GPT-4o.
boy illustration
9 основных словесных инструкций по доработке академических работ с помощью ChatGPT, эффективных и практичных, которые стоит собрать
boy illustration
Вызовите deepseek в vscode для реализации программирования с помощью искусственного интеллекта.
boy illustration
Познакомьтесь с принципами сверточных нейронных сетей (CNN) в одной статье (суперподробно)
boy illustration
50,3 тыс. звезд! Immich: автономное решение для резервного копирования фотографий и видео, которое экономит деньги и избавляет от беспокойства.
boy illustration
Cloud Native|Практика: установка Dashbaord для K8s, графика неплохая
boy illustration
Краткий обзор статьи — использование синтетических данных при обучении больших моделей и оптимизации производительности
boy illustration
MiniPerplx: новая поисковая система искусственного интеллекта с открытым исходным кодом, спонсируемая xAI и Vercel.
boy illustration
Конструкция сервиса Synology Drive сочетает проникновение в интрасеть и синхронизацию папок заметок Obsidian в облаке.
boy illustration
Центр конфигурации————Накос
boy illustration
Начинаем с нуля при разработке в облаке Copilot: начать разработку с минимальным использованием кода стало проще
boy illustration
[Серия Docker] Docker создает мультиплатформенные образы: практика архитектуры Arm64
boy illustration
Обновление новых возможностей coze | Я использовал coze для создания апплета помощника по исправлению домашних заданий по математике
boy illustration
Советы по развертыванию Nginx: практическое создание статических веб-сайтов на облачных серверах
boy illustration
Feiniu fnos использует Docker для развертывания личного блокнота Notepad
boy illustration
Сверточная нейронная сеть VGG реализует классификацию изображений Cifar10 — практический опыт Pytorch
boy illustration
Начало работы с EdgeonePages — новым недорогим решением для хостинга веб-сайтов
boy illustration
[Зона легкого облачного игрового сервера] Управление игровыми архивами
boy illustration
Развертывание SpringCloud-проекта на базе Docker и Docker-Compose