1
// SWFMovieDefinition.cpp: load a SWF definition
3
// Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Free Software
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>
33
#include <algorithm> // std::make_pair
35
#include "GnashSleep.h"
36
#include "smart_ptr.h" // GNASH_USE_GC
37
#include "movie_definition.h" // for inheritance
38
#include "zlib_adapter.h"
39
#include "IOChannel.h" // for use
40
#include "SWFStream.h"
41
#include "GnashImageJpeg.h"
42
#include "RunResources.h"
47
#include "GnashException.h" // for parser exception
48
#include "ControlTag.h"
49
#include "sound_definition.h" // for sound_sample
50
#include "GnashAlgorithm.h"
51
#include "SWFParser.h"
52
#include "Global_as.h"
53
#include "namedStrings.h"
54
#include "as_function.h"
55
#include "CachedBitmap.h"
58
#undef DEBUG_FRAMES_LOAD
60
// Define this this to load movies using a separate thread
61
// (undef and it will fully load a movie before starting to play it)
62
#define LOAD_MOVIES_IN_A_SEPARATE_THREAD 1
64
// Debug threads locking
65
//#undef DEBUG_THREADS_LOCKING
67
// Define this to get debugging output for symbol library use
68
//#define DEBUG_EXPORTS
73
SWFMovieLoader::SWFMovieLoader(SWFMovieDefinition& md)
77
_barrier(2) // us and the main thread..
81
SWFMovieLoader::~SWFMovieLoader()
83
// we should assert _movie_def._loadingCanceled
84
// but we're not friend yet (anyone introduce us ?)
87
//cout << "Joining thread.." << endl;
93
SWFMovieLoader::started() const
95
boost::mutex::scoped_lock lock(_mutex);
97
return _thread.get() != NULL;
101
SWFMovieLoader::isSelfThread() const
103
boost::mutex::scoped_lock lock(_mutex);
105
if (!_thread.get()) {
108
#if BOOST_VERSION < 103500
109
boost::thread this_thread;
110
return this_thread == *_thread;
112
return boost::this_thread::get_id() == _thread->get_id();
119
SWFMovieLoader::execute(SWFMovieLoader& ml, SWFMovieDefinition* md)
121
ml._barrier.wait(); // let _thread assignment happen before going on
126
SWFMovieLoader::start()
128
#ifndef LOAD_MOVIES_IN_A_SEPARATE_THREAD
131
// don't start SWFMovieLoader thread() which rely
132
// on boost::thread() returning before they are executed. Therefore,
133
// we must employ locking.
134
// Those tests do seem a bit redundant, though...
135
boost::mutex::scoped_lock lock(_mutex);
137
_thread.reset(new boost::thread(boost::bind(
138
execute, boost::ref(*this), &_movie_def)));
140
_barrier.wait(); // let execution start befor returning
147
// SWFMovieDefinition
150
SWFMovieDefinition::SWFMovieDefinition(const RunResources& runResources)
156
_waiting_for_frame(0),
157
m_loading_sound_stream(-1),
161
_loadingCanceled(false),
162
_runResources(runResources),
167
SWFMovieDefinition::~SWFMovieDefinition()
169
// Request cancelation of the loading thread
170
_loadingCanceled = true;
174
SWFMovieDefinition::addDisplayObject(boost::uint16_t id, SWF::DefinitionTag* c)
177
boost::mutex::scoped_lock lock(_dictionaryMutex);
178
_dictionary.addDisplayObject(id, c);
183
SWFMovieDefinition::getDefinitionTag(boost::uint16_t id) const
186
boost::mutex::scoped_lock lock(_dictionaryMutex);
188
boost::intrusive_ptr<SWF::DefinitionTag> ch =
189
_dictionary.getDisplayObject(id);
191
assert(ch == NULL || ch->get_ref_count() > 1);
197
SWFMovieDefinition::add_font(int font_id, Font* f)
200
m_fonts.insert(std::make_pair(font_id, boost::intrusive_ptr<Font>(f)));
204
SWFMovieDefinition::get_font(int font_id) const
207
FontMap::const_iterator it = m_fonts.find(font_id);
208
if ( it == m_fonts.end() ) return NULL;
209
boost::intrusive_ptr<Font> f = it->second;
210
assert(f->get_ref_count() > 1);
215
SWFMovieDefinition::get_font(const std::string& name, bool bold, bool italic)
219
for (FontMap::const_iterator it=m_fonts.begin(), itEnd=m_fonts.end(); it != itEnd; ++it)
221
Font* f = it->second.get();
222
if ( f->matches(name, bold, italic) ) return f;
228
SWFMovieDefinition::getBitmap(int id) const
230
const Bitmaps::const_iterator it = _bitmaps.find(id);
231
if (it == _bitmaps.end()) return 0;
232
return it->second.get();
236
SWFMovieDefinition::addBitmap(int id, boost::intrusive_ptr<CachedBitmap> im)
239
_bitmaps.insert(std::make_pair(id, im));
243
SWFMovieDefinition::get_sound_sample(int id) const
245
SoundSampleMap::const_iterator it = m_sound_samples.find(id);
246
if ( it == m_sound_samples.end() ) return 0;
248
boost::intrusive_ptr<sound_sample> ch = it->second;
250
assert(ch->get_ref_count() > 1);
256
void SWFMovieDefinition::add_sound_sample(int id, sound_sample* sam)
260
log_parse(_("Add sound sample %d assigning id %d"),
261
id, sam->m_sound_handler_id);
263
m_sound_samples.insert(std::make_pair(id,
264
boost::intrusive_ptr<sound_sample>(sam)));
267
// Read header and assign url
269
SWFMovieDefinition::readHeader(std::auto_ptr<IOChannel> in,
270
const std::string& url)
275
// we only read a movie once
278
_url = url.empty() ? "<anonymous>" : url;
280
boost::uint32_t file_start_pos = _in->tell();
281
boost::uint32_t header = _in->read_le32();
282
m_file_length = _in->read_le32();
283
_swf_end_pos = file_start_pos + m_file_length;
285
m_version = (header >> 24) & 255;
286
if ((header & 0x0FFFFFF) != 0x00535746
287
&& (header & 0x0FFFFFF) != 0x00535743)
290
log_error(_("gnash::SWFMovieDefinition::read() -- "
291
"file does not start with a SWF header"));
294
const bool compressed = (header & 255) == 'C';
297
log_parse(_("version: %d, file_length: %d"), m_version, m_file_length);
302
log_unimpl(_("SWF%d is not fully supported, trying anyway "
303
"but don't expect it to work"), m_version);
308
log_error(_("SWFMovieDefinition::read(): unable to read "
309
"zipped SWF data; gnash was compiled without zlib support"));
313
log_parse(_("file is compressed"));
316
// Uncompress the input as we read it.
317
_in = zlib_adapter::make_inflater(_in);
323
_str.reset(new SWFStream(_in.get()));
325
m_frame_size.read(*_str);
326
// If the SWFRect is malformed, SWFRect::read would already
327
// print an error. We check again here just to give
328
// the error are better context.
329
if ( m_frame_size.is_null() )
331
IF_VERBOSE_MALFORMED_SWF(
332
log_swferror("non-finite movie bounds");
336
_str->ensureBytes(2 + 2); // frame rate, frame count.
337
m_frame_rate = _str->read_u16() / 256.0f;
339
m_frame_rate = std::numeric_limits<boost::uint16_t>::max();
342
m_frame_count = _str->read_u16();
344
// TODO: This seems dangerous, check closely
345
if (!m_frame_count) ++m_frame_count;
348
log_parse(_("frame size = %s, frame rate = %f, frames = %d"),
349
m_frame_size, m_frame_rate, m_frame_count);
352
setBytesLoaded(_str->tell());
356
// Fire up the loading thread
358
SWFMovieDefinition::completeLoad()
361
// should call this only once
362
assert( ! _loader.started() );
364
// should call readHeader before this
367
#ifdef LOAD_MOVIES_IN_A_SEPARATE_THREAD
369
// Start the loading frame
370
if ( ! _loader.start() )
372
log_error(_("Could not start loading thread"));
376
// Wait until 'startup_frames' have been loaded
378
size_t startup_frames = 0;
380
size_t startup_frames = m_frame_count;
382
ensure_frame_loaded(startup_frames);
384
#else // undef LOAD_MOVIES_IN_A_SEPARATE_THREAD
393
// 1-based frame number
395
SWFMovieDefinition::ensure_frame_loaded(size_t framenum) const
397
boost::mutex::scoped_lock lock(_frames_loaded_mutex);
399
#ifndef LOAD_MOVIES_IN_A_SEPARATE_THREAD
400
return (framenum <= _frames_loaded);
403
if ( framenum <= _frames_loaded ) return true;
405
_waiting_for_frame = framenum;
407
// TODO: return false on timeout
408
_frame_reached_condition.wait(lock);
410
return ( framenum <= _frames_loaded );
414
SWFMovieDefinition::createMovie(Global_as& gl, DisplayObject* parent)
416
as_object* o = getObjectWithPrototype(gl, NSV::CLASS_MOVIE_CLIP);
417
return new SWFMovie(o, this, parent);
422
// CharacterDictionary
426
operator<<(std::ostream& o, const CharacterDictionary& cd)
429
for (CharacterDictionary::CharacterConstIterator it = cd.begin(),
430
endIt = cd.end(); it != endIt; it++)
433
<< "Character: " << it->first
434
<< " at address: " << static_cast<void*>(it->second.get());
440
boost::intrusive_ptr<SWF::DefinitionTag>
441
CharacterDictionary::getDisplayObject(int id) const
443
CharacterConstIterator it = _map.find(id);
444
if ( it == _map.end() )
447
log_parse(_("Could not find char %d, dump is: %s"), id, *this);
449
return boost::intrusive_ptr<SWF::DefinitionTag>();
456
CharacterDictionary::addDisplayObject(int id,
457
boost::intrusive_ptr<SWF::DefinitionTag> c)
464
SWFMovieDefinition::read_all_swf()
468
#ifdef LOAD_MOVIES_IN_A_SEPARATE_THREAD
469
assert( _loader.isSelfThread() );
470
assert( _loader.started() );
472
assert( ! _loader.started() );
473
assert( ! _loader.isSelfThread() );
476
SWFParser parser(*_str, this, _runResources);
478
const size_t startPos = _str->tell();
479
assert (startPos <= _swf_end_pos);
481
size_t left = _swf_end_pos - startPos;
483
const size_t chunkSize = 65535;
488
if (_loadingCanceled) {
489
log_debug("Loading thread cancelation requested, "
490
"returning from read_all_swf");
493
if (!parser.read(std::min<size_t>(left, chunkSize))) break;
495
left -= parser.bytesRead();
496
setBytesLoaded(startPos + parser.bytesRead());
499
// Make sure we won't leave any pending writers
500
// on any eventual fd-based IOChannel.
501
_str->consumeInput();
504
catch (const ParserException& e) {
505
// This is a fatal parser error.
506
log_error(_("Error while parsing SWF stream."));
509
// Set bytesLoaded to the current stream position unless it's greater
510
// than the reported length. TODO: should we be trying to continue
511
// parsing after an exception?
512
setBytesLoaded(std::min<size_t>(_str->tell(), _swf_end_pos));
514
size_t floaded = get_loading_frame();
515
if (!m_playlist[floaded].empty())
517
IF_VERBOSE_MALFORMED_SWF(
518
log_swferror(_("%d control tags are NOT followed by"
519
" a SHOWFRAME tag"), m_playlist[floaded].size());
523
if ( m_frame_count > floaded )
525
IF_VERBOSE_MALFORMED_SWF(
526
log_swferror(_("%d frames advertised in header, but only %d "
527
"SHOWFRAME tags found in stream. Pretending we loaded "
528
"all advertised frames"), m_frame_count, floaded);
530
boost::mutex::scoped_lock lock(_frames_loaded_mutex);
531
_frames_loaded = m_frame_count;
532
// Notify any thread waiting on frame reached condition
533
_frame_reached_condition.notify_all();
538
SWFMovieDefinition::get_loading_frame() const
540
boost::mutex::scoped_lock lock(_frames_loaded_mutex);
541
return _frames_loaded;
545
SWFMovieDefinition::incrementLoadedFrames()
547
boost::mutex::scoped_lock lock(_frames_loaded_mutex);
551
if ( _frames_loaded > m_frame_count )
553
IF_VERBOSE_MALFORMED_SWF(
554
log_swferror(_("number of SHOWFRAME tags "
555
"in SWF stream '%s' (%d) exceeds "
556
"the advertised number in header (%d)."),
557
get_url(), _frames_loaded,
562
#ifdef DEBUG_FRAMES_LOAD
563
log_debug(_("Loaded frame %u/%u"), _frames_loaded, m_frame_count);
566
// signal load of frame if anyone requested it
567
// FIXME: _waiting_for_frame needs mutex ?
568
if (_waiting_for_frame && _frames_loaded >= _waiting_for_frame )
570
// or should we notify_one ?
571
// See: http://boost.org/doc/html/condition.html
572
_frame_reached_condition.notify_all();
578
SWFMovieDefinition::registerExport(const std::string& symbol,
583
boost::mutex::scoped_lock lock(_exportedResourcesMutex);
585
log_debug("%s registering export %s, %s", get_url(), symbol, id);
587
_exportTable[symbol] = id;
592
SWFMovieDefinition::add_frame_name(const std::string& n)
594
boost::mutex::scoped_lock lock1(_namedFramesMutex);
595
boost::mutex::scoped_lock lock2(_frames_loaded_mutex);
597
_namedFrames.insert(std::make_pair(n, _frames_loaded));
601
SWFMovieDefinition::get_labeled_frame(const std::string& label,
602
size_t& frame_number) const
604
boost::mutex::scoped_lock lock(_namedFramesMutex);
605
NamedFrameMap::const_iterator it = _namedFrames.find(label);
606
if (it == _namedFrames.end()) return false;
607
frame_number = it->second;
613
SWFMovieDefinition::exportID(const std::string& symbol) const
615
boost::mutex::scoped_lock lock(_exportedResourcesMutex);
616
Exports::const_iterator it = _exportTable.find(symbol);
617
return (it == _exportTable.end()) ? 0 : it->second;
622
SWFMovieDefinition::importResources(
623
boost::intrusive_ptr<movie_definition> source, const Imports& imports)
625
size_t importedSyms = 0;
629
for (Imports::const_iterator i = imports.begin(), e = imports.end(); i != e;
632
size_t new_loading_frame = source->get_loading_frame();
635
const size_t naptime = 100000;
637
// Timeout after two seconds of NO frames progress
638
const size_t timeout_ms = 2000000;
639
const size_t def_timeout = timeout_ms / naptime;
641
size_t timeout = def_timeout;
642
size_t loading_frame = (size_t)-1; // used to keep track of advancements
644
const int id = i->first;
645
const std::string& symbolName = i->second;
648
log_debug("%s importing %s from %s", get_url(), symbolName,
651
boost::uint16_t targetID;
653
while(!(targetID = source->exportID(symbolName))) {
655
// We checked last (or past-last) advertised frame.
656
// TODO: this check should really be for a parser
657
// process being active or not, as SWF
658
// might advertise less frames then actually
661
if (new_loading_frame >= source->get_frame_count()) {
662
// Update of loading_frame is
663
// really just for the latter debugging output
664
loading_frame = new_loading_frame;
668
// There's more frames to parse, go ahead
669
// TODO: this is still based on *advertised*
670
// number of frames, if SWF advertises
671
// more then actually found we'd be
672
// keep trying till timeout, see the
675
// We made frame progress since last iteration
676
// so sleep some and try again
677
if (new_loading_frame != loading_frame) {
679
log_debug(_("looking for exported resource: frame load "
680
"advancement (from %d to %d)"),
681
loading_frame, new_loading_frame);
683
loading_frame = new_loading_frame;
684
timeout = def_timeout+1;
686
else if (!--timeout) {
687
// no progress since last run, and
688
// timeout reached: give up
692
// 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,
702
source->get_url(), 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());
714
boost::intrusive_ptr<SWF::DefinitionTag> res =
715
source->getDefinitionTag(targetID);
717
// It's a character import.
718
addDisplayObject(id, res.get());
719
registerExport(symbolName, id);
724
Font* f = source->get_font(id);
726
// It's a font import
728
registerExport(symbolName, id);
732
log_error(_("import error: could not find resource '%s' in "
733
"movie '%s'"), symbolName, source->get_url());
737
_importSources.insert(source);