/****************************************************************************** * * * 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: OggSpeexWriter.java * * * * Author: Marc GIMPEL * * * * Date: 9th April 2003 * * * ******************************************************************************/ /* $Id: OggSpeexWriter.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.OutputStream; import java.io.FileOutputStream; import java.util.Random; /** * Ogg Speex Writer * * @author Marc Gimpel, Wimba S.A. (mgimpel@horizonwimba.com) * @version $Revision: 1.2 $ */ public class OggSpeexWriter extends AudioFileWriter { /** Number of packets in an Ogg page (must be less than 255) */ public static final int PACKETS_PER_OGG_PAGE = 250; /** The OutputStream */ private OutputStream out; /** Defines the encoder mode (0=NB, 1=WB and 2-UWB). */ private int mode; /** 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; /** Ogg Stream Serial Number */ private int streamSerialNumber; /** Data buffer */ private byte[] dataBuffer; /** Pointer within the Data buffer */ private int dataBufferPtr; /** Header buffer */ private byte[] headerBuffer; /** Pointer within the Header buffer */ private int headerBufferPtr; /** Ogg Page count */ private int pageCount; /** Speex packet count within an Ogg Page */ private int packetCount; /** * Absolute granule position * (the number of audio samples from beginning of file to end of Ogg Packet). */ private long granulepos; /** * Builds an Ogg Speex Writer. */ public OggSpeexWriter() { if (streamSerialNumber == 0) streamSerialNumber = new Random().nextInt(); dataBuffer = new byte[65565]; dataBufferPtr = 0; headerBuffer = new byte[255]; headerBufferPtr = 0; pageCount = 0; packetCount = 0; granulepos = 0; } /** * Builds an Ogg Speex Writer. * @param mode the mode of the encoder (0=NB, 1=WB, 2=UWB). * @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 OggSpeexWriter(final int mode, final int sampleRate, final int channels, final int nframes, final boolean vbr) { this(); setFormat(mode, sampleRate, channels, nframes, vbr); } /** * Sets the output format. * Must be called before WriteHeader(). * @param mode the mode of the encoder (0=NB, 1=WB, 2=UWB). * @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 setFormat(final int mode, final int sampleRate, final int channels, final int nframes, boolean vbr) { this.mode = mode; this.sampleRate = sampleRate; this.channels = channels; this.nframes = nframes; this.vbr = vbr; } /** * Sets the Stream Serial Number. * Must not be changed mid stream. * @param serialNumber */ public void setSerialNumber(final int serialNumber) { this.streamSerialNumber = serialNumber; } /** * Closes the output file. * @exception IOException if there was an exception closing the Audio Writer. */ public void close() throws IOException { flush(true); out.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(); out = new FileOutputStream(file); size = 0; } /** * Open the output file. * @param filename - file 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 header pages that start the Ogg Speex file. * Prepares file for data to be written. * @param comment description to be included in the header. * @exception IOException */ public void writeHeader(final String comment) throws IOException { int chksum; byte[] header; byte[] data; /* writes the OGG header page */ header = buildOggPageHeader(2, 0, streamSerialNumber, pageCount++, 1, new byte[] {80}); data = buildSpeexHeader(sampleRate, mode, channels, vbr, nframes); chksum = OggCrc.checksum(0, header, 0, header.length); chksum = OggCrc.checksum(chksum, data, 0, data.length); writeInt(header, 22, chksum); out.write(header); out.write(data); /* writes the OGG comment page */ header = buildOggPageHeader(0, 0, streamSerialNumber, pageCount++, 1, new byte[] {(byte) (comment.length() + 8)}); data = buildSpeexComment(comment); chksum = OggCrc.checksum(0, header, 0, header.length); chksum = OggCrc.checksum(chksum, data, 0, data.length); writeInt(header, 22, chksum); out.write(header); out.write(data); } /** * 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 { if (len <= 0) { // nothing to write return; } if (packetCount > PACKETS_PER_OGG_PAGE) { flush(false); } System.arraycopy(data, offset, dataBuffer, dataBufferPtr, len); dataBufferPtr += len; headerBuffer[headerBufferPtr++]=(byte)len; packetCount++; granulepos += nframes * (mode==2 ? 640 : (mode==1 ? 320 : 160)); } /** * Flush the Ogg page out of the buffers into the file. * @param eos - end of stream * @exception IOException */ private void flush(final boolean eos) throws IOException { int chksum; byte[] header; /* writes the OGG header page */ header = buildOggPageHeader((eos ? 4 : 0), granulepos, streamSerialNumber, pageCount++, packetCount, headerBuffer); chksum = OggCrc.checksum(0, header, 0, header.length); chksum = OggCrc.checksum(chksum, dataBuffer, 0, dataBufferPtr); writeInt(header, 22, chksum); out.write(header); out.write(dataBuffer, 0, dataBufferPtr); dataBufferPtr = 0; headerBufferPtr = 0; packetCount = 0; } }