[Spice-devel] [PATCH spice-streaming-agent 2/3] Rework option handling

Christophe de Dinechin christophe at dinechin.org
Tue Nov 14 14:49:42 UTC 2017


From: Christophe de Dinechin <dinechin at redhat.com>

Several functional objectives:

1. Be able to share some common options, e.g. fps
2. Prepare for the possibility to update options on the fly

Also get rid of the null-terminated C++ vector

Signed-off-by: Christophe de Dinechin <dinechin at redhat.com>
---
 include/spice-streaming-agent/plugin.hpp | 120 +++++++++++++++++++++++++++----
 m4/spice-compile-warnings.m4             |   2 +
 src/concrete-agent.cpp                   |  77 ++++++++++++++++----
 src/concrete-agent.hpp                   |  30 ++++----
 src/mjpeg-fallback.cpp                   |  90 +++++++++++------------
 5 files changed, 227 insertions(+), 92 deletions(-)

diff --git a/include/spice-streaming-agent/plugin.hpp b/include/spice-streaming-agent/plugin.hpp
index 607fabf..41ad11f 100644
--- a/include/spice-streaming-agent/plugin.hpp
+++ b/include/spice-streaming-agent/plugin.hpp
@@ -8,6 +8,11 @@
 #define SPICE_STREAMING_AGENT_PLUGIN_HPP
 
 #include <spice/enums.h>
+#include <climits>
+#include <sstream>
+#include <string>
+#include <vector>
+
 
 /*!
  * \file
@@ -20,6 +25,7 @@
 namespace SpiceStreamingAgent {
 
 class FrameCapture;
+class Agent;
 
 /*!
  * Plugin version, only using few bits, schema is 0xMMmm
@@ -42,13 +48,23 @@ enum Ranks : unsigned {
 };
 
 /*!
- * Configuration option.
- * An array of these will be passed to the plugin.
- * Simply a couple name and value passed as string.
- * For instance "framerate" and "20".
+ * Settings that are common to all plugins
  */
-struct ConfigureOption
+struct Settings
 {
+    unsigned    framerate       =      30; // Frames per second (1-240)
+    unsigned    quality         =      80; // Normalized in 0-100 (100=high)
+    unsigned    avg_bitrate     = 3000000; // Target average bitrate in bps
+    unsigned    max_bitrate     = 8000000; // Target maximum bitrate
+};
+
+/*!
+ * Command line option
+ */
+struct Option
+{
+    Option(const char *name, const char *value)
+        : name(name), value(value) {}
     const char *name;
     const char *value;
 };
@@ -59,6 +75,9 @@ struct ConfigureOption
  * A plugin module can register multiple Plugin interfaces to handle
  * multiple codecs. In this case each Plugin will report data for a
  * specific codec.
+ *
+ * A plugin will typically extend the Settings struct to add its own
+ * settings.
  */
 class Plugin
 {
@@ -66,7 +85,17 @@ public:
     /*!
      * Allows to free the plugin when not needed
      */
-    virtual ~Plugin() {};
+    virtual ~Plugin() {}
+
+    /*!
+     * Return the name of the plugin, e.g. for command-line usage information
+     */
+    virtual const char *Name() = 0;
+
+    /*!
+     * Return usage information for the plug-in, e.g. command-line options
+     */
+    virtual const char *Usage() = 0;
 
     /*!
      * Request an object for getting frames.
@@ -89,6 +118,19 @@ public:
      * Get video codec used to encode last frame
      */
     virtual SpiceVideoCodecType VideoCodecType() const=0;
+
+    /*!
+     * Return the concrete Settings for this plugin
+     */
+    virtual Settings &  PluginSettings() = 0;
+
+    /*!
+     * Apply a given option to the plugin settings.
+     * \return true if the option is valid, false if it is not recognized
+     * If there is an error applying the settings, set \arg error.
+     * If this returns false, agent will try StandardOption.
+     */
+    virtual bool ApplyOption(const Option &o, std::string &error) = 0;
 };
 
 /*!
@@ -113,24 +155,74 @@ public:
      * Check if a given plugin version is compatible with this agent
      * \return true is compatible
      */
-    virtual bool PluginVersionIsCompatible(unsigned pluginVersion) const=0;
+    virtual bool PluginVersionIsCompatible(unsigned pluginVersion) const = 0;
 
     /*!
-     * Register a plugin in the system.
+     * Register a plugin in the system, which becomes the owner and should delete it.
      */
-    virtual void Register(Plugin& plugin)=0;
+    virtual void Register(Plugin *plugin)=0;
 
     /*!
-     * Get options array.
-     * Array is terminated with {nullptr, nullptr}.
-     * Never nullptr.
-     * \todo passing options to entry point instead?
+     * Apply the standard options to the given settings (base Settings class)
      */
-    virtual const ConfigureOption* Options() const=0;
+    virtual bool StandardOption(Settings &, const Option &, std::string &error) = 0;
+
 };
 
 typedef bool PluginInitFunc(SpiceStreamingAgent::Agent* agent);
 
+/*!
+ * Helper to get integer values from command-line options
+ */
+
+static inline int IntOption(const Option &opt, std::string &error,
+                            int min=INT_MIN, int max=INT_MAX, bool sizeSuffixOK = false)
+{
+    std::string name = opt.name;
+    std::string value = opt.value;
+    std::ostringstream err;
+    std::istringstream input(value);
+    int result = 0;
+    input >> result;
+
+    if (!input.fail() && !input.eof() && sizeSuffixOK) {
+        std::string suffix;
+        input >> suffix;
+        bool ok = false;
+        if (!input.fail() && suffix.length() == 1) {
+            switch (suffix[0]) {
+            case 'b': case 'B': ok = true;                      break;
+            case 'k': case 'K': ok = true; result *= 1000;      break;
+            case 'm': case 'M': ok = true; result *= 1000000;   break;
+            default:                                            break;
+            }
+        }
+        if (!ok) {
+            err << "Unknown number suffix " << suffix
+                << " for " << name << "\n";
+            error = err.str();
+        }
+    }
+
+    if (input.fail()) {
+        err << "Value " << value << " for " << name << " is not a number\n";
+        error = err.str();
+    }
+    if (!input.eof()) {
+        err << "Value " << value << " for " << name << " does not end like an integer\n";
+        error = err.str();
+    }
+    if (result < min || result > max) {
+        err << "The value " << value << " for " << name
+            << " is out of range (must be in " << min << ".." << max << ")\n";
+        error = err.str();        // May actually combine an earlier error
+        result = (min + max) / 2; // Give a value acceptable by caller
+    }
+
+    return result;
+}
+
+
 }
 
 #ifndef SPICE_STREAMING_AGENT_PROGRAM
diff --git a/m4/spice-compile-warnings.m4 b/m4/spice-compile-warnings.m4
index 66d7179..9e8bb6e 100644
--- a/m4/spice-compile-warnings.m4
+++ b/m4/spice-compile-warnings.m4
@@ -86,6 +86,8 @@ AC_DEFUN([SPICE_COMPILE_WARNINGS],[
     dontwarn="$dontwarn -Wpointer-sign"
     dontwarn="$dontwarn -Wpointer-to-int-cast"
     dontwarn="$dontwarn -Wstrict-prototypes"
+    dontwarn="$dontwarn -Wsuggest-final-types"
+    dontwarn="$dontwarn -Wsuggest-final-methods"
 
     # We want to enable these, but need to sort out the
     # decl mess with  gtk/generated_*.c
diff --git a/src/concrete-agent.cpp b/src/concrete-agent.cpp
index 192054a..59f11b2 100644
--- a/src/concrete-agent.cpp
+++ b/src/concrete-agent.cpp
@@ -9,6 +9,7 @@
 #include <syslog.h>
 #include <glob.h>
 #include <dlfcn.h>
+#include <mutex>
 
 #include "concrete-agent.hpp"
 #include "static-plugin.hpp"
@@ -28,7 +29,11 @@ static inline unsigned MinorVersion(unsigned version)
 
 ConcreteAgent::ConcreteAgent()
 {
-    options.push_back(ConcreteConfigureOption(nullptr, nullptr));
+}
+
+void ConcreteAgent::Register(Plugin *plugin)
+{
+    plugins.push_back(shared_ptr<Plugin>(plugin));
 }
 
 bool ConcreteAgent::PluginVersionIsCompatible(unsigned pluginVersion) const
@@ -38,22 +43,34 @@ bool ConcreteAgent::PluginVersionIsCompatible(unsigned pluginVersion) const
         MinorVersion(version) >= MinorVersion(pluginVersion);
 }
 
-void ConcreteAgent::Register(Plugin& plugin)
+bool ConcreteAgent::StandardOption(Settings &settings,
+                                   const Option &option,
+                                   string &error)
 {
-    plugins.push_back(shared_ptr<Plugin>(&plugin));
-}
+    string name = option.name;
+    if (name == "framerate" || name == "fps") {
+        settings.framerate = IntOption(option, error, 1, 240);
+        return true;
+    }
+    if (name == "quality") {
+        settings.quality = IntOption(option, error, 0, 100);
+        return true;
+    }
+    if (name == "avg_bitrate" || name == "bitrate") {
+        settings.avg_bitrate = IntOption(option, error, 10000, 32000000, true);
+        return true;
+    }
+    if (name == "max_bitrate") {
+        settings.max_bitrate = IntOption(option, error, 10000, 32000000, true);
+        return true;
+    }
 
-const ConfigureOption* ConcreteAgent::Options() const
-{
-    static_assert(sizeof(ConcreteConfigureOption) == sizeof(ConfigureOption),
-                  "ConcreteConfigureOption should be binary compatible with ConfigureOption");
-    return static_cast<const ConfigureOption*>(&options[0]);
+    return false;               // Unrecognized option
 }
 
 void ConcreteAgent::AddOption(const char *name, const char *value)
 {
-    // insert before the last {nullptr, nullptr} value
-    options.insert(--options.end(), ConcreteConfigureOption(name, value));
+    options.push_back(Option(name, value));
 }
 
 void ConcreteAgent::LoadPlugins(const char *directory)
@@ -81,7 +98,8 @@ void ConcreteAgent::LoadPlugin(const char *plugin_filename)
 {
     void *dl = dlopen(plugin_filename, RTLD_LOCAL|RTLD_NOW);
     if (!dl) {
-        syslog(LOG_ERR, "error loading plugin %s", plugin_filename);
+        syslog(LOG_ERR, "error loading plugin %s: %s",
+               plugin_filename, dlerror());
         return;
     }
 
@@ -103,7 +121,7 @@ FrameCapture *ConcreteAgent::GetBestFrameCapture()
     vector<pair<unsigned, shared_ptr<Plugin>>> sorted_plugins;
 
     // sort plugins base on ranking, reverse order
-    for (const auto& plugin: plugins) {
+    for (auto& plugin: plugins) {
         sorted_plugins.push_back(make_pair(plugin->Rank(), plugin));
     }
     sort(sorted_plugins.rbegin(), sorted_plugins.rend());
@@ -113,6 +131,7 @@ FrameCapture *ConcreteAgent::GetBestFrameCapture()
         if (plugin.first == DontUse) {
             break;
         }
+        ApplyOptions(plugin.second.get());
         FrameCapture *capture = plugin.second->CreateCapture();
         if (capture) {
             return capture;
@@ -120,3 +139,35 @@ FrameCapture *ConcreteAgent::GetBestFrameCapture()
     }
     return nullptr;
 }
+
+void ConcreteAgent::ApplyOptions(Plugin *plugin)
+{
+    bool usage = false;
+    for (const auto &opt : options) {
+        string error;
+        bool known = plugin->ApplyOption(opt, error);
+        if (!known) {
+            Settings &settings = plugin->PluginSettings();
+            known = StandardOption(settings, opt, error);
+        }
+        if (!known) {
+            syslog(LOG_ERR, "Option %s not recognized by plugin %s",
+                   opt.name, plugin->Name());
+            usage = true;
+        }
+        if (error != "") {
+            syslog(LOG_ERR, "Plugin %s did not accept setting %s=%s: %s",
+                   plugin->Name(), opt.name, opt.value, error.c_str());
+            usage = true;
+        }
+    }
+    if (usage)
+    {
+        static std:: once_flag once;
+        std::call_once(once, [plugin]()
+        {
+            const char *usage = plugin->Usage();
+            syslog(LOG_ERR, "Usage: for plugin %s: %s", plugin->Name(), usage);
+        });
+    }
+}
diff --git a/src/concrete-agent.hpp b/src/concrete-agent.hpp
index 828368b..b3d4e06 100644
--- a/src/concrete-agent.hpp
+++ b/src/concrete-agent.hpp
@@ -12,33 +12,33 @@
 
 namespace SpiceStreamingAgent {
 
-struct ConcreteConfigureOption: ConfigureOption
-{
-    ConcreteConfigureOption(const char *name, const char *value)
-    {
-        this->name = name;
-        this->value = value;
-    }
-};
-
 class ConcreteAgent final : public Agent
 {
 public:
     ConcreteAgent();
+
+public:
+    // Implementation of the Agent class virtual methods
     unsigned Version() const override {
         return PluginVersion;
     }
-    void Register(Plugin& plugin) override;
-    const ConfigureOption* Options() const override;
-    void LoadPlugins(const char *directory);
-    // pointer must remain valid
+    void Register(Plugin *plugin) override;
+    bool PluginVersionIsCompatible(unsigned pluginVersion) const override;
+    bool StandardOption(Settings &settings,
+                        const Option &option,
+                        std::string &error) override;
+
+public:
+    // Interface used by the main agent program
     void AddOption(const char *name, const char *value);
+    void LoadPlugins(const char *directory);
+    void ApplyOptions(Plugin *plugin);
     FrameCapture *GetBestFrameCapture();
-    bool PluginVersionIsCompatible(unsigned pluginVersion) const override;
+
 private:
     void LoadPlugin(const char *plugin_filename);
     std::vector<std::shared_ptr<Plugin>> plugins;
-    std::vector<ConcreteConfigureOption> options;
+    std::vector<Option> options;
 };
 
 }
diff --git a/src/mjpeg-fallback.cpp b/src/mjpeg-fallback.cpp
index f41e68a..37df01a 100644
--- a/src/mjpeg-fallback.cpp
+++ b/src/mjpeg-fallback.cpp
@@ -41,16 +41,11 @@ static inline uint64_t get_time()
 }
 
 namespace {
-struct MjpegSettings
-{
-    int fps;
-    int quality;
-};
 
 class MjpegFrameCapture final: public FrameCapture
 {
 public:
-    MjpegFrameCapture(const MjpegSettings &settings);
+    MjpegFrameCapture(Settings &settings);
     ~MjpegFrameCapture();
     FrameInfo CaptureFrame() override;
     void Reset() override;
@@ -58,8 +53,8 @@ public:
         return SPICE_VIDEO_CODEC_TYPE_MJPEG;
     }
 private:
-    MjpegSettings settings;
     Display *dpy;
+    Settings &settings;
 
     vector<uint8_t> frame;
 
@@ -72,19 +67,20 @@ private:
 class MjpegPlugin final: public Plugin
 {
 public:
+    virtual const char *Name() override;
+    virtual const char *Usage() override;
     FrameCapture *CreateCapture() override;
     unsigned Rank() override;
-    void ParseOptions(const ConfigureOption *options);
-    SpiceVideoCodecType VideoCodecType() const {
-        return SPICE_VIDEO_CODEC_TYPE_MJPEG;
-    }
+    SpiceVideoCodecType VideoCodecType() const override;
+    Settings &PluginSettings() override;
+    bool ApplyOption(const Option &o, string &error) override;
 private:
-    MjpegSettings settings = { 10, 80 };
+    Settings settings;
 };
 }
 
-MjpegFrameCapture::MjpegFrameCapture(const MjpegSettings& settings):
-    settings(settings)
+MjpegFrameCapture::MjpegFrameCapture(Settings &settings)
+    : settings(settings)
 {
     dpy = XOpenDisplay(NULL);
     if (!dpy)
@@ -111,7 +107,7 @@ FrameInfo MjpegFrameCapture::CaptureFrame()
     if (last_time == 0) {
         last_time = now;
     } else {
-        const uint64_t delta = 1000000000u / settings.fps;
+        const uint64_t delta = 1000000000u / settings.framerate;
         if (now >= last_time + delta) {
             last_time = now;
         } else {
@@ -148,13 +144,13 @@ FrameInfo MjpegFrameCapture::CaptureFrame()
 
     int format = ZPixmap;
     // TODO handle errors
-    XImage *image = XGetImage(dpy, win, win_info.x, win_info.y, 
+    XImage *image = XGetImage(dpy, win, win_info.x, win_info.y,
                               win_info.width, win_info.height, AllPlanes, format);
 
     // TODO handle errors
     // TODO multiple formats (only 32 bit)
-    write_JPEG_file(frame, settings.quality, (uint8_t*) image->data,
-                    image->width, image->height);
+    write_JPEG_file(frame, settings.quality,
+                    (uint8_t*) image->data, image->width, image->height);
 
     image->f.destroy_image(image);
 
@@ -166,6 +162,18 @@ FrameInfo MjpegFrameCapture::CaptureFrame()
     return info;
 }
 
+const char *MjpegPlugin::Name()
+{
+    return "MJPEG";
+}
+
+const char *MjpegPlugin::Usage()
+{
+    return
+        "quality        = [0-100]  Set picture quality\n"
+        "framerate      = [1-240]  Set number of frames per second\n";
+}
+
 FrameCapture *MjpegPlugin::CreateCapture()
 {
     return new MjpegFrameCapture(settings);
@@ -176,33 +184,20 @@ unsigned MjpegPlugin::Rank()
     return FallBackMin;
 }
 
-void MjpegPlugin::ParseOptions(const ConfigureOption *options)
+SpiceVideoCodecType MjpegPlugin::VideoCodecType() const
 {
-#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);
-            }
-        }
-        if (strcmp(name, "mjpeg.quality") == 0) {
-            int val = atoi(value);
-            if (val > 0) {
-                settings.quality = val;
-            }
-            else {
-                arg_error("wrong mjpeg.quality arg %s\n", value);
-            }
-        }
-    }
+    return SPICE_VIDEO_CODEC_TYPE_MJPEG;
+}
+
+Settings &MjpegPlugin::PluginSettings()
+{
+    return settings;
+}
+
+bool MjpegPlugin::ApplyOption(const Option &o, string &error)
+{
+    // This plugin only relies on base options
+    return false;
 }
 
 static bool
@@ -211,12 +206,7 @@ mjpeg_plugin_init(Agent* agent)
     if (agent->Version() != PluginVersion)
         return false;
 
-    std::unique_ptr<MjpegPlugin> plugin(new MjpegPlugin());
-
-    plugin->ParseOptions(agent->Options());
-
-    agent->Register(*plugin.release());
-
+    agent->Register(new MjpegPlugin);
     return true;
 }
 
-- 
2.13.5 (Apple Git-94)



More information about the Spice-devel mailing list