2016-03-14 10 views
1

Я разработал видеоплеер на основе Qt и QtGstreamer. Он используется для воспроизведения прямых трансляций (RTSP). Я должен добавить возможность для пользователя делать снимки, пока он играет в прямом эфире, не нарушая воспроизведение видео.Qt + GStreamer: как сделать снимок при воспроизведении видео в реальном времени

Здесь графы трубопровода я сделал:

       -->queue-->autovideosink 
uridecodebin-->videoflip-->tee--| 
      |     -->queue->videoconvert-->pngenc-->filesink 
      | 
      |->audioconvert-->autoaudiosink 

Я использую pad-added сигнал от uridecodebin добавить и ссылку динамически мои элементы к трубопроводу, функция полученных колпачков.

void Player::onPadAdded(const QGst::PadPtr &pad) 
{ 
    QGst::CapsPtr caps = pad->currentCaps(); 
    if (caps->toString().startsWith("video/x-raw")) { 
     qDebug("Received 'video/x-raw' caps"); 
     handleNewVideoPad(pad); 
    } 
    else if (caps->toString().startsWith("audio/x-raw")) { 
     qDebug("Received 'audio/x-raw' caps"); 
     if (!m_audioEnabled) { 
      qDebug("Audio is disabled in the player. Ignoring..."); 
      return; 
     } 
     handleNewAudioPad(pad); 
    } 
    else { 
     qWarning("Unsuported caps, arborting ...!"); 
     return; 
    } 
} 

[...] 

void Player::handleNewVideoPad(QGst::PadPtr pad) 
{ 
    m_player->videoTeeVideoSrcPad = m_player->videoTee->getRequestPad("src_%u"); 

    // Add video elements 
    m_player->pipeline->add(m_player->videoFlip); 
    m_player->pipeline->add(m_player->videoTee); 
    m_player->pipeline->add(m_player->videoQueue); 
    m_player->pipeline->add(m_player->videoSink); 

    // Add snap elements 
    m_player->pipeline->add(m_player->snapQueue); 
    m_player->pipeline->add(m_player->snapConverter); 
    m_player->pipeline->add(m_player->snapEncoder); 
    m_player->pipeline->add(m_player->snapSink); 

    // Link video elements 
    m_player->videoFlip->link(m_player->videoTee); 
    m_player->videoQueue->link(m_player->videoSink); 

    // Link snap elements 
    m_player->snapQueue->link(m_player->snapConverter); 
    m_player->snapConverter->link(m_player->snapEncoder); 
    m_player->snapEncoder->link(m_player->snapSink); 

    // Lock snap elements 
    m_player->snapQueue->setStateLocked(true); 
    m_player->snapConverter->setStateLocked(true); 
    m_player->snapEncoder->setStateLocked(true); 
    m_player->snapSink->setStateLocked(true); 

    m_player->videoFlip->setState(QGst::StatePlaying); 
    m_player->videoTee->setState(QGst::StatePlaying); 
    m_player->videoQueue->setState(QGst::StatePlaying); 
    m_player->videoSink->setState(QGst::StatePlaying); 

    // Link pads 
    m_player->videoTeeVideoSrcPad->link(m_player->videoQueue->getStaticPad("sink")); 
    pad->link(m_player->videoSinkPad); 

    m_player->videoLinked = true; 
} 

метод, чтобы сделать снимок:

void Player::takeSnapshot() 
{ 
    QDateTime dateTime = QDateTime::currentDateTime(); 
    QString snapLocation = QString("/%1/snap_%2.png").arg(m_snapDir).arg(dateTime.toString(Qt::ISODate)); 

    m_player->inSnapshotCaputre = true; 

    if (m_player->videoTeeSnapSrcPad) { 
     m_player->videoTee->releaseRequestPad(m_player->videoTeeSnapSrcPad); 
     m_player->videoTeeSnapSrcPad.clear(); 
    } 
    m_player->videoTeeSnapSrcPad = m_player->videoTee->getRequestPad("src_%u"); 

    // Stop the snapshot branch 
    m_player->snapQueue->setState(QGst::StateNull); 
    m_player->snapConverter->setState(QGst::StateNull); 
    m_player->snapEncoder->setState(QGst::StateNull); 
    m_player->snapSink->setState(QGst::StateNull); 

    // Link Tee src pad to snap queue sink pad 
    m_player->videoTeeSnapSrcPad->link(m_player->snapQueue->getStaticPad("sink")); 

    // Set the snapshot location property 
    m_player->snapSink->setProperty("location", snapLocation); 

    // Unlock snapshot branch 
    m_player->snapQueue->setStateLocked(false); 
    m_player->snapConverter->setStateLocked(false); 
    m_player->snapEncoder->setStateLocked(false); 
    m_player->snapSink->setStateLocked(false); 
    m_player->videoTeeSnapSrcPad->setActive(true); 

    // Synch snapshot branch state with parent 
    m_player->snapQueue->syncStateWithParent(); 
    m_player->snapConverter->syncStateWithParent(); 
    m_player->snapEncoder->syncStateWithParent(); 
    m_player->snapSink->syncStateWithParent(); 
} 

Обратный вызов шины сообщение:

void Player::onBusMessage(const QGst::MessagePtr & message) 
{ 
    QGst::ElementPtr source = message->source().staticCast<QGst::Element>(); 
    switch (message->type()) { 
    case QGst::MessageEos: { //End of stream. We reached the end of the file. 
     qDebug("Message End Off Stream"); 
     if (m_player->inSnapshotCaputre) { 
      blockSignals(true); 
      pause(); 
      play(); 
      blockSignals(false); 
      m_player->inSnapshotCaputre = false; 
     } 
     else { 
      m_eos = true; 
      stop(); 
     } 
     break; 
    } 
    [...] 
} 

Проблема заключается в том:

  • Когда я установил snapshot свойство true элемент pngenc, я получаю событие EOS, которое останавливает мой конвейер, поэтому мне нужно перезапустить его, что заморозит воспроизведение видео в течение примерно половины секунды, что неприемлемо в моем случае.
  • Когда я установил свойство snapshot в false элемента pngenc, у меня нет никаких возмущений конвейера, но мой png-файл продолжает расти, пока я снова не вызову метод Player::takeSnapshot().

Где я ошибаюсь? Есть ли лучший способ сделать это? Я пробовал безуспешно создание элемента QGst::Bin для моей ветви snapshot. Как насчет зонда?

Спасибо по заранее

+0

Вы можете предотвратить рост файла с помощью зондового зонда; возможно, перед тем, как видеоконвертер сбросит все образцы, когда вы не хотите делать снимки. И просто позвольте одному образцу пройти в случае моментального снимка. Я понятия не имею, хотя если результирующий файл является правильным .png-файлом. Часто эти контейнеры файлов имеют этап финализации по событию EOS, который делает их правильно воспроизводящими (MP4 в качестве примера). –

+0

Спасибо, я слышал про пробковые зонды, но никогда не пробовал. Я проверю это –

ответ

3

Вы можете принять имущество последней пробы на любой раковине, например, ваш видеопоток. Это содержит GstSample, в котором есть буфер с самым последним видео рамкой. Вы можете принять это как снимок и, например, с gst_video_convert_sample() или асинхронным вариантом, преобразуйте его в PNG/JPG/что угодно.

См https://gstreamer.freedesktop.org/data/doc/gstreamer/head/gstreamer-libs/html/GstBaseSink.html#GstBaseSink--last-sample и https://gstreamer.freedesktop.org/data/doc/gstreamer/head/gst-plugins-base-libs/html/gst-plugins-base-libs-gstvideo.html#gst-video-convert-sample

В качестве альтернативы, вы должны закрыть трубопровод filesink снимок после первого кадра. Например, с помощью зондового зонда, чтобы узнать, когда произошел первый кадр, а затем впрыска EOS-события, чтобы предотвратить добавление дополнительных кадров PNG в один и тот же файл.

+0

Большое спасибо! Я пробую эти два решения как можно скорее. –

+0

Я пробовал что-то вроде: GstElement videoSinkElement = (GstElement) * m_videoSink; // m_videoSink - QGst :: ElementPtr GstBaseSink * videoSink = NULL; videoSink = GST_BASE_SINK (& videoSinkElement); sample = gst_base_sink_get_last_sample (videoSink); Но он падает на 'gst_base_sink_get_last_sample (videoSink);'. У вас есть идея? Для меня сложная задача - переключиться между API QtGstreamer и «официальным». –

+0

Ошибка здесь неверна: videoSink = GST_BASE_SINK (& videoSinkElement); Попробуйте удалить амперсанд (&). –

1

Благодаря ответу @ sebastian-droge, я нашел решение, используя gst_video_convert_sample и свойство last-sample своего видео-раковины.

Решение Я реализовал это:

void Player::takeSnapshot() 
{ 
    QDateTime currentDate = QDateTime::currentDateTime(); 
    QString location = QString("%1/snap_%2.png").arg(QDir::homePath()).arg(currentDate.toString(Qt::ISODate)); 
    QImage snapShot; 
    QImage::Format snapFormat; 
    QGlib::Value val = m_videoSink->property("last-sample"); 
    GstSample *videoSample = (GstSample *)g_value_get_boxed(val); 
    QGst::SamplePtr sample = QGst::SamplePtr::wrap(videoSample); 
    QGst::SamplePtr convertedSample; 
    QGst::BufferPtr buffer; 
    QGst::CapsPtr caps = sample->caps(); 
    QGst::MapInfo mapInfo; 
    GError *err = NULL; 
    GstCaps * capsTo = NULL; 
    const QGst::StructurePtr structure = caps->internalStructure(0); 
    int width, height; 

    width = structure.data()->value("width").get<int>(); 
    height = structure.data()->value("height").get<int>(); 

    qDebug() << "Sample caps:" << structure.data()->toString(); 

    /* 
    * { QImage::Format_RGBX8888, GST_VIDEO_FORMAT_RGBx }, 
    * { QImage::Format_RGBA8888, GST_VIDEO_FORMAT_RGBA }, 
    * { QImage::Format_RGB888 , GST_VIDEO_FORMAT_RGB }, 
    * { QImage::Format_RGB16 , GST_VIDEO_FORMAT_RGB16 } 
    */ 
    snapFormat = QImage::Format_RGB888; 
    capsTo = gst_caps_new_simple("video/x-raw", 
           "format", G_TYPE_STRING, "RGB", 
           "width", G_TYPE_INT, width, 
           "height", G_TYPE_INT, height, 
           NULL); 

    convertedSample = QGst::SamplePtr::wrap(gst_video_convert_sample(videoSample, capsTo, GST_SECOND, &err)); 
    if (convertedSample.isNull()) { 
     qWarning() << "gst_video_convert_sample Failed:" << err->message; 
    } 
    else { 
     qDebug() << "Converted sample caps:" << convertedSample->caps()->toString(); 

     buffer = convertedSample->buffer(); 
     buffer->map(mapInfo, QGst::MapRead); 

     snapShot = QImage((const uchar *)mapInfo.data(), 
          width, 
          height, 
          snapFormat); 

     qDebug() << "Saving snap to" << location; 
     snapShot.save(location); 

     buffer->unmap(mapInfo); 
    } 

    val.clear(); 
    sample.clear(); 
    convertedSample.clear(); 
    buffer.clear(); 
    caps.clear(); 
    g_clear_error(&err); 
    if (capsTo) 
     gst_caps_unref(capsTo); 
} 

Я создать простое тестовое приложение, которые реализуют это решение.Код доступен на моем Github