/* * Based on TTA1-C++ library functions * Copyright (c) 2011 Aleksander Djuric. All rights reserved. * Distributed under the GNU Lesser General Public License (LGPL). * The complete text of the license can be found in the COPYING * file included in the distribution. */ package com.tulskiy.tta; import java.io.FileInputStream; import java.io.IOException; import java.util.Arrays; import static com.tulskiy.tta.Constants.*; import static com.tulskiy.tta.Filter.*; import static com.tulskiy.tta.Macros.*; import static com.tulskiy.tta.TTACodecStatus.*; /** * Author: Denis Tulskiy * Date: 5/30/11 */ public class TTA_Decoder { public static final int MAX_DEPTH = 3; public static final int MAX_BPS = (MAX_DEPTH * 8); public static final int MIN_BPS = 16; public static final int MAX_NCH = 6; // TTA audio format public static final int TTA_FORMAT_SIMPLE = 1; public static final int TTA_FORMAT_ENCRYPTED = 2; public static final int[] flt_set = {10, 9, 10}; boolean seek_allowed; // seek table flag TTA_fifo fifo; TTA_codec[] decoder; // decoder (1 per channel) byte[] data; // decoder initialization data boolean password_set; // password protection flag long[] seek_table; // the playing position table int format; // tta data format int bitrate; // bitrate (kbps) long offset; // data start position (header size, bytes) long frames; // total count of frames int depth; // bytes per sample long flen_std; // default frame length in samples long flen_last; // last frame length in samples long flen; // current frame length in samples int fnum; // currently playing frame index long fpos; // the current position in frame int discard_bytes; private int smp_size; public TTA_Decoder(FileInputStream inputStream) { fifo = new TTA_fifo(); fifo.io = inputStream; data = new byte[8]; } public TTA_info init_get_info(long pos) { TTA_info info = new TTA_info(); // set start position if required if (pos > 0) { fifo.seek(pos); } fifo.reader_start(); pos += read_tta_header(info); // check for supported formats if (info.format > 2 || info.bps < MIN_BPS || info.bps > MAX_BPS || info.nch > MAX_NCH) throw new tta_exception(TTA_FORMAT_ERROR); // check for required data is present if (info.format == TTA_FORMAT_ENCRYPTED) { if (!password_set) throw new tta_exception(TTA_PASSWORD_ERROR); } offset = pos; // size of headers format = info.format; depth = (info.bps + 7) / 8; flen_std = MUL_FRAME_TIME(info.sps); flen_last = info.samples % flen_std; frames = info.samples / flen_std + (flen_last > 0 ? 1 : 0); smp_size = depth * info.nch; if (flen_last == 0) flen_last = flen_std; bitrate = info.bitrate; // allocate memory for seek table data seek_table = new long[(int) frames]; seek_allowed = read_seek_table(); decoder = new TTA_codec[info.nch]; for (int i = 0; i < decoder.length; i++) { decoder[i] = new TTA_codec(); } frame_init(0, false); return info; } public int read_tta_header(TTA_info info) { int size = skip_id3v2(); fifo.reader_reset(); byte[] header = new byte[4]; fifo.read(header, header.length); if (!"TTA1".equals(new String(header))) throw new tta_exception(TTA_FORMAT_ERROR); info.format = fifo.read_uint16(); info.nch = fifo.read_uint16(); info.bps = fifo.read_uint16(); info.sps = (int) fifo.read_uint32(); info.samples = (int) fifo.read_uint32(); if (!fifo.read_crc32()) throw new tta_exception(TTA_FILE_ERROR); size += 22; // sizeof TTA header try { int datasize = (int) (fifo.io.available() - size); int origsize = info.samples * info.bps / 8 * info.nch; double compress = (double) datasize / origsize; info.bitrate = (int) (compress * info.sps * info.nch * info.bps / 1000); } catch (IOException e) { e.printStackTrace(); } return size; } // read_tta_header int skip_id3v2() { int size = 0; fifo.reader_reset(); // id3v2 header must be at start byte[] header = new byte[3]; fifo.read(header, 3); if (!"ID3".equals(new String(header))) { fifo.pos = 0; return 0; } fifo.pos += 2; // skip version bytes if ((fifo.read_byte() & 0x10) != 0) size += 10; size += (fifo.read_byte() & 0x7f); size = (size << 7) | (fifo.read_byte() & 0x7f); size = (size << 7) | (fifo.read_byte() & 0x7f); size = (size << 7) | (fifo.read_byte() & 0x7f); fifo.reader_skip_bytes(size); return (size + 10); } // skip_id3v2 boolean read_seek_table() { long tmp; int i; if (seek_table == null) return false; fifo.reader_reset(); tmp = offset + (frames + 1) * 4; for (i = 0; i < frames; i++) { seek_table[i] = tmp; tmp += fifo.read_uint32(); } return fifo.read_crc32(); } // read_seek_table void frame_init(int frame, boolean seek_needed) { int shift = flt_set[depth - 1]; if (frame >= frames) return; fnum = frame; if (seek_needed && seek_allowed) { long pos = seek_table[fnum]; if (pos >= 0) { fifo.seek(pos); } fifo.reader_start(); } if (fnum == frames - 1) flen = flen_last; else flen = flen_std; // init entropy decoder for (TTA_codec dec : decoder) { filter_init(dec.fst, data, shift); rice_init(dec.rice, 10, 10); dec.prev = 0; } fpos = 0; fifo.reader_reset(); } // frame_init void filter_init(TTA_fltst fs, byte[] data, int shift) { fs.error = 0; fs.shift = shift; fs.round = 1 << (shift - 1); Arrays.fill(fs.dl, 0); Arrays.fill(fs.dx, 0); fs.qm[0] = data[0]; fs.qm[1] = data[1]; fs.qm[2] = data[2]; fs.qm[3] = data[3]; fs.qm[4] = data[4]; fs.qm[5] = data[5]; fs.qm[6] = data[6]; fs.qm[7] = data[7]; } // filter_init void rice_init(TTA_adapt rice, int k0, int k1) { rice.k0 = k0; rice.k1 = k1; rice.sum0 = shift_16[k0]; rice.sum1 = shift_16[k1]; } // rice_init public int process_stream(byte[] output) { int current_decoder = 0; TTA_codec dec = decoder[current_decoder]; int ptr = 0; int[] cache = new int[MAX_NCH]; int cp = 0; int end, smp; int value; int ret = 0; while (fpos < flen && ptr < output.length) { value = fifo.get_value(dec.rice); // decompress stage 1: adaptive hybrid filter value = hybrid_filter_dec(dec.fst, value); // System.out.printf("%d\t%d\t%d\t%d\n", value, dec.fst.error, dec.fst.round, dec.fst.shift); // decompress stage 2: fixed order 1 prediction value += (dec.prev * ((1 << 5) - 1)) >> 5; dec.prev = value; if (current_decoder < decoder.length - 1) { cache[cp++] = value; current_decoder++; if (current_decoder < decoder.length) dec = decoder[current_decoder]; else dec = null; } else { cache[cp] = value; if (decoder.length == 1) { WRITE_BUFFER(cache[cp], output, ptr, depth); ptr += depth; } else { end = cp; smp = cp - 1; cache[cp] += cache[smp] / 2; while (smp > 0) { cache[smp] = cache[cp--] - cache[smp]; smp--; } cache[smp] = cache[cp] - cache[smp]; while (smp <= end) { WRITE_BUFFER(cache[smp], output, ptr, depth); ptr += depth; smp++; } } cp = 0; fpos++; ret++; dec = decoder[0]; current_decoder = 0; } if (fpos == flen) { // check frame crc boolean crc_flag = fifo.read_crc32(); if (!crc_flag) { Arrays.fill(output, (byte) 0); if (!seek_allowed) break; } fnum++; // update dynamic info bitrate = (fifo.count << 3) / 1070; // if (tta_callback) // tta_callback(rate, fnum, frames); if (fnum == frames) break; frame_init(fnum, crc_flag); } } if (ret == 0) { return -1; } ret *= smp_size; if (discard_bytes > 0) { ret -= discard_bytes; if (ret < 0) { discard_bytes = Math.abs(ret); return 0; } System.arraycopy(output, discard_bytes, output, 0, ret); discard_bytes = 0; } return ret; } // process_stream public int get_current_bitrate() { return bitrate; } public void set_position(int sample) { int frame = (int) (sample / flen_std); if (!seek_allowed || frame >= frames) throw new tta_exception(TTA_SEEK_ERROR); discard_bytes = (int) ((sample - frame * flen_std) * smp_size); frame_init(frame, true); } // set_position public void close() throws IOException { fifo.io.close(); } }