|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */ |
|
3 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
|
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 #include "nsError.h" |
|
8 #include "nsMimeTypes.h" |
|
9 #include "MediaDecoderStateMachine.h" |
|
10 #include "AbstractMediaDecoder.h" |
|
11 #include "MediaResource.h" |
|
12 #include "GStreamerReader.h" |
|
13 #if GST_VERSION_MAJOR >= 1 |
|
14 #include "GStreamerAllocator.h" |
|
15 #endif |
|
16 #include "GStreamerFormatHelper.h" |
|
17 #include "VideoUtils.h" |
|
18 #include "mozilla/dom/TimeRanges.h" |
|
19 #include "mozilla/Endian.h" |
|
20 #include "mozilla/Preferences.h" |
|
21 #include "mozilla/unused.h" |
|
22 #include "GStreamerLoader.h" |
|
23 #include "gfx2DGlue.h" |
|
24 |
|
25 namespace mozilla { |
|
26 |
|
27 using namespace gfx; |
|
28 using namespace layers; |
|
29 |
|
30 // Un-comment to enable logging of seek bisections. |
|
31 //#define SEEK_LOGGING |
|
32 |
|
33 #ifdef PR_LOGGING |
|
34 extern PRLogModuleInfo* gMediaDecoderLog; |
|
35 #define LOG(type, msg, ...) \ |
|
36 PR_LOG(gMediaDecoderLog, type, ("GStreamerReader(%p) " msg, this, ##__VA_ARGS__)) |
|
37 #else |
|
38 #define LOG(type, msg, ...) |
|
39 #endif |
|
40 |
|
41 #if DEBUG |
|
42 static const unsigned int MAX_CHANNELS = 4; |
|
43 #endif |
|
44 // Let the demuxer work in pull mode for short files. This used to be a micro |
|
45 // optimization to have more accurate durations for ogg files in mochitests. |
|
46 // Since as of today we aren't using gstreamer to demux ogg, and having demuxers |
|
47 // work in pull mode over http makes them slower (since they really assume |
|
48 // near-zero latency in pull mode) set the constant to 0 for now, which |
|
49 // effectively disables it. |
|
50 static const int SHORT_FILE_SIZE = 0; |
|
51 // The default resource->Read() size when working in push mode |
|
52 static const int DEFAULT_SOURCE_READ_SIZE = 50 * 1024; |
|
53 |
|
54 typedef enum { |
|
55 GST_PLAY_FLAG_VIDEO = (1 << 0), |
|
56 GST_PLAY_FLAG_AUDIO = (1 << 1), |
|
57 GST_PLAY_FLAG_TEXT = (1 << 2), |
|
58 GST_PLAY_FLAG_VIS = (1 << 3), |
|
59 GST_PLAY_FLAG_SOFT_VOLUME = (1 << 4), |
|
60 GST_PLAY_FLAG_NATIVE_AUDIO = (1 << 5), |
|
61 GST_PLAY_FLAG_NATIVE_VIDEO = (1 << 6), |
|
62 GST_PLAY_FLAG_DOWNLOAD = (1 << 7), |
|
63 GST_PLAY_FLAG_BUFFERING = (1 << 8), |
|
64 GST_PLAY_FLAG_DEINTERLACE = (1 << 9), |
|
65 GST_PLAY_FLAG_SOFT_COLORBALANCE = (1 << 10) |
|
66 } PlayFlags; |
|
67 |
|
68 GStreamerReader::GStreamerReader(AbstractMediaDecoder* aDecoder) |
|
69 : MediaDecoderReader(aDecoder), |
|
70 mMP3FrameParser(aDecoder->GetResource()->GetLength()), |
|
71 mDataOffset(0), |
|
72 mUseParserDuration(false), |
|
73 #if GST_VERSION_MAJOR >= 1 |
|
74 mAllocator(nullptr), |
|
75 mBufferPool(nullptr), |
|
76 #endif |
|
77 mPlayBin(nullptr), |
|
78 mBus(nullptr), |
|
79 mSource(nullptr), |
|
80 mVideoSink(nullptr), |
|
81 mVideoAppSink(nullptr), |
|
82 mAudioSink(nullptr), |
|
83 mAudioAppSink(nullptr), |
|
84 mFormat(GST_VIDEO_FORMAT_UNKNOWN), |
|
85 mVideoSinkBufferCount(0), |
|
86 mAudioSinkBufferCount(0), |
|
87 mGstThreadsMonitor("media.gst.threads"), |
|
88 mReachedAudioEos(false), |
|
89 mReachedVideoEos(false), |
|
90 #if GST_VERSION_MAJOR >= 1 |
|
91 mConfigureAlignment(true), |
|
92 #endif |
|
93 fpsNum(0), |
|
94 fpsDen(0) |
|
95 { |
|
96 MOZ_COUNT_CTOR(GStreamerReader); |
|
97 |
|
98 mSrcCallbacks.need_data = GStreamerReader::NeedDataCb; |
|
99 mSrcCallbacks.enough_data = GStreamerReader::EnoughDataCb; |
|
100 mSrcCallbacks.seek_data = GStreamerReader::SeekDataCb; |
|
101 |
|
102 mSinkCallbacks.eos = GStreamerReader::EosCb; |
|
103 mSinkCallbacks.new_preroll = GStreamerReader::NewPrerollCb; |
|
104 #if GST_VERSION_MAJOR >= 1 |
|
105 mSinkCallbacks.new_sample = GStreamerReader::NewBufferCb; |
|
106 #else |
|
107 mSinkCallbacks.new_buffer = GStreamerReader::NewBufferCb; |
|
108 mSinkCallbacks.new_buffer_list = nullptr; |
|
109 #endif |
|
110 |
|
111 gst_segment_init(&mVideoSegment, GST_FORMAT_UNDEFINED); |
|
112 gst_segment_init(&mAudioSegment, GST_FORMAT_UNDEFINED); |
|
113 } |
|
114 |
|
115 GStreamerReader::~GStreamerReader() |
|
116 { |
|
117 MOZ_COUNT_DTOR(GStreamerReader); |
|
118 ResetDecode(); |
|
119 |
|
120 if (mPlayBin) { |
|
121 gst_app_src_end_of_stream(mSource); |
|
122 if (mSource) |
|
123 gst_object_unref(mSource); |
|
124 gst_element_set_state(mPlayBin, GST_STATE_NULL); |
|
125 gst_object_unref(mPlayBin); |
|
126 mPlayBin = nullptr; |
|
127 mVideoSink = nullptr; |
|
128 mVideoAppSink = nullptr; |
|
129 mAudioSink = nullptr; |
|
130 mAudioAppSink = nullptr; |
|
131 gst_object_unref(mBus); |
|
132 mBus = nullptr; |
|
133 #if GST_VERSION_MAJOR >= 1 |
|
134 g_object_unref(mAllocator); |
|
135 g_object_unref(mBufferPool); |
|
136 #endif |
|
137 } |
|
138 } |
|
139 |
|
140 nsresult GStreamerReader::Init(MediaDecoderReader* aCloneDonor) |
|
141 { |
|
142 GStreamerFormatHelper::Instance(); |
|
143 |
|
144 #if GST_VERSION_MAJOR >= 1 |
|
145 mAllocator = static_cast<GstAllocator*>(g_object_new(GST_TYPE_MOZ_GFX_MEMORY_ALLOCATOR, nullptr)); |
|
146 moz_gfx_memory_allocator_set_reader(mAllocator, this); |
|
147 |
|
148 mBufferPool = static_cast<GstBufferPool*>(g_object_new(GST_TYPE_MOZ_GFX_BUFFER_POOL, nullptr)); |
|
149 #endif |
|
150 |
|
151 #if GST_VERSION_MAJOR >= 1 |
|
152 mPlayBin = gst_element_factory_make("playbin", nullptr); |
|
153 #else |
|
154 mPlayBin = gst_element_factory_make("playbin2", nullptr); |
|
155 #endif |
|
156 if (!mPlayBin) { |
|
157 LOG(PR_LOG_ERROR, "couldn't create playbin"); |
|
158 return NS_ERROR_FAILURE; |
|
159 } |
|
160 g_object_set(mPlayBin, "buffer-size", 0, nullptr); |
|
161 mBus = gst_pipeline_get_bus(GST_PIPELINE(mPlayBin)); |
|
162 |
|
163 mVideoSink = gst_parse_bin_from_description("capsfilter name=filter ! " |
|
164 "appsink name=videosink sync=false max-buffers=1 " |
|
165 #if GST_VERSION_MAJOR >= 1 |
|
166 "caps=video/x-raw,format=I420" |
|
167 #else |
|
168 "caps=video/x-raw-yuv,format=(fourcc)I420" |
|
169 #endif |
|
170 , TRUE, nullptr); |
|
171 mVideoAppSink = GST_APP_SINK(gst_bin_get_by_name(GST_BIN(mVideoSink), |
|
172 "videosink")); |
|
173 mAudioSink = gst_parse_bin_from_description("capsfilter name=filter ! " |
|
174 "appsink name=audiosink sync=false max-buffers=1", TRUE, nullptr); |
|
175 mAudioAppSink = GST_APP_SINK(gst_bin_get_by_name(GST_BIN(mAudioSink), |
|
176 "audiosink")); |
|
177 GstCaps* caps = BuildAudioSinkCaps(); |
|
178 g_object_set(mAudioAppSink, "caps", caps, nullptr); |
|
179 gst_caps_unref(caps); |
|
180 |
|
181 gst_app_sink_set_callbacks(mVideoAppSink, &mSinkCallbacks, |
|
182 (gpointer) this, nullptr); |
|
183 gst_app_sink_set_callbacks(mAudioAppSink, &mSinkCallbacks, |
|
184 (gpointer) this, nullptr); |
|
185 InstallPadCallbacks(); |
|
186 |
|
187 g_object_set(mPlayBin, "uri", "appsrc://", |
|
188 "video-sink", mVideoSink, |
|
189 "audio-sink", mAudioSink, |
|
190 nullptr); |
|
191 |
|
192 g_signal_connect(G_OBJECT(mPlayBin), "notify::source", |
|
193 G_CALLBACK(GStreamerReader::PlayBinSourceSetupCb), this); |
|
194 g_signal_connect(G_OBJECT(mPlayBin), "element-added", |
|
195 G_CALLBACK(GStreamerReader::PlayElementAddedCb), this); |
|
196 |
|
197 return NS_OK; |
|
198 } |
|
199 |
|
200 GstBusSyncReply |
|
201 GStreamerReader::ErrorCb(GstBus *aBus, GstMessage *aMessage, gpointer aUserData) |
|
202 { |
|
203 return static_cast<GStreamerReader*>(aUserData)->Error(aBus, aMessage); |
|
204 } |
|
205 |
|
206 GstBusSyncReply |
|
207 GStreamerReader::Error(GstBus *aBus, GstMessage *aMessage) |
|
208 { |
|
209 if (GST_MESSAGE_TYPE(aMessage) == GST_MESSAGE_ERROR) { |
|
210 Eos(); |
|
211 } |
|
212 |
|
213 return GST_BUS_PASS; |
|
214 } |
|
215 |
|
216 void GStreamerReader::PlayBinSourceSetupCb(GstElement* aPlayBin, |
|
217 GParamSpec* pspec, |
|
218 gpointer aUserData) |
|
219 { |
|
220 GstElement *source; |
|
221 GStreamerReader* reader = reinterpret_cast<GStreamerReader*>(aUserData); |
|
222 |
|
223 g_object_get(aPlayBin, "source", &source, nullptr); |
|
224 reader->PlayBinSourceSetup(GST_APP_SRC(source)); |
|
225 } |
|
226 |
|
227 void GStreamerReader::PlayBinSourceSetup(GstAppSrc* aSource) |
|
228 { |
|
229 mSource = GST_APP_SRC(aSource); |
|
230 gst_app_src_set_callbacks(mSource, &mSrcCallbacks, (gpointer) this, nullptr); |
|
231 MediaResource* resource = mDecoder->GetResource(); |
|
232 |
|
233 /* do a short read to trigger a network request so that GetLength() below |
|
234 * returns something meaningful and not -1 |
|
235 */ |
|
236 char buf[512]; |
|
237 unsigned int size = 0; |
|
238 resource->Read(buf, sizeof(buf), &size); |
|
239 resource->Seek(SEEK_SET, 0); |
|
240 |
|
241 /* now we should have a length */ |
|
242 int64_t resourceLength = GetDataLength(); |
|
243 gst_app_src_set_size(mSource, resourceLength); |
|
244 if (resource->IsDataCachedToEndOfResource(0) || |
|
245 (resourceLength != -1 && resourceLength <= SHORT_FILE_SIZE)) { |
|
246 /* let the demuxer work in pull mode for local files (or very short files) |
|
247 * so that we get optimal seeking accuracy/performance |
|
248 */ |
|
249 LOG(PR_LOG_DEBUG, "configuring random access, len %lld", resourceLength); |
|
250 gst_app_src_set_stream_type(mSource, GST_APP_STREAM_TYPE_RANDOM_ACCESS); |
|
251 } else { |
|
252 /* make the demuxer work in push mode so that seeking is kept to a minimum |
|
253 */ |
|
254 LOG(PR_LOG_DEBUG, "configuring push mode, len %lld", resourceLength); |
|
255 gst_app_src_set_stream_type(mSource, GST_APP_STREAM_TYPE_SEEKABLE); |
|
256 } |
|
257 |
|
258 // Set the source MIME type to stop typefind trying every. single. format. |
|
259 GstCaps *caps = |
|
260 GStreamerFormatHelper::ConvertFormatsToCaps(mDecoder->GetResource()->GetContentType().get(), |
|
261 nullptr); |
|
262 |
|
263 gst_app_src_set_caps(aSource, caps); |
|
264 gst_caps_unref(caps); |
|
265 } |
|
266 |
|
267 /** |
|
268 * If this stream is an MP3, we want to parse the headers to estimate the |
|
269 * stream duration. |
|
270 */ |
|
271 nsresult GStreamerReader::ParseMP3Headers() |
|
272 { |
|
273 MediaResource *resource = mDecoder->GetResource(); |
|
274 |
|
275 const uint32_t MAX_READ_BYTES = 4096; |
|
276 |
|
277 uint64_t offset = 0; |
|
278 char bytes[MAX_READ_BYTES]; |
|
279 uint32_t bytesRead; |
|
280 do { |
|
281 nsresult rv = resource->ReadAt(offset, bytes, MAX_READ_BYTES, &bytesRead); |
|
282 NS_ENSURE_SUCCESS(rv, rv); |
|
283 NS_ENSURE_TRUE(bytesRead, NS_ERROR_FAILURE); |
|
284 |
|
285 mMP3FrameParser.Parse(bytes, bytesRead, offset); |
|
286 offset += bytesRead; |
|
287 } while (!mMP3FrameParser.ParsedHeaders()); |
|
288 |
|
289 if (mMP3FrameParser.IsMP3()) { |
|
290 mLastParserDuration = mMP3FrameParser.GetDuration(); |
|
291 mDataOffset = mMP3FrameParser.GetMP3Offset(); |
|
292 |
|
293 // Update GStreamer's stream length in case we found any ID3 headers to |
|
294 // ignore. |
|
295 gst_app_src_set_size(mSource, GetDataLength()); |
|
296 } |
|
297 |
|
298 return NS_OK; |
|
299 } |
|
300 |
|
301 int64_t |
|
302 GStreamerReader::GetDataLength() |
|
303 { |
|
304 int64_t streamLen = mDecoder->GetResource()->GetLength(); |
|
305 |
|
306 if (streamLen < 0) { |
|
307 return streamLen; |
|
308 } |
|
309 |
|
310 return streamLen - mDataOffset; |
|
311 } |
|
312 |
|
313 nsresult GStreamerReader::ReadMetadata(MediaInfo* aInfo, |
|
314 MetadataTags** aTags) |
|
315 { |
|
316 NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); |
|
317 nsresult ret = NS_OK; |
|
318 |
|
319 /* |
|
320 * Parse MP3 headers before we kick off the GStreamer pipeline otherwise there |
|
321 * might be concurrent stream operations happening on both decoding and gstreamer |
|
322 * threads which will screw the GStreamer state machine. |
|
323 */ |
|
324 bool isMP3 = mDecoder->GetResource()->GetContentType().EqualsASCII(AUDIO_MP3); |
|
325 if (isMP3) { |
|
326 ParseMP3Headers(); |
|
327 } |
|
328 |
|
329 |
|
330 /* We do 3 attempts here: decoding audio and video, decoding video only, |
|
331 * decoding audio only. This allows us to play streams that have one broken |
|
332 * stream but that are otherwise decodeable. |
|
333 */ |
|
334 guint flags[3] = {GST_PLAY_FLAG_VIDEO|GST_PLAY_FLAG_AUDIO, |
|
335 static_cast<guint>(~GST_PLAY_FLAG_AUDIO), static_cast<guint>(~GST_PLAY_FLAG_VIDEO)}; |
|
336 guint default_flags, current_flags; |
|
337 g_object_get(mPlayBin, "flags", &default_flags, nullptr); |
|
338 |
|
339 GstMessage* message = nullptr; |
|
340 for (unsigned int i = 0; i < G_N_ELEMENTS(flags); i++) { |
|
341 current_flags = default_flags & flags[i]; |
|
342 g_object_set(G_OBJECT(mPlayBin), "flags", current_flags, nullptr); |
|
343 |
|
344 /* reset filter caps to ANY */ |
|
345 GstCaps* caps = gst_caps_new_any(); |
|
346 GstElement* filter = gst_bin_get_by_name(GST_BIN(mAudioSink), "filter"); |
|
347 g_object_set(filter, "caps", caps, nullptr); |
|
348 gst_object_unref(filter); |
|
349 |
|
350 filter = gst_bin_get_by_name(GST_BIN(mVideoSink), "filter"); |
|
351 g_object_set(filter, "caps", caps, nullptr); |
|
352 gst_object_unref(filter); |
|
353 gst_caps_unref(caps); |
|
354 filter = nullptr; |
|
355 |
|
356 if (!(current_flags & GST_PLAY_FLAG_AUDIO)) |
|
357 filter = gst_bin_get_by_name(GST_BIN(mAudioSink), "filter"); |
|
358 else if (!(current_flags & GST_PLAY_FLAG_VIDEO)) |
|
359 filter = gst_bin_get_by_name(GST_BIN(mVideoSink), "filter"); |
|
360 |
|
361 if (filter) { |
|
362 /* Little trick: set the target caps to "skip" so that playbin2 fails to |
|
363 * find a decoder for the stream we want to skip. |
|
364 */ |
|
365 GstCaps* filterCaps = gst_caps_new_simple ("skip", nullptr, nullptr); |
|
366 g_object_set(filter, "caps", filterCaps, nullptr); |
|
367 gst_caps_unref(filterCaps); |
|
368 gst_object_unref(filter); |
|
369 } |
|
370 |
|
371 LOG(PR_LOG_DEBUG, "starting metadata pipeline"); |
|
372 if (gst_element_set_state(mPlayBin, GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE) { |
|
373 LOG(PR_LOG_DEBUG, "metadata pipeline state change failed"); |
|
374 ret = NS_ERROR_FAILURE; |
|
375 continue; |
|
376 } |
|
377 |
|
378 /* Wait for ASYNC_DONE, which is emitted when the pipeline is built, |
|
379 * prerolled and ready to play. Also watch for errors. |
|
380 */ |
|
381 message = gst_bus_timed_pop_filtered(mBus, GST_CLOCK_TIME_NONE, |
|
382 (GstMessageType)(GST_MESSAGE_ASYNC_DONE | GST_MESSAGE_ERROR | GST_MESSAGE_EOS)); |
|
383 if (GST_MESSAGE_TYPE(message) == GST_MESSAGE_ASYNC_DONE) { |
|
384 LOG(PR_LOG_DEBUG, "read metadata pipeline prerolled"); |
|
385 gst_message_unref(message); |
|
386 ret = NS_OK; |
|
387 break; |
|
388 } else { |
|
389 LOG(PR_LOG_DEBUG, "read metadata pipeline failed to preroll: %s", |
|
390 gst_message_type_get_name (GST_MESSAGE_TYPE (message))); |
|
391 |
|
392 if (GST_MESSAGE_TYPE(message) == GST_MESSAGE_ERROR) { |
|
393 GError* error; |
|
394 gchar* debug; |
|
395 gst_message_parse_error(message, &error, &debug); |
|
396 LOG(PR_LOG_ERROR, "read metadata error: %s: %s", error->message, debug); |
|
397 g_error_free(error); |
|
398 g_free(debug); |
|
399 } |
|
400 /* Unexpected stream close/EOS or other error. We'll give up if all |
|
401 * streams are in error/eos. */ |
|
402 gst_element_set_state(mPlayBin, GST_STATE_NULL); |
|
403 gst_message_unref(message); |
|
404 ret = NS_ERROR_FAILURE; |
|
405 } |
|
406 } |
|
407 |
|
408 if (NS_SUCCEEDED(ret)) |
|
409 ret = CheckSupportedFormats(); |
|
410 |
|
411 if (NS_FAILED(ret)) |
|
412 /* we couldn't get this to play */ |
|
413 return ret; |
|
414 |
|
415 /* report the duration */ |
|
416 gint64 duration; |
|
417 |
|
418 if (isMP3 && mMP3FrameParser.IsMP3()) { |
|
419 // The MP3FrameParser has reported a duration; use that over the gstreamer |
|
420 // reported duration for inter-platform consistency. |
|
421 ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); |
|
422 mUseParserDuration = true; |
|
423 mLastParserDuration = mMP3FrameParser.GetDuration(); |
|
424 mDecoder->SetMediaDuration(mLastParserDuration); |
|
425 } else { |
|
426 LOG(PR_LOG_DEBUG, "querying duration"); |
|
427 // Otherwise use the gstreamer duration. |
|
428 #if GST_VERSION_MAJOR >= 1 |
|
429 if (gst_element_query_duration(GST_ELEMENT(mPlayBin), |
|
430 GST_FORMAT_TIME, &duration)) { |
|
431 #else |
|
432 GstFormat format = GST_FORMAT_TIME; |
|
433 if (gst_element_query_duration(GST_ELEMENT(mPlayBin), |
|
434 &format, &duration) && format == GST_FORMAT_TIME) { |
|
435 #endif |
|
436 ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); |
|
437 LOG(PR_LOG_DEBUG, "have duration %" GST_TIME_FORMAT, GST_TIME_ARGS(duration)); |
|
438 duration = GST_TIME_AS_USECONDS (duration); |
|
439 mDecoder->SetMediaDuration(duration); |
|
440 } else { |
|
441 mDecoder->SetMediaSeekable(false); |
|
442 } |
|
443 } |
|
444 |
|
445 int n_video = 0, n_audio = 0; |
|
446 g_object_get(mPlayBin, "n-video", &n_video, "n-audio", &n_audio, nullptr); |
|
447 mInfo.mVideo.mHasVideo = n_video != 0; |
|
448 mInfo.mAudio.mHasAudio = n_audio != 0; |
|
449 |
|
450 *aInfo = mInfo; |
|
451 |
|
452 *aTags = nullptr; |
|
453 |
|
454 // Watch the pipeline for fatal errors |
|
455 #if GST_VERSION_MAJOR >= 1 |
|
456 gst_bus_set_sync_handler(mBus, GStreamerReader::ErrorCb, this, nullptr); |
|
457 #else |
|
458 gst_bus_set_sync_handler(mBus, GStreamerReader::ErrorCb, this); |
|
459 #endif |
|
460 |
|
461 /* set the pipeline to PLAYING so that it starts decoding and queueing data in |
|
462 * the appsinks */ |
|
463 gst_element_set_state(mPlayBin, GST_STATE_PLAYING); |
|
464 |
|
465 return NS_OK; |
|
466 } |
|
467 |
|
468 nsresult GStreamerReader::CheckSupportedFormats() |
|
469 { |
|
470 bool done = false; |
|
471 bool unsupported = false; |
|
472 |
|
473 GstIterator* it = gst_bin_iterate_recurse(GST_BIN(mPlayBin)); |
|
474 while (!done) { |
|
475 GstIteratorResult res; |
|
476 GstElement* element; |
|
477 |
|
478 #if GST_VERSION_MAJOR >= 1 |
|
479 GValue value = {0,}; |
|
480 res = gst_iterator_next(it, &value); |
|
481 #else |
|
482 res = gst_iterator_next(it, (void **) &element); |
|
483 #endif |
|
484 switch(res) { |
|
485 case GST_ITERATOR_OK: |
|
486 { |
|
487 #if GST_VERSION_MAJOR >= 1 |
|
488 element = GST_ELEMENT (g_value_get_object (&value)); |
|
489 #endif |
|
490 GstElementFactory* factory = gst_element_get_factory(element); |
|
491 if (factory) { |
|
492 const char* klass = gst_element_factory_get_klass(factory); |
|
493 GstPad* pad = gst_element_get_static_pad(element, "sink"); |
|
494 if (pad) { |
|
495 GstCaps* caps; |
|
496 |
|
497 #if GST_VERSION_MAJOR >= 1 |
|
498 caps = gst_pad_get_current_caps(pad); |
|
499 #else |
|
500 caps = gst_pad_get_negotiated_caps(pad); |
|
501 #endif |
|
502 |
|
503 if (caps) { |
|
504 /* check for demuxers but ignore elements like id3demux */ |
|
505 if (strstr (klass, "Demuxer") && !strstr(klass, "Metadata")) |
|
506 unsupported = !GStreamerFormatHelper::Instance()->CanHandleContainerCaps(caps); |
|
507 else if (strstr (klass, "Decoder") && !strstr(klass, "Generic")) |
|
508 unsupported = !GStreamerFormatHelper::Instance()->CanHandleCodecCaps(caps); |
|
509 |
|
510 gst_caps_unref(caps); |
|
511 } |
|
512 gst_object_unref(pad); |
|
513 } |
|
514 } |
|
515 |
|
516 #if GST_VERSION_MAJOR >= 1 |
|
517 g_value_unset (&value); |
|
518 #else |
|
519 gst_object_unref(element); |
|
520 #endif |
|
521 done = unsupported; |
|
522 break; |
|
523 } |
|
524 case GST_ITERATOR_RESYNC: |
|
525 unsupported = false; |
|
526 done = false; |
|
527 break; |
|
528 case GST_ITERATOR_ERROR: |
|
529 done = true; |
|
530 break; |
|
531 case GST_ITERATOR_DONE: |
|
532 done = true; |
|
533 break; |
|
534 } |
|
535 } |
|
536 |
|
537 return unsupported ? NS_ERROR_FAILURE : NS_OK; |
|
538 } |
|
539 |
|
540 nsresult GStreamerReader::ResetDecode() |
|
541 { |
|
542 nsresult res = NS_OK; |
|
543 |
|
544 LOG(PR_LOG_DEBUG, "reset decode"); |
|
545 |
|
546 if (NS_FAILED(MediaDecoderReader::ResetDecode())) { |
|
547 res = NS_ERROR_FAILURE; |
|
548 } |
|
549 |
|
550 mVideoQueue.Reset(); |
|
551 mAudioQueue.Reset(); |
|
552 |
|
553 mVideoSinkBufferCount = 0; |
|
554 mAudioSinkBufferCount = 0; |
|
555 mReachedAudioEos = false; |
|
556 mReachedVideoEos = false; |
|
557 #if GST_VERSION_MAJOR >= 1 |
|
558 mConfigureAlignment = true; |
|
559 #endif |
|
560 |
|
561 LOG(PR_LOG_DEBUG, "reset decode done"); |
|
562 |
|
563 return res; |
|
564 } |
|
565 |
|
566 bool GStreamerReader::DecodeAudioData() |
|
567 { |
|
568 NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); |
|
569 |
|
570 GstBuffer *buffer = nullptr; |
|
571 |
|
572 { |
|
573 ReentrantMonitorAutoEnter mon(mGstThreadsMonitor); |
|
574 |
|
575 if (mReachedAudioEos && !mAudioSinkBufferCount) { |
|
576 return false; |
|
577 } |
|
578 |
|
579 /* Wait something to be decoded before return or continue */ |
|
580 if (!mAudioSinkBufferCount) { |
|
581 if(!mVideoSinkBufferCount) { |
|
582 /* We have nothing decoded so it makes no sense to return to the state machine |
|
583 * as it will call us back immediately, we'll return again and so on, wasting |
|
584 * CPU cycles for no job done. So, block here until there is either video or |
|
585 * audio data available |
|
586 */ |
|
587 mon.Wait(); |
|
588 if (!mAudioSinkBufferCount) { |
|
589 /* There is still no audio data available, so either there is video data or |
|
590 * something else has happened (Eos, etc...). Return to the state machine |
|
591 * to process it. |
|
592 */ |
|
593 return true; |
|
594 } |
|
595 } |
|
596 else { |
|
597 return true; |
|
598 } |
|
599 } |
|
600 |
|
601 #if GST_VERSION_MAJOR >= 1 |
|
602 GstSample *sample = gst_app_sink_pull_sample(mAudioAppSink); |
|
603 buffer = gst_buffer_ref(gst_sample_get_buffer(sample)); |
|
604 gst_sample_unref(sample); |
|
605 #else |
|
606 buffer = gst_app_sink_pull_buffer(mAudioAppSink); |
|
607 #endif |
|
608 |
|
609 mAudioSinkBufferCount--; |
|
610 } |
|
611 |
|
612 int64_t timestamp = GST_BUFFER_TIMESTAMP(buffer); |
|
613 timestamp = gst_segment_to_stream_time(&mAudioSegment, |
|
614 GST_FORMAT_TIME, timestamp); |
|
615 |
|
616 timestamp = GST_TIME_AS_USECONDS(timestamp); |
|
617 |
|
618 int64_t offset = GST_BUFFER_OFFSET(buffer); |
|
619 guint8* data; |
|
620 #if GST_VERSION_MAJOR >= 1 |
|
621 GstMapInfo info; |
|
622 gst_buffer_map(buffer, &info, GST_MAP_READ); |
|
623 unsigned int size = info.size; |
|
624 data = info.data; |
|
625 #else |
|
626 unsigned int size = GST_BUFFER_SIZE(buffer); |
|
627 data = GST_BUFFER_DATA(buffer); |
|
628 #endif |
|
629 int32_t frames = (size / sizeof(AudioDataValue)) / mInfo.mAudio.mChannels; |
|
630 |
|
631 typedef AudioCompactor::NativeCopy GstCopy; |
|
632 mAudioCompactor.Push(offset, |
|
633 timestamp, |
|
634 mInfo.mAudio.mRate, |
|
635 frames, |
|
636 mInfo.mAudio.mChannels, |
|
637 GstCopy(data, |
|
638 size, |
|
639 mInfo.mAudio.mChannels)); |
|
640 #if GST_VERSION_MAJOR >= 1 |
|
641 gst_buffer_unmap(buffer, &info); |
|
642 #endif |
|
643 |
|
644 gst_buffer_unref(buffer); |
|
645 |
|
646 return true; |
|
647 } |
|
648 |
|
649 bool GStreamerReader::DecodeVideoFrame(bool &aKeyFrameSkip, |
|
650 int64_t aTimeThreshold) |
|
651 { |
|
652 NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); |
|
653 |
|
654 GstBuffer *buffer = nullptr; |
|
655 |
|
656 { |
|
657 ReentrantMonitorAutoEnter mon(mGstThreadsMonitor); |
|
658 |
|
659 if (mReachedVideoEos && !mVideoSinkBufferCount) { |
|
660 return false; |
|
661 } |
|
662 |
|
663 /* Wait something to be decoded before return or continue */ |
|
664 if (!mVideoSinkBufferCount) { |
|
665 if (!mAudioSinkBufferCount) { |
|
666 /* We have nothing decoded so it makes no sense to return to the state machine |
|
667 * as it will call us back immediately, we'll return again and so on, wasting |
|
668 * CPU cycles for no job done. So, block here until there is either video or |
|
669 * audio data available |
|
670 */ |
|
671 mon.Wait(); |
|
672 if (!mVideoSinkBufferCount) { |
|
673 /* There is still no video data available, so either there is audio data or |
|
674 * something else has happened (Eos, etc...). Return to the state machine |
|
675 * to process it |
|
676 */ |
|
677 return true; |
|
678 } |
|
679 } |
|
680 else { |
|
681 return true; |
|
682 } |
|
683 } |
|
684 |
|
685 mDecoder->NotifyDecodedFrames(0, 1); |
|
686 |
|
687 #if GST_VERSION_MAJOR >= 1 |
|
688 GstSample *sample = gst_app_sink_pull_sample(mVideoAppSink); |
|
689 buffer = gst_buffer_ref(gst_sample_get_buffer(sample)); |
|
690 gst_sample_unref(sample); |
|
691 #else |
|
692 buffer = gst_app_sink_pull_buffer(mVideoAppSink); |
|
693 #endif |
|
694 mVideoSinkBufferCount--; |
|
695 } |
|
696 |
|
697 bool isKeyframe = !GST_BUFFER_FLAG_IS_SET(buffer, GST_BUFFER_FLAG_DELTA_UNIT); |
|
698 if ((aKeyFrameSkip && !isKeyframe)) { |
|
699 gst_buffer_unref(buffer); |
|
700 return true; |
|
701 } |
|
702 |
|
703 int64_t timestamp = GST_BUFFER_TIMESTAMP(buffer); |
|
704 { |
|
705 ReentrantMonitorAutoEnter mon(mGstThreadsMonitor); |
|
706 timestamp = gst_segment_to_stream_time(&mVideoSegment, |
|
707 GST_FORMAT_TIME, timestamp); |
|
708 } |
|
709 NS_ASSERTION(GST_CLOCK_TIME_IS_VALID(timestamp), |
|
710 "frame has invalid timestamp"); |
|
711 |
|
712 timestamp = GST_TIME_AS_USECONDS(timestamp); |
|
713 int64_t duration = 0; |
|
714 if (GST_CLOCK_TIME_IS_VALID(GST_BUFFER_DURATION(buffer))) |
|
715 duration = GST_TIME_AS_USECONDS(GST_BUFFER_DURATION(buffer)); |
|
716 else if (fpsNum && fpsDen) |
|
717 /* add 1-frame duration */ |
|
718 duration = gst_util_uint64_scale(GST_USECOND, fpsDen, fpsNum); |
|
719 |
|
720 if (timestamp < aTimeThreshold) { |
|
721 LOG(PR_LOG_DEBUG, "skipping frame %" GST_TIME_FORMAT |
|
722 " threshold %" GST_TIME_FORMAT, |
|
723 GST_TIME_ARGS(timestamp * 1000), |
|
724 GST_TIME_ARGS(aTimeThreshold * 1000)); |
|
725 gst_buffer_unref(buffer); |
|
726 return true; |
|
727 } |
|
728 |
|
729 if (!buffer) |
|
730 /* no more frames */ |
|
731 return true; |
|
732 |
|
733 #if GST_VERSION_MAJOR >= 1 |
|
734 if (mConfigureAlignment && buffer->pool) { |
|
735 GstStructure *config = gst_buffer_pool_get_config(buffer->pool); |
|
736 GstVideoAlignment align; |
|
737 if (gst_buffer_pool_config_get_video_alignment(config, &align)) |
|
738 gst_video_info_align(&mVideoInfo, &align); |
|
739 gst_structure_free(config); |
|
740 mConfigureAlignment = false; |
|
741 } |
|
742 #endif |
|
743 |
|
744 nsRefPtr<PlanarYCbCrImage> image = GetImageFromBuffer(buffer); |
|
745 if (!image) { |
|
746 /* Ugh, upstream is not calling gst_pad_alloc_buffer(). Fallback to |
|
747 * allocating a PlanarYCbCrImage backed GstBuffer here and memcpy. |
|
748 */ |
|
749 GstBuffer* tmp = nullptr; |
|
750 CopyIntoImageBuffer(buffer, &tmp, image); |
|
751 gst_buffer_unref(buffer); |
|
752 buffer = tmp; |
|
753 } |
|
754 |
|
755 int64_t offset = mDecoder->GetResource()->Tell(); // Estimate location in media. |
|
756 VideoData* video = VideoData::CreateFromImage(mInfo.mVideo, |
|
757 mDecoder->GetImageContainer(), |
|
758 offset, timestamp, duration, |
|
759 static_cast<Image*>(image.get()), |
|
760 isKeyframe, -1, mPicture); |
|
761 mVideoQueue.Push(video); |
|
762 |
|
763 gst_buffer_unref(buffer); |
|
764 |
|
765 return true; |
|
766 } |
|
767 |
|
768 nsresult GStreamerReader::Seek(int64_t aTarget, |
|
769 int64_t aStartTime, |
|
770 int64_t aEndTime, |
|
771 int64_t aCurrentTime) |
|
772 { |
|
773 NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); |
|
774 |
|
775 gint64 seekPos = aTarget * GST_USECOND; |
|
776 LOG(PR_LOG_DEBUG, "%p About to seek to %" GST_TIME_FORMAT, |
|
777 mDecoder, GST_TIME_ARGS(seekPos)); |
|
778 |
|
779 int flags = GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT; |
|
780 if (!gst_element_seek_simple(mPlayBin, |
|
781 GST_FORMAT_TIME, |
|
782 static_cast<GstSeekFlags>(flags), |
|
783 seekPos)) { |
|
784 LOG(PR_LOG_ERROR, "seek failed"); |
|
785 return NS_ERROR_FAILURE; |
|
786 } |
|
787 LOG(PR_LOG_DEBUG, "seek succeeded"); |
|
788 GstMessage* message = gst_bus_timed_pop_filtered(mBus, GST_CLOCK_TIME_NONE, |
|
789 (GstMessageType)(GST_MESSAGE_ASYNC_DONE | GST_MESSAGE_ERROR)); |
|
790 gst_message_unref(message); |
|
791 LOG(PR_LOG_DEBUG, "seek completed"); |
|
792 |
|
793 return NS_OK; |
|
794 } |
|
795 |
|
796 nsresult GStreamerReader::GetBuffered(dom::TimeRanges* aBuffered, |
|
797 int64_t aStartTime) |
|
798 { |
|
799 if (!mInfo.HasValidMedia()) { |
|
800 return NS_OK; |
|
801 } |
|
802 |
|
803 #if GST_VERSION_MAJOR == 0 |
|
804 GstFormat format = GST_FORMAT_TIME; |
|
805 #endif |
|
806 MediaResource* resource = mDecoder->GetResource(); |
|
807 nsTArray<MediaByteRange> ranges; |
|
808 resource->GetCachedRanges(ranges); |
|
809 |
|
810 if (resource->IsDataCachedToEndOfResource(0)) { |
|
811 /* fast path for local or completely cached files */ |
|
812 gint64 duration = 0; |
|
813 |
|
814 { |
|
815 ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); |
|
816 duration = mDecoder->GetMediaDuration(); |
|
817 } |
|
818 |
|
819 double end = (double) duration / GST_MSECOND; |
|
820 LOG(PR_LOG_DEBUG, "complete range [0, %f] for [0, %li]", |
|
821 end, GetDataLength()); |
|
822 aBuffered->Add(0, end); |
|
823 return NS_OK; |
|
824 } |
|
825 |
|
826 for(uint32_t index = 0; index < ranges.Length(); index++) { |
|
827 int64_t startOffset = ranges[index].mStart; |
|
828 int64_t endOffset = ranges[index].mEnd; |
|
829 gint64 startTime, endTime; |
|
830 |
|
831 #if GST_VERSION_MAJOR >= 1 |
|
832 if (!gst_element_query_convert(GST_ELEMENT(mPlayBin), GST_FORMAT_BYTES, |
|
833 startOffset, GST_FORMAT_TIME, &startTime)) |
|
834 continue; |
|
835 if (!gst_element_query_convert(GST_ELEMENT(mPlayBin), GST_FORMAT_BYTES, |
|
836 endOffset, GST_FORMAT_TIME, &endTime)) |
|
837 continue; |
|
838 #else |
|
839 if (!gst_element_query_convert(GST_ELEMENT(mPlayBin), GST_FORMAT_BYTES, |
|
840 startOffset, &format, &startTime) || format != GST_FORMAT_TIME) |
|
841 continue; |
|
842 if (!gst_element_query_convert(GST_ELEMENT(mPlayBin), GST_FORMAT_BYTES, |
|
843 endOffset, &format, &endTime) || format != GST_FORMAT_TIME) |
|
844 continue; |
|
845 #endif |
|
846 |
|
847 double start = (double) GST_TIME_AS_USECONDS (startTime) / GST_MSECOND; |
|
848 double end = (double) GST_TIME_AS_USECONDS (endTime) / GST_MSECOND; |
|
849 LOG(PR_LOG_DEBUG, "adding range [%f, %f] for [%li %li] size %li", |
|
850 start, end, startOffset, endOffset, GetDataLength()); |
|
851 aBuffered->Add(start, end); |
|
852 } |
|
853 |
|
854 return NS_OK; |
|
855 } |
|
856 |
|
857 void GStreamerReader::ReadAndPushData(guint aLength) |
|
858 { |
|
859 MediaResource* resource = mDecoder->GetResource(); |
|
860 NS_ASSERTION(resource, "Decoder has no media resource"); |
|
861 int64_t offset1 = resource->Tell(); |
|
862 unused << offset1; |
|
863 nsresult rv = NS_OK; |
|
864 |
|
865 GstBuffer* buffer = gst_buffer_new_and_alloc(aLength); |
|
866 #if GST_VERSION_MAJOR >= 1 |
|
867 GstMapInfo info; |
|
868 gst_buffer_map(buffer, &info, GST_MAP_WRITE); |
|
869 guint8 *data = info.data; |
|
870 #else |
|
871 guint8* data = GST_BUFFER_DATA(buffer); |
|
872 #endif |
|
873 uint32_t size = 0, bytesRead = 0; |
|
874 while(bytesRead < aLength) { |
|
875 rv = resource->Read(reinterpret_cast<char*>(data + bytesRead), |
|
876 aLength - bytesRead, &size); |
|
877 if (NS_FAILED(rv) || size == 0) |
|
878 break; |
|
879 |
|
880 bytesRead += size; |
|
881 } |
|
882 |
|
883 int64_t offset2 = resource->Tell(); |
|
884 unused << offset2; |
|
885 |
|
886 #if GST_VERSION_MAJOR >= 1 |
|
887 gst_buffer_unmap(buffer, &info); |
|
888 gst_buffer_set_size(buffer, bytesRead); |
|
889 #else |
|
890 GST_BUFFER_SIZE(buffer) = bytesRead; |
|
891 #endif |
|
892 |
|
893 GstFlowReturn ret = gst_app_src_push_buffer(mSource, gst_buffer_ref(buffer)); |
|
894 if (ret != GST_FLOW_OK) { |
|
895 LOG(PR_LOG_ERROR, "ReadAndPushData push ret %s(%d)", gst_flow_get_name(ret), ret); |
|
896 } |
|
897 |
|
898 if (NS_FAILED(rv)) { |
|
899 /* Terminate the stream if there is an error in reading */ |
|
900 LOG(PR_LOG_ERROR, "ReadAndPushData read error, rv=%x", rv); |
|
901 gst_app_src_end_of_stream(mSource); |
|
902 } else if (bytesRead < aLength) { |
|
903 /* If we read less than what we wanted, we reached the end */ |
|
904 LOG(PR_LOG_WARNING, "ReadAndPushData read underflow, " |
|
905 "bytesRead=%u, aLength=%u, offset(%lld,%lld)", |
|
906 bytesRead, aLength, offset1, offset2); |
|
907 gst_app_src_end_of_stream(mSource); |
|
908 } |
|
909 |
|
910 gst_buffer_unref(buffer); |
|
911 |
|
912 /* Ensure offset change is consistent in this function. |
|
913 * If there are other stream operations on another thread at the same time, |
|
914 * it will disturb the GStreamer state machine. |
|
915 */ |
|
916 MOZ_ASSERT(offset1 + bytesRead == offset2); |
|
917 } |
|
918 |
|
919 void GStreamerReader::NeedDataCb(GstAppSrc* aSrc, |
|
920 guint aLength, |
|
921 gpointer aUserData) |
|
922 { |
|
923 GStreamerReader* reader = reinterpret_cast<GStreamerReader*>(aUserData); |
|
924 reader->NeedData(aSrc, aLength); |
|
925 } |
|
926 |
|
927 void GStreamerReader::NeedData(GstAppSrc* aSrc, guint aLength) |
|
928 { |
|
929 if (aLength == static_cast<guint>(-1)) |
|
930 aLength = DEFAULT_SOURCE_READ_SIZE; |
|
931 ReadAndPushData(aLength); |
|
932 } |
|
933 |
|
934 void GStreamerReader::EnoughDataCb(GstAppSrc* aSrc, gpointer aUserData) |
|
935 { |
|
936 GStreamerReader* reader = reinterpret_cast<GStreamerReader*>(aUserData); |
|
937 reader->EnoughData(aSrc); |
|
938 } |
|
939 |
|
940 void GStreamerReader::EnoughData(GstAppSrc* aSrc) |
|
941 { |
|
942 } |
|
943 |
|
944 gboolean GStreamerReader::SeekDataCb(GstAppSrc* aSrc, |
|
945 guint64 aOffset, |
|
946 gpointer aUserData) |
|
947 { |
|
948 GStreamerReader* reader = reinterpret_cast<GStreamerReader*>(aUserData); |
|
949 return reader->SeekData(aSrc, aOffset); |
|
950 } |
|
951 |
|
952 gboolean GStreamerReader::SeekData(GstAppSrc* aSrc, guint64 aOffset) |
|
953 { |
|
954 aOffset += mDataOffset; |
|
955 |
|
956 ReentrantMonitorAutoEnter mon(mGstThreadsMonitor); |
|
957 MediaResource* resource = mDecoder->GetResource(); |
|
958 int64_t resourceLength = resource->GetLength(); |
|
959 |
|
960 if (gst_app_src_get_size(mSource) == -1) { |
|
961 /* It's possible that we didn't know the length when we initialized mSource |
|
962 * but maybe we do now |
|
963 */ |
|
964 gst_app_src_set_size(mSource, GetDataLength()); |
|
965 } |
|
966 |
|
967 nsresult rv = NS_ERROR_FAILURE; |
|
968 if (aOffset < static_cast<guint64>(resourceLength)) { |
|
969 rv = resource->Seek(SEEK_SET, aOffset); |
|
970 } |
|
971 |
|
972 if (NS_FAILED(rv)) { |
|
973 LOG(PR_LOG_ERROR, "seek at %lu failed", aOffset); |
|
974 } else { |
|
975 MOZ_ASSERT(aOffset == static_cast<guint64>(resource->Tell())); |
|
976 } |
|
977 |
|
978 return NS_SUCCEEDED(rv); |
|
979 } |
|
980 |
|
981 GstFlowReturn GStreamerReader::NewPrerollCb(GstAppSink* aSink, |
|
982 gpointer aUserData) |
|
983 { |
|
984 GStreamerReader* reader = reinterpret_cast<GStreamerReader*>(aUserData); |
|
985 |
|
986 if (aSink == reader->mVideoAppSink) |
|
987 reader->VideoPreroll(); |
|
988 else |
|
989 reader->AudioPreroll(); |
|
990 return GST_FLOW_OK; |
|
991 } |
|
992 |
|
993 void GStreamerReader::AudioPreroll() |
|
994 { |
|
995 /* The first audio buffer has reached the audio sink. Get rate and channels */ |
|
996 LOG(PR_LOG_DEBUG, "Audio preroll"); |
|
997 GstPad* sinkpad = gst_element_get_static_pad(GST_ELEMENT(mAudioAppSink), "sink"); |
|
998 #if GST_VERSION_MAJOR >= 1 |
|
999 GstCaps *caps = gst_pad_get_current_caps(sinkpad); |
|
1000 #else |
|
1001 GstCaps* caps = gst_pad_get_negotiated_caps(sinkpad); |
|
1002 #endif |
|
1003 GstStructure* s = gst_caps_get_structure(caps, 0); |
|
1004 mInfo.mAudio.mRate = mInfo.mAudio.mChannels = 0; |
|
1005 gst_structure_get_int(s, "rate", (gint*) &mInfo.mAudio.mRate); |
|
1006 gst_structure_get_int(s, "channels", (gint*) &mInfo.mAudio.mChannels); |
|
1007 NS_ASSERTION(mInfo.mAudio.mRate != 0, ("audio rate is zero")); |
|
1008 NS_ASSERTION(mInfo.mAudio.mChannels != 0, ("audio channels is zero")); |
|
1009 NS_ASSERTION(mInfo.mAudio.mChannels > 0 && mInfo.mAudio.mChannels <= MAX_CHANNELS, |
|
1010 "invalid audio channels number"); |
|
1011 mInfo.mAudio.mHasAudio = true; |
|
1012 gst_caps_unref(caps); |
|
1013 gst_object_unref(sinkpad); |
|
1014 } |
|
1015 |
|
1016 void GStreamerReader::VideoPreroll() |
|
1017 { |
|
1018 /* The first video buffer has reached the video sink. Get width and height */ |
|
1019 LOG(PR_LOG_DEBUG, "Video preroll"); |
|
1020 GstPad* sinkpad = gst_element_get_static_pad(GST_ELEMENT(mVideoAppSink), "sink"); |
|
1021 int PARNumerator, PARDenominator; |
|
1022 #if GST_VERSION_MAJOR >= 1 |
|
1023 GstCaps* caps = gst_pad_get_current_caps(sinkpad); |
|
1024 memset (&mVideoInfo, 0, sizeof (mVideoInfo)); |
|
1025 gst_video_info_from_caps(&mVideoInfo, caps); |
|
1026 mFormat = mVideoInfo.finfo->format; |
|
1027 mPicture.width = mVideoInfo.width; |
|
1028 mPicture.height = mVideoInfo.height; |
|
1029 PARNumerator = GST_VIDEO_INFO_PAR_N(&mVideoInfo); |
|
1030 PARDenominator = GST_VIDEO_INFO_PAR_D(&mVideoInfo); |
|
1031 #else |
|
1032 GstCaps* caps = gst_pad_get_negotiated_caps(sinkpad); |
|
1033 gst_video_format_parse_caps(caps, &mFormat, &mPicture.width, &mPicture.height); |
|
1034 if (!gst_video_parse_caps_pixel_aspect_ratio(caps, &PARNumerator, &PARDenominator)) { |
|
1035 PARNumerator = 1; |
|
1036 PARDenominator = 1; |
|
1037 } |
|
1038 #endif |
|
1039 NS_ASSERTION(mPicture.width && mPicture.height, "invalid video resolution"); |
|
1040 |
|
1041 // Calculate display size according to pixel aspect ratio. |
|
1042 nsIntRect pictureRect(0, 0, mPicture.width, mPicture.height); |
|
1043 nsIntSize frameSize = nsIntSize(mPicture.width, mPicture.height); |
|
1044 nsIntSize displaySize = nsIntSize(mPicture.width, mPicture.height); |
|
1045 ScaleDisplayByAspectRatio(displaySize, float(PARNumerator) / float(PARDenominator)); |
|
1046 |
|
1047 // If video frame size is overflow, stop playing. |
|
1048 if (IsValidVideoRegion(frameSize, pictureRect, displaySize)) { |
|
1049 GstStructure* structure = gst_caps_get_structure(caps, 0); |
|
1050 gst_structure_get_fraction(structure, "framerate", &fpsNum, &fpsDen); |
|
1051 mInfo.mVideo.mDisplay = ThebesIntSize(displaySize.ToIntSize()); |
|
1052 mInfo.mVideo.mHasVideo = true; |
|
1053 } else { |
|
1054 LOG(PR_LOG_DEBUG, "invalid video region"); |
|
1055 Eos(); |
|
1056 } |
|
1057 gst_caps_unref(caps); |
|
1058 gst_object_unref(sinkpad); |
|
1059 } |
|
1060 |
|
1061 GstFlowReturn GStreamerReader::NewBufferCb(GstAppSink* aSink, |
|
1062 gpointer aUserData) |
|
1063 { |
|
1064 GStreamerReader* reader = reinterpret_cast<GStreamerReader*>(aUserData); |
|
1065 |
|
1066 if (aSink == reader->mVideoAppSink) |
|
1067 reader->NewVideoBuffer(); |
|
1068 else |
|
1069 reader->NewAudioBuffer(); |
|
1070 |
|
1071 return GST_FLOW_OK; |
|
1072 } |
|
1073 |
|
1074 void GStreamerReader::NewVideoBuffer() |
|
1075 { |
|
1076 ReentrantMonitorAutoEnter mon(mGstThreadsMonitor); |
|
1077 /* We have a new video buffer queued in the video sink. Increment the counter |
|
1078 * and notify the decode thread potentially blocked in DecodeVideoFrame |
|
1079 */ |
|
1080 |
|
1081 mDecoder->NotifyDecodedFrames(1, 0); |
|
1082 mVideoSinkBufferCount++; |
|
1083 mon.NotifyAll(); |
|
1084 } |
|
1085 |
|
1086 void GStreamerReader::NewAudioBuffer() |
|
1087 { |
|
1088 ReentrantMonitorAutoEnter mon(mGstThreadsMonitor); |
|
1089 /* We have a new audio buffer queued in the audio sink. Increment the counter |
|
1090 * and notify the decode thread potentially blocked in DecodeAudioData |
|
1091 */ |
|
1092 mAudioSinkBufferCount++; |
|
1093 mon.NotifyAll(); |
|
1094 } |
|
1095 |
|
1096 void GStreamerReader::EosCb(GstAppSink* aSink, gpointer aUserData) |
|
1097 { |
|
1098 GStreamerReader* reader = reinterpret_cast<GStreamerReader*>(aUserData); |
|
1099 reader->Eos(aSink); |
|
1100 } |
|
1101 |
|
1102 void GStreamerReader::Eos(GstAppSink* aSink) |
|
1103 { |
|
1104 /* We reached the end of the stream */ |
|
1105 { |
|
1106 ReentrantMonitorAutoEnter mon(mGstThreadsMonitor); |
|
1107 /* Potentially unblock DecodeVideoFrame and DecodeAudioData */ |
|
1108 if (aSink == mVideoAppSink) { |
|
1109 mReachedVideoEos = true; |
|
1110 } else if (aSink == mAudioAppSink) { |
|
1111 mReachedAudioEos = true; |
|
1112 } else { |
|
1113 // Assume this is an error causing an EOS. |
|
1114 mReachedAudioEos = true; |
|
1115 mReachedVideoEos = true; |
|
1116 } |
|
1117 mon.NotifyAll(); |
|
1118 } |
|
1119 |
|
1120 { |
|
1121 ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); |
|
1122 /* Potentially unblock the decode thread in ::DecodeLoop */ |
|
1123 mon.NotifyAll(); |
|
1124 } |
|
1125 } |
|
1126 |
|
1127 /** |
|
1128 * This callback is called while the pipeline is automatically built, after a |
|
1129 * new element has been added to the pipeline. We use it to find the |
|
1130 * uridecodebin instance used by playbin and connect to it to apply our |
|
1131 * whitelist. |
|
1132 */ |
|
1133 void |
|
1134 GStreamerReader::PlayElementAddedCb(GstBin *aBin, GstElement *aElement, |
|
1135 gpointer *aUserData) |
|
1136 { |
|
1137 const static char sUriDecodeBinPrefix[] = "uridecodebin"; |
|
1138 gchar *name = gst_element_get_name(aElement); |
|
1139 |
|
1140 // Attach this callback to uridecodebin, child of playbin. |
|
1141 if (!strncmp(name, sUriDecodeBinPrefix, sizeof(sUriDecodeBinPrefix) - 1)) { |
|
1142 g_signal_connect(G_OBJECT(aElement), "autoplug-sort", |
|
1143 G_CALLBACK(GStreamerReader::AutoplugSortCb), aUserData); |
|
1144 } |
|
1145 |
|
1146 g_free(name); |
|
1147 } |
|
1148 |
|
1149 bool |
|
1150 GStreamerReader::ShouldAutoplugFactory(GstElementFactory* aFactory, GstCaps* aCaps) |
|
1151 { |
|
1152 bool autoplug; |
|
1153 const gchar *klass = gst_element_factory_get_klass(aFactory); |
|
1154 if (strstr(klass, "Demuxer") && !strstr(klass, "Metadata")) { |
|
1155 autoplug = GStreamerFormatHelper::Instance()->CanHandleContainerCaps(aCaps); |
|
1156 } else if (strstr(klass, "Decoder") && !strstr(klass, "Generic")) { |
|
1157 autoplug = GStreamerFormatHelper::Instance()->CanHandleCodecCaps(aCaps); |
|
1158 } else { |
|
1159 /* we only filter demuxers and decoders, let everything else be autoplugged */ |
|
1160 autoplug = true; |
|
1161 } |
|
1162 |
|
1163 return autoplug; |
|
1164 } |
|
1165 |
|
1166 /** |
|
1167 * This is called by uridecodebin (running inside playbin), after it has found |
|
1168 * candidate factories to continue decoding the stream. We apply the whitelist |
|
1169 * here, allowing only demuxers and decoders that output the formats we want to |
|
1170 * support. |
|
1171 */ |
|
1172 GValueArray* |
|
1173 GStreamerReader::AutoplugSortCb(GstElement* aElement, GstPad* aPad, |
|
1174 GstCaps* aCaps, GValueArray* aFactories) |
|
1175 { |
|
1176 if (!aFactories->n_values) { |
|
1177 return nullptr; |
|
1178 } |
|
1179 |
|
1180 /* aFactories[0] is the element factory that is going to be used to |
|
1181 * create the next element needed to demux or decode the stream. |
|
1182 */ |
|
1183 GstElementFactory *factory = (GstElementFactory*) g_value_get_object(g_value_array_get_nth(aFactories, 0)); |
|
1184 if (!ShouldAutoplugFactory(factory, aCaps)) { |
|
1185 /* We don't support this factory. Return an empty array to signal that we |
|
1186 * don't want to continue decoding this (sub)stream. |
|
1187 */ |
|
1188 return g_value_array_new(0); |
|
1189 } |
|
1190 |
|
1191 /* nullptr means that we're ok with the candidates and don't need to apply any |
|
1192 * sorting/filtering. |
|
1193 */ |
|
1194 return nullptr; |
|
1195 } |
|
1196 |
|
1197 /** |
|
1198 * If this is an MP3 stream, pass any new data we get to the MP3 frame parser |
|
1199 * for duration estimation. |
|
1200 */ |
|
1201 void GStreamerReader::NotifyDataArrived(const char *aBuffer, |
|
1202 uint32_t aLength, |
|
1203 int64_t aOffset) |
|
1204 { |
|
1205 MOZ_ASSERT(NS_IsMainThread()); |
|
1206 |
|
1207 if (HasVideo()) { |
|
1208 return; |
|
1209 } |
|
1210 |
|
1211 if (!mMP3FrameParser.NeedsData()) { |
|
1212 return; |
|
1213 } |
|
1214 |
|
1215 mMP3FrameParser.Parse(aBuffer, aLength, aOffset); |
|
1216 |
|
1217 int64_t duration = mMP3FrameParser.GetDuration(); |
|
1218 if (duration != mLastParserDuration && mUseParserDuration) { |
|
1219 ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); |
|
1220 mLastParserDuration = duration; |
|
1221 mDecoder->UpdateEstimatedMediaDuration(mLastParserDuration); |
|
1222 } |
|
1223 } |
|
1224 |
|
1225 #if GST_VERSION_MAJOR >= 1 |
|
1226 GstCaps* GStreamerReader::BuildAudioSinkCaps() |
|
1227 { |
|
1228 GstCaps* caps = gst_caps_from_string("audio/x-raw, channels={1,2}"); |
|
1229 const char* format; |
|
1230 #ifdef MOZ_SAMPLE_TYPE_FLOAT32 |
|
1231 #if MOZ_LITTLE_ENDIAN |
|
1232 format = "F32LE"; |
|
1233 #else |
|
1234 format = "F32BE"; |
|
1235 #endif |
|
1236 #else /* !MOZ_SAMPLE_TYPE_FLOAT32 */ |
|
1237 #if MOZ_LITTLE_ENDIAN |
|
1238 format = "S16LE"; |
|
1239 #else |
|
1240 format = "S16BE"; |
|
1241 #endif |
|
1242 #endif |
|
1243 gst_caps_set_simple(caps, "format", G_TYPE_STRING, format, nullptr); |
|
1244 |
|
1245 return caps; |
|
1246 } |
|
1247 |
|
1248 void GStreamerReader::InstallPadCallbacks() |
|
1249 { |
|
1250 GstPad* sinkpad = gst_element_get_static_pad(GST_ELEMENT(mVideoAppSink), "sink"); |
|
1251 |
|
1252 gst_pad_add_probe(sinkpad, |
|
1253 (GstPadProbeType) (GST_PAD_PROBE_TYPE_SCHEDULING | |
|
1254 GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM | |
|
1255 GST_PAD_PROBE_TYPE_EVENT_UPSTREAM | |
|
1256 GST_PAD_PROBE_TYPE_EVENT_FLUSH), |
|
1257 &GStreamerReader::EventProbeCb, this, nullptr); |
|
1258 gst_pad_add_probe(sinkpad, GST_PAD_PROBE_TYPE_QUERY_DOWNSTREAM, |
|
1259 GStreamerReader::QueryProbeCb, nullptr, nullptr); |
|
1260 |
|
1261 gst_pad_set_element_private(sinkpad, this); |
|
1262 gst_object_unref(sinkpad); |
|
1263 |
|
1264 sinkpad = gst_element_get_static_pad(GST_ELEMENT(mAudioAppSink), "sink"); |
|
1265 gst_pad_add_probe(sinkpad, |
|
1266 (GstPadProbeType) (GST_PAD_PROBE_TYPE_SCHEDULING | |
|
1267 GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM | |
|
1268 GST_PAD_PROBE_TYPE_EVENT_UPSTREAM | |
|
1269 GST_PAD_PROBE_TYPE_EVENT_FLUSH), |
|
1270 &GStreamerReader::EventProbeCb, this, nullptr); |
|
1271 gst_object_unref(sinkpad); |
|
1272 } |
|
1273 |
|
1274 GstPadProbeReturn GStreamerReader::EventProbeCb(GstPad *aPad, |
|
1275 GstPadProbeInfo *aInfo, |
|
1276 gpointer aUserData) |
|
1277 { |
|
1278 GStreamerReader *reader = (GStreamerReader *) aUserData; |
|
1279 GstEvent *aEvent = (GstEvent *)aInfo->data; |
|
1280 return reader->EventProbe(aPad, aEvent); |
|
1281 } |
|
1282 |
|
1283 GstPadProbeReturn GStreamerReader::EventProbe(GstPad *aPad, GstEvent *aEvent) |
|
1284 { |
|
1285 GstElement* parent = GST_ELEMENT(gst_pad_get_parent(aPad)); |
|
1286 |
|
1287 LOG(PR_LOG_DEBUG, "event probe %s", GST_EVENT_TYPE_NAME (aEvent)); |
|
1288 |
|
1289 switch(GST_EVENT_TYPE(aEvent)) { |
|
1290 case GST_EVENT_SEGMENT: |
|
1291 { |
|
1292 const GstSegment *newSegment; |
|
1293 GstSegment* segment; |
|
1294 |
|
1295 /* Store the segments so we can convert timestamps to stream time, which |
|
1296 * is what the upper layers sync on. |
|
1297 */ |
|
1298 ReentrantMonitorAutoEnter mon(mGstThreadsMonitor); |
|
1299 #if GST_VERSION_MINOR <= 1 && GST_VERSION_MICRO < 1 |
|
1300 ResetDecode(); |
|
1301 #endif |
|
1302 gst_event_parse_segment(aEvent, &newSegment); |
|
1303 if (parent == GST_ELEMENT(mVideoAppSink)) |
|
1304 segment = &mVideoSegment; |
|
1305 else |
|
1306 segment = &mAudioSegment; |
|
1307 gst_segment_copy_into (newSegment, segment); |
|
1308 break; |
|
1309 } |
|
1310 case GST_EVENT_FLUSH_STOP: |
|
1311 /* Reset on seeks */ |
|
1312 ResetDecode(); |
|
1313 break; |
|
1314 default: |
|
1315 break; |
|
1316 } |
|
1317 gst_object_unref(parent); |
|
1318 |
|
1319 return GST_PAD_PROBE_OK; |
|
1320 } |
|
1321 |
|
1322 GstPadProbeReturn GStreamerReader::QueryProbeCb(GstPad* aPad, GstPadProbeInfo* aInfo, gpointer aUserData) |
|
1323 { |
|
1324 GStreamerReader* reader = reinterpret_cast<GStreamerReader*>(gst_pad_get_element_private(aPad)); |
|
1325 return reader->QueryProbe(aPad, aInfo, aUserData); |
|
1326 } |
|
1327 |
|
1328 GstPadProbeReturn GStreamerReader::QueryProbe(GstPad* aPad, GstPadProbeInfo* aInfo, gpointer aUserData) |
|
1329 { |
|
1330 GstQuery *query = gst_pad_probe_info_get_query(aInfo); |
|
1331 GstPadProbeReturn ret = GST_PAD_PROBE_OK; |
|
1332 |
|
1333 switch (GST_QUERY_TYPE (query)) { |
|
1334 case GST_QUERY_ALLOCATION: |
|
1335 GstCaps *caps; |
|
1336 GstVideoInfo info; |
|
1337 gboolean need_pool; |
|
1338 |
|
1339 gst_query_parse_allocation(query, &caps, &need_pool); |
|
1340 gst_video_info_init(&info); |
|
1341 gst_video_info_from_caps(&info, caps); |
|
1342 gst_query_add_allocation_param(query, mAllocator, nullptr); |
|
1343 gst_query_add_allocation_pool(query, mBufferPool, info.size, 0, 0); |
|
1344 break; |
|
1345 default: |
|
1346 break; |
|
1347 } |
|
1348 |
|
1349 return ret; |
|
1350 } |
|
1351 |
|
1352 void GStreamerReader::ImageDataFromVideoFrame(GstVideoFrame *aFrame, |
|
1353 PlanarYCbCrImage::Data *aData) |
|
1354 { |
|
1355 NS_ASSERTION(GST_VIDEO_INFO_IS_YUV(&mVideoInfo), |
|
1356 "Non-YUV video frame formats not supported"); |
|
1357 NS_ASSERTION(GST_VIDEO_FRAME_N_COMPONENTS(aFrame) == 3, |
|
1358 "Unsupported number of components in video frame"); |
|
1359 |
|
1360 aData->mPicX = aData->mPicY = 0; |
|
1361 aData->mPicSize = gfx::IntSize(mPicture.width, mPicture.height); |
|
1362 aData->mStereoMode = StereoMode::MONO; |
|
1363 |
|
1364 aData->mYChannel = GST_VIDEO_FRAME_COMP_DATA(aFrame, 0); |
|
1365 aData->mYStride = GST_VIDEO_FRAME_COMP_STRIDE(aFrame, 0); |
|
1366 aData->mYSize = gfx::IntSize(GST_VIDEO_FRAME_COMP_WIDTH(aFrame, 0), |
|
1367 GST_VIDEO_FRAME_COMP_HEIGHT(aFrame, 0)); |
|
1368 aData->mYSkip = GST_VIDEO_FRAME_COMP_PSTRIDE(aFrame, 0) - 1; |
|
1369 aData->mCbCrStride = GST_VIDEO_FRAME_COMP_STRIDE(aFrame, 1); |
|
1370 aData->mCbCrSize = gfx::IntSize(GST_VIDEO_FRAME_COMP_WIDTH(aFrame, 1), |
|
1371 GST_VIDEO_FRAME_COMP_HEIGHT(aFrame, 1)); |
|
1372 aData->mCbChannel = GST_VIDEO_FRAME_COMP_DATA(aFrame, 1); |
|
1373 aData->mCrChannel = GST_VIDEO_FRAME_COMP_DATA(aFrame, 2); |
|
1374 aData->mCbSkip = GST_VIDEO_FRAME_COMP_PSTRIDE(aFrame, 1) - 1; |
|
1375 aData->mCrSkip = GST_VIDEO_FRAME_COMP_PSTRIDE(aFrame, 2) - 1; |
|
1376 } |
|
1377 |
|
1378 nsRefPtr<PlanarYCbCrImage> GStreamerReader::GetImageFromBuffer(GstBuffer* aBuffer) |
|
1379 { |
|
1380 nsRefPtr<PlanarYCbCrImage> image = nullptr; |
|
1381 |
|
1382 if (gst_buffer_n_memory(aBuffer) == 1) { |
|
1383 GstMemory* mem = gst_buffer_peek_memory(aBuffer, 0); |
|
1384 if (GST_IS_MOZ_GFX_MEMORY_ALLOCATOR(mem->allocator)) { |
|
1385 image = moz_gfx_memory_get_image(mem); |
|
1386 |
|
1387 GstVideoFrame frame; |
|
1388 gst_video_frame_map(&frame, &mVideoInfo, aBuffer, GST_MAP_READ); |
|
1389 PlanarYCbCrImage::Data data; |
|
1390 ImageDataFromVideoFrame(&frame, &data); |
|
1391 image->SetDataNoCopy(data); |
|
1392 gst_video_frame_unmap(&frame); |
|
1393 } |
|
1394 } |
|
1395 |
|
1396 return image; |
|
1397 } |
|
1398 |
|
1399 void GStreamerReader::CopyIntoImageBuffer(GstBuffer* aBuffer, |
|
1400 GstBuffer** aOutBuffer, |
|
1401 nsRefPtr<PlanarYCbCrImage> &image) |
|
1402 { |
|
1403 *aOutBuffer = gst_buffer_new_allocate(mAllocator, gst_buffer_get_size(aBuffer), nullptr); |
|
1404 GstMemory *mem = gst_buffer_peek_memory(*aOutBuffer, 0); |
|
1405 GstMapInfo map_info; |
|
1406 gst_memory_map(mem, &map_info, GST_MAP_WRITE); |
|
1407 gst_buffer_extract(aBuffer, 0, map_info.data, gst_buffer_get_size(aBuffer)); |
|
1408 gst_memory_unmap(mem, &map_info); |
|
1409 |
|
1410 /* create a new gst buffer with the newly created memory and copy the |
|
1411 * metadata over from the incoming buffer */ |
|
1412 gst_buffer_copy_into(*aOutBuffer, aBuffer, |
|
1413 (GstBufferCopyFlags)(GST_BUFFER_COPY_METADATA), 0, -1); |
|
1414 image = GetImageFromBuffer(*aOutBuffer); |
|
1415 } |
|
1416 #endif |
|
1417 |
|
1418 } // namespace mozilla |
|
1419 |