/* * SoundStreamHead2.java * Transform * * Copyright (c) 2001-2010 Flagstone Software Ltd. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of Flagstone Software Ltd. nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ package com.flagstone.transform.sound; import java.io.IOException; import com.flagstone.transform.Constants; import com.flagstone.transform.MovieTag; import com.flagstone.transform.MovieTypes; import com.flagstone.transform.coder.Coder; import com.flagstone.transform.coder.Context; import com.flagstone.transform.coder.SWFDecoder; import com.flagstone.transform.coder.SWFEncoder; import com.flagstone.transform.exception.IllegalArgumentRangeException; import com.flagstone.transform.exception.IllegalArgumentValueException; /** * SoundStreamHead2 defines the format of a streaming sound, identifying the * encoding scheme, the rate at which the sound will be played and the size of * the decoded samples. * * <p> * Sounds may be either mono or stereo and encoded using either NATIVE_PCM, * ADPCM, MP3 or NELLYMOSER or SPEEX formats and have sampling rates of 5512, * 11025, 22050 or 44100 Hertz. * </p> * * <p> * The actual sound is streamed used the SoundStreamBlock class which contains * the data for each frame in a movie. * </p> * * <p> * When a stream sound is played if the Flash Player cannot render the frames * fast enough to maintain synchronisation with the sound being played then * frames will be skipped. Normally the player will reduce the frame rate so * every frame of a movie is played. The different sets of attributes that * identify how the sound will be played compared to the way it was encoded * allows the Player more control over how the animation is rendered. Reducing * the resolution or playback rate can improve synchronisation with the frames * displayed. * </p> * * <p> * SoundStreamHead2 allows way the sound is played to differ from the way it is * encoded and streamed to the player. This allows the Player more control over * how the animation is rendered. Reducing the resolution or playback rate can * improve synchronisation with the frames displayed. * </p> * * @see SoundStreamBlock * @see SoundStreamHead */ public final class SoundStreamHead2 implements MovieTag { /** Format string used in toString() method. */ private static final String FORMAT = "SoundStreamHead2: { format=%s;" + " playbackRate=%d; playbackChannels=%d; playbackSampleSize=%d;" + " streamRate=%d; streamChannels=%d; streamSampleSize=%d;" + " streamSampleCount=%d; latency=%d}"; /** The code representing the sound format. */ private int format; /** The playback rate in KHz. */ private int playRate; /** The number of playback channels: 1 = mono, 2 = stereo. */ private int playChannels; /** The number of bits in each sample. */ private int playSampleSize; /** The sound rate in KHz of the stream. */ private int streamRate; /** The number of channels in the stream: 1 = mono, 2 = stereo. */ private int streamChannels; /** The number of bits in each stream sample. */ private int streamSampleSize; /** The number of samples in the stream. */ private int streamSampleCount; /** The latency for MP3 sounds. */ private int latency; /** The length of the object, minus the header, when it is encoded. */ private transient int length; /** * The following variable is used to preserve the value of a reserved field * when decoding then encoding an existing Flash file. Macromedia's file * file format specification states that this field is always zero - it is * not, so this is used to preserve the value in case it is implementing an * undocumented feature. */ private transient int reserved = 0; /** * Creates and initialises a SoundStreamHead2 object using values encoded * in the Flash binary format. * * @param coder * an SWFDecoder object that contains the encoded Flash data. * * @throws IOException * if an error occurs while decoding the data. */ public SoundStreamHead2(final SWFDecoder coder) throws IOException { length = coder.readUnsignedShort() & Coder.LENGTH_FIELD; if (length == Coder.IS_EXTENDED) { length = coder.readInt(); } coder.mark(); int info = coder.readByte(); reserved = (info & Coder.NIB1) >> Coder.TO_LOWER_NIB; playRate = readRate(info & Coder.PAIR1); playSampleSize = ((info & Coder.BIT1) >> 1) + 1; playChannels = (info & Coder.BIT0) + 1; info = coder.readByte(); format = (info & Coder.NIB1) >> Coder.TO_LOWER_NIB; streamRate = readRate(info & Coder.PAIR1); streamSampleSize = ((info & Coder.BIT1) >> 1) + 1; streamChannels = (info & Coder.BIT0) + 1; streamSampleCount = coder.readUnsignedShort(); // CHECKSTYLE IGNORE MagicNumberCheck FOR NEXT 1 LINES if ((length == 6) && (format == 2)) { latency = coder.readSignedShort(); } coder.check(length); coder.unmark(); } /** * Creates a SoundStreamHead2 object specifying all the parameters required * to define the sound. * * @param encoding * the compression format for the sound data, either * DefineSound.NATIVE_PCM, DefineSound.ADPCM, DefineSound.MP3, * DefineSound.PCM or DefineSound.NELLYMOSER (Flash 6+ only). * @param playbackRate * the recommended rate for playing the sound, either 5512, * 11025, 22050 or 44100 Hz. * @param playbackChannels * The recommended number of playback channels: 1 = mono or 2 = * stereo. * @param playSize * the recommended uncompressed sample size for playing the * sound, either 1 or 2 bytes. * @param streamingRate * the rate at which the sound was sampled, either 5512, 11025, * 22050 or 44100 Hz. * @param streamingChannels * the number of channels: 1 = mono or 2 = stereo. * @param streamingSize * the sample size for the sound, either 1 or 2 bytes. * @param streamingCount * the number of samples in each subsequent SoundStreamBlock * object. */ public SoundStreamHead2(final SoundFormat encoding, final int playbackRate, final int playbackChannels, final int playSize, final int streamingRate, final int streamingChannels, final int streamingSize, final int streamingCount) { setFormat(encoding); setPlayRate(playbackRate); setPlayChannels(playbackChannels); setPlaySampleSize(playSize); setStreamRate(streamingRate); setStreamChannels(streamingChannels); setStreamSampleSize(streamingSize); setStreamSampleCount(streamingCount); } /** * Creates and initialises a SoundStreamHead2 object using the values copied * from another SoundStreamHead2 object. * * @param object * a SoundStreamHead2 object from which the values will be * copied. */ public SoundStreamHead2(final SoundStreamHead2 object) { format = object.format; playRate = object.playRate; playChannels = object.playChannels; playSampleSize = object.playSampleSize; streamRate = object.streamRate; streamChannels = object.streamChannels; streamSampleSize = object.streamSampleSize; streamSampleCount = object.streamSampleCount; latency = object.latency; } /** * Get the compression format used. * * @return the format for the sound data. */ public SoundFormat getFormat() { return SoundFormat.fromInt(format); } /** * Sets the format for the streaming sound. * * @param encoding * the compression format for the sound data, must be either * NATIVE_PCM, ADPCM, MP3, PCM or NELLYMOSER from SoundFormat. */ public void setFormat(final SoundFormat encoding) { format = encoding.getValue(); } /** * Returns the recommended playback rate: 5512, 11025, 22050 or 44100 Hz. * * @return the playback rate in Hertz. */ public int getPlayRate() { return playRate; } /** * Get the recommended number of playback channels = 1 = mono 2 = * stereo. * * @return the number of channels. */ public int getPlayChannels() { return playChannels; } /** * Get the recommended playback sample range in bytes: 1 or 2. * * @return the number of bytes in each sample. */ public int getPlaySampleSize() { return playSampleSize; } /** * Get the sample rate: 5512, 11025, 22050 or 44100 Hz in the streaming * sound. * * @return the stream rate in Hertz. */ public float getStreamRate() { return streamRate; } /** * Get the number of channels, 1 = mono 2 = stereo, in the streaming * sound. * * @return the number of channels defined in the streaming sound. */ public int getStreamChannels() { return streamChannels; } /** * Get the sample size in bytes: 1 or 2 in the streaming sound. * * @return the number of bytes per sample in the streaming sound. */ public int getStreamSampleSize() { return streamSampleSize; } /** * Get the average number of samples in each stream block following. * * @return the number of sample in each stream block. */ public int getStreamSampleCount() { return streamSampleCount; } /** * Sets the recommended playback rate in Hz. Must be either: 5512, 11025, * 22050 or 44100. * * @param rate * the recommended rate for playing the sound. */ public void setPlayRate(final int rate) { if ((rate != SoundRate.KHZ_5K) && (rate != SoundRate.KHZ_11K) && (rate != SoundRate.KHZ_22K) && (rate != SoundRate.KHZ_44K)) { throw new IllegalArgumentValueException( new int[] {SoundRate.KHZ_5K, SoundRate.KHZ_11K, SoundRate.KHZ_22K, SoundRate.KHZ_44K}, rate); } playRate = rate; } /** * Sets the recommended number of playback channels = 1 = mono 2 = stereo. * * @param channels * the recommended number of playback channels. */ public void setPlayChannels(final int channels) { if ((channels < 1) || (channels > 2)) { throw new IllegalArgumentRangeException(1, 2, channels); } playChannels = channels; } /** * Sets the recommended playback sample size in bytes. Must be wither 1 or * 2. * * @param playSize * the recommended sample size for playing the sound. */ public void setPlaySampleSize(final int playSize) { if ((playSize < 1) || (playSize > 2)) { throw new IllegalArgumentRangeException(1, 2, playSize); } playSampleSize = playSize; } /** * Sets the sample rate in Hz for the streaming sound. Must be either: 5512, * 11025, 22050 or 44100. * * @param rate * the rate at which the streaming sound was sampled. */ public void setStreamRate(final int rate) { if ((rate != SoundRate.KHZ_5K) && (rate != SoundRate.KHZ_11K) && (rate != SoundRate.KHZ_22K) && (rate != SoundRate.KHZ_44K)) { throw new IllegalArgumentValueException( new int[] {SoundRate.KHZ_5K, SoundRate.KHZ_11K, SoundRate.KHZ_22K, SoundRate.KHZ_44K}, rate); } streamRate = rate; } /** * Sets the number of channels in the streaming sound: 1 = mono 2 = stereo. * * @param channels * the number of channels in the streaming sound. */ public void setStreamChannels(final int channels) { if ((channels < 1) || (channels > 2)) { throw new IllegalArgumentRangeException(1, 2, channels); } streamChannels = channels; } /** * Sets the sample size in bytes for the streaming sound. Must be 1 or 2. * * @param size * the sample size for the sound. */ public void setStreamSampleSize(final int size) { if ((size < 1) || (size > 2)) { throw new IllegalArgumentRangeException(1, 2, size); } streamSampleSize = size; } /** * Sets the number of samples in each stream block. * * @param count * the number of samples in each subsequent SoundStreamBlock * object. */ public void setStreamSampleCount(final int count) { if (count < 0) { throw new IllegalArgumentRangeException(0, Integer.MAX_VALUE, count); } streamSampleCount = count; } /** * For MP3 encoded sounds, returns the number of samples to skip when * starting to play a sound. * * @return the number of samples skipped in an MP3 encoded sound Returns 0 * for other sound formats. */ public int getLatency() { return latency; } /** * Set the number of samples to skip when starting to play an MP3 encoded * sound. * * @param delay * the number of samples to be skipped in an MP3 encoded sound * should be 0 for other sound formats. */ public void setLatency(final int delay) { latency = delay; } /** {@inheritDoc} */ @Override public SoundStreamHead2 copy() { return new SoundStreamHead2(this); } @Override public String toString() { return String.format(FORMAT, format, playRate, playChannels, playSampleSize, streamRate, streamChannels, streamSampleSize, streamSampleCount, latency); } /** {@inheritDoc} */ @Override public int prepareToEncode(final Context context) { // CHECKSTYLE IGNORE MagicNumberCheck FOR NEXT 1 LINES length = 4; if ((format == 2) && (latency > 0)) { length += 2; } return (length > Coder.HEADER_LIMIT ? Coder.LONG_HEADER : Coder.SHORT_HEADER) + length; } /** {@inheritDoc} */ @Override public void encode(final SWFEncoder coder, final Context context) throws IOException { if (length > Coder.HEADER_LIMIT) { coder.writeShort((MovieTypes.SOUND_STREAM_HEAD_2 << Coder.LENGTH_FIELD_SIZE) | Coder.IS_EXTENDED); coder.writeInt(length); } else { coder.writeShort((MovieTypes.SOUND_STREAM_HEAD_2 << Coder.LENGTH_FIELD_SIZE) | length); } if (Constants.DEBUG) { coder.mark(); } int bits = reserved << Coder.TO_UPPER_NIB; bits |= writeRate(playRate); bits |= (playSampleSize - 1) << 1; bits |= playChannels - 1; coder.writeByte(bits); bits = format << Coder.TO_UPPER_NIB; bits |= writeRate(streamRate); bits |= (streamSampleSize - 1) << 1; bits |= streamChannels - 1; coder.writeByte(bits); coder.writeShort(streamSampleCount); if ((format == 2) && (latency > 0)) { coder.writeShort(latency); } if (Constants.DEBUG) { coder.check(length); coder.unmark(); } } /** * Convert the code representing the rate into actual KHz. * @param value the code representing the sound rate. * @return the actual rate in KHz. */ private int readRate(final int value) { int rate; switch (value) { case 0: rate = SoundRate.KHZ_5K; break; case Coder.BIT2: rate = SoundRate.KHZ_11K; break; case Coder.BIT3: rate = SoundRate.KHZ_22K; break; case Coder.BIT2 | Coder.BIT3: rate = SoundRate.KHZ_44K; break; default: rate = 0; break; } return rate; } /** * Convert the rate in KHz to the code that represents the rate. * @param rate the rate in KHz. * @return the code representing the sound rate. */ private int writeRate(final int rate) { int value; switch (rate) { case SoundRate.KHZ_11K: value = Coder.BIT2; break; case SoundRate.KHZ_22K: value = Coder.BIT3; break; case SoundRate.KHZ_44K: value = Coder.BIT2 | Coder.BIT3; break; default: value = 0; break; } return value; } }