big merge from master, fix rpm creation, drop fetching swfdec
[gnash.git] / libcore / parser / SWFMovieDefinition.cpp
blob6845ca91f972df0448dbefa444912b249e6126d3
1 // SWFMovieDefinition.cpp: load a SWF definition
2 //
3 // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010,
4 // 2011 Free Software Foundation, Inc
5 //
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
21 #ifdef HAVE_CONFIG_H
22 #include "gnashconfig.h" // USE_SWFTREE
23 #endif
25 #include "SWFMovieDefinition.h"
27 #include <boost/bind.hpp>
28 #include <boost/version.hpp>
29 #include <boost/thread.hpp>
30 #include <iomanip>
31 #include <memory>
32 #include <string>
33 #include <algorithm>
35 #include "GnashSleep.h"
36 #include "smart_ptr.h"
37 #include "movie_definition.h"
38 #include "zlib_adapter.h"
39 #include "IOChannel.h"
40 #include "SWFStream.h"
41 #include "RunResources.h"
42 #include "Font.h"
43 #include "VM.h"
44 #include "log.h"
45 #include "SWFMovie.h"
46 #include "GnashException.h" // for parser exception
47 #include "ControlTag.h"
48 #include "sound_definition.h" // for sound_sample
49 #include "GnashAlgorithm.h"
50 #include "SWFParser.h"
51 #include "Global_as.h"
52 #include "namedStrings.h"
53 #include "as_function.h"
54 #include "CachedBitmap.h"
55 #include "TypesParser.h"
56 #include "GnashImageJpeg.h"
58 // Debug frames load
59 #undef DEBUG_FRAMES_LOAD
61 // Define this this to load movies using a separate thread
62 // (undef and it will fully load a movie before starting to play it)
63 #define LOAD_MOVIES_IN_A_SEPARATE_THREAD 1
65 // Debug threads locking
66 //#undef DEBUG_THREADS_LOCKING
68 // Define this to get debugging output for symbol library use
69 //#define DEBUG_EXPORTS
71 namespace gnash
74 SWFMovieLoader::SWFMovieLoader(SWFMovieDefinition& md)
76 _movie_def(md),
77 _thread(NULL),
78 _barrier(2) // us and the main thread..
82 SWFMovieLoader::~SWFMovieLoader()
84 // we should assert _movie_def._loadingCanceled
85 // but we're not friend yet (anyone introduce us ?)
86 if ( _thread.get() )
88 //cout << "Joining thread.." << endl;
89 _thread->join();
93 bool
94 SWFMovieLoader::started() const
96 boost::mutex::scoped_lock lock(_mutex);
98 return _thread.get() != NULL;
101 bool
102 SWFMovieLoader::isSelfThread() const
104 boost::mutex::scoped_lock lock(_mutex);
106 if (!_thread.get()) {
107 return false;
109 #if BOOST_VERSION < 103500
110 boost::thread this_thread;
111 return this_thread == *_thread;
112 #else
113 return boost::this_thread::get_id() == _thread->get_id();
114 #endif
118 // static..
119 void
120 SWFMovieLoader::execute(SWFMovieLoader& ml, SWFMovieDefinition* md)
122 ml._barrier.wait(); // let _thread assignment happen before going on
123 md->read_all_swf();
126 bool
127 SWFMovieLoader::start()
129 #ifndef LOAD_MOVIES_IN_A_SEPARATE_THREAD
130 std::abort();
131 #endif
132 // don't start SWFMovieLoader thread() which rely
133 // on boost::thread() returning before they are executed. Therefore,
134 // we must employ locking.
135 // Those tests do seem a bit redundant, though...
136 boost::mutex::scoped_lock lock(_mutex);
138 _thread.reset(new boost::thread(boost::bind(
139 execute, boost::ref(*this), &_movie_def)));
141 _barrier.wait(); // let execution start befor returning
143 return true;
148 // SWFMovieDefinition
151 SWFMovieDefinition::SWFMovieDefinition(const RunResources& runResources)
153 m_frame_rate(30.0f),
154 m_frame_count(0u),
155 m_version(0),
156 _frames_loaded(0u),
157 _waiting_for_frame(0),
158 m_loading_sound_stream(-1),
159 m_file_length(0),
160 m_jpeg_in(0),
161 _loader(*this),
162 _loadingCanceled(false),
163 _runResources(runResources),
164 _as3(false)
168 SWFMovieDefinition::~SWFMovieDefinition()
170 // Request cancellation of the loading thread
171 _loadingCanceled = true;
174 void
175 SWFMovieDefinition::addDisplayObject(boost::uint16_t id, SWF::DefinitionTag* c)
177 assert(c);
178 boost::mutex::scoped_lock lock(_dictionaryMutex);
179 _dictionary.addDisplayObject(id, c);
180 addControlTag(c);
183 SWF::DefinitionTag*
184 SWFMovieDefinition::getDefinitionTag(boost::uint16_t id) const
186 boost::mutex::scoped_lock lock(_dictionaryMutex);
187 boost::intrusive_ptr<SWF::DefinitionTag> ch =
188 _dictionary.getDisplayObject(id);
189 return ch.get();
192 void
193 SWFMovieDefinition::add_font(int font_id, boost::intrusive_ptr<Font> f)
195 assert(f);
196 m_fonts.insert(std::make_pair(font_id, f));
199 Font*
200 SWFMovieDefinition::get_font(int font_id) const
203 FontMap::const_iterator it = m_fonts.find(font_id);
204 if ( it == m_fonts.end() ) return NULL;
205 boost::intrusive_ptr<Font> f = it->second;
206 assert(f->get_ref_count() > 1);
207 return f.get();
210 Font*
211 SWFMovieDefinition::get_font(const std::string& name, bool bold, bool italic)
212 const
215 for (FontMap::const_iterator it=m_fonts.begin(), itEnd=m_fonts.end(); it != itEnd; ++it)
217 Font* f = it->second.get();
218 if ( f->matches(name, bold, italic) ) return f;
220 return 0;
223 CachedBitmap*
224 SWFMovieDefinition::getBitmap(int id) const
226 const Bitmaps::const_iterator it = _bitmaps.find(id);
227 if (it == _bitmaps.end()) return 0;
228 return it->second.get();
231 void
232 SWFMovieDefinition::addBitmap(int id, boost::intrusive_ptr<CachedBitmap> im)
234 assert(im);
235 _bitmaps.insert(std::make_pair(id, im));
238 sound_sample*
239 SWFMovieDefinition::get_sound_sample(int id) const
241 SoundSampleMap::const_iterator it = m_sound_samples.find(id);
242 if (it == m_sound_samples.end()) return 0;
244 boost::intrusive_ptr<sound_sample> ch = it->second;
246 return ch.get();
249 void
250 SWFMovieDefinition::add_sound_sample(int id, sound_sample* sam)
252 assert(sam);
253 IF_VERBOSE_PARSE(
254 log_parse(_("Add sound sample %d assigning id %d"),
255 id, sam->m_sound_handler_id);
257 m_sound_samples.insert(std::make_pair(id,
258 boost::intrusive_ptr<sound_sample>(sam)));
261 // Read header and assign url
262 bool
263 SWFMovieDefinition::readHeader(std::auto_ptr<IOChannel> in,
264 const std::string& url)
267 _in = in;
269 // we only read a movie once
270 assert(!_str.get());
272 _url = url.empty() ? "<anonymous>" : url;
274 boost::uint32_t file_start_pos = _in->tell();
275 boost::uint32_t header = _in->read_le32();
276 m_file_length = _in->read_le32();
277 _swf_end_pos = file_start_pos + m_file_length;
279 m_version = (header >> 24) & 255;
280 if ((header & 0x0FFFFFF) != 0x00535746
281 && (header & 0x0FFFFFF) != 0x00535743) {
282 // ERROR
283 log_error(_("gnash::SWFMovieDefinition::read() -- "
284 "file does not start with a SWF header"));
285 return false;
287 const bool compressed = (header & 255) == 'C';
289 IF_VERBOSE_PARSE(
290 log_parse(_("version: %d, file_length: %d"), m_version, m_file_length);
293 if (compressed) {
294 #ifndef HAVE_ZLIB_H
295 log_error(_("SWFMovieDefinition::read(): unable to read "
296 "zipped SWF data; gnash was compiled without zlib support"));
297 return false;
298 #else
299 IF_VERBOSE_PARSE(
300 log_parse(_("file is compressed"));
303 // Uncompress the input as we read it.
304 _in = zlib_adapter::make_inflater(_in);
305 #endif
308 assert(_in.get());
310 _str.reset(new SWFStream(_in.get()));
312 m_frame_size = readRect(*_str);
314 // If the SWFRect is malformed, SWFRect::read would already
315 // print an error. We check again here just to give
316 // the error are better context.
317 if (m_frame_size.is_null()) {
318 IF_VERBOSE_MALFORMED_SWF(
319 log_swferror("non-finite movie bounds");
323 _str->ensureBytes(2 + 2); // frame rate, frame count.
324 m_frame_rate = _str->read_u16() / 256.0f;
325 if (!m_frame_rate) {
326 m_frame_rate = std::numeric_limits<boost::uint16_t>::max();
329 m_frame_count = _str->read_u16();
331 // TODO: This seems dangerous, check closely
332 if (!m_frame_count) ++m_frame_count;
334 IF_VERBOSE_PARSE(
335 log_parse(_("frame size = %s, frame rate = %f, frames = %d"),
336 m_frame_size, m_frame_rate, m_frame_count);
339 setBytesLoaded(_str->tell());
340 return true;
343 // Fire up the loading thread
344 bool
345 SWFMovieDefinition::completeLoad()
348 // should call this only once
349 assert( ! _loader.started() );
351 // should call readHeader before this
352 assert(_str.get());
354 #ifdef LOAD_MOVIES_IN_A_SEPARATE_THREAD
356 // Start the loading frame
357 if ( ! _loader.start() )
359 log_error(_("Could not start loading thread"));
360 return false;
363 // Wait until 'startup_frames' have been loaded
364 size_t startup_frames = 0;
365 ensure_frame_loaded(startup_frames);
367 #else // undef LOAD_MOVIES_IN_A_SEPARATE_THREAD
369 read_all_swf();
370 #endif
372 return true;
376 // 1-based frame number
377 bool
378 SWFMovieDefinition::ensure_frame_loaded(size_t framenum) const
380 boost::mutex::scoped_lock lock(_frames_loaded_mutex);
382 #ifndef LOAD_MOVIES_IN_A_SEPARATE_THREAD
383 return (framenum <= _frames_loaded);
384 #endif
386 if ( framenum <= _frames_loaded ) return true;
388 _waiting_for_frame = framenum;
390 // TODO: return false on timeout
391 _frame_reached_condition.wait(lock);
393 return ( framenum <= _frames_loaded );
396 Movie*
397 SWFMovieDefinition::createMovie(Global_as& gl, DisplayObject* parent)
399 as_object* o = getObjectWithPrototype(gl, NSV::CLASS_MOVIE_CLIP);
400 return new SWFMovie(o, this, parent);
405 // CharacterDictionary
408 std::ostream&
409 operator<<(std::ostream& o, const CharacterDictionary& cd)
412 for (CharacterDictionary::CharacterConstIterator it = cd.begin(),
413 endIt = cd.end(); it != endIt; it++)
415 o << std::endl
416 << "Character: " << it->first
417 << " at address: " << static_cast<void*>(it->second.get());
420 return o;
423 boost::intrusive_ptr<SWF::DefinitionTag>
424 CharacterDictionary::getDisplayObject(int id) const
426 CharacterConstIterator it = _map.find(id);
427 if ( it == _map.end() )
429 IF_VERBOSE_PARSE(
430 log_parse(_("Could not find char %d, dump is: %s"), id, *this);
432 return boost::intrusive_ptr<SWF::DefinitionTag>();
435 return it->second;
438 void
439 CharacterDictionary::addDisplayObject(int id,
440 boost::intrusive_ptr<SWF::DefinitionTag> c)
442 _map[id] = c;
446 void
447 SWFMovieDefinition::read_all_swf()
449 assert(_str.get());
451 #ifdef LOAD_MOVIES_IN_A_SEPARATE_THREAD
452 assert( _loader.isSelfThread() );
453 assert( _loader.started() );
454 #else
455 assert( ! _loader.started() );
456 assert( ! _loader.isSelfThread() );
457 #endif
459 SWFParser parser(*_str, this, _runResources);
461 const size_t startPos = _str->tell();
462 assert (startPos <= _swf_end_pos);
464 size_t left = _swf_end_pos - startPos;
466 const size_t chunkSize = 65535;
468 try {
469 while (left) {
471 if (_loadingCanceled) {
472 log_debug("Loading thread cancellation requested, "
473 "returning from read_all_swf");
474 return;
476 if (!parser.read(std::min<size_t>(left, chunkSize))) break;
478 left -= parser.bytesRead();
479 setBytesLoaded(startPos + parser.bytesRead());
482 // Make sure we won't leave any pending writers
483 // on any eventual fd-based IOChannel.
484 _str->consumeInput();
487 catch (const ParserException& e) {
488 // This is a fatal parser error.
489 log_error(_("Error while parsing SWF stream."));
492 // Set bytesLoaded to the current stream position unless it's greater
493 // than the reported length. TODO: should we be trying to continue
494 // parsing after an exception?
495 setBytesLoaded(std::min<size_t>(_str->tell(), _swf_end_pos));
497 size_t floaded = get_loading_frame();
498 if (!m_playlist[floaded].empty())
500 IF_VERBOSE_MALFORMED_SWF(
501 log_swferror(_("%d control tags are NOT followed by"
502 " a SHOWFRAME tag"), m_playlist[floaded].size());
506 if ( m_frame_count > floaded )
508 IF_VERBOSE_MALFORMED_SWF(
509 log_swferror(_("%d frames advertised in header, but only %d "
510 "SHOWFRAME tags found in stream. Pretending we loaded "
511 "all advertised frames"), m_frame_count, floaded);
513 boost::mutex::scoped_lock lock(_frames_loaded_mutex);
514 _frames_loaded = m_frame_count;
515 // Notify any thread waiting on frame reached condition
516 _frame_reached_condition.notify_all();
520 size_t
521 SWFMovieDefinition::get_loading_frame() const
523 boost::mutex::scoped_lock lock(_frames_loaded_mutex);
524 return _frames_loaded;
527 void
528 SWFMovieDefinition::incrementLoadedFrames()
530 boost::mutex::scoped_lock lock(_frames_loaded_mutex);
532 ++_frames_loaded;
534 if ( _frames_loaded > m_frame_count )
536 IF_VERBOSE_MALFORMED_SWF(
537 log_swferror(_("number of SHOWFRAME tags "
538 "in SWF stream '%s' (%d) exceeds "
539 "the advertised number in header (%d)."),
540 get_url(), _frames_loaded,
541 m_frame_count);
545 #ifdef DEBUG_FRAMES_LOAD
546 log_debug("Loaded frame %u/%u", _frames_loaded, m_frame_count);
547 #endif
549 // signal load of frame if anyone requested it
550 // FIXME: _waiting_for_frame needs mutex ?
551 if (_waiting_for_frame && _frames_loaded >= _waiting_for_frame )
553 // or should we notify_one ?
554 // See: http://boost.org/doc/html/condition.html
555 _frame_reached_condition.notify_all();
560 void
561 SWFMovieDefinition::registerExport(const std::string& symbol,
562 boost::uint16_t id)
564 assert(id);
566 boost::mutex::scoped_lock lock(_exportedResourcesMutex);
567 #ifdef DEBUG_EXPORTS
568 log_debug("%s registering export %s, %s", get_url(), symbol, id);
569 #endif
570 _exportTable[symbol] = id;
574 void
575 SWFMovieDefinition::add_frame_name(const std::string& n)
577 boost::mutex::scoped_lock lock1(_namedFramesMutex);
578 boost::mutex::scoped_lock lock2(_frames_loaded_mutex);
580 _namedFrames.insert(std::make_pair(n, _frames_loaded));
583 bool
584 SWFMovieDefinition::get_labeled_frame(const std::string& label,
585 size_t& frame_number) const
587 boost::mutex::scoped_lock lock(_namedFramesMutex);
588 NamedFrameMap::const_iterator it = _namedFrames.find(label);
589 if (it == _namedFrames.end()) return false;
590 frame_number = it->second;
591 return true;
594 void
595 SWFMovieDefinition::set_jpeg_loader(std::auto_ptr<image::JpegInput> j_in)
597 if (m_jpeg_in.get()) {
598 /// There should be only one JPEGTABLES tag in an SWF (see:
599 /// http://www.m2osw.com/en/swf_alexref.html#tag_jpegtables)
600 /// Discard any subsequent attempts to set the jpeg loader
601 /// to avoid crashing on very malformed SWFs. (No conclusive tests
602 /// for pp behaviour, though one version also crashes out on the
603 /// malformed SWF that triggers this assert in Gnash).
604 log_swferror(_("More than one JPEGTABLES tag found: not "
605 "resetting JPEG loader"));
606 return;
608 m_jpeg_in = j_in;
611 boost::uint16_t
612 SWFMovieDefinition::exportID(const std::string& symbol) const
614 boost::mutex::scoped_lock lock(_exportedResourcesMutex);
615 Exports::const_iterator it = _exportTable.find(symbol);
616 return (it == _exportTable.end()) ? 0 : it->second;
620 void
621 SWFMovieDefinition::importResources(
622 boost::intrusive_ptr<movie_definition> source, const Imports& imports)
624 size_t importedSyms = 0;
626 // Mutex scope.
628 for (Imports::const_iterator i = imports.begin(), e = imports.end(); i != e;
629 ++i) {
631 size_t new_loading_frame = source->get_loading_frame();
633 // 0.1 seconds.
634 const size_t naptime = 100000;
636 // Timeout after two seconds of NO frames progress
637 const size_t timeout_ms = 2000000;
638 const size_t def_timeout = timeout_ms / naptime;
640 size_t timeout = def_timeout;
641 size_t loading_frame = (size_t)-1; // used to keep track of advancements
643 const int id = i->first;
644 const std::string& symbolName = i->second;
646 #ifdef DEBUG_EXPORTS
647 log_debug("%s importing %s from %s", get_url(), symbolName,
648 source->get_url());
649 #endif
650 boost::uint16_t targetID;
652 while(!(targetID = source->exportID(symbolName))) {
654 // We checked last (or past-last) advertised frame.
655 // TODO: this check should really be for a parser
656 // process being active or not, as SWF
657 // might advertise less frames then actually
658 // found in it...
660 if (new_loading_frame >= source->get_frame_count()) {
661 // Update of loading_frame is
662 // really just for the latter debugging output
663 loading_frame = new_loading_frame;
664 break;
667 // There's more frames to parse, go ahead
668 // TODO: this is still based on *advertised*
669 // number of frames, if SWF advertises
670 // more then actually found we'd be
671 // keep trying till timeout, see the
672 // other TODO above.
674 // We made frame progress since last iteration
675 // so sleep some and try again
676 if (new_loading_frame != loading_frame) {
677 #ifdef DEBUG_EXPORTS
678 log_debug("looking for exported resource: frame load "
679 "advancement (from %d to %d)",
680 loading_frame, new_loading_frame);
681 #endif
682 loading_frame = new_loading_frame;
683 timeout = def_timeout+1;
685 else if (!--timeout) {
686 // no progress since last run, and
687 // timeout reached: give up
688 break;
691 // take a breath to give other threads more time to advance
692 gnashSleep(naptime);
696 if ( ! targetID ) {
697 // timed out
698 if (!timeout) {
699 log_error("Timeout (%d milliseconds) seeking export "
700 "symbol %s in movie %s. Frames loaded %d/%d",
701 timeout_ms / 1000, symbolName,
702 source->get_url(),
703 loading_frame, source->get_frame_count());
705 else {
706 // eof
707 //assert(loading_frame >= m_frame_count);
708 log_error("No export symbol %s found in movie %s. "
709 "Frames loaded %d/%d",
710 symbolName, source->get_url(), loading_frame,
711 source->get_frame_count());
713 continue;
716 #ifdef DEBUG_EXPORTS
717 log_debug("Export symbol %s found in movie %s with targetID %d. "
718 "Frames loaded %d/%d",
719 symbolName, source->get_url(),
720 targetID,
721 loading_frame,
722 source->get_frame_count());
723 #endif
725 boost::intrusive_ptr<SWF::DefinitionTag> res =
726 source->getDefinitionTag(targetID);
727 if (res) {
728 // It's a character import.
729 addDisplayObject(id, res.get());
730 registerExport(symbolName, id);
731 ++importedSyms;
732 continue;
735 Font* f = source->get_font(id);
736 if (f) {
737 // It's a font import
738 add_font(id, f);
739 registerExport(symbolName, id);
740 ++importedSyms;
741 continue;
744 log_error(_("import error: could not find resource '%s' in "
745 "movie '%s'"), symbolName, source->get_url());
748 if (importedSyms) {
749 _importSources.insert(source);
753 } // namespace gnash