From 63f6f05d66537877365a1099ac176dc3afbb9f6b Mon Sep 17 00:00:00 2001 From: Edward Hervey Date: Fri, 12 Jun 2015 10:53:23 +0200 Subject: [PATCH] gst: New Stream listing/selection system * GstStream * GstStreamCollection * GST_EVENT_SELECT_STREAMS * GST_MESSAGE_STREAM_COLLECTION --- docs/design/part-stream-selection.txt | 611 ++++++++++++++++++++++++++ docs/gst/gstreamer-docs.sgml | 2 + docs/gst/gstreamer-sections.txt | 73 +++ gst/Makefile.am | 4 + gst/gst.c | 2 + gst/gst.h | 2 + gst/gstevent.c | 157 +++++++ gst/gstevent.h | 64 ++- gst/gstmessage.c | 198 +++++++++ gst/gstmessage.h | 18 + gst/gstquark.c | 4 +- gst/gstquark.h | 9 +- gst/gststreamcollection.c | 324 ++++++++++++++ gst/gststreamcollection.h | 106 +++++ gst/gststreams.c | 518 ++++++++++++++++++++++ gst/gststreams.h | 131 ++++++ gst/gstutils.c | 35 ++ gst/gstutils.h | 1 + tests/check/Makefile.am | 1 + tests/check/gst/.gitignore | 1 + tests/check/gst/gstevent.c | 56 +++ tests/check/gst/gstmessage.c | 84 ++++ tests/check/gst/gststream.c | 225 ++++++++++ tests/check/gst/gststream.h | 53 +++ win32/common/libgstreamer.def | 33 ++ 25 files changed, 2686 insertions(+), 26 deletions(-) create mode 100644 docs/design/part-stream-selection.txt create mode 100644 gst/gststreamcollection.c create mode 100644 gst/gststreamcollection.h create mode 100644 gst/gststreams.c create mode 100644 gst/gststreams.h create mode 100644 tests/check/gst/gststream.c create mode 100644 tests/check/gst/gststream.h diff --git a/docs/design/part-stream-selection.txt b/docs/design/part-stream-selection.txt new file mode 100644 index 0000000000..43f3b76232 --- /dev/null +++ b/docs/design/part-stream-selection.txt @@ -0,0 +1,611 @@ +Stream selection +---------------- + + History + v0.1: Jun 11th 2015 + Initial Draft + v0.2: Sep 18th 2015 + Update to reflect design changes + v1.0: Jun 28th 2016 + Pre-commit revision + + This document describes the events and objects involved in stream + selection in GStreamer pipelines, elements and applications + + +0. Background +---------------- + + This new API is intended to address the use cases described in + this section: + + 1) As a user/app I want an overview and control of the media streams + that can be configured within a pipeline for processing, even + when some streams are mutually exclusive or logical constructs only. + + 2) The user/app can disable entirely streams it's not interested + in so they don't occupy memory or processing power - discarded + as early as possible in the pipeline. The user/app can also + (re-)enable them at a later time. + + 3) If the set of possible stream configurations is changing, + the user/app should be aware of the pending change and + be able to make configuration choices for the new set of streams, + as well as possibly still reconfiguring the old set + + 4) Elements that have some other internal mechanism for triggering + stream selections (DVD, or maybe some scripted playback + playlist) should be able to trigger 'selection' of some particular + stream. + + 5) Indicate known relationships between streams - for example that + 2 separate video feeds represent the 2 views of a stereoscopic + view, or that certain streams are mutually exclusive. + +Note: the streams that are "available" are not automatically +the ones active, or present in the pipeline as pads. Think HLS/DASH +alternate streams. + +Use case examples: + + 1) Playing an MPEG-TS multi-program stream, we want to tell the + app that there are multiple programs that could be extracted + from the incoming feed. Further, we want to provide a mechanism + for the app to select which program(s) to decode, and once + that is known to further tell the app which elementary streams + are then available within those program(s) so the app/user can + choose which audio track(s) to decode and/or use. + + 2) A new PMT arrives for an MPEG-TS stream, due to a codec or + channel change. The pipeline will need to reconfigure to + play the desired streams from new program. Equally, there + may be multiple seconds of content buffered from the old + program and it should still be possible to switch (for example) + subtitle tracks responsively in the draining out data, as + well as selecting which subs track to play from the new feed. + + This same scenario applies when doing gapless transition to a + new source file/URL, except that likely the element providing + the list of streams also changes as a new demuxer is installed. + + 3) When playing a multi-angle DVD, the DVD Virtual Machine needs to + extract 1 angle from the data for presentation. It can publish + the available angles as logical streams, even though only one + stream can be chosen. + + 4) When playing a DVD, the user can make stream selections from the + DVD menu to choose audio or sub-picture tracks, or the DVD VM + can trigger automatic selections. In addition, the player UI + should be able to show which audio/subtitle tracks are available + and allow direct selection in a GUI the same as for normal + files with subtitle tracks in them. + + 5) Playing a SCHC (3DTV) feed, where one view is MPEG-2 and the other + is H.264 and they should be combined for 3D presentation, or + not bother decoding 1 stream if displaying 2D. + (bug https://bugzilla.gnome.org/show_bug.cgi?id=719333) + + *) FIXME - need some use cases indicating what alternate streams in + HLS might require - what are the possibilities? + + +1. Design Overview +----------- + + Stream selection in GStreamer is implemented in several parts: + 1) Objects describing streams : GstStream + 2) Objects describing a collection of streams : GstStreamCollection + 3) Events from the app allowing selection and activation of some streams: + GST_EVENT_SELECT_STREAMS + 4) Messages informing the user/application about the available + streams and current status: + GST_MESSAGE_STREAM_COLLECTION + GST_MESSAGE_STREAMS_SELECTED + + +2. GstStream objects +-------------------- + + API: GstStream + API: gst_stream_new(..) + API: gst_stream_get_*(...) + API: gst_stream_set_*() + API: gst_event_set_stream(...) + API: gst_event_parse_stream(...) + + GstStream objects are a high-level convenience object containing + information regarding a possible data stream that can be exposed by + GStreamer elements. + + They are mostly the aggregation of information present in other + GStreamer components (STREAM_START, CAPS, TAGS event) but are not + tied to the presence of a GstPad, and for some use-cases provide + information that the existing components don't provide. + + The various properties of a GstStream object are: + - stream_id (from the STREAM_START event) + - flags (from the STREAM_START event) + - caps + - tags + - type (high-level type of stream: Audio, Video, Container,...) + + GstStream objects can be subclassed so that they can be re-used by + elements already using the notion of stream (which is common for + example in demuxers). + + Elements that create GstStream should also set it on the + GST_EVENT_STREAM_START event of the relevent pad. This helps + downstream elements to have all information in one location. + + +3. Exposing collections of streams +---------------------------------- + + API: GstStreamCollection + API: gst_stream_collection_new(...) + API: gst_stream_collection_add_stream(...) + API: gst_stream_collection_get_size(...) + API: gst_stream_collection_get_stream(...) + API: GST_MESSAGE_STREAM_COLLECTION + API: gst_message_new_stream_collection(...) + API: gst_message_parse_stream_collection(...) + API: GST_EVENT_STREAM_COLLECTION + API: gst_event_new_stream_collection(...) + API: gst_event_parse_stream_collection(...) + + Elements that create new streams (such as demuxers) or can create + new streams (like the HLS/DASH alternative streams) can list the + streams they can make available with the GstStreamCollection object. + + Other elements that might generate GstStreamCollections are the + DVD-VM, which handles internal switching of tracks, or parsebin and + decodebin3 when it aggregates and presents multiple internal stream + sources as a single configurable collection. + + The GstStreamCollection object is a flat listing of GstStream objects. + + The various properties of a GstStreamCollection are: + - 'identifier' + - the identifier of the collection (unique name) + - Generated from the 'upstream stream id' (or stream ids, plural) + - the list of GstStreams in the collection. + - (Not implemented) : Flags - + For now, the only flag is 'INFORMATIONAL' - used by container parsers to + publish information about detected streams without allowing selection of + the streams. + - (Not implemented yet) : The relationship between the various streams + This specifies which streams are exclusive (can not be selected at the + same time), are related (such as LINKED_VIEW or ENHANCEMENT), or need to + be selected together. + + An element will inform outside components about that collection via: + * a GST_MESSAGE_STREAM_COLLECTION message on the bus. + * a GST_EVENT_STREAM_COLLECTION on each source pads. + + Applications and container bin elements can listen and collect the + various stream collections to know the full range of streams + available within a bin/pipeline. + + Once posted on the bus, a GstStreamCollection is immutable. It is + updated by subsquent messages with a matching identifier. + + If the element that provided the collection goes away, there is no way + to know that the streams are no longer valid (without having the + user/app track that element). The exception to that is if the bin + containing that element (such as parsebin or decodebin3) informs that + the next collection is a replacement of the former one. + + The mutual exclusion and relationship lists use stream-ids + rather than GstStream references in order to avoid circular + referencing problems. + + +3.1 Usage from elements +----------------------- + + When a demuxer knows the list of streams it can expose, it + creates a new GstStream for each stream it can provide with the + appropriate information (stream id, flag, tags, caps, ...). + + The demuxer then creates a GstStreamCollection object in which it + will put the list of GstStream it can expose. That collection is + then both posted on the bus (via a GST_MESSAGE_COLLECTION) and on + each pad (via a GST_EVENT_STREAM_COLLECTION). + + That new collection must be posted on the bus *before* the changes + are made available. i.e. before pads corresponding to that selection + are added/removed. + + In order to be backwards-compatible and support elements that don't + create streams/collection yet, the new 'parsebin' element used by + decodebin3 will automatically create those if not provided. + + +3.2 Usage from application +-------------------------- + + Applications can know what streams are available by listening to the + GST_MESSAGE_STREAM_COLLECTION messages posted on the bus. + + The application can list the available streams per-type (such as all + the audio streams, or all the video streams) by iterating the + streams available in the collection by GST_STREAM_TYPE. + + The application will also be able to use these stream information to + decide which streams should be activated or not (see the stream + selection event below). + + +3.3 Backwards compatibility +--------------------------- + + Not all demuxers will create the various GstStream and + GstStreamCollection objects. In order to remain backwards + compatible, a parent bin (parsebin in decodebin3) will create the + GstStream and GstStreamCollection based on the pads being + added/removed from an element. + + This allows providing stream listing/selection for any demuxer-like + element even if it doesn't implement the GstStreamCollection usage. + + +4. Stream selection event +------------------------- + + API: GST_EVENT_SELECT_STREAMS + API: gst_event_new_select_streams(...) + API: gst_event_parse_select_streams(...) + + Stream selection events are generated by the application and + sent into the pipeline to configure the streams. + + The event carries: + * List of GstStreams to activate - a subset of the GstStreamCollection + * (Not implemented) - List of GstStreams to be kept discarded - a + subset of streams for which hot-swapping will not be desired, + allowing elements (such as decodebin3, demuxers, ...) to not parse or + buffer those streams at all. + + +4.1. Usage from application +--------------------------- + + There are two use-cases where an application needs to specify in a + generic fashion which streams it wants in output: + 1) When there are several present streams of which it only wants a + subset (such as one audio, one video and one subtitle + stream). Those streams are demuxed and present in the pipeline. + 2) When the stream the user wants require some element to undertake + some action to expose that stream in the pipeline (such as + DASH/HLS alternative streams). + + From the point of view of the application, those two use-cases are + treated identically. The streams are all available through the + GstStreamCollection posted on the bus, and it will select a subset. + + The application can select the streams it wants by creating a + GST_EVENT_SELECT_STREAMS event with the list of stream-id of the + streams it wants. That event is then sent on the pipeline, + eventually travelling all the way upstream from each sink. + + In some cases, selecting one stream may trigger the availability of + other dependent streams, resulting in new GstStreamCollection + messages. This can happen in the case where chosing a different DVB + channel would create a new single-program collection. + + +4.2. Usage in elements +---------------------- + + Elements that receive the GST_EVENT_SELECT_STREAMS event and that + can activate/deactivate streams need to look at the list of + stream-id contained in the event and decide if they need to do some + action. + + In the standard demuxer case (demuxing and exposing all streams), + there is nothing to do by default. + + In decodebin3, activating or deactivating streams is taken care of by + linking only the streams present in the event to decoders and output + ghostpad. + + In the case of elements that can expose alternate streams that are + not present in the pipeline as pads, they will take the appropriate + action to add/remove those streams. + + Containers that receive the event should pass it to any elements + with no downstream peers, so that streams can be configured during + pre-roll before a pipeline is completely linked down to sinks. + + +5. decodebin3 usage and example +------------------------------- + + This is an example of how decodebin3 works by using the + above-mentioned objects/events/messages. + + For clarity/completeness, we will consider a mpeg-ts stream that has + multiple audio streams. Furthermore that stream might have changes + at some point (switching video codec, or adding/removing audio + streams). + + +5.1. Initial differences +------------------------ + + decodebin3 is different, compared to decodebin2, in the sense that, by + default: + * it will only expose as output ghost source pads one stream of each + type (one audio, one video, ..). + * It will only decode the exposed streams + + The multiqueue element is still used and takes in all elementary + (non-decoded) streams. If parsers are needed/present they are placed + before the multiqueue. This is needed in order for multiqueue to + work only with packetized and properly timestamped streams. + + Note that the whole typefinding of streams, and optional depayloading, + demuxing and parsing is done in a new 'parsebin' element. + + Just like the current implementation, demuxers will expose all + streams present within a program as source pads. They will connect + to parsers and multiqueue. + + Initial setup. 1 video stream, 2 audio streams. + + +---------------------+ + | parsebin | + | --------- | +-------------+ + | | demux |--[parser]-+-| multiqueue |--[videodec]---[ + ]-+-| |--[parser]-+-| | + | | |--[parser]-+-| |--[audiodec]---[ + | --------- | +-------------+ + +---------------------+ + + +5.2. GstStreamCollection +------------------------ + + When parsing the initial PAT/PMT, the demuxer will: + 1) create the various GstStream objects for each stream. + 2) create the GstStreamCollection for that initial PMT + 3) post the GST_MESSAGE_STREAM_COLLECTION + + Decodebin will intercept that message and know what the demuxer will + be exposing. + + 4) The demuxer creates the various pads and sends the corresponding + STREAM_START event (with the same stream-id as the corresponding + GstStream objects), CAPS event, and TAGS event. + + parsebin will add all relevant parsers and expose those streams. + + Decodebin will be able to correlate, based on STREAM_START event + stream-id, what pad corresponds to which stream. It links each stream + from parsebin to multiqueue. + + Decodebin knows all the streams that will be available. Since by + default it is configured to only expose a stream of each type, it + will pick a stream of each for which it will complete the + auto-plugging (finding a decoder and then exposing that stream as a + source ghostpad. + + Note: + If the demuxer doesn't create/post the GstStreamCollection, + parsebin will create it on itself, as explained in section 2.3 + above. + + +5.3. Changing the active selection from the application +------------------------------------------------------- + + The user wants to change the audio track. The application received + the GST_MESSAGE_STREAM_COLLECTION containing the list of available + streams. For clarity, we will assume those stream-ids are + "video-main", "audio-english" and "audio-french". + + The user prefers to use the french soundtrack (which it knows based + on the language tag contained in the GstStream objects). + + The application will create and send a GST_EVENT_SELECT_STREAM event + containing the list of streams: "video-main", "audio-french". + + That event gets sent on the pipeline, the sinks send it upstream and + eventually reach decodebin. + + Decodebin compares: + * The currently active selection ("video-main", "audio-english") + * The available stream collection ("video-main", "audio-english", + "audio-french") + * The list of streams in the event ("video-main", "audio-french") + + Decodebin determines that no change is required for "video-main", + but sees that it needs to deactivate "audio-english" and activate + "audio-french". + + It unlinks the multiqueue source pad connected to the audiodec. Then + it queries audiodec, using the GST_QUERY_ACCEPT_CAPS, whether it can + accept as-is the caps from the "audio-french" stream. + 1) If it does, the multiqueue source pad corresponding to + "audio-french" is linked to the decoder. + 2) If it does not, the existing audio decoder is removed, + a new decoder is selected (like during initial + auto-plugging), and replaces the old audio decoder element. + + The newly selected stream gets decoded and output through the same + pad as the previous audio stream. + + Note: + The default behaviour would be to only expose one stream of each + type. But nothing prevents decodebin from outputting more/less of + each type if the GST_EVENT_SELECT_STREAM event specifies that. This + allows covering more use-case than the simple playback one. + Such examples could be : + * Wanting just a video stream or just a audio stream + * Wanting all decoded streams + * Wanting all audio streams + ... + + +5.4. Changes coming from upstream +--------------------------------- + + At some point in time, a PMT change happens. Let's assume a change + in video-codec and/or PID. + + The demuxer creates a new GstStream for the changed/new stream, + creates a new GstStreamCollection for the updated PMT and posts it. + + Decodebin sees the new GstStreamCollection message. + + The demuxer (and parsebin) then adds and removes pads. + 1) decodebin will match the new pads to GstStream in the "new" + GstStreamCollection the same way it did for the initial pads in + section 4.2 above. + 2) decodebin will see whether the new stream can re-use a multiqueue + slot used by a stream of the same type no longer present (it + compares the old collection to the new collection). + In this case, decodebin sees that the new video stream can re-use + the same slot as the previous video stream. + 3) If the new stream is going to be active by default (in this case + it does because we are replacing the only video stream, which was + active), it will check whether the caps are compatible with the + existing videodec (in the same way it was done for the audio + decoder switch in section 4.3). + + Eventually, the stream that switched will be decoded and output + through the same pad as the previous video stream in a gapless fashion. + + +5.5. Further examples +--------------------- + +5.5.1. HLS alternates +--------------------- + + There is a main (multi-bitrate or not) stream with audio and + video interleaved in mpeg-ts. The manifest also indicates the + presence of alternate language audio-only streams. + HLS would expose one collection containing: + 1) The main A+V CONTAINER stream (mpeg-ts), initially active, + downloaded and exposed as a pad + 2) The alternate A-only streams, initially inactive and not + exposed as pads + the tsdemux element connected to the first stream will also + expose a collection containing + 1.1) A video stream + 1.2) An audio stream + + [ Collection 1 ] [ Collection 2 ] + [ (hlsdemux) ] [ (tsdemux) ] + [ upstream:nil ] /----[ upstream:main] + [ ] / [ ] + [ "main" (A+V) ]<-/ [ "video" (V) ] viddec1 : "video" + [ "fre" (A) ] [ "eng" (A) ] auddec1 : "eng" + [ "kor" (A) ] [ ] + + The user might want to use the korean audio track instead of the + default english one. + => SELECT_STREAMS ("video", "kor") + + + 1) decodebin3 receives and sends the event further upstream + 2) tsdemux sees that "video" is part of its current upstream, + so adds the corresponding stream-id ("main") to the event + and sends it upstream ("main", "video", "kor") + 3) hlsdemux receives the event + => It activates "kor" in addition to "main" + 4) The event travels back to decodebin3 which will remember the + requested selection. If "kor" is already present it will switch + the "eng" stream from the audio decoder to the "kor" stream. + If it appears a bit later, it will wait until that "kor" stream + is available before switching + + +5.5.2 multi-program MPEG-TS +--------------------------- + + Assuming the case of a mpeg-ts stream which contains multiple + programs. + There would be three "levels" of collection: + 1) The collection of programs present in the stream + 2) The collection of elementary streams present in a stream + 3) The collection of streams decodebin can expose + + Initially tsdemux exposes the first program present (default) + + [ Collection 1 ] [ Collection 2 ] [ Collection 3 ] + [ (tsdemux) ] [ (tsdemux) ] [ (decodebin) ] + [ id:Programs ]<-\ [ id:BBC1 ]<-\ [ id:BBC1-decoded ] + [ upstream:nil ] \-----[ upstream:Programs] \----[ upstream:BBC1 ] + [ ] [ ] [ ] + [ "BBC1" (C) ] [ id:"bbcvideo"(V) ] [ id:"bbcvideo"(V)] + [ "ITV" (C) ] [ id:"bbcaudio"(A) ] [ id:"bbcvideo"(A)] + [ "NBC" (C) ] [ ] [ ] + + At some point the user wants to switch to ITV (of which we do not + know the topology at this point in time. A SELECT_STREAMS event + is sent with "ITV" in it and the pointer to the Collection1. + 1) The event travels up the pipeline until tsdemux receives it + and begins the switch. + 2) tsdemux publishes a new 'Collection 2a/ITV' and marks 'Collection 2/BBC' + as replaced. + 2a) App may send a SELECT_STREAMS event configuring which demuxer output + streams should selected (parsed) + 3) tsdemux adds/removes pads as needed (flushing pads as it removes them?) + 4) Decodebin feeds new pad streams through existing parsers/decoders as + needed. As data from the new collection arrives out each decoder, + decodebin sends new GstStreamCollection messages to the app so it + can know that the new streams are now switchable at that level. + 4a) As new GstStreamCollections are published, the app may override + the default decodebin stream selection to expose more/fewer streams. + The default is to decode and output 1 stream of each type. + + Final state: + + [ Collection 1 ] [ Collection 4 ] [ Collection 5 ] + [ (tsdemux) ] [ (tsdemux) ] [ (decodebin) ] + [ id:Programs ]<-\ [ id:ITV ]<-\ [ id:ITV-decoded ] + [ upstream:nil ] \-----[ upstream:Programs] \----[ upstream:ITV ] + [ ] [ ] [ ] + [ "BBC1" (C) ] [ id:"itvvideo"(V) ] [ id:"itvvideo"(V)] + [ "ITV" (C) ] [ id:"itvaudio"(A) ] [ id:"itvvideo"(A)] + [ "NBC" (C) ] [ ] [ ] + +6.0 TODO +-------- + +* Add missing implementation + - Add flags to GstStreamCollection + - Add mutual-exclusion and relationship API to GstStreamCollection + +* Add helper API to figure out whether a collection is a replacement of another + or a completely new one. This will require a more generic system to know whether + a certain stream-id is a replacement of another or not. + + +7.0 OPEN QUESTIONS +------------------ + +* Is a FLUSHING flag for stream-selection required or not ? + This would make the handler of the SELECT_STREAMS event send FLUSH START/STOP + before switching to the other streams. + This is tricky when dealing where situations where we keep some streams and + only switch some others. Do we flush all streams ? Do we only flush the new + streams, potentially resulting in delay to fully switch ? + Furthermore, due to efficient buffering in decodebin3, the switching time has + been minimized extensively, to the point where flushing might not bring a + noticeable improvement. + +* Store the stream collection in bins/pipelines ? + A Bin/Pipeline could store all active collection internally, so that it + could be queried later on. This could be useful to then get, on any pipeline, + at any point in time, the full list of collections available without having + to listen to all COLLECTION messages on the bus. + This would require fixing the "is a collection a replacement or not" issue first. + +* When switching to new collections, should decodebin3 make any effort + to 'map' corresponding streams from the old to new PMT - that is, + try and stick to the 'english' language audio track, for example? + Alternatively, rely on the app to do such smarts with stream-select + messages ? diff --git a/docs/gst/gstreamer-docs.sgml b/docs/gst/gstreamer-docs.sgml index dfa293b2b2..3d1f6dc850 100644 --- a/docs/gst/gstreamer-docs.sgml +++ b/docs/gst/gstreamer-docs.sgml @@ -99,6 +99,8 @@ Windows. It is released under the GNU Library General Public License + + diff --git a/docs/gst/gstreamer-sections.txt b/docs/gst/gstreamer-sections.txt index 191a0d25de..23521ed486 100644 --- a/docs/gst/gstreamer-sections.txt +++ b/docs/gst/gstreamer-sections.txt @@ -1123,6 +1123,9 @@ gst_event_parse_stream_flags gst_event_set_group_id gst_event_parse_group_id +gst_event_set_stream +gst_event_parse_stream + gst_event_new_segment gst_event_parse_segment gst_event_copy_segment @@ -1169,6 +1172,12 @@ gst_event_parse_segment_done gst_event_new_protection gst_event_parse_protection + +gst_event_new_select_streams +gst_event_parse_select_streams + +gst_event_new_stream_collection +gst_event_parse_stream_collection GstEventClass GST_EVENT @@ -1621,6 +1630,8 @@ gst_message_new_stream_start gst_message_set_group_id gst_message_parse_group_id +gst_message_new_stream_collection +gst_message_parse_stream_collection GstStructureChangeType gst_message_new_structure_change gst_message_parse_structure_change @@ -1649,6 +1660,13 @@ gst_message_parse_device_removed gst_message_new_property_notify gst_message_parse_property_notify + +gst_message_new_streams_selected +gst_message_parse_streams_selected +gst_message_streams_selected_add +gst_message_streams_selected_get_size +gst_message_streams_selected_get_stream + GstMessageClass GST_MESSAGE @@ -1977,6 +1995,7 @@ gst_pad_create_stream_id_printf gst_pad_create_stream_id_printf_valist gst_pad_get_stream_id +gst_pad_get_stream GstPadForwardFunction gst_pad_forward @@ -2616,6 +2635,60 @@ gst_segment_flags_get_type +
+gststreams +GstStream +GstStream +GstStreamClass +GstStreamType +gst_stream_new +gst_stream_get_caps +gst_stream_get_stream_flags +gst_stream_get_stream_id +gst_stream_get_stream_type +gst_stream_get_tags +gst_stream_set_caps +gst_stream_set_stream_flags +gst_stream_set_stream_type +gst_stream_set_tags +gst_stream_type_get_name + +GST_IS_STREAM +GST_IS_STREAM_CLASS +GST_STREAM +GST_STREAM_CAST +GST_STREAM_CLASS +GST_STREAM_GET_CLASS +GST_TYPE_STREAM +GST_TYPE_STREAM_TYPE +gst_stream_get_type +gst_stream_type_get_type + +GstStreamPrivate +
+ +
+gststreamcollection +GstStreamCollection +GstStreamCollection +GstStreamCollectionClass +gst_stream_collection_new +gst_stream_collection_add_stream +gst_stream_collection_get_upstream_id +gst_stream_collection_get_size +gst_stream_collection_get_stream + +gst_stream_collection_get_type +GST_IS_STREAM_COLLECTION +GST_IS_STREAM_COLLECTION_CLASS +GST_STREAM_COLLECTION +GST_STREAM_COLLECTION_CAST +GST_STREAM_COLLECTION_CLASS +GST_STREAM_COLLECTION_GET_CLASS +GST_TYPE_STREAM_COLLECTION + +GstStreamCollectionPrivate +
gststructure diff --git a/gst/Makefile.am b/gst/Makefile.am index 7b113e948f..655211a99a 100644 --- a/gst/Makefile.am +++ b/gst/Makefile.am @@ -106,6 +106,8 @@ libgstreamer_@GST_API_VERSION@_la_SOURCES = \ gstregistrychunks.c \ gstsample.c \ gstsegment.c \ + gststreamcollection.c \ + gststreams.c \ gststructure.c \ gstsystemclock.c \ gsttaglist.c \ @@ -214,6 +216,8 @@ gst_headers = \ gstquery.h \ gstsample.h \ gstsegment.h \ + gststreamcollection.h \ + gststreams.h \ gststructure.h \ gstsystemclock.h \ gsttaglist.h \ diff --git a/gst/gst.c b/gst/gst.c index 33a5a62b5a..cbdb4bb6bf 100644 --- a/gst/gst.c +++ b/gst/gst.c @@ -684,6 +684,7 @@ init_post (GOptionContext * context, GOptionGroup * group, gpointer data, g_type_class_ref (gst_lock_flags_get_type ()); g_type_class_ref (gst_allocator_flags_get_type ()); g_type_class_ref (gst_stream_flags_get_type ()); + g_type_class_ref (gst_stream_type_get_type ()); _priv_gst_event_initialize (); _priv_gst_buffer_initialize (); @@ -1123,6 +1124,7 @@ gst_deinit (void) g_type_class_unref (g_type_class_peek (gst_pad_probe_return_get_type ())); g_type_class_unref (g_type_class_peek (gst_segment_flags_get_type ())); g_type_class_unref (g_type_class_peek (gst_scheduling_flags_get_type ())); + g_type_class_unref (g_type_class_peek (gst_stream_type_get_type ())); g_type_class_unref (g_type_class_peek (gst_control_binding_get_type ())); g_type_class_unref (g_type_class_peek (gst_control_source_get_type ())); diff --git a/gst/gst.h b/gst/gst.h index 5a5cde6432..0b07f41aeb 100644 --- a/gst/gst.h +++ b/gst/gst.h @@ -58,6 +58,7 @@ #include #include #include +#include #include #include #include @@ -69,6 +70,7 @@ #include #include #include +#include #include #include #include diff --git a/gst/gstevent.c b/gst/gstevent.c index e0e0ea6f6a..9059fe51ec 100644 --- a/gst/gstevent.c +++ b/gst/gstevent.c @@ -104,7 +104,9 @@ static GstEventQuarks event_quarks[] = { {GST_EVENT_UNKNOWN, "unknown", 0}, {GST_EVENT_FLUSH_START, "flush-start", 0}, {GST_EVENT_FLUSH_STOP, "flush-stop", 0}, + {GST_EVENT_SELECT_STREAMS, "select-streams", 0}, {GST_EVENT_STREAM_START, "stream-start", 0}, + {GST_EVENT_STREAM_COLLECTION, "stream-collection", 0}, {GST_EVENT_CAPS, "caps", 0}, {GST_EVENT_SEGMENT, "segment", 0}, {GST_EVENT_TAG, "tag", 0}, @@ -575,6 +577,77 @@ gst_event_parse_flush_stop (GstEvent * event, gboolean * reset_time) GST_QUARK (RESET_TIME))); } +/** + * gst_event_new_select_streams: + * @streams: (element-type gchar) (transfer none): the list of streams to + * activate + * + * Allocate a new select-streams event. + * + * The select-streams event requests the specified @streams to be activated. + * + * The list of @streams corresponds to the "Stream ID" of each stream to be + * activated. Those ID can be obtained via the #GstStream objects present + * in #GST_EVENT_STREAM_START, #GST_EVENT_STREAM_COLLECTION or + * #GST_MESSSAGE_STREAM_COLLECTION. + * + * Returns: (transfer full): a new select-streams event. + */ +GstEvent * +gst_event_new_select_streams (GList * streams) +{ + GstEvent *event; + GValue val = G_VALUE_INIT; + GstStructure *struc; + GList *tmpl; + + GST_CAT_INFO (GST_CAT_EVENT, "Creating new select-streams event"); + struc = gst_structure_new_id_empty (GST_QUARK (EVENT_SELECT_STREAMS)); + g_value_init (&val, GST_TYPE_LIST); + /* Fill struc with streams */ + for (tmpl = streams; tmpl; tmpl = tmpl->next) { + GValue strval = G_VALUE_INIT; + const gchar *str = (const gchar *) tmpl->data; + g_value_init (&strval, G_TYPE_STRING); + g_value_set_string (&strval, str); + gst_value_list_append_and_take_value (&val, &strval); + } + gst_structure_id_take_value (struc, GST_QUARK (STREAMS), &val); + event = gst_event_new_custom (GST_EVENT_SELECT_STREAMS, struc); + + return event; +} + +/** + * gst_event_parse_select_streams: + * @event: The event to parse + * @streams: (out) (element-type gchar) (transfer full): the streams + * + * Parse the SELECT_STREAMS event and retrieve the contained streams. + */ +void +gst_event_parse_select_streams (GstEvent * event, GList ** streams) +{ + GstStructure *structure; + GList *res = NULL; + + g_return_if_fail (GST_IS_EVENT (event)); + g_return_if_fail (GST_EVENT_TYPE (event) == GST_EVENT_SELECT_STREAMS); + + structure = GST_EVENT_STRUCTURE (event); + if (G_LIKELY (streams)) { + const GValue *vlist = + gst_structure_id_get_value (structure, GST_QUARK (STREAMS)); + guint i, sz = gst_value_list_get_size (vlist); + for (i = 0; i < sz; i++) { + const GValue *strv = gst_value_list_get_value (vlist, i); + res = g_list_append (res, g_value_dup_string (strv)); + } + *streams = res; + } +} + + /** * gst_event_new_eos: * @@ -1507,6 +1580,44 @@ gst_event_parse_stream_start (GstEvent * event, const gchar ** stream_id) *stream_id = g_value_get_string (val); } +/** + * gst_event_set_stream: + * @event: a stream-start event + * @stream: (transfer none): the stream object to set + * + * Set the @stream on the stream-start @event + **/ +void +gst_event_set_stream (GstEvent * event, GstStream * stream) +{ + g_return_if_fail (event != NULL); + g_return_if_fail (GST_EVENT_TYPE (event) == GST_EVENT_STREAM_START); + g_return_if_fail (gst_event_is_writable (event)); + + gst_structure_id_set (GST_EVENT_STRUCTURE (event), + GST_QUARK (STREAM), GST_TYPE_STREAM, stream, NULL); +} + +/** + * gst_event_parse_stream: + * @event: a stream-start event + * @stream: (out) (transfer full): adress of variable to store the stream + * + * Parse a stream-start @event and extract the #GstStream from it. + **/ +void +gst_event_parse_stream (GstEvent * event, GstStream ** stream) +{ + g_return_if_fail (event != NULL); + g_return_if_fail (GST_EVENT_TYPE (event) == GST_EVENT_STREAM_START); + + if (stream) { + gst_structure_id_get (GST_EVENT_STRUCTURE (event), + GST_QUARK (STREAM), GST_TYPE_STREAM, stream, NULL); + } + +} + /** * gst_event_set_stream_flags: * @event: a stream-start event @@ -1595,6 +1706,52 @@ gst_event_parse_group_id (GstEvent * event, guint * group_id) return TRUE; } +/** + * gst_event_new_stream_collection: + * @collection: Active collection for this data flow + * + * Create a new STREAM_COLLECTION event. The stream collection event can only + * travel downstream synchronized with the buffer flow. + * + * Source elements, demuxers and other elements that manage collections + * of streams and post #GstStreamCollection messages on the bus also send + * this event downstream on each pad involved in the collection, so that + * activation of a new collection can be tracked through the downstream + * data flow. + * + * Returns: (transfer full): the new STREAM_COLLECTION event. + */ +GstEvent * +gst_event_new_stream_collection (GstStreamCollection * collection) +{ + GstStructure *s; + + g_return_val_if_fail (collection != NULL, NULL); + g_return_val_if_fail (GST_IS_STREAM_COLLECTION (collection), NULL); + + s = gst_structure_new_id (GST_QUARK (EVENT_STREAM_COLLECTION), + GST_QUARK (COLLECTION), GST_TYPE_STREAM_COLLECTION, collection, NULL); + + return gst_event_new_custom (GST_EVENT_STREAM_COLLECTION, s); +} + +void +gst_event_parse_stream_collection (GstEvent * event, + GstStreamCollection ** collection) +{ + const GstStructure *structure; + + g_return_if_fail (event != NULL); + g_return_if_fail (GST_EVENT_TYPE (event) == GST_EVENT_STREAM_COLLECTION); + + structure = gst_event_get_structure (event); + + if (collection) { + gst_structure_id_get (structure, + GST_QUARK (COLLECTION), GST_TYPE_STREAM_COLLECTION, collection, NULL); + } +} + /** * gst_event_new_toc: * @toc: (transfer none): #GstToc structure. diff --git a/gst/gstevent.h b/gst/gstevent.h index e20c82066f..82f2f29d01 100644 --- a/gst/gstevent.h +++ b/gst/gstevent.h @@ -79,6 +79,7 @@ typedef enum { * from the pipeline and unblock all streaming threads. * @GST_EVENT_FLUSH_STOP: Stop a flush operation. This event resets the * running-time of the pipeline. + * @GST_EVENT_SELECT_STREAMS: A request to select one or more streams. * @GST_EVENT_STREAM_START: Event to mark the start of a new stream. Sent before any * other serialized event and only sent at the start of a new stream, * not after flushing seeks. @@ -87,6 +88,7 @@ typedef enum { * segment events contains information for clipping buffers and * converting buffer timestamps to running-time and * stream-time. + * @GST_EVENT_STREAM_COLLECTION: A new #GstStreamCollection is available. * @GST_EVENT_TAG: A new set of metadata tags has been found in the stream. * @GST_EVENT_BUFFERSIZE: Notification of buffering requirements. Currently not * used yet. @@ -94,7 +96,8 @@ typedef enum { * send messages that should be emitted in sync with * rendering. * @GST_EVENT_EOS: End-Of-Stream. No more data is to be expected to follow - * without a SEGMENT event. + * without either a STREAM_START event, or a FLUSH_STOP and a SEGMENT + * event. * @GST_EVENT_SEGMENT_DONE: Marks the end of a segment playback. * @GST_EVENT_GAP: Marks a gap in the datastream. * @GST_EVENT_TOC: An event which indicates that a new table of contents (TOC) @@ -144,6 +147,7 @@ typedef enum { GST_EVENT_STREAM_START = GST_EVENT_MAKE_TYPE (40, FLAG(DOWNSTREAM) | FLAG(SERIALIZED) | FLAG(STICKY)), GST_EVENT_CAPS = GST_EVENT_MAKE_TYPE (50, FLAG(DOWNSTREAM) | FLAG(SERIALIZED) | FLAG(STICKY)), GST_EVENT_SEGMENT = GST_EVENT_MAKE_TYPE (70, FLAG(DOWNSTREAM) | FLAG(SERIALIZED) | FLAG(STICKY)), + GST_EVENT_STREAM_COLLECTION = GST_EVENT_MAKE_TYPE (75, FLAG(DOWNSTREAM) | FLAG(SERIALIZED) | FLAG(STICKY) | FLAG(STICKY_MULTI)), GST_EVENT_TAG = GST_EVENT_MAKE_TYPE (80, FLAG(DOWNSTREAM) | FLAG(SERIALIZED) | FLAG(STICKY) | FLAG(STICKY_MULTI)), GST_EVENT_BUFFERSIZE = GST_EVENT_MAKE_TYPE (90, FLAG(DOWNSTREAM) | FLAG(SERIALIZED) | FLAG(STICKY)), GST_EVENT_SINK_MESSAGE = GST_EVENT_MAKE_TYPE (100, FLAG(DOWNSTREAM) | FLAG(SERIALIZED) | FLAG(STICKY) | FLAG(STICKY_MULTI)), @@ -163,6 +167,7 @@ typedef enum { GST_EVENT_STEP = GST_EVENT_MAKE_TYPE (230, FLAG(UPSTREAM)), GST_EVENT_RECONFIGURE = GST_EVENT_MAKE_TYPE (240, FLAG(UPSTREAM)), GST_EVENT_TOC_SELECT = GST_EVENT_MAKE_TYPE (250, FLAG(UPSTREAM)), + GST_EVENT_SELECT_STREAMS = GST_EVENT_MAKE_TYPE (260, FLAG(UPSTREAM)), /* custom events start here */ GST_EVENT_CUSTOM_UPSTREAM = GST_EVENT_MAKE_TYPE (270, FLAG(UPSTREAM)), @@ -174,6 +179,30 @@ typedef enum { } GstEventType; #undef FLAG +/** + * GstStreamFlags: + * @GST_STREAM_FLAG_NONE: This stream has no special attributes + * @GST_STREAM_FLAG_SPARSE: This stream is a sparse stream (e.g. a subtitle + * stream), data may flow only in irregular intervals with large gaps in + * between. + * @GST_STREAM_FLAG_SELECT: This stream should be selected by default. This + * flag may be used by demuxers to signal that a stream should be selected + * by default in a playback scenario. + * @GST_STREAM_FLAG_UNSELECT: This stream should not be selected by default. + * This flag may be used by demuxers to signal that a stream should not + * be selected by default in a playback scenario, but only if explicitly + * selected by the user (e.g. an audio track for the hard of hearing or + * a director's commentary track). + * + * Since: 1.2 + */ +typedef enum { + GST_STREAM_FLAG_NONE, + GST_STREAM_FLAG_SPARSE = (1 << 0), + GST_STREAM_FLAG_SELECT = (1 << 1), + GST_STREAM_FLAG_UNSELECT = (1 << 2) +} GstStreamFlags; + #include #include #include @@ -355,29 +384,6 @@ typedef enum { GST_QOS_TYPE_THROTTLE = 2 } GstQOSType; -/** - * GstStreamFlags: - * @GST_STREAM_FLAG_NONE: This stream has no special attributes - * @GST_STREAM_FLAG_SPARSE: This stream is a sparse stream (e.g. a subtitle - * stream), data may flow only in irregular intervals with large gaps in - * between. - * @GST_STREAM_FLAG_SELECT: This stream should be selected by default. This - * flag may be used by demuxers to signal that a stream should be selected - * by default in a playback scenario. - * @GST_STREAM_FLAG_UNSELECT: This stream should not be selected by default. - * This flag may be used by demuxers to signal that a stream should not - * be selected by default in a playback scenario, but only if explicitly - * selected by the user (e.g. an audio track for the hard of hearing or - * a director's commentary track). - * - * Since: 1.2 - */ -typedef enum { - GST_STREAM_FLAG_NONE, - GST_STREAM_FLAG_SPARSE = (1 << 0), - GST_STREAM_FLAG_SELECT = (1 << 1), - GST_STREAM_FLAG_UNSELECT = (1 << 2) -} GstStreamFlags; /** * GstEvent: @@ -467,6 +473,8 @@ void gst_event_set_running_time_offset (GstEvent *event, gint64 offse /* Stream start event */ GstEvent * gst_event_new_stream_start (const gchar *stream_id) G_GNUC_MALLOC; void gst_event_parse_stream_start (GstEvent *event, const gchar **stream_id); +void gst_event_set_stream (GstEvent *event, GstStream *stream); +void gst_event_parse_stream (GstEvent *event, GstStream **stream); void gst_event_set_stream_flags (GstEvent *event, GstStreamFlags flags); void gst_event_parse_stream_flags (GstEvent *event, GstStreamFlags *flags); @@ -480,6 +488,14 @@ GstEvent * gst_event_new_flush_start (void) G_GNUC_MALLOC; GstEvent * gst_event_new_flush_stop (gboolean reset_time) G_GNUC_MALLOC; void gst_event_parse_flush_stop (GstEvent *event, gboolean *reset_time); +/* Stream collection event */ +GstEvent * gst_event_new_stream_collection (GstStreamCollection *collection) G_GNUC_MALLOC; +void gst_event_parse_stream_collection (GstEvent *event, GstStreamCollection **collection); + +/* select streams event */ +GstEvent * gst_event_new_select_streams (GList *streams); +void gst_event_parse_select_streams (GstEvent *event, GList **streams); + /* EOS event */ GstEvent * gst_event_new_eos (void) G_GNUC_MALLOC; diff --git a/gst/gstmessage.c b/gst/gstmessage.c index 673df03066..e485135824 100644 --- a/gst/gstmessage.c +++ b/gst/gstmessage.c @@ -52,6 +52,7 @@ #include "gsttaglist.h" #include "gstutils.h" #include "gstquark.h" +#include "gstvalue.h" typedef struct @@ -106,6 +107,8 @@ static GstMessageQuarks message_quarks[] = { {GST_MESSAGE_DEVICE_ADDED, "device-added", 0}, {GST_MESSAGE_DEVICE_REMOVED, "device-removed", 0}, {GST_MESSAGE_PROPERTY_NOTIFY, "property-notify", 0}, + {GST_MESSAGE_STREAM_COLLECTION, "stream-collection", 0}, + {GST_MESSAGE_STREAMS_SELECTED, "streams-selected", 0}, {0, NULL, 0} }; @@ -2522,3 +2525,198 @@ gst_message_parse_property_notify (GstMessage * message, GstObject ** object, *property_value = gst_structure_id_get_value (s, GST_QUARK (PROPERTY_VALUE)); } + +/** + * gst_message_new_stream_collection: + * @src: The #GstObject that created the message + * @collection: (transfer none): The #GstStreamCollection + * + * Creates a new stream-collection message. The message is used to announce new + * #GstStreamCollection + * + * Returns: a newly allocated #GstMessage + * + * Since: 1.x + */ +GstMessage * +gst_message_new_stream_collection (GstObject * src, + GstStreamCollection * collection) +{ + GstMessage *message; + GstStructure *structure; + + g_return_val_if_fail (collection != NULL, NULL); + g_return_val_if_fail (GST_IS_STREAM_COLLECTION (collection), NULL); + + structure = + gst_structure_new_id (GST_QUARK (MESSAGE_STREAM_COLLECTION), + GST_QUARK (COLLECTION), GST_TYPE_STREAM_COLLECTION, collection, NULL); + message = + gst_message_new_custom (GST_MESSAGE_STREAM_COLLECTION, src, structure); + + return message; +} + +/** + * gst_message_parse_stream_collection: + * @message: a #GstMessage of type %GST_MESSAGE_STREAM_COLLECTION + * @collection: (out) (allow-none) (transfer none): A location where to store a + * pointer to the #GstStreamCollection, or %NULL + * + * Parses a stream-collection message. + * + * Since: 1.x + */ +void +gst_message_parse_stream_collection (GstMessage * message, + GstStreamCollection ** collection) +{ + g_return_if_fail (GST_IS_MESSAGE (message)); + g_return_if_fail (GST_MESSAGE_TYPE (message) == + GST_MESSAGE_STREAM_COLLECTION); + + if (collection) + gst_structure_id_get (GST_MESSAGE_STRUCTURE (message), + GST_QUARK (COLLECTION), GST_TYPE_STREAM_COLLECTION, collection, NULL); +} + +/** + * gst_message_new_streams_selected: + * @src: The #GstObject that created the message + * @collection: (transfer none): The #GstStreamCollection + * + * Creates a new steams-selected message. The message is used to announce + * that an array of streams has been selected. This is generally in response + * to a #GST_EVENT_SELECT_STREAMS event, or when an element (such as decodebin3) + * makes an initial selection of streams. + * + * The message also contains the #GstStreamCollection to which the various streams + * belong to. + * + * Users of gst_message_new_streams_selected() can add the selected streams with + * gst_message_streams_selected_add(). + * + * Returns: a newly allocated #GstMessage + * + * Since: 1.x + */ +GstMessage * +gst_message_new_streams_selected (GstObject * src, + GstStreamCollection * collection) +{ + GstMessage *message; + GstStructure *structure; + GValue val = G_VALUE_INIT; + + g_return_val_if_fail (collection != NULL, NULL); + g_return_val_if_fail (GST_IS_STREAM_COLLECTION (collection), NULL); + + structure = + gst_structure_new_id (GST_QUARK (MESSAGE_STREAMS_SELECTED), + GST_QUARK (COLLECTION), GST_TYPE_STREAM_COLLECTION, collection, NULL); + g_value_init (&val, GST_TYPE_ARRAY); + gst_structure_id_take_value (structure, GST_QUARK (STREAMS), &val); + message = + gst_message_new_custom (GST_MESSAGE_STREAMS_SELECTED, src, structure); + + return message; +} + +/** + * gst_message_streams_selected_get_size: + * @message: a #GstMessage of type %GST_MESSAGE_STREAMS_SELECTED + * + * Returns the number of streams contained in the @message. + * + * Returns: The number of streams contained within. + */ +guint +gst_message_streams_selected_get_size (GstMessage * msg) +{ + const GValue *val; + + g_return_val_if_fail (GST_IS_MESSAGE (msg), 0); + g_return_val_if_fail (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_STREAMS_SELECTED, + 0); + + val = + gst_structure_id_get_value (GST_MESSAGE_STRUCTURE (msg), + GST_QUARK (STREAMS)); + return gst_value_array_get_size (val); +} + +/** + * gst_message_streams_selected_add: + * @message: a #GstMessage of type %GST_MESSAGE_STREAMS_SELECTED + * @stream: (transfer none): a #GstStream to add to @message + * + * Adds the @stream to the @message. + */ +void +gst_message_streams_selected_add (GstMessage * msg, GstStream * stream) +{ + GValue *val; + GValue to_add = G_VALUE_INIT; + + g_return_if_fail (GST_IS_MESSAGE (msg)); + g_return_if_fail (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_STREAMS_SELECTED); + g_return_if_fail (GST_IS_STREAM (stream)); + + val = + (GValue *) gst_structure_id_get_value (GST_MESSAGE_STRUCTURE (msg), + GST_QUARK (STREAMS)); + g_value_init (&to_add, GST_TYPE_STREAM); + g_value_set_object (&to_add, stream); + gst_value_array_append_and_take_value (val, &to_add); +} + +/** + * gst_message_streams_selected_get_stream: + * @message: a #GstMessage of type %GST_MESSAGE_STREAMS_SELECTED + * @idx: Index of the stream to retrieve + * + * Retrieves the #GstStream with index @index from the @message. + * + * Returns: (transfer full): A #GstStream + */ +GstStream * +gst_message_streams_selected_get_stream (GstMessage * msg, guint idx) +{ + const GValue *streams, *val; + + g_return_val_if_fail (GST_IS_MESSAGE (msg), NULL); + g_return_val_if_fail (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_STREAMS_SELECTED, + NULL); + + streams = + gst_structure_id_get_value (GST_MESSAGE_STRUCTURE (msg), + GST_QUARK (STREAMS)); + val = gst_value_array_get_value (streams, idx); + if (val) { + return (GstStream *) g_value_dup_object (val); + } + + return NULL; +} + +/** + * gst_message_parse_streams_selected: + * @message: a #GstMessage of type %GST_MESSAGE_STREAMS_SELECTED + * @collection: (out) (allow-none) (transfer none): A location where to store a + * pointer to the #GstStreamCollection, or %NULL + * + * Parses a streams-selected message. + * + * Since: 1.x + */ +void +gst_message_parse_streams_selected (GstMessage * message, + GstStreamCollection ** collection) +{ + g_return_if_fail (GST_IS_MESSAGE (message)); + g_return_if_fail (GST_MESSAGE_TYPE (message) == GST_MESSAGE_STREAMS_SELECTED); + + if (collection) + gst_structure_id_get (GST_MESSAGE_STRUCTURE (message), + GST_QUARK (COLLECTION), GST_TYPE_STREAM_COLLECTION, collection, NULL); +} diff --git a/gst/gstmessage.h b/gst/gstmessage.h index 68449d051c..f91001d886 100644 --- a/gst/gstmessage.h +++ b/gst/gstmessage.h @@ -110,6 +110,10 @@ typedef struct _GstMessage GstMessage; * from a #GstDeviceProvider (Since 1.4) * @GST_MESSAGE_PROPERTY_NOTIFY: Message indicating a #GObject property has * changed (Since 1.10) + * @GST_MESSAGE_STREAM_COLLECTION: Message indicating a new #GstStreamCollection + * is available. + * @GST_MESSAGE_STREAMS_SELECTED: Message indicating the active selection of + * #GstStreams has changed. * @GST_MESSAGE_ANY: mask for all of the above messages. * * The different message types that are available. @@ -159,6 +163,8 @@ typedef enum GST_MESSAGE_DEVICE_ADDED = GST_MESSAGE_EXTENDED + 1, GST_MESSAGE_DEVICE_REMOVED = GST_MESSAGE_EXTENDED + 2, GST_MESSAGE_PROPERTY_NOTIFY = GST_MESSAGE_EXTENDED + 3, + GST_MESSAGE_STREAM_COLLECTION = GST_MESSAGE_EXTENDED + 4, + GST_MESSAGE_STREAMS_SELECTED = GST_MESSAGE_EXTENDED + 5, GST_MESSAGE_ANY = (gint) (0xffffffff) } GstMessageType; @@ -170,6 +176,7 @@ typedef enum #include #include #include +#include GST_EXPORT GType _gst_message_type; @@ -599,6 +606,17 @@ void gst_message_parse_device_removed (GstMessage * message, GstDevi GstMessage * gst_message_new_property_notify (GstObject * src, const gchar * property_name, GValue * val) G_GNUC_MALLOC; void gst_message_parse_property_notify (GstMessage * message, GstObject ** object, const gchar ** property_name, const GValue ** property_value); +/* STREAM_COLLECTION */ +GstMessage * gst_message_new_stream_collection (GstObject * src, GstStreamCollection * collection) G_GNUC_MALLOC; +void gst_message_parse_stream_collection (GstMessage *message, GstStreamCollection **collection); + +/* STREAMS_SELECTED */ +GstMessage * gst_message_new_streams_selected (GstObject *src, GstStreamCollection *collection); +void gst_message_streams_selected_add (GstMessage *message, GstStream *stream); +void gst_message_parse_streams_selected (GstMessage * message, GstStreamCollection **collection); +guint gst_message_streams_selected_get_size (GstMessage * message); +GstStream *gst_message_streams_selected_get_stream (GstMessage *message, guint idx); + #ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstMessage, gst_message_unref) #endif diff --git a/gst/gstquark.c b/gst/gstquark.c index bfa793367e..88693454d2 100644 --- a/gst/gstquark.c +++ b/gst/gstquark.c @@ -71,7 +71,9 @@ static const gchar *_quark_strings[] = { "GstMessageStreamStart", "group-id", "uri-redirection", "GstMessageDeviceAdded", "GstMessageDeviceRemoved", "device", "uri-redirection-permanent", "GstMessagePropertyNotify", "property-name", - "property-value" + "property-value", "streams", "GstEventSelectStreams", + "GstMessageStreamCollection", "collection", "stream", "stream-collection", + "GstMessageStreamsSelected" }; GQuark _priv_gst_quark_table[GST_QUARK_MAX]; diff --git a/gst/gstquark.h b/gst/gstquark.h index 5365a7e21e..dd0cde43de 100644 --- a/gst/gstquark.h +++ b/gst/gstquark.h @@ -205,7 +205,14 @@ typedef enum _GstQuarkId GST_QUARK_MESSAGE_PROPERTY_NOTIFY = 174, GST_QUARK_PROPERTY_NAME = 175, GST_QUARK_PROPERTY_VALUE = 176, - GST_QUARK_MAX = 177 + GST_QUARK_STREAMS = 177, + GST_QUARK_EVENT_SELECT_STREAMS = 178, + GST_QUARK_MESSAGE_STREAM_COLLECTION = 179, + GST_QUARK_COLLECTION = 180, + GST_QUARK_STREAM = 181, + GST_QUARK_EVENT_STREAM_COLLECTION = 182, + GST_QUARK_MESSAGE_STREAMS_SELECTED = 183, + GST_QUARK_MAX = 184 } GstQuarkId; extern GQuark _priv_gst_quark_table[GST_QUARK_MAX]; diff --git a/gst/gststreamcollection.c b/gst/gststreamcollection.c new file mode 100644 index 0000000000..f466851c78 --- /dev/null +++ b/gst/gststreamcollection.c @@ -0,0 +1,324 @@ +/* GStreamer + * + * Copyright (C) 2015 Centricular Ltd + * @author: Edward Hervey + * @author: Jan Schmidt + * + * gststreams.c: GstStreamCollection object and methods + * + * 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. + * + * MT safe. + */ + +/** + * SECTION:gststreamcollection + * @short_description: Base class for collection of streams + * + */ + +#include "gst_private.h" + +#include "gstenumtypes.h" +#include "gstevent.h" +#include "gststreamcollection.h" + +GST_DEBUG_CATEGORY_STATIC (stream_collection_debug); +#define GST_CAT_DEFAULT stream_collection_debug + +#define GST_STREAM_COLLECTION_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_STREAM_COLLECTION, GstStreamCollectionPrivate)) + +struct _GstStreamCollectionPrivate +{ + /* Maybe switch this to a GArray if performance is + * ever an issue? */ + GQueue *streams; +}; + +/* stream signals and properties */ +enum +{ + SIG_STREAM_NOTIFY, + LAST_SIGNAL +}; + +enum +{ + PROP_0, + PROP_UPSTREAM_ID, + PROP_LAST +}; + +static guint gst_stream_collection_signals[LAST_SIGNAL] = { 0 }; + +static void gst_stream_collection_dispose (GObject * object); + +static void gst_stream_collection_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_stream_collection_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + +static void +proxy_stream_notify_cb (GstStream * stream, GParamSpec * pspec, + GstStreamCollection * collection); + +#define _do_init \ +{ \ + GST_DEBUG_CATEGORY_INIT (stream_collection_debug, "streamcollection", GST_DEBUG_BOLD, \ + "debugging info for the stream collection objects"); \ + \ +} + +#define gst_stream_collection_parent_class parent_class +G_DEFINE_TYPE_WITH_CODE (GstStreamCollection, gst_stream_collection, + GST_TYPE_OBJECT, _do_init); + +static void +gst_stream_collection_class_init (GstStreamCollectionClass * klass) +{ + GObjectClass *gobject_class; + + gobject_class = (GObjectClass *) klass; + + g_type_class_add_private (klass, sizeof (GstStreamCollectionPrivate)); + + gobject_class->set_property = gst_stream_collection_set_property; + gobject_class->get_property = gst_stream_collection_get_property; + + /** + * GstStream:upstream-id: + * + * stream-id + */ + g_object_class_install_property (gobject_class, PROP_UPSTREAM_ID, + g_param_spec_string ("upstream-id", "Upstream ID", + "The stream ID of the parent stream", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + + /** + * GstStream::stream-notify: + * @collection: a #GstStreamCollection + * @prop_stream: the #GstStream that originated the signal + * @prop: the property that changed + * + * The stream notify signal is used to be notified of property changes to + * streams within the collection. + */ + gst_stream_collection_signals[SIG_STREAM_NOTIFY] = + g_signal_new ("stream-notify", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE | G_SIGNAL_DETAILED | + G_SIGNAL_NO_HOOKS, G_STRUCT_OFFSET (GstStreamCollectionClass, + stream_notify), NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, + 2, GST_TYPE_STREAM, G_TYPE_PARAM); + + gobject_class->dispose = gst_stream_collection_dispose; +} + +static void +gst_stream_collection_init (GstStreamCollection * collection) +{ + collection->priv = GST_STREAM_COLLECTION_GET_PRIVATE (collection); + collection->priv->streams = g_queue_new (); +} + +static void +release_gst_stream (GstStream * stream, GstStreamCollection * collection) +{ + g_signal_handlers_disconnect_by_func (stream, + proxy_stream_notify_cb, collection); + gst_object_unref (stream); +} + +static void +gst_stream_collection_dispose (GObject * object) +{ + GstStreamCollection *collection = GST_STREAM_COLLECTION_CAST (object); + + if (collection->upstream_id) { + g_free (collection->upstream_id); + collection->upstream_id = NULL; + } + + if (collection->priv->streams) { + g_queue_foreach (collection->priv->streams, + (GFunc) release_gst_stream, collection); + g_queue_free (collection->priv->streams); + collection->priv->streams = NULL; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +/** + * gst_stream_collection_new: + * @upstream_id: (allow-none): The stream id of the parent stream + * + * Create a new #GstStreamCollection. + * + * Returns: The new #GstStreamCollection. + */ +GstStreamCollection * +gst_stream_collection_new (const gchar * upstream_id) +{ + return g_object_new (GST_TYPE_STREAM_COLLECTION, "upstream-id", upstream_id, + NULL); +} + +static void +gst_stream_collection_set_upstream_id (GstStreamCollection * collection, + const gchar * upstream_id) +{ + g_return_if_fail (collection->upstream_id == NULL); + + /* Upstream ID should only be set once on construction, but let's + * not leak in case someone does something silly */ + if (collection->upstream_id) + g_free (collection->upstream_id); + + if (upstream_id) + collection->upstream_id = g_strdup (upstream_id); +} + +/** + * gst_stream_collection_get_upstream_id: + * @collection: a #GstStreamCollection + * + * Returns the upstream id of the @collection. + * + * Returns: (transfer none): The upstream id + */ +const gchar * +gst_stream_collection_get_upstream_id (GstStreamCollection * collection) +{ + const gchar *res; + + res = collection->upstream_id; + + return res; +} + +static void +gst_stream_collection_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstStreamCollection *collection; + + collection = GST_STREAM_COLLECTION_CAST (object); + + switch (prop_id) { + case PROP_UPSTREAM_ID: + gst_stream_collection_set_upstream_id (collection, + g_value_get_string (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_stream_collection_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstStreamCollection *collection; + + collection = GST_STREAM_COLLECTION_CAST (object); + + switch (prop_id) { + case PROP_UPSTREAM_ID: + g_value_set_string (value, + gst_stream_collection_get_upstream_id (collection)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +proxy_stream_notify_cb (GstStream * stream, GParamSpec * pspec, + GstStreamCollection * collection) +{ + GST_DEBUG_OBJECT (collection, "Stream %" GST_PTR_FORMAT " updated %s", + stream, pspec->name); + g_signal_emit (collection, gst_stream_collection_signals[SIG_STREAM_NOTIFY], + g_quark_from_string (pspec->name), stream, pspec); +} + +/** + * gst_stream_collection_add_stream: + * @collection: a #GstStreamCollection + * @stream: (transfer full): the #GstStream to add + * + * Add the given @stream to the @collection. + * + * Returns: %TRUE if the @stream was properly added, else %FALSE + */ +gboolean +gst_stream_collection_add_stream (GstStreamCollection * collection, + GstStream * stream) +{ + g_return_val_if_fail (GST_IS_STREAM_COLLECTION (collection), FALSE); + g_return_val_if_fail (GST_IS_STREAM (stream), FALSE); + g_return_val_if_fail (collection->priv->streams, FALSE); + + GST_DEBUG_OBJECT (collection, "Adding stream %" GST_PTR_FORMAT, stream); + + g_queue_push_tail (collection->priv->streams, stream); + g_signal_connect (stream, "notify", (GCallback) proxy_stream_notify_cb, + collection); + + return TRUE; +} + +/** + * gst_stream_collection_get_size: + * @collection: a #GstStreamCollection + * + * Get the number of streams this collection contains + * + * Returns: The number of streams that @collection contains + */ +guint +gst_stream_collection_get_size (GstStreamCollection * collection) +{ + g_return_val_if_fail (GST_IS_STREAM_COLLECTION (collection), 0); + g_return_val_if_fail (collection->priv->streams, 0); + + return g_queue_get_length (collection->priv->streams); +} + +/** + * gst_stream_collection_get_stream: + * @collection: a #GstStreamCollection + * @index: Index of the stream to retrieve + * + * Retrieve the #GstStream with index @index from the collection. + * + * The caller should not modify the returned #GstStream + * + * Returns: (transfer none): A #GstStream + */ +GstStream * +gst_stream_collection_get_stream (GstStreamCollection * collection, guint index) +{ + g_return_val_if_fail (GST_IS_STREAM_COLLECTION (collection), NULL); + g_return_val_if_fail (collection->priv->streams, NULL); + + return g_queue_peek_nth (collection->priv->streams, index); +} diff --git a/gst/gststreamcollection.h b/gst/gststreamcollection.h new file mode 100644 index 0000000000..7ee2ae2450 --- /dev/null +++ b/gst/gststreamcollection.h @@ -0,0 +1,106 @@ +/* GStreamer + * Copyright (C) 2015 Centricular Ltd + * @author: Edward Hervey + * @author: Jan Schmidt + * + * gststreams.h : Header for GstStreamCollection subsystem + * + * 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. + */ + + +#ifndef __GST_STREAM_COLLECTION_H__ +#define __GST_STREAM_COLLECTION_H__ + +#include +#include + +G_BEGIN_DECLS + +#define GST_TYPE_STREAM_COLLECTION (gst_stream_collection_get_type ()) +#define GST_IS_STREAM_COLLECTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_STREAM_COLLECTION)) +#define GST_IS_STREAM_COLLECTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_STREAM_COLLECTION)) +#define GST_STREAM_COLLECTION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_STREAM_COLLECTION, GstStreamCollectionClass)) +#define GST_STREAM_COLLECTION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_STREAM_COLLECTION, GstStreamCollection)) +#define GST_STREAM_COLLECTION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_STREAM_COLLECTION, GstStreamCollectionClass)) +#define GST_STREAM_COLLECTION_CAST(obj) ((GstStreamCollection*)(obj)) + +typedef struct _GstStreamCollection GstStreamCollection; +typedef struct _GstStreamCollectionClass GstStreamCollectionClass; +typedef struct _GstStreamCollectionPrivate GstStreamCollectionPrivate; +/** + * GstStreamCollection: + * + * A collection of #GstStream that are available. + * + * A #GstStreamCollection will be provided by elements that can make those + * streams available. Applications can use the collection to show the user + * what streams are available by using %gst_stream_collection_get_stream() + * + * Once posted, a #GstStreamCollection is immutable. Updates are made by sending + * a new #GstStreamCollection message, which may or may not share some of + * the #GstStream objects from the collection it replaces. The receiver can check + * the sender of a stream collection message to know which collection is + * obsoleted. + * + * Several elements in a pipeline can provide #GstStreamCollection. + * + * Applications can activate streams from a collection by using the + * #GST_EVENT_SELECT_STREAMS event on a pipeline, bin or element. + * + */ +struct _GstStreamCollection { + GstObject object; + + /*< private >*/ + gchar *upstream_id; + GstStreamCollectionPrivate *priv; + + gpointer _gst_reserved[GST_PADDING]; +}; + +/** + * GstStreamCollectionClass: + * @parent_class: the parent class structure + * @stream_notify: default signal handler for the stream-notify signal + * + * GstStreamCollection class structure + */ +struct _GstStreamCollectionClass { + GstObjectClass parent_class; + + /* signals */ + void (*stream_notify) (GstStreamCollection *collection, GstStream *stream, GParamSpec * pspec); + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING]; +}; + +GType gst_stream_collection_get_type (void); + +GstStreamCollection *gst_stream_collection_new (const gchar *upstream_id); + +const gchar *gst_stream_collection_get_upstream_id (GstStreamCollection *collection); + +guint gst_stream_collection_get_size (GstStreamCollection *collection); +GstStream *gst_stream_collection_get_stream (GstStreamCollection *collection, guint index); + +gboolean gst_stream_collection_add_stream (GstStreamCollection *collection, + GstStream *stream); + +G_END_DECLS + +#endif /* __GST_STREAM_COLLECTION_H__ */ diff --git a/gst/gststreams.c b/gst/gststreams.c new file mode 100644 index 0000000000..b3f4864d32 --- /dev/null +++ b/gst/gststreams.c @@ -0,0 +1,518 @@ +/* GStreamer + * + * Copyright (C) 2015 Centricular Ltd + * @author: Edward Hervey + * @author: Jan Schmidt + * + * gststreams.c: GstStream and GstStreamCollection object and methods + * + * 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. + * + * MT safe. + */ + +/** + * SECTION:gststreams + * @short_description: Base class for stream objects + * + * A #GstStream is a high-level object defining a stream of data which is, or + * can be, present in a #GstPipeline. + * + * It is defined by a unique identifier, a "Stream ID". A #GstStream does not + * automatically imply the stream is present within a pipeline or element. + * + * Any element that can introduce new streams in a pipeline should create the + * appropriate #GstStream object, and can convey that object via the + * %GST_EVENT_STREAM_START event and/or the #GstStreamCollection. + * + * Elements that do not modify the nature of the stream can add extra information + * on it (such as enrich the #GstCaps, or #GstTagList). This is typically done + * by parsing elements. + */ + +#include "gst_private.h" + +#include "gstenumtypes.h" +#include "gstevent.h" +#include "gststreams.h" + +GST_DEBUG_CATEGORY_STATIC (streams_debug); +#define GST_CAT_DEFAULT streams_debug + +#define GST_STREAM_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_STREAM, GstStreamPrivate)) + +struct _GstStreamPrivate +{ + GstStreamFlags flags; + GstStreamType type; + GstTagList *tags; + GstCaps *caps; +}; + +/* stream signals and properties */ +enum +{ + LAST_SIGNAL +}; + +enum +{ + PROP_0, + PROP_STREAM_ID, + PROP_STREAM_FLAGS, + PROP_STREAM_TYPE, + PROP_TAGS, + PROP_CAPS, + PROP_LAST +}; + +static GParamSpec *gst_stream_pspecs[PROP_LAST] = { 0 }; + +#if 0 +static guint gst_stream_signals[LAST_SIGNAL] = { 0 }; +#endif + +static void gst_stream_finalize (GObject * object); + +static void gst_stream_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_stream_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + +#define _do_init \ +{ \ + GST_DEBUG_CATEGORY_INIT (streams_debug, "streams", GST_DEBUG_BOLD, \ + "debugging info for the stream and stream collection objects"); \ + \ +} + +#define gst_stream_parent_class parent_class +G_DEFINE_TYPE_WITH_CODE (GstStream, gst_stream, GST_TYPE_OBJECT, _do_init); + +static void +gst_stream_class_init (GstStreamClass * klass) +{ + GObjectClass *gobject_class; + + gobject_class = (GObjectClass *) klass; + + g_type_class_add_private (klass, sizeof (GstStreamPrivate)); + + gobject_class->set_property = gst_stream_set_property; + gobject_class->get_property = gst_stream_get_property; + + /** + * GstStream:stream-id: + * + * The unique identifier of the #GstStream. Can only be set at construction + * time. + */ + g_object_class_install_property (gobject_class, PROP_STREAM_ID, + g_param_spec_string ("stream-id", "Stream ID", + "The stream ID of the stream", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + /** + * GstStream:flags: + * + * The #GstStreamFlags of the #GstStream. Can only be set at construction time. + **/ + gst_stream_pspecs[PROP_STREAM_FLAGS] = + g_param_spec_flags ("stream-flags", "Stream Flags", "The stream flags", + GST_TYPE_STREAM_FLAGS, GST_STREAM_FLAG_NONE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (gobject_class, PROP_STREAM_FLAGS, + gst_stream_pspecs[PROP_STREAM_FLAGS]); + + /** + * GstStream:stream-type: + * + * The #GstStreamType of the #GstStream. Can only be set at construction time. + **/ + gst_stream_pspecs[PROP_STREAM_TYPE] = + g_param_spec_flags ("stream-type", "Stream Type", "The type of stream", + GST_TYPE_STREAM_TYPE, GST_STREAM_TYPE_UNKNOWN, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (gobject_class, PROP_STREAM_TYPE, + gst_stream_pspecs[PROP_STREAM_TYPE]); + + /** + * GstStream:caps: + * + * The #GstCaps of the #GstStream. + **/ + gst_stream_pspecs[PROP_CAPS] = + g_param_spec_boxed ("caps", "Caps", "The caps of the stream", + GST_TYPE_CAPS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (gobject_class, PROP_CAPS, + gst_stream_pspecs[PROP_CAPS]); + + /** + * GstStream:tags: + * + * The #GstTagList of the #GstStream. + **/ + gst_stream_pspecs[PROP_TAGS] = + g_param_spec_boxed ("tags", "Tags", "The tags of the stream", + GST_TYPE_TAG_LIST, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (gobject_class, PROP_TAGS, + gst_stream_pspecs[PROP_TAGS]); + + gobject_class->finalize = gst_stream_finalize; +} + +static void +gst_stream_init (GstStream * stream) +{ + stream->priv = GST_STREAM_GET_PRIVATE (stream); + stream->priv->type = GST_STREAM_TYPE_UNKNOWN; +} + +static void +gst_stream_finalize (GObject * object) +{ + GstStream *stream = GST_STREAM_CAST (object); + + gst_mini_object_replace ((GstMiniObject **) & stream->priv->tags, + (GstMiniObject *) NULL); + gst_caps_replace (&stream->priv->caps, NULL); + g_free ((gchar *) stream->stream_id); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +/** + * gst_stream_new: + * @stream_id: (allow-none): the id for the new stream. If %NULL, + * a new one will be automatically generated + * @caps: (allow-none) (transfer none): the #GstCaps of the stream + * @type: the #GstStreamType of the stream + * @flags: the #GstStreamFlags of the stream + * + * Create a new #GstStream for the given @stream_id, @caps, @type + * and @flags + * + * Returns: The new #GstStream + */ +GstStream * +gst_stream_new (const gchar * stream_id, GstCaps * caps, GstStreamType type, + GstStreamFlags flags) +{ + return g_object_new (GST_TYPE_STREAM, "stream-id", stream_id, "caps", caps, + "stream-type", type, "stream-flags", flags, NULL); +} + +static void +gst_stream_set_stream_id (GstStream * stream, const gchar * stream_id) +{ + GST_OBJECT_LOCK (stream); + g_assert (stream->stream_id == NULL); + if (stream_id) + stream->stream_id = g_strdup (stream_id); + else { + /* Create a randoom stream_id if NULL */ + GST_FIXME_OBJECT (stream, "Creating random stream-id, consider " + "implementing a deterministic way of creating a stream-id"); + stream->stream_id = + g_strdup_printf ("%08x%08x%08x%08x", g_random_int (), g_random_int (), + g_random_int (), g_random_int ()); + } + + GST_OBJECT_UNLOCK (stream); +} + +/** + * gst_stream_get_stream_id: + * @stream: a #GstStream + * + * Returns the stream ID of @stream. + * + * Returns: (transfer none) (nullable): the stream ID of @stream. Only valid + * during the lifetime of @stream. + */ +const gchar * +gst_stream_get_stream_id (GstStream * stream) +{ + return stream->stream_id; +} + +/** + * gst_stream_set_stream_flags: + * @stream: a #GstStream + * @flags: the flags to set on @stream + * + * Set the @flags for the @stream. + */ +void +gst_stream_set_stream_flags (GstStream * stream, GstStreamFlags flags) +{ + GST_OBJECT_LOCK (stream); + stream->priv->flags = flags; + GST_OBJECT_UNLOCK (stream); + + g_object_notify_by_pspec (G_OBJECT (stream), + gst_stream_pspecs[PROP_STREAM_FLAGS]); +} + +/** + * gst_stream_get_stream_flags: + * @stream: a #GstStream + * + * Retrieve the current stream flags for @stream + * + * Returns: The #GstStreamFlags for @stream + * + */ +GstStreamFlags +gst_stream_get_stream_flags (GstStream * stream) +{ + GstStreamFlags res; + + GST_OBJECT_LOCK (stream); + res = stream->priv->flags; + GST_OBJECT_UNLOCK (stream); + + return res; +} + +/** + * gst_stream_set_stream_type: + * @stream: a #GstStream + * @stream_type: the type to set on @stream + * + * Set the stream type of @stream + */ +void +gst_stream_set_stream_type (GstStream * stream, GstStreamType stream_type) +{ + GST_OBJECT_LOCK (stream); + stream->priv->type = stream_type; + GST_OBJECT_UNLOCK (stream); + + g_object_notify_by_pspec (G_OBJECT (stream), + gst_stream_pspecs[PROP_STREAM_TYPE]); +} + +/** + * gst_stream_get_stream_type: + * @stream: a #GstStream + * + * Retrieve the stream type for @stream + * + * Returns: The #GstStreamType for @stream + * + */ +GstStreamType +gst_stream_get_stream_type (GstStream * stream) +{ + GstStreamType res; + + GST_OBJECT_LOCK (stream); + res = stream->priv->type; + GST_OBJECT_UNLOCK (stream); + + return res; +} + +/** + * gst_stream_set_tags: + * @stream: a #GstStream + * @tags: (transfer none) (allow-none): a #GstTagList + * + * Set the tags for the #GstStream + * + */ +void +gst_stream_set_tags (GstStream * stream, GstTagList * tags) +{ + GST_OBJECT_LOCK (stream); + gst_mini_object_replace ((GstMiniObject **) & stream->priv->tags, + (GstMiniObject *) tags); + GST_OBJECT_UNLOCK (stream); + g_object_notify_by_pspec (G_OBJECT (stream), gst_stream_pspecs[PROP_TAGS]); +} + +/** + * gst_stream_get_tags: + * @stream: a #GstStream + * + * Retrieve the tags for @stream, if any + * + * Returns: (transfer full) (nullable): The #GstTagList for @stream + * + */ +GstTagList * +gst_stream_get_tags (GstStream * stream) +{ + GstTagList *res = NULL; + + GST_OBJECT_LOCK (stream); + if (stream->priv->tags) + res = gst_tag_list_ref (stream->priv->tags); + GST_OBJECT_UNLOCK (stream); + + return res; +} + +/** + * gst_stream_set_caps: + * @stream: a #GstStream + * @caps: (transfer none) (allow-none): a #GstCaps + * + * Set the caps for the #GstStream + * + */ +void +gst_stream_set_caps (GstStream * stream, GstCaps * caps) +{ + gboolean notify = FALSE; + + GST_OBJECT_LOCK (stream); + if (stream->priv->caps == NULL || (caps + && !gst_caps_is_equal (stream->priv->caps, caps))) { + gst_caps_replace (&stream->priv->caps, caps); + notify = TRUE; + } + GST_OBJECT_UNLOCK (stream); + + if (notify) + g_object_notify_by_pspec (G_OBJECT (stream), gst_stream_pspecs[PROP_CAPS]); +} + + +/** + * gst_stream_get_caps: + * @stream: a #GstStream + * + * Retrieve the caps for @stream, if any + * + * Returns: (transfer full) (nullable): The #GstCaps for @stream + * + */ +GstCaps * +gst_stream_get_caps (GstStream * stream) +{ + GstCaps *res = NULL; + + GST_OBJECT_LOCK (stream); + if (stream->priv->caps) + res = gst_caps_ref (stream->priv->caps); + GST_OBJECT_UNLOCK (stream); + + return res; +} + +static void +gst_stream_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstStream *stream; + + stream = GST_STREAM_CAST (object); + + switch (prop_id) { + case PROP_STREAM_ID: + gst_stream_set_stream_id (stream, g_value_get_string (value)); + break; + case PROP_STREAM_FLAGS: + GST_OBJECT_LOCK (stream); + stream->priv->flags = g_value_get_flags (value); + GST_OBJECT_UNLOCK (stream); + break; + case PROP_STREAM_TYPE: + GST_OBJECT_LOCK (stream); + stream->priv->type = g_value_get_flags (value); + GST_OBJECT_UNLOCK (stream); + break; + case PROP_TAGS: + GST_OBJECT_LOCK (stream); + gst_mini_object_replace ((GstMiniObject **) & stream->priv->tags, + (GstMiniObject *) g_value_get_boxed (value)); + GST_OBJECT_UNLOCK (stream); + break; + case PROP_CAPS: + GST_OBJECT_LOCK (stream); + gst_mini_object_replace ((GstMiniObject **) & stream->priv->caps, + (GstMiniObject *) g_value_get_boxed (value)); + GST_OBJECT_UNLOCK (stream); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_stream_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstStream *stream; + + stream = GST_STREAM_CAST (object); + + switch (prop_id) { + case PROP_STREAM_ID: + g_value_set_string (value, gst_stream_get_stream_id (stream)); + break; + case PROP_STREAM_FLAGS: + g_value_set_flags (value, gst_stream_get_stream_flags (stream)); + break; + case PROP_STREAM_TYPE: + g_value_set_flags (value, gst_stream_get_stream_type (stream)); + break; + case PROP_TAGS: + g_value_take_boxed (value, gst_stream_get_tags (stream)); + break; + case PROP_CAPS: + g_value_take_boxed (value, gst_stream_get_caps (stream)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +/** + * gst_stream_type_get_name: + * @stype: a #GstStreamType + * + * Get a descriptive string for a given #GstStreamType + * + * Returns: A string describing the stream type + */ +const gchar * +gst_stream_type_get_name (GstStreamType stype) +{ + /* FIXME : Make this more flexible */ + switch (stype) { + case GST_STREAM_TYPE_UNKNOWN: + return "unknown"; + case GST_STREAM_TYPE_AUDIO: + return "audio"; + case GST_STREAM_TYPE_VIDEO: + return "video"; + case GST_STREAM_TYPE_CONTAINER: + return "container"; + case GST_STREAM_TYPE_TEXT: + return "text"; + default: + return NULL; + } + + return NULL; +} diff --git a/gst/gststreams.h b/gst/gststreams.h new file mode 100644 index 0000000000..a82d81f575 --- /dev/null +++ b/gst/gststreams.h @@ -0,0 +1,131 @@ +/* GStreamer + * Copyright (C) 2015 Centricular Ltd + * @author: Edward Hervey + * @author: Jan Schmidt + * + * gststreams.h : Header for GstStream subsystem + * + * 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. + */ + + +#ifndef __GST_STREAMS_H__ +#define __GST_STREAMS_H__ + +#include +#include + +G_BEGIN_DECLS + +#define GST_TYPE_STREAM (gst_stream_get_type ()) +#define GST_IS_STREAM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_STREAM)) +#define GST_IS_STREAM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_STREAM)) +#define GST_STREAM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_STREAM, GstStreamClass)) +#define GST_STREAM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_STREAM, GstStream)) +#define GST_STREAM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_STREAM, GstStreamClass)) +#define GST_STREAM_CAST(obj) ((GstStream*)(obj)) + +/** + * GstStreamType: + * @GST_STREAM_TYPE_UNKNOWN: The stream is of unknown (unclassified) type. + * @GST_STREAM_TYPE_AUDIO: The stream is of audio data + * @GST_STREAM_TYPE_VIDEO: The stream carries video data + * @GST_STREAM_TYPE_CONTAINER: The stream is a muxed container type + * @GST_STREAM_TYPE_TEXT: The stream contains subtitle / subpicture data. + * + * #GstStreamType describes a high level classification set for + * flows of data in #GstStream objects. + */ +typedef enum { + GST_STREAM_TYPE_UNKNOWN = 1 << 0, + GST_STREAM_TYPE_AUDIO = 1 << 1, + GST_STREAM_TYPE_VIDEO = 1 << 2, + GST_STREAM_TYPE_CONTAINER = 1 << 3, + GST_STREAM_TYPE_TEXT = 1 << 4 +} GstStreamType; + + +typedef struct _GstStream GstStream; +typedef struct _GstStreamClass GstStreamClass; +typedef struct _GstStreamPrivate GstStreamPrivate; + +/** + * GstStream: + * @stream_id: The Stream Identifier for this #GstStream + * + * A high-level object representing a single stream. It might be backed, or + * not, by an actual flow of data in a pipeline (#GstPad). + * + * A #GstStream does not care about data changes (such as decoding, encoding, + * parsing,...) as long as the underlying data flow corresponds to the same + * high-level flow (ex: a certain audio track). + * + * A #GstStream contains all the information pertinent to a stream, such as + * stream-id, tags, caps, type, ... + * + * Elements can subclass a #GstStream for internal usage (to contain information + * pertinent to streams of data). + */ +struct _GstStream { + GstObject object; + + /*< public >*/ + const gchar *stream_id; + + /*< private >*/ + GstStreamPrivate *priv; + + gpointer _gst_reserved[GST_PADDING]; +}; + +/** + * GstStreamClass: + * @parent_class: the parent class structure + * + * GstStream class structure + */ +struct _GstStreamClass { + GstObjectClass parent_class; + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING]; +}; + +GType gst_stream_get_type (void); + +GstStream *gst_stream_new (const gchar *stream_id, + GstCaps *caps, + GstStreamType type, + GstStreamFlags flags); + +const gchar *gst_stream_get_stream_id (GstStream *stream); + +void gst_stream_set_stream_flags (GstStream *stream, GstStreamFlags flags); +GstStreamFlags gst_stream_get_stream_flags (GstStream *stream); + +void gst_stream_set_stream_type (GstStream *stream, GstStreamType stream_type); +GstStreamType gst_stream_get_stream_type (GstStream *stream); + +void gst_stream_set_tags (GstStream *stream, GstTagList *tags); +GstTagList *gst_stream_get_tags (GstStream *stream); + +void gst_stream_set_caps (GstStream *stream, GstCaps *caps); +GstCaps *gst_stream_get_caps (GstStream *stream); + +const gchar *gst_stream_type_get_name (GstStreamType stype); +G_END_DECLS + +#endif /* __GST_STREAMS_H__ */ diff --git a/gst/gstutils.c b/gst/gstutils.c index 1e690ce60e..a9b046b0ac 100644 --- a/gst/gstutils.c +++ b/gst/gstutils.c @@ -3993,6 +3993,41 @@ gst_pad_get_stream_id (GstPad * pad) return ret; } +/** + * gst_pad_get_stream: + * @pad: A source #GstPad + * + * Returns the current #GstStream for the @pad, or %NULL if none has been + * set yet, i.e. the pad has not received a stream-start event yet. + * + * This is a convenience wrapper around gst_pad_get_sticky_event() and + * gst_event_parse_stream(). + * + * Returns: (nullable) (transfer full): the current #GstStream for @pad, or %NULL. + * unref the returned stream when no longer needed. + * + * Since: 1.X + */ +GstStream * +gst_pad_get_stream (GstPad * pad) +{ + GstStream *stream = NULL; + GstEvent *event; + + g_return_val_if_fail (GST_IS_PAD (pad), NULL); + + event = gst_pad_get_sticky_event (pad, GST_EVENT_STREAM_START, 0); + if (event != NULL) { + gst_event_parse_stream (event, &stream); + gst_event_unref (event); + GST_LOG_OBJECT (pad, "pad has stream object %p", stream); + } else { + GST_DEBUG_OBJECT (pad, "pad has not received a stream-start event yet"); + } + + return stream; +} + /** * gst_util_group_id_next: * diff --git a/gst/gstutils.h b/gst/gstutils.h index 3bc032f4dd..9360c53e48 100644 --- a/gst/gstutils.h +++ b/gst/gstutils.h @@ -942,6 +942,7 @@ gchar * gst_pad_create_stream_id_printf (GstPad * pad, Gs gchar * gst_pad_create_stream_id_printf_valist (GstPad * pad, GstElement * parent, const gchar *stream_id, va_list var_args) G_GNUC_PRINTF (3, 0) G_GNUC_MALLOC; gchar * gst_pad_get_stream_id (GstPad * pad); +GstStream * gst_pad_get_stream (GstPad * pad); /* bin functions */ void gst_bin_add_many (GstBin *bin, GstElement *element_1, ...) G_GNUC_NULL_TERMINATED; diff --git a/tests/check/Makefile.am b/tests/check/Makefile.am index cc8c8077da..5304831344 100644 --- a/tests/check/Makefile.am +++ b/tests/check/Makefile.am @@ -137,6 +137,7 @@ check_PROGRAMS = \ gst/gstsegment \ gst/gstsystemclock \ gst/gstclock \ + gst/gststream \ gst/gststructure \ gst/gsttag \ gst/gsttracerrecord \ diff --git a/tests/check/gst/.gitignore b/tests/check/gst/.gitignore index 623abeea36..865a4e52d2 100644 --- a/tests/check/gst/.gitignore +++ b/tests/check/gst/.gitignore @@ -38,6 +38,7 @@ gstprintf gstprotection gstregistry gstsegment +gststream gststructure gstsystemclock gsttag diff --git a/tests/check/gst/gstevent.c b/tests/check/gst/gstevent.c index 387f22985c..ebb836ced5 100644 --- a/tests/check/gst/gstevent.c +++ b/tests/check/gst/gstevent.c @@ -52,6 +52,31 @@ GST_START_TEST (create_events) fail_unless (reset_time == TRUE); gst_event_unref (event); } + /* SELECT_STREAMS */ + { + GList *streams = NULL; + GList *res = NULL; + GList *tmp; + streams = g_list_append (streams, (gpointer) "stream1"); + streams = g_list_append (streams, (gpointer) "stream2"); + event = gst_event_new_select_streams (streams); + fail_if (event == NULL); + fail_unless (GST_EVENT_TYPE (event) == GST_EVENT_SELECT_STREAMS); + fail_unless (GST_EVENT_IS_UPSTREAM (event)); + + gst_event_parse_select_streams (event, &res); + fail_if (res == NULL); + fail_unless_equals_int (g_list_length (res), 2); + tmp = res; + fail_unless_equals_string (tmp->data, "stream1"); + tmp = tmp->next; + fail_unless_equals_string (tmp->data, "stream2"); + + gst_event_unref (event); + + g_list_free (streams); + g_list_free_full (res, g_free); + } /* EOS */ { event = gst_event_new_eos (); @@ -219,6 +244,37 @@ GST_START_TEST (create_events) gst_event_unref (event); } + /* STREAM_COLLECTION */ + { + GstStreamCollection *collection, *res = NULL; + GstStream *stream1, *stream2; + GstCaps *caps1, *caps2; + + /* Create a collection of two streams */ + caps1 = gst_caps_from_string ("some/caps"); + caps2 = gst_caps_from_string ("some/other-string"); + + stream1 = gst_stream_new ("stream-1", caps1, GST_STREAM_TYPE_AUDIO, 0); + stream2 = gst_stream_new ("stream-2", caps2, GST_STREAM_TYPE_VIDEO, 0); + + collection = gst_stream_collection_new ("something"); + fail_unless (gst_stream_collection_add_stream (collection, stream1)); + fail_unless (gst_stream_collection_add_stream (collection, stream2)); + + event = gst_event_new_stream_collection (collection); + fail_unless (event != NULL); + + gst_event_parse_stream_collection (event, &res); + fail_unless (res != NULL); + fail_unless (res == collection); + + gst_event_unref (event); + gst_object_unref (res); + gst_object_unref (collection); + gst_caps_unref (caps1); + gst_caps_unref (caps2); + } + /* NAVIGATION */ { structure = gst_structure_new ("application/x-gst-navigation", "event", diff --git a/tests/check/gst/gstmessage.c b/tests/check/gst/gstmessage.c index 94d5cdd16c..8c404b29cd 100644 --- a/tests/check/gst/gstmessage.c +++ b/tests/check/gst/gstmessage.c @@ -377,6 +377,90 @@ GST_START_TEST (test_parsing) gst_message_unref (message); } + /* GST_MESSAGE_STREAM_COLLECTION */ + { + GstMessage *message; + GstStreamCollection *collection, *res = NULL; + GstStream *stream1, *stream2; + GstCaps *caps1, *caps2; + + /* Create a collection of two streams */ + caps1 = gst_caps_from_string ("some/caps"); + caps2 = gst_caps_from_string ("some/other-string"); + + stream1 = gst_stream_new ("stream-1", caps1, GST_STREAM_TYPE_AUDIO, 0); + stream2 = gst_stream_new ("stream-2", caps2, GST_STREAM_TYPE_VIDEO, 0); + + collection = gst_stream_collection_new ("something"); + fail_unless (gst_stream_collection_add_stream (collection, stream1)); + fail_unless (gst_stream_collection_add_stream (collection, stream2)); + + message = gst_message_new_stream_collection (NULL, collection); + fail_unless (message != NULL); + + gst_message_parse_stream_collection (message, &res); + fail_unless (res != NULL); + + gst_message_unref (message); + gst_object_unref (res); + gst_object_unref (collection); + gst_caps_unref (caps1); + gst_caps_unref (caps2); + } + /* GST_MESSAGE_STREAMS_SELECTED */ + { + GstMessage *message; + GstStreamCollection *collection, *res = NULL; + GstStream *stream1, *stream2, *stream3; + GstCaps *caps1, *caps2; + + /* Create a collection of two streams */ + caps1 = gst_caps_from_string ("some/caps"); + caps2 = gst_caps_from_string ("some/other-string"); + + stream1 = gst_stream_new ("stream-1", caps1, GST_STREAM_TYPE_AUDIO, 0); + stream2 = gst_stream_new ("stream-2", caps2, GST_STREAM_TYPE_VIDEO, 0); + + collection = gst_stream_collection_new ("something"); + fail_unless (gst_stream_collection_add_stream (collection, stream1)); + fail_unless (gst_stream_collection_add_stream (collection, stream2)); + + message = gst_message_new_streams_selected (NULL, collection); + fail_unless (message != NULL); + + gst_message_parse_streams_selected (message, &res); + fail_unless (res != NULL); + + fail_unless (gst_message_streams_selected_get_size (message) == 0); + gst_object_unref (res); + gst_message_unref (message); + + /* Once again, this time with a stream in it */ + message = gst_message_new_streams_selected (NULL, collection); + fail_unless (message != NULL); + + gst_message_streams_selected_add (message, stream1); + + gst_message_parse_streams_selected (message, &res); + fail_unless (res != NULL); + + /* There is only one stream ! */ + fail_unless (gst_message_streams_selected_get_size (message) == 1); + + stream3 = gst_message_streams_selected_get_stream (message, 0); + fail_unless (stream3 != NULL); + gst_object_unref (stream3); + + /* Shoul fail */ + ASSERT_CRITICAL (gst_message_streams_selected_get_stream (message, 1)); + + gst_object_unref (res); + gst_message_unref (message); + + gst_object_unref (collection); + gst_caps_unref (caps1); + gst_caps_unref (caps2); + } } GST_END_TEST; diff --git a/tests/check/gst/gststream.c b/tests/check/gst/gststream.c new file mode 100644 index 0000000000..aa09b2b7d2 --- /dev/null +++ b/tests/check/gst/gststream.c @@ -0,0 +1,225 @@ +/* GStreamer + * Copyright (C) <2015> Edward Hervey + * + * gststructure.c: Unit tests for GstStream and GstStreamCollection + * + * 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 + +GST_START_TEST (test_stream_creation) +{ + GstStream *stream; + GstCaps *caps; + GstCaps *caps2; + GstTagList *tags, *tags2; + + caps = gst_caps_from_string ("some/caps"); + stream = gst_stream_new ("stream-id", caps, GST_STREAM_TYPE_AUDIO, 0); + fail_unless (stream != NULL); + + fail_unless_equals_string (gst_stream_get_stream_id (stream), "stream-id"); + caps2 = gst_stream_get_caps (stream); + fail_unless (gst_caps_is_equal (caps, caps2)); + gst_caps_unref (caps2); + + fail_unless (gst_stream_get_stream_type (stream) == GST_STREAM_TYPE_AUDIO); + + gst_caps_unref (caps); + + tags = gst_tag_list_new (GST_TAG_ALBUM, "test-album", NULL); + g_object_set (stream, "tags", tags, NULL); + tags2 = gst_stream_get_tags (stream); + fail_unless (gst_tag_list_is_equal (tags, tags2)); + gst_tag_list_unref (tags); + gst_tag_list_unref (tags2); + + gst_object_unref (stream); +} + +GST_END_TEST; + +GST_START_TEST (test_stream_event) +{ + GstEvent *event; + GstStream *stream, *stream2 = NULL; + GstCaps *caps; + GstCaps *caps2; + + event = gst_event_new_stream_start ("here/we/go"); + /* By default a stream-start event has no stream */ + gst_event_parse_stream (event, &stream2); + fail_if (stream2 != NULL); + + /* Create and set stream on event */ + caps = gst_caps_from_string ("some/caps"); + stream = gst_stream_new ("here/we/go", caps, GST_STREAM_TYPE_AUDIO, 0); + fail_unless (stream != NULL); + gst_event_set_stream (event, stream); + + /* Parse and check it's the same */ + gst_event_parse_stream (event, &stream2); + fail_unless (stream2 != NULL); + fail_unless_equals_string (gst_stream_get_stream_id (stream2), "here/we/go"); + caps2 = gst_stream_get_caps (stream); + fail_unless (gst_caps_is_equal (caps, caps2)); + fail_unless (gst_stream_get_stream_type (stream) == GST_STREAM_TYPE_AUDIO); + gst_caps_unref (caps2); + + gst_event_unref (event); + gst_caps_unref (caps); + gst_object_unref (stream); + gst_object_unref (stream2); +} + +GST_END_TEST; + +struct NotifyStats +{ + guint collection_notify; + guint collection_notify_caps; + guint collection_notify_tags; + guint collection_notify_type; + guint collection_notify_flags; + + guint stream_notify; + guint stream_notify_caps; + guint stream_notify_tags; + guint stream_notify_type; + guint stream_notify_flags; + + guint stream2_notify; + guint stream2_notify_caps; + guint stream2_notify_tags; + guint stream2_notify_type; + guint stream2_notify_flags; +}; + +static void +stream_notify_cb (GstStreamCollection * collection, GstStream * stream, + GParamSpec * pspec, guint * val) +{ + GST_LOG ("Got stream-notify from %" GST_PTR_FORMAT " for %s from %" + GST_PTR_FORMAT, stream, pspec->name, collection); + (*val)++; +} + +static void +notify_cb (GstStream * stream, GParamSpec * pspec, guint * val) +{ + GST_LOG ("Got notify from %" GST_PTR_FORMAT " for %s", stream, pspec->name); + (*val)++; +} + +GST_START_TEST (test_notifies) +{ + GstStreamCollection *collection; + GstStream *stream, *stream2 = NULL; + GstCaps *caps; + struct NotifyStats stats = { 0, }; + GstTagList *tags; + + collection = gst_stream_collection_new ("check-collection"); + g_signal_connect (collection, "stream-notify", (GCallback) stream_notify_cb, + &stats.collection_notify); + g_signal_connect (collection, "stream-notify::stream-type", + (GCallback) stream_notify_cb, &stats.collection_notify_type); + g_signal_connect (collection, "stream-notify::stream-flags", + (GCallback) stream_notify_cb, &stats.collection_notify_flags); + g_signal_connect (collection, "stream-notify::caps", + (GCallback) stream_notify_cb, &stats.collection_notify_caps); + g_signal_connect (collection, "stream-notify::tags", + (GCallback) stream_notify_cb, &stats.collection_notify_tags); + + caps = gst_caps_from_string ("some/audio-caps"); + stream = gst_stream_new ("here/we/go", caps, GST_STREAM_TYPE_AUDIO, 0); + gst_caps_unref (caps); + g_signal_connect (stream, "notify", (GCallback) notify_cb, + &stats.stream_notify); + g_signal_connect (stream, "notify::stream-type", (GCallback) notify_cb, + &stats.stream_notify_type); + g_signal_connect (stream, "notify::stream-flags", (GCallback) notify_cb, + &stats.stream_notify_flags); + g_signal_connect (stream, "notify::caps", (GCallback) notify_cb, + &stats.stream_notify_caps); + g_signal_connect (stream, "notify::tags", (GCallback) notify_cb, + &stats.stream_notify_tags); + gst_stream_collection_add_stream (collection, stream); + + caps = gst_caps_from_string ("some/video-caps"); + stream2 = gst_stream_new ("here/we/go/again", caps, GST_STREAM_TYPE_VIDEO, 0); + gst_caps_unref (caps); + g_signal_connect (stream2, "notify", (GCallback) notify_cb, + &stats.stream2_notify); + g_signal_connect (stream2, "notify::stream-type", (GCallback) notify_cb, + &stats.stream2_notify_type); + g_signal_connect (stream2, "notify::stream-flags", (GCallback) notify_cb, + &stats.stream2_notify_flags); + g_signal_connect (stream2, "notify::caps", (GCallback) notify_cb, + &stats.stream2_notify_caps); + g_signal_connect (stream2, "notify::tags", (GCallback) notify_cb, + &stats.stream2_notify_tags); + gst_stream_collection_add_stream (collection, stream2); + + caps = gst_caps_from_string ("some/new-video-caps"); + gst_stream_set_caps (stream2, caps); + gst_caps_unref (caps); + + fail_unless (stats.collection_notify == 1); + fail_unless (stats.collection_notify_caps == 1); + fail_unless (stats.stream_notify == 0); + fail_unless (stats.stream_notify_caps == 0); + fail_unless (stats.stream_notify_tags == 0); + fail_unless (stats.stream2_notify == 1); + fail_unless (stats.stream2_notify_caps == 1); + fail_unless (stats.stream2_notify_tags == 0); + + tags = gst_tag_list_new (GST_TAG_ALBUM, "test-album", NULL); + gst_stream_set_tags (stream, tags); + gst_tag_list_unref (tags); + + fail_unless (stats.collection_notify == 2); + fail_unless (stats.collection_notify_caps == 1); + fail_unless (stats.collection_notify_tags == 1); + fail_unless (stats.stream_notify == 1); + fail_unless (stats.stream_notify_caps == 0); + fail_unless (stats.stream_notify_tags == 1); + fail_unless (stats.stream2_notify == 1); + fail_unless (stats.stream2_notify_caps == 1); + fail_unless (stats.stream2_notify_tags == 0); + + gst_object_unref (collection); +} + +GST_END_TEST; + +static Suite * +gst_streams_suite (void) +{ + Suite *s = suite_create ("GstStream"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_stream_creation); + tcase_add_test (tc_chain, test_stream_event); + tcase_add_test (tc_chain, test_notifies); + return s; +} + +GST_CHECK_MAIN (gst_streams); diff --git a/tests/check/gst/gststream.h b/tests/check/gst/gststream.h new file mode 100644 index 0000000000..c2631bdff9 --- /dev/null +++ b/tests/check/gst/gststream.h @@ -0,0 +1,53 @@ +/* GStreamer + * Copyright (C) <2015> Edward Hervey + * + * gststructure.c: Unit tests for GstStream and GstStreamCollection + * + * 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 + +GST_START_TEST (test_stream_creation) +{ + GstStream *stream; + GstCaps *caps; + + caps = gst_caps_from_string("some/caps"); + stream = gst_stream_new ("upstream-id", caps, GST_STREAM_TYPE_AUDIO, 0); + fail_unless (stream != NULL); + + fail_unless_equals_string (gst_stream_get_stream_id (stream), "upstream-id"); + + gst_object_unref (stream); +} + +GST_END_TEST; + +static Suite * +gst_streams_suite (void) +{ + Suite *s = suite_create ("GstStream"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_stream_creation); + return s; +} + +GST_CHECK_MAIN (gst_streams); diff --git a/win32/common/libgstreamer.def b/win32/common/libgstreamer.def index 333f10ee07..065c5269a6 100644 --- a/win32/common/libgstreamer.def +++ b/win32/common/libgstreamer.def @@ -588,8 +588,10 @@ EXPORTS gst_event_new_seek gst_event_new_segment gst_event_new_segment_done + gst_event_new_select_streams gst_event_new_sink_message gst_event_new_step + gst_event_new_stream_collection gst_event_new_stream_start gst_event_new_tag gst_event_new_toc @@ -605,8 +607,11 @@ EXPORTS gst_event_parse_seek gst_event_parse_segment gst_event_parse_segment_done + gst_event_parse_select_streams gst_event_parse_sink_message gst_event_parse_step + gst_event_parse_stream + gst_event_parse_stream_collection gst_event_parse_stream_flags gst_event_parse_stream_start gst_event_parse_tag @@ -615,6 +620,7 @@ EXPORTS gst_event_set_group_id gst_event_set_running_time_offset gst_event_set_seqnum + gst_event_set_stream gst_event_set_stream_flags gst_event_type_flags_get_type gst_event_type_get_flags @@ -726,8 +732,10 @@ EXPORTS gst_message_new_state_dirty gst_message_new_step_done gst_message_new_step_start + gst_message_new_stream_collection gst_message_new_stream_start gst_message_new_stream_status + gst_message_new_streams_selected gst_message_new_structure_change gst_message_new_tag gst_message_new_toc @@ -757,7 +765,9 @@ EXPORTS gst_message_parse_state_changed gst_message_parse_step_done gst_message_parse_step_start + gst_message_parse_stream_collection gst_message_parse_stream_status + gst_message_parse_streams_selected gst_message_parse_structure_change gst_message_parse_tag gst_message_parse_toc @@ -768,6 +778,9 @@ EXPORTS gst_message_set_qos_values gst_message_set_seqnum gst_message_set_stream_status_object + gst_message_streams_selected_add + gst_message_streams_selected_get_size + gst_message_streams_selected_get_stream gst_message_type_get_name gst_message_type_get_type gst_message_type_to_quark @@ -850,6 +863,7 @@ EXPORTS gst_pad_get_peer gst_pad_get_range gst_pad_get_sticky_event + gst_pad_get_stream gst_pad_get_stream_id gst_pad_get_type gst_pad_has_current_caps @@ -1192,10 +1206,29 @@ EXPORTS gst_static_pad_template_get gst_static_pad_template_get_caps gst_static_pad_template_get_type + gst_stream_collection_add_stream + gst_stream_collection_get_size + gst_stream_collection_get_stream + gst_stream_collection_get_type + gst_stream_collection_get_upstream_id + gst_stream_collection_new gst_stream_error_get_type gst_stream_error_quark gst_stream_flags_get_type + gst_stream_get_caps + gst_stream_get_stream_flags + gst_stream_get_stream_id + gst_stream_get_stream_type + gst_stream_get_tags + gst_stream_get_type + gst_stream_new + gst_stream_set_caps + gst_stream_set_stream_flags + gst_stream_set_stream_type + gst_stream_set_tags gst_stream_status_type_get_type + gst_stream_type_get_name + gst_stream_type_get_type gst_structure_can_intersect gst_structure_change_type_get_type gst_structure_copy