Skip to content

Commit 4280f92

Browse files
committed
Add GStreamer-based video capture implementation for Linux
Implements CaptureImplGStreamer with full Mode support for Linux video capture. Uses GStreamer's v4l2src for camera access with automatic format negotiation and conversion pipelines. Supports both compressed (MJPEG, H264, HEVC) and uncompressed (YUV/RGB) formats with automatic decompression when needed. Enumerates device capabilities through V4L2 and provides Mode-based initialization for explicit format control. Handles device disconnection gracefully and provides detailed error reporting. Updates build system with GStreamer dependencies and adds GStreamer to the dependencies documentation table.
1 parent e770673 commit 4280f92

File tree

5 files changed

+1441
-4
lines changed

5 files changed

+1441
-4
lines changed

docs/htmlsrc/guides/dependencies/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ <h1>3rd Party Dependencies</h1>
5050
<tr><td><a href="http://www.freetype.org/">Freetype</a></td><td>2.9.1</td><td><a href="http://git.savannah.gnu.org/cgit/freetype/freetype2.git/tree/docs/FTL.TXT">Freetype License<a/></td><td>Font rendering</td></tr>
5151
<tr><td><a href="http://www.glfw.org/">GLFW</a></td><td>3.2.1</td><td><a href="http://www.glfw.org/license.html">zlib</a></td><td>Linux windowing</td></tr>
5252
<tr><td><a href="https://github.com/nothings/stb">stb_image</a></td><td>2.02</td><td>Public domain</td><td>Linux & Android image read/write</td></tr>
53+
<tr><td><a href="https://gstreamer.freedesktop.org/">GStreamer</a></td><td>&lt;system&gt;</td><td><a href="https://gstreamer.freedesktop.org/documentation/frequently-asked-questions/licensing.html">LGPL</a></td><td>Linux video capture and playback</td></tr>
5354
<tr><td><a href="https://github.com/ofTheo/videoInput">videoInput</a></td><td>0.1995</td><td>Public Domain</td><td>Windows desktop video capture</td></tr>
5455
<tr><td><a href="https://github.com/MSOpenTech/angle">ANGLE</a></td><td></td><td><a href="https://github.com/MSOpenTech/angle/blob/ms-master/LICENSE">BSD</a></td><td>Windows OpenGL emulation</td></tr>
5556
<tr><td><a href="https://github.com/nemtrif/utfcpp">utf8cpp</a></td><td></td><td><a href="https://github.com/nemtrif/utfcpp/blob/master/source/utf8.h">Boost</a></td><td>Unicode UTF-8 conversion</td></tr>
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
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 )

proj/cmake/libcinder_source_files.cmake

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,10 @@ list( APPEND SRC_SET_CINDER
6565
${CINDER_SRC_DIR}/cinder/Xml.cpp
6666
)
6767

68-
if( ( NOT CINDER_LINUX ) AND ( NOT CINDER_ANDROID ) )
69-
list( APPEND SRC_SET_CINDER
70-
${CINDER_SRC_DIR}/cinder/Capture.cpp
71-
)
68+
if( NOT CINDER_ANDROID )
69+
list( APPEND SRC_SET_CINDER
70+
${CINDER_SRC_DIR}/cinder/Capture.cpp
71+
)
7272
endif()
7373
if( ( NOT CINDER_COCOA_TOUCH ) AND ( NOT CINDER_ANDROID ) )
7474
list( APPEND SRC_SET_CINDER

proj/cmake/platform_linux.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ endif()
4545

4646
if( NOT CINDER_DISABLE_VIDEO )
4747
list( APPEND SRC_SET_CINDER_VIDEO_LINUX
48+
${CINDER_SRC_DIR}/cinder/CaptureImplGStreamer.cpp
4849
${CINDER_SRC_DIR}/cinder/linux/GstPlayer.cpp
4950
${CINDER_SRC_DIR}/cinder/linux/Movie.cpp
5051
)

0 commit comments

Comments
 (0)