1 // Sound_as.cpp:  ActionScript "Sound" class, for Gnash.
2 //
3 //   Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014, 2016
4 //   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.
10 //
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.
15 //
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
19 //
20 
21 #ifdef HAVE_CONFIG_H
22 #include "gnashconfig.h"
23 #endif
24 
25 #include "Sound_as.h"
26 
27 #include <string>
28 #include <cstdint>
29 #include <boost/optional.hpp>
30 
31 #include "RunResources.h"
32 #include "log.h"
33 #include "sound_handler.h"
34 #include "MediaParser.h"
35 #include "AudioDecoder.h"
36 #include "MediaHandler.h"
37 #include "sound_definition.h"
38 #include "movie_root.h"
39 #include "movie_definition.h"
40 #include "fn_call.h"
41 #include "Global_as.h"
42 #include "GnashException.h" // for ActionException
43 #include "NativeFunction.h" // need builtin_function
44 #include "VM.h"
45 #include "namedStrings.h"
46 #include "StreamProvider.h"
47 #include "ObjectURI.h"
48 #include "Relay.h"
49 #include "Id3Info.h"
50 
51 //#define GNASH_DEBUG_SOUND_AS 1
52 
53 namespace gnash {
54 
55 // Forward declarations
56 namespace {
57     as_value sound_new(const fn_call& fn);
58     as_value sound_attachsound(const fn_call& fn);
59     as_value sound_getbytesloaded(const fn_call& fn);
60     as_value sound_setPosition(const fn_call& fn);
61     as_value sound_areSoundsInaccessible(const fn_call& fn);
62     as_value sound_getbytestotal(const fn_call& fn);
63     as_value sound_getpan(const fn_call& fn);
64     as_value sound_setpan(const fn_call& fn);
65     as_value sound_getDuration(const fn_call& fn);
66     as_value sound_setDuration(const fn_call& fn);
67     as_value sound_gettransform(const fn_call& fn);
68     as_value sound_getPosition(const fn_call& fn);
69     as_value sound_getvolume(const fn_call& fn);
70     as_value sound_loadsound(const fn_call& fn);
71     as_value sound_settransform(const fn_call& fn);
72     as_value sound_setvolume(const fn_call& fn);
73     as_value sound_start(const fn_call& fn);
74     as_value sound_stop(const fn_call& fn);
75     as_value checkPolicyFile_getset(const fn_call& fn);
76     void attachSoundInterface(as_object& o);
77 
78     /// If there is Id3 data, create an id3 member and call the onID3 function.
79     void handleId3Data(boost::optional<media::Id3Info> id3, as_object& sound);
80 }
81 
82 /// A Sound object in ActionScript can control and play sound
83 //
84 /// Two types of sound are handled:
85 ///
86 /// 1. external sounds, either loaded or streamed
87 /// 2. embedded sounds, referenced by library (export) symbol.
88 //
89 /// Sound objects also control volume, pan, and other properties for a target
90 /// movieclip.
91 //
92 /// Sound_as objects
93 //
94 /// 1. May be associated with a particular DisplayObject.
95 /// 2. May be associated with one or more playing sounds.
96 //
97 /// A Sound_as that is not associated with a particular DisplayObject controls
98 /// the sound properties of the whole Movie.
99 class Sound_as : public ActiveRelay
100 {
101 
102 public:
103 
104     Sound_as(as_object* owner);
105 
106     ~Sound_as();
107 
108     /// Make this sound control the given DisplayObject
109     //
110     /// NOTE: 0 is accepted, to implement an "invalid"
111     ///       controller type.
112     ///
113     void attachCharacter(DisplayObject* attachedChar);
114 
115     void attachSound(int si, const std::string& name);
116 
117     /// Get number of bytes loaded.
118     //
119     /// This only applies to external sounds. If unknown or not external, -1
120     /// is returned.
121     long getBytesLoaded();
122 
123     /// Get total number of bytes in the external sound being loaded
124     //
125     /// This only applies to external sounds. If unknown or not external, -1
126     /// is returned.
127     long getBytesTotal();
128 
129     /// Whether the Sound_as has any sound data
active() const130     bool active() const {
131         return soundId >= 0 || isStreaming;
132     }
133 
134     /// Get the pan setting of the attached DisplayObject.
135     //
136     /// If no object is attached, this retrieves settings for the whole Movie.
137     void getPan();
138 
139     /// Get the sound transform of the attached DisplayObject.
140     //
141     /// If no object is attached, this retrieves settings for the whole Movie.
142     void getTransform();
143 
144     /// Get volume from associated resource
145     //
146     /// @return true of volume was obtained, false
147     ///         otherwise (for example if the associated
148     ///         DisplayObject was unloaded).
149     ///
150     bool getVolume(int& volume);
151     void setVolume(int volume);
152 
153     /// Load an external sound.
154     //
155     /// The Sound object is then associated with the external sound.
156     void loadSound(const std::string& file, bool streaming);
157 
158     void setPan();
159 
160     void setTransform();
161 
162     void start(double secsStart, int loops);
163 
164     void stop(int si);
165 
166     /// Get the duration of the sound.
167     //
168     /// This is only meaningful when the Sound_as object has an associated
169     /// sound, that is after attachSound or loadSound has been called.
170     size_t getDuration() const;
171 
172     /// Get the position within the sound.
173     //
174     /// This is only meaningful when the Sound_as object has an associated
175     /// sound, that is after attachSound or loadSound has been called.
176     size_t getPosition() const;
177 
178     std::string soundName;
179 
180 private:
181 
182     void markReachableResources() const;
183 
184     std::unique_ptr<CharacterProxy> _attachedCharacter;
185     int soundId;
186     bool externalSound;
187     bool isStreaming;
188 
189     sound::sound_handler* _soundHandler;
190 
191     media::MediaHandler* _mediaHandler;
192 
193     std::unique_ptr<media::MediaParser> _mediaParser;
194 
195     std::unique_ptr<media::AudioDecoder> _audioDecoder;
196 
197     /// Number of milliseconds into the sound to start it
198     //
199     /// This is set by start()
200     std::uint64_t _startTime;
201 
202     std::unique_ptr<std::uint8_t[]> _leftOverData;
203     std::uint8_t* _leftOverPtr;
204     std::uint32_t _leftOverSize;
205 
206     /// This is a sound_handler::aux_streamer_ptr type.
207     static unsigned int getAudioWrapper(void *owner, std::int16_t* samples,
208             unsigned int nSamples, bool& etEOF);
209 
210     unsigned int getAudio(std::int16_t* samples, unsigned int nSamples,
211             bool& atEOF);
212 
213     /// The aux streamer for sound handler
214     sound::InputStream* _inputStream;
215 
216     int remainingLoops;
217 
218     /// Query media parser for audio info, create decoder and attach aux streamer
219     /// if found.
220     ///
221     /// @return  an InputStream* if audio found and aux streamer attached,
222     ///          0 if no audio found.
223     ///
224     /// May throw a MediaException if audio was found but
225     /// audio decoder could not be created
226     ///
227     sound::InputStream* attachAuxStreamerIfNeeded();
228 
229     /// Register a timer for audio info probing
230     void startProbeTimer();
231 
232     /// Unregister the probe timer
233     void stopProbeTimer();
234 
235     virtual void update();
236 
237     /// Probe audio
238     void probeAudio();
239 
240     std::atomic<bool> _soundCompleted;
241 
242     /// Setter for _soundCompleted
243     void markSoundCompleted(bool completed);
244 
245     bool _soundLoaded;
246 
247     // Does this sound have a live input stream?
isAttached() const248     bool isAttached() const {
249         return (_inputStream);
250     }
251 
252 };
253 
Sound_as(as_object * owner)254 Sound_as::Sound_as(as_object* owner)
255     :
256     ActiveRelay(owner),
257     _attachedCharacter(),
258     soundId(-1),
259     externalSound(false),
260     isStreaming(false),
261     _soundHandler(getRunResources(*owner).soundHandler()),
262     _mediaHandler(getRunResources(*owner).mediaHandler()),
263     _startTime(0),
264     _leftOverData(),
265     _leftOverPtr(nullptr),
266     _leftOverSize(0),
267     _inputStream(nullptr),
268     remainingLoops(0),
269     _soundCompleted(false),
270     _soundLoaded(false)
271 {
272 }
273 
~Sound_as()274 Sound_as::~Sound_as()
275 {
276     // Just in case...
277     if (_inputStream && _soundHandler) {
278         _soundHandler->unplugInputStream(_inputStream);
279         _inputStream=nullptr;
280     }
281 
282 }
283 
284 // extern (used by Global.cpp)
285 void
sound_class_init(as_object & where,const ObjectURI & uri)286 sound_class_init(as_object& where, const ObjectURI& uri)
287 {
288 
289     Global_as& gl = getGlobal(where);
290     as_object* proto = createObject(gl);
291     as_object* cl = gl.createClass(&sound_new, proto);
292     attachSoundInterface(*proto);
293     proto->set_member_flags(NSV::PROP_CONSTRUCTOR, PropFlags::readOnly);
294     proto->set_member_flags(NSV::PROP_uuPROTOuu, PropFlags::readOnly, 0);
295 
296     where.init_member(uri, cl, as_object::DefaultFlags);
297 }
298 
299 void
registerSoundNative(as_object & global)300 registerSoundNative(as_object& global)
301 {
302     VM& vm = getVM(global);
303     vm.registerNative(sound_getpan, 500, 0);
304     vm.registerNative(sound_gettransform, 500, 1);
305     vm.registerNative(sound_getvolume, 500, 2);
306     vm.registerNative(sound_setpan, 500, 3);
307     vm.registerNative(sound_settransform, 500, 4);
308     vm.registerNative(sound_setvolume, 500, 5);
309     vm.registerNative(sound_stop, 500, 6);
310     vm.registerNative(sound_attachsound, 500, 7);
311     vm.registerNative(sound_start, 500, 8);
312     vm.registerNative(sound_getDuration, 500, 9);
313     vm.registerNative(sound_setDuration, 500, 10);
314     vm.registerNative(sound_getPosition, 500, 11);
315     vm.registerNative(sound_setPosition, 500, 12);
316     vm.registerNative(sound_loadsound, 500, 13);
317     vm.registerNative(sound_getbytesloaded, 500, 14);
318     vm.registerNative(sound_getbytestotal, 500, 15);
319     vm.registerNative(sound_areSoundsInaccessible, 500, 16);
320 }
321 
322 /*private*/
323 void
startProbeTimer()324 Sound_as::startProbeTimer()
325 {
326     getRoot(owner()).addAdvanceCallback(this);
327 }
328 
329 /*private*/
330 void
stopProbeTimer()331 Sound_as::stopProbeTimer()
332 {
333 #ifdef GNASH_DEBUG_SOUND_AS
334     log_debug("stopProbeTimer called");
335 #endif
336     getRoot(owner()).removeAdvanceCallback(this);
337 }
338 
339 void
update()340 Sound_as::update()
341 {
342     probeAudio();
343 
344     if (active()) {
345         owner().set_member(NSV::PROP_DURATION, getDuration());
346         owner().set_member(NSV::PROP_POSITION, getPosition());
347     }
348 }
349 
350 void
probeAudio()351 Sound_as::probeAudio()
352 {
353 #ifdef USE_SOUND
354     if ( ! externalSound ) {
355         // Only probe for sound complete
356         assert(_soundHandler);
357         assert(!_soundCompleted.load());
358         if (!_soundHandler->isSoundPlaying(soundId)) {
359             stopProbeTimer();
360             // dispatch onSoundComplete
361             callMethod(&owner(), NSV::PROP_ON_SOUND_COMPLETE);
362         }
363         return;
364     }
365 
366     if (!_mediaParser) return; // nothing to do here w/out a media parser
367 
368 #ifdef USE_MEDIA
369     if ( ! _soundLoaded ) {
370 #ifdef GNASH_DEBUG_SOUND_AS
371         log_debug("Probing audio for load");
372 #endif
373         if (_mediaParser->parsingCompleted()) {
374 
375             _soundLoaded = true;
376 
377             if (!isStreaming) {
378                 stopProbeTimer(); // will be re-started on Sound.start()
379             }
380             bool success = _mediaParser->getAudioInfo() != nullptr;
381             callMethod(&owner(), NSV::PROP_ON_LOAD, success);
382 
383             // TODO: check if this should be called anyway.
384             if (success) handleId3Data(_mediaParser->getId3Info(), owner());
385         }
386 
387         // If this is an event sound, we are done. Otherwise, if there is
388         // any data available, we should start playback.
389         if (!isStreaming || _mediaParser->isBufferEmpty()) {
390             return;
391         }
392     }
393 
394     if (isAttached()) {
395 #ifdef GNASH_DEBUG_SOUND_AS
396         log_debug("Probing audio for end");
397 #endif
398 
399         if (_soundCompleted.load()) {
400             // when _soundCompleted is true we're NOT attached !
401             // MediaParser may be still needed,
402             // if this is a non-streaming sound
403             if ( isStreaming ) {
404                 _mediaParser.reset(); // no use for this anymore...
405             }
406             _inputStream = nullptr;
407             _soundCompleted = false;
408             stopProbeTimer();
409 
410             // dispatch onSoundComplete
411             callMethod(&owner(), NSV::PROP_ON_SOUND_COMPLETE);
412         }
413     }
414     else {
415 #ifdef GNASH_DEBUG_SOUND_AS
416         log_debug("Probing audio for start");
417 #endif
418 
419         bool parsingCompleted = _mediaParser->parsingCompleted();
420         try {
421             log_debug("Attaching aux streamer");
422             _inputStream = attachAuxStreamerIfNeeded();
423         }
424         catch (const MediaException& e) {
425             assert(!_inputStream);
426             assert(!_audioDecoder.get());
427             log_error(_("Could not create audio decoder: %s"), e.what());
428             _mediaParser.reset(); // no use for this anymore...
429             stopProbeTimer();
430             return;
431         }
432 
433         if ( ! _inputStream ) {
434             if ( parsingCompleted ) {
435                 log_error(_("No audio in Sound input."));
436                 stopProbeTimer();
437                 _mediaParser.reset(); // no use for this anymore...
438             } else {
439                 // keep probing
440             }
441         } else {
442             // An audio decoder was constructed, good!
443             assert(_audioDecoder.get());
444         }
445     }
446 #endif  // USE_MEDIA
447 #endif  // USE_SOUND
448 }
449 
450 void
markReachableResources() const451 Sound_as::markReachableResources() const
452 {
453     if (_attachedCharacter) {
454         _attachedCharacter->setReachable();
455     }
456 }
457 
458 void
markSoundCompleted(bool completed)459 Sound_as::markSoundCompleted(bool completed)
460 {
461     _soundCompleted=completed;
462 }
463 
464 void
attachCharacter(DisplayObject * attachTo)465 Sound_as::attachCharacter(DisplayObject* attachTo)
466 {
467     _attachedCharacter.reset(new CharacterProxy(attachTo, getRoot(owner())));
468 }
469 
470 void
attachSound(int si,const std::string & name)471 Sound_as::attachSound(int si, const std::string& name)
472 {
473     soundId = si;
474     soundName = name;
475 
476     owner().set_member(NSV::PROP_DURATION, getDuration());
477     owner().set_member(NSV::PROP_POSITION, getPosition());
478 
479 }
480 
481 long
getBytesLoaded()482 Sound_as::getBytesLoaded()
483 {
484     if ( _mediaParser ) {
485         return _mediaParser->getBytesLoaded();
486     }
487 
488     return -1;
489 }
490 
491 long
getBytesTotal()492 Sound_as::getBytesTotal()
493 {
494     if ( _mediaParser ) {
495         return _mediaParser->getBytesTotal();
496     }
497 
498     return -1;
499 }
500 
501 void
getPan()502 Sound_as::getPan()
503 {
504     LOG_ONCE(log_unimpl(__FUNCTION__));
505 }
506 
507 void
getTransform()508 Sound_as::getTransform()
509 {
510     LOG_ONCE(log_unimpl(__FUNCTION__));
511 }
512 
513 bool
getVolume(int & volume)514 Sound_as::getVolume(int& volume)
515 {
516     // TODO: check what takes precedence in case we
517     //       have both an attached DisplayObject *and*
518     //       some other sound...
519     //
520     if ( _attachedCharacter ) {
521         //log_debug("Sound has an attached DisplayObject");
522         DisplayObject* ch = _attachedCharacter->get();
523         if (! ch) {
524             log_debug("Character attached to Sound was unloaded and "
525                         "couldn't rebind");
526             return false;
527         }
528         volume = ch->getVolume();
529         return true;
530     }
531 
532     // If we're not attached to a DisplayObject we'll need to query
533     // sound_handler for volume. If we have no sound handler, we
534     // can't do much, so we'll return false
535     if (!_soundHandler) {
536         log_debug("We have no sound handler here...");
537         return false;
538     }
539 
540     // Now, we may be controlling a specific sound or
541     // the final output as a whole.
542     // If soundId is -1 we're controlling as a whole
543     //
544     if (soundId == -1) {
545         volume = _soundHandler->getFinalVolume();
546     } else {
547         volume = _soundHandler->get_volume(soundId);
548     }
549 
550     return true;
551 }
552 
553 void
loadSound(const std::string & file,bool streaming)554 Sound_as::loadSound(const std::string& file, bool streaming)
555 {
556     if (!_mediaHandler || !_soundHandler) {
557         log_debug("No media or sound handlers, won't load any sound");
558         return;
559     }
560 
561     /// If we are already streaming stop doing so as we'll replace
562     /// the media parser
563     if (_inputStream) {
564         _soundHandler->unplugInputStream(_inputStream);
565         _inputStream = nullptr;
566     }
567 
568     /// Mark sound as not being loaded
569     // TODO: should we check for _soundLoaded == true?
570     _soundLoaded = false;
571 
572     /// Delete any media parser being used (make sure we have detached!)
573     _mediaParser.reset();
574 
575     /// Start at offset 0, in case a previous ::start() call
576     /// changed that.
577     _startTime = 0;
578 
579     const RunResources& rr = getRunResources(owner());
580     URL url(file, rr.streamProvider().baseURL());
581 
582     const RcInitFile& rcfile = RcInitFile::getDefaultInstance();
583 
584     const StreamProvider& streamProvider = rr.streamProvider();
585     std::unique_ptr<IOChannel> inputStream(streamProvider.getStream(url,
586                 rcfile.saveStreamingMedia()));
587 
588     if (!inputStream.get()) {
589         log_error(_("Gnash could not open this URL: %s"), url );
590         // dispatch onLoad (false)
591         callMethod(&owner(), NSV::PROP_ON_LOAD, false);
592         return;
593     }
594 
595     externalSound = true;
596     isStreaming = streaming;
597 
598     _mediaParser = std::move(_mediaHandler->createMediaParser(std::move(inputStream)));
599     if (!_mediaParser) {
600         log_error(_("Unable to create parser for Sound at %s"), url);
601         // not necessarely correct, the stream might have been found...
602         // dispatch onLoad (false)
603         callMethod(&owner(), NSV::PROP_ON_LOAD, false);
604         return;
605     }
606 
607     // TODO: use global _soundbuftime
608     if (isStreaming) {
609         _mediaParser->setBufferTime(60000); // one minute buffer... should be fine
610     } else {
611         // If this is an event sound, we must not limit buffering (parsing),
612         // because onLoad will not be called until we have finished doing so.
613         _mediaParser->setBufferTime(std::numeric_limits<std::uint64_t>::max());
614     }
615 
616     startProbeTimer();
617 
618     owner().set_member(NSV::PROP_DURATION, getDuration());
619     owner().set_member(NSV::PROP_POSITION, getPosition());
620 }
621 
622 sound::InputStream*
attachAuxStreamerIfNeeded()623 Sound_as::attachAuxStreamerIfNeeded()
624 {
625     media::AudioInfo* audioInfo =  _mediaParser->getAudioInfo();
626     if (!audioInfo) return nullptr;
627 
628     // the following may throw an exception
629     _audioDecoder.reset(_mediaHandler->createAudioDecoder(*audioInfo).release());
630 
631     // start playing ASAP, a call to ::start will just change _startTime
632 #ifdef GNASH_DEBUG_SOUND_AS
633     log_debug("Attaching the aux streamer");
634 #endif
635     return _soundHandler->attach_aux_streamer(getAudioWrapper, (void*) this);
636 }
637 
638 void
setPan()639 Sound_as::setPan()
640 {
641     LOG_ONCE(log_unimpl(__FUNCTION__));
642 }
643 
644 void
setTransform()645 Sound_as::setTransform()
646 {
647     LOG_ONCE(log_unimpl(__FUNCTION__));
648 }
649 
650 void
setVolume(int volume)651 Sound_as::setVolume(int volume)
652 {
653     // TODO: check what takes precedence in case we
654     //       have both an attached DisplayObject *and*
655     //       some other sound...
656     //
657     if ( _attachedCharacter ) {
658         DisplayObject* ch = _attachedCharacter->get();
659         if ( ! ch ) {
660             log_debug("Character attached to Sound was unloaded and "
661                       "couldn't rebind");
662             return;
663         }
664         ch->setVolume(volume);
665         return;
666     }
667 
668     // If we're not attached to a DisplayObject we'll need to use
669     // sound_handler for volume. If we have no sound handler, we
670     // can't do much, so we'll just return
671     if (!_soundHandler) {
672         return;
673     }
674 
675     // Now, we may be controlling a specific sound or
676     // the final output as a whole.
677     // If soundId is -1 we're controlling as a whole
678     //
679     if ( soundId == -1 ) {
680         _soundHandler->setFinalVolume(volume);
681     } else {
682         _soundHandler->set_volume(soundId, volume);
683     }
684 }
685 
686 void
start(double secOff,int loops)687 Sound_as::start(double secOff, int loops)
688 {
689     if ( ! _soundHandler ) {
690         log_error(_("No sound handler, nothing to start..."));
691         return;
692     }
693 
694 #ifdef USE_SOUND
695     if (externalSound) {
696         if ( ! _mediaParser ) {
697             log_error(_("No MediaParser initialized, can't start an external sound"));
698             return;
699         }
700 
701         if (isStreaming) {
702             IF_VERBOSE_ASCODING_ERRORS(
703             log_aserror(_("Sound.start() has no effect on a streaming Sound"));
704             );
705             return;
706         }
707 
708         // Always seek as we might be called during or after some playing...
709         {
710             _startTime = secOff * 1000;
711             std::uint32_t seekms = std::uint32_t(secOff * 1000);
712             // TODO: std::lock_guard<std::mutex> parserLock(_parserMutex);
713             bool seeked = _mediaParser->seek(seekms); // well, we try...
714             log_debug("Seeked MediaParser to %d, returned: %d", seekms, seeked);
715         }
716 
717 
718         // Save how many loops to do (not when streaming)
719         if (loops > 0) {
720             remainingLoops = loops;
721         }
722 
723         startProbeTimer();
724 
725     } else {
726         unsigned int inPoint = 0;
727 
728         if ( secOff > 0 ) {
729             inPoint = (secOff*44100);
730         }
731 
732         log_debug("Sound.start: secOff:%d loops:%d", secOff, loops);
733 
734         _soundHandler->startSound(
735                     soundId,
736                     loops,
737                     nullptr, // envelopes
738                     true, // allow multiple instances (checked)
739                     inPoint
740                     );
741 
742         startProbeTimer(); // to dispatch onSoundComplete
743     }
744 #endif  // USE_SOUND
745 }
746 
747 void
stop(int si)748 Sound_as::stop(int si)
749 {
750     if ( ! _soundHandler ) {
751         log_error(_("No sound handler, nothing to stop..."));
752         return;
753     }
754 
755 #ifdef USE_SOUND
756     // stop the sound
757     if (si < 0) {
758         if (externalSound) {
759             if ( _inputStream ) {
760                 _soundHandler->unplugInputStream(_inputStream);
761                 _inputStream=nullptr;
762             }
763         } else {
764             if ( ! _attachedCharacter ) {
765                 // See https://savannah.gnu.org/bugs/index.php?33888
766                 _soundHandler->stopAllEventSounds();
767             } else {
768                 _soundHandler->stopEventSound(soundId);
769             }
770         }
771     } else {
772         _soundHandler->stopEventSound(si);
773     }
774 #endif  // USE_SOUND
775 }
776 
777 size_t
getDuration() const778 Sound_as::getDuration() const
779 {
780     if ( ! _soundHandler ) {
781         log_error(_("No sound handler, can't check duration..."));
782         return 0;
783     }
784 
785     // If this is a event sound get the info from the soundhandler
786     if (!externalSound) {
787         return _soundHandler->get_duration(soundId);
788     }
789 
790     // If we have a media parser (we'd do for an externalSound)
791     // try fetching duration from it
792     if ( _mediaParser ) {
793         media::AudioInfo* info = _mediaParser->getAudioInfo();
794         if ( info ) {
795             return info->duration;
796         }
797     }
798 
799     return 0;
800 }
801 
802 size_t
getPosition() const803 Sound_as::getPosition() const
804 {
805     if (!_soundHandler) {
806         log_error(_("No sound handler, can't check position (we're "
807                     "likely not playing anyway)..."));
808         return 0;
809     }
810 #ifdef USE_SOUND
811     // If this is a event sound get the info from the soundhandler
812     if (!externalSound) {
813         return _soundHandler->tell(soundId);
814     }
815 
816 #ifdef USE_MEDIA
817     if (_mediaParser) {
818         std::uint64_t ts;
819         if ( _mediaParser->nextAudioFrameTimestamp(ts) ) {
820             return ts;
821         }
822     }
823 #endif  // USE_MEDIA
824 #endif  // USE_SOUND
825 
826     return 0;
827 }
828 
829 
830 unsigned int
getAudio(std::int16_t * samples,unsigned int nSamples,bool & atEOF)831 Sound_as::getAudio(std::int16_t* samples, unsigned int nSamples, bool& atEOF)
832 {
833 #if defined USE_SOUND && defined USE_MEDIA
834     std::uint8_t* stream = reinterpret_cast<std::uint8_t*>(samples);
835     int len = nSamples*2;
836     //GNASH_REPORT_FUNCTION;
837 
838     while (len) {
839         if ( ! _leftOverData ) {
840             bool parsingComplete = _mediaParser->parsingCompleted(); // check *before* calling nextAudioFrame
841             std::unique_ptr<media::EncodedAudioFrame> frame = _mediaParser->nextAudioFrame();
842             if ( ! frame.get() ) {
843                 // just wait some more if parsing isn't complete yet
844                 if ( ! parsingComplete ) {
845                     //log_debug("Parsing not complete and no more audio frames in input, try again later");
846                     break;
847                 }
848 
849                 // or detach and stop here...
850                 // (should really honour loopings if any,
851                 // but that should be only done for non-streaming sound!)
852                 //log_debug("Parsing complete and no more audio frames in input, detaching");
853 
854                 markSoundCompleted(true);
855 
856                 // Setting atEOF to true will detach us.
857                 // We should change _inputStream, but need thread safety!
858                 // So on probeAudio, if _soundCompleted is set
859                 // we'll consider ourselves detached already and set
860                 // _inputStream to zero
861                 atEOF=true;
862                 return nSamples-(len/2);
863             }
864 
865             // if we've been asked to start at a specific time, skip
866             // any frame with earlier timestamp
867             if ( frame->timestamp < _startTime ) {
868                 //log_debug("This audio frame timestamp (%d) < requested start time (%d)", frame->timestamp, _startTime);
869                 continue;
870             }
871 
872             _leftOverData.reset( _audioDecoder->decode(*frame, _leftOverSize) );
873             _leftOverPtr = _leftOverData.get();
874             if ( ! _leftOverData ) {
875                 log_error(_("No samples decoded from input of %d bytes"),
876                           frame->dataSize);
877                 continue;
878             }
879 
880             //log_debug(" decoded %d bytes of audio", _leftOverSize);
881         }
882 
883         assert( !(_leftOverSize%2) );
884 
885         int n = std::min<int>(_leftOverSize, len);
886         //log_debug(" consuming %d bytes of decoded audio", n);
887 
888         std::copy(_leftOverPtr, _leftOverPtr+n, stream);
889 
890         stream += n;
891         _leftOverPtr += n;
892         _leftOverSize -= n;
893         len -= n;
894 
895         if (_leftOverSize == 0) {
896             _leftOverData.reset();
897             _leftOverPtr = nullptr;
898         }
899 
900     }
901 
902     // drop any queued video frame
903     while (_mediaParser->nextVideoFrame().get()) {};
904 
905     atEOF=false;
906     return nSamples-(len/2);
907 #else   // USE_MEDIA && USE_SOUND
908     UNUSED(samples);
909     UNUSED(nSamples);
910     UNUSED(atEOF);
911     return 0;
912 #endif
913 }
914 
915 // audio callback is running in sound handler thread
916 unsigned int
getAudioWrapper(void * owner,std::int16_t * samples,unsigned int nSamples,bool & atEOF)917 Sound_as::getAudioWrapper(void* owner, std::int16_t* samples,
918         unsigned int nSamples, bool& atEOF)
919 {
920     Sound_as* so = static_cast<Sound_as*>(owner);
921     return so->getAudio(samples, nSamples, atEOF);
922 }
923 
924 
925 namespace {
926 
927 void
attachSoundInterface(as_object & o)928 attachSoundInterface(as_object& o)
929 {
930 
931     int flags = PropFlags::dontEnum |
932                 PropFlags::dontDelete |
933                 PropFlags::readOnly;
934 
935     VM& vm = getVM(o);
936     o.init_member("getPan", vm.getNative(500, 0), flags);
937     o.init_member("getTransform", vm.getNative(500, 1), flags);
938     o.init_member("getVolume", vm.getNative(500, 2), flags);
939     o.init_member("setPan", vm.getNative(500, 3), flags);
940     o.init_member("setTransform", vm.getNative(500, 4), flags);
941     o.init_member("setVolume", vm.getNative(500, 5), flags);
942     o.init_member("stop", vm.getNative(500, 6), flags);
943     o.init_member("attachSound", vm.getNative(500, 7), flags);
944     o.init_member("start", vm.getNative(500, 8), flags);
945 
946     int flagsn6 = flags | PropFlags::onlySWF6Up;
947 
948     o.init_member("getDuration", vm.getNative(500, 9), flagsn6);
949     o.init_member("setDuration", vm.getNative(500, 10), flagsn6);
950     o.init_member("getPosition", vm.getNative(500, 11), flagsn6);
951     o.init_member("setPosition", vm.getNative(500, 12), flagsn6);
952     o.init_member("loadSound", vm.getNative(500, 13), flagsn6);
953     o.init_member("getBytesLoaded", vm.getNative(500, 14), flagsn6);
954     o.init_member("getBytesTotal", vm.getNative(500, 15), flagsn6);
955 
956     int flagsn9 = PropFlags::dontEnum |
957                   PropFlags::dontDelete |
958                   PropFlags::readOnly |
959                   PropFlags::onlySWF9Up;
960 
961     o.init_member("areSoundsInaccessible", vm.getNative(500, 16), flagsn9);
962 
963     int fl_hp = PropFlags::dontEnum | PropFlags::dontDelete;
964 
965     o.init_property("checkPolicyFile", &checkPolicyFile_getset,
966             &checkPolicyFile_getset, fl_hp);
967 }
968 
969 
970 as_value
sound_new(const fn_call & fn)971 sound_new(const fn_call& fn)
972 {
973     as_object* so = ensure<ValidThis>(fn);
974     Sound_as* s(new Sound_as(so));
975     so->setRelay(s);
976 
977     if (fn.nargs) {
978         IF_VERBOSE_ASCODING_ERRORS(
979             if (fn.nargs > 1) {
980                 std::stringstream ss; fn.dump_args(ss);
981                 log_aserror(_("new Sound(%d) : args after first one ignored"),
982                     ss.str());
983             }
984         );
985 
986         const as_value& arg0 = fn.arg(0);
987 
988         if (!arg0.is_null() && !arg0.is_undefined()) {
989 
990             as_object* obj = toObject(arg0, getVM(fn));
991             DisplayObject* ch = get<DisplayObject>(obj);
992             IF_VERBOSE_ASCODING_ERRORS(
993                 if (!ch) {
994                     std::stringstream ss; fn.dump_args(ss);
995                     log_aserror(_("new Sound(%s) : first argument isn't null "
996                         "or undefined, and isn't a DisplayObject. "
997                                   "We'll take as an invalid DisplayObject ref."),
998                         ss.str());
999                 }
1000             );
1001 
1002             s->attachCharacter(ch);
1003         }
1004     }
1005 
1006     return as_value();
1007 }
1008 
1009 as_value
sound_start(const fn_call & fn)1010 sound_start(const fn_call& fn)
1011 {
1012     IF_VERBOSE_ACTION (
1013     log_action(_("-- start sound"));
1014     )
1015     Sound_as* so = ensure<ThisIsNative<Sound_as> >(fn);
1016     int loop = 0;
1017     double secondOffset = 0;
1018 
1019     if (fn.nargs > 0) {
1020         secondOffset = toNumber(fn.arg(0), getVM(fn));
1021 
1022         if (fn.nargs > 1) {
1023             // Negative values count as playing once (aka looping 0 times)
1024             loop = std::max(0, toInt(fn.arg(1), getVM(fn)) - 1);
1025         }
1026     }
1027     so->start(secondOffset, loop);
1028     return as_value();
1029 }
1030 
1031 as_value
sound_stop(const fn_call & fn)1032 sound_stop(const fn_call& fn)
1033 {
1034     IF_VERBOSE_ACTION (
1035     log_action(_("-- stop sound "));
1036     )
1037     Sound_as* so = ensure<ThisIsNative<Sound_as> >(fn);
1038 
1039     int si = -1;
1040 
1041     if (fn.nargs > 0) {
1042         const std::string& name = fn.arg(0).to_string();
1043 
1044         // check the import.
1045         const movie_definition* def = fn.callerDef;
1046         assert(def);
1047 
1048         const std::uint16_t id = def->exportID(name);
1049         if (!id) {
1050             IF_VERBOSE_MALFORMED_SWF(
1051                 log_swferror(_("No such export '%s'"),
1052                     name);
1053                 );
1054             return as_value();
1055         }
1056 
1057         sound_sample* ss = def->get_sound_sample(id);
1058         if (!ss) {
1059             IF_VERBOSE_MALFORMED_SWF(
1060                 log_swferror(_("Export '%s' is not a sound"), name);
1061                 );
1062             return as_value();
1063         }
1064 
1065         si = ss->m_sound_handler_id;
1066     }
1067 
1068     so->stop(si);
1069     return as_value();
1070 }
1071 
1072 as_value
sound_attachsound(const fn_call & fn)1073 sound_attachsound(const fn_call& fn)
1074 {
1075     IF_VERBOSE_ACTION(
1076         log_action(_("-- attach sound"));
1077     )
1078 
1079     if (fn.nargs < 1) {
1080         IF_VERBOSE_ASCODING_ERRORS(
1081         log_aserror(_("attach sound needs one argument"));
1082             );
1083         return as_value();
1084     }
1085 
1086     Sound_as* so = ensure<ThisIsNative<Sound_as> >(fn);
1087 
1088     const std::string& name = fn.arg(0).to_string();
1089     if (name.empty()) {
1090         IF_VERBOSE_ASCODING_ERRORS(
1091         log_aserror(_("attachSound needs a non-empty string"));
1092         );
1093         return as_value();
1094     }
1095 
1096     // check the import.
1097     // NOTE: we should be checking in the SWF containing the calling code
1098     // (see 'winter bell' from orisinal morning sunshine for a testcase)
1099     const movie_definition* def = fn.callerDef;
1100     assert(def);
1101 
1102 
1103     const std::uint16_t id = def->exportID(name);
1104     if (!id) {
1105         IF_VERBOSE_MALFORMED_SWF(
1106             log_swferror(_("No such export '%s'"),
1107                 name);
1108             );
1109         return as_value();
1110     }
1111 
1112     sound_sample* ss = def->get_sound_sample(id);
1113     if (!ss) {
1114         IF_VERBOSE_MALFORMED_SWF(
1115             log_swferror(_("Export '%s'is not a sound"), name);
1116             );
1117         return as_value();
1118     }
1119 
1120     const int si = ss->m_sound_handler_id;
1121 
1122     // sanity check
1123     assert(si >= 0);
1124     so->attachSound(si, name);
1125 
1126     return as_value();
1127 }
1128 
1129 as_value
sound_getbytesloaded(const fn_call & fn)1130 sound_getbytesloaded(const fn_call& fn)
1131 {
1132     Sound_as* so = ensure<ThisIsNative<Sound_as> >(fn);
1133     long loaded = so->getBytesLoaded();
1134     if (loaded < 0) return as_value();
1135     return as_value(loaded);
1136 }
1137 
1138 as_value
sound_getbytestotal(const fn_call & fn)1139 sound_getbytestotal(const fn_call& fn)
1140 {
1141     Sound_as* so = ensure<ThisIsNative<Sound_as> >(fn);
1142     long total = so->getBytesTotal();
1143     if (total < 0) return as_value();
1144     return as_value(total);
1145 }
1146 
1147 as_value
sound_getpan(const fn_call &)1148 sound_getpan(const fn_call& /*fn*/)
1149 {
1150     LOG_ONCE(log_unimpl(_("Sound.getPan()")));
1151     return as_value();
1152 }
1153 
1154 as_value
sound_getDuration(const fn_call & fn)1155 sound_getDuration(const fn_call& fn)
1156 {
1157     Sound_as* so = ensure<ThisIsNative<Sound_as> >(fn);
1158     if (!so->active()) return as_value();
1159     return as_value(so->getDuration());
1160 }
1161 
1162 as_value
sound_setDuration(const fn_call &)1163 sound_setDuration(const fn_call& /*fn*/)
1164 {
1165     LOG_ONCE(log_unimpl(_("Sound.setDuration()")));
1166     return as_value();
1167 }
1168 
1169 as_value
sound_getPosition(const fn_call & fn)1170 sound_getPosition(const fn_call& fn)
1171 {
1172     Sound_as* so = ensure<ThisIsNative<Sound_as> >(fn);
1173     if (!so->active()) return as_value();
1174     return as_value(so->getPosition());
1175 }
1176 
1177 as_value
sound_setPosition(const fn_call &)1178 sound_setPosition(const fn_call& /*fn*/)
1179 {
1180     LOG_ONCE(log_unimpl(_("Sound.setPosition()")));
1181     return as_value();
1182 }
1183 
1184 as_value
sound_gettransform(const fn_call &)1185 sound_gettransform(const fn_call& /*fn*/)
1186 {
1187     LOG_ONCE( log_unimpl(_("Sound.getTransform()")));
1188     return as_value();
1189 }
1190 
1191 as_value
sound_getvolume(const fn_call & fn)1192 sound_getvolume(const fn_call& fn)
1193 {
1194 
1195     Sound_as* so = ensure<ThisIsNative<Sound_as> >(fn);
1196 
1197     if ( fn.nargs ) {
1198         IF_VERBOSE_ASCODING_ERRORS(
1199             std::stringstream ss; fn.dump_args(ss);
1200             log_aserror(_("Sound.getVolume(%s) : arguments ignored"));
1201         );
1202     }
1203 
1204     int volume;
1205     if (so->getVolume(volume)) return as_value(volume);
1206     return as_value();
1207 }
1208 
1209 as_value
sound_loadsound(const fn_call & fn)1210 sound_loadsound(const fn_call& fn)
1211 {
1212     Sound_as* so = ensure<ThisIsNative<Sound_as> >(fn);
1213 
1214     if (!fn.nargs) {
1215         IF_VERBOSE_ASCODING_ERRORS(
1216         log_aserror(_("Sound.loadSound() needs at least 1 argument"));
1217             );
1218         return as_value();
1219     }
1220 
1221     std::string url = fn.arg(0).to_string();
1222 
1223     bool streaming = false;
1224     if ( fn.nargs > 1 ) {
1225         streaming = toBool(fn.arg(1), getVM(fn));
1226 
1227         IF_VERBOSE_ASCODING_ERRORS(
1228         if ( fn.nargs > 2 )
1229         {
1230             std::stringstream ss; fn.dump_args(ss);
1231             log_aserror(_("Sound.loadSound(%s): arguments after first 2 "
1232                     "discarded"), ss.str());
1233         }
1234         );
1235     }
1236 
1237     so->loadSound(url, streaming);
1238 
1239     return as_value();
1240 }
1241 
1242 as_value
sound_setpan(const fn_call &)1243 sound_setpan(const fn_call& /*fn*/)
1244 {
1245     LOG_ONCE(log_unimpl(_("Sound.setPan()")));
1246     return as_value();
1247 }
1248 
1249 as_value
sound_settransform(const fn_call &)1250 sound_settransform(const fn_call& /*fn*/)
1251 {
1252     LOG_ONCE(log_unimpl(_("Sound.setTransform()")));
1253     return as_value();
1254 }
1255 
1256 as_value
sound_setvolume(const fn_call & fn)1257 sound_setvolume(const fn_call& fn)
1258 {
1259     if (fn.nargs < 1) {
1260         IF_VERBOSE_ASCODING_ERRORS(
1261         log_aserror(_("set volume of sound needs one argument"));
1262         );
1263         return as_value();
1264     }
1265 
1266     Sound_as* so = ensure<ThisIsNative<Sound_as> >(fn);
1267     int volume = (int) toNumber(fn.arg(0), getVM(fn));
1268 
1269     so->setVolume(volume);
1270     return as_value();
1271 }
1272 
1273 as_value
checkPolicyFile_getset(const fn_call &)1274 checkPolicyFile_getset(const fn_call& /*fn*/)
1275 {
1276     LOG_ONCE(log_unimpl(_("Sound.checkPolicyFile")));
1277     return as_value();
1278 }
1279 
1280 as_value
sound_areSoundsInaccessible(const fn_call &)1281 sound_areSoundsInaccessible(const fn_call& /*fn*/)
1282 {
1283     // TODO: I guess this would have to do with permissions (crossdomain stuff)
1284     // more then capability.
1285     // See http://www.actionscript.org/forums/showthread.php3?t=160028
1286     //
1287     // naive test shows this always being undefined..
1288     //
1289     LOG_ONCE(log_unimpl(_("Sound.areSoundsInaccessible()")));
1290     return as_value();
1291 }
1292 
1293 void
handleId3Data(boost::optional<media::Id3Info> id3,as_object & sound)1294 handleId3Data(boost::optional<media::Id3Info> id3, as_object& sound)
1295 {
1296     if (!id3) return;
1297     VM& vm = getVM(sound);
1298 
1299     as_object* o = new as_object(getGlobal(sound));
1300 
1301     // TODO: others.
1302     if (id3->album) o->set_member(getURI(vm, "album"), *id3->album);
1303     if (id3->year) o->set_member(getURI(vm, "year"), *id3->year);
1304 
1305     // Add Sound.id3 member
1306     const ObjectURI& id3prop = getURI(vm, "id3");
1307     sound.set_member(id3prop, o);
1308 
1309     // Notify onID3 function.
1310     const ObjectURI& onID3 = getURI(vm, "onID3");
1311     callMethod(&sound, onID3);
1312 }
1313 
1314 } // anonymous namespace
1315 } // gnash namespace
1316 
1317 // local Variables:
1318 // mode: C++
1319 // indent-tabs-mode: nil
1320 // End:
1321 
1322