/* * myLib - https://github.com/taktod/myLib * Copyright (c) 2014 ttProject. All rights reserved. * * Licensed under The MIT license. */ package com.ttProject.container.mkv; import java.util.ArrayList; import java.util.List; import org.apache.log4j.Logger; import com.ttProject.container.mkv.type.ContentCompression; import com.ttProject.container.mkv.type.ContentEncoding; import com.ttProject.container.mkv.type.ContentEncodings; import com.ttProject.container.mkv.type.TrackEntry; import com.ttProject.frame.Frame; import com.ttProject.frame.IAnalyzer; import com.ttProject.frame.IAudioFrame; import com.ttProject.frame.IFrame; import com.ttProject.frame.IVideoFrame; import com.ttProject.frame.NullFrame; import com.ttProject.frame.extra.AudioMultiFrame; import com.ttProject.frame.extra.VideoMultiFrame; import com.ttProject.nio.channels.ByteReadChannel; import com.ttProject.nio.channels.IReadChannel; import com.ttProject.unit.extra.BitLoader; import com.ttProject.unit.extra.EbmlValue; import com.ttProject.unit.extra.bit.Bit16; import com.ttProject.unit.extra.bit.Bit8; import com.ttProject.util.BufferUtil; /** * base for blockTag * blockTag is the frame holding tag, such as SimpleTag and BlockTag. * @author taktod */ public abstract class MkvBlockTag extends MkvBinaryTag { /** logger */ private Logger logger = Logger.getLogger(MkvBlockTag.class); private EbmlValue trackId = new EbmlValue(); private Bit16 timestampDiff = new Bit16(); private long time = 0; private IFrame frame = null; /** * constructor * @param id * @param size */ public MkvBlockTag(Type id, EbmlValue size) { super(id, size); } /** * {@inheritDoc} */ @Override public void minimumLoad(IReadChannel channel) throws Exception { super.minimumLoad(channel); BitLoader loader = new BitLoader(channel); loader.load(trackId, timestampDiff); } /** * {@inheritDoc} */ @Override public void load(IReadChannel channel) throws Exception { super.load(channel); time = getMkvTagReader().getClusterTime() + timestampDiff.get(); } /** * ref the lacing type * @return */ protected abstract Lacing getLacingType() throws Exception; /** * ref the frame. * @return */ public IFrame getFrame() throws Exception { if(frame == null) { analyzeFrames(); } return frame; } /** * analyzeFrames * @throws Exception */ private void analyzeFrames() throws Exception { IReadChannel channel = null; try { // check lacing type List<Integer> lacingSizeList = new ArrayList<Integer>(); channel = new ByteReadChannel(getMkvData()); switch(getLacingType()) { case No: lacingSizeList.add(channel.size()); break; case Xiph: { Bit8 num = new Bit8(); BitLoader loader = new BitLoader(channel); loader.load(num); int size = 0; for(int i = 0;i < num.get();i ++) { Bit8 sizeByte = null; do { sizeByte = new Bit8(); loader.load(sizeByte); size += sizeByte.get(); } while(sizeByte.get() == 0xFF); lacingSizeList.add(size); size = 0; } } break; case EBML: { Bit8 num = new Bit8(); BitLoader loader = new BitLoader(channel); loader.load(num); int size = 0; for(int i = 0;i < num.get();i ++) { EbmlValue value = new EbmlValue(); loader.load(value); if(i == 0) { size = value.get(); } else { // calcurate diff. int diff = value.get() - ((1 << (7 * value.getBitCount() / 8 - 1)) - 1); size = size + diff; } lacingSizeList.add(size); } } break; case FixedSize: { Bit8 num = new Bit8(); BitLoader loader = new BitLoader(channel); loader.load(num); // fixed size = size / (num + 1) int lacingSize = (channel.size() - 1) / (num.get() + 1); for(int i = 0;i < num.get() + 1;i ++) { lacingSizeList.add(lacingSize); } } break; default: throw new Exception("lacing type is corrupted."); } TrackEntry entry = getMkvTagReader().getTrackEntry(trackId.get()); ContentEncodings encodings = entry.getEncodings(); if(encodings == null) { for(Integer size : lacingSizeList) { analyzeFrame(entry, new ByteReadChannel(BufferUtil.safeRead(channel, size))); } } else { for(MkvTag tag : encodings.getChildList()) { if(tag instanceof ContentEncoding) { ContentEncoding encoding = (ContentEncoding)tag; logger.info(encoding); for(MkvTag etag : encoding.getChildList()) { if(etag instanceof ContentCompression) { ContentCompression compression = (ContentCompression)etag; logger.info(compression); switch(compression.getAlgoType()) { case Zlib: throw new Exception("zlib is not supported, I need sample."); case HeaderStripping: logger.info("header stripping..."); for(Integer size : lacingSizeList) { analyzeFrame(entry, new ByteReadChannel(BufferUtil.connect(compression.getSettingData(), BufferUtil.safeRead(channel, size)))); } break; default: throw new Exception("this algo type is not supported:" + compression.getAlgoType()); } } else { throw new Exception("unknown encoding data is founded.:" + etag); } } } else { throw new Exception("contentEncodings has unexpected data.:" + tag); } } } } finally { if(channel != null) { channel.close(); channel = null; } } } /** * analyze each frame. * @param entry * @param channel * @throws Exception */ private void analyzeFrame(TrackEntry entry, IReadChannel channel) throws Exception { IAnalyzer analyzer = entry.getAnalyzer(); IFrame analyzedFrame = null; do { analyzedFrame = analyzer.analyze(channel); if(analyzedFrame == null) { throw new Exception("no frame is analyzed"); } if(analyzedFrame instanceof NullFrame || !(analyzedFrame instanceof Frame)) { continue; } Frame tmpFrame = (Frame)analyzedFrame; tmpFrame.setPts(time); tmpFrame.setTimebase(entry.getTimebase()); addFrame(tmpFrame); } while(channel.size() != channel.position()); // need to check remainFrame. analyzedFrame = analyzer.getRemainFrame(); if(analyzedFrame != null && !(analyzedFrame instanceof NullFrame) && analyzedFrame instanceof Frame) { Frame tmpFrame = (Frame)analyzedFrame; tmpFrame.setPts(time); tmpFrame.setTimebase(entry.getTimebase()); addFrame(tmpFrame); } } /** * add frame. * @param tmpFrame * @throws Exception */ protected void addFrame(IFrame tmpFrame) throws Exception { if(tmpFrame == null) { return; } if(frame == null) { frame = tmpFrame; } else if(frame instanceof AudioMultiFrame) { if(!(tmpFrame instanceof IAudioFrame)) { throw new Exception("try to add non-audioFrame for audioMultiFrame."); } ((AudioMultiFrame)frame).addFrame((IAudioFrame)tmpFrame); } else if(frame instanceof VideoMultiFrame) { if(!(tmpFrame instanceof IVideoFrame)) { throw new Exception("try to add non-videoFrame for videoMultiFrame:" + tmpFrame); } ((VideoMultiFrame)frame).addFrame((IVideoFrame)tmpFrame); } else if(frame instanceof IAudioFrame) { AudioMultiFrame multiFrame = new AudioMultiFrame(); multiFrame.addFrame((IAudioFrame)frame); if(!(tmpFrame instanceof IAudioFrame)) { throw new Exception("try to add non-audioFrame for audioFrame."); } multiFrame.addFrame((IAudioFrame)tmpFrame); frame = multiFrame; } else if(frame instanceof IVideoFrame) { VideoMultiFrame multiFrame = new VideoMultiFrame(); multiFrame.addFrame((IVideoFrame)frame); if(!(tmpFrame instanceof IVideoFrame)) { throw new Exception("try to add non-videoFrame for videoFrame."); } multiFrame.addFrame((IVideoFrame)tmpFrame); frame = multiFrame; } else { throw new Exception("unknown frame is detected."); } } /** * ref the trackId * @return */ public EbmlValue getTrackId() { return trackId; } /** * ref the diff between cluster and block. * @return */ protected Bit16 getTimestampDiff() { return timestampDiff; } /** * {@inheritDoc} */ @Override public String toString(String space) { StringBuilder data = new StringBuilder(); data.append(super.toString(space)); data.append(" trackId:").append(trackId.get()); data.append(" timeDiff:").append(timestampDiff.get()); return data.toString(); } }