/* * Flazr <http://flazr.com> Copyright (C) 2009 Peter Thomas. * * This file is part of Flazr. * * Flazr 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 3 of the License, or * (at your option) any later version. * * Flazr 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 Flazr. If not, see <http://www.gnu.org/licenses/>. */ package com.flazr.io.f4v; import com.flazr.io.BufferReader; import com.flazr.io.FileChannelReader; import com.flazr.io.flv.FlvAtom; import com.flazr.rtmp.RtmpHeader; import com.flazr.rtmp.RtmpMessage; import com.flazr.rtmp.RtmpReader; import com.flazr.rtmp.message.Aggregate; import com.flazr.rtmp.message.Audio; import com.flazr.rtmp.message.Metadata; import com.flazr.rtmp.message.Video; import com.flazr.util.Utils; import java.util.List; import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.buffer.ChannelBuffers; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class F4vReader implements RtmpReader { private static final Logger logger = LoggerFactory.getLogger(F4vReader.class); private static final byte[] MP4A_BEGIN_PREFIX = Utils.fromHex("af00"); private static final byte[] MP4A_PREFIX = Utils.fromHex("af01"); private static final byte[] AVC1_BEGIN_PREFIX = Utils.fromHex("1700000000"); private static final byte[] AVC1_PREFIX_KEYFRAME = Utils.fromHex("1701"); private static final byte[] AVC1_PREFIX = Utils.fromHex("2701"); private byte[] AVC1_BEGIN; private byte[] MP4A_BEGIN; private final BufferReader in; private final List<Sample> samples; private final Metadata metadata; private int cursor; private int aggregateDuration; public F4vReader(final String path) { in = new FileChannelReader(path); final MovieInfo movie = new MovieInfo(in); in.position(0); AVC1_BEGIN = movie.getVideoDecoderConfig(); MP4A_BEGIN = movie.getAudioDecoderConfig(); logger.debug("video decoder config inited: {}", Utils.toHex(AVC1_BEGIN)); metadata = Metadata.onMetaData(movie); samples = movie.getSamples(); cursor = 0; } @Override public Metadata getMetadata() { return metadata; } @Override public RtmpMessage[] getStartMessages() { return new RtmpMessage[] { getMetadata(), new Video(AVC1_BEGIN_PREFIX, AVC1_BEGIN), new Audio(MP4A_BEGIN_PREFIX, MP4A_BEGIN) }; } @Override public void setAggregateDuration(int targetDuration) { this.aggregateDuration = targetDuration; } @Override public long getTimePosition() { final int index; if(cursor == samples.size()) { index = cursor - 1; } else { index = cursor; } return samples.get(index).getTime(); } @Override public long seek(long timePosition) { cursor = 0; while(cursor < samples.size()) { final Sample sample = samples.get(cursor); if(sample.getTime() >= timePosition) { break; } cursor++; } while(!samples.get(cursor).isSyncSample() && cursor > 0) { cursor--; } return samples.get(cursor).getTime(); } @Override public boolean hasNext() { return cursor < samples.size(); } private static final int AGGREGATE_SIZE_LIMIT = 65536; @Override public RtmpMessage next() { if(aggregateDuration <= 0) { return getMessage(samples.get(cursor++)); } final ChannelBuffer out = ChannelBuffers.dynamicBuffer(); int startSampleTime = -1; while(cursor < samples.size()) { final Sample sample = samples.get(cursor++); if(startSampleTime == -1) { startSampleTime = sample.getTime(); } final RtmpMessage message = getMessage(sample); final RtmpHeader header = message.getHeader(); final FlvAtom flvAtom = new FlvAtom(header.getMessageType(), header.getTime(), message.encode()); final ChannelBuffer temp = flvAtom.write(); if(out.readableBytes() + temp.readableBytes() > AGGREGATE_SIZE_LIMIT) { cursor--; break; } out.writeBytes(temp); if(sample.getTime() - startSampleTime > aggregateDuration) { break; } } return new Aggregate(startSampleTime, out); } private RtmpMessage getMessage(final Sample sample) { in.position(sample.getFileOffset()); final byte[] sampleBytes = in.readBytes(sample.getSize()); final byte[] prefix; if(sample.isVideo()) { if(sample.isSyncSample()) { prefix = AVC1_PREFIX_KEYFRAME; } else { prefix = AVC1_PREFIX; } // TODO move prefix logic to Audio / Video return new Video(sample.getTime(), prefix, sample.getCompositionTimeOffset(), sampleBytes); } else { prefix = MP4A_PREFIX; return new Audio(sample.getTime(), prefix, sampleBytes); } } @Override public void close() { in.close(); } public static void main(String[] args) { F4vReader reader = new F4vReader("test2.5.mp4"); while(reader.hasNext()) { logger.debug("read: {}", reader.next()); } } @Override public int getWidth() { // TODO Auto-generated method stub return 0; } @Override public int getHeight() { // TODO Auto-generated method stub return 0; } }