/*
* myLib - https://github.com/taktod/myLib
* Copyright (c) 2014 ttProject. All rights reserved.
*
* Licensed under The MIT license.
*/
package com.ttProject.media.extra.mp4;
import java.io.File;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import org.apache.log4j.Logger;
import com.ttProject.media.mp4.Atom;
import com.ttProject.media.mp4.IAtomAnalyzer;
import com.ttProject.media.mp4.Type;
import com.ttProject.media.mp4.atom.Ftyp;
import com.ttProject.media.mp4.atom.Mdhd;
import com.ttProject.media.mp4.atom.Mdia;
import com.ttProject.media.mp4.atom.Minf;
import com.ttProject.media.mp4.atom.Moov;
import com.ttProject.media.mp4.atom.Smhd;
import com.ttProject.media.mp4.atom.Stbl;
import com.ttProject.media.mp4.atom.Stco;
import com.ttProject.media.mp4.atom.Stsc;
import com.ttProject.media.mp4.atom.Stsd;
import com.ttProject.media.mp4.atom.Stss;
import com.ttProject.media.mp4.atom.Stsz;
import com.ttProject.media.mp4.atom.Stts;
import com.ttProject.media.mp4.atom.Tkhd;
import com.ttProject.media.mp4.atom.Trak;
import com.ttProject.media.mp4.atom.Vmhd;
import com.ttProject.media.mp4.atom.stsd.Record;
import com.ttProject.media.mp4.atom.stsd.RecordAnalyzer;
import com.ttProject.media.mp4.atom.stsd.data.Avcc;
import com.ttProject.media.mp4.atom.stsd.record.Aac;
import com.ttProject.media.mp4.atom.stsd.record.H264;
import com.ttProject.nio.channels.FileReadChannel;
import com.ttProject.nio.channels.IReadChannel;
import com.ttProject.util.BufferUtil;
/**
* indexファイルを作成していきます。
* @author taktod
*/
public class IndexFileCreator implements IAtomAnalyzer {
private Logger logger = Logger.getLogger(IndexFileCreator.class);
// private FileChannel idx; // 書き込み対象ファイル
private FileOutputStream idx;
private final File targetFile;
private int trakStartPos; // トラックの開始位置
private CurrentType type = null; // 現在の処理trakタイプ
private Vdeo vdeo = null;
private Sond sond = null;
private Meta meta = null;
private Mdhd mdhd = null;
private Tkhd tkhd = null;
private enum CurrentType { // タイプリスト
AUDIO,
VIDEO,
HINT,
MEDIA
}
/**
* コンストラクタ
* @param targetFile
* @throws Exception
*/
public IndexFileCreator(File targetFile) throws Exception {
this.targetFile = targetFile;
idx = new FileOutputStream(targetFile);
}
@Override
public Atom analyze(IReadChannel ch) throws Exception {
if(ch.size() == ch.position()) {
return null;
}
int position = ch.position();
ByteBuffer buffer = BufferUtil.safeRead(ch, 8);
int size = buffer.getInt();
String tag = BufferUtil.getDwordText(buffer);
Type type = Type.getType(tag);
switch(type) {
case Ftyp:
Ftyp ftyp = new Ftyp(position, size);
ch.position(position + size);
return ftyp;
case Moov:
Moov moov = new Moov(position, size);
moov.analyze(ch, this);
ch.position(position + size);
return moov;
/* case Mvhd:
Mvhd mvhd = new Mvhd(position, size);
// とりあえず解析せずほっとく
ch.position(position + size);
return mvhd;
/* case Iods: // 消す候補
// iodsは必要ないと思う。消す。
Iods iods = new Iods(position, size);
ch.position(position + size);
return iods;
case Udta: // 消す候補
// udtaはいらない。
Udta udta = new Udta(position, size);
ch.position(position + size);
return udta;*/
case Trak:
updatePrevTag();
this.type = null;
// trakの開始位置を調べる。
this.trakStartPos = (int)idx.getChannel().position();
Trak trak = new Trak(position, size);
trak.analyze(ch, this);
ch.position(position + size);
return trak;
case Tkhd: // 今回はこれがいる。
tkhd = new Tkhd(position, size);
tkhd.analyze(ch);
ch.position(position + size);
return tkhd;
case Mdia:
Mdia mdia = new Mdia(position, size);
mdia.analyze(ch, this);
ch.position(position + size);
return mdia;
case Mdhd:
mdhd = new Mdhd(position, size);
mdhd.analyze(ch, null);
ch.position(position + size);
if(tkhd.getHeight() != 0 && tkhd.getWidth() != 0) {
meta = new Meta(this.trakStartPos, 28);
meta.setHeight(tkhd.getHeight());
meta.setWidth(tkhd.getWidth());
meta.setDuration(mdhd.getDuration() * 1000 / mdhd.getTimescale());
}
return mdhd;
/* case Hdlr:
Hdlr hdlr = new Hdlr(position, size);
ch.position(position + size);
return hdlr;*/
case Minf:
Minf minf = new Minf(position, size);
minf.analyze(ch, this);
ch.position(position + size);
return minf;
case Vmhd:
this.type = CurrentType.VIDEO;
this.vdeo = new Vdeo(this.trakStartPos, 0);
this.vdeo.setTimescale(mdhd.getTimescale());
this.vdeo.writeIndex(idx.getChannel());
Vmhd vmhd = new Vmhd(position, size);
ch.position(position + size);
return vmhd;
case Smhd:
this.type = CurrentType.AUDIO;
this.sond = new Sond(this.trakStartPos, 0);
this.sond.setTimescale(mdhd.getTimescale());
this.sond.writeIndex(idx.getChannel());
Smhd smhd = new Smhd(position, size);
ch.position(position + size);
return smhd;
/* case Dinf:
Dinf dinf = new Dinf(position, size);
ch.position(position + size);
return dinf;*/
case Stbl:
Stbl stbl = new Stbl(position, size);
stbl.analyze(ch, this);
ch.position(position + size);
return stbl;
case Stsd:
Stsd stsd = new Stsd(position, size);
try {
stsd.analyze(ch, new RecordAnalyzer());
// 適合している場合はmshを取り出す。
for(Record record : stsd.getRecords()) {
if(record instanceof H264) {
H264 h264 = (H264)record;
Avcc avcc = h264.getAvcc();
// そのままコピーしておく。
buffer = ByteBuffer.allocate(8);
buffer.putInt(avcc.getSize());
buffer.put("msh ".getBytes());
buffer.flip();
idx.getChannel().write(buffer);
ch.position(avcc.getPosition() + 8);
BufferUtil.quickCopy(ch, idx.getChannel(), avcc.getSize() - 8);
}
else if(record instanceof Aac) {
// どうやらavconvでmp3に変換したらrecordタグはmp4aになるみたい。
// その場合mshはnullになってしまう。
Aac aac = (Aac)record;
// TODO この上書きの部分が少々気に入らない
// このタイミングでsondの中にデータをいれておく。
logger.info("sampleRate:" + aac.getSampleRate());
logger.info("channels:" + aac.getChannelCount());
long prevPos = idx.getChannel().position();
idx.getChannel().position(sond.getPosition() + 24);
buffer = ByteBuffer.allocate(5);
buffer.putInt(aac.getSampleRate());
buffer.put((byte)aac.getChannelCount());
buffer.flip();
idx.getChannel().write(buffer);
idx.getChannel().position(prevPos);
byte[] data = aac.getEsds().getSequenceHeader();
if(data != null) {
buffer = ByteBuffer.allocate(8 + data.length);
buffer.putInt(8 + data.length);
buffer.put("msh ".getBytes());
buffer.put(data);
buffer.flip();
idx.getChannel().write(buffer);
}
}
}
}
catch(Exception e) {
this.type = null;
e.printStackTrace();
// 適合していない場合は開始位置までfileを削っておく
idx.getChannel().truncate(trakStartPos);
idx.getChannel().position(trakStartPos);
return null;
}
ch.position(position + size);
return stsd;
case Stts:
Stts stts = new Stts(position, size);
buffer.position(0);
idx.getChannel().write(buffer);
BufferUtil.quickCopy(ch, idx.getChannel(), size - 8);
ch.position(position + size);
return stts;
case Stss: // keyFrameのデータになる必須
// 映像の場合はkeyFrame指示になるっぽい
Stss stss = new Stss(position, size);
buffer.position(0);
idx.getChannel().write(buffer);
BufferUtil.quickCopy(ch, idx.getChannel(), size - 8);
ch.position(position + size);
return stss;
case Stsc:
// 各チャンクのサンプル量
Stsc stsc = new Stsc(position, size);
buffer.position(0);
idx.getChannel().write(buffer);
BufferUtil.quickCopy(ch, idx.getChannel(), size - 8);
ch.position(position + size);
return stsc;
case Stsz:
// サンプルのサイズ量
Stsz stsz = new Stsz(position, size);
buffer.position(0);
idx.getChannel().write(buffer);
// この処理の部分を書き換えてサイズの計算を実施しておくべき。
// コピーしつつサイズの合計を調べておく。
BufferUtil.quickCopy(ch, idx.getChannel(), size - 8);
ch.position(position + size);
return stsz;
case Stco:
// 各チャンクの開始位置
Stco stco = new Stco(position, size);
buffer.position(0);
idx.getChannel().write(buffer);
BufferUtil.quickCopy(ch, idx.getChannel(), size - 8);
ch.position(position + size);
return stco;
/* case Mdat:
Mdat mdat = new Mdat(position, size);
// mdatの位置を考えることでmoovが後ろにあるmp4でも対応できるようになる。(ただし処理がおそくなる)
ch.position(position + size);
return mdat;*/
default:
break;
}
ch.position(position + size);
return new Atom(tag, position, size) {
@Override
public void analyze(IReadChannel ch, IAtomAnalyzer analyzer)
throws Exception {
;
}
};
}
public void updatePrevTag() throws Exception {
if(this.type == CurrentType.AUDIO
|| this.type == CurrentType.VIDEO) {
// いままでよんできたデータが正しいtagだった場合
int prevPosition = (int)idx.getChannel().position();
int prevSize = prevPosition - this.trakStartPos;
ByteBuffer buf = ByteBuffer.allocate(4);
buf.putInt(prevSize);
buf.flip();
idx.getChannel().position(trakStartPos);
idx.getChannel().write(buf);
idx.getChannel().position(prevPosition);
}
if(meta != null) {
// metaデータを書き込んでおく。
meta.writeIndex(idx.getChannel());
// meta = null;
}
}
/**
* 各要素の有用データ量を確認しておく
*/
public void checkDataSize() throws Exception {
IReadChannel tmp = FileReadChannel.openFileReadChannel(targetFile.getAbsolutePath());
while(tmp.position() < tmp.size()) {
int position = tmp.position();
ByteBuffer buffer = BufferUtil.safeRead(tmp, 8);
int size = buffer.getInt();
String tag = BufferUtil.getDwordText(buffer);
if("vdeo".equals(tag)) {
Vdeo vdeo = new Vdeo(position, size);
vdeo.analyze(tmp);
// 読み込み可能データ量を調べる
vdeo.getStco().start(tmp, false); // dataPos
vdeo.getStsc().start(tmp, false); // samples in chunk
vdeo.getStsz().start(tmp, false); // sample size
vdeo.getStts().start(tmp, false);
int totalSize = 0;
int sampleCount = 0;
while(vdeo.getStco().nextChunkPos() != -1) {
vdeo.getStsc().nextChunk();
int chunkSampleCount = vdeo.getStsc().getSampleCount();
for(int i = 0;i < chunkSampleCount;i ++) {
int sampleSize = vdeo.getStsz().nextSampleSize();
if(sampleSize == -1) {
break;
}
sampleCount ++;
totalSize += sampleSize;
if(vdeo.getStts().nextDuration() == -1) {
break;
}
}
}
idx.getChannel().position(vdeo.getPosition() + 12);
BufferUtil.writeInt(idx.getChannel(), sampleCount);
BufferUtil.writeInt(idx.getChannel(), totalSize);
tmp.position(position + size);
}
else if("sond".equals(tag)) {
Sond sond = new Sond(position, size);
sond.analyze(tmp);
sond.getStco().start(tmp, false); // dataPos
sond.getStsc().start(tmp, false); // samples in chunk
sond.getStsz().start(tmp, false); // sample size
sond.getStts().start(tmp, false);
int totalSize = 0;
int sampleCount = 0;
while(sond.getStco().nextChunkPos() != -1) {
sond.getStsc().nextChunk();
int chunkSampleCount = sond.getStsc().getSampleCount();
for(int i = 0;i < chunkSampleCount;i ++) {
int sampleSize = sond.getStsz().nextSampleSize();
if(sampleSize == -1) {
break;
}
sampleCount ++;
totalSize += sampleSize;
if(sond.getStts().nextDuration() == -1) {
break;
}
}
}
idx.getChannel().position(sond.getPosition() + 12);
BufferUtil.writeInt(idx.getChannel(), sampleCount);
BufferUtil.writeInt(idx.getChannel(), totalSize);
tmp.position(position + size);
}
else {
tmp.position(position + size);
}
}
tmp.close();
}
public Meta getMeta() {
return meta;
}
public void close() {
if(idx != null) {
try {
idx.close();
}
catch(Exception e) {
}
idx = null;
}
}
}