/* * Copyright (c) 2008, 2009, 2010 Denis Tulskiy * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * version 3 along with this work. If not, see <http://www.gnu.org/licenses/>. */ package com.tulskiy.musique.audio.formats.flac.oggflac; import com.jcraft.jogg.Packet; import com.jcraft.jogg.Page; import com.jcraft.jogg.StreamState; import com.jcraft.jogg.SyncState; import org.kc7bfi.jflac.FLACDecoder; import org.kc7bfi.jflac.metadata.Metadata; import org.kc7bfi.jflac.metadata.StreamInfo; import org.kc7bfi.jflac.util.RingBuffer; import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; import java.util.Arrays; /** * @Author: Denis Tulskiy * @Date: 22.07.2009 */ public class OggFlacDecoder { private static final int CHUNKSIZE = 8500; private static final byte SUPPORTED_MAPPING = 1; public static final int OV_FALSE = -1; public static final int OV_EOF = -2; public static final int OV_HOLE = -3; public static final int OV_EREAD = -128; public static final int OV_EFAULT = -129; public static final int OV_EIMPL = -130; public static final int OV_EINVAL = -131; public static final int OV_EBADHEADER = -133; public static final int OV_EVERSION = -134; public static final int OV_ENOTAUDIO = -135; public static final int OV_EBADPACKET = -136; public static final int OV_EBADLINK = -137; public static final int OV_ENOSEEK = -138; private RandomAccessFile input; private StreamState os = new StreamState(); private SyncState oy = new SyncState(); private long offset; private Page page = new Page(); private Packet packet = new Packet(); private OggInputStream inputStream; private StreamInfo streamInfo; private Metadata[] metadata; private FLACDecoder decoder; private int serialno = -1; private int links; private long[] offsets; byte majorVersion; byte minorVersion; int headerPackets; int pcm_offset; public int open(RandomAccessFile input) { this.input = input; oy.clear(); oy.init(); inputStream = new OggInputStream(); decoder = new FLACDecoder(inputStream); long end = 0; try { end = input.length(); } catch (IOException e) { e.printStackTrace(); } getNextPage(1); if (bisect_forward_serialno(0, end, end + 1, serialno, 0) < 0) { clear(); return OV_EREAD; } readMetadata(); return 0; } private void clear() { os.clear(); oy.clear(); } @SuppressWarnings({"ResultOfMethodCallIgnored"}) public int readMetadata() { seekHelper(0); getNextPacket(); DataInputStream dis = new DataInputStream(inputStream); byte[] firstPacket = new byte[]{0x7F, 'F', 'L', 'A', 'C'}; try { byte[] packetHeader = new byte[5]; dis.read(packetHeader); if (!Arrays.equals(packetHeader, firstPacket)) return OV_EBADPACKET; majorVersion = dis.readByte(); if (majorVersion > SUPPORTED_MAPPING) return OV_EVERSION; minorVersion = dis.readByte(); headerPackets = dis.readShort(); streamInfo = decoder.readStreamInfo(); //read other headers metadata = new Metadata[headerPackets]; for (int i = 0; i < headerPackets; i++) { getNextPacket(); metadata[i] = decoder.readNextMetadata(); } } catch (IOException e) { e.printStackTrace(); } return 0; } public int getData() { int index = oy.buffer(CHUNKSIZE); byte[] buffer = oy.data; int bytes; try { bytes = input.read(buffer, index, CHUNKSIZE); } catch (Exception e) { return OV_EREAD; } oy.wrote(bytes); if (bytes == -1) { bytes = 0; } return bytes; } public void seekHelper(long offset) { try { input.seek(offset); this.offset = offset; oy.reset(); } catch (IOException e) { e.printStackTrace(); } } public int getNextPage(long boundary) { if (boundary > 0) boundary += offset; while (true) { int more; if (boundary > 0 && offset >= boundary) return OV_FALSE; more = oy.pageseek(page); if (more < 0) { offset -= more; } else { if (more == 0) { if (boundary == 0) return OV_FALSE; int ret = getData(); if (ret == 0) return OV_EOF; if (ret < 0) return OV_EREAD; } else { int ret = (int) offset; //!!! offset += more; if (serialno == -1) { serialno = page.serialno(); os.init(serialno); } // System.out.println(page.granulepos() + " " + page.pageno()); return ret; } } } } public int getNextPacket() { while (os.packetout(packet) == 0) { int ret = getNextPage(1); if (ret < 0) return ret; os.pagein(page); } inputStream.add(packet.packet_base, packet.packet, packet.bytes); return 0; } public InputStream getInputStream() { return inputStream; } public StreamInfo getStreamInfo() { return streamInfo; } public FLACDecoder getDecoder() { return decoder; } int bisect_forward_serialno(long begin, long searched, long end, int currentno, int m) { long endsearched = end; long next = end; int ret; while (searched < endsearched) { long bisect; if (endsearched - searched < CHUNKSIZE) { bisect = searched; } else { bisect = (searched + endsearched) / 2; } seekHelper(bisect); ret = getNextPage(-1); if (ret == OV_EREAD) return OV_EREAD; if (ret < 0 || page.serialno() != currentno) { endsearched = bisect; if (ret >= 0) next = ret; } else { searched = ret + page.header_len + page.body_len; } } seekHelper(next); ret = getNextPage(-1); if (ret == OV_EREAD) return OV_EREAD; if (searched >= end || ret == -1) { links = m + 1; offsets = new long[m + 2]; offsets[m + 1] = searched; } else { ret = bisect_forward_serialno(next, offset, end, page.serialno(), m + 1); if (ret == OV_EREAD) return OV_EREAD; } offsets[m] = begin; return 0; } public void flush() { os.reset(); oy.reset(); inputStream.flush(); } public Metadata[] getMetadata() { return metadata; } class OggInputStream extends InputStream { RingBuffer buf = new RingBuffer(65536); byte[] single = new byte[1]; public void add(byte[] b, int off, int len) { buf.put(b, off, len); } @Override public int read() throws IOException { if (read(single, 0, 1) > 0) return single[0]; return -1; } @Override public int read(byte[] b, int off, int len) throws IOException { while (buf.getAvailable() < len) { int ret = getNextPacket(); if (ret < 0) break; } if (buf.getAvailable() <= 0) return -1; return buf.get(b, off, len); } public void flush() { buf.empty(); } } }