1 // MediaParser.cpp: Media file parser, for Gnash.
3 // Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016
4 // Free Software Foundation, Inc.
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 3 of the License, or
9 // (at your option) any later version.
11 // This program 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
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
22 #include "MediaParser.h"
28 #include "GnashSleep.h" // for usleep.
31 // Define this to get debugging output from MediaParser
32 //#define GNASH_DEBUG_MEDIAPARSER
37 MediaParser::MediaParser(std::unique_ptr
<IOChannel
> stream
)
39 _parsingComplete(false),
41 _stream(std::move(stream
)),
42 _bufferTime(100), // 100 ms
43 _parserThreadKillRequested(false),
50 MediaParser::startParserThread()
52 #ifdef LOAD_MEDIA_IN_A_SEPARATE_THREAD
53 log_debug("Starting MediaParser thread");
54 _parserThread
= std::thread(&MediaParser::parserLoop
, this);
59 MediaParser::getBufferLength() const
61 #ifdef LOAD_MEDIA_IN_A_SEPARATE_THREAD
62 std::lock_guard
<std::mutex
> lock(_qMutex
);
64 return getBufferLengthNoLock();
69 MediaParser::isBufferEmpty() const
71 #ifdef LOAD_MEDIA_IN_A_SEPARATE_THREAD
72 std::lock_guard
<std::mutex
> lock(_qMutex
);
74 return _videoFrames
.empty() && _audioFrames
.empty();
77 boost::optional
<Id3Info
>
78 MediaParser::getId3Info() const
80 log_error(_("No ID3 support implemented in this MediaParser"));
81 return boost::optional
<Id3Info
>();
85 MediaParser::getBufferLengthNoLock() const
87 bool hasVideo
= _videoInfo
.get();
88 bool hasAudio
= _audioInfo
.get();
90 //log_debug("MediaParser::getBufferLength: %d video %d audio frames", _videoFrames.size(), _audioFrames.size());
92 if (hasVideo
&& hasAudio
) {
93 return std::min(audioBufferLength(), videoBufferLength());
96 if (hasVideo
) return videoBufferLength();
98 if (hasAudio
) return audioBufferLength();
104 MediaParser::videoBufferLength() const
106 if (_videoFrames
.empty()) return 0;
108 log_debug("videoBufferLength: %d - %d == %d",
109 _videoFrames
.back()->timestamp(), _videoFrames
.front()->timestamp(),
110 _videoFrames
.back()->timestamp() - _videoFrames
.front()->timestamp());
112 return _videoFrames
.back()->timestamp() - _videoFrames
.front()->timestamp();
116 MediaParser::audioBufferLength() const
118 if (_audioFrames
.empty()) return 0;
120 log_debug("audioBufferLength: %d - %d == %d",
121 _audioFrames
.back()->timestamp
, _audioFrames
.front()->timestamp
,
122 _audioFrames
.back()->timestamp
- _audioFrames
.front()->timestamp
);
124 return _audioFrames
.back()->timestamp
- _audioFrames
.front()->timestamp
;
128 const EncodedVideoFrame
*
129 MediaParser::peekNextVideoFrame() const
131 #ifdef LOAD_MEDIA_IN_A_SEPARATE_THREAD
132 // TODO: assert _qMutex is locked by this thread
133 #else // ndef LOAD_MEDIA_IN_A_SEPARATE_THREAD
134 while (!parsingCompleted() && _videoInfo
.get() && _videoFrames
.empty())
136 const_cast<MediaParser
*>(this)->parseNextChunk();
140 if (!_videoInfo
.get() || _videoFrames
.empty()) return nullptr;
141 return _videoFrames
.front().get();
145 MediaParser::nextFrameTimestamp(std::uint64_t& ts
) const
147 #ifdef LOAD_MEDIA_IN_A_SEPARATE_THREAD
148 std::lock_guard
<std::mutex
> lock(_qMutex
);
149 #else // ndef LOAD_MEDIA_IN_A_SEPARATE_THREAD
150 while (!parsingCompleted() && _videoInfo
.get() && _videoFrames
.empty())
152 const_cast<MediaParser
*>(this)->parseNextChunk();
156 if (_videoFrames
.empty())
158 if (_audioFrames
.empty())
164 ts
= _audioFrames
.front()->timestamp
;
170 if (_audioFrames
.empty())
172 ts
= _videoFrames
.front()->timestamp();
177 ts
= std::min(_videoFrames
.front()->timestamp(),
178 _audioFrames
.front()->timestamp
);
185 MediaParser::nextVideoFrameTimestamp(std::uint64_t& ts
) const
187 #ifdef LOAD_MEDIA_IN_A_SEPARATE_THREAD
188 std::lock_guard
<std::mutex
> lock(_qMutex
);
189 #endif // def LOAD_MEDIA_IN_A_SEPARATE_THREAD
190 const EncodedVideoFrame
* ef
= peekNextVideoFrame();
191 if ( ! ef
) return false;
192 ts
= ef
->timestamp();
196 std::unique_ptr
<EncodedVideoFrame
>
197 MediaParser::nextVideoFrame()
199 #ifdef LOAD_MEDIA_IN_A_SEPARATE_THREAD
200 std::lock_guard
<std::mutex
> lock(_qMutex
);
201 #else // ndef LOAD_MEDIA_IN_A_SEPARATE_THREAD
202 while (!parsingCompleted() && _videoInfo
.get() && _videoFrames
.empty())
204 const_cast<MediaParser
*>(this)->parseNextChunk();
208 std::unique_ptr
<EncodedVideoFrame
> ret
;
209 if (_videoFrames
.empty()) return ret
;
210 ret
= std::move(_videoFrames
.front());
211 _videoFrames
.pop_front();
212 #ifdef GNASH_DEBUG_MEDIAPARSER
213 log_debug("nextVideoFrame: waking up parser (in case it was sleeping)");
214 #endif // GNASH_DEBUG_MEDIAPARSER
215 _parserThreadWakeup
.notify_all(); // wake it up, to refill the buffer, SHOULDN'T WE HOLD A LoCK HERE?
219 std::unique_ptr
<EncodedAudioFrame
>
220 MediaParser::nextAudioFrame()
222 #ifdef LOAD_MEDIA_IN_A_SEPARATE_THREAD
223 std::lock_guard
<std::mutex
> lock(_qMutex
);
224 #else // ndef LOAD_MEDIA_IN_A_SEPARATE_THREAD
225 while (!parsingCompleted() && _audioInfo
.get() && _audioFrames
.empty())
227 const_cast<MediaParser
*>(this)->parseNextChunk();
231 std::unique_ptr
<EncodedAudioFrame
> ret
;
232 if (_audioFrames
.empty()) return ret
;
233 ret
= std::move(_audioFrames
.front());
234 _audioFrames
.pop_front();
235 #ifdef GNASH_DEBUG_MEDIAPARSER
236 log_debug("nextAudioFrame: waking up parser (in case it was sleeping)");
237 #endif // GNASH_DEBUG_MEDIAPARSER
238 _parserThreadWakeup
.notify_all(); // wake it up, to refill the buffer, SHOULDN'T WE HOLD A LoCK HERE?
243 MediaParser::nextAudioFrameTimestamp(std::uint64_t& ts
) const
245 #ifdef LOAD_MEDIA_IN_A_SEPARATE_THREAD
246 std::lock_guard
<std::mutex
> lock(_qMutex
);
247 #endif // def LOAD_MEDIA_IN_A_SEPARATE_THREAD
248 const EncodedAudioFrame
* ef
= peekNextAudioFrame();
249 if ( ! ef
) return false;
255 const EncodedAudioFrame
*
256 MediaParser::peekNextAudioFrame() const
258 #ifdef LOAD_MEDIA_IN_A_SEPARATE_THREAD
259 // TODO: assert _qMutex is locked by this thread
260 #else // ndef LOAD_MEDIA_IN_A_SEPARATE_THREAD
261 while (!parsingCompleted() && _audioInfo
.get() && _audioFrames
.empty())
263 const_cast<MediaParser
*>(this)->parseNextChunk();
266 if (!_audioInfo
.get() || _audioFrames
.empty()) return nullptr;
267 return _audioFrames
.front().get();
271 MediaParser::stopParserThread()
273 if ( _parserThread
.joinable() )
275 requestParserThreadKill();
276 _parserThread
.join();
280 MediaParser::~MediaParser()
286 MediaParser::clearBuffers()
288 #ifdef LOAD_MEDIA_IN_A_SEPARATE_THREAD
289 std::lock_guard
<std::mutex
> lock(_qMutex
);
292 _audioFrames
.clear();
293 _videoFrames
.clear();
295 _parserThreadWakeup
.notify_all(); // wake it up, to refill the buffer
299 MediaParser::pushEncodedAudioFrame(std::unique_ptr
<EncodedAudioFrame
> frame
)
301 #ifdef LOAD_MEDIA_IN_A_SEPARATE_THREAD
302 std::unique_lock
<std::mutex
> lock(_qMutex
);
305 // Find location to insert this new frame to, so that
306 // timestamps are sorted
308 AudioFrames::iterator loc
= _audioFrames
.end();
309 if ( ! _audioFrames
.empty() ) {
311 AudioFrames::reverse_iterator i
=_audioFrames
.rbegin();
312 for (AudioFrames::reverse_iterator e
=_audioFrames
.rend(); i
!=e
; ++i
)
314 if ( (*i
)->timestamp
<= frame
->timestamp
) break;
321 log_debug("Timestamp of last %d/%d audio frames in queue "
322 "greater then timestamp in the frame being "
323 "inserted to it (%d).", gap
, _audioFrames
.size(),
328 //log_debug("Inserting audio frame with timestamp %d", frame->timestamp);
329 _audioFrames
.insert(loc
, std::move(frame
));
331 #ifdef LOAD_MEDIA_IN_A_SEPARATE_THREAD
332 // if the push reaches a "buffer full" condition, or if we find the parsing
333 // to be completed, wait to be waken up
339 MediaParser::pushEncodedVideoFrame(std::unique_ptr
<EncodedVideoFrame
> frame
)
341 #ifdef LOAD_MEDIA_IN_A_SEPARATE_THREAD
342 std::unique_lock
<std::mutex
> lock(_qMutex
);
345 // Find location to insert this new frame to, so that
346 // timestamps are sorted
348 VideoFrames::iterator loc
= _videoFrames
.end();
349 if ( ! _videoFrames
.empty() ) {
351 VideoFrames::reverse_iterator i
=_videoFrames
.rbegin();
352 for (VideoFrames::reverse_iterator e
=_videoFrames
.rend(); i
!=e
; ++i
)
354 if ( (*i
)->timestamp() <= frame
->timestamp() ) break;
361 log_debug("Timestamp of last %d/%d video frames in queue "
362 "greater then timestamp() in the frame being "
363 "inserted to it (%d).", gap
, _videoFrames
.size(),
368 //log_debug("Pushing video frame with timestamp %d", frame->timestamp());
369 _videoFrames
.insert(loc
, std::move(frame
));
371 #ifdef LOAD_MEDIA_IN_A_SEPARATE_THREAD
372 waitIfNeeded(lock
); // if the push reaches a "buffer full" condition, wait to be waken up
377 MediaParser::waitIfNeeded(std::unique_lock
<std::mutex
>& lock
)
379 // We hold a lock on the queue here...
380 bool pc
=parsingCompleted();
381 bool ic
=indexingCompleted();
382 bool bf
=bufferFull();
383 if (( pc
|| (bf
&& ic
)) && !parserThreadKillRequested()) // TODO: or seekRequested ?
385 #ifdef GNASH_DEBUG_MEDIAPARSER
386 log_debug("Parser thread waiting on wakeup lock, parsingComplete=%d, bufferFull=%d", pc
, bf
);
387 #endif // GNASH_DEBUG_MEDIAPARSER
388 _parserThreadWakeup
.wait(lock
);
389 #ifdef GNASH_DEBUG_MEDIAPARSER
390 log_debug("Parser thread finished waiting on wakeup lock");
391 #endif // GNASH_DEBUG_MEDIAPARSER
396 MediaParser::bufferFull() const
398 // Callers are expected to hold a lock on _qMutex
399 std::uint64_t bl
= getBufferLengthNoLock();
400 std::uint64_t bt
= getBufferTime();
401 #ifdef GNASH_DEBUG_MEDIAPARSER
402 log_debug("MediaParser::bufferFull: %d/%d", bl
, bt
);
403 #endif // GNASH_DEBUG_MEDIAPARSER
408 MediaParser::parserLoop()
410 while (!parserThreadKillRequested())
413 gnashSleep(100); // thread switch
415 // check for parsing complete
416 // TODO: have a setParsingComplete() function
417 // exposed in base class for taking care
418 // of this on appropriate time.
419 std::unique_lock
<std::mutex
> lock(_qMutex
);
426 MediaParser::fetchMetaTags(OrderedMetaTags
& /*tags*/, std::uint64_t /*ts*/)
432 operator<< (std::ostream
& os
, const VideoInfo
& vi
)
434 os
<< "codec:" << vi
.codec
<< " (type " << vi
.type
<< ") - "
435 << "size:" << vi
.width
<< "x" << vi
.height
<< " - "
436 << "frameRate:" << vi
.frameRate
<< " - "
437 << "duration:" << vi
.duration
;
442 operator<< (std::ostream
& os
, const videoCodecType
& t
)
446 case VIDEO_CODEC_H263
:
449 case VIDEO_CODEC_SCREENVIDEO
:
452 case VIDEO_CODEC_VP6
:
455 case VIDEO_CODEC_VP6A
:
458 case VIDEO_CODEC_SCREENVIDEO2
:
459 os
<< "Screenvideo2";
461 case VIDEO_CODEC_H264
:
465 os
<< "unknown/invalid codec " << static_cast<int>(t
);
471 } // end of gnash::media namespace
472 } // end of gnash namespace