/*
* myLib - https://github.com/taktod/myLib
* Copyright (c) 2014 ttProject. All rights reserved.
*
* Licensed under The MIT license.
*/
package com.ttProject.container.mkv.type;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import org.apache.log4j.Logger;
import com.ttProject.container.mkv.MkvMasterTag;
import com.ttProject.container.mkv.MkvTag;
import com.ttProject.container.mkv.Type;
import com.ttProject.container.mkv.type.TrackType.Media;
import com.ttProject.container.riff.type.Fmt;
import com.ttProject.frame.AudioAnalyzer;
import com.ttProject.frame.AudioSelector;
import com.ttProject.frame.CodecType;
import com.ttProject.frame.IAnalyzer;
import com.ttProject.frame.IAudioFrame;
import com.ttProject.frame.IFrame;
import com.ttProject.frame.IVideoFrame;
import com.ttProject.frame.VideoAnalyzer;
import com.ttProject.frame.VideoSelector;
import com.ttProject.frame.aac.AacDsiFrameAnalyzer;
import com.ttProject.frame.aac.AacFrame;
import com.ttProject.frame.adpcmimawav.AdpcmImaWavFrameAnalyzer;
import com.ttProject.frame.h264.ConfigData;
import com.ttProject.frame.h264.DataNalAnalyzer;
import com.ttProject.frame.h264.H264Frame;
import com.ttProject.frame.h264.H264FrameSelector;
import com.ttProject.frame.h265.H265DataNalAnalyzer;
import com.ttProject.frame.h265.H265FrameSelector;
import com.ttProject.frame.mjpeg.MjpegFrameAnalyzer;
import com.ttProject.frame.mp3.Mp3FrameAnalyzer;
import com.ttProject.frame.theora.TheoraFrameAnalyzer;
import com.ttProject.frame.vorbis.VorbisFrame;
import com.ttProject.frame.vorbis.VorbisFrameAnalyzer;
import com.ttProject.frame.vp8.Vp8FrameAnalyzer;
import com.ttProject.frame.vp9.Vp9FrameAnalyzer;
import com.ttProject.nio.channels.ByteReadChannel;
import com.ttProject.nio.channels.IReadChannel;
import com.ttProject.unit.extra.EbmlValue;
/**
* TrackEntry
* @author taktod
*/
public class TrackEntry extends MkvMasterTag {
/** logger */
private Logger logger = Logger.getLogger(TrackEntry.class);
private long timebase; // timebase is settled by others(come from Info)
private int lacingFlag = 0; // global lacing type.
@SuppressWarnings("unused")
private Media type = null; // type depend on child elements.(frames)
/** frame analyzer */
private IAnalyzer analyzer = null;
/**
* constructor
* @param size
*/
public TrackEntry(EbmlValue size) {
super(Type.TrackEntry, size);
}
/**
* constructor
*/
public TrackEntry() {
this(new EbmlValue());
}
/**
* setup entry
* make it easy to use after loading.
* @param defaultTimebase (meaning is same as frame and container. 1000 means 1milisec. 44100 means 1/44100 sec)
* @return trackId
*/
public int setupEntry(long defaultTimebase) throws Exception {
timebase = defaultTimebase;
TrackNumber trackNumber = null;
CodecPrivate codecPrivate = null;
CodecID codecId = null;
int width = 0;
int height = 0;
int bitDepth = 16;
int channels = 0;
int samplingRate = 0;
for(MkvTag tag : getChildList()) {
// seems to better to use trackNumber instead of trackId
if(tag instanceof TrackNumber) {
trackNumber = (TrackNumber)tag;
}
else if(tag instanceof FlagLacing) {
lacingFlag = (int)((FlagLacing) tag).getValue();
}
else if(tag instanceof CodecID) {
codecId = (CodecID)tag;
}
else if(tag instanceof CodecPrivate) {
codecPrivate = (CodecPrivate)tag;
}
else if(tag instanceof Video) {
for(MkvTag vTag : ((Video) tag).getChildList()) {
if(vTag instanceof PixelWidth) {
width = (int)((PixelWidth) vTag).getValue();
}
else if(vTag instanceof PixelHeight) {
height = (int)((PixelHeight) vTag).getValue();
}
}
}
else if(tag instanceof Audio) {
for(MkvTag aTag : ((Audio) tag).getChildList()) {
if(aTag instanceof SamplingFrequency) {
samplingRate = (int)((SamplingFrequency) aTag).getValue();
}
else if(aTag instanceof Channels) {
channels = (int)((Channels) aTag).getValue();
}
else if(aTag instanceof BitDepth) {
bitDepth = (int)((BitDepth) aTag).getValue();
}
}
}
else if(tag instanceof TrackType) {
type = ((TrackType)tag).getType();
}
}
if(trackNumber == null) {
throw new Exception("trackNumber is undefined.");
}
switch(codecId.getMkvCodecType()) {
case A_AAC:
analyzer = new AacDsiFrameAnalyzer();
analyzer.setPrivateData(new ByteReadChannel(codecPrivate.getMkvData()));
break;
case A_MPEG_L3:
analyzer = new Mp3FrameAnalyzer();
break;
case A_VORBIS:
analyzer = new VorbisFrameAnalyzer();
((VorbisFrameAnalyzer)analyzer).setPrivateData(new ByteReadChannel(codecPrivate.getMkvData()));
break;
case A_MS_ACM:
Fmt fmt = new Fmt();
ByteBuffer privateBuffer = codecPrivate.getMkvData();
ByteBuffer buffer = ByteBuffer.allocate(privateBuffer.remaining() + 4);
buffer.order(ByteOrder.LITTLE_ENDIAN);
buffer.putInt(privateBuffer.remaining());
buffer.put(privateBuffer);
buffer.flip();
IReadChannel channel = new ByteReadChannel(buffer);
fmt.minimumLoad(channel);
fmt.load(channel);
codecId.setCodecType(fmt.getCodecType());
// check the information of fmt.
switch(codecId.getCodecType()) {
case ADPCM_IMA_WAV:
analyzer = new AdpcmImaWavFrameAnalyzer();
break;
default:
throw new RuntimeException(codecId.getCodecType() + " is under construct for mkv.");
}
break;
case V_MPEG4_ISO_AVC:
DataNalAnalyzer dataNalAnalyzer = new DataNalAnalyzer();
// for h264, load sps and pps from CodecPrivate information.
ConfigData configData = new ConfigData();
configData.setSelector((H264FrameSelector)dataNalAnalyzer.getSelector());
configData.analyzeData(new ByteReadChannel(codecPrivate.getMkvData()));
dataNalAnalyzer.setConfigData(configData);
analyzer = dataNalAnalyzer;
break;
case V_MPEG_ISO_HEVC:
logger.info("h265 is not check yet.");
analyzer = new H265DataNalAnalyzer();
com.ttProject.frame.h265.ConfigData h265ConfigData = new com.ttProject.frame.h265.ConfigData();
h265ConfigData.setSelector((H265FrameSelector)((H265DataNalAnalyzer)analyzer).getSelector());
h265ConfigData.analyze(new ByteReadChannel(codecPrivate.getMkvData()));
break;
case V_THEORA:
analyzer = new TheoraFrameAnalyzer();
((TheoraFrameAnalyzer)analyzer).setPrivateData(new ByteReadChannel(codecPrivate.getMkvData()));
break;
case V_MJPEG:
analyzer = new MjpegFrameAnalyzer();
break;
case V_VP8:
analyzer = new Vp8FrameAnalyzer();
break;
case V_VP9:
logger.info("vp9 is not check yet.");
analyzer = new Vp9FrameAnalyzer();
break;
default:
throw new Exception("unexpected codec.");
}
if(analyzer instanceof AudioAnalyzer) {
AudioSelector selector = ((AudioAnalyzer)analyzer).getSelector();
selector.setBit(bitDepth);
selector.setChannel(channels);
selector.setSampleRate(samplingRate);
}
else if(analyzer instanceof VideoAnalyzer) {
VideoSelector selector = ((VideoAnalyzer)analyzer).getSelector();
selector.setWidth(width);
selector.setHeight(height);
}
return (int)trackNumber.getValue();
}
/**
* ref analyzer
* @return
*/
public IAnalyzer getAnalyzer() {
return analyzer;
}
/**
* ref timebase
*/
@Override
public long getTimebase() {
return timebase;
}
/**
* ref the lacing flag.
* @return
*/
public int getLacingFlag() {
return lacingFlag;
}
/**
* ref encoding
* @return
*/
public ContentEncodings getEncodings() {
for(MkvTag tag : getChildList()) {
if(tag instanceof ContentEncodings) {
return (ContentEncodings)tag;
}
}
return null;
}
/**
* ref codecType
* @return
* @throws Exception
*/
public CodecType getCodecType() throws Exception {
for(MkvTag tag : getChildList()) {
if(tag instanceof CodecID) {
return ((CodecID) tag).getCodecType();
}
}
throw new Exception("CodecID is undefined.");
}
/**
* set the codecType.
* @param codecType
*/
public void setCodecType(CodecType codecType) throws Exception {
CodecID codecId = new CodecID();
codecId.setCodecType(codecType);
addChild(codecId);
}
/**
* setup frame.
* @param trackId
* @param frame
* @throws Exception
*/
public void setupFrame(int trackId, IFrame frame, long defaultTimebase) throws Exception {
timebase = defaultTimebase;
TrackNumber trackNumber = new TrackNumber();
trackNumber.setValue(trackId);
addChild(trackNumber);
TrackUID trackUID = new TrackUID();
trackUID.setValue(trackId);
addChild(trackUID);
FlagLacing flagLacing = new FlagLacing();
flagLacing.setValue(0);
addChild(flagLacing);
Language language = new Language();
language.setValue("und");
addChild(language);
if(frame instanceof IAudioFrame) {
IAudioFrame aFrame = (IAudioFrame)frame;
TrackType trackType = new TrackType();
trackType.setType(Media.Audio);
addChild(trackType);
Audio audio = new Audio();
audio.setup(aFrame);
addChild(audio);
switch(aFrame.getCodecType()) {
case AAC:
{
AacFrame aacFrame = (AacFrame)aFrame;
CodecPrivate codecPrivate = new CodecPrivate();
codecPrivate.setValue(aacFrame.getPrivateData());
addChild(codecPrivate);
}
break;
case VORBIS:
{
VorbisFrame vorbisFrame = (VorbisFrame)aFrame;
CodecPrivate codecPrivate = new CodecPrivate();
codecPrivate.setValue(vorbisFrame.getPrivateData());
addChild(codecPrivate);
}
break;
case SPEEX:
case OPUS:
logger.error(aFrame.getCodecType() + " is under construction.");
break;
default:
break;
}
}
else if(frame instanceof IVideoFrame) {
IVideoFrame vFrame = (IVideoFrame)frame;
TrackType trackType = new TrackType();
trackType.setType(Media.Video);
addChild(trackType);
DefaultDuration defaultDuration = new DefaultDuration();
defaultDuration.setValue(1000000L);
addChild(defaultDuration);
Video video = new Video();
video.setup(vFrame);
addChild(video);
// h264 and h265 we need to makeup CodecPrivate.
switch(vFrame.getCodecType()) {
case H264:
{
H264Frame h264Frame = (H264Frame) vFrame;
// h264のcodecPrivateを作る必要あり
com.ttProject.frame.h264.ConfigData configData = new com.ttProject.frame.h264.ConfigData();
CodecPrivate codecPrivate = new CodecPrivate();
codecPrivate.setValue(configData.makeConfigData(h264Frame.getSps(), h264Frame.getPps()));
addChild(codecPrivate);
}
break;
case H265:
logger.error("h265 is under construction.");
break;
default:
break;
}
}
}
}