GStreamer mp4mux выдает ошибку «У буфера нет PTS» при использовании пользовательского приложения appsrc

У меня есть конвейер, написанный на C ++, который выглядит так:

      appsrc do-timestamp=TRUE is-live=TRUE caps=
“video/x-h264, stream-format=(string)byte-stream, alignment=(string)none, framerate=(fraction)0/1” min-latency=300000000 ! h264parse ! video/x-h264, stream-format=(string)avc, alignment=(string)au ! tee name=t \
t. ! queue ! valve drop=FALSE ! decodebin ! glupload ! glcolorconvert ! qtsink sync=FALSE \
t. ! queue ! valve drop=FALSE ! mp4mux reserved-max-duration=3600000000000 reserved-moov-update-period=10000000000 ! filesink sync=FALSE location=”....../out.mp4”

appsrc вставляет видео, поступающее с беспроводного USB-видеоприемника дрона, в конвейер.

Еще немного контекста:

  • Аппаратное обеспечение USB-приемника дает нам 512-байтовые фрагменты сырого видео в формате H.264 Приложения-B без метки времени.
  • Частота кадров должна составлять 60 кадров в секунду, но на практике она редко успевает за ней и варьируется в зависимости от мощности сигнала (поэтому частота кадров = (дробная) 0/1 ”, и это причина, по которой ни qtsink, ни filesink не синхронизируются с конвейером. (синхронизация = ЛОЖЬ))
  • Аппаратное обеспечение обеспечивает минимальную задержку в 300 мс, как установлено в appsrc.
  • appsrc автоматически ставит метку времени в мои буферы (do-timestamp=TRUE)
  • Я использую mp4mux зарезервировано-макс-продолжительность и зарезервировано-moov-update-period, чтобы предотвратить сбои приложений из-за разрушения файлов mp4.
  • Я использую GStreamer 1.18.4 для Android

Запись видео работает нормально, когда дрон не находится в воздухе. Но когда он взлетает, примерно через 15 секунд правильной видеозаписи элемент mp4mux выходит из строя с сообщением «У буфера нет PTS ». К сожалению, об этом постоянно сообщали некоторые пользователи, но я не могу воспроизвести это (поскольку для этого требуется летать на дроне, которого у меня нет), что не имеет большого смысла. До сих пор я предполагаю, что в этот конкретный момент, вероятно, существует некоторая перегрузка в беспроводной видеосвязи, и некоторые видеопакеты могут задерживаться на несколько миллисекунд, что может вызвать некоторые проблемы.

Вот (упрощенный) код, который создает appsrc

         _pAppSrc = gst_element_factory_make("appsrc", "artosyn_source");
    gpointer pAppSrc = static_cast<gpointer>(_pAppSrc);

    // Retain one more ref, so the source is destroyed
    // in a controlled way
    gst_object_ref(_pAppSrc);

    pCaps = gst_caps_from_string("video/x-h264, stream-format=(string)byte-stream, alignment=none, framerate=(fraction)0/1"));
    g_object_set(G_OBJECT(pAppSrc), "caps", pCaps,
                                    "is-live", TRUE,
                                    "min-latency", G_GINT64_CONSTANT(300000000),
                                    "format", GST_FORMAT_TIME,
                                    "do-timestamp", TRUE,
                                    nullptr);

   _pBufferPool = gst_buffer_pool_new();

   pConfig = gst_buffer_pool_get_config (_pBufferPool);

   static const guint kBufferSize  = 512;
   static const guint kPoolSize    = 0x400000;
   static const guint kPoolSizeMax = 0x600000;

    qsizetype nBuffersMin = kPoolSize / kBufferSize;
    qsizetype nBuffersMax = kPoolSizeMax / kBufferSize;

    gst_buffer_pool_config_set_params(pConfig, pCaps, kBufferSize, nBuffersMin, nBuffersMax);

   gst_buffer_pool_set_config(_pBufferPool, pConfig);
   gst_buffer_pool_set_active(_pBufferPool, TRUE);

   gst_caps_unref(GST_CAPS(pCaps));

Когда драйвер USB заполняет новый буфер, он помещается в конвейер следующим образом:

      bool unref = false;

gst_buffer_unmap(b->pBuffer, &b->mapInfo);
gst_buffer_set_size(b->pBuffer, xfer.pXfer->actual_length);

if(result == LIBUSB_TRANSFER_COMPLETED)
{
    //-- DROP DATA IF NOT IN PLAYING STATE --
    GstState st, pend;
    GstStateChangeReturn scr = gst_element_get_state(GST_ELEMENT(_pAppSrc), &st, &pend, GST_CLOCK_TIME_NONE);
    Q_UNUSED(scr)
    bool drop = (st != GST_STATE_PLAYING);

    if(!drop)
    {
        GstFlowReturn ret = GST_FLOW_OK;

        // Push into pipeline
        ret = gst_app_src_push_buffer(GST_APP_SRC(_pAppSrc), b->pBuffer);

        if(ret != GST_FLOW_OK)
            qCDebug(MMCVideoLog()) << "Can't push buffer to the pipeline (" << ret << ")";
        else
            unref = false;  // Don't unref since gst_app_src_push_buffer() steals one reference and takes ownership
    }
} else if(result == LIBUSB_TRANSFER_CANCELLED)
{
    qCDebug(MMCVideoLog()) << "! Buffer canceled";
} else {
    qCDebug(MMCVideoLog()) << "? Buffer result = " << result;
}    

if(unref)
    gst_buffer_unref(b->pBuffer);

Вот что я получил от Android logcat с пораженной машины:

      [07-22 18:37:45.753 17414:18734 E/QGroundControl]
VideoReceiverLog: GStreamer error: [element ' "mp4mux0" ']  Could not multiplex stream.

[07-22 18:37:45.753 17414:18734 E/QGroundControl]
VideoReceiverLog: Details:  ../gst/isomp4/gstqtmux.c(5010): gst_qt_mux_add_buffer (): /GstPipeline:receiver/GstBin:sinkbin/GstMP4Mux:mp4mux0:
Buffer has no PTS.

Что я пробовал:

  • Установка GstBaseParser pts_interpolation в TRUE и infer_ts в TRUE

Итак, мои вопросы:

  • Вы видите что-то не так с моим кодом? Что мне не хватает?
  • Могу ли я положиться на matroskamux, чтобы временно избежать проблемы, пока не найду истинную причину?

1 ответ

Решение

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

Беспроводной видеопередатчик в дроне может динамически изменять битрейт видео в зависимости от доступной полосы пропускания радиосвязи. Или по-другому: когда дрон находится слишком далеко или есть сильные помехи, качество видео ухудшается.

Когда это происходит, видеокадры (содержащиеся только в одном фрагменте в пределах одного NAL) начинают становиться значительно меньше. Поскольку я читаю 512-байтовые фрагменты потока h264 без определенного выравнивания и перенаправляю их в GStreamer как GstBuffers, если размер данных, необходимых для одного кадра, меньше 512 байтов, существует вероятность, что буфер содержит несколько кадров. . В этом случае h264parse видит это как N разных буферов с одинаковыми временными метками . По-видимому, поведение по умолчанию состоит в том, чтобы игнорировать как восходящие PTS, так и DTS и пытаться вычислить временную метку на основе продолжительности кадра, считывая VUI из SPS, которого нет в моем потоке. Следовательно, буфер, покидающий исходную панель h264parse, не будет иметь ни PTS, ни DTS, что заставит mp4mux жаловаться.

Как я уже упоминал, мой поток довольно прост, поэтому я написал простой синтаксический анализатор для определения начала каждого NAL. Таким образом, я могу «распаковать» поток, поступающий от USB-оборудования, и убедиться, что каждый буфер, помещенный в мой конвейер, будет содержать только один NAL (следовательно, один кадр) с независимыми отметками времени.

И для избыточности я добавил датчик, прикрепленный к контактной площадке моего тройника, чтобы убедиться, что у меня есть правильные временные метки в каждом проходящем через него буфере. В противном случае они будут вынуждены учитывать время работы элемента, как это.

      if (!GST_BUFFER_PTS_IS_VALID(buffer) || !GST_BUFFER_DTS_IS_VALID(buffer))
{

    GstElement* elm = gst_pad_get_parent_element(pad);
    qCDebug(VideoReceiverLog) << "Invalid timestamp out of source. Replacing with element running time.";
    GstClockTime ts = gst_element_get_current_running_time(elm);
    GST_BUFFER_PTS(buffer) = ts;
    GST_BUFFER_DTS(buffer) = ts;
}

После этого я больше не могу воспроизводить проблему с моим тестовым дампом.

Другие вопросы по тегам