[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