1 // SWFMovieDefinition.cpp: load a SWF definition
3 // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010,
4 // 2011 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 "gnashconfig.h" // USE_SWFTREE
25 #include "SWFMovieDefinition.h"
27 #include <boost/bind.hpp>
28 #include <boost/version.hpp>
29 #include <boost/thread.hpp>
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"
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"
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
74 SWFMovieLoader::SWFMovieLoader(SWFMovieDefinition
& md
)
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 ?)
88 //cout << "Joining thread.." << endl;
94 SWFMovieLoader::started() const
96 boost::mutex::scoped_lock
lock(_mutex
);
98 return _thread
.get() != NULL
;
102 SWFMovieLoader::isSelfThread() const
104 boost::mutex::scoped_lock
lock(_mutex
);
106 if (!_thread
.get()) {
109 #if BOOST_VERSION < 103500
110 boost::thread this_thread
;
111 return this_thread
== *_thread
;
113 return boost::this_thread::get_id() == _thread
->get_id();
120 SWFMovieLoader::execute(SWFMovieLoader
& ml
, SWFMovieDefinition
* md
)
122 ml
._barrier
.wait(); // let _thread assignment happen before going on
127 SWFMovieLoader::start()
129 #ifndef LOAD_MOVIES_IN_A_SEPARATE_THREAD
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
148 // SWFMovieDefinition
151 SWFMovieDefinition::SWFMovieDefinition(const RunResources
& runResources
)
157 _waiting_for_frame(0),
158 m_loading_sound_stream(-1),
162 _loadingCanceled(false),
163 _runResources(runResources
),
168 SWFMovieDefinition::~SWFMovieDefinition()
170 // Request cancellation of the loading thread
171 _loadingCanceled
= true;
175 SWFMovieDefinition::addDisplayObject(boost::uint16_t id
, SWF::DefinitionTag
* c
)
178 boost::mutex::scoped_lock
lock(_dictionaryMutex
);
179 _dictionary
.addDisplayObject(id
, c
);
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
);
193 SWFMovieDefinition::add_font(int font_id
, boost::intrusive_ptr
<Font
> f
)
196 m_fonts
.insert(std::make_pair(font_id
, f
));
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);
211 SWFMovieDefinition::get_font(const std::string
& name
, bool bold
, bool italic
)
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
;
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();
232 SWFMovieDefinition::addBitmap(int id
, boost::intrusive_ptr
<CachedBitmap
> im
)
235 _bitmaps
.insert(std::make_pair(id
, im
));
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
;
250 SWFMovieDefinition::add_sound_sample(int id
, sound_sample
* sam
)
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
263 SWFMovieDefinition::readHeader(std::auto_ptr
<IOChannel
> in
,
264 const std::string
& url
)
269 // we only read a movie once
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) {
283 log_error(_("gnash::SWFMovieDefinition::read() -- "
284 "file does not start with a SWF header"));
287 const bool compressed
= (header
& 255) == 'C';
290 log_parse(_("version: %d, file_length: %d"), m_version
, m_file_length
);
295 log_error(_("SWFMovieDefinition::read(): unable to read "
296 "zipped SWF data; gnash was compiled without zlib support"));
300 log_parse(_("file is compressed"));
303 // Uncompress the input as we read it.
304 _in
= zlib_adapter::make_inflater(_in
);
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
;
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
;
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());
343 // Fire up the loading thread
345 SWFMovieDefinition::completeLoad()
348 // should call this only once
349 assert( ! _loader
.started() );
351 // should call readHeader before this
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"));
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
376 // 1-based frame number
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
);
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
);
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
409 operator<<(std::ostream
& o
, const CharacterDictionary
& cd
)
412 for (CharacterDictionary::CharacterConstIterator it
= cd
.begin(),
413 endIt
= cd
.end(); it
!= endIt
; it
++)
416 << "Character: " << it
->first
417 << " at address: " << static_cast<void*>(it
->second
.get());
423 boost::intrusive_ptr
<SWF::DefinitionTag
>
424 CharacterDictionary::getDisplayObject(int id
) const
426 CharacterConstIterator it
= _map
.find(id
);
427 if ( it
== _map
.end() )
430 log_parse(_("Could not find char %d, dump is: %s"), id
, *this);
432 return boost::intrusive_ptr
<SWF::DefinitionTag
>();
439 CharacterDictionary::addDisplayObject(int id
,
440 boost::intrusive_ptr
<SWF::DefinitionTag
> c
)
447 SWFMovieDefinition::read_all_swf()
451 #ifdef LOAD_MOVIES_IN_A_SEPARATE_THREAD
452 assert( _loader
.isSelfThread() );
453 assert( _loader
.started() );
455 assert( ! _loader
.started() );
456 assert( ! _loader
.isSelfThread() );
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;
471 if (_loadingCanceled
) {
472 log_debug("Loading thread cancellation requested, "
473 "returning from read_all_swf");
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();
521 SWFMovieDefinition::get_loading_frame() const
523 boost::mutex::scoped_lock
lock(_frames_loaded_mutex
);
524 return _frames_loaded
;
528 SWFMovieDefinition::incrementLoadedFrames()
530 boost::mutex::scoped_lock
lock(_frames_loaded_mutex
);
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
,
545 #ifdef DEBUG_FRAMES_LOAD
546 log_debug("Loaded frame %u/%u", _frames_loaded
, m_frame_count
);
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();
561 SWFMovieDefinition::registerExport(const std::string
& symbol
,
566 boost::mutex::scoped_lock
lock(_exportedResourcesMutex
);
568 log_debug("%s registering export %s, %s", get_url(), symbol
, id
);
570 _exportTable
[symbol
] = id
;
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
));
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
;
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"));
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
;
621 SWFMovieDefinition::importResources(
622 boost::intrusive_ptr
<movie_definition
> source
, const Imports
& imports
)
624 size_t importedSyms
= 0;
628 for (Imports::const_iterator i
= imports
.begin(), e
= imports
.end(); i
!= e
;
631 size_t new_loading_frame
= source
->get_loading_frame();
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
;
647 log_debug("%s importing %s from %s", get_url(), symbolName
,
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
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
;
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
674 // We made frame progress since last iteration
675 // so sleep some and try again
676 if (new_loading_frame
!= loading_frame
) {
678 log_debug("looking for exported resource: frame load "
679 "advancement (from %d to %d)",
680 loading_frame
, new_loading_frame
);
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
691 // take a breath to give other threads more time to advance
699 log_error("Timeout (%d milliseconds) seeking export "
700 "symbol %s in movie %s. Frames loaded %d/%d",
701 timeout_ms
/ 1000, symbolName
,
703 loading_frame
, source
->get_frame_count());
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());
717 log_debug("Export symbol %s found in movie %s with targetID %d. "
718 "Frames loaded %d/%d",
719 symbolName
, source
->get_url(),
722 source
->get_frame_count());
725 boost::intrusive_ptr
<SWF::DefinitionTag
> res
=
726 source
->getDefinitionTag(targetID
);
728 // It's a character import.
729 addDisplayObject(id
, res
.get());
730 registerExport(symbolName
, id
);
735 Font
* f
= source
->get_font(id
);
737 // It's a font import
739 registerExport(symbolName
, id
);
744 log_error(_("import error: could not find resource '%s' in "
745 "movie '%s'"), symbolName
, source
->get_url());
749 _importSources
.insert(source
);