From a1a27328e284f198fad0a2b5435b48cf00be6b90 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 6 Jul 2016 19:43:37 +0200 Subject: [PATCH] v4l2: continue work on the capture device Remove activate and deactivate commands. Add STEP property range type for v4l2 frame sizes later v4l2: implement negotiation and data capture --- pinos/modules/spa/spa-alsa-sink.c | 15 +- spa/include/spa/command.h | 2 - spa/include/spa/defs.h | 1 + spa/include/spa/event.h | 8 +- spa/include/spa/node.h | 3 +- spa/include/spa/props.h | 2 + spa/lib/audio-raw.c | 35 +- spa/plugins/alsa/alsa-sink.c | 67 ++- spa/plugins/alsa/alsa-utils.c | 37 +- spa/plugins/audiomixer/audiomixer.c | 60 ++- spa/plugins/audiotestsrc/audiotestsrc.c | 60 ++- spa/plugins/v4l2/v4l2-source.c | 152 ++++--- spa/plugins/v4l2/v4l2-utils.c | 519 +++++++++++++++++++++++- spa/plugins/volume/volume.c | 60 ++- spa/tests/meson.build | 5 + spa/tests/test-mixer.c | 60 +-- spa/tests/test-v4l2.c | 198 +++++++++ 17 files changed, 976 insertions(+), 308 deletions(-) create mode 100644 spa/tests/test-v4l2.c diff --git a/pinos/modules/spa/spa-alsa-sink.c b/pinos/modules/spa/spa-alsa-sink.c index a456ec92d..600b6e642 100644 --- a/pinos/modules/spa/spa-alsa-sink.c +++ b/pinos/modules/spa/spa-alsa-sink.c @@ -25,7 +25,7 @@ #include #include -#include +#include #include "spa-alsa-sink.h" @@ -163,7 +163,6 @@ create_pipeline (PinosSpaAlsaSink *this) SpaResult res; SpaProps *props; SpaPropValue value; - SpaCommand cmd; if ((res = make_node (&priv->sink, &priv->sink_node, "spa/build/plugins/alsa/libspa-alsa.so", "alsa-sink")) < 0) { g_error ("can't create alsa-sink: %d", res); @@ -181,10 +180,6 @@ create_pipeline (PinosSpaAlsaSink *this) if ((res = priv->sink_node->set_props (priv->sink, props)) < 0) g_debug ("got set_props error %d", res); - - cmd.type = SPA_COMMAND_ACTIVATE; - if ((res = priv->sink_node->send_command (priv->sink, &cmd)) < 0) - g_debug ("got activate error %d", res); } static void @@ -218,15 +213,7 @@ stop_pipeline (PinosSpaAlsaSink *sink) static void destroy_pipeline (PinosSpaAlsaSink *sink) { - PinosSpaAlsaSinkPrivate *priv = sink->priv; - SpaResult res; - SpaCommand cmd; - g_debug ("spa-alsa-sink %p: destroy pipeline", sink); - - cmd.type = SPA_COMMAND_DEACTIVATE; - if ((res = priv->sink_node->send_command (priv->sink, &cmd)) < 0) - g_debug ("got error %d", res); } static gboolean diff --git a/spa/include/spa/command.h b/spa/include/spa/command.h index fff02fed2..e61fd24eb 100644 --- a/spa/include/spa/command.h +++ b/spa/include/spa/command.h @@ -30,8 +30,6 @@ typedef struct _SpaCommand SpaCommand; typedef enum { SPA_COMMAND_INVALID = 0, - SPA_COMMAND_ACTIVATE, - SPA_COMMAND_DEACTIVATE, SPA_COMMAND_START, SPA_COMMAND_STOP, SPA_COMMAND_FLUSH, diff --git a/spa/include/spa/defs.h b/spa/include/spa/defs.h index 730f5f953..963308d94 100644 --- a/spa/include/spa/defs.h +++ b/spa/include/spa/defs.h @@ -54,6 +54,7 @@ typedef enum { SPA_RESULT_INVALID_DIRECTION = -23, SPA_RESULT_TOO_MANY_PORTS = -24, SPA_RESULT_INVALID_PROPERTY_ACCESS = -25, + SPA_RESULT_UNEXPECTED = -26, } SpaResult; typedef enum { diff --git a/spa/include/spa/event.h b/spa/include/spa/event.h index 211adb077..96cbfd24f 100644 --- a/spa/include/spa/event.h +++ b/spa/include/spa/event.h @@ -31,8 +31,8 @@ typedef struct _SpaEvent SpaEvent; /** * SpaEventType: * @SPA_EVENT_TYPE_INVALID: invalid event, should be ignored - * @SPA_EVENT_TYPE_ACTIVATED: emited when the ACTIVATE command completes - * @SPA_EVENT_TYPE_DEACTIVATED: emited when the DEACTIVATE command completes + * @SPA_EVENT_TYPE_STARTED: emited when the START command completes + * @SPA_EVENT_TYPE_STOPPED: emited when the STOP command completes * @SPA_EVENT_TYPE_CAN_PULL_OUTPUT: emited when an async node has output that can be pulled * @SPA_EVENT_TYPE_CAN_PUSH_INTPUT: emited when more data can be pushed to an async node * @SPA_EVENT_TYPE_PULL_INPUT: emited when data needs to be provided on an input @@ -46,8 +46,8 @@ typedef struct _SpaEvent SpaEvent; */ typedef enum { SPA_EVENT_TYPE_INVALID = 0, - SPA_EVENT_TYPE_ACTIVATED, - SPA_EVENT_TYPE_DEACTIVATED, + SPA_EVENT_TYPE_STARTED, + SPA_EVENT_TYPE_STOPPED, SPA_EVENT_TYPE_CAN_PULL_OUTPUT, SPA_EVENT_TYPE_CAN_PUSH_INTPUT, SPA_EVENT_TYPE_PULL_INPUT, diff --git a/spa/include/spa/node.h b/spa/include/spa/node.h index eb5ca91dc..a23d77c3b 100644 --- a/spa/include/spa/node.h +++ b/spa/include/spa/node.h @@ -265,6 +265,7 @@ struct _SpaNode { * SpaNode::set_port_format: * @handle: a #SpaHandle * @port_id: the port to configure + * @test_only: only check if the format is accepted * @format: a #SpaFormat with the format * * Set a format on @port_id of @node. @@ -282,7 +283,7 @@ struct _SpaNode { */ SpaResult (*set_port_format) (SpaHandle *handle, uint32_t port_id, - int test_only, + bool test_only, const SpaFormat *format); /** * SpaNode::get_port_format: diff --git a/spa/include/spa/props.h b/spa/include/spa/props.h index 56bb5392a..c059aed30 100644 --- a/spa/include/spa/props.h +++ b/spa/include/spa/props.h @@ -79,12 +79,14 @@ typedef enum { /* SpaPropRangeType: * @SPA_PROP_RANGE_TYPE_NONE: no range specified, full range of type applies * @SPA_PROP_RANGE_TYPE_MIN_MAX: range contains 2 values, min and max + * @SPA_PROP_RANGE_TYPE_STEP: range contains 3 values, min, max and step * @SPA_PROP_RANGE_TYPE_ENUM: range contains enum of possible values * @SPA_PROP_RANGE_TYPE_FLAGS: range contains flags of possible values */ typedef enum { SPA_PROP_RANGE_TYPE_NONE = 0, SPA_PROP_RANGE_TYPE_MIN_MAX, + SPA_PROP_RANGE_TYPE_STEP, SPA_PROP_RANGE_TYPE_ENUM, SPA_PROP_RANGE_TYPE_FLAGS, } SpaPropRangeType; diff --git a/spa/lib/audio-raw.c b/spa/lib/audio-raw.c index 80f590af2..2e9f5a827 100644 --- a/spa/lib/audio-raw.c +++ b/spa/lib/audio-raw.c @@ -25,6 +25,15 @@ #include #include +static const SpaAudioRawInfo default_info = { + SPA_AUDIO_FORMAT_S16, + SPA_AUDIO_FLAG_NONE, + SPA_AUDIO_LAYOUT_INTERLEAVED, + 44100, + 2, + 0 +}; + static const uint32_t format_values[] = { SPA_AUDIO_FORMAT_S8, SPA_AUDIO_FORMAT_U8, @@ -98,13 +107,6 @@ static const SpaPropRangeInfo format_format_range[] = { { "F64BE", "F64BE", sizeof (uint32_t), &format_values[29] }, }; -static const uint32_t default_format = SPA_AUDIO_FORMAT_S16; -static const uint32_t default_flags = SPA_AUDIO_FLAG_NONE; -static const uint32_t default_layout = SPA_AUDIO_LAYOUT_INTERLEAVED; -static const uint32_t default_rate = 44100; -static const uint32_t default_channels = 2; -static const uint32_t default_channel_mask = 0; - static const uint32_t format_layouts[] = { SPA_AUDIO_LAYOUT_INTERLEAVED, SPA_AUDIO_LAYOUT_NON_INTERLEAVED, @@ -138,7 +140,7 @@ static const SpaPropInfo raw_format_prop_info[] = { SPA_PROP_ID_AUDIO_FORMAT, "format", "The media format", SPA_PROP_FLAG_READWRITE, SPA_PROP_TYPE_UINT32, sizeof (uint32_t), - sizeof (uint32_t), &default_format, + sizeof (uint32_t), &default_info.format, SPA_PROP_RANGE_TYPE_ENUM, SPA_N_ELEMENTS (format_format_range), format_format_range, NULL, offsetof (SpaAudioRawFormat, info.format), @@ -147,7 +149,7 @@ static const SpaPropInfo raw_format_prop_info[] = { SPA_PROP_ID_AUDIO_FLAGS, "flags", "Sample Flags", SPA_PROP_FLAG_READWRITE, SPA_PROP_TYPE_UINT32, sizeof (uint32_t), - sizeof (uint32_t), &default_flags, + sizeof (uint32_t), &default_info.flags, SPA_PROP_RANGE_TYPE_FLAGS, SPA_N_ELEMENTS (flags_range), flags_range, NULL, offsetof (SpaAudioRawFormat, info.flags), @@ -156,7 +158,7 @@ static const SpaPropInfo raw_format_prop_info[] = { SPA_PROP_ID_AUDIO_LAYOUT, "layout", "Sample Layout", SPA_PROP_FLAG_READWRITE, SPA_PROP_TYPE_UINT32, sizeof (uint32_t), - sizeof (uint32_t), &default_layout, + sizeof (uint32_t), &default_info.layout, SPA_PROP_RANGE_TYPE_ENUM, SPA_N_ELEMENTS (layouts_range), layouts_range, NULL, offsetof (SpaAudioRawFormat, info.layout), @@ -165,7 +167,7 @@ static const SpaPropInfo raw_format_prop_info[] = { SPA_PROP_ID_AUDIO_RATE, "rate", "Audio sample rate", SPA_PROP_FLAG_READWRITE, SPA_PROP_TYPE_UINT32, sizeof (uint32_t), - sizeof (uint32_t), &default_rate, + sizeof (uint32_t), &default_info.rate, SPA_PROP_RANGE_TYPE_MIN_MAX, 2, uint32_range, NULL, offsetof (SpaAudioRawFormat, info.rate), @@ -174,7 +176,7 @@ static const SpaPropInfo raw_format_prop_info[] = { SPA_PROP_ID_AUDIO_CHANNELS, "channels", "Audio channels", SPA_PROP_FLAG_READWRITE, SPA_PROP_TYPE_UINT32, sizeof (uint32_t), - sizeof (uint32_t), &default_channels, + sizeof (uint32_t), &default_info.channels, SPA_PROP_RANGE_TYPE_MIN_MAX, 2, uint32_range, NULL, offsetof (SpaAudioRawFormat, info.channels), @@ -183,7 +185,7 @@ static const SpaPropInfo raw_format_prop_info[] = { SPA_PROP_ID_AUDIO_CHANNEL_MASK, "channel-mask", "Audio channel mask", SPA_PROP_FLAG_READWRITE, SPA_PROP_TYPE_BITMASK, sizeof (uint32_t), - sizeof (uint32_t), &default_channel_mask, + sizeof (uint32_t), &default_info.channel_mask, SPA_PROP_RANGE_TYPE_NONE, 0, NULL, NULL, offsetof (SpaAudioRawFormat, info.channel_mask), @@ -210,12 +212,7 @@ spa_audio_raw_format_init (SpaAudioRawFormat *format) format->format.props.set_prop = spa_props_generic_set_prop; format->format.props.get_prop = spa_props_generic_get_prop; format->unset_mask = (1 << 0) | (1 << 2) | (1 << 3) | (1 << 4); - format->info.format = default_format; - format->info.flags = default_flags; - format->info.layout = default_layout; - format->info.rate = default_rate; - format->info.channels = default_channels; - format->info.channel_mask = default_channel_mask; + format->info = default_info; return SPA_RESULT_OK; } diff --git a/spa/plugins/alsa/alsa-sink.c b/spa/plugins/alsa/alsa-sink.c index 9b7816761..382147379 100644 --- a/spa/plugins/alsa/alsa-sink.c +++ b/spa/plugins/alsa/alsa-sink.c @@ -52,6 +52,7 @@ reset_alsa_sink_props (SpaALSASinkProps *props) } typedef struct { + bool opened; snd_pcm_t *handle; snd_output_t *output; snd_pcm_sframes_t buffer_size; @@ -77,8 +78,6 @@ struct _SpaALSASink { SpaALSASinkProps tmp_props; SpaALSASinkProps props; - bool activated; - SpaEventCallback event_cb; void *user_data; @@ -223,47 +222,37 @@ spa_alsa_sink_node_send_command (SpaHandle *handle, case SPA_COMMAND_INVALID: return SPA_RESULT_INVALID_COMMAND; - case SPA_COMMAND_ACTIVATE: - if (!this->activated) { - spa_alsa_open (this); - this->activated = true; - } - if (this->event_cb) { - SpaEvent event; - - event.refcount = 1; - event.notify = NULL; - event.type = SPA_EVENT_TYPE_ACTIVATED; - event.port_id = -1; - event.data = NULL; - event.size = 0; - - this->event_cb (handle, &event, this->user_data); - } - break; - case SPA_COMMAND_DEACTIVATE: - if (this->activated) { - spa_alsa_close (this); - this->activated = false; - } - if (this->event_cb) { - SpaEvent event; - - event.refcount = 1; - event.notify = NULL; - event.type = SPA_EVENT_TYPE_DEACTIVATED; - event.port_id = -1; - event.data = NULL; - event.size = 0; - - this->event_cb (handle, &event, this->user_data); - } - break; case SPA_COMMAND_START: spa_alsa_start (this); + + if (this->event_cb) { + SpaEvent event; + + event.refcount = 1; + event.notify = NULL; + event.type = SPA_EVENT_TYPE_STARTED; + event.port_id = -1; + event.data = NULL; + event.size = 0; + + this->event_cb (handle, &event, this->user_data); + } break; case SPA_COMMAND_STOP: spa_alsa_stop (this); + + if (this->event_cb) { + SpaEvent event; + + event.refcount = 1; + event.notify = NULL; + event.type = SPA_EVENT_TYPE_STOPPED; + event.port_id = -1; + event.data = NULL; + event.size = 0; + + this->event_cb (handle, &event, this->user_data); + } break; case SPA_COMMAND_FLUSH: case SPA_COMMAND_DRAIN: @@ -375,7 +364,7 @@ spa_alsa_sink_node_enum_port_formats (SpaHandle *handle, static SpaResult spa_alsa_sink_node_set_port_format (SpaHandle *handle, uint32_t port_id, - int test_only, + bool test_only, const SpaFormat *format) { SpaALSASink *this = (SpaALSASink *) handle; diff --git a/spa/plugins/alsa/alsa-utils.c b/spa/plugins/alsa/alsa-utils.c index f5cb1de35..cef93201f 100644 --- a/spa/plugins/alsa/alsa-utils.c +++ b/spa/plugins/alsa/alsa-utils.c @@ -310,6 +310,9 @@ spa_alsa_open (SpaALSASink *this) SpaALSAState *state = &this->state; int err; + if (state->opened) + return 0; + CHECK (snd_output_stdio_attach (&state->output, stdout, 0), "attach failed"); printf ("Playback device is '%s'\n", this->props.device); @@ -321,15 +324,36 @@ spa_alsa_open (SpaALSASink *this) SND_PCM_NO_AUTO_CHANNELS | SND_PCM_NO_AUTO_FORMAT), "open failed"); + state->opened = true; + return 0; } +static int +spa_alsa_close (SpaALSASink *this) +{ + SpaALSAState *state = &this->state; + int err = 0; + + if (!state->opened) + return 0; + + CHECK (snd_pcm_close (state->handle), "close failed"); + + state->opened = false; + + return err; +} + static int spa_alsa_start (SpaALSASink *this) { SpaALSAState *state = &this->state; int err; + if (spa_alsa_open (this) < 0) + return -1; + CHECK (set_hwparams (this), "hwparams"); CHECK (set_swparams (this), "swparams"); @@ -352,16 +376,7 @@ spa_alsa_stop (SpaALSASink *this) state->running = false; pthread_join (state->thread, NULL); } + spa_alsa_close (this); + return 0; } - -static int -spa_alsa_close (SpaALSASink *this) -{ - SpaALSAState *state = &this->state; - int err = 0; - - CHECK (snd_pcm_close (state->handle), "close failed"); - - return err; -} diff --git a/spa/plugins/audiomixer/audiomixer.c b/spa/plugins/audiomixer/audiomixer.c index 59c492c65..b141c383a 100644 --- a/spa/plugins/audiomixer/audiomixer.c +++ b/spa/plugins/audiomixer/audiomixer.c @@ -137,38 +137,36 @@ spa_audiomixer_node_send_command (SpaHandle *handle, case SPA_COMMAND_INVALID: return SPA_RESULT_INVALID_COMMAND; - case SPA_COMMAND_ACTIVATE: - if (this->event_cb) { - SpaEvent event; - - event.refcount = 1; - event.notify = NULL; - event.type = SPA_EVENT_TYPE_ACTIVATED; - event.port_id = -1; - event.data = NULL; - event.size = 0; - - this->event_cb (handle, &event, this->user_data); - } - break; - - case SPA_COMMAND_DEACTIVATE: - if (this->event_cb) { - SpaEvent event; - - event.refcount = 1; - event.notify = NULL; - event.type = SPA_EVENT_TYPE_DEACTIVATED; - event.port_id = -1; - event.data = NULL; - event.size = 0; - - this->event_cb (handle, &event, this->user_data); - } - break; - case SPA_COMMAND_START: + if (this->event_cb) { + SpaEvent event; + + event.refcount = 1; + event.notify = NULL; + event.type = SPA_EVENT_TYPE_STARTED; + event.port_id = -1; + event.data = NULL; + event.size = 0; + + this->event_cb (handle, &event, this->user_data); + } + break; + case SPA_COMMAND_STOP: + if (this->event_cb) { + SpaEvent event; + + event.refcount = 1; + event.notify = NULL; + event.type = SPA_EVENT_TYPE_STOPPED; + event.port_id = -1; + event.data = NULL; + event.size = 0; + + this->event_cb (handle, &event, this->user_data); + } + break; + case SPA_COMMAND_FLUSH: case SPA_COMMAND_DRAIN: case SPA_COMMAND_MARKER: @@ -330,7 +328,7 @@ spa_audiomixer_node_enum_port_formats (SpaHandle *handle, static SpaResult spa_audiomixer_node_set_port_format (SpaHandle *handle, uint32_t port_id, - int test_only, + bool test_only, const SpaFormat *format) { SpaAudioMixer *this = (SpaAudioMixer *) handle; diff --git a/spa/plugins/audiotestsrc/audiotestsrc.c b/spa/plugins/audiotestsrc/audiotestsrc.c index 002e5e85e..f56e284ce 100644 --- a/spa/plugins/audiotestsrc/audiotestsrc.c +++ b/spa/plugins/audiotestsrc/audiotestsrc.c @@ -169,38 +169,36 @@ spa_audiotestsrc_node_send_command (SpaHandle *handle, case SPA_COMMAND_INVALID: return SPA_RESULT_INVALID_COMMAND; - case SPA_COMMAND_ACTIVATE: - if (this->event_cb) { - SpaEvent event; - - event.refcount = 1; - event.notify = NULL; - event.type = SPA_EVENT_TYPE_ACTIVATED; - event.port_id = -1; - event.data = NULL; - event.size = 0; - - this->event_cb (handle, &event, this->user_data); - } - break; - - case SPA_COMMAND_DEACTIVATE: - if (this->event_cb) { - SpaEvent event; - - event.refcount = 1; - event.notify = NULL; - event.type = SPA_EVENT_TYPE_DEACTIVATED; - event.port_id = -1; - event.data = NULL; - event.size = 0; - - this->event_cb (handle, &event, this->user_data); - } - break; - case SPA_COMMAND_START: + if (this->event_cb) { + SpaEvent event; + + event.refcount = 1; + event.notify = NULL; + event.type = SPA_EVENT_TYPE_STARTED; + event.port_id = -1; + event.data = NULL; + event.size = 0; + + this->event_cb (handle, &event, this->user_data); + } + break; + case SPA_COMMAND_STOP: + if (this->event_cb) { + SpaEvent event; + + event.refcount = 1; + event.notify = NULL; + event.type = SPA_EVENT_TYPE_STOPPED; + event.port_id = -1; + event.data = NULL; + event.size = 0; + + this->event_cb (handle, &event, this->user_data); + } + break; + case SPA_COMMAND_FLUSH: case SPA_COMMAND_DRAIN: case SPA_COMMAND_MARKER: @@ -307,7 +305,7 @@ spa_audiotestsrc_node_enum_port_formats (SpaHandle *handle, static SpaResult spa_audiotestsrc_node_set_port_format (SpaHandle *handle, uint32_t port_id, - int test_only, + bool test_only, const SpaFormat *format) { SpaAudioTestSrc *this = (SpaAudioTestSrc *) handle; diff --git a/spa/plugins/v4l2/v4l2-source.c b/spa/plugins/v4l2/v4l2-source.c index 614fe5bfb..bc7a984a2 100644 --- a/spa/plugins/v4l2/v4l2-source.c +++ b/spa/plugins/v4l2/v4l2-source.c @@ -22,8 +22,8 @@ #include #include -#include #include +#include #include #include @@ -45,11 +45,7 @@ reset_v4l2_source_props (SpaV4l2SourceProps *props) strncpy (props->device, default_device, 64); } -typedef struct { - int fd; - pthread_t thread; - bool running; -} SpaV4l2State; +#define MAX_BUFFERS 256 typedef struct _V4l2Buffer V4l2Buffer; @@ -59,29 +55,42 @@ struct _V4l2Buffer { SpaMetaHeader header; SpaData data[1]; V4l2Buffer *next; + uint32_t index; + SpaV4l2Source *source; + bool outstanding; }; +typedef struct { + bool opened; + int fd; + struct v4l2_capability cap; + struct v4l2_format fmt; + enum v4l2_buf_type type; + struct v4l2_requestbuffers reqbuf; + pthread_t thread; + bool running; + V4l2Buffer buffers[MAX_BUFFERS]; + V4l2Buffer *ready; + uint32_t ready_count; +} SpaV4l2State; + struct _SpaV4l2Source { SpaHandle handle; SpaV4l2SourceProps tmp_props; SpaV4l2SourceProps props; - bool activated; - SpaEventCallback event_cb; void *user_data; - bool have_format; - SpaVideoRawFormat query_format; - SpaVideoRawFormat current_format; + SpaVideoRawFormat raw_format[2]; + SpaFormat *current_format; SpaV4l2State state; SpaPortInfo info; SpaPortStatus status; - V4l2Buffer buffer; }; #include "v4l2-utils.c" @@ -181,48 +190,39 @@ spa_v4l2_source_node_send_command (SpaHandle *handle, case SPA_COMMAND_INVALID: return SPA_RESULT_INVALID_COMMAND; - case SPA_COMMAND_ACTIVATE: - if (!this->activated) { - spa_v4l2_open (this); - this->activated = true; - } - if (this->event_cb) { - SpaEvent event; - - event.refcount = 1; - event.notify = NULL; - event.type = SPA_EVENT_TYPE_ACTIVATED; - event.port_id = -1; - event.data = NULL; - event.size = 0; - - this->event_cb (handle, &event, this->user_data); - } - break; - case SPA_COMMAND_DEACTIVATE: - if (this->activated) { - spa_v4l2_close (this); - this->activated = false; - } - if (this->event_cb) { - SpaEvent event; - - event.refcount = 1; - event.notify = NULL; - event.type = SPA_EVENT_TYPE_DEACTIVATED; - event.port_id = -1; - event.data = NULL; - event.size = 0; - - this->event_cb (handle, &event, this->user_data); - } - break; case SPA_COMMAND_START: spa_v4l2_start (this); + + if (this->event_cb) { + SpaEvent event; + + event.refcount = 1; + event.notify = NULL; + event.type = SPA_EVENT_TYPE_STARTED; + event.port_id = -1; + event.data = NULL; + event.size = 0; + + this->event_cb (handle, &event, this->user_data); + } break; case SPA_COMMAND_STOP: spa_v4l2_stop (this); + + if (this->event_cb) { + SpaEvent event; + + event.refcount = 1; + event.notify = NULL; + event.type = SPA_EVENT_TYPE_STOPPED; + event.port_id = -1; + event.data = NULL; + event.size = 0; + + this->event_cb (handle, &event, this->user_data); + } break; + case SPA_COMMAND_FLUSH: case SPA_COMMAND_DRAIN: case SPA_COMMAND_MARKER: @@ -317,12 +317,12 @@ spa_v4l2_source_node_enum_port_formats (SpaHandle *handle, switch (index) { case 0: - spa_video_raw_format_init (&this->query_format); + spa_video_raw_format_init (&this->raw_format[0]); break; default: return SPA_RESULT_ENUM_END; } - *format = &this->query_format.format; + *format = &this->raw_format[0].format; return SPA_RESULT_OK; } @@ -330,11 +330,13 @@ spa_v4l2_source_node_enum_port_formats (SpaHandle *handle, static SpaResult spa_v4l2_source_node_set_port_format (SpaHandle *handle, uint32_t port_id, - int test_only, + bool test_only, const SpaFormat *format) { SpaV4l2Source *this = (SpaV4l2Source *) handle; SpaResult res; + SpaFormat *f, *tf; + size_t fs; if (handle == NULL || format == NULL) return SPA_RESULT_INVALID_ARGUMENTS; @@ -343,14 +345,30 @@ spa_v4l2_source_node_set_port_format (SpaHandle *handle, return SPA_RESULT_INVALID_PORT; if (format == NULL) { - this->have_format = false; + this->current_format = NULL; return SPA_RESULT_OK; } - if ((res = spa_video_raw_format_parse (format, &this->current_format)) < 0) - return res; + if (format->media_type == SPA_MEDIA_TYPE_VIDEO) { + if (format->media_subtype == SPA_MEDIA_SUBTYPE_RAW) { + if ((res = spa_video_raw_format_parse (format, &this->raw_format[0]) < 0)) + return res; - this->have_format = true; + f = &this->raw_format[0].format; + tf = &this->raw_format[1].format; + fs = sizeof (SpaVideoRawFormat); + } else + return SPA_RESULT_INVALID_MEDIA_TYPE; + } else + return SPA_RESULT_INVALID_MEDIA_TYPE; + + if (spa_v4l2_set_format (this, f, test_only) < 0) + return SPA_RESULT_INVALID_MEDIA_TYPE; + + if (!test_only) { + memcpy (tf, f, fs); + this->current_format = tf; + } return SPA_RESULT_OK; } @@ -368,10 +386,10 @@ spa_v4l2_source_node_get_port_format (SpaHandle *handle, if (port_id != 0) return SPA_RESULT_INVALID_PORT; - if (!this->have_format) + if (this->current_format == NULL) return SPA_RESULT_NO_FORMAT; - *format = &this->current_format.format; + *format = this->current_format; return SPA_RESULT_OK; } @@ -442,23 +460,41 @@ spa_v4l2_source_node_pull_port_output (SpaHandle *handle, SpaOutputInfo *info) { SpaV4l2Source *this = (SpaV4l2Source *) handle; + SpaV4l2State *state; unsigned int i; bool have_error = false; if (handle == NULL || n_info == 0 || info == NULL) return SPA_RESULT_INVALID_ARGUMENTS; + state = &this->state; + for (i = 0; i < n_info; i++) { + V4l2Buffer *b; + if (info[i].port_id != 0) { info[i].status = SPA_RESULT_INVALID_PORT; have_error = true; continue; } - if (!this->have_format) { + if (this->current_format == NULL) { info[i].status = SPA_RESULT_NO_FORMAT; have_error = true; continue; } + if (state->ready_count == 0) { + info[i].status = SPA_RESULT_UNEXPECTED; + have_error = true; + continue; + } + + b = state->ready; + state->ready = b->next; + state->ready_count--; + + b->outstanding = true; + + info[i].buffer = &b->buffer; info[i].status = SPA_RESULT_OK; } if (have_error) diff --git a/spa/plugins/v4l2/v4l2-utils.c b/spa/plugins/v4l2/v4l2-utils.c index 8c797dc7a..5aba05a39 100644 --- a/spa/plugins/v4l2/v4l2-utils.c +++ b/spa/plugins/v4l2/v4l2-utils.c @@ -1,20 +1,35 @@ #include -#include #include +#include #include #include -#include -#include -#include +#include +#include +#include -#define CHECK(s,msg) if ((err = (s)) < 0) { printf (msg ": %s\n", snd_strerror(err)); return err; } +#define CLEAR(x) memset(&(x), 0, sizeof(x)) +static int +xioctl (int fd, int request, void *arg) +{ + int err; + + do { + err = ioctl (fd, request, arg); + } while (err == -1 && errno == EINTR); + + return err; +} static int spa_v4l2_open (SpaV4l2Source *this) { + SpaV4l2State *state = &this->state; struct stat st; + if (state->opened) + return 0; + fprintf (stderr, "Playback device is '%s'\n", this->props.device); if (stat (this->props.device, &st) < 0) { @@ -28,42 +43,511 @@ spa_v4l2_open (SpaV4l2Source *this) return -1; } - this->state.fd = open (this->props.device, O_RDWR | O_NONBLOCK, 0); + state->fd = open (this->props.device, O_RDWR | O_NONBLOCK, 0); - if (this->state.fd == -1) { - fprintf(stderr, "Cannot open '%s': %d, %s\n", + if (state->fd == -1) { + fprintf (stderr, "Cannot open '%s': %d, %s\n", this->props.device, errno, strerror (errno)); return -1; } + + if (xioctl (state->fd, VIDIOC_QUERYCAP, &state->cap) < 0) { + perror ("QUERYCAP"); + return -1; + } + + if ((state->cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == 0) { + fprintf (stderr, "%s is no video capture device\n", this->props.device); + return -1; + } + state->opened = true; + + return 0; +} + +#if 0 +static SpaVideoFormat +fourcc_to_video_format (uint32_t fourcc) +{ + SpaVideoFormat format; + + switch (fourcc) { + case V4L2_PIX_FMT_GREY: /* 8 Greyscale */ + format = SPA_VIDEO_FORMAT_GRAY8; + break; + case V4L2_PIX_FMT_Y16: + format = SPA_VIDEO_FORMAT_GRAY16_LE; + break; + case V4L2_PIX_FMT_Y16_BE: + format = SPA_VIDEO_FORMAT_GRAY16_BE; + break; + case V4L2_PIX_FMT_XRGB555: + case V4L2_PIX_FMT_RGB555: + format = SPA_VIDEO_FORMAT_RGB15; + break; + case V4L2_PIX_FMT_XRGB555X: + case V4L2_PIX_FMT_RGB555X: + format = SPA_VIDEO_FORMAT_BGR15; + break; + case V4L2_PIX_FMT_RGB565: + format = SPA_VIDEO_FORMAT_RGB16; + break; + case V4L2_PIX_FMT_RGB24: + format = SPA_VIDEO_FORMAT_RGB; + break; + case V4L2_PIX_FMT_BGR24: + format = SPA_VIDEO_FORMAT_BGR; + break; + case V4L2_PIX_FMT_XRGB32: + case V4L2_PIX_FMT_RGB32: + format = SPA_VIDEO_FORMAT_xRGB; + break; + case V4L2_PIX_FMT_XBGR32: + case V4L2_PIX_FMT_BGR32: + format = SPA_VIDEO_FORMAT_BGRx; + break; + case V4L2_PIX_FMT_ABGR32: + format = SPA_VIDEO_FORMAT_BGRA; + break; + case V4L2_PIX_FMT_ARGB32: + format = SPA_VIDEO_FORMAT_ARGB; + break; + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV12M: + format = SPA_VIDEO_FORMAT_NV12; + break; + case V4L2_PIX_FMT_NV12MT: + format = SPA_VIDEO_FORMAT_NV12_64Z32; + break; + case V4L2_PIX_FMT_NV21: + case V4L2_PIX_FMT_NV21M: + format = SPA_VIDEO_FORMAT_NV21; + break; + case V4L2_PIX_FMT_YVU410: + format = SPA_VIDEO_FORMAT_YVU9; + break; + case V4L2_PIX_FMT_YUV410: + format = SPA_VIDEO_FORMAT_YUV9; + break; + case V4L2_PIX_FMT_YUV420: + case V4L2_PIX_FMT_YUV420M: + format = SPA_VIDEO_FORMAT_I420; + break; + case V4L2_PIX_FMT_YUYV: + format = SPA_VIDEO_FORMAT_YUY2; + break; + case V4L2_PIX_FMT_YVU420: + format = SPA_VIDEO_FORMAT_YV12; + break; + case V4L2_PIX_FMT_UYVY: + format = SPA_VIDEO_FORMAT_UYVY; + break; + case V4L2_PIX_FMT_YUV411P: + format = SPA_VIDEO_FORMAT_Y41B; + break; + case V4L2_PIX_FMT_YUV422P: + format = SPA_VIDEO_FORMAT_Y42B; + break; + case V4L2_PIX_FMT_YVYU: + format = SPA_VIDEO_FORMAT_YVYU; + break; + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_NV16M: + format = SPA_VIDEO_FORMAT_NV16; + break; + case V4L2_PIX_FMT_NV61: + case V4L2_PIX_FMT_NV61M: + format = SPA_VIDEO_FORMAT_NV61; + break; + case V4L2_PIX_FMT_NV24: + format = SPA_VIDEO_FORMAT_NV24; + break; + default: + format = SPA_VIDEO_FORMAT_UNKNOWN; + break; + } + return format; +} +#endif + +static uint32_t +video_format_to_fourcc (SpaVideoFormat format) +{ + uint32_t fourcc; + + switch (format) { + case SPA_VIDEO_FORMAT_I420: + fourcc = V4L2_PIX_FMT_YUV420; + break; + case SPA_VIDEO_FORMAT_YUY2: + fourcc = V4L2_PIX_FMT_YUYV; + break; + case SPA_VIDEO_FORMAT_UYVY: + fourcc = V4L2_PIX_FMT_UYVY; + break; + case SPA_VIDEO_FORMAT_YV12: + fourcc = V4L2_PIX_FMT_YVU420; + break; + case SPA_VIDEO_FORMAT_Y41B: + fourcc = V4L2_PIX_FMT_YUV411P; + break; + case SPA_VIDEO_FORMAT_Y42B: + fourcc = V4L2_PIX_FMT_YUV422P; + break; + case SPA_VIDEO_FORMAT_NV12: + fourcc = V4L2_PIX_FMT_NV12; + break; + case SPA_VIDEO_FORMAT_NV12_64Z32: + fourcc = V4L2_PIX_FMT_NV12MT; + break; + case SPA_VIDEO_FORMAT_NV21: + fourcc = V4L2_PIX_FMT_NV21; + break; + case SPA_VIDEO_FORMAT_NV16: + fourcc = V4L2_PIX_FMT_NV16; + break; + case SPA_VIDEO_FORMAT_NV61: + fourcc = V4L2_PIX_FMT_NV61; + break; + case SPA_VIDEO_FORMAT_NV24: + fourcc = V4L2_PIX_FMT_NV24; + break; + case SPA_VIDEO_FORMAT_YVYU: + fourcc = V4L2_PIX_FMT_YVYU; + break; + case SPA_VIDEO_FORMAT_RGB15: + fourcc = V4L2_PIX_FMT_RGB555; + break; + case SPA_VIDEO_FORMAT_RGB16: + fourcc = V4L2_PIX_FMT_RGB565; + break; + case SPA_VIDEO_FORMAT_RGB: + fourcc = V4L2_PIX_FMT_RGB24; + break; + case SPA_VIDEO_FORMAT_BGR: + fourcc = V4L2_PIX_FMT_BGR24; + break; + case SPA_VIDEO_FORMAT_xRGB: + fourcc = V4L2_PIX_FMT_RGB32; + break; + case SPA_VIDEO_FORMAT_ARGB: + fourcc = V4L2_PIX_FMT_RGB32; + break; + case SPA_VIDEO_FORMAT_BGRx: + fourcc = V4L2_PIX_FMT_BGR32; + break; + case SPA_VIDEO_FORMAT_BGRA: + fourcc = V4L2_PIX_FMT_BGR32; + break; + case SPA_VIDEO_FORMAT_GRAY8: + fourcc = V4L2_PIX_FMT_GREY; + break; + case SPA_VIDEO_FORMAT_GRAY16_LE: + fourcc = V4L2_PIX_FMT_Y16; + break; + case SPA_VIDEO_FORMAT_GRAY16_BE: + fourcc = V4L2_PIX_FMT_Y16_BE; + break; + default: + fourcc = 0; + break; + } + return fourcc; +} + + +static int +spa_v4l2_set_format (SpaV4l2Source *this, SpaFormat *format, bool try_only) +{ + SpaV4l2State *state = &this->state; + int cmd = try_only ? VIDIOC_TRY_FMT : VIDIOC_S_FMT; + struct v4l2_format reqfmt, fmt; + + CLEAR (fmt); + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + if (format->media_type == SPA_MEDIA_TYPE_VIDEO) { + if (format->media_subtype == SPA_MEDIA_SUBTYPE_RAW) { + SpaVideoRawFormat *f = (SpaVideoRawFormat *) format; + + fmt.fmt.pix.pixelformat = video_format_to_fourcc (f->info.format); + fmt.fmt.pix.width = f->info.width; + fmt.fmt.pix.height = f->info.height; + fmt.fmt.pix.field = V4L2_FIELD_ANY; + fprintf (stderr, "set %08x %dx%d\n", fmt.fmt.pix.pixelformat, + fmt.fmt.pix.width, fmt.fmt.pix.height); + } else + return -1; + } else + return -1; + + reqfmt = fmt; + + if (spa_v4l2_open (this) < 0) + return -1; + + if (xioctl (state->fd, cmd, &fmt) < 0) { + perror ("VIDIOC_S_FMT"); + return -1; + } + + if (reqfmt.fmt.pix.pixelformat != fmt.fmt.pix.pixelformat || + reqfmt.fmt.pix.width != fmt.fmt.pix.width || + reqfmt.fmt.pix.height != fmt.fmt.pix.height) + return -1; + + if (try_only) + return 0; + + state->fmt = fmt; + return 0; } static int spa_v4l2_close (SpaV4l2Source *this) { - if (close(this->state.fd)) - fprintf(stderr, "Cannot close %d, %s\n", - errno, strerror (errno)); + SpaV4l2State *state = &this->state; + + if (!state->opened) + return 0; + + if (close(state->fd)) + perror ("close"); + + state->fd = -1; + state->opened = false; + + return 0; +} + +static int +mmap_read (SpaV4l2Source *this) +{ + SpaV4l2State *state = &this->state; + struct v4l2_buffer buf; + V4l2Buffer *b; + + CLEAR(buf); + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + + if (xioctl (state->fd, VIDIOC_DQBUF, &buf) < 0) { + switch (errno) { + case EAGAIN: + return 0; + case EIO: + default: + perror ("VIDIOC_DQBUF"); + return -1; + } + } + fprintf (stderr, "captured buffer %d\n", buf.index); + + b = &state->buffers[buf.index]; + b->next = state->ready; + state->ready = b; + state->ready_count++; - this->state.fd = -1; return 0; } static void * v4l2_loop (void *user_data) { + SpaV4l2Source *this = user_data; + SpaV4l2State *state = &this->state; + struct pollfd fds[1]; + + fds[0].fd = state->fd; + fds[0].events = POLLIN | POLLPRI | POLLERR; + fds[0].revents = 0; + + while (state->running) { + int r; + + r = poll (fds, 1, 2000); + if (r < 0) { + if (errno == EINTR) + continue; + break; + } + if (r == 0) { + fprintf (stderr, "select timeout\n"); + break; + } + + if (mmap_read (this) < 0) + break; + + { + SpaEvent event; + + event.refcount = 1; + event.notify = NULL; + event.type = SPA_EVENT_TYPE_CAN_PULL_OUTPUT; + event.port_id = 0; + event.size = 0; + event.data = NULL; + + this->event_cb (&this->handle, &event, this->user_data); + } + } return NULL; } +static void +v4l2_buffer_free (void *data) +{ + V4l2Buffer *b = (V4l2Buffer *) data; + SpaV4l2Source *this = b->source; + SpaV4l2State *state = &this->state; + struct v4l2_buffer buf; + + CLEAR (buf); + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = b->index; + + b->buffer.refcount = 1; + b->outstanding = false; + + fprintf (stderr, "queue buffer %d\n", buf.index); + + if (xioctl (state->fd, VIDIOC_QBUF, &buf) < 0) { + perror ("VIDIOC_QBUF"); + } +} + +static int +mmap_init (SpaV4l2Source *this) +{ + SpaV4l2State *state = &this->state; + struct v4l2_requestbuffers reqbuf; + int i; + + CLEAR(reqbuf); + reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + reqbuf.memory = V4L2_MEMORY_MMAP; + reqbuf.count = MAX_BUFFERS; + + if (xioctl (state->fd, VIDIOC_REQBUFS, &reqbuf) < 0) { + perror ("VIDIOC_REQBUFS"); + return -1; + } + + fprintf (stderr, "got %d buffers\n", reqbuf.count); + if (reqbuf.count < 2) { + fprintf (stderr, "can't allocate enough buffers\n"); + return -1; + } + + state->reqbuf = reqbuf; + + for (i = 0; i < reqbuf.count; i++) { + struct v4l2_buffer buf; + V4l2Buffer *b; + + CLEAR (buf); + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = i; + + if (xioctl (state->fd, VIDIOC_QUERYBUF, &buf) < 0) { + perror ("VIDIOC_QUERYBUF"); + return -1; + } + + b = &state->buffers[i]; + b->index = i; + b->source = this; + b->buffer.refcount = 1; + b->buffer.notify = v4l2_buffer_free; + b->buffer.size = buf.length; + b->buffer.n_metas = 1; + b->buffer.metas = b->meta; + b->buffer.n_datas = 1; + b->buffer.datas = b->data; + + b->header.flags = 0; + b->header.seq = 0; + b->header.pts = 0; + b->header.dts_offset = 0; + + b->meta[0].type = SPA_META_TYPE_HEADER; + b->meta[0].data = &b->header; + b->meta[0].size = sizeof (b->header); + + b->data[0].type = SPA_DATA_TYPE_MEMPTR; + b->data[0].size = buf.length; + b->data[0].data = mmap (NULL, + buf.length, + PROT_READ | PROT_WRITE, + MAP_SHARED, + state->fd, + buf.m.offset); + + if (b->data[0].data == MAP_FAILED) { + perror ("mmap"); + return -1; + } + } + for (i = 0; i < state->reqbuf.count; ++i) { + struct v4l2_buffer buf; + + CLEAR (buf); + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = i; + + if (xioctl (state->fd, VIDIOC_QBUF, &buf) < 0) { + perror ("VIDIOC_QBUF"); + return -1; + } + } + return 0; +} + +static int +userptr_init (SpaV4l2Source *this) +{ + return -1; +} + +static int +read_init (SpaV4l2Source *this) +{ + return -1; +} static int spa_v4l2_start (SpaV4l2Source *this) { SpaV4l2State *state = &this->state; int err; + enum v4l2_buf_type type; + + if (spa_v4l2_open (this) < 0) + return -1; + + if (state->cap.capabilities & V4L2_CAP_STREAMING) { + if (mmap_init (this) < 0) + if (userptr_init (this) < 0) + return -1; + } else if (state->cap.capabilities & V4L2_CAP_READWRITE) { + if (read_init (this) < 0) + return -1; + } else + return -1; + + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (xioctl (state->fd, VIDIOC_STREAMON, &type) < 0) { + perror ("VIDIOC_STREAMON"); + return -1; + } state->running = true; if ((err = pthread_create (&state->thread, NULL, v4l2_loop, this)) != 0) { - printf ("can't create thread: %d", err); + printf ("can't create thread: %d %s", err, strerror (err)); state->running = false; } return err; @@ -73,10 +557,19 @@ static int spa_v4l2_stop (SpaV4l2Source *this) { SpaV4l2State *state = &this->state; + enum v4l2_buf_type type; if (state->running) { state->running = false; pthread_join (state->thread, NULL); } + + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (xioctl (state->fd, VIDIOC_STREAMOFF, &type) < 0) { + perror ("VIDIOC_STREAMOFF"); + return -1; + } + + spa_v4l2_close (this); return 0; } diff --git a/spa/plugins/volume/volume.c b/spa/plugins/volume/volume.c index ba8ffe9e1..09e0b7c7f 100644 --- a/spa/plugins/volume/volume.c +++ b/spa/plugins/volume/volume.c @@ -147,38 +147,36 @@ spa_volume_node_send_command (SpaHandle *handle, case SPA_COMMAND_INVALID: return SPA_RESULT_INVALID_COMMAND; - case SPA_COMMAND_ACTIVATE: - if (this->event_cb) { - SpaEvent event; - - event.refcount = 1; - event.notify = NULL; - event.type = SPA_EVENT_TYPE_ACTIVATED; - event.port_id = -1; - event.data = NULL; - event.size = 0; - - this->event_cb (handle, &event, this->user_data); - } - break; - - case SPA_COMMAND_DEACTIVATE: - if (this->event_cb) { - SpaEvent event; - - event.refcount = 1; - event.notify = NULL; - event.type = SPA_EVENT_TYPE_DEACTIVATED; - event.port_id = -1; - event.data = NULL; - event.size = 0; - - this->event_cb (handle, &event, this->user_data); - } - break; - case SPA_COMMAND_START: + if (this->event_cb) { + SpaEvent event; + + event.refcount = 1; + event.notify = NULL; + event.type = SPA_EVENT_TYPE_STARTED; + event.port_id = -1; + event.data = NULL; + event.size = 0; + + this->event_cb (handle, &event, this->user_data); + } + break; + case SPA_COMMAND_STOP: + if (this->event_cb) { + SpaEvent event; + + event.refcount = 1; + event.notify = NULL; + event.type = SPA_EVENT_TYPE_STOPPED; + event.port_id = -1; + event.data = NULL; + event.size = 0; + + this->event_cb (handle, &event, this->user_data); + } + break; + case SPA_COMMAND_FLUSH: case SPA_COMMAND_DRAIN: case SPA_COMMAND_MARKER: @@ -288,7 +286,7 @@ spa_volume_node_enum_port_formats (SpaHandle *handle, static SpaResult spa_volume_node_set_port_format (SpaHandle *handle, uint32_t port_id, - int test_only, + bool test_only, const SpaFormat *format) { SpaVolume *this = (SpaVolume *) handle; diff --git a/spa/tests/meson.build b/spa/tests/meson.build index 0cce8cc0b..05474d7ef 100644 --- a/spa/tests/meson.build +++ b/spa/tests/meson.build @@ -2,3 +2,8 @@ executable('test-mixer', 'test-mixer.c', include_directories : inc, dependencies : [dl_lib], install : false) + +executable('test-v4l2', 'test-v4l2.c', + include_directories : inc, + dependencies : [dl_lib], + install : false) diff --git a/spa/tests/test-mixer.c b/spa/tests/test-mixer.c index 2f94e3708..0b3a3aead 100644 --- a/spa/tests/test-mixer.c +++ b/spa/tests/test-mixer.c @@ -239,72 +239,34 @@ negotiate_formats (AppData *data) if ((res = props->set_prop (props, spa_props_index_for_id (props, SPA_PROP_ID_AUDIO_CHANNELS), &value)) < 0) return res; - if ((res = data->sink_node->set_port_format (data->sink, 0, 0, format)) < 0) + if ((res = data->sink_node->set_port_format (data->sink, 0, false, format)) < 0) return res; - if ((res = data->mix_node->set_port_format (data->mix, 0, 0, format)) < 0) + if ((res = data->mix_node->set_port_format (data->mix, 0, false, format)) < 0) return res; if ((res = data->mix_node->add_port (data->mix, SPA_DIRECTION_INPUT, &data->mix_ports[0])) < 0) return res; - if ((res = data->mix_node->set_port_format (data->mix, data->mix_ports[0], 0, format)) < 0) + if ((res = data->mix_node->set_port_format (data->mix, data->mix_ports[0], false, format)) < 0) return res; - if ((res = data->source1_node->set_port_format (data->source1, 0, 0, format)) < 0) + if ((res = data->source1_node->set_port_format (data->source1, 0, false, format)) < 0) return res; if ((res = data->mix_node->add_port (data->mix, SPA_DIRECTION_INPUT, &data->mix_ports[1])) < 0) return res; - if ((res = data->mix_node->set_port_format (data->mix, data->mix_ports[1], 0, format)) < 0) + if ((res = data->mix_node->set_port_format (data->mix, data->mix_ports[1], false, format)) < 0) return res; - if ((res = data->source2_node->set_port_format (data->source2, 0, 0, format)) < 0) + if ((res = data->source2_node->set_port_format (data->source2, 0, false, format)) < 0) return res; return SPA_RESULT_OK; } -static SpaResult -start_nodes (AppData *data) -{ - SpaResult res; - SpaCommand cmd; - - cmd.type = SPA_COMMAND_ACTIVATE; - if ((res = data->sink_node->send_command (data->sink, &cmd)) < 0) - return res; - if ((res = data->mix_node->send_command (data->mix, &cmd)) < 0) - return res; - if ((res = data->source1_node->send_command (data->source1, &cmd)) < 0) - return res; - if ((res = data->source2_node->send_command (data->source1, &cmd)) < 0) - return res; - - return res; -} - -static SpaResult -stop_nodes (AppData *data) -{ - SpaResult res; - SpaCommand cmd; - - cmd.type = SPA_COMMAND_DEACTIVATE; - if ((res = data->sink_node->send_command (data->sink, &cmd)) < 0) - return res; - if ((res = data->mix_node->send_command (data->mix, &cmd)) < 0) - return res; - if ((res = data->source1_node->send_command (data->source1, &cmd)) < 0) - return res; - if ((res = data->source2_node->send_command (data->source1, &cmd)) < 0) - return res; - - return res; -} - static void run_async_sink (AppData *data) { @@ -337,17 +299,7 @@ main (int argc, char *argv[]) printf ("can't negotiate nodes: %d\n", res); return -1; } - if ((res = start_nodes (&data)) < 0) { - printf ("can't start nodes: %d\n", res); - return -1; - } run_async_sink (&data); - if ((res = stop_nodes (&data)) < 0) { - printf ("can't stop nodes: %d\n", res); - return -1; - } - - return 0; } diff --git a/spa/tests/test-v4l2.c b/spa/tests/test-v4l2.c new file mode 100644 index 000000000..a6056bcfb --- /dev/null +++ b/spa/tests/test-v4l2.c @@ -0,0 +1,198 @@ +/* Spa + * Copyright (C) 2016 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include + +#include +#include + +typedef struct { + SpaHandle *source; + const SpaNode *source_node; +} AppData; + +static SpaResult +make_node (SpaHandle **handle, const SpaNode **node, const char *lib, const char *name) +{ + SpaResult res; + void *hnd; + SpaEnumHandleFactoryFunc enum_func; + unsigned int i; + + if ((hnd = dlopen (lib, RTLD_NOW)) == NULL) { + printf ("can't load %s: %s\n", lib, dlerror()); + return SPA_RESULT_ERROR; + } + if ((enum_func = dlsym (hnd, "spa_enum_handle_factory")) == NULL) { + printf ("can't find enum function\n"); + return SPA_RESULT_ERROR; + } + + for (i = 0; ;i++) { + const SpaHandleFactory *factory; + const void *iface; + + if ((res = enum_func (i, &factory)) < 0) { + if (res != SPA_RESULT_ENUM_END) + printf ("can't enumerate factories: %d\n", res); + break; + } + if (strcmp (factory->name, name)) + continue; + + if ((res = factory->instantiate (factory, handle)) < 0) { + printf ("can't make factory instance: %d\n", res); + return res; + } + if ((res = (*handle)->get_interface (*handle, SPA_INTERFACE_ID_NODE, &iface)) < 0) { + printf ("can't get interface %d\n", res); + return res; + } + *node = iface; + return SPA_RESULT_OK; + } + return SPA_RESULT_ERROR; +} + +static void +on_source_event (SpaHandle *handle, SpaEvent *event, void *user_data) +{ + AppData *data = user_data; + + switch (event->type) { + case SPA_EVENT_TYPE_CAN_PULL_OUTPUT: + { + SpaOutputInfo info[1] = { 0, }; + SpaResult res; + + if ((res = data->source_node->pull_port_output (data->source, 1, info)) < 0) + printf ("got pull error %d\n", res); + + if (info[0].buffer) { + spa_buffer_unref (info[0].buffer); + } + break; + } + default: + printf ("got event %d\n", event->type); + break; + } +} + +static SpaResult +make_nodes (AppData *data) +{ + SpaResult res; + SpaProps *props; + SpaPropValue value; + + if ((res = make_node (&data->source, &data->source_node, "plugins/v4l2/libspa-v4l2.so", "v4l2-source")) < 0) { + printf ("can't create v4l2-source: %d\n", res); + return res; + } + data->source_node->set_event_callback (data->source, on_source_event, data); + + if ((res = data->source_node->get_props (data->source, &props)) < 0) + printf ("got get_props error %d\n", res); + + value.type = SPA_PROP_TYPE_STRING; + value.value = "/dev/video1"; + value.size = strlen (value.value)+1; + props->set_prop (props, spa_props_index_for_name (props, "device"), &value); + + if ((res = data->source_node->set_props (data->source, props)) < 0) + printf ("got set_props error %d\n", res); + return res; +} + +static SpaResult +negotiate_formats (AppData *data) +{ + SpaResult res; + SpaFormat *format; + SpaProps *props; + uint32_t val; + SpaPropValue value; + + if ((res = data->source_node->enum_port_formats (data->source, 0, 0, &format)) < 0) + return res; + + props = &format->props; + + value.type = SPA_PROP_TYPE_UINT32; + value.size = sizeof (uint32_t); + value.value = &val; + + val = SPA_VIDEO_FORMAT_YUY2; + if ((res = props->set_prop (props, spa_props_index_for_id (props, SPA_PROP_ID_VIDEO_FORMAT), &value)) < 0) + return res; + val = 320; + if ((res = props->set_prop (props, spa_props_index_for_id (props, SPA_PROP_ID_VIDEO_WIDTH), &value)) < 0) + return res; + val = 240; + if ((res = props->set_prop (props, spa_props_index_for_id (props, SPA_PROP_ID_VIDEO_HEIGHT), &value)) < 0) + return res; + + if ((res = data->source_node->set_port_format (data->source, 0, false, format)) < 0) + return res; + + return SPA_RESULT_OK; +} + +static void +run_async_source (AppData *data) +{ + SpaResult res; + SpaCommand cmd; + + cmd.type = SPA_COMMAND_START; + if ((res = data->source_node->send_command (data->source, &cmd)) < 0) + printf ("got error %d\n", res); + + printf ("sleeping for 10 seconds\n"); + sleep (10); + + cmd.type = SPA_COMMAND_STOP; + if ((res = data->source_node->send_command (data->source, &cmd)) < 0) + printf ("got error %d\n", res); +} + +int +main (int argc, char *argv[]) +{ + AppData data; + SpaResult res; + + if ((res = make_nodes (&data)) < 0) { + printf ("can't make nodes: %d\n", res); + return -1; + } + if ((res = negotiate_formats (&data)) < 0) { + printf ("can't negotiate nodes: %d\n", res); + return -1; + } + + run_async_source (&data); + + return 0; +}