/*
* myLib - https://github.com/taktod/myLib
* Copyright (c) 2014 ttProject. All rights reserved.
*
* Licensed under GNU GENERAL PUBLIC LICENSE Version 3.
*/
package com.ttProject.xuggle.frameutil;
import java.nio.ByteBuffer;
import org.apache.log4j.Logger;
import com.ttProject.frame.IAudioFrame;
import com.ttProject.frame.IFrame;
import com.ttProject.frame.IVideoFrame;
import com.ttProject.frame.extra.AudioMultiFrame;
import com.ttProject.frame.extra.VideoMultiFrame;
import com.ttProject.frame.vorbis.VorbisFrame;
import com.ttProject.frame.vorbis.type.CommentHeaderFrame;
import com.ttProject.frame.vorbis.type.IdentificationHeaderFrame;
import com.ttProject.frame.vorbis.type.SetupHeaderFrame;
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;
/**
* frame -> packet
* @author taktod
*/
public class Packetizer {
/** logger */
@SuppressWarnings("unused")
private Logger logger = Logger.getLogger(Packetizer.class);
/**
* make packet from frame.
* I want to support multiFrame. However, there is a trouble for reuse of IPacket.
* @param frame
* @param packet
* @return null: no data. IPacket object: pakcet
*/
public IPacket getPacket(IFrame frame, IPacket packet) throws Exception {
if(packet == null) {
packet = IPacket.make();
}
if(frame instanceof AudioMultiFrame) {
throw new Exception("multiFrame is not supported.");
}
else if(frame instanceof VideoMultiFrame) {
throw new Exception("multiFrame is not supported.");
}
else if(frame instanceof IAudioFrame) {
return getAudioPacket((IAudioFrame)frame, packet);
}
else if(frame instanceof IVideoFrame) {
return getVideoPacket((IVideoFrame)frame, packet);
}
return null;
}
/**
* make video packet
* @param frame
* @param packet
* @return
* @throws Exception
*/
private IPacket getVideoPacket(IVideoFrame frame, IPacket packet) throws Exception {
ByteBuffer buffer = frame.getPackBuffer();
if(buffer == null) {
return null;
}
int size = buffer.remaining();
IBuffer bufData = IBuffer.make(null, buffer.array(), 0, size);
packet.setData(bufData);
packet.setFlags(0);
packet.setDts(frame.getDts());
packet.setPts(frame.getPts());
packet.setTimeBase(IRational.make(1, (int)frame.getTimebase()));
packet.setComplete(true, size);
packet.setKeyPacket(frame.isKeyFrame());
return packet;
}
/**
* make audio packet
* @param frame
* @param packet
* @return
* @throws Exception
*/
private IPacket getAudioPacket(IAudioFrame frame, IPacket packet) throws Exception {
ByteBuffer buffer = frame.getPackBuffer();
if(buffer == null) {
return null;
}
int size = buffer.remaining();
IBuffer bufData = IBuffer.make(null, buffer.array(), 0, size);
packet.setData(bufData);
packet.setPts(frame.getPts());
packet.setTimeBase(IRational.make(1, (int)frame.getTimebase()));
packet.setComplete(true, size);
return packet;
}
/**
* get the decoder for targetFrame.
* @param frame
* @param decoder decoder on process, if changed, make new one.
* @return
*/
public IStreamCoder getDecoder(IFrame frame, IStreamCoder decoder) throws Exception {
switch(frame.getCodecType()) {
case AAC:
if(decoder == null || decoder.getCodecID() != ICodec.ID.CODEC_ID_AAC) {
decoder = makeAudioDecoder((IAudioFrame) frame, ICodec.ID.CODEC_ID_AAC);
}
break;
case ADPCM_IMA_WAV:
if(decoder == null || decoder.getCodecID() != ICodec.ID.CODEC_ID_ADPCM_IMA_WAV) {
decoder = makeAudioDecoder((IAudioFrame) frame, ICodec.ID.CODEC_ID_ADPCM_IMA_WAV);
}
break;
case ADPCM_SWF:
if(decoder == null || decoder.getCodecID() != ICodec.ID.CODEC_ID_ADPCM_SWF) {
decoder = makeAudioDecoder((IAudioFrame) frame, ICodec.ID.CODEC_ID_ADPCM_SWF);
}
break;
case MP3:
if(decoder == null || decoder.getCodecID() != ICodec.ID.CODEC_ID_MP3) {
decoder = makeAudioDecoder((IAudioFrame) frame, ICodec.ID.CODEC_ID_MP3);
}
break;
case NELLYMOSER:
if(decoder == null || decoder.getCodecID() != ICodec.ID.CODEC_ID_NELLYMOSER) {
decoder = makeAudioDecoder((IAudioFrame) frame, ICodec.ID.CODEC_ID_NELLYMOSER);
}
break;
case PCM_ALAW:
if(decoder == null || decoder.getCodecID() != ICodec.ID.CODEC_ID_PCM_ALAW) {
decoder = makeAudioDecoder((IAudioFrame) frame, ICodec.ID.CODEC_ID_PCM_ALAW);
}
break;
case PCM_MULAW:
if(decoder == null || decoder.getCodecID() != ICodec.ID.CODEC_ID_PCM_MULAW) {
decoder = makeAudioDecoder((IAudioFrame) frame, ICodec.ID.CODEC_ID_PCM_MULAW);
}
break;
case SPEEX:
if(decoder == null || decoder.getCodecID() != ICodec.ID.CODEC_ID_SPEEX) {
decoder = makeAudioDecoder((IAudioFrame) frame, ICodec.ID.CODEC_ID_SPEEX);
}
break;
case VORBIS:
if(decoder == null || decoder.getCodecID() != ICodec.ID.CODEC_ID_VORBIS) {
if(frame instanceof IdentificationHeaderFrame || frame instanceof CommentHeaderFrame || frame instanceof SetupHeaderFrame) {
// before initialize, cannot do anything.
return null;
}
decoder = makeAudioDecoder((IAudioFrame) frame, ICodec.ID.CODEC_ID_VORBIS);
VorbisFrame vorbisFrame = (VorbisFrame)frame;
// need to put private data for vorbis for decode.
ByteBuffer buffer = vorbisFrame.getPrivateData();
int size = buffer.remaining();
IBuffer extraData = IBuffer.make(decoder, buffer.array(), 0, size);
decoder.setExtraData(extraData, 0, size, true);
}
break;
case FLV1:
if(decoder == null || decoder.getCodecID() != ICodec.ID.CODEC_ID_FLV1) {
decoder = IStreamCoder.make(Direction.DECODING, ICodec.ID.CODEC_ID_FLV1);
decoder.setTimeBase(IRational.make(1, (int)frame.getTimebase()));
}
break;
case H264:
if(decoder == null || decoder.getCodecID() != ICodec.ID.CODEC_ID_H264) {
decoder = IStreamCoder.make(Direction.DECODING, ICodec.ID.CODEC_ID_H264);
decoder.setTimeBase(IRational.make(1, (int)frame.getTimebase()));
}
break;
case MJPEG:
if(decoder == null || decoder.getCodecID() != ICodec.ID.CODEC_ID_MJPEG) {
decoder = IStreamCoder.make(Direction.DECODING, ICodec.ID.CODEC_ID_MJPEG);
decoder.setTimeBase(IRational.make(1, (int)frame.getTimebase()));
}
break;
case THEORA:
if(decoder == null || decoder.getCodecID() != ICodec.ID.CODEC_ID_THEORA) {
decoder = IStreamCoder.make(Direction.DECODING, ICodec.ID.CODEC_ID_THEORA);
decoder.setTimeBase(IRational.make(1, (int)frame.getTimebase()));
}
break;
case VP6:
if(decoder == null || decoder.getCodecID() != ICodec.ID.CODEC_ID_VP6F) {
decoder = IStreamCoder.make(Direction.DECODING, ICodec.ID.CODEC_ID_VP6F);
decoder.setTimeBase(IRational.make(1, (int)frame.getTimebase()));
}
break;
case VP8:
if(decoder == null|| decoder.getCodecID() != ICodec.ID.CODEC_ID_VP8) {
decoder = IStreamCoder.make(Direction.DECODING, ICodec.ID.CODEC_ID_VP8);
decoder.setTimeBase(IRational.make(1, (int)frame.getTimebase()));
}
break;
case H265:
case NONE:
case OPUS:
case VP9:
default:
throw new RuntimeException("xuggle doesn't support these codec:" + frame.getCodecType());
}
return decoder;
}
/**
* make audioDecoder.
* @param frame
* @param id
* @return
*/
private IStreamCoder makeAudioDecoder(IAudioFrame frame, ICodec.ID id) {
IStreamCoder decoder = null;
if(frame.getSampleRate() == 0 || frame.getTimebase() == 0 || frame.getChannel() == 0) {
// if no actual audio data, skip.(like meta data.)
return null;
}
decoder = IStreamCoder.make(Direction.DECODING, id);
decoder.setSampleRate(frame.getSampleRate());
decoder.setTimeBase(IRational.make(1, (int)frame.getTimebase()));
decoder.setChannels(frame.getChannel());
return decoder;
}
}