package us.sosia.video.stream.handler;
import java.awt.Dimension;
import java.awt.image.BufferedImage;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.oneone.OneToOneDecoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import us.sosia.video.stream.handler.frame.FrameDecoder;
import com.xuggle.ferry.IBuffer;
import com.xuggle.xuggler.ICodec;
import com.xuggle.xuggler.IPacket;
import com.xuggle.xuggler.IPixelFormat;
import com.xuggle.xuggler.IStreamCoder;
import com.xuggle.xuggler.IStreamCoder.Direction;
import com.xuggle.xuggler.IVideoPicture;
import com.xuggle.xuggler.video.ConverterFactory;
import com.xuggle.xuggler.video.ConverterFactory.Type;
import com.xuggle.xuggler.video.IConverter;
/**
* This codec will encode the bufferedImage to h264 stream
**/
public class H264StreamDecoder extends OneToOneDecoder {
protected final static Logger logger = LoggerFactory.getLogger(H264StreamDecoder.class);
protected final IStreamCoder iStreamCoder = IStreamCoder.make(Direction.DECODING, ICodec.ID.CODEC_ID_H264);
protected final Type type = ConverterFactory.findRegisteredConverter(ConverterFactory.XUGGLER_BGR_24);
protected final StreamFrameListener streamFrameListener;
protected final Dimension dimension;
protected final FrameDecoder frameDecoder;
protected final ExecutorService decodeWorker;
/**
* Cause there may be one or more image in the frame,so we need an Stream listener here to get
* all the image
*
* @param streamFrameListener
* @param dimension
* @param internalFrameDecoder
* @param decodeInOtherThread
*/
public H264StreamDecoder(StreamFrameListener streamFrameListener,
Dimension dimension, boolean internalFrameDecoder,
boolean decodeInOtherThread) {
super();
this.streamFrameListener = streamFrameListener;
this.dimension = dimension;
if (internalFrameDecoder) {
frameDecoder = new FrameDecoder(4);
} else {
frameDecoder = null;
}
if (decodeInOtherThread) {
decodeWorker = Executors.newSingleThreadExecutor();
} else {
decodeWorker = null;
}
initialize();
}
private void initialize() {
// iStreamCoder.setNumPicturesInGroupOfPictures(20);
// iStreamCoder.setBitRate(250000);
// iStreamCoder.setBitRateTolerance(9000);
// iStreamCoder.setPixelType(IPixelFormat.Type.YUV420P);
// iStreamCoder.setHeight(dimension.height);
// iStreamCoder.setWidth(dimension.width);
// iStreamCoder.setFlag(IStreamCoder.Flags.FLAG_QSCALE, true);
// iStreamCoder.setGlobalQuality(0);
// rate
// IRational rate = IRational.make(25, 1);
// iStreamCoder.setFrameRate(rate);
// time base
// iStreamCoder.setAutomaticallyStampPacketsForStream(true);
// iStreamCoder.setTimeBase(IRational.make(rate.getDenominator(),rate.getNumerator()));
iStreamCoder.open(null, null);
}
@Override
protected Object decode(ChannelHandlerContext ctx, Channel channel, final Object msg) throws Exception {
if (decodeWorker != null) {
decodeWorker.execute(new decodeTask(msg));
return null;
} else {
if (msg == null) {
throw new NullPointerException("you cannot pass into an null to the decode");
}
ChannelBuffer frameBuffer;
if (frameDecoder != null) {
frameBuffer = frameDecoder.decode((ChannelBuffer) msg);
if (frameBuffer == null) {
return null;
}
} else {
frameBuffer = (ChannelBuffer) msg;
}
int size = frameBuffer.readableBytes();
logger.info("decode the frame size :{}", size);
// start to decode
IBuffer iBuffer = IBuffer.make(null, size);
IPacket iPacket = IPacket.make(iBuffer);
iPacket.getByteBuffer().put(frameBuffer.toByteBuffer());
// decode the packet
if (!iPacket.isComplete()) {
return null;
}
IVideoPicture picture = IVideoPicture.make(IPixelFormat.Type.YUV420P,
dimension.width, dimension.height);
try {
// decode the packet into the video picture
int postion = 0;
int packageSize = iPacket.getSize();
while (postion < packageSize) {
postion += iStreamCoder.decodeVideo(picture, iPacket, postion);
if (postion < 0) {
throw new RuntimeException("error "
+ " decoding video");
}
// if this is a complete picture, dispatch the picture
if (picture.isComplete()) {
IConverter converter = ConverterFactory.createConverter(type
.getDescriptor(), picture);
BufferedImage image = converter.toImage(picture);
// BufferedImage convertedImage = ImageUtils.convertToType(image,
// BufferedImage.TYPE_3BYTE_BGR);
// here ,put out the image
if (streamFrameListener != null) {
streamFrameListener.onFrameReceived(image);
}
converter.delete();
} else {
picture.delete();
iPacket.delete();
return null;
}
// clean the picture and reuse it
picture.getByteBuffer().clear();
}
} finally {
if (picture != null) {
picture.delete();
}
iPacket.delete();
// ByteBufferUtil.destroy(data);
}
return null;
}
}
private class decodeTask implements Runnable {
private final Object msg;
public decodeTask(Object msg) {
super();
this.msg = msg;
}
@Override
public void run() {
if (msg == null) {
return;
}
ChannelBuffer frameBuffer;
if (frameDecoder != null) {
try {
frameBuffer = frameDecoder.decode((ChannelBuffer) msg);
if (frameBuffer == null) {
return;
}
} catch (Exception e) {
return;
}
} else {
frameBuffer = (ChannelBuffer) msg;
}
int size = frameBuffer.readableBytes();
logger.info("decode the frame size :{}", size);
// start to decode
IBuffer iBuffer = IBuffer.make(null, size);
IPacket iPacket = IPacket.make(iBuffer);
iPacket.getByteBuffer().put(frameBuffer.toByteBuffer());
// decode the packet
if (!iPacket.isComplete()) {
return;
}
IVideoPicture picture = IVideoPicture.make(IPixelFormat.Type.YUV420P,
dimension.width, dimension.height);
try {
// decode the packet into the video picture
int postion = 0;
int packageSize = iPacket.getSize();
while (postion < packageSize) {
postion += iStreamCoder.decodeVideo(picture, iPacket, postion);
if (postion < 0) {
throw new RuntimeException("error "
+ " decoding video");
}
// if this is a complete picture, dispatch the picture
if (picture.isComplete()) {
IConverter converter = ConverterFactory.createConverter(type
.getDescriptor(), picture);
BufferedImage image = converter.toImage(picture);
// BufferedImage convertedImage = ImageUtils.convertToType(image,
// BufferedImage.TYPE_3BYTE_BGR);
// here ,put out the image
if (streamFrameListener != null) {
streamFrameListener.onFrameReceived(image);
}
converter.delete();
} else {
picture.delete();
iPacket.delete();
return;
}
// clean the picture and reuse it
picture.getByteBuffer().clear();
}
} finally {
if (picture != null) {
picture.delete();
}
iPacket.delete();
// ByteBufferUtil.destroy(data);
}
return;
}
}
}