从 cam 到文件 C 代码的 gstreamer 管道以空输出文件结束

gstreamer pipeline from cam to file C code ends up with empty output file

我有一个 USB 摄像头。我有工作终端命令来录制或显示全高清视频并保存一张 4k 图像。我想通过 C++ 应用程序处理这一切。如果我们专注于视频保存:

gst-launch-1.0 v4l2src device=/dev/video0 num-buffers=900! image/jpeg, width=1920, height=1080, io-mode=4 ! imxvpudec ! imxvpuenc_mjpeg ! avimux ! filesink location=/mnt/ssd/test.avi

将保存 900 帧(又名 30 秒)的视频。我想让 C++ 代码无限期地记录(将来可能会记录一个小时的片段),直到我(应用程序)告诉它结束。

我想到了


   struct {
       GstElement *pipeline_sink, *source, *appsink;
       GstElement *pipeline_src, *appsrc, *decoder, *mux, *sink, *encoder;
   } usbCam::mGstData;

int usbCam::gstInit(){
   GstCaps *caps;
   GstStateChangeReturn ret;

   // Initialize GStreamer
   if (!gst_is_initialized()) {
       setenv("GST_DEBUG", ("*:" + std::to_string(3)).c_str(), 1);
       gst_init(nullptr, nullptr);
   }
   // Create the elements
   mGstData.source = gst_element_factory_make ("v4l2src", "source");
   g_object_set (mGstData.source, "device", "/dev/video0", NULL);
   mGstData.pipeline_sink = gst_pipeline_new ("pipeline_sink");


   caps = gst_caps_new_any();
   gst_app_sink_set_caps(GST_APP_SINK(mGstData.appsink), caps);
   gst_caps_unref (caps);

   gst_app_sink_set_emit_signals(GST_APP_SINK(mGstData.appsink), true);

   // Build the pipeline
   gst_bin_add_many (GST_BIN (mGstData.pipeline_sink), mGstData.source, mGstData.appsink, NULL);

   if (gst_element_link_many(mGstData.source, mGstData.appsink, NULL) != TRUE) {
       g_printerr ("Elements could not be linked.\n");
       gst_object_unref (mGstData.pipeline_sink);
       return -1;
   }
   return 0;
}

int usbCam::videoStart(){
       GstCaps *caps;
       GstStateChangeReturn ret;


       if (!mGstData.pipeline_sink || !mGstData.source) {
           g_printerr ("Not all elements could be created.\n");
           return -1;
       }


       mGstData.appsrc = gst_element_factory_make ("appsrc", "appsrc");
       mGstData.decoder = gst_element_factory_make ("imxvpudec", "transform_enc");
       mGstData.mux = gst_element_factory_make ("avimux", "avimux");
       mGstData.sink = gst_element_factory_make ("filesink", "sink");

       g_object_set (mGstData.sink, "location", "/mnt/ssd/videoTest.avi", NULL);

       mGstData.pipeline_src = gst_pipeline_new ("pipeline_src");

       if (!mGstData.pipeline_src || !mGstData.appsrc || !mGstData.decoder || !mGstData.mux || !mGstData.sink) {
         g_printerr ("Not all elements could be created.\n");
         return -1;
       }
       caps = gst_caps_new_simple ("image/jpeg",
                    "width", G_TYPE_INT, 1920,
                    "height", G_TYPE_INT, 1080,
                    "io-mode", G_TYPE_INT, 4,
                    NULL);
       gst_app_src_set_caps(GST_APP_SRC(mGstData.appsrc), caps);
       gst_caps_unref (caps);

       gst_app_src_set_duration(GST_APP_SRC(mGstData.appsrc), GST_TIME_AS_MSECONDS(80)); 
  gst_app_src_set_stream_type(GST_APP_SRC(mGstData.appsrc), GST_APP_STREAM_TYPE_STREAM);
       gst_app_src_set_latency(GST_APP_SRC(mGstData.appsrc), -1, 0);

       gst_bin_add_many (GST_BIN (mGstData.pipeline_src), mGstData.appsrc, mGstData.decoder, mGstData.sink, NULL);

       if (gst_element_link_many(mGstData.appsrc, mGstData.decoder, mGstData.sink, NULL) != TRUE) {
         g_printerr ("Elements could not be linked.\n");
         gst_object_unref (mGstData.pipeline_src);
         return -1;
       }

       ret = gst_element_set_state (mGstData.pipeline_src, GST_STATE_PLAYING);

       if (ret == GST_STATE_CHANGE_FAILURE) {
         g_printerr ("Unable to set the pipeline to the playing state.\n");
         gst_object_unref (mGstData.pipeline_src);
         return -1;
       }

       return 0;
}

int usbCam::videoEnd(){
{
   gst_app_src_end_of_stream(GST_APP_SRC(mGstData.appsrc));
   usleep(500000);
   gst_element_set_state (mGstData.pipeline_src, GST_STATE_NULL);
   gst_object_unref (mGstData.pipeline_src);
   return 0;
}

现在,这段代码运行了。输出中没有错误,但有一个警告:

(GLib-GObject-WARNING **: 17:51:34.132: g_object_set_is_valid_property: object class 'GstSplitMuxSink' has no property named 'h}\x9fe h\xe6a_no_\xc1') . 真正困扰我的是输出文件。它已创建,但它是一个大小为 0b 的空文件。谁能指出正确修复的方向?

编辑:今天我想出了另外两个尝试。第一个与此处已发布的没有什么不同。第二个给了我错误参数(不同的 FPS)的管道,我无法正确地停止它以使文件具有正确的 EOF。


     GstElement *pipeline;
     GstBus *bus;
     GstMessage *msg;

     std::string command = "v4l2src device=/dev/video0 ! image/jpeg, width=1920, height=1080, io-mode=4 ! imxvpudec ! imxvpuenc_mjpeg ! avimux ! filesink location = /mnt/ssd/testPipeline.avi";
     /* Build the pipeline */

     pipeline =
         gst_parse_launch
         (command.c_str(),
         NULL);

     /* Start playing */
     gst_element_set_state (pipeline, GST_STATE_PLAYING);

     /* Wait until error or EOS */
     bus = gst_element_get_bus (pipeline);
     msg =
         gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE, GstMessageType(
         GST_MESSAGE_ERROR | GST_MESSAGE_EOS));

     /* Free resources */
     if (msg != NULL)
       gst_message_unref (msg);
     gst_object_unref (bus);
     gst_element_set_state (pipeline, GST_STATE_NULL);
     gst_object_unref (pipeline);

编辑 2:

好的,现在我的代码如下所示:

GstElement *pipeline;
GstElement *tee; //in the future I would like to save video and images AND stream or use thi pipeline data internally.
void gstFail(const gchar* message){
    g_printerr(message);
    gst_object_unref (pipeline);
    return;
}
void videoStart(std::string path){
    if (!gst_is_initialized()) {
        setenv("GST_DEBUG", ("*:" + std::to_string(3)).c_str(), 1);
        gst_init(nullptr, nullptr);
    }
    GstCaps *caps;
    GstStateChangeReturn ret;
    GstElement *source, *muxer, *sink;
    source = gst_element_factory_make ("v4l2src", "source");
    g_object_set (source, "device", mVideoDevice.toStdString().c_str(), NULL);

    muxer = gst_element_factory_make ("avimux", "avimux");
    tee = gst_element_factory_make("tee", "tee");
    sink = gst_element_factory_make ("filesink", "sink");

    g_object_set (sink, "location", path.c_str(), NULL);
    
    pipeline = gst_pipeline_new ("pipeline_src");

    if (!pipeline || !source || !muxer || !sink) {
      g_printerr ("Not all elements could be created.\n");
      return;
    }
    caps = gst_caps_new_simple ("image/jpeg",
                 "width", G_TYPE_INT, 1920,
                 "height", G_TYPE_INT, 1080,
                 "io-mode", G_TYPE_INT, 4,
                 "framerate", GST_TYPE_FRACTION, 30, 1,
                 "pixel-aspect-ratio", GST_TYPE_FRACTION, 1,1,
                 "interlace-mode", G_TYPE_STRING, "progresive",
                 NULL);


    gst_bin_add_many (GST_BIN (pipeline), source, muxer,tee, sink, NULL);
    if (gst_element_link_filtered(source, muxer, caps) != TRUE) {
      gst_caps_unref (caps);
      gstFail("Elements could not be linked or caps set.\n");
      return;
    }
    gst_caps_unref (caps);
    if (gst_element_link_many(muxer,tee, sink, NULL) != TRUE) {
        gstFail("Elements could not be linked or caps set.\n");
        return;
    }
    ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);

    if (ret == GST_STATE_CHANGE_FAILURE) {
      gstFail("Unable to set the pipeline to the playing state.\n");
      return;
    }
   return;
}

void videoEnd(void)
{
    GstMessage *message = gst_message_new_eos(&pipeline->object);
    gst_bus_post(pipeline->bus, message);
    /* Free resources */
    if (message != NULL)
      gst_message_unref (message);
    gst_element_change_state(pipeline, GST_STATE_CHANGE_PLAYING_TO_PAUSED);
    gst_element_change_state(pipeline, GST_STATE_CHANGE_PAUSED_TO_READY);

    gst_element_set_state (pipeline, GST_STATE_NULL);
    gst_object_unref(pipeline);
}
void takeImage(std::string path){
        GstElement *sink = gst_element_factory_make("multifilesink", "multifilesink");
        g_object_set (sink, "location", path.c_str(), NULL);
        gst_bin_add_many (GST_BIN (pipeline), sink, NULL);
        if (gst_element_link_many(tee, sink, NULL) != TRUE) {
            gstFail("Elements could not be linked or caps set.\n");
            return;
        }
        return;
}

这几乎可以保存视频(VLC 不显示正确的长度。但是当我在 Ubuntu 中通过 Nautilus 查看视频文件属性时,显示了正确的长度并且视频可以播放)。它不保存图片。

好的,这就是我的解决方法:我的初始管道使用 tee 元素分成两个接收器:保存视频的原始接收器和 appsink。在 appsink 的回调函数中,我创建了新的管道,并在我想保存图像时随时推送框架。基本上:

...
int saveSampleFromAppsinkJpeg( GstSample *sample){
   if (!shouldSaveImage) {
       return -2;
   }
   if (capturing){
       return -3;
   }
   std::thread([=]{
       capturing = true;
       GstStateChangeReturn ret;

       GstElement *appsrc = gst_element_factory_make ("appsrc", "appsrc");
       GstElement *sink = gst_element_factory_make ("multifilesink", "sink");

       g_object_set (sink, "location", "some/path", NULL);

       GstElement *pipeline_img = gst_pipeline_new ("pipeline_img");

       if (!pipeline_img || !appsrc || !sink) {
         g_printerr ("Not all elements could be created.\n");
         capturing = false;
         return -1;
       }
       gst_app_src_set_caps(GST_APP_SRC(appsrc), caps);
       gst_app_src_set_duration(GST_APP_SRC(appsrc), GST_TIME_AS_MSECONDS(80)); // TODO 80
       gst_app_src_set_stream_type(GST_APP_SRC(appsrc), GST_APP_STREAM_TYPE_STREAM);
       gst_app_src_set_latency(GST_APP_SRC(appsrc), -1, 0);

       gst_bin_add_many (GST_BIN (pipeline_img), appsrc, sink, NULL);

       if (gst_element_link_many(appsrc, sink, NULL) != TRUE) {
         g_printerr ("Elements could not be linked.\n");
         gst_object_unref (pipeline_img);
         capturing = false;
         return -1;
       }

       ret = gst_element_set_state (pipeline_img, GST_STATE_PLAYING);
       if (ret == GST_STATE_CHANGE_FAILURE) {
         g_printerr ("Unable to set the pipeline to the playing state.\n");
         gst_object_unref (pipeline_img);
         capturing = false;
         return -1;
       }

       //push the image in the pipeline
       GstFlowReturn status = GstFlowReturn::GST_FLOW_OK;
       status = gst_app_src_push_sample(GST_APP_SRC(appsrc), sample);
       if (status !=  GstFlowReturn::GST_FLOW_OK) g_printerr ("Sample for saving image not pushed.\n");
       status = gst_app_src_end_of_stream(GST_APP_SRC(appsrc));
       if (status !=  GstFlowReturn::GST_FLOW_OK) g_printerr ("EOS for saving image not pushed.\n");

       //end the pipeline
       usleep(500000); // Important
       GstMessage *message = gst_message_new_eos(&pipeline_img->object);
       gst_bus_post(pipeline_img->bus, message);
       /* Free resources */
       if (message != NULL)
         gst_message_unref (message);
       gst_element_set_state (pipeline_img, GST_STATE_PAUSED);
       gst_element_set_state (pipeline_img, GST_STATE_NULL);
       gst_object_unref (pipeline_img);
       shouldSaveImage = false;
       capturing = false;
       return 1;
   }).detach();
   return 1;
}

static GstFlowReturn new_sample_jpeg(GstElement * elt)
{
    GstSample *sample;
    GstBuffer *buffer;
    GstMemory *memory;

    GstFlowReturn ret = GST_FLOW_OK;

    // get the sample from appsink
    sample = gst_app_sink_pull_sample (GST_APP_SINK (elt));
    buffer = gst_sample_get_buffer (sample);
    if (buffer != NULL) {
        memory = gst_buffer_get_memory (buffer, 0);
        if (memory != NULL) {
            //now all data are image data. If image wanted->image save!
            if (wantToSave) saveSampleFromAppsinkJpeg(sample);
         }
    ...
     }
}
void startVideo(){
    if (!gst_is_initialized()) {
        setenv("GST_DEBUG", ("*:" + std::to_string(3)).c_str(), 1);
        gst_init(nullptr, nullptr);
    }
    GstStateChangeReturn ret;
    GstElement *source, *muxer, *sink, *queue_rcr, *queue_app, *appsink;
    source = gst_element_factory_make ("v4l2src", "source");
    g_object_set (source, "device", "/dev/video1", NULL);

    muxer = gst_element_factory_make ("avimux", "avimux");
    tee = gst_element_factory_make("tee", "tee");
    sink = gst_element_factory_make ("filesink", "sink");
    queue_rcr = gst_element_factory_make ("queue", "record_queue");
    queue_app = gst_element_factory_make ("queue", "app_queue");

    appsink = gst_element_factory_make("appsink", "appsink");
    g_object_set (sink, "location", path.toStdString().c_str(), NULL);

    pipeline = gst_pipeline_new ("pipeline_src");

    if (!pipeline || !source || !muxer || !sink || !queue_rcr || !appsink) {
      g_printerr ("Not all elements could be created.\n");
      return;
    }
    caps = gst_caps_new_simple ("image/jpeg",
                 "width", G_TYPE_INT, 1920,
                 "height", G_TYPE_INT, 1080,
                 "io-mode", G_TYPE_INT, 4,
                 "framerate", GST_TYPE_FRACTION, 30, 1,
                 "pixel-aspect-ratio", GST_TYPE_FRACTION, 1,1,
                 "interlace-mode", G_TYPE_STRING, "progresive",
                 NULL);


    gst_bin_add_many (GST_BIN (pipeline), source, muxer,tee, sink,queue_rcr, appsink, queue_app, NULL);
    if (gst_element_link_filtered(source, tee, caps) != TRUE) {
     //failhandling
    }
    if (gst_element_link_many(tee, queue_rcr, muxer, sink, NULL) != TRUE) {
     //failhandling
    }
    if (gst_element_link_many(tee, queue_app, appsink, NULL) != TRUE) {
     //failhandling
    }
    gst_app_sink_set_emit_signals(GST_APP_SINK(appsink), true);
    g_signal_connect (appsink, "new-sample", G_CALLBACK (new_sample_jpeg));
    ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);

    if (ret == GST_STATE_CHANGE_FAILURE) {
     //failhandling
    }

    // Start playing
    recording = true;
   return;
}