/*
* myLib - https://github.com/taktod/myLib
* Copyright (c) 2014 ttProject. All rights reserved.
*
* Licensed under The MIT license.
*/
package com.ttProject.frame.h264;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import org.apache.log4j.Logger;
import com.ttProject.frame.IVideoFrame;
import com.ttProject.frame.extra.VideoMultiFrame;
import com.ttProject.frame.h264.exception.ConfigDataException;
import com.ttProject.frame.h264.type.PictureParameterSet;
import com.ttProject.frame.h264.type.SequenceParameterSet;
import com.ttProject.nio.channels.ByteReadChannel;
import com.ttProject.nio.channels.IReadChannel;
import com.ttProject.unit.ISelector;
import com.ttProject.unit.extra.BitLoader;
import com.ttProject.unit.extra.bit.Bit16;
import com.ttProject.unit.extra.bit.Bit2;
import com.ttProject.unit.extra.bit.Bit3;
import com.ttProject.unit.extra.bit.Bit5;
import com.ttProject.unit.extra.bit.Bit6;
import com.ttProject.unit.extra.bit.Bit8;
import com.ttProject.util.BufferUtil;
/**
* configData
* from flv mediaSequenceHeader or mp4 decodeBox(Avcc)
* nalデータを取り出す動作
* 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
* 01 42 00 16 FF E1 00 2E 67 42 80 16 96 54 05 01 ED 80 A84000000300400000053800007A10000F425FC638C00003D080007A12FE31C3B4244D4001000468CE3520
* 01 42 80 16 FF E1 00 2E 67 42 80 16 96 54 05 01 ED 80 A84000000300400000053800007A10000F425FC638C00003D080007A12FE31C3B4244D4001000468CE3520
* [] avcC version 1
* [ ] profile compatibility level
* [] 111111 + 2bit nal size - 1(ff固定とおもっててOKでしょう)
* [] number of SPS e1固定?
* [ ] spsLength
* [spsNalデータ ]
* [] number of PPS
* [ ] ppsLength
* [ ] ppsData
* もしくはnalデータからconfigDataを作り出す動作
* 参照元のデータが古かった模様です。
* @see http://blog.arcen.org/201109/article_1.html
* こっちを参考にして組み直しておこう。
* 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 version1(互換性がくずれたら新しい番号が振られるらしい、いまのところ1以外みたことない)
* [ ] profile compatibility levelの3点セット
* [] lengthSizeMinusOneWithReserved 0x3F | (nalLength - 1); (nalLengthは1,2,4のどれかっぽい)
* ここ・・・0xFCの間違いじゃないかな (注)
* [] numOfSequenceParameterSetsWithReserved 0xE0 | 数になる(この場合1つ)
* [ ] spsの長さ
* [] spsの本体
* numOfSequenceParameterSetsWithReserved | 0x1Fの数分、spsが繰り返されます。(よってspsが複数ある場合を想定していないので、現状のmyLib.frame.h264は複数ある場合はつかえないですね。)
* 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
* [] numOfPictureParameterSets ppsの数
* [ ] ppsのサイズ
* [ ] ppsデータ
* spsと同じくppsの数分だけ繰り返されることになります。
* profileの値が100 110 122 144の場合はさらにspse(sequenceParameterSetExtがあります。)
* 注:lengthSizeMinusOneの部分は、flvやmp4のnalSize定義の部分に影響しているものと思われます。
* 09 00 03 05 00 00 00 00 00 00 00 17 01 00 00 00 00 00 02 fc 65 88 80 80 0f ff
* [ ]この部分0x000002fcがNalのサイズになっていますが、
* これはNalLengthMinusOneが3になっているので4byteになっているだけみたいです。
* mkvやmp4、flvでlengthSizeMinusOneの部分が3の場合に4byteによるnalデータサイズ定義があることを確認しました。
* これらのコンテナでは、ConfigDataを取り回して使わないとNalのサイズ決定ができないっぽいですね。
* @author taktod
*/
public class ConfigData {
private Logger logger = Logger.getLogger(ConfigData.class);
/** h264 data selector */
private H264FrameSelector selector = null;
private Bit8 avcCVersion = new Bit8();
private Bit8 profile = new Bit8();
private Bit8 compatibility = new Bit8();
private Bit8 level = new Bit8();
private Bit6 reservedBit1 = new Bit6();
private Bit2 nalSizeMinusOne = new Bit2();
private Bit3 reservedBit2 = new Bit3();
private Bit5 numOfSps = new Bit5();
private List<SequenceParameterSet> spsList = new ArrayList<SequenceParameterSet>();
private Bit8 numOfPps = new Bit8();
private List<PictureParameterSet> ppsList = new ArrayList<PictureParameterSet>();
// sps ext(profile = 100? 110 122 144 required.)
/* private Bit6 reservedBit3;
private Bit2 chromaFormat;
private Bit5 reservedBit4;
private Bit3 bitDepthLumaMinus8;
private Bit5 reservedBit5;
private Bit3 bitDepthChromaMinus8;
private Bit8 numOfSpse;
private List<SequenceParameterSetExt> spseList; */
/**
* set the selector
* @param selector
*/
public void setSelector(H264FrameSelector selector) {
this.selector = selector;
}
/**
* ref the nal size for data nal.
* usually 4byte.
* @return
*/
public int getNalSizeBytes() {
return nalSizeMinusOne.get() + 1;
}
/**
* ref the sps list.
* @return
*/
public List<SequenceParameterSet> getSpsList() {
return new ArrayList<SequenceParameterSet>(spsList);
}
/**
* ref the pps list.
* @return
*/
public List<PictureParameterSet> getPpsList() {
return new ArrayList<PictureParameterSet>(ppsList);
}
/**
* analyze configData.
* @param channel
* @throws Exception
*/
public void analyzeData(IReadChannel channel) throws Exception {
ISelector selector = null;
if(this.selector != null) {
selector = this.selector;
}
else {
selector = new H264FrameSelector();
}
// check first 5 byte.
BitLoader loader = new BitLoader(channel);
loader.load(avcCVersion, profile, compatibility, level, reservedBit1, nalSizeMinusOne);
// get sps
loader.load(reservedBit2, numOfSps);
for(int i = 0;i < numOfSps.get();i ++) {
Bit16 spsSize = new Bit16();
loader.load(spsSize);
logger.info("spsSize:" + spsSize.get());
IReadChannel byteChannel = new ByteReadChannel(BufferUtil.safeRead(channel, spsSize.get()));
IVideoFrame sps = (IVideoFrame)selector.select(byteChannel);
if(sps == null) {
throw new ConfigDataException("sps is missing, use nalAnalyzer.");
}
if(!(sps instanceof SequenceParameterSet)) {
throw new Exception("sps is expected, however, found other frame.:" + sps.getClass().getSimpleName());
}
sps.load(byteChannel);
spsList.add((SequenceParameterSet)sps);
}
// get pps
loader.load(numOfPps);
for(int i = 0;i < numOfPps.get();i ++) {
Bit16 ppsSize = new Bit16();
loader.load(ppsSize);
IReadChannel byteChannel = new ByteReadChannel(BufferUtil.safeRead(channel, ppsSize.get()));
IVideoFrame pps = (IVideoFrame)selector.select(byteChannel);
if(!(pps instanceof PictureParameterSet)) {
throw new Exception("pps is expected, however, found other frame.:" + pps.getClass().getSimpleName());
}
pps.load(byteChannel);
ppsList.add((PictureParameterSet)pps);
}
switch(profile.get()) {
case 100:
break;
case 110:
case 122:
case 144:
throw new Exception("profile required spsext, however, not code yet.");
default:
break;
}
}
/**
* get sps and pps.
* @param channel
* @return
* @throws Exception
*/
public IVideoFrame getNalsFrame(IReadChannel channel) throws Exception {
ISelector selector = null;
if(this.selector != null) {
selector = this.selector;
}
else {
selector = new H264FrameSelector();
}
VideoMultiFrame result = new VideoMultiFrame();
if(channel.size() - channel.position() < 8) {
throw new Exception("data size is too small");
}
ByteBuffer buffer = BufferUtil.safeRead(channel, 6);
if(buffer.get() != 0x01) {
throw new Exception("avccVersion is not 1, I need sample.");
}
short spsSize = BufferUtil.safeRead(channel, 2).getShort();
IReadChannel byteChannel = new ByteReadChannel(BufferUtil.safeRead(channel, spsSize));
IVideoFrame sps = (IVideoFrame)selector.select(byteChannel);
if(!(sps instanceof SequenceParameterSet)) {
throw new Exception("sps is expected, however, the frame is " + sps.getClass().getSimpleName());
}
sps.load(byteChannel);
result.addFrame(sps);
BufferUtil.safeRead(channel, 1);
short ppsSize = BufferUtil.safeRead(channel, 2).getShort();
byteChannel = new ByteReadChannel(BufferUtil.safeRead(channel, ppsSize));
IVideoFrame pps = (IVideoFrame)selector.select(byteChannel);
if(!(pps instanceof PictureParameterSet)) {
throw new Exception("pps is expected, however, the frame is " + pps.getClass().getSimpleName());
}
pps.load(byteChannel);
result.addFrame(pps);
return result;
}
/**
* make configData from sps and pps.
* @param sps
* @param pps
* @return
* @throws Exception
*/
public ByteBuffer makeConfigData(SequenceParameterSet sps, PictureParameterSet pps) throws Exception {
ByteBuffer spsBuffer = sps.getData();
ByteBuffer ppsBuffer = pps.getData();
ByteBuffer data = ByteBuffer.allocate(11 + spsBuffer.remaining() + ppsBuffer.remaining());
data.put((byte)1);
spsBuffer.position(1);
int profile = spsBuffer.get() & 0xFF;
byte compatibility = spsBuffer.get();
data.put((byte)profile);
switch(profile) {
case 0x42:
if(compatibility != (byte)0xE0) {
compatibility = 0x00;
}
break;
case 0x58:
if(compatibility != (byte)0xA0) {
compatibility = 0x00;
}
break;
case 0x4D:
if(compatibility != (byte)0x40) {
compatibility = 0x00;
}
break;
case 0x64:
compatibility = 0x00;
break;
default:
compatibility = 0x00;
break;
}
data.put(compatibility);
data.put(spsBuffer.get());
spsBuffer.position(0);
data.put((byte)0xFF);
data.put((byte)0xE1);
data.putShort((short)spsBuffer.remaining());
data.put(spsBuffer);
data.put((byte)1);
data.putShort((short)ppsBuffer.remaining());
data.put(ppsBuffer);
data.flip();
return data;
}
}