1 /* GStreamer
2 * Copyright (C) 2020 Julien Isorce <jisorce@oblong.com>
3 *
4 * gstgssrc.c:
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Library General Public License for more details.
15 *
16 * You should have received a copy of the GNU Library General Public
17 * License along with this library; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19 * Boston, MA 02110-1301, USA.
20 */
21
22 /**
23 * SECTION:element-gssrc
24 * @title: gssrc
25 * @see_also: #GstGsSrc
26 *
27 * Read data from a file in a Google Cloud Storage.
28 *
29 * ## Example launch line
30 * ```
31 * gst-launch-1.0 gssrc location=gs://mybucket/myvideo.mkv ! decodebin !
32 * glimagesink
33 * ```
34 * ### Play a video from a Google Cloud Storage.
35 * ```
36 * gst-launch-1.0 gssrc location=gs://mybucket/myvideo.mkv ! decodebin ! navseek
37 * seek-offset=10 ! glimagesink
38 * ```
39 * ### Play a video from a Google Cloud Storage and seek using the keyboard
40 * from the terminal.
41 *
42 * See also: #GstGsSink
43 * Since: 1.20
44 */
45
46 #ifdef HAVE_CONFIG_H
47 #include "config.h"
48 #endif
49
50 #include "gstgscommon.h"
51 #include "gstgssrc.h"
52
53 static GstStaticPadTemplate srctemplate =
54 GST_STATIC_PAD_TEMPLATE("src",
55 GST_PAD_SRC,
56 GST_PAD_ALWAYS,
57 GST_STATIC_CAPS_ANY);
58
59 GST_DEBUG_CATEGORY_STATIC(gst_gs_src_debug);
60 #define GST_CAT_DEFAULT gst_gs_src_debug
61
62 enum { LAST_SIGNAL };
63
64 // https://github.com/googleapis/google-cloud-cpp/issues/2657
65 #define DEFAULT_BLOCKSIZE 3 * 1024 * 1024 / 2
66
67 enum {
68 PROP_0,
69 PROP_LOCATION,
70 PROP_SERVICE_ACCOUNT_EMAIL,
71 PROP_SERVICE_ACCOUNT_CREDENTIALS
72 };
73
74 class GSReadStream;
75
76 struct _GstGsSrc {
77 GstBaseSrc parent;
78
79 std::unique_ptr<google::cloud::storage::Client> gcs_client;
80 std::unique_ptr<GSReadStream> gcs_stream;
81 gchar* uri;
82 gchar* service_account_email;
83 gchar* service_account_credentials;
84 std::string bucket_name;
85 std::string object_name;
86 guint64 read_position;
87 guint64 object_size;
88 };
89
90 static void gst_gs_src_finalize(GObject* object);
91
92 static void gst_gs_src_set_property(GObject* object,
93 guint prop_id,
94 const GValue* value,
95 GParamSpec* pspec);
96 static void gst_gs_src_get_property(GObject* object,
97 guint prop_id,
98 GValue* value,
99 GParamSpec* pspec);
100
101 static gboolean gst_gs_src_start(GstBaseSrc* basesrc);
102 static gboolean gst_gs_src_stop(GstBaseSrc* basesrc);
103
104 static gboolean gst_gs_src_is_seekable(GstBaseSrc* src);
105 static gboolean gst_gs_src_get_size(GstBaseSrc* src, guint64* size);
106 static GstFlowReturn gst_gs_src_fill(GstBaseSrc* src,
107 guint64 offset,
108 guint length,
109 GstBuffer* buf);
110 static gboolean gst_gs_src_query(GstBaseSrc* src, GstQuery* query);
111
112 static void gst_gs_src_uri_handler_init(gpointer g_iface, gpointer iface_data);
113
114 #define _do_init \
115 G_IMPLEMENT_INTERFACE(GST_TYPE_URI_HANDLER, gst_gs_src_uri_handler_init); \
116 GST_DEBUG_CATEGORY_INIT(gst_gs_src_debug, "gssrc", 0, "gssrc element");
117 #define gst_gs_src_parent_class parent_class
118 G_DEFINE_TYPE_WITH_CODE(GstGsSrc, gst_gs_src, GST_TYPE_BASE_SRC, _do_init);
119 GST_ELEMENT_REGISTER_DEFINE(gssrc, "gssrc", GST_RANK_NONE, GST_TYPE_GS_SRC)
120
121 namespace gcs = google::cloud::storage;
122
123 class GSReadStream {
124 public:
GSReadStream(GstGsSrc * src,const std::int64_t start=0,const std::int64_t end=-1)125 GSReadStream(GstGsSrc* src,
126 const std::int64_t start = 0,
127 const std::int64_t end = -1)
128 : gcs_stream_(src->gcs_client->ReadObject(src->bucket_name,
129 src->object_name,
130 gcs::ReadFromOffset(start))) {}
~GSReadStream()131 ~GSReadStream() { gcs_stream_.Close(); }
132
stream()133 gcs::ObjectReadStream& stream() { return gcs_stream_; }
134
135 private:
136 gcs::ObjectReadStream gcs_stream_;
137 };
138
gst_gs_src_class_init(GstGsSrcClass * klass)139 static void gst_gs_src_class_init(GstGsSrcClass* klass) {
140 GObjectClass* gobject_class = G_OBJECT_CLASS(klass);
141 GstElementClass* gstelement_class = GST_ELEMENT_CLASS(klass);
142 GstBaseSrcClass* gstbasesrc_class = GST_BASE_SRC_CLASS(klass);
143
144 gobject_class->set_property = gst_gs_src_set_property;
145 gobject_class->get_property = gst_gs_src_get_property;
146
147 /**
148 * GstGsSink:location:
149 *
150 * Name of the Google Cloud Storage bucket.
151 *
152 * Since: 1.20
153 */
154 g_object_class_install_property(
155 gobject_class, PROP_LOCATION,
156 g_param_spec_string(
157 "location", "File Location", "Location of the file to read", NULL,
158 (GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
159 GST_PARAM_MUTABLE_READY)));
160
161 /**
162 * GstGsSrc:service-account-email:
163 *
164 * Service Account Email to use for credentials.
165 *
166 * Since: 1.20
167 */
168 g_object_class_install_property(
169 gobject_class, PROP_SERVICE_ACCOUNT_EMAIL,
170 g_param_spec_string(
171 "service-account-email", "Service Account Email",
172 "Service Account Email to use for credentials", NULL,
173 (GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
174 GST_PARAM_MUTABLE_READY)));
175
176 /**
177 * GstGsSrc:service-account-credentials:
178 *
179 * Service Account Credentials as a JSON string to use for credentials.
180 *
181 * Since: 1.20
182 */
183 g_object_class_install_property(
184 gobject_class, PROP_SERVICE_ACCOUNT_CREDENTIALS,
185 g_param_spec_string(
186 "service-account-credentials", "Service Account Credentials",
187 "Service Account Credentials as a JSON string to use for credentials",
188 NULL,
189 (GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
190 GST_PARAM_MUTABLE_READY)));
191
192 gobject_class->finalize = gst_gs_src_finalize;
193
194 gst_element_class_set_static_metadata(
195 gstelement_class, "Google Cloud Storage Source", "Source/File",
196 "Read from arbitrary point from a file in a Google Cloud Storage",
197 "Julien Isorce <jisorce@oblong.com>");
198 gst_element_class_add_static_pad_template(gstelement_class, &srctemplate);
199
200 gstbasesrc_class->start = GST_DEBUG_FUNCPTR(gst_gs_src_start);
201 gstbasesrc_class->stop = GST_DEBUG_FUNCPTR(gst_gs_src_stop);
202 gstbasesrc_class->is_seekable = GST_DEBUG_FUNCPTR(gst_gs_src_is_seekable);
203 gstbasesrc_class->get_size = GST_DEBUG_FUNCPTR(gst_gs_src_get_size);
204 gstbasesrc_class->fill = GST_DEBUG_FUNCPTR(gst_gs_src_fill);
205 gstbasesrc_class->query = GST_DEBUG_FUNCPTR(gst_gs_src_query);
206 }
207
gst_gs_src_init(GstGsSrc * src)208 static void gst_gs_src_init(GstGsSrc* src) {
209 src->gcs_stream = nullptr;
210 src->uri = NULL;
211 src->service_account_email = NULL;
212 src->service_account_credentials = NULL;
213 src->read_position = 0;
214 src->object_size = 0;
215
216 gst_base_src_set_blocksize(GST_BASE_SRC(src), DEFAULT_BLOCKSIZE);
217 gst_base_src_set_dynamic_size(GST_BASE_SRC(src), FALSE);
218 gst_base_src_set_live(GST_BASE_SRC(src), FALSE);
219 }
220
gst_gs_src_finalize(GObject * object)221 static void gst_gs_src_finalize(GObject* object) {
222 GstGsSrc* src = GST_GS_SRC(object);
223
224 g_free(src->uri);
225 src->uri = NULL;
226 g_free(src->service_account_email);
227 src->service_account_email = NULL;
228 g_free(src->service_account_credentials);
229 src->service_account_credentials = NULL;
230 src->read_position = 0;
231 src->object_size = 0;
232
233 G_OBJECT_CLASS(parent_class)->finalize(object);
234 }
235
gst_gs_src_set_location(GstGsSrc * src,const gchar * location,GError ** err)236 static gboolean gst_gs_src_set_location(GstGsSrc* src,
237 const gchar* location,
238 GError** err) {
239 GstState state = GST_STATE_NULL;
240 std::string filepath = location;
241 size_t delimiter = std::string::npos;
242
243 // The element must be stopped in order to do this.
244 GST_OBJECT_LOCK(src);
245 state = GST_STATE(src);
246 if (state != GST_STATE_READY && state != GST_STATE_NULL)
247 goto wrong_state;
248 GST_OBJECT_UNLOCK(src);
249
250 g_free(src->uri);
251 src->uri = NULL;
252
253 if (location) {
254 if (g_str_has_prefix(location, "gs://")) {
255 src->uri = g_strdup(location);
256 filepath = filepath.substr(5);
257 } else {
258 src->uri = g_strdup_printf("gs://%s", location);
259 filepath = location;
260 }
261
262 delimiter = filepath.find_first_of('/');
263 if (delimiter == std::string::npos)
264 goto wrong_location;
265
266 std::string bucket_name = filepath.substr(0, delimiter);
267 src->bucket_name = bucket_name;
268 src->object_name = filepath.substr(delimiter + 1);
269
270 GST_INFO_OBJECT(src, "uri is %s", src->uri);
271 GST_INFO_OBJECT(src, "bucket name is %s", src->bucket_name.c_str());
272 GST_INFO_OBJECT(src, "object name is %s", src->object_name.c_str());
273 }
274 g_object_notify(G_OBJECT(src), "location");
275
276 return TRUE;
277
278 // ERROR.
279 wrong_state : {
280 g_warning(
281 "Changing the `location' property on gssrc when a file is open"
282 "is not supported.");
283 if (err)
284 g_set_error(
285 err, GST_URI_ERROR, GST_URI_ERROR_BAD_STATE,
286 "Changing the `location' property on gssrc when a file is open is "
287 "not supported.");
288 GST_OBJECT_UNLOCK(src);
289 return FALSE;
290 }
291 wrong_location : {
292 if (err)
293 g_set_error(err, GST_URI_ERROR, GST_URI_ERROR_BAD_URI,
294 "Failed to find a bucket name");
295 GST_OBJECT_UNLOCK(src);
296 return FALSE;
297 }
298 }
299
gst_gs_src_set_service_account_email(GstGsSrc * src,const gchar * service_account_email)300 static gboolean gst_gs_src_set_service_account_email(
301 GstGsSrc* src,
302 const gchar* service_account_email) {
303 if (GST_STATE(src) == GST_STATE_PLAYING ||
304 GST_STATE(src) == GST_STATE_PAUSED) {
305 GST_WARNING_OBJECT(src,
306 "Setting a new service account email not supported in "
307 "PLAYING or PAUSED state");
308 return FALSE;
309 }
310
311 GST_OBJECT_LOCK(src);
312 g_free(src->service_account_email);
313 src->service_account_email = NULL;
314
315 if (service_account_email)
316 src->service_account_email = g_strdup(service_account_email);
317
318 GST_OBJECT_UNLOCK(src);
319
320 return TRUE;
321 }
322
gst_gs_src_set_service_account_credentials(GstGsSrc * src,const gchar * service_account_credentials)323 static gboolean gst_gs_src_set_service_account_credentials(
324 GstGsSrc* src,
325 const gchar* service_account_credentials) {
326 if (GST_STATE(src) == GST_STATE_PLAYING ||
327 GST_STATE(src) == GST_STATE_PAUSED) {
328 GST_WARNING_OBJECT(
329 src,
330 "Setting a new service account credentials not supported in "
331 "PLAYING or PAUSED state");
332 return FALSE;
333 }
334
335 GST_OBJECT_LOCK(src);
336 g_free(src->service_account_credentials);
337 src->service_account_credentials = NULL;
338
339 if (service_account_credentials)
340 src->service_account_credentials = g_strdup(service_account_credentials);
341
342 GST_OBJECT_UNLOCK(src);
343
344 return TRUE;
345 }
346
gst_gs_src_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)347 static void gst_gs_src_set_property(GObject* object,
348 guint prop_id,
349 const GValue* value,
350 GParamSpec* pspec) {
351 GstGsSrc* src = GST_GS_SRC(object);
352
353 g_return_if_fail(GST_IS_GS_SRC(object));
354
355 switch (prop_id) {
356 case PROP_LOCATION:
357 gst_gs_src_set_location(src, g_value_get_string(value), NULL);
358 break;
359 case PROP_SERVICE_ACCOUNT_EMAIL:
360 gst_gs_src_set_service_account_email(src, g_value_get_string(value));
361 break;
362 case PROP_SERVICE_ACCOUNT_CREDENTIALS:
363 gst_gs_src_set_service_account_credentials(src,
364 g_value_get_string(value));
365 break;
366 default:
367 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
368 break;
369 }
370 }
371
gst_gs_src_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)372 static void gst_gs_src_get_property(GObject* object,
373 guint prop_id,
374 GValue* value,
375 GParamSpec* pspec) {
376 GstGsSrc* src = GST_GS_SRC(object);
377
378 g_return_if_fail(GST_IS_GS_SRC(object));
379
380 switch (prop_id) {
381 case PROP_LOCATION:
382 GST_OBJECT_LOCK(src);
383 g_value_set_string(value, src->uri);
384 GST_OBJECT_UNLOCK(src);
385 break;
386 case PROP_SERVICE_ACCOUNT_EMAIL:
387 GST_OBJECT_LOCK(src);
388 g_value_set_string(value, src->service_account_email);
389 GST_OBJECT_UNLOCK(src);
390 break;
391 case PROP_SERVICE_ACCOUNT_CREDENTIALS:
392 GST_OBJECT_LOCK(src);
393 g_value_set_string(value, src->service_account_credentials);
394 GST_OBJECT_UNLOCK(src);
395 break;
396 default:
397 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
398 break;
399 }
400 }
401
gst_gs_read_stream(GstGsSrc * src,guint8 * data,const guint64 offset,const guint length)402 static gint gst_gs_read_stream(GstGsSrc* src,
403 guint8* data,
404 const guint64 offset,
405 const guint length) {
406 gint gcount = 0;
407 gchar* sdata = reinterpret_cast<gchar*>(data);
408
409 gcs::ObjectReadStream& stream = src->gcs_stream->stream();
410
411 while (!stream.eof()) {
412 stream.read(sdata, length);
413 if (stream.status().ok())
414 break;
415
416 GST_ERROR_OBJECT(src, "Restart after (%s)",
417 stream.status().message().c_str());
418 src->gcs_stream = std::make_unique<GSReadStream>(src, offset);
419 }
420
421 gcount = stream.gcount();
422
423 GST_INFO_OBJECT(src, "Client read %d bytes", gcount);
424
425 return gcount;
426 }
427
gst_gs_src_fill(GstBaseSrc * basesrc,guint64 offset,guint length,GstBuffer * buf)428 static GstFlowReturn gst_gs_src_fill(GstBaseSrc* basesrc,
429 guint64 offset,
430 guint length,
431 GstBuffer* buf) {
432 GstGsSrc* src = GST_GS_SRC(basesrc);
433 guint to_read = 0;
434 guint bytes_read = 0;
435 gint ret = 0;
436 GstMapInfo info = {};
437 guint8* data = NULL;
438
439 if (G_UNLIKELY(offset != (guint64)-1 && src->read_position != offset)) {
440 src->gcs_stream = std::make_unique<GSReadStream>(src, offset);
441 src->read_position = offset;
442 }
443
444 if (!gst_buffer_map(buf, &info, GST_MAP_WRITE))
445 goto buffer_write_fail;
446
447 data = info.data;
448
449 bytes_read = 0;
450 to_read = length;
451 while (to_read > 0) {
452 GST_INFO_OBJECT(src, "Reading %d bytes at offset 0x%" G_GINT64_MODIFIER "x",
453 to_read, offset + bytes_read);
454
455 ret = gst_gs_read_stream(src, data + bytes_read, offset, to_read);
456 if (G_UNLIKELY(ret < 0))
457 goto could_not_read;
458
459 if (G_UNLIKELY(ret == 0)) {
460 // Push any remaining data.
461 if (bytes_read > 0)
462 break;
463 goto eos;
464 }
465
466 to_read -= ret;
467 bytes_read += ret;
468
469 src->read_position += ret;
470 }
471
472 GST_INFO_OBJECT(
473 src, "Read %" G_GUINT32_FORMAT " bytes of %" G_GUINT32_FORMAT " length",
474 bytes_read, length);
475
476 gst_buffer_unmap(buf, &info);
477 if (bytes_read != length)
478 gst_buffer_resize(buf, 0, bytes_read);
479
480 GST_BUFFER_OFFSET(buf) = offset;
481 GST_BUFFER_OFFSET_END(buf) = offset + bytes_read;
482
483 return GST_FLOW_OK;
484
485 // ERROR.
486 could_not_read : {
487 GST_ELEMENT_ERROR(src, RESOURCE, READ, (NULL), GST_ERROR_SYSTEM);
488 gst_buffer_unmap(buf, &info);
489 gst_buffer_resize(buf, 0, 0);
490 return GST_FLOW_ERROR;
491 }
492 eos : {
493 GST_INFO_OBJECT(src, "EOS");
494 gst_buffer_unmap(buf, &info);
495 gst_buffer_resize(buf, 0, 0);
496 return GST_FLOW_EOS;
497 }
498 buffer_write_fail : {
499 GST_ELEMENT_ERROR(src, RESOURCE, WRITE, (NULL), ("Can't write to buffer"));
500 return GST_FLOW_ERROR;
501 }
502 }
503
gst_gs_src_is_seekable(GstBaseSrc * basesrc)504 static gboolean gst_gs_src_is_seekable(GstBaseSrc* basesrc) {
505 return TRUE;
506 }
507
gst_gs_src_get_size(GstBaseSrc * basesrc,guint64 * size)508 static gboolean gst_gs_src_get_size(GstBaseSrc* basesrc, guint64* size) {
509 GstGsSrc* src = GST_GS_SRC(basesrc);
510
511 *size = src->object_size;
512
513 return TRUE;
514 }
515
gst_gs_src_start(GstBaseSrc * basesrc)516 static gboolean gst_gs_src_start(GstBaseSrc* basesrc) {
517 GstGsSrc* src = GST_GS_SRC(basesrc);
518 GError* err = NULL;
519
520 src->read_position = 0;
521 src->object_size = 0;
522
523 if (src->uri == NULL || src->uri[0] == '\0') {
524 GST_ELEMENT_ERROR(src, RESOURCE, NOT_FOUND,
525 ("No uri specified for reading."), (NULL));
526 return FALSE;
527 }
528
529 GST_INFO_OBJECT(src, "Opening file %s", src->uri);
530
531 src->gcs_client = gst_gs_create_client(
532 src->service_account_email, src->service_account_credentials, &err);
533 if (err) {
534 GST_ELEMENT_ERROR(src, RESOURCE, OPEN_READ,
535 ("Could not create client (%s)", err->message),
536 GST_ERROR_SYSTEM);
537 g_clear_error(&err);
538 return FALSE;
539 }
540
541 GST_INFO_OBJECT(src, "Parsed bucket name (%s) and object name (%s)",
542 src->bucket_name.c_str(), src->object_name.c_str());
543
544 google::cloud::StatusOr<gcs::ObjectMetadata> object_metadata =
545 src->gcs_client->GetObjectMetadata(src->bucket_name, src->object_name);
546 if (!object_metadata) {
547 GST_ELEMENT_ERROR(src, RESOURCE, OPEN_READ,
548 ("Could not get object metadata (%s)",
549 object_metadata.status().message().c_str()),
550 GST_ERROR_SYSTEM);
551 return FALSE;
552 }
553
554 src->object_size = object_metadata->size();
555
556 GST_INFO_OBJECT(src, "Object size %" G_GUINT64_FORMAT "\n", src->object_size);
557
558 src->gcs_stream = std::make_unique<GSReadStream>(src);
559
560 return TRUE;
561 }
562
gst_gs_src_stop(GstBaseSrc * basesrc)563 static gboolean gst_gs_src_stop(GstBaseSrc* basesrc) {
564 GstGsSrc* src = GST_GS_SRC(basesrc);
565
566 src->gcs_stream = nullptr;
567 src->read_position = 0;
568 src->object_size = 0;
569
570 return TRUE;
571 }
572
gst_gs_src_query(GstBaseSrc * src,GstQuery * query)573 static gboolean gst_gs_src_query(GstBaseSrc* src, GstQuery* query) {
574 gboolean ret;
575
576 switch (GST_QUERY_TYPE(query)) {
577 case GST_QUERY_SCHEDULING: {
578 // A pushsrc can by default never operate in pull mode override
579 // if you want something different.
580 gst_query_set_scheduling(query, GST_SCHEDULING_FLAG_SEQUENTIAL, 1, -1, 0);
581 gst_query_add_scheduling_mode(query, GST_PAD_MODE_PUSH);
582
583 ret = TRUE;
584 break;
585 }
586 default:
587 ret = GST_BASE_SRC_CLASS(parent_class)->query(src, query);
588 break;
589 }
590 return ret;
591 }
592
gst_gs_src_uri_get_type(GType type)593 static GstURIType gst_gs_src_uri_get_type(GType type) {
594 return GST_URI_SRC;
595 }
596
gst_gs_src_uri_get_protocols(GType type)597 static const gchar* const* gst_gs_src_uri_get_protocols(GType type) {
598 static const gchar* protocols[] = {"gs", NULL};
599
600 return protocols;
601 }
602
gst_gs_src_uri_get_uri(GstURIHandler * handler)603 static gchar* gst_gs_src_uri_get_uri(GstURIHandler* handler) {
604 GstGsSrc* src = GST_GS_SRC(handler);
605
606 return g_strdup(src->uri);
607 }
608
gst_gs_src_uri_set_uri(GstURIHandler * handler,const gchar * uri,GError ** err)609 static gboolean gst_gs_src_uri_set_uri(GstURIHandler* handler,
610 const gchar* uri,
611 GError** err) {
612 GstGsSrc* src = GST_GS_SRC(handler);
613
614 if (strcmp(uri, "gs://") == 0) {
615 // Special case for "gs://" as this is used by some applications
616 // to test with gst_element_make_from_uri if there's an element
617 // that supports the URI protocol.
618 gst_gs_src_set_location(src, NULL, NULL);
619 return TRUE;
620 }
621
622 return gst_gs_src_set_location(src, uri, err);
623 }
624
gst_gs_src_uri_handler_init(gpointer g_iface,gpointer iface_data)625 static void gst_gs_src_uri_handler_init(gpointer g_iface, gpointer iface_data) {
626 GstURIHandlerInterface* iface = (GstURIHandlerInterface*)g_iface;
627
628 iface->get_type = gst_gs_src_uri_get_type;
629 iface->get_protocols = gst_gs_src_uri_get_protocols;
630 iface->get_uri = gst_gs_src_uri_get_uri;
631 iface->set_uri = gst_gs_src_uri_set_uri;
632 }
633