package com.mogujie.tt.support.audio; import java.io.EOFException; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.util.ArrayList; import java.util.List; import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioTrack; import android.os.Message; import android.os.RecoverySystem.ProgressListener; import com.mogujie.tt.adapter.MessageAdapter; import com.mogujie.tt.config.HandlerConstant; import com.mogujie.tt.log.Logger; import com.mogujie.tt.ui.activity.MessageActivity; public class SpeexDecoder { protected Speex speexDecoder; protected boolean enhanced = false; private boolean paused = false; protected String srcFile; private List<ProgressListener> listenerList = new ArrayList<ProgressListener>(); private File srcPath; private AudioTrack track; public SpeexDecoder(File srcPath) throws Exception { this.srcPath = srcPath; } private void initializeAndroidAudio(int sampleRate) throws Exception { int minBufferSize = AudioTrack.getMinBufferSize(sampleRate, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT); if (minBufferSize < 0) { throw new Exception("Failed to get minimum buffer size: " + Integer.toString(minBufferSize)); } track = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, minBufferSize, AudioTrack.MODE_STREAM); } public void addOnMetadataListener(ProgressListener l) { listenerList.add(l); } public synchronized void setPaused(boolean paused) { this.paused = paused; } public synchronized boolean isPaused() { return paused; } @SuppressWarnings("resource") public void decode() throws Exception { byte[] header = new byte[2048]; byte[] payload = new byte[65536]; final int OGG_HEADERSIZE = 27; final int OGG_SEGOFFSET = 26; final String OGGID = "OggS"; int segments = 0; int curseg = 0; int bodybytes = 0; int decsize = 0; int packetNo = 0; // construct a new decoder speexDecoder = new Speex(); speexDecoder.init(); // open the input stream RandomAccessFile dis = new RandomAccessFile(srcPath, "r"); int origchksum; int chksum; try { // read until we get to EOF while (true) { if (Thread.interrupted()) { dis.close(); track.stop(); return; } while (this.isPaused()) { track.stop(); Thread.sleep(100); } // read the OGG header dis.readFully(header, 0, OGG_HEADERSIZE); origchksum = readInt(header, 22); readLong(header, 6); header[22] = 0; header[23] = 0; header[24] = 0; header[25] = 0; chksum = OggCrc.checksum(0, header, 0, OGG_HEADERSIZE); // make sure its a OGG header if (!OGGID.equals(new String(header, 0, 4))) { System.err.println("missing ogg id!"); return; } /* how many segments are there? */ segments = header[OGG_SEGOFFSET] & 0xFF; dis.readFully(header, OGG_HEADERSIZE, segments); chksum = OggCrc.checksum(chksum, header, OGG_HEADERSIZE, segments); /* decode each segment, writing output to wav */ for (curseg = 0; curseg < segments; curseg++) { if (Thread.interrupted()) { Logger.getLogger(MessageAdapter.class).d( srcPath + "be interrupted !!! for"); dis.close(); track.stop(); return; } while (this.isPaused()) { track.stop(); Thread.sleep(100); } /* get the number of bytes in the segment */ bodybytes = header[OGG_HEADERSIZE + curseg] & 0xFF; if (bodybytes == 255) { System.err.println("sorry, don't handle 255 sizes!"); return; } dis.readFully(payload, 0, bodybytes); chksum = OggCrc.checksum(chksum, payload, 0, bodybytes); /* decode the segment */ /* if first packet, read the Speex header */ if (packetNo == 0) { if (readSpeexHeader(payload, 0, bodybytes, true)) { packetNo++; } else { packetNo = 0; } } else if (packetNo == 1) { // Ogg Comment packet packetNo++; } else { /* get the amount of decoded data */ short[] decoded = new short[160]; if ((decsize = speexDecoder.decode(payload, decoded, 160)) > 0) { track.write(decoded, 0, decsize); track.setStereoVolume(0.7f, 0.7f); track.play(); } packetNo++; } } if (chksum != origchksum) throw new IOException("Ogg CheckSums do not match"); } } catch (EOFException eof) { } finally { if (track != null) { track.stop(); track.release(); } Message message = Message.obtain(); message.what = HandlerConstant.HANDLER_STOP_PLAY; message.obj = this.srcPath.getAbsolutePath(); MessageActivity.getUiHandler().sendMessage(message); } dis.close(); } /** * Reads the header packet. * * <pre> * 0 - 7: speex_string: "Speex " * 8 - 27: speex_version: "speex-1.0" * 28 - 31: speex_version_id: 1 * 32 - 35: header_size: 80 * 36 - 39: rate * 40 - 43: mode: 0=narrowband, 1=wb, 2=uwb * 44 - 47: mode_bitstream_version: 4 * 48 - 51: nb_channels * 52 - 55: bitrate: -1 * 56 - 59: frame_size: 160 * 60 - 63: vbr * 64 - 67: frames_per_packet * 68 - 71: extra_headers: 0 * 72 - 75: reserved1 * 76 - 79: reserved2 * </pre> * * @param packet * @param offset * @param bytes * @return * @throws Exception */ @SuppressWarnings("unused") private boolean readSpeexHeader(final byte[] packet, final int offset, final int bytes, boolean init) throws Exception { if (bytes != 80) { return false; } if (!"Speex ".equals(new String(packet, offset, 8))) { return false; } int mode = packet[40 + offset] & 0xFF; int sampleRate = readInt(packet, offset + 36); int channels = readInt(packet, offset + 48); int nframes = readInt(packet, offset + 64); int frameSize = readInt(packet, offset + 56); // System.out.println("mode=" + mode + " sampleRate==" + sampleRate // + " channels=" + channels + "nframes=" + nframes + "framesize=" // + frameSize); initializeAndroidAudio(sampleRate); if (init) { // return speexDecoder.init(mode, sampleRate, channels, enhanced); return true; } else { return true; } } protected static int readInt(final byte[] data, final int offset) { /* * no 0xff on the last one to keep the sign */ return (data[offset] & 0xff) | ((data[offset + 1] & 0xff) << 8) | ((data[offset + 2] & 0xff) << 16) | (data[offset + 3] << 24); } protected static long readLong(final byte[] data, final int offset) { /* * no 0xff on the last one to keep the sign */ return (data[offset] & 0xff) | ((data[offset + 1] & 0xff) << 8) | ((data[offset + 2] & 0xff) << 16) | ((data[offset + 3] & 0xff) << 24) | ((data[offset + 4] & 0xff) << 32) | ((data[offset + 5] & 0xff) << 40) | ((data[offset + 6] & 0xff) << 48) | (data[offset + 7] << 56); } protected static int readShort(final byte[] data, final int offset) { /* * no 0xff on the last one to keep the sign */ return (data[offset] & 0xff) | (data[offset + 1] << 8); } }