/****************************************************************************** * * * Copyright (c) 1999-2003 Wimba S.A., All Rights Reserved. * * * * COPYRIGHT: * * This software is the property of Wimba S.A. * * This software is redistributed under the Xiph.org variant of * * the BSD license. * * 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 Wimba, the Xiph.org Foundation nor the names of * * its contributors may be used to endorse or promote products derived * * from this software without specific prior written permission. * * * * WARRANTIES: * * This software is made available by the authors in the hope * * that it will be useful, but without any warranty. * * Wimba S.A. is not liable for any consequence related to the * * use of the provided software. * * * * Class: PcmWaveWriter.java * * * * Author: James LAWRENCE * * Modified by: Marc GIMPEL * * * * Date: March 2003 * * * ******************************************************************************/ /* $Id: PcmWaveWriter.java,v 1.2 2004/10/21 16:21:57 mgimpel Exp $ */ package org.xiph.speex; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; /** * Writes basic PCM wave files from binary audio data. * * <p>Here's an example that writes 2 seconds of silence * <pre> * PcmWaveWriter s_wsw = new PcmWaveWriter(2, 44100); * byte[] silence = new byte[16*2*44100]; * wsw.Open("C:\\out.wav"); * wsw.WriteHeader(); * wsw.WriteData(silence, 0, silence.length); * wsw.WriteData(silence, 0, silence.length); * wsw.Close(); * </pre> * * @author Jim Lawrence, helloNetwork.com * @author Marc Gimpel, Wimba S.A. (mgimpel@horizonwimba.com) * @version $Revision: 1.2 $ */ public class PcmWaveWriter extends AudioFileWriter { /** Wave type code of PCM */ public static final short WAVE_FORMAT_PCM = (short) 0x01; /** Wave type code of Speex */ public static final short WAVE_FORMAT_SPEEX = (short) 0xa109; /** * Table describing the number of frames per packet in a Speex Wave file, * depending on its mode-1 (1=NB, 2=WB, 3=UWB), channels-1 (1=mono, 2=stereo) * and the quality setting (0 to 10). * See end of file for exerpt from SpeexACM code for more explanations. */ public static final int[][][] WAVE_FRAME_SIZES = new int[][][] {{{8, 8, 8, 1, 1, 2, 2, 2, 2, 2, 2}, // NB mono {2, 1, 1, 7, 7, 8, 8, 8, 8, 3, 3}}, // NB stereo {{8, 8, 8, 2, 1, 1, 2, 2, 2, 2, 2}, // WB mono {1, 2, 2, 8, 7, 6, 3, 3, 3, 3, 3}}, // WB stereo {{8, 8, 8, 1, 2, 2, 1, 1, 1, 1, 1}, // UWB mono {2, 1, 1, 7, 8, 3, 6, 6, 5, 5, 5}}}; // UWB stereo /** * Table describing the number of bit per Speex frame, depending on its * mode-1 (1=NB, 2=WB, 3=UWB), channels-1 (1=mono, 2=stereo) and the quality * setting (0 to 10). * See end of file for exerpt from SpeexACM code for more explanations. */ public static final int[][][] WAVE_BITS_PER_FRAME = new int[][][] {{{ 43, 79, 119, 160, 160, 220, 220, 300, 300, 364, 492}, // NB mono { 60, 96, 136, 177, 177, 237, 237, 317, 317, 381, 509}}, // NB stereo {{ 79, 115, 155, 196, 256, 336, 412, 476, 556, 684, 844}, // WB mono { 96, 132, 172, 213, 273, 353, 429, 493, 573, 701, 861}}, // WB stereo {{ 83, 151, 191, 232, 292, 372, 448, 512, 592, 720, 880}, // UWB mono {100, 168, 208, 249, 309, 389, 465, 529, 609, 737, 897}}}; // UWB stereo private RandomAccessFile raf; /** Defines the encoder mode (0=NB, 1=WB and 2-UWB). */ private int mode; /** */ private int quality; /** Defines the sampling rate of the audio input. */ private int sampleRate; /** Defines the number of channels of the audio input (1=mono, 2=stereo). */ private int channels; /** Defines the number of frames per speex packet. */ private int nframes; /** Defines whether or not to use VBR (Variable Bit Rate). */ private boolean vbr; /** */ private int size; /** */ private boolean isPCM; /** * Constructor. */ public PcmWaveWriter() { size = 0; } /** * Constructor. * @param sampleRate the number of samples per second. * @param channels the number of audio channels (1=mono, 2=stereo, ...). */ public PcmWaveWriter(final int sampleRate, final int channels) { this(); setPCMFormat(sampleRate, channels); } /** * Constructor. * @param mode the mode of the encoder (0=NB, 1=WB, 2=UWB). * @param quality * @param sampleRate the number of samples per second. * @param channels the number of audio channels (1=mono, 2=stereo, ...). * @param nframes the number of frames per speex packet. * @param vbr */ public PcmWaveWriter(final int mode, final int quality, final int sampleRate, final int channels, final int nframes, final boolean vbr) { this(); setSpeexFormat(mode, quality, sampleRate, channels, nframes, vbr); } /** * Sets the output format for a PCM Wave file. * Must be called before WriteHeader(). * @param sampleRate the number of samples per second. * @param channels the number of audio channels (1=mono, 2=stereo, ...). */ private void setPCMFormat(final int sampleRate, final int channels) { this.channels = channels; this.sampleRate = sampleRate; isPCM = true; } /** * Sets the output format for a Speex Wave file. * Must be called before WriteHeader(). * @param mode the mode of the encoder (0=NB, 1=WB, 2=UWB). * @param quality * @param sampleRate the number of samples per second. * @param channels the number of audio channels (1=mono, 2=stereo, ...). * @param nframes the number of frames per speex packet. * @param vbr */ private void setSpeexFormat(final int mode, final int quality, final int sampleRate, final int channels, final int nframes, final boolean vbr) { this.mode = mode; this.quality = quality; this.sampleRate = sampleRate; this.channels = channels; this.nframes = nframes; this.vbr = vbr; isPCM = false; } /** * Closes the output file. * MUST be called to have a correct stream. * @exception IOException if there was an exception closing the Audio Writer. */ public void close() throws IOException { /* update the total file length field from RIFF chunk */ raf.seek(4); int fileLength = (int) raf.length() - 8; writeInt(raf, fileLength); /* update the data chunk length size */ raf.seek(40); writeInt(raf, size); /* close the output file */ raf.close(); } /** * Open the output file. * @param file - file to open. * @exception IOException if there was an exception opening the Audio Writer. */ public void open(final File file) throws IOException { file.delete(); raf = new RandomAccessFile(file, "rw"); size = 0; } /** * Open the output file. * @param filename filename to open. * @exception IOException if there was an exception opening the Audio Writer. */ public void open(final String filename) throws IOException { open(new File(filename)); } /** * Writes the initial data chunks that start the wave file. * Prepares file for data samples to written. * @param comment ignored by the WAV header. * @exception IOException */ public void writeHeader(final String comment) throws IOException { /* writes the RIFF chunk indicating wave format */ byte[] chkid = "RIFF".getBytes(); raf.write(chkid, 0, chkid.length); writeInt(raf, 0); /* total length must be blank */ chkid = "WAVE".getBytes(); raf.write(chkid, 0, chkid.length); /* format subchunk: of size 16 */ chkid = "fmt ".getBytes(); raf.write(chkid, 0, chkid.length); if (isPCM) { writeInt(raf, 16); // Size of format chunk writeShort(raf, WAVE_FORMAT_PCM); // Format tag: PCM writeShort(raf, (short)channels); // Number of channels writeInt(raf, sampleRate); // Sampling frequency writeInt(raf, sampleRate*channels*2); // Average bytes per second writeShort(raf, (short) (channels*2)); // Blocksize of data writeShort(raf, (short) 16); // Bits per sample } else { int length = comment.length(); writeInt(raf, (short) (18+2+80+length)); // Size of format chunk writeShort(raf, WAVE_FORMAT_SPEEX); // Format tag: Speex writeShort(raf, (short)channels); // Number of channels writeInt(raf, sampleRate); // Sampling frequency writeInt(raf, (calculateEffectiveBitrate(mode, channels, quality) + 7) >> 3); // Average bytes per second writeShort(raf, (short) calculateBlockSize(mode, channels, quality)); // Blocksize of data writeShort(raf, (short) quality); // Bits per sample writeShort(raf, (short) (2+80+length)); // The count in bytes of the extra size raf.writeByte(0xff & 1); // ACM major version number raf.writeByte(0xff & 0); // ACM minor version number raf.write(buildSpeexHeader(sampleRate, mode, channels, vbr, nframes)); raf.writeBytes(comment); } /* write the start of data chunk */ chkid = "data".getBytes(); raf.write(chkid, 0, chkid.length); writeInt(raf, 0); } /** * Writes a packet of audio. * @param data audio data * @param offset the offset from which to start reading the data. * @param len the length of data to read. * @exception IOException */ public void writePacket(final byte[] data, final int offset, final int len) throws IOException { raf.write(data, offset, len); size+= len; } /** * Calculates effective bitrate (considering padding). * See end of file for exerpt from SpeexACM code for more explanations. * @param mode * @param channels * @param quality * @return effective bitrate (considering padding). */ private static final int calculateEffectiveBitrate(final int mode, final int channels, final int quality) { return ((((WAVE_FRAME_SIZES[mode-1][channels-1][quality] * WAVE_BITS_PER_FRAME[mode-1][channels-1][quality]) + 7) >> 3) * 50 * 8) / WAVE_BITS_PER_FRAME[mode-1][channels-1][quality]; } /** * Calculates block size (considering padding). * See end of file for exerpt from SpeexACM code for more explanations. * @param mode * @param channels * @param quality * @return block size (considering padding). */ private static final int calculateBlockSize(final int mode, final int channels, final int quality) { return (((WAVE_FRAME_SIZES[mode-1][channels-1][quality] * WAVE_BITS_PER_FRAME[mode-1][channels-1][quality]) + 7) >> 3); } } // The following is taken from the SpeexACM 1.0.1.1 Source code (codec.c file). // // This array describes how many bits are required by an encoded audio frame. // It also specifies the optimal framesperblock parameter to minimize // padding loss. It also lists the effective bitrate (considering padding). // // The array indices are rate, channels, quality (each as a 0 based index) // /* struct tagQualityInfo { UINT nBitsPerFrame; UINT nFrameSize; UINT nFramesPerBlock; UINT nEffectiveBitrate; } QualityInfo[3][2][11] = { 43, 160, 8, 2150, // 8000 1 0 79, 160, 8, 3950, // 8000 1 1 119, 160, 8, 5950, // 8000 1 2 160, 160, 1, 8000, // 8000 1 3 160, 160, 1, 8000, // 8000 1 4 220, 160, 2, 11000, // 8000 1 5 220, 160, 2, 11000, // 8000 1 6 300, 160, 2, 15000, // 8000 1 7 300, 160, 2, 15000, // 8000 1 8 364, 160, 2, 18200, // 8000 1 9 492, 160, 2, 24600, // 8000 1 10 60, 160, 2, 3000, // 8000 2 0 96, 160, 1, 4800, // 8000 2 1 136, 160, 1, 6800, // 8000 2 2 177, 160, 7, 8857, // 8000 2 3 177, 160, 7, 8857, // 8000 2 4 237, 160, 8, 11850, // 8000 2 5 237, 160, 8, 11850, // 8000 2 6 317, 160, 8, 15850, // 8000 2 7 317, 160, 8, 15850, // 8000 2 8 381, 160, 3, 19066, // 8000 2 9 509, 160, 3, 25466, // 8000 2 10 79, 320, 8, 3950, // 16000 1 0 115, 320, 8, 5750, // 16000 1 1 155, 320, 8, 7750, // 16000 1 2 196, 320, 2, 9800, // 16000 1 3 256, 320, 1, 12800, // 16000 1 4 336, 320, 1, 16800, // 16000 1 5 412, 320, 2, 20600, // 16000 1 6 476, 320, 2, 23800, // 16000 1 7 556, 320, 2, 27800, // 16000 1 8 684, 320, 2, 34200, // 16000 1 9 844, 320, 2, 42200, // 16000 1 10 96, 320, 1, 4800, // 16000 2 0 132, 320, 2, 6600, // 16000 2 1 172, 320, 2, 8600, // 16000 2 2 213, 320, 8, 10650, // 16000 2 3 273, 320, 7, 13657, // 16000 2 4 353, 320, 6, 17666, // 16000 2 5 429, 320, 3, 21466, // 16000 2 6 493, 320, 3, 24666, // 16000 2 7 573, 320, 3, 28666, // 16000 2 8 701, 320, 3, 35066, // 16000 2 9 861, 320, 3, 43066, // 16000 2 10 83, 640, 8, 4150, // 32000 1 0 151, 640, 8, 7550, // 32000 1 1 191, 640, 8, 9550, // 32000 1 2 232, 640, 1, 11600, // 32000 1 3 292, 640, 2, 14600, // 32000 1 4 372, 640, 2, 18600, // 32000 1 5 448, 640, 1, 22400, // 32000 1 6 512, 640, 1, 25600, // 32000 1 7 592, 640, 1, 29600, // 32000 1 8 720, 640, 1, 36000, // 32000 1 9 880, 640, 1, 44000, // 32000 1 10 100, 640, 2, 5000, // 32000 2 0 168, 640, 1, 8400, // 32000 2 1 208, 640, 1, 10400, // 32000 2 2 249, 640, 7, 12457, // 32000 2 3 309, 640, 8, 15450, // 32000 2 4 389, 640, 3, 19466, // 32000 2 5 465, 640, 6, 23266, // 32000 2 6 529, 640, 6, 26466, // 32000 2 7 609, 640, 5, 30480, // 32000 2 8 737, 640, 5, 36880, // 32000 2 9 897, 640, 5, 44880, // 32000 2 10 }; */