/** * Copies the content of the most recently received packet into <tt>data</tt>. * * @param buffer an optional <tt>Buffer</tt> instance associated with the specified <tt>data</tt>, * <tt>offset</tt> and <tt>length</tt> and provided to the method in case the implementation * would like to provide additional <tt>Buffer</tt> properties such as <tt>flags</tt> * @param data the <tt>byte[]</tt> that we'd like to copy the content of the packet to. * @param offset the position where we are supposed to start writing in <tt>data</tt>. * @param length the number of <tt>byte</tt>s available for writing in <tt>data</tt>. * @return the number of bytes read * @throws IOException if <tt>length</tt> is less than the size of the packet. */ protected int read(Buffer buffer, byte[] data, int offset, int length) throws IOException { if (data == null) throw new NullPointerException("data"); if (ioError) return -1; RawPacket pkt; synchronized (pktSyncRoot) { pkt = this.pkt; this.pkt = null; } int pktLength; if (pkt == null) { pktLength = 0; } else { // By default, pkt will be returned to the pool after it was read. boolean poolPkt = true; try { pktLength = pkt.getLength(); if (length < pktLength) { /* * If pkt is still the latest RawPacket made available to * reading, reinstate it for the next invocation of read; * otherwise, return it to the pool. */ poolPkt = false; throw new IOException("Input buffer not big enough for " + pktLength); } else { byte[] pktBuffer = pkt.getBuffer(); if (pktBuffer == null) { throw new NullPointerException( "pkt.buffer null, pkt.length " + pktLength + ", pkt.offset " + pkt.getOffset()); } else { System.arraycopy(pkt.getBuffer(), pkt.getOffset(), data, offset, pktLength); if (buffer != null) buffer.setFlags(pkt.getFlags()); } } } finally { if (!poolPkt) { synchronized (pktSyncRoot) { if (this.pkt == null) this.pkt = pkt; else poolPkt = true; } } if (poolPkt) { // Return pkt to the pool because it was successfully read. poolRawPacket(pkt); } } } return pktLength; }
public VideoTrack(PullSourceStream stream) throws ResourceUnavailableException { super(); this.stream = stream; // set format // read first frame to determine format final Buffer buffer = new Buffer(); readFrame(buffer); if (buffer.isDiscard() || buffer.isEOM()) throw new ResourceUnavailableException("Unable to read first frame"); // TODO: catch runtime exception too? // parse jpeg final java.awt.Image image; try { image = ImageIO.read( new ByteArrayInputStream( (byte[]) buffer.getData(), buffer.getOffset(), buffer.getLength())); } catch (IOException e) { logger.log(Level.WARNING, "" + e, e); throw new ResourceUnavailableException("Error reading image: " + e); } if (image == null) { logger.log(Level.WARNING, "Failed to read image (ImageIO.read returned null)."); throw new ResourceUnavailableException(); } if (frameContentType.equals("image/jpeg")) format = new JPEGFormat( new Dimension(image.getWidth(null), image.getHeight(null)), Format.NOT_SPECIFIED, Format.byteArray, -1.f, Format.NOT_SPECIFIED, Format.NOT_SPECIFIED); else if (frameContentType.equals("image/gif")) format = new GIFFormat( new Dimension(image.getWidth(null), image.getHeight(null)), Format.NOT_SPECIFIED, Format.byteArray, -1.f); else if (frameContentType.equals("image/png")) format = new PNGFormat( new Dimension(image.getWidth(null), image.getHeight(null)), Format.NOT_SPECIFIED, Format.byteArray, -1.f); else throw new ResourceUnavailableException( "Unsupported frame content type: " + frameContentType); // TODO: this discards first image. save and return first time // readFrame is called. }
@Override public void readFrame(Buffer buffer) { // example data: // --ssBoundary8345 // Content-Type: image/jpeg // Content-Length: 114587 try { String line; // eat leading blank lines while (true) { line = readLine(MAX_LINE_LENGTH); if (line == null) { buffer.setEOM(true); buffer.setLength(0); return; } if (!line.trim().equals("")) break; // end of header } if (boundary == null) { boundary = line.trim(); // TODO: we should be able to get // this from the content type, but // the content type has this // stripped out. So we'll just take // the first nonblank line to be the // boundary. // System.out.println("boundary: " + boundary); } else { if (!line.trim().equals(boundary)) { // throw new IOException("Expected boundary: " + // toPrintable(line)); // TODO: why do we seem to get these when playing back // mmr files recorded using FmjTranscode? logger.warning("Expected boundary (frame " + framesRead + "): " + toPrintable(line)); // handle streams that are truncated in the middle of a // frame: final int eatResult = eatUntil(boundary); // TODO: no // need to // store the // data logger.info( "Ignored bytes (eom after=" + (eatResult < 0) + "): " + (eatResult < 0 ? (-1 * eatResult - 1) : eatResult)); if (eatResult < 0) { buffer.setEOM(true); buffer.setLength(0); return; } // now read boundary line = readLine(MAX_LINE_LENGTH); if (!line.trim().equals(boundary)) { throw new RuntimeException("No boundary found after eatUntil(boundary)"); // should // never // happen } } } final Properties properties = new Properties(); while (true) { line = readLine(MAX_LINE_LENGTH); if (line == null) { buffer.setEOM(true); buffer.setLength(0); return; } if (line.trim().equals("")) break; // end of header if (!parseProperty(line, properties)) throw new IOException("Expected property: " + toPrintable(line)); } final String contentType = properties.getProperty("Content-Type".toUpperCase()); if (contentType == null) { logger.warning("Header properties: " + properties); throw new IOException("Expected Content-Type in header"); } // check supported content types: if (!isSupportedFrameContentType(contentType)) { throw new IOException("Unsupported Content-Type: " + contentType); } if (frameContentType == null) { frameContentType = contentType; } else { if (!contentType.equals(frameContentType)) throw new IOException( "Content type changed during stream from " + frameContentType + " to " + contentType); } // TODO: check that size doesn't change throughout final byte[] data; final String contentLenStr = properties.getProperty("Content-Length".toUpperCase()); if (contentLenStr != null) { // if we know the content length, use it final int contentLen; try { contentLen = Integer.parseInt(contentLenStr); } catch (NumberFormatException e) { throw new IOException("Invalid content length: " + contentLenStr); } // now, read the content-length bytes data = readFully(contentLen); // TODO: don't realloc each // time } else { // if we don't know the content length, just read until we // find the boundary. // Some IP cameras don't specify it, like // http://webcam-1.duesseldorf.it-on.net/cgi-bin/nph-update.cgi data = readUntil(boundary); } // ext final String timestampStr = properties.getProperty(TIMESTAMP_KEY.toUpperCase()); if (timestampStr != null) { try { final long timestamp = Long.parseLong(timestampStr); buffer.setTimeStamp(timestamp); } catch (NumberFormatException e) { logger.log(Level.WARNING, "" + e, e); } } if (data == null) { buffer.setEOM(true); buffer.setLength(0); return; } buffer.setData(data); buffer.setOffset(0); buffer.setLength(data.length); ++framesRead; } catch (IOException e) { throw new RuntimeException(e); } }
@Override public void read(Buffer buffer) throws IOException { pbs.read(buffer); // Remap the time stamps so it won't wrap around // while changing to a new file. if (buffer.getTimeStamp() != Buffer.TIME_UNKNOWN) { long diff = buffer.getTimeStamp() - lastTS; lastTS = buffer.getTimeStamp(); if (diff > 0) timeStamp += diff; buffer.setTimeStamp(timeStamp); } // If this track is to be used as the master time base, // we'll need to compute the master time based on this track. if (useAsMaster) { if (buffer.getFormat() instanceof AudioFormat) { AudioFormat af = (AudioFormat) buffer.getFormat(); masterAudioLen += buffer.getLength(); long t = af.computeDuration(masterAudioLen); if (t > 0) { masterTime = t; } else { masterTime = buffer.getTimeStamp(); } } else { masterTime = buffer.getTimeStamp(); } } if (buffer.isEOM()) { tInfo.done = true; if (!ds.handleEOM(tInfo)) { // This is not the last processor to be done. // We'll need to un-set the EOM flag. buffer.setEOM(false); buffer.setDiscard(true); } } }