Source code: com/anotherbigidea/flash/sound/ADPCMHelper.java
1 /****************************************************************
2 * Copyright (c) 2001, David N. Main, All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or
5 * without modification, are permitted provided that the
6 * following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above
9 * copyright notice, this list of conditions and the following
10 * disclaimer.
11 *
12 * 2. Redistributions in binary form must reproduce the above
13 * copyright notice, this list of conditions and the following
14 * disclaimer in the documentation and/or other materials
15 * provided with the distribution.
16 *
17 * 3. The name of the author may not be used to endorse or
18 * promote products derived from this software without specific
19 * prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY
22 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
23 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
24 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25 * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
27 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
31 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
32 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 ****************************************************************/
34 package com.anotherbigidea.flash.sound;
35
36 import java.io.*;
37 import java.util.*;
38 import com.anotherbigidea.io.*;
39 import com.anotherbigidea.flash.*;
40 import com.anotherbigidea.flash.structs.*;
41 import com.anotherbigidea.flash.interfaces.*;
42 import com.anotherbigidea.flash.writers.*;
43 import javax.sound.sampled.*;
44 import com.anotherbigidea.flash.movie.*;
45
46 /**
47 * ADPCM Utilities
48 */
49 public class ADPCMHelper
50 {
51 public class ADPCMPacket
52 {
53 public int initialLeftSample = 0;
54 public int initialLeftIndex = 0;
55 public int initialRightSample = 0;
56 public int initialRightIndex = 0;
57 public int[] leftData;
58 public int[] rightData;
59 public int sampleCount;
60 }
61
62 protected AudioInputStream audioIn;
63 protected boolean isStereo;
64 protected boolean is16Bit;
65 protected int sampleRate;
66 protected int rate;
67 protected int samplesPerFrame;
68 protected boolean isSigned;
69
70 protected int sampleCount = 0;
71
72 protected ADPCMEncodeStream leftEncoder;
73 protected ADPCMEncodeStream rightEncoder;
74 protected ADPCMPacket currentPacket;
75
76 public ADPCMHelper( InputStream audioFile, int framesPerSecond )
77 throws IOException, UnsupportedAudioFileException
78 {
79 audioIn = AudioSystem.getAudioInputStream( new BufferedInputStream( audioFile ) );
80
81 AudioFormat format = audioIn.getFormat();
82 int frameSize = format.getFrameSize();
83 isStereo = format.getChannels() == 2;
84 is16Bit = format.getSampleSizeInBits() > 8;
85 sampleRate = (int)format.getSampleRate();
86
87 isSigned = format.getEncoding() == AudioFormat.Encoding.PCM_SIGNED;
88
89 //System.out.println( format + " FrameSize=" + frameSize );
90
91 if ( sampleRate >= 44000 ) sampleRate = 44000;
92 else if( sampleRate >= 22000 ) sampleRate = 22000;
93 else if( sampleRate >= 11000 ) sampleRate = 11000;
94 else sampleRate = 5500;
95
96 rate = SWFConstants.SOUND_FREQ_5_5KHZ;
97 if ( sampleRate == 44000 ) rate = SWFConstants.SOUND_FREQ_44KHZ;
98 else if( sampleRate == 22000 ) rate = SWFConstants.SOUND_FREQ_22KHZ;
99 else if( sampleRate == 11000 ) rate = SWFConstants.SOUND_FREQ_11KHZ;
100
101 samplesPerFrame = sampleRate / framesPerSecond;
102
103 FramedInputStream frameIn = new FramedInputStream( audioIn, frameSize );
104
105 leftEncoder = new ADPCMEncodeStream( frameIn, is16Bit, isSigned );
106
107 if( isStereo )
108 {
109 rightEncoder = new ADPCMEncodeStream( frameIn, is16Bit, isSigned );
110 }
111 }
112
113 public Sound getSoundDefinition() throws IOException
114 {
115 ByteArrayOutputStream bout = new ByteArrayOutputStream();
116 OutStream out = new OutStream( bout );
117 sampleCount = 0;
118 boolean first = true;
119
120 for( ADPCMPacket packet = readPacket( ADPCMConstants.PACKET_SIZE );
121 packet != null;
122 packet = readPacket( ADPCMConstants.PACKET_SIZE ) )
123 {
124 sampleCount += packet.sampleCount + 1;
125 writePacket( packet, out, first );
126 first = false;
127
128 //System.out.println( "Packet size = " + packet.leftData.length );
129 }
130
131 out.flush();
132 byte[] soundData = bout.toByteArray();
133 return new Sound( SWFConstants.SOUND_FORMAT_ADPCM, rate, true, isStereo, sampleCount, soundData );
134 }
135
136 /**
137 * @return null if no more packets are available
138 */
139 public ADPCMPacket readPacket( int packetSize ) throws IOException
140 {
141 ADPCMPacket packet = new ADPCMPacket();
142 packet.initialLeftSample = leftEncoder.getFirstPacketSample();
143 if( leftEncoder.isDone() ) return null;
144
145 int count = 0;
146
147 if( isStereo )
148 {
149 packet.initialRightSample = rightEncoder.getFirstPacketSample();
150 }
151
152 packet.initialLeftIndex = leftEncoder.setIndex( packet.initialLeftSample );
153
154 if( isStereo )
155 {
156 packet.initialRightIndex = rightEncoder.setIndex( packet.initialRightSample );
157 }
158
159 packet.leftData = new int[ packetSize-1 ];;
160 if( isStereo ) packet.rightData = new int[ packetSize-1 ];;
161
162 for( int i = 0; i < packetSize-1; i++ )
163 {
164 packet.leftData[i] = leftEncoder.getDelta();
165
166 if( ! leftEncoder.isDone() ) count++;
167
168 if( isStereo ) packet.rightData[i] = rightEncoder.getDelta();
169 }
170
171 packet.sampleCount = count;
172 return packet;
173 }
174
175 /**
176 * Streaming block
177 * @return null if no more blocks
178 */
179 public byte[] getBlockData( boolean firstBlock ) throws IOException
180 {
181 ByteArrayOutputStream bout = new ByteArrayOutputStream();
182 OutStream os = new OutStream( bout );
183
184 currentPacket = readPacket( samplesPerFrame );
185 if( currentPacket == null ) return null;
186
187 writePacket( currentPacket, os, true );
188 os.flushBits();
189
190 return bout.toByteArray();
191 }
192
193 public void writePacket( ADPCMPacket packet, OutStream out, boolean includeBitCount ) throws IOException
194 {
195 if( packet == null ) return;
196
197 int sample = packet.initialLeftSample;
198
199 if( includeBitCount ) out.writeUBits( 2, 2 );
200 out.writeUBits( 16, sample );
201 out.writeUBits( 6, packet.initialLeftIndex );
202
203 if( isStereo )
204 {
205 sample = packet.initialRightSample;
206
207 out.writeUBits( 16, sample );
208 out.writeUBits( 6, packet.initialRightIndex );
209 }
210
211 for( int i = 0; i < packet.sampleCount; i++ )
212 {
213 out.writeUBits( 4, packet.leftData[i] );
214 if( isStereo ) out.writeUBits( 4, packet.rightData[i] );
215 }
216 }
217
218 public SoundStreamHead getStreamHeader()
219 {
220 return new SoundStreamHead( rate, true, isStereo,
221 SWFConstants.SOUND_FORMAT_ADPCM,
222 rate, true, isStereo,
223 samplesPerFrame );
224 }
225
226 /**
227 * InputStream wrapper that ensures AudioInputStream is read on a frame-by-frame basis
228 */
229 public static class FramedInputStream extends InputStream
230 {
231 protected InputStream in;
232 protected byte[] frameData;
233 protected int dataPtr;
234 protected int frameSize;
235 protected boolean done = false;
236
237 public FramedInputStream( InputStream in, int frameSize )
238 {
239 this.in = in;
240 this.frameSize = frameSize;
241 frameData = new byte[ frameSize ];
242 dataPtr = frameSize;
243 }
244
245 public int read() throws IOException
246 {
247 if( dataPtr < frameData.length )
248 {
249 int val = frameData[dataPtr++];
250 if( val < 0 ) val = val + 0x100;
251 return val;
252 }
253
254 if( done ) return -1;
255
256 dataPtr = 0;
257 int read = 0;
258
259 while( dataPtr < frameSize && (read = in.read( frameData, dataPtr, frameSize-dataPtr)) >= 0 )
260 {
261 dataPtr += read;
262 }
263
264 if( dataPtr == 0 )
265 {
266 done = true;
267 return -1;
268 }
269
270 while( dataPtr < frameData.length ) frameData[dataPtr++] = 0;
271 dataPtr = 0;
272
273 return read();
274 }
275 }
276
277
278 /**
279 * Makes a non-streaming SWF from a Java Sound compatible audio file.
280 * args[0] = audio in filename
281 * args[1] = SWF out filename
282 */
283 public static void main( String[] args ) throws Exception
284 {
285 InputStream audioFile = new FileInputStream( args[0] );
286
287 Movie movie = new Movie();
288 movie.setFrameRate(30);
289 ADPCMHelper helper = new ADPCMHelper( audioFile, 30 );
290
291 Frame frame = movie.appendFrame();
292 Sound sound = helper.getSoundDefinition();
293 int frames = frame.startSound( sound, 30 );
294
295 while( frames-- > 0 ) frame = movie.appendFrame();
296 frame.stop();
297
298 audioFile.close();
299
300 movie.write( args[1] );
301 }
302
303
304 /**
305 * Makes a streaming SWF from a Java Sound compatible audio file.
306 * args[0] = audio in filename
307 * args[1] = SWF out filename
308 */
309 public static void main2( String[] args ) throws Exception
310 {
311 InputStream audioFile = new FileInputStream( args[0] );
312 SWFWriter swfwriter = new SWFWriter( args[1] );
313
314 SWFTagTypes tags = new TagWriter( swfwriter );
315
316 tags.header( 5, -1, 200, 200, 12, -1 );
317 tags.tagSetBackgroundColor( new Color(255,255,255));
318
319 ADPCMHelper helper = new ADPCMHelper( audioFile, 12 );
320
321 SoundStreamHead header = helper.getStreamHeader();
322
323 header.write( tags );
324
325 byte[] block = helper.getBlockData( true );
326
327 while( block != null )
328 {
329 tags.tagSoundStreamBlock( block );
330 tags.tagShowFrame();
331
332 block = helper.getBlockData( false );
333 }
334
335 SWFActions acts = tags.tagDoAction();
336 acts.start(0);
337 acts.stop();
338 acts.end();
339 acts.done();
340
341 tags.tagShowFrame();
342 tags.tagEnd();
343
344 audioFile.close();
345 }
346 }