/* * 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.rtmp.reader; import java.util.List; import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.buffer.ChannelBuffers; import org.jboss.netty.buffer.DynamicChannelBuffer; import org.jboss.netty.util.internal.LinkedTransferQueue; import android.util.Log; import com.flazr.io.f4vutil.MovieInfo; import com.flazr.io.f4vutil.Sample; import com.flazr.rtmp.RtmpHeader; import com.flazr.rtmp.RtmpMessage; import com.flazr.rtmp.client.FlvAtom; import com.flazr.rtmp.message.Aggregate; import com.flazr.rtmp.message.Audio; import com.flazr.rtmp.message.MetadataAmf0; import com.flazr.rtmp.message.Video; import com.flazr.util.Utils; public class F4vReader implements RtmpReader { 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 FileChannelReader in; private List<Sample> samples; private MetadataAmf0 metadata; private int cursor; private int aggregateDuration; @Override public int init(final String path) throws RuntimeException{ in = new FileChannelReader(); int val = in.init(path); if(val < 0){ return val; } final MovieInfo movie = new MovieInfo(in); in.position(0); AVC1_BEGIN = movie.getVideoDecoderConfig(); MP4A_BEGIN = movie.getAudioDecoderConfig(); Log.d("F4vReader",""+ Utils.toHex(AVC1_BEGIN,0,AVC1_BEGIN.length,false)); metadata = MetadataAmf0.onMetaData(movie); samples = movie.getSamples(); cursor = 0; return 0; } @Override public MetadataAmf0 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) }; } public void setAggregateDuration(int targetDuration) { this.aggregateDuration = targetDuration; } public long getTimePosition() { final int index; if(cursor == samples.size()) { index = cursor - 1; } else { index = cursor; } return samples.get(index).getTime(); } 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 = new DynamicChannelBuffer(ChannelBuffers.BIG_ENDIAN,256); 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,temp.readableBytes()); 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(); reader.init("test2.5.mp4"); while(reader.hasNext()) { Log.d("read: {}",""+ reader.next()); } } @Override public LinkedTransferQueue<RtmpMessage> getGrobalQueue() { // エンコとかしないので何もしない return null; } }