FreeRDP
Loading...
Searching...
No Matches
tsmf_gstreamer.c
1/*
2 * FreeRDP: A Remote Desktop Protocol Implementation
3 * Video Redirection Virtual Channel - GStreamer Decoder
4 *
5 * (C) Copyright 2012 HP Development Company, LLC
6 * (C) Copyright 2014 Thincast Technologies GmbH
7 * (C) Copyright 2014 Armin Novak <armin.novak@thincast.com>
8 *
9 * Licensed under the Apache License, Version 2.0 (the "License");
10 * you may not use this file except in compliance with the License.
11 * You may obtain a copy of the License at
12 *
13 * http://www.apache.org/licenses/LICENSE-2.0
14 *
15 * Unless required by applicable law or agreed to in writing, software
16 * distributed under the License is distributed on an "AS IS" BASIS,
17 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 * See the License for the specific language governing permissions and
19 * limitations under the License.
20 */
21
22#include <freerdp/config.h>
23
24#include <winpr/assert.h>
25
26#include <fcntl.h>
27#include <stdio.h>
28#include <stdlib.h>
29#include <string.h>
30#include <unistd.h>
31
32#include <winpr/string.h>
33#include <winpr/platform.h>
34
35#include <gst/gst.h>
36
37#include <gst/app/gstappsrc.h>
38#include <gst/app/gstappsink.h>
39
40#include "tsmf_constants.h"
41#include "tsmf_decoder.h"
42#include "tsmf_platform.h"
43
44/* 1 second = 10,000,000 100ns units*/
45#define SEEK_TOLERANCE 10 * 1000 * 1000
46
47static BOOL tsmf_gstreamer_pipeline_build(TSMFGstreamerDecoder* mdecoder);
48static void tsmf_gstreamer_clean_up(TSMFGstreamerDecoder* mdecoder);
49static int tsmf_gstreamer_pipeline_set_state(TSMFGstreamerDecoder* mdecoder,
50 GstState desired_state);
51static BOOL tsmf_gstreamer_buffer_level(ITSMFDecoder* decoder);
52
53static const char* get_type(TSMFGstreamerDecoder* mdecoder)
54{
55 if (!mdecoder)
56 return nullptr;
57
58 switch (mdecoder->media_type)
59 {
60 case TSMF_MAJOR_TYPE_VIDEO:
61 return "VIDEO";
62 case TSMF_MAJOR_TYPE_AUDIO:
63 return "AUDIO";
64 default:
65 return "UNKNOWN";
66 }
67}
68
69static void cb_child_added(GstChildProxy* child_proxy, GObject* object,
70 TSMFGstreamerDecoder* mdecoder)
71{
72 DEBUG_TSMF("NAME: %s", G_OBJECT_TYPE_NAME(object));
73
74 if (!g_strcmp0(G_OBJECT_TYPE_NAME(object), "GstXvImageSink") ||
75 !g_strcmp0(G_OBJECT_TYPE_NAME(object), "GstXImageSink") ||
76 !g_strcmp0(G_OBJECT_TYPE_NAME(object), "GstFluVAAutoSink"))
77 {
78 gst_base_sink_set_max_lateness((GstBaseSink*)object, 10000000); /* nanoseconds */
79 g_object_set(G_OBJECT(object), "sync", TRUE, nullptr); /* synchronize on the clock */
80 g_object_set(G_OBJECT(object), "async", TRUE, nullptr); /* no async state changes */
81 }
82
83 else if (!g_strcmp0(G_OBJECT_TYPE_NAME(object), "GstAlsaSink") ||
84 !g_strcmp0(G_OBJECT_TYPE_NAME(object), "GstPulseSink"))
85 {
86 gst_base_sink_set_max_lateness((GstBaseSink*)object, 10000000); /* nanoseconds */
87 g_object_set(G_OBJECT(object), "slave-method", 1, nullptr);
88 g_object_set(G_OBJECT(object), "buffer-time", (gint64)20000, nullptr); /* microseconds */
89 g_object_set(G_OBJECT(object), "drift-tolerance", (gint64)20000,
90 nullptr); /* microseconds */
91 g_object_set(G_OBJECT(object), "latency-time", (gint64)10000, nullptr); /* microseconds */
92 g_object_set(G_OBJECT(object), "sync", TRUE, nullptr); /* synchronize on the clock */
93 g_object_set(G_OBJECT(object), "async", TRUE, nullptr); /* no async state changes */
94 }
95}
96
97static void tsmf_gstreamer_enough_data(GstAppSrc* src, gpointer user_data)
98{
99 TSMFGstreamerDecoder* mdecoder = user_data;
100 (void)mdecoder;
101 DEBUG_TSMF("%s", get_type(mdecoder));
102}
103
104static void tsmf_gstreamer_need_data(GstAppSrc* src, guint length, gpointer user_data)
105{
106 TSMFGstreamerDecoder* mdecoder = user_data;
107 (void)mdecoder;
108 DEBUG_TSMF("%s length=%u", get_type(mdecoder), length);
109}
110
111static gboolean tsmf_gstreamer_seek_data(GstAppSrc* src, guint64 offset, gpointer user_data)
112{
113 TSMFGstreamerDecoder* mdecoder = user_data;
114 (void)mdecoder;
115 DEBUG_TSMF("%s offset=%" PRIu64 "", get_type(mdecoder), offset);
116
117 return TRUE;
118}
119
120static BOOL tsmf_gstreamer_change_volume(ITSMFDecoder* decoder, UINT32 newVolume, UINT32 muted)
121{
122 TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder;
123
124 if (!mdecoder || !mdecoder->pipe)
125 return TRUE;
126
127 if (mdecoder->media_type == TSMF_MAJOR_TYPE_VIDEO)
128 return TRUE;
129
130 mdecoder->gstMuted = (BOOL)muted;
131 DEBUG_TSMF("mute=[%" PRId32 "]", mdecoder->gstMuted);
132 mdecoder->gstVolume = (double)newVolume / (double)10000;
133 DEBUG_TSMF("gst_new_vol=[%f]", mdecoder->gstVolume);
134
135 if (!mdecoder->volume)
136 return TRUE;
137
138 if (!G_IS_OBJECT(mdecoder->volume))
139 return TRUE;
140
141 g_object_set(mdecoder->volume, "mute", mdecoder->gstMuted, nullptr);
142 g_object_set(mdecoder->volume, "volume", mdecoder->gstVolume, nullptr);
143
144 return TRUE;
145}
146
147static inline GstClockTime tsmf_gstreamer_timestamp_ms_to_gst(UINT64 ms_timestamp)
148{
149 /*
150 * Convert Microsoft 100ns timestamps to Gstreamer 1ns units.
151 */
152 return (GstClockTime)(ms_timestamp * 100);
153}
154
155int tsmf_gstreamer_pipeline_set_state(TSMFGstreamerDecoder* mdecoder, GstState desired_state)
156{
157 GstStateChangeReturn state_change;
158 const char* name;
159 const char* sname = get_type(mdecoder);
160
161 if (!mdecoder)
162 return 0;
163
164 if (!mdecoder->pipe)
165 return 0; /* Just in case this is called during startup or shutdown when we don't expect it
166 */
167
168 if (desired_state == mdecoder->state)
169 return 0; /* Redundant request - Nothing to do */
170
171 name = gst_element_state_get_name(desired_state); /* For debug */
172 DEBUG_TSMF("%s to %s", sname, name);
173 state_change = gst_element_set_state(mdecoder->pipe, desired_state);
174
175 if (state_change == GST_STATE_CHANGE_FAILURE)
176 {
177 WLog_ERR(TAG, "%s: (%s) GST_STATE_CHANGE_FAILURE.", sname, name);
178 }
179 else if (state_change == GST_STATE_CHANGE_ASYNC)
180 {
181 WLog_ERR(TAG, "%s: (%s) GST_STATE_CHANGE_ASYNC.", sname, name);
182 mdecoder->state = desired_state;
183 }
184 else
185 {
186 mdecoder->state = desired_state;
187 }
188
189 return 0;
190}
191
192static GstBuffer* tsmf_get_buffer_from_data(const void* raw_data, gsize size)
193{
194 GstBuffer* buffer;
195 gpointer data;
196
197 if (!raw_data)
198 return nullptr;
199
200 if (size < 1)
201 return nullptr;
202
203 data = g_malloc(size);
204
205 if (!data)
206 {
207 WLog_ERR(TAG, "Could not allocate %" G_GSIZE_FORMAT " bytes of data.", size);
208 return nullptr;
209 }
210
211 CopyMemory(data, raw_data, size);
212
213#if GST_VERSION_MAJOR > 0
214 buffer = gst_buffer_new_wrapped(data, size);
215#else
216 buffer = gst_buffer_new();
217
218 if (!buffer)
219 {
220 WLog_ERR(TAG, "Could not create GstBuffer");
221 free(data);
222 return nullptr;
223 }
224
225 GST_BUFFER_MALLOCDATA(buffer) = data;
226 GST_BUFFER_SIZE(buffer) = size;
227 GST_BUFFER_DATA(buffer) = GST_BUFFER_MALLOCDATA(buffer);
228#endif
229
230 return buffer;
231}
232
233static BOOL tsmf_gstreamer_set_format(ITSMFDecoder* decoder, TS_AM_MEDIA_TYPE* media_type)
234{
235 TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder;
236
237 if (!mdecoder)
238 return FALSE;
239
240 DEBUG_TSMF("");
241
242 switch (media_type->MajorType)
243 {
244 case TSMF_MAJOR_TYPE_VIDEO:
245 mdecoder->media_type = TSMF_MAJOR_TYPE_VIDEO;
246 break;
247 case TSMF_MAJOR_TYPE_AUDIO:
248 mdecoder->media_type = TSMF_MAJOR_TYPE_AUDIO;
249 break;
250 default:
251 return FALSE;
252 }
253
254 switch (media_type->SubType)
255 {
256 case TSMF_SUB_TYPE_WVC1:
257 mdecoder->gst_caps = gst_caps_new_simple(
258 "video/x-wmv", "bitrate", G_TYPE_UINT, media_type->BitRate, "width", G_TYPE_INT,
259 media_type->Width, "height", G_TYPE_INT, media_type->Height, "wmvversion",
260 G_TYPE_INT, 3,
261#if GST_VERSION_MAJOR > 0
262 "format", G_TYPE_STRING, "WVC1",
263#else
264 "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC('W', 'V', 'C', '1'),
265#endif
266 "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator,
267 media_type->SamplesPerSecond.Denominator, "pixel-aspect-ratio", GST_TYPE_FRACTION,
268 1, 1, nullptr);
269 break;
270 case TSMF_SUB_TYPE_MP4S:
271 mdecoder->gst_caps = gst_caps_new_simple(
272 "video/x-divx", "divxversion", G_TYPE_INT, 5, "bitrate", G_TYPE_UINT,
273 media_type->BitRate, "width", G_TYPE_INT, media_type->Width, "height", G_TYPE_INT,
274 media_type->Height,
275#if GST_VERSION_MAJOR > 0
276 "format", G_TYPE_STRING, "MP42",
277#else
278 "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC('M', 'P', '4', '2'),
279#endif
280 "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator,
281 media_type->SamplesPerSecond.Denominator, nullptr);
282 break;
283 case TSMF_SUB_TYPE_MP42:
284 mdecoder->gst_caps = gst_caps_new_simple(
285 "video/x-msmpeg", "msmpegversion", G_TYPE_INT, 42, "bitrate", G_TYPE_UINT,
286 media_type->BitRate, "width", G_TYPE_INT, media_type->Width, "height", G_TYPE_INT,
287 media_type->Height,
288#if GST_VERSION_MAJOR > 0
289 "format", G_TYPE_STRING, "MP42",
290#else
291 "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC('M', 'P', '4', '2'),
292#endif
293 "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator,
294 media_type->SamplesPerSecond.Denominator, nullptr);
295 break;
296 case TSMF_SUB_TYPE_MP43:
297 mdecoder->gst_caps = gst_caps_new_simple(
298 "video/x-msmpeg", "msmpegversion", G_TYPE_INT, 43, "bitrate", G_TYPE_UINT,
299 media_type->BitRate, "width", G_TYPE_INT, media_type->Width, "height", G_TYPE_INT,
300 media_type->Height,
301#if GST_VERSION_MAJOR > 0
302 "format", G_TYPE_STRING, "MP43",
303#else
304 "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC('M', 'P', '4', '3'),
305#endif
306 "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator,
307 media_type->SamplesPerSecond.Denominator, nullptr);
308 break;
309 case TSMF_SUB_TYPE_M4S2:
310 mdecoder->gst_caps = gst_caps_new_simple(
311 "video/mpeg", "mpegversion", G_TYPE_INT, 4, "width", G_TYPE_INT, media_type->Width,
312 "height", G_TYPE_INT, media_type->Height,
313#if GST_VERSION_MAJOR > 0
314 "format", G_TYPE_STRING, "M4S2",
315#else
316 "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC('M', '4', 'S', '2'),
317#endif
318 "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator,
319 media_type->SamplesPerSecond.Denominator, nullptr);
320 break;
321 case TSMF_SUB_TYPE_WMA9:
322 mdecoder->gst_caps = gst_caps_new_simple(
323 "audio/x-wma", "wmaversion", G_TYPE_INT, 3, "rate", G_TYPE_INT,
324 media_type->SamplesPerSecond.Numerator, "channels", G_TYPE_INT,
325 media_type->Channels, "bitrate", G_TYPE_INT, media_type->BitRate, "depth",
326 G_TYPE_INT, media_type->BitsPerSample, "width", G_TYPE_INT,
327 media_type->BitsPerSample, "block_align", G_TYPE_INT, media_type->BlockAlign,
328 nullptr);
329 break;
330 case TSMF_SUB_TYPE_WMA1:
331 mdecoder->gst_caps = gst_caps_new_simple(
332 "audio/x-wma", "wmaversion", G_TYPE_INT, 1, "rate", G_TYPE_INT,
333 media_type->SamplesPerSecond.Numerator, "channels", G_TYPE_INT,
334 media_type->Channels, "bitrate", G_TYPE_INT, media_type->BitRate, "depth",
335 G_TYPE_INT, media_type->BitsPerSample, "width", G_TYPE_INT,
336 media_type->BitsPerSample, "block_align", G_TYPE_INT, media_type->BlockAlign,
337 nullptr);
338 break;
339 case TSMF_SUB_TYPE_WMA2:
340 mdecoder->gst_caps = gst_caps_new_simple(
341 "audio/x-wma", "wmaversion", G_TYPE_INT, 2, "rate", G_TYPE_INT,
342 media_type->SamplesPerSecond.Numerator, "channels", G_TYPE_INT,
343 media_type->Channels, "bitrate", G_TYPE_INT, media_type->BitRate, "depth",
344 G_TYPE_INT, media_type->BitsPerSample, "width", G_TYPE_INT,
345 media_type->BitsPerSample, "block_align", G_TYPE_INT, media_type->BlockAlign,
346 nullptr);
347 break;
348 case TSMF_SUB_TYPE_MP3:
349 mdecoder->gst_caps =
350 gst_caps_new_simple("audio/mpeg", "mpegversion", G_TYPE_INT, 1, "layer", G_TYPE_INT,
351 3, "rate", G_TYPE_INT, media_type->SamplesPerSecond.Numerator,
352 "channels", G_TYPE_INT, media_type->Channels, nullptr);
353 break;
354 case TSMF_SUB_TYPE_WMV1:
355 mdecoder->gst_caps = gst_caps_new_simple(
356 "video/x-wmv", "bitrate", G_TYPE_UINT, media_type->BitRate, "width", G_TYPE_INT,
357 media_type->Width, "height", G_TYPE_INT, media_type->Height, "wmvversion",
358 G_TYPE_INT, 1,
359#if GST_VERSION_MAJOR > 0
360 "format", G_TYPE_STRING, "WMV1",
361#else
362 "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC('W', 'M', 'V', '1'),
363#endif
364 "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator,
365 media_type->SamplesPerSecond.Denominator, nullptr);
366 break;
367 case TSMF_SUB_TYPE_WMV2:
368 mdecoder->gst_caps = gst_caps_new_simple(
369 "video/x-wmv", "width", G_TYPE_INT, media_type->Width, "height", G_TYPE_INT,
370 media_type->Height, "wmvversion", G_TYPE_INT, 2,
371#if GST_VERSION_MAJOR > 0
372 "format", G_TYPE_STRING, "WMV2",
373#else
374 "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC('W', 'M', 'V', '2'),
375#endif
376 "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator,
377 media_type->SamplesPerSecond.Denominator, "pixel-aspect-ratio", GST_TYPE_FRACTION,
378 1, 1, nullptr);
379 break;
380 case TSMF_SUB_TYPE_WMV3:
381 mdecoder->gst_caps = gst_caps_new_simple(
382 "video/x-wmv", "bitrate", G_TYPE_UINT, media_type->BitRate, "width", G_TYPE_INT,
383 media_type->Width, "height", G_TYPE_INT, media_type->Height, "wmvversion",
384 G_TYPE_INT, 3,
385#if GST_VERSION_MAJOR > 0
386 "format", G_TYPE_STRING, "WMV3",
387#else
388 "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC('W', 'M', 'V', '3'),
389#endif
390 "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator,
391 media_type->SamplesPerSecond.Denominator, "pixel-aspect-ratio", GST_TYPE_FRACTION,
392 1, 1, nullptr);
393 break;
394 case TSMF_SUB_TYPE_AVC1:
395 case TSMF_SUB_TYPE_H264:
396 mdecoder->gst_caps = gst_caps_new_simple(
397 "video/x-h264", "width", G_TYPE_INT, media_type->Width, "height", G_TYPE_INT,
398 media_type->Height, "framerate", GST_TYPE_FRACTION,
399 media_type->SamplesPerSecond.Numerator, media_type->SamplesPerSecond.Denominator,
400 "pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1, "stream-format", G_TYPE_STRING,
401 "byte-stream", "alignment", G_TYPE_STRING, "nal", nullptr);
402 break;
403 case TSMF_SUB_TYPE_AC3:
404 mdecoder->gst_caps = gst_caps_new_simple(
405 "audio/x-ac3", "rate", G_TYPE_INT, media_type->SamplesPerSecond.Numerator,
406 "channels", G_TYPE_INT, media_type->Channels, nullptr);
407 break;
408 case TSMF_SUB_TYPE_AAC:
409
410 /* For AAC the pFormat is a HEAACWAVEINFO struct, and the codec data
411 is at the end of it. See
412 http://msdn.microsoft.com/en-us/library/dd757806.aspx */
413 if (media_type->ExtraData)
414 {
415 if (media_type->ExtraDataSize < 12)
416 return FALSE;
417 media_type->ExtraData += 12;
418 media_type->ExtraDataSize -= 12;
419 }
420
421 mdecoder->gst_caps = gst_caps_new_simple(
422 "audio/mpeg", "rate", G_TYPE_INT, media_type->SamplesPerSecond.Numerator,
423 "channels", G_TYPE_INT, media_type->Channels, "mpegversion", G_TYPE_INT, 4,
424 "framed", G_TYPE_BOOLEAN, TRUE, "stream-format", G_TYPE_STRING, "raw", nullptr);
425 break;
426 case TSMF_SUB_TYPE_MP1A:
427 mdecoder->gst_caps =
428 gst_caps_new_simple("audio/mpeg", "mpegversion", G_TYPE_INT, 1, "channels",
429 G_TYPE_INT, media_type->Channels, nullptr);
430 break;
431 case TSMF_SUB_TYPE_MP1V:
432 mdecoder->gst_caps =
433 gst_caps_new_simple("video/mpeg", "mpegversion", G_TYPE_INT, 1, "width", G_TYPE_INT,
434 media_type->Width, "height", G_TYPE_INT, media_type->Height,
435 "systemstream", G_TYPE_BOOLEAN, FALSE, nullptr);
436 break;
437 case TSMF_SUB_TYPE_YUY2:
438#if GST_VERSION_MAJOR > 0
439 mdecoder->gst_caps = gst_caps_new_simple(
440 "video/x-raw", "format", G_TYPE_STRING, "YUY2", "width", G_TYPE_INT,
441 media_type->Width, "height", G_TYPE_INT, media_type->Height, nullptr);
442#else
443 mdecoder->gst_caps = gst_caps_new_simple(
444 "video/x-raw-yuv", "format", G_TYPE_STRING, "YUY2", "width", G_TYPE_INT,
445 media_type->Width, "height", G_TYPE_INT, media_type->Height, "framerate",
446 GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator,
447 media_type->SamplesPerSecond.Denominator, nullptr);
448#endif
449 break;
450 case TSMF_SUB_TYPE_MP2V:
451 mdecoder->gst_caps =
452 gst_caps_new_simple("video/mpeg", "mpegversion", G_TYPE_INT, 2, "systemstream",
453 G_TYPE_BOOLEAN, FALSE, nullptr);
454 break;
455 case TSMF_SUB_TYPE_MP2A:
456 mdecoder->gst_caps =
457 gst_caps_new_simple("audio/mpeg", "mpegversion", G_TYPE_INT, 1, "rate", G_TYPE_INT,
458 media_type->SamplesPerSecond.Numerator, "channels", G_TYPE_INT,
459 media_type->Channels, nullptr);
460 break;
461 case TSMF_SUB_TYPE_FLAC:
462 mdecoder->gst_caps = gst_caps_new_simple("audio/x-flac", "", nullptr);
463 break;
464 default:
465 WLog_ERR(TAG, "unknown format:(%d).", media_type->SubType);
466 return FALSE;
467 }
468
469 if (media_type->ExtraDataSize > 0)
470 {
471 GstBuffer* buffer;
472 DEBUG_TSMF("Extra data available (%" PRIu32 ")", media_type->ExtraDataSize);
473 buffer = tsmf_get_buffer_from_data(media_type->ExtraData, media_type->ExtraDataSize);
474
475 if (!buffer)
476 {
477 WLog_ERR(TAG, "could not allocate GstBuffer!");
478 return FALSE;
479 }
480
481 gst_caps_set_simple(mdecoder->gst_caps, "codec_data", GST_TYPE_BUFFER, buffer, nullptr);
482 }
483
484 DEBUG_TSMF("%p format '%s'", (void*)mdecoder, gst_caps_to_string(mdecoder->gst_caps));
485 tsmf_platform_set_format(mdecoder);
486
487 /* Create the pipeline... */
488 if (!tsmf_gstreamer_pipeline_build(mdecoder))
489 return FALSE;
490
491 return TRUE;
492}
493
494void tsmf_gstreamer_clean_up(TSMFGstreamerDecoder* mdecoder)
495{
496 if (!mdecoder || !mdecoder->pipe)
497 return;
498
499 if (mdecoder->pipe && GST_OBJECT_REFCOUNT_VALUE(mdecoder->pipe) > 0)
500 {
501 tsmf_gstreamer_pipeline_set_state(mdecoder, GST_STATE_NULL);
502 gst_object_unref(mdecoder->pipe);
503 }
504
505 mdecoder->ready = FALSE;
506 mdecoder->paused = FALSE;
507
508 mdecoder->pipe = nullptr;
509 mdecoder->src = nullptr;
510 mdecoder->queue = nullptr;
511}
512
513BOOL tsmf_gstreamer_pipeline_build(TSMFGstreamerDecoder* mdecoder)
514{
515#if GST_VERSION_MAJOR > 0
516 const char* video =
517 "appsrc name=videosource ! queue2 name=videoqueue ! decodebin name=videodecoder !";
518 const char* audio =
519 "appsrc name=audiosource ! queue2 name=audioqueue ! decodebin name=audiodecoder ! "
520 "audioconvert ! audiorate ! audioresample ! volume name=audiovolume !";
521#else
522 const char* video =
523 "appsrc name=videosource ! queue2 name=videoqueue ! decodebin2 name=videodecoder !";
524 const char* audio =
525 "appsrc name=audiosource ! queue2 name=audioqueue ! decodebin2 name=audiodecoder ! "
526 "audioconvert ! audiorate ! audioresample ! volume name=audiovolume !";
527#endif
528 char pipeline[1024];
529
530 if (!mdecoder)
531 return FALSE;
532
533 /* TODO: Construction of the pipeline from a string allows easy overwrite with arguments.
534 * The only fixed elements necessary are appsrc and the volume element for audio streams.
535 * The rest could easily be provided in gstreamer pipeline notation from command line. */
536 if (mdecoder->media_type == TSMF_MAJOR_TYPE_VIDEO)
537 {
538 if (sprintf_s(pipeline, sizeof(pipeline), "%s %s name=videosink", video,
539 tsmf_platform_get_video_sink()) < 0)
540 return FALSE;
541 }
542 else
543 {
544 if (sprintf_s(pipeline, sizeof(pipeline), "%s %s name=audiosink", audio,
545 tsmf_platform_get_audio_sink()) < 0)
546 return FALSE;
547 }
548
549 DEBUG_TSMF("pipeline=%s", pipeline);
550 mdecoder->pipe = gst_parse_launch(pipeline, nullptr);
551
552 if (!mdecoder->pipe)
553 {
554 WLog_ERR(TAG, "Failed to create new pipe");
555 return FALSE;
556 }
557
558 if (mdecoder->media_type == TSMF_MAJOR_TYPE_VIDEO)
559 mdecoder->src = gst_bin_get_by_name(GST_BIN(mdecoder->pipe), "videosource");
560 else
561 mdecoder->src = gst_bin_get_by_name(GST_BIN(mdecoder->pipe), "audiosource");
562
563 if (!mdecoder->src)
564 {
565 WLog_ERR(TAG, "Failed to get appsrc");
566 return FALSE;
567 }
568
569 if (mdecoder->media_type == TSMF_MAJOR_TYPE_VIDEO)
570 mdecoder->queue = gst_bin_get_by_name(GST_BIN(mdecoder->pipe), "videoqueue");
571 else
572 mdecoder->queue = gst_bin_get_by_name(GST_BIN(mdecoder->pipe), "audioqueue");
573
574 if (!mdecoder->queue)
575 {
576 WLog_ERR(TAG, "Failed to get queue");
577 return FALSE;
578 }
579
580 if (mdecoder->media_type == TSMF_MAJOR_TYPE_VIDEO)
581 mdecoder->outsink = gst_bin_get_by_name(GST_BIN(mdecoder->pipe), "videosink");
582 else
583 mdecoder->outsink = gst_bin_get_by_name(GST_BIN(mdecoder->pipe), "audiosink");
584
585 if (!mdecoder->outsink)
586 {
587 WLog_ERR(TAG, "Failed to get sink");
588 return FALSE;
589 }
590
591 g_signal_connect(mdecoder->outsink, "child-added", G_CALLBACK(cb_child_added), mdecoder);
592
593 if (mdecoder->media_type == TSMF_MAJOR_TYPE_AUDIO)
594 {
595 mdecoder->volume = gst_bin_get_by_name(GST_BIN(mdecoder->pipe), "audiovolume");
596
597 if (!mdecoder->volume)
598 {
599 WLog_ERR(TAG, "Failed to get volume");
600 return FALSE;
601 }
602
603 tsmf_gstreamer_change_volume((ITSMFDecoder*)mdecoder, mdecoder->gstVolume * ((double)10000),
604 mdecoder->gstMuted);
605 }
606
607 tsmf_platform_register_handler(mdecoder);
608 /* AppSrc settings */
609 GstAppSrcCallbacks callbacks = {
610 tsmf_gstreamer_need_data, tsmf_gstreamer_enough_data, tsmf_gstreamer_seek_data, { nullptr }
611 };
612 g_object_set(mdecoder->src, "format", GST_FORMAT_TIME, nullptr);
613 g_object_set(mdecoder->src, "is-live", FALSE, nullptr);
614 g_object_set(mdecoder->src, "block", FALSE, nullptr);
615 g_object_set(mdecoder->src, "blocksize", 1024, nullptr);
616 gst_app_src_set_caps((GstAppSrc*)mdecoder->src, mdecoder->gst_caps);
617 gst_app_src_set_callbacks((GstAppSrc*)mdecoder->src, &callbacks, mdecoder, nullptr);
618 gst_app_src_set_stream_type((GstAppSrc*)mdecoder->src, GST_APP_STREAM_TYPE_SEEKABLE);
619 gst_app_src_set_latency((GstAppSrc*)mdecoder->src, 0, -1);
620 gst_app_src_set_max_bytes((GstAppSrc*)mdecoder->src, (guint64)0); // unlimited
621 g_object_set(G_OBJECT(mdecoder->queue), "use-buffering", FALSE, nullptr);
622 g_object_set(G_OBJECT(mdecoder->queue), "use-rate-estimate", FALSE, nullptr);
623 g_object_set(G_OBJECT(mdecoder->queue), "max-size-buffers", 0, nullptr);
624 g_object_set(G_OBJECT(mdecoder->queue), "max-size-bytes", 0, nullptr);
625 g_object_set(G_OBJECT(mdecoder->queue), "max-size-time", (guint64)0, nullptr);
626
627 /* Only set these properties if not an autosink, otherwise we will set properties when real
628 * sinks are added */
629 if (!g_strcmp0(G_OBJECT_TYPE_NAME(mdecoder->outsink), "GstAutoVideoSink") &&
630 !g_strcmp0(G_OBJECT_TYPE_NAME(mdecoder->outsink), "GstAutoAudioSink"))
631 {
632 if (mdecoder->media_type == TSMF_MAJOR_TYPE_VIDEO)
633 {
634 gst_base_sink_set_max_lateness((GstBaseSink*)mdecoder->outsink,
635 10000000); /* nanoseconds */
636 }
637 else
638 {
639 gst_base_sink_set_max_lateness((GstBaseSink*)mdecoder->outsink,
640 10000000); /* nanoseconds */
641 g_object_set(G_OBJECT(mdecoder->outsink), "buffer-time", (gint64)20000,
642 nullptr); /* microseconds */
643 g_object_set(G_OBJECT(mdecoder->outsink), "drift-tolerance", (gint64)20000,
644 nullptr); /* microseconds */
645 g_object_set(G_OBJECT(mdecoder->outsink), "latency-time", (gint64)10000,
646 nullptr); /* microseconds */
647 g_object_set(G_OBJECT(mdecoder->outsink), "slave-method", 1, nullptr);
648 }
649 g_object_set(G_OBJECT(mdecoder->outsink), "sync", TRUE,
650 nullptr); /* synchronize on the clock */
651 g_object_set(G_OBJECT(mdecoder->outsink), "async", TRUE,
652 nullptr); /* no async state changes */
653 }
654
655 tsmf_window_create(mdecoder);
656 tsmf_gstreamer_pipeline_set_state(mdecoder, GST_STATE_READY);
657 tsmf_gstreamer_pipeline_set_state(mdecoder, GST_STATE_PLAYING);
658 mdecoder->pipeline_start_time_valid = 0;
659 mdecoder->shutdown = 0;
660 mdecoder->paused = FALSE;
661
662 GST_DEBUG_BIN_TO_DOT_FILE(GST_BIN(mdecoder->pipe), GST_DEBUG_GRAPH_SHOW_ALL,
663 get_type(mdecoder));
664
665 return TRUE;
666}
667
668static BOOL tsmf_gstreamer_decodeEx(ITSMFDecoder* decoder, const BYTE* data, UINT32 data_size,
669 UINT32 extensions, UINT64 start_time, UINT64 end_time,
670 UINT64 duration)
671{
672 GstBuffer* gst_buf;
673 TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder;
674 UINT64 sample_time = tsmf_gstreamer_timestamp_ms_to_gst(start_time);
675 BOOL useTimestamps = TRUE;
676
677 if (!mdecoder)
678 {
679 WLog_ERR(TAG, "Decoder not initialized!");
680 return FALSE;
681 }
682
683 /*
684 * This function is always called from a stream-specific thread.
685 * It should be alright to block here if necessary.
686 * We don't expect to block here often, since the pipeline should
687 * have more than enough buffering.
688 */
689 DEBUG_TSMF(
690 "%s. Start:(%" PRIu64 ") End:(%" PRIu64 ") Duration:(%" PRIu64 ") Last Start:(%" PRIu64 ")",
691 get_type(mdecoder), start_time, end_time, duration, mdecoder->last_sample_start_time);
692
693 if (mdecoder->shutdown)
694 {
695 WLog_ERR(TAG, "decodeEx called on shutdown decoder");
696 return TRUE;
697 }
698
699 if (mdecoder->gst_caps == nullptr)
700 {
701 WLog_ERR(TAG, "tsmf_gstreamer_set_format not called or invalid format.");
702 return FALSE;
703 }
704
705 if (!mdecoder->pipe)
706 tsmf_gstreamer_pipeline_build(mdecoder);
707
708 if (!mdecoder->src)
709 {
710 WLog_ERR(
711 TAG,
712 "failed to construct pipeline correctly. Unable to push buffer to source element.");
713 return FALSE;
714 }
715
716 gst_buf = tsmf_get_buffer_from_data(data, data_size);
717
718 if (gst_buf == nullptr)
719 {
720 WLog_ERR(TAG, "tsmf_get_buffer_from_data(%p, %" PRIu32 ") failed.", (void*)data, data_size);
721 return FALSE;
722 }
723
724 /* Relative timestamping will sometimes be set to 0
725 * so we ignore these timestamps just to be safe(bit 8)
726 */
727 if (extensions & 0x00000080)
728 {
729 DEBUG_TSMF("Ignoring the timestamps - relative - bit 8");
730 useTimestamps = FALSE;
731 }
732
733 /* If no timestamps exist then we don't want to look at the timestamp values (bit 7) */
734 if (extensions & 0x00000040)
735 {
736 DEBUG_TSMF("Ignoring the timestamps - none - bit 7");
737 useTimestamps = FALSE;
738 }
739
740 /* If performing a seek */
741 if (mdecoder->seeking)
742 {
743 mdecoder->seeking = FALSE;
744 tsmf_gstreamer_pipeline_set_state(mdecoder, GST_STATE_PAUSED);
745 mdecoder->pipeline_start_time_valid = 0;
746 }
747
748 if (mdecoder->pipeline_start_time_valid)
749 {
750 DEBUG_TSMF("%s start time %" PRIu64 "", get_type(mdecoder), start_time);
751
752 /* Adjusted the condition for a seek to be based on start time only
753 * WMV1 and WMV2 files in particular have bad end time and duration values
754 * there seems to be no real side effects of just using the start time instead
755 */
756 UINT64 minTime = mdecoder->last_sample_start_time - (UINT64)SEEK_TOLERANCE;
757 UINT64 maxTime = mdecoder->last_sample_start_time + (UINT64)SEEK_TOLERANCE;
758
759 /* Make sure the minTime stops at 0 , should we be at the beginning of the stream */
760 if (mdecoder->last_sample_start_time < (UINT64)SEEK_TOLERANCE)
761 minTime = 0;
762
763 /* If the start_time is valid and different from the previous start time by more than the
764 * seek tolerance, then we have a seek condition */
765 if (((start_time > maxTime) || (start_time < minTime)) && useTimestamps)
766 {
767 DEBUG_TSMF("tsmf_gstreamer_decodeEx: start_time=[%" PRIu64
768 "] > last_sample_start_time=[%" PRIu64 "] OR ",
769 start_time, mdecoder->last_sample_start_time);
770 DEBUG_TSMF("tsmf_gstreamer_decodeEx: start_time=[%" PRIu64
771 "] < last_sample_start_time=[%" PRIu64 "] with",
772 start_time, mdecoder->last_sample_start_time);
773 DEBUG_TSMF(
774 "tsmf_gstreamer_decodeEX: a tolerance of more than [%lu] from the last sample",
775 SEEK_TOLERANCE);
776 DEBUG_TSMF("tsmf_gstreamer_decodeEX: minTime=[%" PRIu64 "] maxTime=[%" PRIu64 "]",
777 minTime, maxTime);
778
779 mdecoder->seeking = TRUE;
780
781 /* since we can't make the gstreamer pipeline jump to the new start time after a seek -
782 * we just maintain an offset between realtime and gstreamer time
783 */
784 mdecoder->seek_offset = start_time;
785 }
786 }
787 else
788 {
789 DEBUG_TSMF("%s start time %" PRIu64 "", get_type(mdecoder), start_time);
790 /* Always set base/start time to 0. Will use seek offset to translate real buffer times
791 * back to 0. This allows the video to be started from anywhere and the ability to handle
792 * seeks without rebuilding the pipeline, etc. since that is costly
793 */
794 gst_element_set_base_time(mdecoder->pipe, tsmf_gstreamer_timestamp_ms_to_gst(0));
795 gst_element_set_start_time(mdecoder->pipe, tsmf_gstreamer_timestamp_ms_to_gst(0));
796 mdecoder->pipeline_start_time_valid = 1;
797
798 /* Set the seek offset if buffer has valid timestamps. */
799 if (useTimestamps)
800 mdecoder->seek_offset = start_time;
801
802 if (!gst_element_seek(mdecoder->pipe, 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH,
803 GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE))
804 {
805 WLog_ERR(TAG, "seek failed");
806 }
807 }
808
809#if GST_VERSION_MAJOR > 0
810 if (useTimestamps)
811 GST_BUFFER_PTS(gst_buf) =
812 sample_time - tsmf_gstreamer_timestamp_ms_to_gst(mdecoder->seek_offset);
813 else
814 GST_BUFFER_PTS(gst_buf) = GST_CLOCK_TIME_NONE;
815#else
816 if (useTimestamps)
817 GST_BUFFER_TIMESTAMP(gst_buf) =
818 sample_time - tsmf_gstreamer_timestamp_ms_to_gst(mdecoder->seek_offset);
819 else
820 GST_BUFFER_TIMESTAMP(gst_buf) = GST_CLOCK_TIME_NONE;
821#endif
822 GST_BUFFER_DURATION(gst_buf) = GST_CLOCK_TIME_NONE;
823 GST_BUFFER_OFFSET(gst_buf) = GST_BUFFER_OFFSET_NONE;
824#if GST_VERSION_MAJOR > 0
825#else
826 gst_buffer_set_caps(gst_buf, mdecoder->gst_caps);
827#endif
828 gst_app_src_push_buffer(GST_APP_SRC(mdecoder->src), gst_buf);
829
830 /* Should only update the last timestamps if the current ones are valid */
831 if (useTimestamps)
832 {
833 mdecoder->last_sample_start_time = start_time;
834 mdecoder->last_sample_end_time = end_time;
835 }
836
837 if (mdecoder->pipe && (GST_STATE(mdecoder->pipe) != GST_STATE_PLAYING))
838 {
839 DEBUG_TSMF("%s: state=%s", get_type(mdecoder),
840 gst_element_state_get_name(GST_STATE(mdecoder->pipe)));
841
842 DEBUG_TSMF("%s Paused: %" PRIi32 " Shutdown: %i Ready: %" PRIi32 "", get_type(mdecoder),
843 mdecoder->paused, mdecoder->shutdown, mdecoder->ready);
844 if (!mdecoder->paused && !mdecoder->shutdown && mdecoder->ready)
845 tsmf_gstreamer_pipeline_set_state(mdecoder, GST_STATE_PLAYING);
846 }
847
848 return TRUE;
849}
850
851static BOOL tsmf_gstreamer_control(ITSMFDecoder* decoder, ITSMFControlMsg control_msg, UINT32* arg)
852{
853 TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder;
854
855 if (!mdecoder)
856 {
857 WLog_ERR(TAG, "Control called with no decoder!");
858 return TRUE;
859 }
860
861 if (control_msg == Control_Pause)
862 {
863 DEBUG_TSMF("Control_Pause %s", get_type(mdecoder));
864
865 if (mdecoder->paused)
866 {
867 WLog_ERR(TAG, "%s: Ignoring Control_Pause, already received!", get_type(mdecoder));
868 return TRUE;
869 }
870
871 tsmf_gstreamer_pipeline_set_state(mdecoder, GST_STATE_PAUSED);
872 mdecoder->shutdown = 0;
873 mdecoder->paused = TRUE;
874 }
875 else if (control_msg == Control_Resume)
876 {
877 DEBUG_TSMF("Control_Resume %s", get_type(mdecoder));
878
879 if (!mdecoder->paused && !mdecoder->shutdown)
880 {
881 WLog_ERR(TAG, "%s: Ignoring Control_Resume, already received!", get_type(mdecoder));
882 return TRUE;
883 }
884
885 mdecoder->shutdown = 0;
886 mdecoder->paused = FALSE;
887 }
888 else if (control_msg == Control_Stop)
889 {
890 DEBUG_TSMF("Control_Stop %s", get_type(mdecoder));
891
892 if (mdecoder->shutdown)
893 {
894 WLog_ERR(TAG, "%s: Ignoring Control_Stop, already received!", get_type(mdecoder));
895 return TRUE;
896 }
897
898 /* Reset stamps, flush buffers, etc */
899 if (mdecoder->pipe)
900 {
901 tsmf_gstreamer_pipeline_set_state(mdecoder, GST_STATE_NULL);
902 tsmf_window_destroy(mdecoder);
903 tsmf_gstreamer_clean_up(mdecoder);
904 }
905 mdecoder->seek_offset = 0;
906 mdecoder->pipeline_start_time_valid = 0;
907 mdecoder->shutdown = 1;
908 }
909 else if (control_msg == Control_Restart)
910 {
911 DEBUG_TSMF("Control_Restart %s", get_type(mdecoder));
912 mdecoder->shutdown = 0;
913 mdecoder->paused = FALSE;
914
915 if (mdecoder->pipeline_start_time_valid)
916 tsmf_gstreamer_pipeline_set_state(mdecoder, GST_STATE_PLAYING);
917 }
918 else
919 WLog_ERR(TAG, "Unknown control message %08x", control_msg);
920
921 return TRUE;
922}
923
924static BOOL tsmf_gstreamer_buffer_level(ITSMFDecoder* decoder)
925{
926 TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder;
927 DEBUG_TSMF("");
928
929 if (!mdecoder)
930 return FALSE;
931
932 guint clbuff = 0;
933
934 if (G_IS_OBJECT(mdecoder->queue))
935 g_object_get(mdecoder->queue, "current-level-buffers", &clbuff, nullptr);
936
937 DEBUG_TSMF("%s buffer level %u", get_type(mdecoder), clbuff);
938 return clbuff;
939}
940
941static void tsmf_gstreamer_free(ITSMFDecoder* decoder)
942{
943 TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder;
944 DEBUG_TSMF("%s", get_type(mdecoder));
945
946 if (mdecoder)
947 {
948 tsmf_window_destroy(mdecoder);
949 tsmf_gstreamer_clean_up(mdecoder);
950
951 if (mdecoder->gst_caps)
952 gst_caps_unref(mdecoder->gst_caps);
953
954 tsmf_platform_free(mdecoder);
955 ZeroMemory(mdecoder, sizeof(TSMFGstreamerDecoder));
956 free(mdecoder);
957 mdecoder = nullptr;
958 }
959}
960
961static UINT64 tsmf_gstreamer_get_running_time(ITSMFDecoder* decoder)
962{
963 TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder;
964
965 if (!mdecoder)
966 return 0;
967
968 if (!mdecoder->outsink)
969 return mdecoder->last_sample_start_time;
970
971 if (!mdecoder->pipe)
972 return 0;
973
974 GstFormat fmt = GST_FORMAT_TIME;
975 gint64 pos = 0;
976#if GST_VERSION_MAJOR > 0
977 gst_element_query_position(mdecoder->pipe, fmt, &pos);
978#else
979 gst_element_query_position(mdecoder->pipe, &fmt, &pos);
980#endif
981 return (UINT64)(pos / 100 + mdecoder->seek_offset);
982}
983
984static BOOL tsmf_gstreamer_update_rendering_area(ITSMFDecoder* decoder, int newX, int newY,
985 int newWidth, int newHeight, int numRectangles,
986 RDP_RECT* rectangles)
987{
988 TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder;
989 DEBUG_TSMF("x=%d, y=%d, w=%d, h=%d, rect=%d", newX, newY, newWidth, newHeight, numRectangles);
990
991 if (mdecoder->media_type == TSMF_MAJOR_TYPE_VIDEO)
992 {
993 return tsmf_window_resize(mdecoder, newX, newY, newWidth, newHeight, numRectangles,
994 rectangles) == 0;
995 }
996
997 return TRUE;
998}
999
1000static BOOL tsmf_gstreamer_ack(ITSMFDecoder* decoder, BOOL (*cb)(void*, BOOL), void* stream)
1001{
1002 TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder;
1003 DEBUG_TSMF("");
1004 mdecoder->ack_cb = nullptr;
1005 mdecoder->stream = stream;
1006 return TRUE;
1007}
1008
1009static BOOL tsmf_gstreamer_sync(ITSMFDecoder* decoder, void (*cb)(void*), void* stream)
1010{
1011 TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder;
1012 DEBUG_TSMF("");
1013 mdecoder->sync_cb = nullptr;
1014 mdecoder->stream = stream;
1015 return TRUE;
1016}
1017
1018FREERDP_ENTRY_POINT(UINT VCAPITYPE gstreamer_freerdp_tsmf_client_decoder_subsystem_entry(void* ptr))
1019{
1020 ITSMFDecoder** sptr = (ITSMFDecoder**)ptr;
1021 WINPR_ASSERT(sptr);
1022 *sptr = nullptr;
1023
1024#if GST_CHECK_VERSION(0, 10, 31)
1025 if (!gst_is_initialized())
1026 {
1027 gst_init(nullptr, nullptr);
1028 }
1029#else
1030 gst_init(nullptr, nullptr);
1031#endif
1032
1033 TSMFGstreamerDecoder* decoder;
1034 decoder = calloc(1, sizeof(TSMFGstreamerDecoder));
1035
1036 if (!decoder)
1037 return ERROR_OUTOFMEMORY;
1038
1039 decoder->iface.SetFormat = tsmf_gstreamer_set_format;
1040 decoder->iface.Decode = nullptr;
1041 decoder->iface.GetDecodedData = nullptr;
1042 decoder->iface.GetDecodedFormat = nullptr;
1043 decoder->iface.GetDecodedDimension = nullptr;
1044 decoder->iface.GetRunningTime = tsmf_gstreamer_get_running_time;
1045 decoder->iface.UpdateRenderingArea = tsmf_gstreamer_update_rendering_area;
1046 decoder->iface.Free = tsmf_gstreamer_free;
1047 decoder->iface.Control = tsmf_gstreamer_control;
1048 decoder->iface.DecodeEx = tsmf_gstreamer_decodeEx;
1049 decoder->iface.ChangeVolume = tsmf_gstreamer_change_volume;
1050 decoder->iface.BufferLevel = tsmf_gstreamer_buffer_level;
1051 decoder->iface.SetAckFunc = tsmf_gstreamer_ack;
1052 decoder->iface.SetSyncFunc = tsmf_gstreamer_sync;
1053 decoder->paused = FALSE;
1054 decoder->gstVolume = 0.5;
1055 decoder->gstMuted = FALSE;
1056 decoder->state = GST_STATE_VOID_PENDING; /* No real state yet */
1057 decoder->last_sample_start_time = 0;
1058 decoder->last_sample_end_time = 0;
1059 decoder->seek_offset = 0;
1060 decoder->seeking = FALSE;
1061
1062 if (tsmf_platform_create(decoder) < 0)
1063 {
1064 free(decoder);
1065 return ERROR_INTERNAL_ERROR;
1066 }
1067
1068 *sptr = &decoder->iface;
1069 return CHANNEL_RC_OK;
1070}