/*
* 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.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import org.apache.log4j.Logger;
import com.ttProject.media.flv.CodecType;
import com.ttProject.media.flv.FlvHeader;
import com.ttProject.media.flv.ITagAnalyzer;
import com.ttProject.media.flv.Tag;
import com.ttProject.media.flv.TagAnalyzer;
import com.ttProject.media.flv.tag.AudioTag;
import com.ttProject.media.flv.tag.VideoTag;
import com.ttProject.nio.CacheBuffer;
import com.ttProject.nio.channels.FileReadChannel;
import com.ttProject.nio.channels.IFileReadChannel;
import com.ttProject.nio.channels.IReadChannel;
import com.ttProject.util.BufferUtil;
/**
* IndexFileCreatorでつくったデータを参照して、flvのデータを応答するモデル
* このモデルで動作させるにはいくつか必要なものがある。
* 1:全体のデータの長さ
* 2:映像コーデック
* 3:音声コーデック
* 4:audioTagByte
* 5:アクセス先ファイルの全長
* @author taktod
*/
public class FlvOrderModel {
private Logger logger = Logger.getLogger(FlvOrderModel.class);
private boolean isMshSended;
private int duration = 0;
private IFileReadChannel idx;
private CodecType videoCodec = null;
// private CodecType audioCodec = null; // こっちはこれは必要なくて、audioTagByteがあれば十分
private byte audioByte = 0;
private int size;
// データの読み込みを実行する開始部分。
private int position = -1;
private final boolean videoFlg;
private final boolean audioFlg;
private final int startMilliSecond;
// 各MSHタグデータ保持
private AudioTag audioMshTag = null;
private VideoTag videoMshTag = null;
private ITagAnalyzer analyzer = new TagAnalyzer();
/**
* コンストラクタ
* @param idxFile
* @param videoFlg
* @param audioFlg
* @param startMilliSecond
* @throws Exception
*/
public FlvOrderModel(IndexFileCreator idxFileCreator, boolean videoFlg, boolean audioFlg, int startMilliSecond) throws Exception {
idx = FileReadChannel.openFileReadChannel(idxFileCreator.getIdxFile().toURI().toURL());
duration = idxFileCreator.getDuration();
videoCodec = idxFileCreator.getVideoCodec();
audioByte = idxFileCreator.getAudioTagByte();
size = idxFileCreator.getSize();
this.videoFlg = videoFlg;
this.audioFlg = audioFlg;
this.startMilliSecond = startMilliSecond;
// 始めのアクセスデータについて調査しておく必要あり。
// idxFileを読み込んでaudioMshとvideoMshについて調査
// 可能なら、自身のデータの先頭位置についても調査しておく(一番はじめにアクセスするデータ位置(開始時刻依存))
}
/**
* 初期セットアップ
* TODO これ・・・sourceへのアクセスがないとinitializeできない。
* あっちの場合は(mp4の方)、mshデータをタグとして記録しているので、このままでも運用できたわけか・・・
* 仕方ないのでpublic化した。始めに呼び出す必要あり。
*/
public void initialize(IFileReadChannel source) throws Exception {
isMshSended = false;
position = -1;
while(idx.position() < idx.size()) {
ByteBuffer buffer = BufferUtil.safeRead(idx, 9);
byte flg = buffer.get();
// 位置
int position = buffer.getInt();
// timestamp
int timestamp = buffer.getInt();
Tag tag = null;
switch(flg) {
case 0: // keyFrame
break;
case 1: // videoMsh
logger.info("初期化でvideoMsh発見");
source.position(position);
tag = analyzer.analyze(source);
videoMshTag = (VideoTag) tag;
continue;
case 2: // audioMsh
logger.info("初期化でaudioMsh発見");
source.position(position);
tag = analyzer.analyze(source);
audioMshTag = (AudioTag) tag;
continue;
default:
throw new Exception("解析不能なデータを受け取りました。");
}
if(timestamp >= startMilliSecond) {
// 読み込みを実行する位置
source.position(position);
this.position = position;
// 位置が決定した場合は、mshを応答してやらないとだめなんだが・・・
break;
}
}
}
/**
* flvHeaderを応答する。
* @return
*/
public FlvHeader getFlvHeader() {
FlvHeader flvHeader = new FlvHeader();
if(!audioFlg) {
flvHeader.setAudioFlg(false);
}
else {
flvHeader.setAudioFlg(audioByte != 0x00);
}
if(!videoFlg) {
flvHeader.setVideoFlg(false);
}
else {
flvHeader.setVideoFlg(videoCodec != CodecType.NONE);
}
return flvHeader;
}
/**
* 応答データのvideoMshTagを応答します。
* @return
*/
public VideoTag getVideoMsh() {
return videoMshTag;
}
/**
* 応答データのaudioMshTagを応答します。
* @return
*/
public AudioTag getAudioMsh() {
return audioMshTag;
}
/**
* データを順番に応答します。データがない場合はnullを返します。まだあるけど、準備できていない場合はlistを返します。
* @param source
* @return
* @throws Exception
*/
public List<Tag> nextTagList(IReadChannel source) throws Exception {
List<Tag> result = new ArrayList<Tag>();
if(position == -1) {
logger.info("初アクセスなので初期化する");
// 始めのデータの場合はindexファイルのデータを確認して、自分の欲しいtimestampデータがない場合
// 推定でアクセスして、そのデータにアクセスするようにしておく。
// positionが-1の場合は位置が決定しなかったので、推測する。
position = (int)((long)startMilliSecond * size / duration);
logger.info("startPos:" + Integer.toHexString(position));
// 1:アクセスがきたら00 00 00 XXもしくは 00 00 00 00 XXの位置のデータをみつける。
source.position(position);
// ここから読み込んでいく。shortで0がでたら、そこがあやしい。
CacheBuffer cacheBuffer = new CacheBuffer(source);
// shortで取得していって中身を見たいがあとで巻き戻す動作が若干必要。
while(cacheBuffer.remaining() > 1) {
if(cacheBuffer.getShort() == 0) {
// ここで取得できるpositionは00 00 [XX
// 次のデータも確認して、00もしくは00 00ならtrackIDである可能性が高い
// 次以降のデータで0x00以外のデータがくるまで、読み込みつづける。
byte b = 0;
while((b = cacheBuffer.get()) == 0) {
;
}
logger.info("position:" + Integer.toHexString(cacheBuffer.position()));
logger.info(Integer.toHexString(b));
// bの値がaudioのtagByteか
int pos = cacheBuffer.position() - 12;
if(b == audioByte) {
logger.info("タグをみつけたと思われる。");
// 12バイト前のデータを見てみる。
source.position(pos);
cacheBuffer = new CacheBuffer(source);
if(cacheBuffer.get() != 0x08) {
logger.info("音声タグではなかったのでやり直し");
continue;
}
source.position(pos);
break;
}
else if((b & 0x0F) == CodecType.getVideoByte(videoCodec)) {
logger.info("タグをみつけたと思われる。");
// 12バイト前のデータを見てみる。
source.position(pos);
cacheBuffer = new CacheBuffer(source);
if(cacheBuffer.get() != 0x09) {
logger.info("映像タグではなかったのでやり直し");
continue;
}
source.position(pos);
break;
}
}
}
// tagを見つけたので、あとはkeyFrameの開始位置までスキップさせる。
Tag tag = null;
TagPositionAnalyzer analyzer = new TagPositionAnalyzer();
while((tag = analyzer.analyze(source)) != null) {
if(!videoFlg || videoCodec == CodecType.NONE) {
// 音声タグをみつけたらそこから実行させる。
if(tag instanceof AudioTag) {
AudioTag aTag = (AudioTag) tag;
aTag.analyze(source);
position = tag.getPosition();
if(audioMshTag != null) {
audioMshTag.setTimestamp(aTag.getTimestamp());
result.add(audioMshTag);
}
result.add(aTag);
return result;
}
}
else {
if(tag instanceof VideoTag) {
VideoTag vTag = (VideoTag) tag;
if(vTag.isKeyFrame()) {
vTag.analyze(source);
position = tag.getPosition();
if(videoMshTag != null) {
videoMshTag.setTimestamp(vTag.getTimestamp());
result.add(videoMshTag);
}
if(audioMshTag != null) {
audioMshTag.setTimestamp(vTag.getTimestamp());
result.add(audioMshTag);
}
result.add(vTag);
return result;
}
}
}
}
logger.info("ループぬけた");
}
// このタイミングで続きの動作を実行します。
Tag tag = analyzer.analyze(source);
if(tag == null) {
if(source.position() == source.size()) {
logger.info("終端?");
}
else {
logger.info("まだ");
}
// 終端までいったのかが問題。
return null;
}
if(!isMshSended) {
if(audioMshTag != null) {
audioMshTag.setTimestamp(tag.getTimestamp());
result.add(audioMshTag);
}
if(videoMshTag != null) {
videoMshTag.setTimestamp(tag.getTimestamp());
result.add(videoMshTag);
}
isMshSended = true;
}
result.add(tag);
return result;
}
public void close() {
if(idx != null) {
try {
idx.close();
}
catch (Exception e) {
}
idx = null;
}
}
}