[Spice-devel] [PATCH spice-streaming-agent 2/3] Rework option handling
Frediano Ziglio
fziglio at redhat.com
Wed Nov 15 12:55:20 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.
Not clear how and why.
> */
> 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.
The agent knows its options so does not need to call ApplyOption to
know if is a standard option or not.
Why not using exceptions instead of a value passed as reference?
> + */
> + 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;
>
We should add a style document file.
> /*!
> - * 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;
> +
Is not clear this. Is the plugin telling the agent it changed some settings?
Or telling the agent to update the setting structure?
What the result mean?
> };
>
> 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)
Why not Agent::ParseIntOption or something similar?
> +{
> + 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);
Can't the agent check for standard before?
How the plugin knows the agent changed the settings?
And why StandardOption is public if called only by the agent?
> + }
> + 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);
> + });
This way the error/warning depends on the order of the plugins.
> + }
> +}
> 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);
Why this is public?
Is the program dealing directly with plugins? Should not.
> 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;
> }
>
More information about the Spice-devel
mailing list