/* * myLib - https://github.com/taktod/myLib * Copyright (c) 2014 ttProject. All rights reserved. * * Licensed under The MIT license. */ package com.ttProject.media.flv.model; import java.io.File; import java.io.FileOutputStream; import java.nio.ByteBuffer; import org.apache.log4j.Logger; import com.ttProject.media.flv.CodecType; import com.ttProject.media.flv.FlvHeader; import com.ttProject.media.flv.Tag; import com.ttProject.media.flv.tag.AudioTag; import com.ttProject.media.flv.tag.VideoTag; import com.ttProject.nio.channels.FileReadChannel; import com.ttProject.nio.channels.IFileReadChannel; import com.ttProject.util.BufferUtil; /** * 高速seek用の一時ファイルを作成するプログラム * とりあえず知りたいことは * mshもtimestamp -> 位置 * timestamp -> keyFrameの位置 * これが知りたい * あとはmshのデータも欲しい * * とりあえずこうする。 * 1:先頭のデータを読み取ってmshを回収しておく。 * 2:終端のデータを読み取ってflvの長さを知っておく。 * 3:ここから解析開始。 * 4:解析がおわっている部分へのアクセスの場合 * →解析結果から正しいmshとkeyFrame位置を取得してそれに合わせて動作させる。 * 5:解析がおわっていない部分へのアクセスの場合 * 全体のbyte数と終端データのtimestampからおおよそのデータ位置を割り出す。 * そこから00 00 00 XXもしくは00 00 00 00 XXになっている部分を割り出す。(これがtagのtrackIDである可能性があるため。) * みつけたら11バイト前からのデータを確認して、tagデータであると仮定して処理する。 * skip後の位置に次のtagを見つけることができたらtag確定 * tagに沿って位置を探していって、keyFrameがきたらその位置から再生を実行することにする。 * * の2本立てでいいと思われる。 * * というわけでやらないといけないことは次のようになります。 * 現在の位置から解析させるためのデータ記録動作を作成。 * [開始位置][内容]の羅列のみでいいと思う。 * [tagの位置(int 4byte)] [type(byte 1byte)] * type:00 keyFrame 01 videoMSH 02 audioMSH * みたいな感じのファイルをつくっておく。 * 作成は始めのmshの確認だけ、本流でやっておく。(別途アクセスがあったときに必要だから) * その後の作成は別threadにやらせる(任意だけどやった方がよい:間にmshがある場合とかにも対応できるし・・・) * * データへのアクセスは別の関数で実行、実行するとその時間からはじまる映像のkeyFrame部が応答される。 * 映像のないデータの場合は音声のフレームの開始位置が応答される。 * 見つけ方は上記に書いた方法 * * みたいな感じのプログラムをつくっておく。 * @author taktod */ public class IndexFileCreator { private Logger logger = Logger.getLogger(IndexFileCreator.class); private final File targetFile; private FileOutputStream idx; // 書き込み対象 private long startTime; private long lastFoundTime; private int duration = 0; private IFileReadChannel source; private CodecType videoCodec = null; private CodecType audioCodec = null; private byte audioTagByte = 0; private int size; /** * コンストラクタ * @param targetFile 一時ファイル出力先 */ public IndexFileCreator(File targetFile, IFileReadChannel source) throws Exception { this.targetFile = targetFile; idx = new FileOutputStream(targetFile); startTime = System.currentTimeMillis(); lastFoundTime = startTime; // オブジェクトはコピーにしておく。 this.source = FileReadChannel.openFileReadChannel(source.getUri()); } public File getIdxFile() { return targetFile; } public int getDuration() { return duration; } public CodecType getVideoCodec() { return videoCodec; } public CodecType getAudioCodec() { return audioCodec; } public byte getAudioTagByte() { return audioTagByte; } public int getSize() { return size; } /** * 初期セットアップ */ public void initSetup() throws Exception { size = source.size(); // そこまで進んだという状態でいきたいので、先にデータを確認しておく。 source.position(source.size() - 4); int lastTagSize = BufferUtil.safeRead(source, 4).getInt(); int lastTagPos = source.size() - 4 - lastTagSize; source.position(lastTagPos); TagPositionAnalyzer analyzer = new TagPositionAnalyzer(); Tag tag = analyzer.analyze(source); duration = tag.getTimestamp(); // 最終タグの時刻位置を最終サイズとします。 source.position(0); // 内部データを解析していく。(ただし、5つのtagのみにしておく。(それ以外のデータは別スレッドで解析していけばいいと思う。)) FlvHeader header = new FlvHeader(); header.analyze(source); videoCodec = CodecType.NONE; audioCodec = CodecType.NONE; // boolean noVideoFlg = header.hasVideo(); // flvHeader的にvideoFlgがなければ、始めからaudioTagでシークできるようにしておきたいところ。 // tagが5つみつかったら終わりとする。 int foundedCount = 0; while(foundedCount < 5) { tag = analyzer.analyze(source); if(tag == null) { break; } foundedCount ++; if(tag instanceof AudioTag) { // msh判定のみほしい AudioTag aTag = (AudioTag) tag; audioTagByte = aTag.getTagByte(); audioCodec = aTag.getCodec(); if(aTag.isMediaSequenceHeader()) { logger.info("msh発見:"); logger.info(tag.getPosition()); long now = System.currentTimeMillis(); logger.info((now - lastFoundTime)); logger.info((now - startTime)); ByteBuffer buffer = ByteBuffer.allocate(9); buffer.put((byte)0x02); buffer.putInt(aTag.getPosition()); buffer.putInt(aTag.getTimestamp()); buffer.flip(); idx.getChannel().write(buffer); lastFoundTime = now; } } else if(tag instanceof VideoTag) { VideoTag vTag = (VideoTag) tag; videoCodec = vTag.getCodec(); if(vTag.isKeyFrame()) { logger.info("keyFrame発見:"); ByteBuffer buffer = ByteBuffer.allocate(9); if(vTag.isMediaSequenceHeader()) { logger.info("msh発見:"); buffer.put((byte)0x01); } else { buffer.put((byte)0x00); } buffer.putInt(vTag.getPosition()); buffer.putInt(vTag.getTimestamp()); buffer.flip(); idx.getChannel().write(buffer); logger.info(tag.getPosition()); long now = System.currentTimeMillis(); logger.info((now - lastFoundTime)); logger.info((now - startTime)); lastFoundTime = now; } } } } /** * 解析本家sourceの内容の解析の続きを請け負っておく。でいいはず * @throws Exception */ public void analyze() throws Exception { // 内部データを解析していく。 // 適当な位置からすすんでいった先の00 00 00になっているところをとりあえず探したい。 // 00 00 00 XXもしくは 00 00 00 00 XXになっているところがメディアデータの母体と思われる。 // tagを解析していって、動画のキーフレームの部分を抜き出したい。 Tag tag = null; TagPositionAnalyzer analyzer = new TagPositionAnalyzer(); while((tag = analyzer.analyze(source)) != null) { if(tag instanceof AudioTag) { // msh判定のみほしい AudioTag aTag = (AudioTag) tag; if(aTag.isMediaSequenceHeader()) { logger.info("msh発見:"); logger.info(tag.getPosition()); long now = System.currentTimeMillis(); logger.info((now - lastFoundTime)); logger.info((now - startTime)); lastFoundTime = now; } } else if(tag instanceof VideoTag) { VideoTag vTag = (VideoTag) tag; if(vTag.isKeyFrame()) { logger.info("keyFrame発見:"); if(vTag.isMediaSequenceHeader()) { logger.info("msh発見:"); } logger.info(tag.getPosition()); long now = System.currentTimeMillis(); logger.info((now - lastFoundTime)); logger.info((now - startTime)); lastFoundTime = now; } } } } public void close() { if(source != null) { try { source.close(); } catch (Exception e) { } source = null; } if(idx != null) { try { idx.close(); } catch (Exception e) { e.printStackTrace(); } idx = null; } } @Override public String toString() { StringBuilder data = new StringBuilder(); data.append("IndexFileCreator:"); data.append("[duration:").append(duration).append("]"); data.append("[videoCodec:").append(videoCodec).append("]"); data.append("[audioCodec:").append(audioCodec).append("]"); data.append("[size:").append(size).append("]"); return data.toString(); } }