Skip to content

Commit 137e0a6

Browse files
authored
[camera] Use common configs to create a GStreamer pipeline (#39009)
* Use common configs to create a GStreamer pipeline * Don't mix aspect ratios between the min and max resolutions.
1 parent 9f1208d commit 137e0a6

File tree

2 files changed

+95
-122
lines changed

2 files changed

+95
-122
lines changed

examples/camera-app/linux/include/camera-device.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,13 @@ static constexpr uint32_t kMaxEncodedPixelRate = 27648000; // 720p at 30fp
4444
static constexpr uint8_t kMicrophoneMinLevel = 1;
4545
static constexpr uint8_t kMicrophoneMaxLevel = 254; // Spec constraint
4646
static constexpr uint8_t kMicrophoneMaxChannelCount = 8; // Spec Constraint in AudioStreamAllocate
47-
static constexpr uint16_t kMinResolutionWidth = 256; // Low SD resolution
48-
static constexpr uint16_t kMinResolutionHeight = 144; // Low SD resolution
47+
static constexpr uint16_t kMinResolutionWidth = 640; // Low SD resolution
48+
static constexpr uint16_t kMinResolutionHeight = 360; // Low SD resolution
4949
static constexpr uint16_t kMaxResolutionWidth = 1920; // 1080p resolution
5050
static constexpr uint16_t kMaxResolutionHeight = 1080; // 1080p resolution
5151
static constexpr uint16_t kSnapshotStreamFrameRate = 30;
5252
static constexpr uint16_t kMaxVideoFrameRate = 120;
53-
static constexpr uint16_t kMinVideoFrameRate = 15;
53+
static constexpr uint16_t kMinVideoFrameRate = 30;
5454
static constexpr uint32_t kMinBitRateBps = 10000; // 10 kbps
5555
static constexpr uint32_t kMaxBitRateBps = 2000000; // 2 mbps
5656
static constexpr uint32_t kMinFragLenMsec = 1000; // 1 sec

examples/camera-app/linux/src/camera-device.cpp

Lines changed: 92 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -127,93 +127,78 @@ GstElement * CameraDevice::CreateSnapshotPipeline(const std::string & device, in
127127
if (!pipeline || !source || !jpeg_caps || !videorate || !videorate_caps || !queue || !filesink)
128128
{
129129
ChipLogError(Camera, "Not all elements could be created.");
130-
gst_object_unref(pipeline);
130+
131+
if (pipeline)
132+
gst_object_unref(pipeline);
133+
if (source)
134+
gst_object_unref(source);
135+
if (jpeg_caps)
136+
gst_object_unref(jpeg_caps);
137+
if (videorate)
138+
gst_object_unref(videorate);
139+
if (videorate_caps)
140+
gst_object_unref(videorate_caps);
141+
if (queue)
142+
gst_object_unref(queue);
143+
if (filesink)
144+
gst_object_unref(filesink);
145+
131146
error = CameraError::ERROR_INIT_FAILED;
132147
return nullptr;
133148
}
134149

150+
// Set the source device and caps
151+
g_object_set(source, "device", device.c_str(), "do-timestamp", TRUE, nullptr);
152+
153+
GstCaps * caps = gst_caps_new_simple("image/jpeg", "width", G_TYPE_INT, width, "height", G_TYPE_INT, height, "framerate",
154+
GST_TYPE_FRACTION, framerate, 1, "quality", G_TYPE_INT, quality, nullptr);
155+
g_object_set(jpeg_caps, "caps", caps, nullptr);
156+
gst_caps_unref(caps);
157+
158+
// Set the output file location
159+
g_object_set(filesink, "location", filename.c_str(), nullptr);
160+
135161
// Add elements to the pipeline
136-
gst_bin_add_many(GST_BIN(pipeline), source, jpeg_caps, videorate, videorate_caps, queue, filesink, NULL);
162+
gst_bin_add_many(GST_BIN(pipeline), source, jpeg_caps, videorate, videorate_caps, queue, filesink, nullptr);
137163

138164
// Link the elements
139-
if (gst_element_link_many(source, jpeg_caps, videorate, videorate_caps, queue, filesink, NULL) != TRUE)
165+
if (gst_element_link_many(source, jpeg_caps, videorate, videorate_caps, queue, filesink, nullptr) != TRUE)
140166
{
141167
ChipLogError(Camera, "Elements could not be linked.");
168+
169+
// The pipeline will unref all added elements automatically when you unref the pipeline.
142170
gst_object_unref(pipeline);
143171
error = CameraError::ERROR_INIT_FAILED;
144172
return nullptr;
145173
}
146174

147-
// Set the source device and caps
148-
g_object_set(source, "device", device.c_str(), "do-timestamp", TRUE, NULL);
149-
150-
GstCaps * caps = gst_caps_new_simple("image/jpeg", "width", G_TYPE_INT, width, "height", G_TYPE_INT, height, "framerate",
151-
GST_TYPE_FRACTION, framerate, 1, "quality", G_TYPE_INT, quality, NULL);
152-
g_object_set(jpeg_caps, "caps", caps, NULL);
153-
gst_caps_unref(caps);
154-
155-
// Set the output file location
156-
g_object_set(filesink, "location", filename.c_str(), NULL);
157-
158175
return pipeline;
159176
}
160177

161-
// Helper function to create a GStreamer pipeline
178+
// Helper function to create a GStreamer pipeline that ingests MJPEG frames coming
179+
// from the camera, converted to H.264, and sent out on UDP port over RTP/UDP.
162180
GstElement * CameraDevice::CreateVideoPipeline(const std::string & device, int width, int height, int framerate,
163181
CameraError & error)
164182
{
165-
GstElement * pipeline = nullptr;
183+
GstElement * pipeline = gst_pipeline_new("video-pipeline");
184+
GstElement * capsfilter = gst_element_factory_make("capsfilter", "mjpeg_caps");
185+
GstElement * jpegdec = gst_element_factory_make("jpegdec", "jpegdec");
186+
GstElement * videoconvert = gst_element_factory_make("videoconvert", "videoconvert");
187+
GstElement * x264enc = gst_element_factory_make("x264enc", "encoder");
188+
GstElement * rtph264pay = gst_element_factory_make("rtph264pay", "rtph264");
189+
GstElement * udpsink = gst_element_factory_make("udpsink", "udpsink");
166190
GstElement * source = nullptr;
167-
GstElement * capsfilter = nullptr;
168-
GstElement * videoconvert = nullptr;
169-
GstElement * x264enc = nullptr;
170-
GstElement * rtph264pay = nullptr;
171-
GstElement * udpsink = nullptr;
172-
173-
// Create the pipeline elements
174-
pipeline = gst_pipeline_new("video-pipeline");
175191

176-
// Create elements
177192
#ifdef AV_STREAM_GST_USE_TEST_SRC
178193
source = gst_element_factory_make("videotestsrc", "source");
179194
#else
180195
source = gst_element_factory_make("v4l2src", "source");
181196
#endif
182-
capsfilter = gst_element_factory_make("capsfilter", "filter");
183-
videoconvert = gst_element_factory_make("videoconvert", "videoconvert");
184-
x264enc = gst_element_factory_make("x264enc", "encoder");
185-
rtph264pay = gst_element_factory_make("rtph264pay", "rtph264");
186-
udpsink = gst_element_factory_make("udpsink", "udpsink");
187197

188-
if (pipeline == nullptr || source == nullptr || capsfilter == nullptr || videoconvert == nullptr || x264enc == nullptr ||
189-
rtph264pay == nullptr || udpsink == nullptr)
198+
if (!pipeline || !source || !capsfilter || !jpegdec || !videoconvert || !x264enc || !rtph264pay || !udpsink)
190199
{
191200
ChipLogError(Camera, "Not all elements could be created.");
192-
if (pipeline)
193-
gst_object_unref(pipeline);
194-
if (source)
195-
gst_object_unref(source);
196-
if (capsfilter)
197-
gst_object_unref(capsfilter);
198-
if (videoconvert)
199-
gst_object_unref(videoconvert);
200-
if (x264enc)
201-
gst_object_unref(x264enc);
202-
if (rtph264pay)
203-
gst_object_unref(rtph264pay);
204-
if (udpsink)
205-
gst_object_unref(udpsink);
206-
error = CameraError::ERROR_INIT_FAILED;
207-
return nullptr;
208-
}
209-
210-
// Add elements to the pipeline
211-
gst_bin_add_many(GST_BIN(pipeline), source, capsfilter, videoconvert, x264enc, rtph264pay, udpsink, NULL);
212201

213-
// Link the elements
214-
if (!gst_element_link_many(source, capsfilter, videoconvert, x264enc, rtph264pay, udpsink, NULL))
215-
{
216-
ChipLogError(Camera, "Elements could not be linked.");
217202
if (pipeline)
218203
gst_object_unref(pipeline);
219204
if (source)
@@ -222,68 +207,76 @@ GstElement * CameraDevice::CreateVideoPipeline(const std::string & device, int w
222207
gst_object_unref(capsfilter);
223208
if (videoconvert)
224209
gst_object_unref(videoconvert);
210+
if (jpegdec)
211+
gst_object_unref(jpegdec);
225212
if (x264enc)
226213
gst_object_unref(x264enc);
227214
if (rtph264pay)
228215
gst_object_unref(rtph264pay);
229216
if (udpsink)
230217
gst_object_unref(udpsink);
218+
231219
error = CameraError::ERROR_INIT_FAILED;
232220
return nullptr;
233221
}
234222

235-
// Create GstCaps for the video source
236-
GstCaps * caps = gst_caps_new_simple("video/x-raw", "width", G_TYPE_INT, width, "height", G_TYPE_INT, height, "format",
237-
G_TYPE_STRING, "NV12", // Adjust format as needed
238-
"framerate", GST_TYPE_FRACTION, framerate, 1, NULL);
223+
// Caps asking the camera for MJPEG at the requested resolution / rate
224+
GstCaps * caps = gst_caps_new_simple("image/jpeg", "width", G_TYPE_INT, width, "height", G_TYPE_INT, height, "framerate",
225+
GST_TYPE_FRACTION, framerate, 1, nullptr);
226+
g_object_set(capsfilter, "caps", caps, nullptr);
227+
gst_caps_unref(caps);
239228

240-
// Set video test src pattern
229+
// Configure source device
241230
#ifdef AV_STREAM_GST_USE_TEST_SRC
242-
g_object_set(source, "pattern", kBallAnimationPattern, NULL);
231+
g_object_set(source, "pattern", kBallAnimationPattern, nullptr);
232+
#else
233+
g_object_set(source, "device", device.c_str(), nullptr);
243234
#endif
244235

245-
// Set the caps on the capsfilter element
246-
g_object_set(capsfilter, "caps", caps, NULL);
236+
// Configure encoder / sink for low‑latency RTP
237+
gst_util_set_object_arg(G_OBJECT(x264enc), "tune", "zerolatency");
238+
g_object_set(udpsink, "host", STREAM_GST_DEST_IP, "port", VIDEO_STREAM_GST_DEST_PORT, "sync", FALSE, "async", FALSE, nullptr);
247239

248-
// Set udpsink properties
249-
g_object_set(udpsink, "host", STREAM_GST_DEST_IP, "port", VIDEO_STREAM_GST_DEST_PORT, NULL);
240+
// Build pipeline: v4l2src → capsfilter → jpegdec → videoconvert → x264enc → rtph264pay → udpsink
241+
gst_bin_add_many(GST_BIN(pipeline), source, capsfilter, jpegdec, videoconvert, x264enc, rtph264pay, udpsink, nullptr);
250242

251-
// Unref the caps to free memory
252-
gst_caps_unref(caps);
243+
// Link the elements
244+
if (!gst_element_link_many(source, capsfilter, jpegdec, videoconvert, x264enc, rtph264pay, udpsink, nullptr))
245+
{
246+
ChipLogError(Camera, "CreateVideoPipeline: link failed");
247+
248+
// The bin (pipeline) will unref all added elements automatically when you unref the bin.
249+
gst_object_unref(pipeline);
250+
error = CameraError::ERROR_INIT_FAILED;
251+
return nullptr;
252+
}
253253

254254
return pipeline;
255255
}
256256

257257
// Helper function to create a GStreamer pipeline
258258
GstElement * CameraDevice::CreateAudioPipeline(const std::string & device, int channels, int sampleRate, CameraError & error)
259259
{
260-
GstElement * pipeline = nullptr;
261-
GstElement * source = nullptr;
262-
GstElement * capsfilter = nullptr;
263-
GstElement * audioconvert = nullptr;
264-
GstElement * opusenc = nullptr;
265-
GstElement * rtpopuspay = nullptr;
266-
GstElement * udpsink = nullptr;
260+
GstElement * pipeline = gst_pipeline_new("audio-pipeline");
267261

268-
// Create the pipeline elements
269-
pipeline = gst_pipeline_new("audio-pipeline");
262+
GstElement * capsfilter = gst_element_factory_make("capsfilter", "filter");
263+
GstElement * audioconvert = gst_element_factory_make("audioconvert", "audio-convert");
264+
GstElement * opusenc = gst_element_factory_make("opusenc", "opus-encoder");
265+
GstElement * rtpopuspay = gst_element_factory_make("rtpopuspay", "rtpopuspay");
266+
GstElement * udpsink = gst_element_factory_make("udpsink", "udpsink");
270267

268+
GstElement * source = nullptr;
271269
// Create elements
272270
#ifdef AV_STREAM_GST_USE_TEST_SRC
273271
source = gst_element_factory_make("audiotestsrc", "source");
274272
#else
275273
source = gst_element_factory_make("pulsesrc", "source");
276274
#endif
277-
capsfilter = gst_element_factory_make("capsfilter", "filter");
278-
audioconvert = gst_element_factory_make("audioconvert", "audio-convert");
279-
opusenc = gst_element_factory_make("opusenc", "opus-encoder");
280-
rtpopuspay = gst_element_factory_make("rtpopuspay", "rtpopuspay");
281-
udpsink = gst_element_factory_make("udpsink", "udpsink");
282275

283-
if (pipeline == nullptr || source == nullptr || capsfilter == nullptr || audioconvert == nullptr || opusenc == nullptr ||
284-
rtpopuspay == nullptr || udpsink == nullptr)
276+
if (!pipeline || !source || !capsfilter || !audioconvert || !opusenc || !rtpopuspay || !udpsink)
285277
{
286278
ChipLogError(Camera, "Not all elements could be created.");
279+
287280
if (pipeline)
288281
gst_object_unref(pipeline);
289282
if (source)
@@ -298,43 +291,29 @@ GstElement * CameraDevice::CreateAudioPipeline(const std::string & device, int c
298291
gst_object_unref(rtpopuspay);
299292
if (udpsink)
300293
gst_object_unref(udpsink);
294+
301295
error = CameraError::ERROR_INIT_FAILED;
302296
return nullptr;
303297
}
304298

305299
// Create GstCaps for the audio source
306-
GstCaps * caps = gst_caps_new_simple("audio/x-raw", "channels", G_TYPE_INT, channels, "rate", G_TYPE_INT, sampleRate, NULL);
307-
308-
// Set the caps on the capsfilter element
309-
g_object_set(capsfilter, "caps", caps, NULL);
300+
GstCaps * caps = gst_caps_new_simple("audio/x-raw", "channels", G_TYPE_INT, channels, "rate", G_TYPE_INT, sampleRate, nullptr);
301+
g_object_set(capsfilter, "caps", caps, nullptr);
302+
gst_caps_unref(caps);
310303

311304
// Set udpsink properties
312-
g_object_set(udpsink, "host", STREAM_GST_DEST_IP, "port", AUDIO_STREAM_GST_DEST_PORT, NULL);
313-
314-
// Unref the caps to free memory
315-
gst_caps_unref(caps);
305+
g_object_set(udpsink, "host", STREAM_GST_DEST_IP, "port", AUDIO_STREAM_GST_DEST_PORT, nullptr);
316306

317307
// Add elements to the pipeline
318-
gst_bin_add_many(GST_BIN(pipeline), source, capsfilter, audioconvert, opusenc, rtpopuspay, udpsink, NULL);
308+
gst_bin_add_many(GST_BIN(pipeline), source, capsfilter, audioconvert, opusenc, rtpopuspay, udpsink, nullptr);
319309

320310
// Link elements
321-
if (!gst_element_link_many(source, capsfilter, audioconvert, opusenc, rtpopuspay, udpsink, NULL))
311+
if (!gst_element_link_many(source, capsfilter, audioconvert, opusenc, rtpopuspay, udpsink, nullptr))
322312
{
323313
ChipLogError(Camera, "Elements could not be linked.");
324-
if (pipeline)
325-
gst_object_unref(pipeline);
326-
if (source)
327-
gst_object_unref(source);
328-
if (capsfilter)
329-
gst_object_unref(capsfilter);
330-
if (audioconvert)
331-
gst_object_unref(audioconvert);
332-
if (opusenc)
333-
gst_object_unref(opusenc);
334-
if (rtpopuspay)
335-
gst_object_unref(rtpopuspay);
336-
if (udpsink)
337-
gst_object_unref(udpsink);
314+
315+
// The pipeline will unref all added elements automatically when you unref the pipeline.
316+
gst_object_unref(pipeline);
338317
error = CameraError::ERROR_INIT_FAILED;
339318
return nullptr;
340319
}
@@ -428,18 +407,12 @@ CameraError CameraDevice::StartVideoStream(uint16_t streamID)
428407
return CameraError::ERROR_VIDEO_STREAM_START_FAILED;
429408
}
430409

431-
// Start the pipeline
432-
GstStateChangeReturn result = gst_element_set_state(videoPipeline, GST_STATE_PLAYING);
433-
if (result == GST_STATE_CHANGE_FAILURE)
434-
{
435-
ChipLogError(Camera, "Failed to start video pipeline.");
436-
gst_object_unref(videoPipeline);
437-
it->videoContext = nullptr;
438-
return CameraError::ERROR_VIDEO_STREAM_START_FAILED;
439-
}
410+
ChipLogProgress(Camera, "Starting video stream (id=%u): %u×%u @ %ufps", streamID, it->videoStreamParams.minResolution.width,
411+
it->videoStreamParams.minResolution.height, it->videoStreamParams.minFrameRate);
440412

441413
// Start the pipeline
442-
result = gst_element_set_state(videoPipeline, GST_STATE_PLAYING);
414+
ChipLogProgress(Camera, "Requesting PLAYING …");
415+
GstStateChangeReturn result = gst_element_set_state(videoPipeline, GST_STATE_PLAYING);
443416
if (result == GST_STATE_CHANGE_FAILURE)
444417
{
445418
ChipLogError(Camera, "Failed to start video pipeline.");
@@ -450,7 +423,7 @@ CameraError CameraDevice::StartVideoStream(uint16_t streamID)
450423

451424
// Wait for the pipeline to reach the PLAYING state
452425
GstState state;
453-
gst_element_get_state(videoPipeline, &state, nullptr, GST_CLOCK_TIME_NONE);
426+
gst_element_get_state(videoPipeline, &state, nullptr, 5 * GST_SECOND);
454427
if (state != GST_STATE_PLAYING)
455428
{
456429
ChipLogError(Camera, "Video pipeline did not reach PLAYING state.");

0 commit comments

Comments
 (0)