/* * myLib - https://github.com/taktod/myLib * Copyright (c) 2014 ttProject. All rights reserved. * * Licensed under The MIT license. */ package com.ttProject.media.flv.tag; import java.nio.ByteBuffer; import java.nio.channels.WritableByteChannel; import com.ttProject.media.flv.CodecType; import com.ttProject.media.flv.Tag; import com.ttProject.nio.channels.IReadChannel; import com.ttProject.util.BufferUtil; /** * videoデータ * @author taktod * * 01 4D 40 1E FF E1 00 19 67 4D 40 1E 92 42 01 40 5F F2 E0 22 00 00 03 00 C8 00 00 2E D5 1E 2C 5C 90 01 00 04 68 EE 32 C8 * [] avcC version 1 * [ ] profile compatibility level * [] 111111 + 2bit nal size - 1(ff固定とおもっててOKでしょう) * [] number of SPS e1固定? * [ ] spsLength * [spsNalデータ ] * [] number of PPS * [ ] ppsLength * [ ] ppsData */ public class VideoTag extends Tag implements Cloneable { /** コーデック */ private CodecType codec; /** フレーム情報 */ private FrameType frame; /** * フレームタイプ定義 */ private enum FrameType { Key, Inner, Disposable } /** avcのframeType 0:avcSequenceHeader 1:data 2:endOfSequence */ private byte avcFrameType = 0x01; // avcである場合のframeTypeデータ /** データ本体 */ private ByteBuffer data; /** * コンストラクタ(データがファイルにない場合の処理) */ public VideoTag() { super(); } /** * コンストラクタ(データがそもそもファイルにある場合の処理) * @param size * @param position * @param timestamp */ public VideoTag(final int position, final int size, final int timestamp) { super(position, size, timestamp); } /** * byteデータからコーデックとframeタイプを解析する * @param tagByte * @return true: mediaSequenceHeaderの判定データが必要 false:いらない */ public boolean analyzeTagByte(byte tagByte) { switch(tagByte & 0xF0) { case 0x10: frame = FrameType.Key; break; default: case 0x20: frame = FrameType.Inner; break; case 0x30: frame = FrameType.Disposable; break; } codec = CodecType.getVideoCodecType(tagByte); // h.264である場合はmshが必要 return (codec == CodecType.AVC); } /** * コーデックを設定 * @param codec */ public void setCodec(CodecType codec) { this.codec = codec; } /** * コーデックを参照 * @return */ public CodecType getCodec() { return codec; } /** * フレームタイプを設定する * @param keyFrame */ public void setFrameType(boolean keyFrame) { if(keyFrame) { frame = FrameType.Key; } else { frame = FrameType.Inner; } } /** * mshであるか設定する * @param flg */ public void setMSHFlg(boolean flg) { if(flg) { avcFrameType = 0x00; } else { avcFrameType = 0x01; } } /** * データを登録しておく * @param source * @param size * @throws Exception */ public void setData(IReadChannel source, int size) throws Exception { if(codec == CodecType.AVC) { // h.264の場合は、頭に00 00 00をいれてsourceのデータコピーが必要になる。 data = ByteBuffer.allocate(size + 3); data.put((byte)0x00); data.put((byte)0x00); data.put((byte)0x00); data.put(BufferUtil.safeRead(source, size)); data.flip(); } else { data = BufferUtil.safeRead(source, size); } } /** * データを登録しておく * @param buffer */ public void setRawData(ByteBuffer buffer) { data = buffer.duplicate(); } public ByteBuffer getRawData() { return data.duplicate(); } /** * キーフレームであるか応答する。 * @return */ public boolean isKeyFrame() { return frame == FrameType.Key; } /** * disposableInnerFrameであるか応答する。 * @return */ public boolean isDisposableInner() { return frame == FrameType.Disposable; } /** * {@inheritDoc} */ @Override public void analyze(IReadChannel ch, boolean atBegin) throws Exception { super.analyze(ch, atBegin); // 実データを読み込んでおく。 ch.position(getPosition() + 11); // 1バイト読み込んで、コーデックを解析しておく。 analyzeTagByte(BufferUtil.safeRead(ch, 1).get()); int dataSize = getSize() - 16; // h.264の場合はさらに4バイト読み込む必要あり。 if(codec == CodecType.AVC) { avcFrameType = BufferUtil.safeRead(ch, 1).get(); // 続く3バイトは0x00であることが予想されているべきだが、ほっとく。(つづく3バイトはbframeがある場合にdtsのデータがはいっているみたい。) dataSize --; } setRawData(BufferUtil.safeRead(ch, dataSize)); // tailを読み込む if(BufferUtil.safeRead(ch, 4).getInt() != getSize() - 4) { throw new Exception("tailByteの長さが狂ってます"); } } /** * {@inheritDoc} */ @Override public void writeTag(WritableByteChannel target) throws Exception { target.write(getBuffer()); } /** * {@inheritDoc} */ @Override public ByteBuffer getBuffer() throws Exception { data.position(0); // tagの作成 byte tagByte = 0x00; // デフォルトサイズの更新 setSize(data.remaining() + 1 + 15); switch(codec) { case JPEG: tagByte = 0x01; break; case H263: tagByte = 0x02; break; case SCREEN: tagByte = 0x03; break; case ON2VP6: tagByte = 0x04; break; case ON2VP6_ALPHA: tagByte = 0x05; break; case SCREEN_V2: tagByte = 0x06; break; case AVC: tagByte = 0x07; setSize(data.remaining() + 2 + 15); // avcの場合はサイズがかわるの修正しておく。 break; default: throw new Exception("定義されていないコーデックです。"); } switch(frame) { case Key: tagByte |= 0x10;break; case Inner: tagByte |= 0x20;break; case Disposable: tagByte |= 0x30;break; default: throw new Exception("定義されていないフレームです。"); } ByteBuffer buffer = ByteBuffer.allocate(getSize()); // header buffer.put(getHeaderBuffer((byte)0x09)); // tag buffer.put(tagByte); // avcの場合はmsh判定が必要 if(codec == CodecType.AVC) { buffer.put(avcFrameType); } // 実データ buffer.put(data.duplicate()); // 終端処理 buffer.put(getTailBuffer()); // 読み込みモードにする。 buffer.flip(); return buffer; } /** * mediaSequenceHeaderかどうか参照 * @return */ public boolean isMediaSequenceHeader() { return avcFrameType == 0x00; } public boolean isEndOfSequence() { return avcFrameType == 0x02; } /** * {@inheritDoc} */ @Override public String toString() { return "videoTag:" + getTimestamp() + " codec:" + getCodec(); } /** * 同じタグを作成して応答します。 */ public VideoTag clone() { VideoTag vTag = new VideoTag(0, getInitSize(), getTimestamp()); vTag.avcFrameType = avcFrameType; vTag.codec = codec; vTag.data = data; vTag.frame = frame; return vTag; } }