
org.red5.io.flv.FLVWriter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of android-rmtp-client Show documentation
Show all versions of android-rmtp-client Show documentation
A standalone RTMP client library ported from the Red5 project
The newest version!
package org.red5.io.flv;
/*
* RED5 Open Source Flash Server - http://code.google.com/p/red5/
*
* Copyright (c) 2006-2010 by respective authors (see below). All rights reserved.
*
* This library is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 2.1 of the License, or (at your option) any later
* version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with this library; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Map;
import org.apache.mina.core.buffer.IoBuffer;
import org.red5.io.IStreamableFile;
import org.red5.io.ITag;
import org.red5.io.ITagReader;
import org.red5.io.ITagWriter;
import org.red5.io.amf.Output;
import org.red5.io.object.Serializer;
//import org.red5.io.utils.HexDump;
import org.red5.io.utils.IOUtils;
//import org.red5.server.Red5;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A Writer is used to write the contents of a FLV file
*
* @author The Red5 Project ([email protected])
* @author Dominick Accattato ([email protected])
* @author Luke Hubbard, Codegent Ltd ([email protected])
* @author Tiago Jacobs ([email protected])
*/
public class FLVWriter implements ITagWriter {
private static Logger log = LoggerFactory.getLogger(FLVWriter.class);
/**
* Length of the flv header in bytes
*/
private final static int HEADER_LENGTH = 9;
/**
* Length of the flv tag in bytes
*/
private final static int TAG_HEADER_LENGTH = 11;
/**
* Position of the meta data tag in our file.
*/
private final static int META_POSITION = 13;
/**
* FLV object
*/
private IFLV flv;
/**
* Number of bytes written
*/
private volatile long bytesWritten;
/**
* Position in file
*/
private int offset;
/**
* Size of tag containing onMetaData.
*/
private int fileMetaSize = 0;
/**
* Id of the video codec used.
*/
private volatile int videoCodecId = -1;
/**
* Id of the audio codec used.
*/
private volatile int audioCodecId = -1;
/**
* Are we appending to an existing file?
*/
private boolean append;
/**
* Duration of the file.
*/
private int duration;
/**
* need direct access to file to append full duration metadata
*/
private RandomAccessFile file;
// size of the previous flv tag
private volatile int previousTagSize = 0;
/**
* Creates writer implementation with given file and last tag
*
* FLV.java uses this constructor so we have access to the file object
*
* @param file
* File output stream
* @param append
* true if append to existing file
*/
public FLVWriter(File file, boolean append) {
try {
log.debug("Writing to: {}", file.getAbsolutePath());
this.file = new RandomAccessFile(file, "rw"); // rwd
this.append = append;
init();
} catch (Exception e) {
log.error("Failed to create FLV writer", e);
}
}
/**
* Initialize the writer
*/
private void init() {
if (!append) {
try {
// write the flv file type header
writeHeader();
// write intermediate onMetaData tag, will be replaced later
writeMetadataTag(0, videoCodecId, audioCodecId);
} catch (IOException e) {
log.warn("Exception writing header or intermediate meta data",
e);
}
}
}
/**
* Writes the header bytes
*
* @throws IOException
* Any I/O exception
*/
public void writeHeader() throws IOException {
FLVHeader flvHeader = new FLVHeader();
flvHeader.setFlagAudio(true);
flvHeader.setFlagVideo(true);
// create a buffer
ByteBuffer header = ByteBuffer.allocate(HEADER_LENGTH + 4); // FLVHeader
// (9 bytes)
// +
// PreviousTagSize0
// (4 bytes)
flvHeader.write(header);
// write header to output channel
file.setLength(HEADER_LENGTH + 4);
if (header.hasArray()) {
log.debug("Header bytebuffer has a backing array");
file.write(header.array());
} else {
log.debug("Header bytebuffer does not have a backing array");
byte[] tmp = new byte[HEADER_LENGTH + 4];
header.get(tmp);
file.write(tmp);
}
bytesWritten = file.length();
header.clear();
}
/**
* {@inheritDoc}
*/
public IStreamableFile getFile() {
return flv;
}
/**
* Sets the base file.
*
* @param file
* source flv
*/
public void setFile(File file) {
try {
this.file = new RandomAccessFile(file, "rw");
} catch (FileNotFoundException e) {
log.warn("File could not be set", e);
}
}
/**
* Setter for FLV object
*
* @param flv
* FLV source
*
*/
public void setFLV(IFLV flv) {
this.flv = flv;
}
/**
* {@inheritDoc}
*/
public int getOffset() {
return offset;
}
/**
* Setter for offset
*
* @param offset
* Value to set for offset
*/
public void setOffset(int offset) {
this.offset = offset;
}
/**
* {@inheritDoc}
*/
public long getBytesWritten() {
return bytesWritten;
}
/**
* {@inheritDoc}
*/
public synchronized boolean writeTag(ITag tag) throws IOException {
/*
* Tag header = 11 bytes |-|---|----|---| 0 = type 1-3 = data size 4-7 =
* timestamp 8-10 = stream id (always 0) Tag data = variable bytes
* Previous tag = 4 bytes (tag header size + tag data size)
*/
// log.debug("writeTag - previous size: {}", previousTagSize);
// log.trace("Tag: {}", tag);
long prevBytesWritten = bytesWritten;
// skip tags with no data
int bodySize = tag.getBodySize();
// log.debug("Tag body size: {}", bodySize);
if (bodySize > 0) {
// ensure that the channel is still open
if (file != null) {
// set a var holding the entire tag size including the previous
// tag length
int totalTagSize = TAG_HEADER_LENGTH + bodySize + 4;
// resize
//metadata
// file.setLength(file.length() + totalTagSize);
// create a buffer for this tag
ByteBuffer tagBuffer = ByteBuffer.allocate(totalTagSize);
// get the data type
byte dataType = tag.getDataType();
// dont reset previous tag size on metadata
if (previousTagSize != tag.getPreviousTagSize()
&& dataType != ITag.TYPE_METADATA) {
// log.debug("Previous tag size: {} previous per tag: {}",
// previousTagSize, tag.getPreviousTagSize());
tag.setPreviousTagSize(previousTagSize);
}
int timestamp = tag.getTimestamp() + offset;
// create an array big enough
byte[] bodyBuf = new byte[bodySize];
// put the bytes into the array
tag.getBody().get(bodyBuf);
if (dataType == ITag.TYPE_AUDIO && audioCodecId == -1) {
int id = bodyBuf[0] & 0xff; // must be unsigned
audioCodecId = (id & ITag.MASK_SOUND_FORMAT) >> 4;
// log.debug("Audio codec id: {}", audioCodecId);
} else if (dataType == ITag.TYPE_VIDEO && videoCodecId == -1) {
int id = bodyBuf[0] & 0xff; // must be unsigned
videoCodecId = id & ITag.MASK_VIDEO_CODEC;
// log.debug("Video codec id: {}", videoCodecId);
}
// Data Type
tagBuffer.put(dataType); // 1
// Body Size - Length of the message. Number of bytes after
// StreamID to end of tag
// (Equal to length of the tag - 11)
IOUtils.writeMediumInt(tagBuffer, bodySize); // 3
// Timestamp
IOUtils.writeExtendedMediumInt(tagBuffer, timestamp); // 4
// Stream id
IOUtils.writeMediumInt(tagBuffer, 0); // 3
// get the body
tagBuffer.put(bodyBuf);
// update previous tag size
previousTagSize = TAG_HEADER_LENGTH + bodySize;
// we add the tag size
tagBuffer.putInt(previousTagSize);
// flip so we can process from the beginning
tagBuffer.flip();
// write the tag
file.write(tagBuffer.array());
bytesWritten = file.length();
tagBuffer.clear();
// update the duration
duration = Math.max(duration, timestamp);
// log.debug("Writer duration: {}", duration);
// validate written amount
//MetadataTag
if ((bytesWritten - prevBytesWritten) != (previousTagSize + 4)) {
log.debug(
"Not all of the bytes appear to have been written, prev-current: {}",
(bytesWritten - prevBytesWritten));
}
} else {
// throw an exception and let them know the cause
throw new IOException(
"FLV write channel has been closed and cannot be written to",
new ClosedChannelException());
}
} else {
log.debug("Empty tag skipped: {}", tag);
return false;
}
return true;
}
/**
* {@inheritDoc}
*/
public boolean writeTag(byte type, IoBuffer data) throws IOException {
return false;
}
/**
* {@inheritDoc}
*/
public void close() {
log.debug("close");
try {
// set to where the flv header goes
file.seek(0);
// Header fields (in same order than spec, for comparison purposes)
FLVHeader flvHeader = new FLVHeader();
flvHeader.setFlagAudio(audioCodecId != -1 ? true : false);
flvHeader.setFlagVideo(videoCodecId != -1 ? true : false);
// create a buffer
ByteBuffer header = ByteBuffer.allocate(HEADER_LENGTH + 4);
flvHeader.write(header);
// write header to output channel
file.write(header.array());
header.clear();
// here we overwrite the metadata with the final duration
log.debug("In the metadata writing (close) method - duration:{}",
duration);
file.seek(META_POSITION);
writeMetadataTag(duration * 0.001, videoCodecId, audioCodecId);
} catch (IOException e) {
log.error("IO error on close", e);
} finally {
try {
if (file != null) {
// run a test on the flv if debugging is on
// if (log.isDebugEnabled()) {
// // debugging
// //testFLV();
// }
// close the file
file.close();
}
} catch (IOException e) {
log.error("", e);
}
}
}
/** {@inheritDoc} */
public boolean writeStream(byte[] b) {
// TODO implement writing byte stream
return false;
}
/**
* Write "onMetaData" tag to the file.
*
* @param duration
* Duration to write in milliseconds.
* @param videoCodecId
* Id of the video codec used while recording.
* @param audioCodecId
* Id of the audio codec used while recording.
* @throws IOException
* if the tag could not be written
*/
private void writeMetadataTag(double duration, Integer videoCodecId,
Integer audioCodecId) throws IOException {
log.debug(
"writeMetadataTag - duration: {} video codec: {} audio codec: {}",
new Object[] { duration, videoCodecId, audioCodecId });
IoBuffer buf = IoBuffer.allocate(192);
buf.setAutoExpand(true);
Output out = new Output(buf);
out.writeString("onMetaData");
Map
© 2015 - 2025 Weber Informatics LLC | Privacy Policy