/* [<][>][^][v][top][bottom][index][help] */
DEFINITIONS
This source file includes following definitions.
- samplesFetched
- _decodedData
- createDecoder
- getDecodedData
- reachedCustomEnd
- fetchSamples
- decodeNextBlock
- getEncodedData
- adjustVolume
- applyEnvelopes
- eof
- appendDecodedData
// EmbedSoundInst.cpp - instance of an embedded sound, for gnash
//
// Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010,
// 2011 Free Software Foundation, Inc
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
//
#include "EmbedSoundInst.h"
#include "SoundInfo.h" // for use
#include "MediaHandler.h" // for use
#include "GnashException.h" // for SoundException
#include "AudioDecoder.h" // for use
#include "SoundEnvelope.h" // for use
#include "log.h" // will import boost::format too
#include <cmath>
#include <vector>
#include <boost/scoped_array.hpp>
// Debug sound decoding
//#define GNASH_DEBUG_SOUNDS_DECODING
//#define GNASH_DEBUG_SOUNDS_MANAGEMENT
namespace gnash {
namespace sound {
unsigned int
EmbedSoundInst::samplesFetched() const
{
return _samplesFetched;
}
EmbedSoundInst::EmbedSoundInst(EmbedSound& soundData,
media::MediaHandler& mediaHandler,
sound_handler::StreamBlockId blockOffset,
unsigned int inPoint,
unsigned int outPoint,
const SoundEnvelopes* env,
unsigned int loopCount)
:
// should store blockOffset somewhere else too, for resetting
decodingPosition(blockOffset),
loopCount(loopCount),
// parameter is in stereo samples (44100 per second)
// we double to take 2 channels into account
// and double again to use bytes
_inPoint(inPoint*4),
// parameter is in stereo samples (44100 per second)
// we double to take 2 channels into account
// and double again to use bytes
_outPoint( outPoint == std::numeric_limits<unsigned int>::max() ?
std::numeric_limits<unsigned long>::max()
: outPoint * 4),
envelopes(env),
current_env(0),
_samplesFetched(0),
_decoder(0),
_soundDef(soundData),
_decodedData(0)
{
playbackPosition = _inPoint;
createDecoder(mediaHandler);
}
/*private*/
void
EmbedSoundInst::createDecoder(media::MediaHandler& mediaHandler)
{
media::SoundInfo& si = *_soundDef.soundinfo;
media::AudioInfo info(
(int)si.getFormat(), // codeci
si.getSampleRate(), // sampleRatei
si.is16bit() ? 2 : 1, // sampleSizei
si.isStereo(), // stereoi
0, // duration unknown, does it matter ?
media::CODEC_TYPE_FLASH);
try {
_decoder = mediaHandler.createAudioDecoder(info);
}
catch (const MediaException& e) {
log_error("AudioDecoder initialization failed: %s", e.what());
}
}
// Pointer handling and checking functions
boost::int16_t*
EmbedSoundInst::getDecodedData(unsigned long int pos)
{
if ( _decodedData.get() )
{
assert(pos < _decodedData->size());
return reinterpret_cast<boost::int16_t*>(_decodedData->data()+pos);
}
else return 0;
}
bool
EmbedSoundInst::reachedCustomEnd() const
{
if ( _outPoint == std::numeric_limits<unsigned long>::max() )
return false;
if ( playbackPosition >= _outPoint ) return true;
return false;
}
unsigned int
EmbedSoundInst::fetchSamples(boost::int16_t* to, unsigned int nSamples)
{
// If there exist no decoder, then we can't decode!
// TODO: isn't it documented that an EmbedSoundInst w/out a decoder
// means that the EmbedSound data is already decoded ?
if (!_decoder.get())
{
return 0;
}
unsigned int fetchedSamples=0;
while ( nSamples )
{
unsigned int availableSamples = decodedSamplesAhead();
if ( availableSamples )
{
boost::int16_t* data = getDecodedData(playbackPosition);
if ( availableSamples >= nSamples )
{
std::copy(data, data+nSamples, to);
fetchedSamples += nSamples;
// Update playback position (samples are 16bit)
playbackPosition += nSamples*2;
break; // fetched all
}
else
{
// not enough decoded samples available:
// copy what we have and go on
std::copy(data, data+availableSamples, to);
fetchedSamples += availableSamples;
// Update playback position (samples are 16bit)
playbackPosition += availableSamples*2;
to+=availableSamples;
nSamples-=availableSamples;
assert ( nSamples );
}
}
// We haven't finished fetching yet, so see if we
// have more to decode or not
if ( decodingCompleted() || reachedCustomEnd() )
{
if ( loopCount )
{
#ifdef GNASH_DEBUG_SOUNDS_MANAGEMENT
log_debug("Loops left: %d", loopCount);
#endif
// loops ahead, reset playbackPosition to the starting
// position and keep looping
--loopCount;
// Start next loop
playbackPosition = _inPoint;
_samplesFetched = 0;
continue;
}
#ifdef GNASH_DEBUG_SOUNDS_MANAGEMENT
if ( reachedCustomEnd() )
{
log_debug("Reached custom end (pos:%d out:%d) and no looping, "
"sound is over", playbackPosition, _outPoint);
}
else
{
log_debug("Decoding completed and no looping, sound is over");
}
#endif
break; // fetched what possible, filled with silence the rest
}
// More to decode, then decode it
decodeNextBlock();
}
// update samples played
_samplesFetched += fetchedSamples;
return fetchedSamples;
}
/*private*/
void
EmbedSoundInst::decodeNextBlock()
{
assert(!decodingCompleted());
// Should only be called when no more decoded data
// are available for fetching.
// Doing so we know what's the sample number
// of the first sample in the newly decoded block.
//
assert( playbackPosition >= decodedDataSize() );
boost::uint32_t inputSize = 0; // or blockSize
bool parse = true; // need to parse ?
// this block figures inputSize (blockSize) and need to parse (parse)
// @todo: turn into a private function
{
const EmbedSound& sndData = _soundDef;
// Figure the need to parse..
switch (sndData.soundinfo->getFormat())
{
case media::AUDIO_CODEC_ADPCM:
#ifdef GNASH_DEBUG_SOUNDS_DECODING
log_debug(" sound format is ADPCM");
#endif
parse = false;
break;
default:
break;
}
// Figure the frame size ...
inputSize = encodedDataSize() - decodingPosition;
if (!sndData.m_frames_size.empty())
{
const EmbedSound::FrameSizeMap& m = sndData.m_frames_size;
EmbedSound::FrameSizeMap::const_iterator it =
m.find(decodingPosition);
if ( it != m.end() )
{
inputSize = it->second;
#ifdef GNASH_DEBUG_SOUNDS_DECODING
log_debug(" frame size for frame starting at offset %d is %d",
decodingPosition, inputSize);
#endif
}
else
{
// this should never happen, as we keep track of
// sizes for each audio block in input
log_error("Unknown size of audio block starting at offset %d",
" (should never happen)",
decodingPosition);
}
}
}
#ifdef GNASH_DEBUG_SOUNDS_DECODING
log_debug(" decoding %d bytes, parse:%d", inputSize, parse);
#endif
assert(inputSize);
const boost::uint8_t* input = getEncodedData(decodingPosition);
boost::uint32_t consumed = 0;
boost::uint32_t decodedDataSize = 0;
boost::uint8_t* decodedData = _decoder->decode(
input,
inputSize,
decodedDataSize,
consumed,
parse);
decodingPosition += consumed;
assert(!(decodedDataSize%2));
// @todo I hope there are no alignment issues in this cast from int8_t* to int16_t* !
boost::int16_t* samples = reinterpret_cast<boost::int16_t*>(decodedData);
unsigned int nSamples = decodedDataSize/2;
#ifdef GNASH_DEBUG_MIXING
log_debug(" applying volume/envelope to %d bytes (%d samples)"
"of decoded data", decodedDataSize, nSamples);
#endif
// If the volume needs adjustments we call a function to do that (why are we doing this manually ?)
if (_soundDef.volume != 100) // volume is a private member
{
// TODO: have adjust_volume take samples, not bytes
adjustVolume(samples, nSamples, _soundDef.volume/100.0);
}
/// @todo is use of envelopes really mutually exclusive with
/// setting the volume ??
else if (envelopes) // envelopes are a private member
{
unsigned int firstSample = playbackPosition/2;
// TODO: have applyEnvelopes take samples, not bytes
applyEnvelopes(samples, nSamples, firstSample, *envelopes);
}
#ifdef GNASH_DEBUG_MIXING
log_debug(" appending %d bytes to decoded buffer", decodedDataSize);
#endif
// decodedData ownership transferred here
appendDecodedData(decodedData, decodedDataSize);
}
const boost::uint8_t*
EmbedSoundInst::getEncodedData(unsigned long int pos)
{
return _soundDef.data(pos);
}
/* static private */
void
EmbedSoundInst::adjustVolume(boost::int16_t* data, unsigned int nSamples, float volume)
{
//log_error("skipping volume adjustment (intentionally)"); return;
for (unsigned int i=0; i<nSamples; ++i)
{
data[i] = data[i] * volume;
}
}
/* private */
void
EmbedSoundInst::applyEnvelopes(boost::int16_t* samples, unsigned int nSamples,
unsigned int firstSampleOffset, const SoundEnvelopes& env)
{
//log_error("skipping envelopes (intentionally)"); return;
// Number of envelopes defined
size_t numEnvs = env.size();
// Nothing to do if we applied all envelopes already
if ( numEnvs <= current_env)
{
return;
}
// Not yet time to use the current envelope
if (env[current_env].m_mark44 >= firstSampleOffset+nSamples)
{
return;
}
assert(env[current_env].m_mark44 < firstSampleOffset+nSamples);
// Get next envelope position (absolute samples offset)
boost::uint32_t next_env_pos = 0;
if (current_env == (env.size()-1)) {
// If there is no "next envelope" then set the next envelope start point to be unreachable
next_env_pos = env[current_env].m_mark44 + nSamples + 1;
} else {
next_env_pos = env[current_env+1].m_mark44;
}
// Scan all samples in the block, applying the envelope
// which is in effect in each subportion
for (unsigned int i=0; i<nSamples/2; i+=2)
{
// @todo cache these left/right floats (in the SoundEnvelope class?)
float left = env[current_env].m_level0 / 32768.0;
float right = env[current_env].m_level1 / 32768.0;
samples[i] = samples[i] * left; // Left
samples[i+1] = samples[i+1] * right; // Right
// TODO: continue from here (what is the code below doing ??
// if we encounter the offset of next envelope,
// switch to it !
if ( (firstSampleOffset+nSamples-i) >= next_env_pos )
{
if ( numEnvs <= ++current_env )
{
// no more envelopes to apply
return;
}
// Get next envelope position (absolute samples offset)
if (current_env == (env.size()-1)) {
// If there is no "next envelope" then set the next envelope start point to be unreachable
next_env_pos = env[current_env].m_mark44 + nSamples + 1;
} else {
next_env_pos = env[current_env+1].m_mark44;
}
}
}
}
bool
EmbedSoundInst::eof() const
{
// it isn't threaded, but just in case, we call decodingCompleted first
// and we also check loopCount... (over paranoid?)
return ( ( decodingCompleted() || reachedCustomEnd() ) && !loopCount && !decodedSamplesAhead() );
}
EmbedSoundInst::~EmbedSoundInst()
{
_soundDef.eraseActiveSound(this);
}
void
EmbedSoundInst::appendDecodedData(boost::uint8_t* data, unsigned int size)
{
if ( ! _decodedData.get() )
{
_decodedData.reset( new SimpleBuffer );
}
_decodedData->append(data, size);
delete [] data; // ownership transferred...
}
} // gnash.sound namespace
} // namespace gnash