1+ /*
2+ Copyright (c) 2025, The Cinder Project, All rights reserved.
3+ This code is intended for use with the Cinder C++ library: http://libcinder.org
4+
5+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that
6+ the following conditions are met:
7+
8+ * Redistributions of source code must retain the above copyright notice, this list of conditions and
9+ the following disclaimer.
10+ * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
11+ the following disclaimer in the documentation and/or other materials provided with the distribution.
12+
13+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
14+ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
15+ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
16+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
17+ TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
18+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
19+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
20+ POSSIBILITY OF SUCH DAMAGE.
21+ */
22+
23+ #pragma once
24+
25+ #include " cinder/Cinder.h"
26+
27+ #if defined( CINDER_LINUX )
28+
29+ #include " cinder/Capture.h"
30+ #include " cinder/Surface.h"
31+
32+ #include < gst/gst.h>
33+ #include < gst/app/gstappsink.h>
34+ #include < gst/video/video.h>
35+
36+ #include < atomic>
37+ #include < memory>
38+ #include < mutex>
39+ #include < thread>
40+ #include < vector>
41+
42+ namespace cinder {
43+
44+ /* * GStreamer-based video capture implementation for Linux.
45+ * Automatically constructs optimal GStreamer pipelines based on camera capabilities.
46+ * Cameras provide either uncompressed data (YUV/RGB) or compressed data (JPEG/H.264/HEVC).
47+ *
48+ * Uncompressed path: v4l2src → videoconvert → appsink
49+ * Compressed path: v4l2src → [decoder] → videoconvert → appsink
50+ *
51+ * The decoder element depends on format: jpegdec, avdec_h264, avdec_h265, or decodebin fallback.
52+ */
53+ class CaptureImplGStreamer {
54+ public:
55+ class Device ;
56+
57+ // ! Creates a GStreamer-based video capture with desired width and height. Algorithm will find best matching resolution.
58+ CaptureImplGStreamer ( int32_t width, int32_t height, const Capture::DeviceRef device );
59+
60+ // ! Creates a GStreamer-based video capture with specific Mode from device's getModes() list.
61+ CaptureImplGStreamer ( const Capture::DeviceRef& device, const Capture::Mode& mode );
62+ ~CaptureImplGStreamer ();
63+
64+ // ! Starts video capture. Transitions pipeline to PLAYING state.
65+ void start ();
66+
67+ // ! Stops video capture. Transitions pipeline to NULL state. Safe to call multiple times.
68+ void stop ();
69+
70+ // ! Returns true if pipeline is in PLAYING state.
71+ bool isCapturing ();
72+
73+ // ! Returns true if a new frame is available since last call. Thread-safe and resets the new frame flag.
74+ bool checkNewFrame () const ;
75+
76+ // ! Returns actual capture width (may differ from requested).
77+ int32_t getWidth () const { return mWidth ; }
78+
79+ // ! Returns actual capture height (may differ from requested).
80+ int32_t getHeight () const { return mHeight ; }
81+
82+ // ! Returns the most recent video frame as RGB surface, or nullptr if no frame available.
83+ Surface8uRef getSurface () const ;
84+
85+ // ! Returns the device associated with this capture.
86+ const Capture::DeviceRef getDevice () const { return mDevice ; }
87+
88+ // ! Enumerates all available video capture devices. Set forceRefresh=true to detect newly connected/disconnected devices.
89+ static const std::vector<Capture::DeviceRef>& getDevices ( bool forceRefresh = false );
90+
91+ // ! Device wrapper for GStreamer video devices. Represents a physical video capture device with GStreamer integration.
92+ class Device : public Capture ::Device {
93+ public:
94+ Device ( GstDevice* device, const std::string& name, const std::string& uniqueId );
95+ ~Device () override ;
96+
97+ // ! Returns true if device can be opened for capture. Tests by attempting to create a GStreamer element.
98+ bool checkAvailable () const override ;
99+
100+ // ! Returns true if device is physically connected (device node exists in file system).
101+ bool isConnected () const override ;
102+
103+ // ! Returns unique device identifier (e.g., "/dev/video0").
104+ Capture::DeviceIdentifier getUniqueId () const override { return mUniqueId ; }
105+
106+ // ! Returns underlying GStreamer device handle for advanced usage.
107+ GstDevice* getGstDevice () const { return mDevice ; }
108+
109+ // ! Returns all supported capture modes for this device. Results are cached for performance.
110+ std::vector<Capture::Mode> getModes () const override ;
111+
112+ private:
113+ std::string mUniqueId ;
114+ GstDevice* mDevice ;
115+
116+ // Mode enumeration caching
117+ mutable std::vector<Capture::Mode> mCachedModes ;
118+ mutable bool mModesQueried ;
119+ };
120+
121+ private:
122+ class SurfaceCache ;
123+
124+ // Initialize pipeline based on device capabilities and target dimensions
125+ bool initializePipeline ( int32_t width, int32_t height );
126+
127+ // Build pipeline from mode
128+ bool buildPipeline ( const Capture::Mode& mode );
129+
130+ // Clean up GStreamer pipeline resources
131+ void cleanupPipeline ();
132+
133+ // ! Starts background bus monitoring thread. GStreamer uses a "bus" system for error reporting and state changes.
134+ void startBusWatch ();
135+
136+ // ! Stops background bus monitoring thread.
137+ void stopBusWatch ();
138+
139+ // ! GStreamer callback for new frame arrival. Called by GStreamer when a new frame is available.
140+ static GstFlowReturn onNewSample ( GstAppSink* sink, gpointer userData );
141+
142+ // ! GStreamer callback for dynamic pad connections (decodebin only). Connects dynamically created pads to the pipeline.
143+ static void onDecoderPadAdded ( GstElement* decoder, GstPad* pad, gpointer userData );
144+
145+ // ! Processes incoming video frame. Converts GStreamer video frame to Cinder Surface format.
146+ GstFlowReturn handleSample ( GstSample* sample );
147+
148+ // ! Ensures GStreamer is initialized. Thread-safe initialization of GStreamer library.
149+ static void ensureGStreamerInitialized ();
150+
151+
152+ // Thread synchronization
153+ mutable std::mutex mMutex ;
154+ std::unique_ptr<SurfaceCache> mSurfaceCache ;
155+ mutable Surface8uRef mCurrentFrame ;
156+ mutable bool mHasNewFrame ;
157+
158+ // GStreamer pipeline elements
159+ GstElement* mPipeline ; // /< Main pipeline container
160+ GstElement* mSource ; // /< v4l2src element
161+ GstElement* mVideoConvert ; // /< videoconvert element
162+ GstElement* mCapsFilter ; // /< Output format filter
163+ GstElement* mAppSink ; // /< Application sink for frame delivery
164+ GstBus* mBus ; // /< Pipeline message bus
165+
166+ // Background processing
167+ std::thread mBusWatchThread ; // /< Bus monitoring thread
168+ std::atomic<bool > mRunBusWatch ; // /< Bus monitoring control flag
169+
170+ // Resolution tracking
171+ int32_t mRequestedWidth ; // /< Originally requested width
172+ int32_t mRequestedHeight ; // /< Originally requested height
173+ int32_t mBestWidth ; // /< Best available width from device
174+ int32_t mBestHeight ; // /< Best available height from device
175+ std::atomic<int32_t > mWidth ; // /< Current actual width
176+ std::atomic<int32_t > mHeight ; // /< Current actual height
177+ std::atomic<bool > mIsCapturing ; // /< Capture state flag
178+
179+ Capture::DeviceRef mDevice ; // /< Associated device
180+ };
181+
182+ } // namespace cinder
183+
184+ #endif // defined( CINDER_LINUX )
0 commit comments