/*
* myLib - https://github.com/taktod/myLib
* Copyright (c) 2014 ttProject. All rights reserved.
*
* Licensed under GNU GENERAL PUBLIC LICENSE Version 3.
*/
package com.ttProject.transcode.xuggle.packet;
import java.nio.ByteBuffer;
import com.ttProject.media.Unit;
import com.ttProject.media.flv.tag.VideoTag;
import com.ttProject.media.h264.ConfigData;
import com.ttProject.media.h264.DataNalAnalyzer;
import com.ttProject.media.h264.Frame;
import com.ttProject.media.h264.frame.PictureParameterSet;
import com.ttProject.media.h264.frame.SequenceParameterSet;
import com.ttProject.media.h264.frame.Slice;
import com.ttProject.media.h264.frame.SliceIDR;
import com.ttProject.nio.channels.ByteReadChannel;
import com.ttProject.nio.channels.IReadChannel;
import com.ttProject.transcode.exception.FormatChangeException;
import com.xuggle.ferry.IBuffer;
import com.xuggle.xuggler.ICodec;
import com.xuggle.xuggler.IPacket;
import com.xuggle.xuggler.IRational;
import com.xuggle.xuggler.IStreamCoder;
import com.xuggle.xuggler.IStreamCoder.Direction;
/**
* flvTagからIPacketを作る動作
* @author taktod
*/
public class FlvVideoPacketizer implements IPacketizer {
/** 最終映像タグ */
private VideoTag lastVideoTag = null;
/** h264のsps */
private SequenceParameterSet sps = null;
/** h264のpps */
private PictureParameterSet pps = null;
/**
* データをあらかじめ確認します。
*/
@Override
public boolean check(Unit unit) throws FormatChangeException {
// 先にデータを確認して、データの整合性を確認します。
// なおtypeが違う場合は、そのままtrueを返します。
// falseになる場合の例:中途でコーデックがかわったなど
if(!(unit instanceof VideoTag)) {
return false;
}
if(lastVideoTag == null) {
return true;
}
VideoTag vTag = (VideoTag) unit;
if(vTag.getCodec() == lastVideoTag.getCodec()) {
return true;
}
throw new FormatChangeException();
}
/**
* tagからpacketを取り出します。
*/
@Override
public IPacket getPacket(Unit unit, IPacket packet) throws Exception {
if(unit instanceof VideoTag) {
lastVideoTag = (VideoTag) unit;
switch(lastVideoTag.getCodec()) {
case JPEG:
sps = null;
pps = null;
throw new RuntimeException("JPEGの変換は未実装です。");
case H263:
sps = null;
pps = null;
return getH263Packet(lastVideoTag, packet);
case SCREEN:
sps = null;
pps = null;
throw new RuntimeException("SCREENの変換は未実装です。");
case ON2VP6:
sps = null;
pps = null;
return getVP6Packet(lastVideoTag, packet);
case ON2VP6_ALPHA:
sps = null;
pps = null;
throw new RuntimeException("vp6 alphaの変換は未実装です。");
case SCREEN_V2:
sps = null;
pps = null;
throw new RuntimeException("Screen V2の変換は未実装です。");
case AVC:
return getAVCPacket(lastVideoTag, packet);
default:
break;
}
}
return null;
}
/**
* h263用のIPacketを生成します。
* @param tag
* @return
*/
private IPacket getH263Packet(VideoTag tag, IPacket packet) {
if(packet == null) {
packet = IPacket.make();
}
ByteBuffer buffer = tag.getRawData();
int size = buffer.remaining();
IBuffer bufData = IBuffer.make(null, buffer.array(), 0, size);
packet.setData(bufData);
packet.setFlags(0);
packet.setDts(tag.getTimestamp());
packet.setPts(tag.getTimestamp());
packet.setTimeBase(IRational.make(1, 1000));
packet.setComplete(true, size);
packet.setKeyPacket(tag.isKeyFrame());
return packet;
}
/**
* vp6用のIPacketを生成します。
* @param tag
* @return
*/
private IPacket getVP6Packet(VideoTag tag, IPacket packet) {
if(packet == null) {
packet = IPacket.make();
}
ByteBuffer buffer = tag.getRawData();
int size = buffer.remaining();
byte first = buffer.get();
byte[] data = new byte[size];
buffer.get(data, 0, size - 1);
data[data.length - 1] = first;
IBuffer bufData = IBuffer.make(null, data, 0, size);
packet.setData(bufData);
packet.setFlags(0);
packet.setDts(tag.getTimestamp());
packet.setPts(tag.getTimestamp());
packet.setTimeBase(IRational.make(1, 1000));
packet.setComplete(true, size);
packet.setKeyPacket(tag.isKeyFrame());
return packet;
}
/**
* avc用のIPacketを生成します。
* @param tag
* @return
*/
private IPacket getAVCPacket(VideoTag tag, IPacket packet) throws Exception {
if(tag.isMediaSequenceHeader()) {
// spsとppsを抜き出す。
ConfigData configData = new ConfigData();
IReadChannel rawData = new ByteReadChannel(tag.getRawData());
rawData.position(3);
for(Frame nal : configData.getNals(rawData)) {
if(nal instanceof SequenceParameterSet) {
sps = (SequenceParameterSet) nal;
}
else if(nal instanceof PictureParameterSet) {
pps = (PictureParameterSet) nal;
}
}
return null;
}
if(tag.isEndOfSequence()) {
return null;
}
if(sps == null || pps == null) {
throw new RuntimeException("spsもしくはppsが決定していません。");
}
// mshではないので、実データである。
if(packet == null) {
packet = IPacket.make();
}
ByteBuffer rawData = tag.getRawData();
rawData.position(3); // 3の位置に移動(ここからDataNalAnalyzerにいれるとnalが取り出せる)
DataNalAnalyzer dataAnalyzer = new DataNalAnalyzer(); // TODO これもglobal化しておいた方が本当はよい。ただし、ここでの動作はNALの解析ではなく、データの取り出しなので、globalでなくても問題ない。
// まぁnewするコストは無駄だけど。
IReadChannel rawDataChannel = new ByteReadChannel(rawData);
ByteBuffer buffer = null;
Frame avcFrame = null;
if(tag.isKeyFrame()) {
while((avcFrame = dataAnalyzer.analyze(rawDataChannel)) != null) {
if(avcFrame instanceof SliceIDR) {
break;
}
}
ByteBuffer spsData = sps.getData();
ByteBuffer ppsData = pps.getData();
ByteBuffer sliceIDRData = avcFrame.getData();
buffer = ByteBuffer.allocate(4 + spsData.remaining()
+ 4 + ppsData.remaining()
+ 4 + sliceIDRData.remaining());
buffer.putInt(1);
buffer.put(spsData);
buffer.putInt(1);
buffer.put(ppsData);
buffer.putInt(1);
buffer.put(sliceIDRData);
buffer.flip();
}
else {
while((avcFrame = dataAnalyzer.analyze(rawDataChannel)) != null) {
if(avcFrame instanceof Slice) {
break;
}
}
ByteBuffer sliceData =avcFrame.getData();
buffer = ByteBuffer.allocate(4 + sliceData.remaining());
buffer.putInt(1);
buffer.put(sliceData);
buffer.flip();
}
int size = buffer.remaining();
IBuffer bufData = IBuffer.make(null, buffer.array(), 0, size);
packet.setData(bufData);
packet.setFlags(1);
packet.setDts(tag.getTimestamp());
packet.setPts(tag.getTimestamp());
packet.setTimeBase(IRational.make(1, 1000));
packet.setComplete(true, size);
packet.setKeyPacket(tag.isKeyFrame());
return packet;
}
/**
* 現在動作しているデータに対応するデコーダーを取得します
*/
@Override
public IStreamCoder createDecoder() throws Exception {
// まだデータがわからない場合はnullを返す
if(lastVideoTag == null) {
return null;
}
IStreamCoder decoder = null;
switch(lastVideoTag.getCodec()) {
case JPEG:
throw new RuntimeException("JPEGの変換は未実装です。");
case H263:
decoder = IStreamCoder.make(Direction.DECODING, ICodec.ID.CODEC_ID_FLV1);
decoder.setTimeBase(IRational.make(1, 1000));
break;
case SCREEN:
throw new RuntimeException("SCREENの変換は未実装です。");
case ON2VP6:
decoder = IStreamCoder.make(Direction.DECODING, ICodec.ID.CODEC_ID_VP6F);
decoder.setTimeBase(IRational.make(1, 1000));
break;
case ON2VP6_ALPHA:
throw new RuntimeException("vp6 alphaの変換は未実装です。");
case SCREEN_V2:
throw new RuntimeException("Screen V2の変換は未実装です。");
case AVC:
decoder = IStreamCoder.make(Direction.DECODING, ICodec.ID.CODEC_ID_H264);
decoder.setTimeBase(IRational.make(1, 1000));
break;
default:
return null;
}
return decoder;
}
@Override
public void close() {
}
}