On Thu, 2018-02-22 at 11:40 -0500, Frediano Ziglio wrote: > > > > Gstreamer based plugin utilizing gstreamer elements to capture > > screen from X, convert and encode into h264 stream using x264enc > > gstreamer plugin. > > Configure with --with-gst, will be built as a separate plugin. > > > > Signed-off-by: Snir Sheriber <ssheribe@xxxxxxxxxx> > > Signed-off-by: Frediano Ziglio <fziglio@xxxxxxxxxx> > > I can say it works. I just got around to testing this, and it seemed like it may have worked once, but now I can't get it to work properly. I get debug output like this: spice-streaming-agent[32712]: GOT START_STOP message -- request to START streaming spice-streaming-agent[32712]: streaming starts now spice-streaming-agent[32712]: fps = 25 spice-streaming-agent[32712]: Elements were linked successfully spice-streaming-agent[32712]: No sample- EOS or state change spice-streaming-agent[32712]: got a frame -- size is 0 (116 ms) (1519853449483 ms from last frame)(1519853449366907 us) spice-streaming-agent[32712]: write_all -- 8 bytes written spice-streaming-agent[32712]: wrote 8 bytes of header of data msg with frame of size 0 bytes spice-streaming-agent[32712]: write_all -- 0 bytes written spice-streaming-agent[32712]: wrote data msg body of size 0 spice-streaming-agent[32712]: No sample- EOS or state change spice-streaming-agent[32712]: got a frame -- size is 0 (0 ms) (0 ms from last frame)(107 us) spice-streaming-agent[32712]: write_all -- 8 bytes written spice-streaming-agent[32712]: wrote 8 bytes of header of data msg with frame of size 0 bytes spice-streaming-agent[32712]: write_all -- 0 bytes written spice-streaming-agent[32712]: wrote data msg body of size 0 spice-streaming-agent[32712]: No sample- EOS or state change spice-streaming-agent[32712]: got a frame -- size is 0 (0 ms) (0 ms from last frame)(50 us) spice-streaming-agent[32712]: write_all -- 8 bytes written spice-streaming-agent[32712]: wrote 8 bytes of header of data msg with frame of size 0 bytes spice-streaming-agent[32712]: write_all -- 0 bytes written spice-streaming-agent[32712]: wrote data msg body of size 0 spice-streaming-agent[32712]: No sample- EOS or state change spice-streaming-agent[32712]: got a frame -- size is 0 (0 ms) (0 ms from last frame)(29 us) spice-streaming-agent[32712]: write_all -- 8 bytes written spice-streaming-agent[32712]: wrote 8 bytes of header of data msg with frame of size 0 bytes spice-streaming-agent[32712]: write_all -- 0 bytes written spice-streaming-agent[32712]: wrote data msg body of size 0 ..... And it seems to do this in a tight loop so you get massive amounts of this output. > > > --- > > configure.ac | 8 ++ > > src/Makefile.am | 26 +++++++ > > src/gst-plugin.cpp | 222 > > +++++++++++++++++++++++++++++++++++++++++++++++++++++ > > 3 files changed, 256 insertions(+) > > create mode 100644 src/gst-plugin.cpp > > > > diff --git a/configure.ac b/configure.ac > > index 5aab662..ee2ef68 100644 > > --- a/configure.ac > > +++ b/configure.ac > > @@ -52,6 +52,13 @@ AC_SUBST(JPEG_LIBS) > > > > AC_CHECK_HEADER([catch/catch.hpp],,[AC_MSG_ERROR([Could not find > > Catch > > dependency header (catch/catch.hpp)])]) > > > > +AC_ARG_WITH([gst], AS_HELP_STRING([--with-gst], [Build with the > > gstreamer > > plugin])) > > +have_gst=no > > +if test "x$with_gst" = "xyes"; then > > + PKG_CHECK_MODULES(GST, [gstreamer-1.0 gstreamer-app-1.0], > > [have_gst=yes], [have_gst=no]) > > +fi > > +AM_CONDITIONAL([HAVE_GST],[test "$have_gst" = "yes"]) > > + > > dnl > > I was just checking spice-server, it uses --enable-gstreamer. > Not that important. > > > ================================================================== > > ========= > > dnl check compiler flags > > > > @@ -102,6 +109,7 @@ AC_MSG_NOTICE([ > > prefix: ${prefix} > > C compiler: ${CC} > > C++ compiler: ${CXX} > > + Gstreamer plugin: ${have_gst} > > > > Now type 'make' to build $PACKAGE > > ]) > > diff --git a/src/Makefile.am b/src/Makefile.am > > index 3717b5c..c09a2d7 100644 > > --- a/src/Makefile.am > > +++ b/src/Makefile.am > > @@ -5,6 +5,8 @@ > > > > NULL = > > SUBDIRS = . unittests > > +plugin_LTLIBRARIES = > > +plugindir = $(pkglibdir)/plugins > > > > AM_CPPFLAGS = \ > > -DSPICE_STREAMING_AGENT_PROGRAM \ > > @@ -56,3 +58,27 @@ spice_streaming_agent_SOURCES = \ > > jpeg.cpp \ > > jpeg.hpp \ > > $(NULL) > > + > > +if HAVE_GST > > +plugin_LTLIBRARIES += gst-plugin.la > > + > > +gst_plugin_la_LDFLAGS = \ > > + -module -avoid-version \ > > + $(RELRO_LDFLAGS) \ > > + $(NO_INDIRECT_LDFLAGS) \ > > + $(NULL) > > + > > +gst_plugin_la_LIBADD = \ > > + $(GST_LIBS) \ > > + $(NULL) > > + > > +gst_plugin_la_SOURCES = \ > > + gst-plugin.cpp \ > > + $(NULL) > > + > > +gst_plugin_la_CPPFLAGS = \ > > + -I$(top_srcdir)/include \ > > + $(SPICE_PROTOCOL_CFLAGS) \ > > + $(GST_CFLAGS) \ > > + $(NULL) > > +endif > > diff --git a/src/gst-plugin.cpp b/src/gst-plugin.cpp > > new file mode 100644 > > index 0000000..2d170a0 > > --- /dev/null > > +++ b/src/gst-plugin.cpp > > @@ -0,0 +1,222 @@ > > Oh, missing the copyright header. > > > +#include <config.h> > > +#include <cstring> > > +#include <exception> > > +#include <stdexcept> > > +#include <sstream> > > +#include <memory> > > +#include <syslog.h> > > +#include <unistd.h> > > +#include <gst/gst.h> > > +#include <gst/app/gstappsink.h> > > + > > +#include <spice-streaming-agent/plugin.hpp> > > +#include <spice-streaming-agent/frame-capture.hpp> > > + > > +using namespace spice::streaming_agent; > > + > > +namespace { > > +struct GstSettings > > +{ > > + int fps; > > + int encode_speed; > > +}; > > + > > +class GstFrameCapture final: public FrameCapture > > +{ > > +public: > > + GstFrameCapture(const GstSettings &settings); > > + ~GstFrameCapture(); > > + FrameInfo CaptureFrame() override; > > + void Reset() override; > > + SpiceVideoCodecType VideoCodecType() const override { > > + return SPICE_VIDEO_CODEC_TYPE_H264; > > + } > > +private: > > + void free_buffer(); > > + > > + GstElement *pipeline, *capture, *sink; > > + GstSettings settings; > > + bool is_first = true; > > + int w, h; > > + GstSample *sample = nullptr; > > + GstBuffer *buffer = nullptr; > > + GstMapInfo map = {}; > > +}; > > + > > +class GstreamerPlugin final: public Plugin > > +{ > > +public: > > + FrameCapture *CreateCapture() override; > > + unsigned Rank() override; > > + void ParseOptions(const ConfigureOption *options); > > + SpiceVideoCodecType VideoCodecType() const override { > > + return SPICE_VIDEO_CODEC_TYPE_H264; > > + } > > +private: > > + GstSettings settings = { 25, 1 }; > > +}; > > +} > > + > > +GstFrameCapture::GstFrameCapture(const GstSettings& settings): > > + settings(settings) > > +{ > > + GstElement *encoder, *convert; > > + GstCaps *caps; > > + > > + pipeline = gst_pipeline_new("pipeline"); > > + capture = gst_element_factory_make("ximagesrc", "capture"); > > + convert = gst_element_factory_make("videoconvert", "convert"); > > + encoder = gst_element_factory_make("x264enc", "encoder"); > > //TODO: move > > to use encodebin and profiles - much more generic > > + sink = gst_element_factory_make("appsink", "sink"); > > + if (!capture || !convert || !encoder || !sink) { > > + //TODO: check elements existence in build time (and\or > > improve error > > in runtime) > > + throw std::runtime_error("One or more gstreamr element > > cannot be > > created"); > > + } > > + > > + syslog(LOG_INFO, "fps = %d\n", settings.fps); > > + std::string str = > > "video/x-h264,stream-format=(string)byte-stream,framerate=" + > > std::to_string(settings.fps) + "/1"; > > + caps = gst_caps_from_string(str.c_str()); > > + g_object_set(sink, > > + "caps", caps, > > + "sync", FALSE, > > + "drop", TRUE, > > + "max-buffers", 1, > > + NULL); > > + gst_caps_unref(caps); > > + > > + g_object_set(capture, > > + "use-damage", 0, > > + NULL); > > + > > + gst_util_set_object_arg(G_OBJECT(encoder), "tune", > > "zerolatency");//stillimage,fastdecode,zerolatency > > + g_object_set(encoder, > > + "bframes", 0, > > + "speed-preset", 1, //1-ultrafast,6-med, 9- > > veryslow > > + NULL); > > + > > + gst_bin_add_many(GST_BIN(pipeline), capture, convert, encoder, > > sink, > > NULL); > > + > > + caps = gst_caps_from_string("video/x- > > raw,format=(string)I420"); > > + if (gst_element_link(capture, convert) && > > + gst_element_link_filtered(convert, encoder, caps) && > > + gst_element_link(encoder, sink)) { > > + syslog(LOG_INFO, "Elements were linked successfully\n"); > > + } else { > > + throw std::runtime_error("Link failed"); > > + } > > + > > + gst_caps_unref(caps); > > + gst_element_set_state(pipeline, GST_STATE_PLAYING);//TODO: Not > > sure > > playing state is ideal for this case (timing wise) > > +} > > + > > +void GstFrameCapture::free_buffer() > > +{ > > + if (buffer) { > > + gst_buffer_unmap(buffer, &map); > > + // don't unref buffer, will be unref when sample is unref > > + buffer = nullptr; > > + } > > + if (sample) { > > + gst_sample_unref(sample); > > + sample = nullptr; > > + } > > +} > > + > > +GstFrameCapture::~GstFrameCapture() > > +{ > > + free_buffer(); > > + gst_element_set_state(pipeline, GST_STATE_NULL); > > + gst_object_unref(pipeline); > > +} > > + > > +void GstFrameCapture::Reset() > > +{ > > + //TODO > > +} > > + > > +FrameInfo GstFrameCapture::CaptureFrame() > > +{ > > + free_buffer(); > > + > > + sample = gst_app_sink_pull_sample(GST_APP_SINK(sink));//block, > > timeout > > needed? > > + > > + FrameInfo info{}; > > + if (sample) { > > + buffer = gst_sample_get_buffer(sample); > > + gst_buffer_map(buffer, &map, GST_MAP_READ); > > + > > + info.stream_start = is_first; > > + if (is_first) { > > + int sx,sy,ex,ey; > > + g_object_get(capture, > > + "endx", &ex, > > + "endy", &ey, > > + "startx", &sx , > > + "starty", &sy, > > + NULL); > > + w = ex - sx; > > + h = ey - sy; > > + syslog(LOG_INFO, "%d x %d \n", w, h); > > + if ( h <= 0 || w <= 0 ) { // <=16? > > + throw std::runtime_error("Invalid stream size"); > > + } > > + is_first = false; > > + } > > + info.size.width = w; > > + info.size.height = h; > > + info.buffer = map.data; > > + info.buffer_size = map.size; > > + } else { > > + syslog(LOG_ERR, "No sample- EOS or state change\n"); > > + } > > + > > + return info; > > +} > > + > > +FrameCapture *GstreamerPlugin::CreateCapture() > > +{ > > + return new GstFrameCapture(settings); > > +} > > + > > +unsigned GstreamerPlugin::Rank() > > +{ > > + return SoftwareMin; > > +} > > + > > +void GstreamerPlugin::ParseOptions(const ConfigureOption *options) > > +{ > > +#define arg_error(...) syslog(LOG_ERR, ## __VA_ARGS__); > > + > > + for (; options->name; ++options) { > > + const char *name = options->name; > > + const char *value = options->value; > > + > > + if (strcmp(name, "framerate") == 0) { > > + int val = atoi(value); > > + if (val > 0) { > > + settings.fps = val; > > + } > > + else { > > + arg_error("wrong framerate arg %s\n", value); > > + } > > + } > > + } > > +} > > + > > +__attribute__ ((visibility ("default"))) > > +bool spice_streaming_agent_plugin_init(Agent* agent) > > +{ > > + if (agent->Version() != PluginVersion) { > > + return false; > > + } > > + > > + gst_init(NULL, NULL); > > + > > + std::unique_ptr<GstreamerPlugin> plugin(new > > GstreamerPlugin()); > > + > > + plugin->ParseOptions(agent->Options()); > > + > > + agent->Register(*plugin.release()); > > + > > + return true; > > +} > > Not perfect but surely helpful. > > Fine for me. > > Frediano > _______________________________________________ > Spice-devel mailing list > Spice-devel@xxxxxxxxxxxxxxxxxxxxx > https://lists.freedesktop.org/mailman/listinfo/spice-devel _______________________________________________ Spice-devel mailing list Spice-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/spice-devel