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 @Override public int read() throws IOException { // TODO: how do we detect IOException? fillBuffer(); if (buffer.getLength() == 0 && buffer.isEOM()) // TODO: will always be // EOM if length is 0 return -1; final byte[] data = (byte[]) buffer.getData(); final int result = data[buffer.getOffset()] & 0xff; buffer.setOffset(buffer.getOffset() + 1); buffer.setLength(buffer.getLength() - 1); return result; }
// @Override @Override public int read(byte[] b, int off, int len) throws IOException { // TODO: how do we detect IOException? fillBuffer(); if (buffer.getLength() == 0 && buffer.isEOM()) // TODO: will always be // EOM if length is 0 return -1; final byte[] data = (byte[]) buffer.getData(); int lengthToCopy = buffer.getLength() < len ? buffer.getLength() : len; System.arraycopy(data, buffer.getOffset(), b, off, lengthToCopy); buffer.setOffset(buffer.getOffset() + lengthToCopy); buffer.setLength(buffer.getLength() - lengthToCopy); return lengthToCopy; }
private void fillBuffer() { if (buffer == null) { buffer = new Buffer(); buffer.setFormat(track.getFormat()); } do { if (buffer.isEOM()) return; if (buffer.getLength() > 0) return; // still have data in buffer // TODO: any fields to set? track.readFrame(buffer); logger.fine("Read buffer from track: " + buffer.getLength()); } while (buffer.isDiscard()); }
@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); } } }
/** Write the buffer to the SourceDataLine. */ public int process(Buffer buffer) { // if we need to convert the format, do so using the codec. if (codec != null) { final int codecResult = codec.process(buffer, codecBuffer); if (codecResult == BUFFER_PROCESSED_FAILED) return BUFFER_PROCESSED_FAILED; if (codecResult == OUTPUT_BUFFER_NOT_FILLED) return BUFFER_PROCESSED_OK; buffer = codecBuffer; } int length = buffer.getLength(); int offset = buffer.getOffset(); final Format format = buffer.getFormat(); final Class type = format.getDataType(); if (type != Format.byteArray) { return BUFFER_PROCESSED_FAILED; } final byte[] data = (byte[]) buffer.getData(); final boolean bufferNotConsumed; final int newBufferLength; // only applicable if bufferNotConsumed final int newBufferOffset; // only applicable if bufferNotConsumed if (NON_BLOCKING) { // TODO: handle sourceLine.available(). This code currently causes choppy audio. if (length > sourceLine.available()) { // we should only write sourceLine.available() bytes, then return INPUT_BUFFER_NOT_CONSUMED. length = sourceLine.available(); // don't try to write more than available bufferNotConsumed = true; newBufferLength = buffer.getLength() - length; newBufferOffset = buffer.getOffset() + length; } else { bufferNotConsumed = false; newBufferLength = length; newBufferOffset = offset; } } else { bufferNotConsumed = false; newBufferLength = 0; newBufferOffset = 0; } if (length == 0) { logger.finer("Buffer has zero length, flags = " + buffer.getFlags()); } // make sure all the bytes are written. while (length > 0) { // logger.fine("Available: " + sourceLine.available()); // logger.fine("length: " + length); // logger.fine("sourceLine.getBufferSize(): " + sourceLine.getBufferSize()); final int n = sourceLine.write( data, offset, length); // TODO: this can block for a very long time if it doesn't if (n >= length) break; else if (n == 0) { // TODO: we could choose to handle a write failure this way, // assuming that it is considered legal to call stop while process is being called. // however, that seems like a bad idea in general. // if (!sourceLine.isRunning()) // { // buffer.setLength(offset); // buffer.setOffset(length); // return INPUT_BUFFER_NOT_CONSUMED; // our write was interrupted. // } logger.warning( "sourceLine.write returned 0, offset=" + offset + "; length=" + length + "; available=" + sourceLine.available() + "; frame size in bytes" + sourceLine.getFormat().getFrameSize() + "; sourceLine.isActive() = " + sourceLine.isActive() + "; " + sourceLine.isOpen() + "; sourceLine.isRunning()=" + sourceLine.isRunning()); return BUFFER_PROCESSED_FAILED; // sourceLine.write docs indicate that this will only happen // if there is an error. } else { offset += n; length -= n; } } if (bufferNotConsumed) { // return INPUT_BUFFER_NOT_CONSUMED if not all bytes were written buffer.setLength(newBufferLength); buffer.setOffset(newBufferOffset); return INPUT_BUFFER_NOT_CONSUMED; } if (buffer.isEOM()) { // TODO: the proper way to do this is to implement Drainable, and let the processor call our // drain method. sourceLine .drain(); // we need to ensure that the media finishes playing, otherwise the EOM event // will // be posted before the media finishes playing. } return BUFFER_PROCESSED_OK; }