001package net.bramp.ffmpeg.nut; 002 003import static com.google.common.base.Preconditions.checkNotNull; 004import static net.bramp.ffmpeg.nut.Packet.Startcode; 005 006import java.io.IOException; 007import java.io.InputStream; 008import java.nio.charset.StandardCharsets; 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 /** Constructs a new NutReader for the given input stream and listener. */ 038 public NutReader(InputStream in, NutReaderListener listener) { 039 this.in = new NutDataInputStream(in); 040 this.listener = checkNotNull(listener); 041 } 042 043 /** Returns whether the given startcode is a known NUT packet startcode. */ 044 public static boolean isKnownStartcode(long startcode) { 045 return Startcode.of(startcode) != null; 046 } 047 048 /** 049 * Read the magic at the beginning of the file. 050 * 051 * @throws IOException If a I/O error occurs 052 */ 053 protected void readFileId() throws IOException { 054 byte[] b = new byte[HEADER.length]; 055 in.readFully(b); 056 057 if (!Arrays.equals(b, HEADER)) { 058 throw new IOException( 059 "file_id_string does not match. got: " + new String(b, StandardCharsets.ISO_8859_1)); 060 } 061 } 062 063 /** 064 * Read headers we don't know how to parse yet, returning the next startcode. 065 * 066 * @return The next startcode 067 * @throws IOException If a I/O error occurs 068 */ 069 protected long readReservedHeaders() throws IOException { 070 long startcode = in.readStartCode(); 071 072 while (Startcode.isPossibleStartcode(startcode) && isKnownStartcode(startcode)) { 073 new Packet().read(in, startcode); // Discard unknown packet 074 startcode = in.readStartCode(); 075 } 076 return startcode; 077 } 078 079 /** 080 * Demux the inputstream. 081 * 082 * @throws IOException If a I/O error occurs 083 */ 084 public void read() throws IOException { 085 readFileId(); 086 in.resetCRC(); 087 088 long startcode = in.readStartCode(); 089 090 while (true) { 091 // Start parsing main and stream information 092 header = new MainHeaderPacket(); 093 094 if (!Startcode.MAIN.equalsCode(startcode)) { 095 throw new IOException(String.format("expected main header found: 0x%X", startcode)); 096 } 097 098 header.read(in, startcode); 099 startcode = readReservedHeaders(); 100 101 streams.clear(); 102 for (int i = 0; i < header.streamCount; i++) { 103 if (!Startcode.STREAM.equalsCode(startcode)) { 104 throw new IOException(String.format("expected stream header found: 0x%X", startcode)); 105 } 106 107 StreamHeaderPacket streamHeader = new StreamHeaderPacket(); 108 streamHeader.read(in, startcode); 109 110 Stream stream = new Stream(header, streamHeader); 111 streams.add(stream); 112 listener.stream(stream); 113 114 startcode = readReservedHeaders(); 115 } 116 117 while (Startcode.INFO.equalsCode(startcode)) { 118 new Packet().read(in, startcode); // Discard for the moment 119 startcode = readReservedHeaders(); 120 } 121 122 if (Startcode.INDEX.equalsCode(startcode)) { 123 new Packet().read(in, startcode); // Discard for the moment 124 startcode = in.readStartCode(); 125 } 126 127 // Now main frame parsing loop 128 while (!Startcode.MAIN.equalsCode(startcode)) { 129 130 if (Startcode.SYNCPOINT.equalsCode(startcode)) { 131 new Packet().read(in, startcode); // Discard for the moment 132 startcode = in.readStartCode(); 133 } 134 135 if (Startcode.isPossibleStartcode(startcode)) { 136 throw new IOException("expected framecode, found " + Startcode.toString(startcode)); 137 } 138 139 Frame f = new Frame(); 140 f.read(this, in, (int) startcode); 141 142 listener.frame(f); 143 144 try { 145 startcode = readReservedHeaders(); 146 } catch (java.io.EOFException e) { 147 return; 148 } 149 } 150 } 151 } 152}