001package net.bramp.ffmpeg.nut; 002 003import static com.google.common.base.Preconditions.checkNotNull; 004import static net.bramp.ffmpeg.nut.Packet.Startcode; 005 006import com.google.common.base.Charsets; 007import java.io.IOException; 008import java.io.InputStream; 009import java.util.ArrayList; 010import java.util.Arrays; 011import java.util.List; 012 013/** 014 * Demuxer for the FFmpeg Nut file format. 015 * 016 * <p>Lots of things not implemented, startcode searching, crc checks, etc 017 * 018 * @see <a 019 * href="https://www.ffmpeg.org/~michael/nut.txt">https://www.ffmpeg.org/~michael/nut.txt</a> 020 * @see <a 021 * href="https://github.com/FFmpeg/FFmpeg/blob/master/libavformat/nutdec.c">https://github.com/FFmpeg/FFmpeg/blob/master/libavformat/nutdec.c</a> 022 */ 023public class NutReader { 024 025 // HEADER is the string "nut/multimedia container\0" 026 static final byte[] HEADER = { 027 0x6e, 0x75, 0x74, 0x2f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x20, 0x63, 028 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x00 029 }; 030 031 public MainHeaderPacket header; 032 public final List<Stream> streams = new ArrayList<>(); 033 034 final NutDataInputStream in; 035 final NutReaderListener listener; 036 037 public NutReader(InputStream in, NutReaderListener listener) { 038 this.in = new NutDataInputStream(in); 039 this.listener = checkNotNull(listener); 040 } 041 042 public static boolean isKnownStartcode(long startcode) { 043 return Startcode.of(startcode) != null; 044 } 045 046 /** 047 * Read the magic at the beginning of the file. 048 * 049 * @throws IOException If a I/O error occurs 050 */ 051 protected void readFileId() throws IOException { 052 byte[] b = new byte[HEADER.length]; 053 in.readFully(b); 054 055 if (!Arrays.equals(b, HEADER)) { 056 throw new IOException( 057 "file_id_string does not match. got: " + new String(b, Charsets.ISO_8859_1)); 058 } 059 } 060 061 /** 062 * Read headers we don't know how to parse yet, returning the next startcode. 063 * 064 * @return The next startcode 065 * @throws IOException If a I/O error occurs 066 */ 067 protected long readReservedHeaders() throws IOException { 068 long startcode = in.readStartCode(); 069 070 while (Startcode.isPossibleStartcode(startcode) && isKnownStartcode(startcode)) { 071 new Packet().read(in, startcode); // Discard unknown packet 072 startcode = in.readStartCode(); 073 } 074 return startcode; 075 } 076 077 /** 078 * Demux the inputstream 079 * 080 * @throws IOException If a I/O error occurs 081 */ 082 public void read() throws IOException { 083 readFileId(); 084 in.resetCRC(); 085 086 long startcode = in.readStartCode(); 087 088 while (true) { 089 // Start parsing main and stream information 090 header = new MainHeaderPacket(); 091 092 if (!Startcode.MAIN.equalsCode(startcode)) { 093 throw new IOException(String.format("expected main header found: 0x%X", startcode)); 094 } 095 096 header.read(in, startcode); 097 startcode = readReservedHeaders(); 098 099 streams.clear(); 100 for (int i = 0; i < header.streamCount; i++) { 101 if (!Startcode.STREAM.equalsCode(startcode)) { 102 throw new IOException(String.format("expected stream header found: 0x%X", startcode)); 103 } 104 105 StreamHeaderPacket streamHeader = new StreamHeaderPacket(); 106 streamHeader.read(in, startcode); 107 108 Stream stream = new Stream(header, streamHeader); 109 streams.add(stream); 110 listener.stream(stream); 111 112 startcode = readReservedHeaders(); 113 } 114 115 while (Startcode.INFO.equalsCode(startcode)) { 116 new Packet().read(in, startcode); // Discard for the moment 117 startcode = readReservedHeaders(); 118 } 119 120 if (Startcode.INDEX.equalsCode(startcode)) { 121 new Packet().read(in, startcode); // Discard for the moment 122 startcode = in.readStartCode(); 123 } 124 125 // Now main frame parsing loop 126 while (!Startcode.MAIN.equalsCode(startcode)) { 127 128 if (Startcode.SYNCPOINT.equalsCode(startcode)) { 129 new Packet().read(in, startcode); // Discard for the moment 130 startcode = in.readStartCode(); 131 } 132 133 if (Startcode.isPossibleStartcode(startcode)) { 134 throw new IOException("expected framecode, found " + Startcode.toString(startcode)); 135 } 136 137 Frame f = new Frame(); 138 f.read(this, in, (int) startcode); 139 140 listener.frame(f); 141 142 try { 143 startcode = readReservedHeaders(); 144 } catch (java.io.EOFException e) { 145 return; 146 } 147 } 148 } 149 } 150}