1 /* Copyright (C) <2009> Maik Merten <maikmerten@googlemail.com>
2 * Copyright (C) <2004> Wim Taymans <wim@fluendo.com> (TheoraDec.java parts)
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 * Boston, MA 02111-1307, USA.
19 package com.fluendo.examples;
21 import com.fluendo.jheora.Comment;
22 import com.fluendo.jheora.Info;
23 import com.fluendo.jheora.State;
24 import com.fluendo.jheora.YUVBuffer;
25 import com.fluendo.utils.Debug;
26 import com.fluendo.utils.MemUtils;
27 import com.jcraft.jogg.Packet;
28 import com.jcraft.jogg.Page;
29 import com.jcraft.jogg.StreamState;
30 import com.jcraft.jogg.SyncState;
32 import java.io.FileInputStream;
33 import java.io.FileNotFoundException;
34 import java.io.FileOutputStream;
35 import java.io.IOException;
36 import java.io.InputStream;
37 import java.io.OutputStream;
38 import java.util.Date;
39 import java.util.HashMap;
40 import java.util.HashSet;
41 import java.util.LinkedList;
42 import java.util.List;
48 * This class borrows code from TheoraDec.java
50 public class DumpVideo {
52 public static final Integer OK = new Integer(0);
53 public static final Integer ERROR = new Integer(-5);
54 private static final byte[] signature = {-128, 0x74, 0x68, 0x65, 0x6f, 0x72, 0x61};
56 private class TheoraDecoderWrapper {
61 private YUVBuffer yuv;
64 private boolean needKeyframe;
66 public TheoraDecoderWrapper() {
71 yuv = new YUVBuffer();
74 public int takeHeader(Packet op) {
77 ret = ti.decodeHeader(tc, op);
78 header = op.packet_base[op.packet];
85 public boolean isHeader(Packet op) {
86 return (op.packet_base[op.packet] & 0x80) == 0x80;
89 public boolean isKeyFrame(Packet op) {
90 return ts.isKeyframe(op);
93 public Object decode(Packet op) {
99 //System.out.println ("decoding header");
100 if (takeHeader(op) < 0) {
101 // error case; not a theora header
102 Debug.log(Debug.ERROR, "does not contain Theora video data.");
108 Debug.log(Debug.INFO, "theora dimension: " + ti.width + "x" + ti.height);
109 if (ti.aspect_denominator == 0) {
110 ti.aspect_numerator = 1;
111 ti.aspect_denominator = 1;
113 Debug.log(Debug.INFO, "theora offset: " + ti.offset_x + "," + ti.offset_y);
114 Debug.log(Debug.INFO, "theora frame: " + ti.frame_width + "," + ti.frame_height);
115 Debug.log(Debug.INFO, "theora aspect: " + ti.aspect_numerator + "/" + ti.aspect_denominator);
116 Debug.log(Debug.INFO, "theora framerate: " + ti.fps_numerator + "/" + ti.fps_denominator);
123 if ((op.packet_base[op.packet] & 0x80) == 0x80) {
124 Debug.log(Debug.INFO, "ignoring header");
127 if (needKeyframe && ts.isKeyframe(op)) {
128 needKeyframe = false;
134 if (ts.decodePacketin(op) != 0) {
135 Debug.log(Debug.ERROR, "Bad Theora packet. Most likely not fatal, hoping for better luck next packet.");
137 if (ts.decodeYUVout(yuv) != 0) {
138 Debug.log(Debug.ERROR, "Error getting the picture.");
142 System.out.println("Decoded frame: " + ++frame);
144 return yuv.getObject(ti.offset_x, ti.offset_y, ti.frame_width, ti.frame_height);
145 } catch (Exception e) {
159 private class YUVWriter {
161 private OutputStream os;
162 private boolean wroteHeader = false;
163 private byte[] ybytes;
164 private byte[] uvbytes;
167 public YUVWriter(File outfile, boolean raw) {
170 os = new FileOutputStream(outfile);
171 } catch (FileNotFoundException ex) {
172 ex.printStackTrace();
176 public void writeYUVFrame(Info ti, YUVBuffer yuv) {
180 String headerstring = "YUV4MPEG2 W" + ti.width + " H" + ti.height + " F" + ti.fps_numerator + ":" + ti.fps_denominator + " Ip A" + ti.aspect_numerator + ":" + ti.aspect_denominator + "\n";
181 os.write(headerstring.getBytes());
184 os.write("FRAME\n".getBytes());
187 if (ybytes == null || ybytes.length != yuv.y_width * yuv.y_height) {
188 ybytes = new byte[yuv.y_width * yuv.y_height];
192 for (int i = 0; i < yuv.y_height; ++i) {
193 int start = yuv.y_offset + (i * yuv.y_stride);
194 for (int j = start; j < start + yuv.y_width; ++j) {
195 ybytes[offset++] = (byte) yuv.data[j];
201 if (uvbytes == null || uvbytes.length != yuv.uv_width * yuv.uv_height) {
202 uvbytes = new byte[yuv.uv_width * yuv.uv_height];
206 for (int i = 0; i < yuv.uv_height; ++i) {
207 int start = yuv.u_offset + (i * yuv.uv_stride);
208 for (int j = start; j < start + yuv.uv_width; ++j) {
209 uvbytes[offset++] = (byte) yuv.data[j];
215 for (int i = 0; i < yuv.uv_height; ++i) {
216 int start = yuv.v_offset + (i * yuv.uv_stride);
217 for (int j = start; j < start + yuv.uv_width; ++j) {
218 uvbytes[offset++] = (byte) yuv.data[j];
223 } catch (IOException ex) {
224 ex.printStackTrace();
229 public boolean isTheora(Packet op) {
230 return typeFind(op.packet_base, op.packet, op.bytes) > 0;
233 public int typeFind(byte[] data, int offset, int length) {
234 if (MemUtils.startsWith(data, offset, length, signature)) {
240 public void dumpVideo(File videofile, List outfiles, boolean raw) throws IOException {
241 InputStream is = new FileInputStream(videofile);
243 boolean onlytime = outfiles.size() == 0;
245 SyncState oy = new SyncState();
246 Page og = new Page();
247 Packet op = new Packet();
248 byte[] buf = new byte[512];
250 Map streamstates = new HashMap();
251 Map theoradecoders = new HashMap();
252 Map yuvwriters = new HashMap();
253 Set hasdecoder = new HashSet();
255 int read = is.read(buf);
257 int offset = oy.buffer(read);
258 java.lang.System.arraycopy(buf, 0, oy.data, offset, read);
261 while (oy.pageout(og) == 1) {
263 Integer serialno = new Integer(og.serialno());
265 StreamState state = (StreamState) streamstates.get(serialno);
267 state = new StreamState();
268 state.init(serialno.intValue());
269 streamstates.put(serialno, state);
270 Debug.info("created StreamState for stream no. " + og.serialno());
275 while (state.packetout(op) == 1) {
277 if (!(hasdecoder.contains(serialno)) && isTheora(op)) {
279 TheoraDecoderWrapper theoradec = (TheoraDecoderWrapper) theoradecoders.get(serialno);
280 if (theoradec == null) {
281 theoradec = new TheoraDecoderWrapper();
282 theoradecoders.put(serialno, theoradec);
283 hasdecoder.add(serialno);
286 Debug.info("is Theora: " + serialno);
289 TheoraDecoderWrapper theoradec = (TheoraDecoderWrapper) theoradecoders.get(serialno);
291 if (theoradec != null) {
292 Object result = theoradec.decode(op);
293 if (!onlytime && result instanceof YUVBuffer) {
295 YUVWriter yuvwriter = (YUVWriter) yuvwriters.get(serialno);
296 if (yuvwriter == null && !outfiles.isEmpty()) {
297 yuvwriter = new YUVWriter((File) outfiles.get(0), raw);
298 yuvwriters.put(serialno, yuvwriter);
302 if (yuvwriter != null) {
303 YUVBuffer yuvbuf = (YUVBuffer) result;
304 yuvwriter.writeYUVFrame(theoradec.ti, yuvbuf);
317 public static void main(String[] args) throws IOException {
319 if (args.length < 1) {
320 System.err.println("usage: DumpVideo <videofile> [<outfile_1> ... <outfile_n>} [--raw>]");
325 File infile = new File(args[0]);
327 List outfiles = new LinkedList();
328 for (int i = 1; i < args.length; ++i) {
329 if(args[i].equals("--raw")) {
333 outfiles.add(new File(args[i]));
336 if(outfiles.size() == 0) {
337 System.out.println("no output files given, will only time decode");
340 DumpVideo dv = new DumpVideo();
342 Date start = new Date();
343 dv.dumpVideo(infile, outfiles, raw);
344 Date end = new Date();
346 System.out.println("time: " + (end.getTime() - start.getTime()) + " milliseconds");