/*
* myLib - https://github.com/taktod/myLib
* Copyright (c) 2014 ttProject. All rights reserved.
*
* Licensed under GNU GENERAL PUBLIC LICENSE Version 3.
*/
package com.ttProject.myLib.setup;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import com.xuggle.xuggler.IAudioResampler;
import com.xuggle.xuggler.IAudioSamples;
import com.xuggle.xuggler.ICodec;
import com.xuggle.xuggler.IContainer;
import com.xuggle.xuggler.IPacket;
import com.xuggle.xuggler.IPixelFormat;
import com.xuggle.xuggler.IStreamCoder;
import com.xuggle.xuggler.IVideoPicture;
import com.xuggle.xuggler.IVideoResampler;
import com.xuggle.xuggler.IAudioSamples.Format;
import com.xuggle.xuggler.video.ConverterFactory;
import com.xuggle.xuggler.video.IConverter;
/**
* basic task for setup.
* @author taktod
*/
public class SetupBase {
/** counter for audioFrame */
private int audioCounter = 0;
/** counter for videoFrame */
private int videoCounter = 0;
/**
* initialize
*/
protected void init() {
audioCounter = 0;
videoCounter = 0;
}
/**
* process convert
* @param container
* @param videoEncoder
* @param audioEncoder
*/
protected void processConvert(IContainer container, IStreamCoder videoEncoder, IStreamCoder audioEncoder) throws Exception {
IVideoResampler videoResampler = null;
IAudioResampler audioResampler = null;
try {
if(videoEncoder != null) {
ICodec codec = videoEncoder.getCodec();
// need to check pixel type.
// if YUV420P is accepted, use it.
IPixelFormat.Type findType = null;
for(IPixelFormat.Type type : codec.getSupportedVideoPixelFormats()) {
if(findType == null) {
findType = type;
}
if(type == IPixelFormat.Type.YUV420P) {
findType = type;
break;
}
}
if(findType == null) {
throw new Exception("pixel format is unknown, error.");
}
videoEncoder.setPixelType(findType);
if(videoEncoder.open(null, null) < 0) {
throw new Exception("cannot open videoEncoder.");
}
}
if(audioEncoder != null) {
ICodec codec = audioEncoder.getCodec();
// need to check audioFormat.
// if S16 is accepted, use it.
IAudioSamples.Format findFormat = null;
for(IAudioSamples.Format format : codec.getSupportedAudioSampleFormats()) {
if(findFormat == null) {
findFormat = format;
}
if(findFormat == IAudioSamples.Format.FMT_S16) {
findFormat = format;
break;
}
}
if(findFormat == null) {
throw new Exception("audioSampleFormat is unknown, error.");
}
audioEncoder.setSampleFormat(findFormat);
if(audioEncoder.open(null, null) < 0) {
throw new Exception("cannot open audioEncoder.");
}
}
// open container header.
if(container.writeHeader() < 0) {
throw new Exception("failed to write header.");
}
IPacket packet = IPacket.make();
// start packet writing.
while(true) {
IVideoPicture picture = null;
if(videoEncoder != null) {
picture = image();
if(picture.getWidth() != videoEncoder.getWidth()
|| picture.getHeight() != videoEncoder.getHeight()
|| picture.getPixelType() != videoEncoder.getPixelType()) {
if(videoResampler == null) {
videoResampler = IVideoResampler.make(
videoEncoder.getWidth(), videoEncoder.getHeight(), videoEncoder.getPixelType(),
picture.getWidth(), picture.getHeight(), picture.getPixelType());
}
IVideoPicture pct = IVideoPicture.make(videoEncoder.getPixelType(), videoEncoder.getWidth(), videoEncoder.getHeight());
int retVal = videoResampler.resample(pct, picture);
if(retVal <= 0) {
throw new Exception("failed to picture resample.");
}
picture = pct;
}
if(videoEncoder.encodeVideo(packet, picture, 0) < 0) {
throw new Exception("failed to picture encode.");
}
if(packet.isComplete()) {
if(container.writePacket(packet) < 0) {
packet.setDts(packet.getPts());
throw new Exception("failed to write video track.");
}
}
}
if(audioEncoder != null) {
while(true) {
IAudioSamples samples = samples();
if(samples.getSampleRate() != audioEncoder.getSampleRate()
|| samples.getFormat() != audioEncoder.getSampleFormat()
|| samples.getChannels() != audioEncoder.getChannels()) {
if(audioResampler == null) {
audioResampler = IAudioResampler.make(
audioEncoder.getChannels(), samples.getChannels(),
audioEncoder.getSampleRate(), samples.getSampleRate(),
audioEncoder.getSampleFormat(), samples.getFormat());
}
IAudioSamples spl = IAudioSamples.make(1024, audioEncoder.getChannels());
int retVal = audioResampler.resample(spl, samples, samples.getNumSamples());
if(retVal <= 0) {
throw new Exception("failed to audio resample.");
}
samples = spl;
}
int samplesConsumed = 0;
while(samplesConsumed < samples.getNumSamples()) {
int retval = audioEncoder.encodeAudio(packet, samples, samplesConsumed);
if(retval < 0) {
throw new Exception("failed to audio encode.");
}
samplesConsumed += retval;
if(packet.isComplete()) {
packet.setDts(packet.getPts());
if(container.writePacket(packet) < 0) {
throw new Exception("failed to write audio track.");
}
}
}
if((picture != null && samples.getPts() > picture.getPts())
|| samples.getPts() > 1000000) {
break;
}
}
}
if(picture == null || picture.getPts() > 1000000) {
break;
}
}
if(container.writeTrailer() < 0) {
throw new Exception("failed to write container tailer.");
}
}
catch(Exception e) {
e.printStackTrace();
}
finally {
if(videoEncoder != null) {
videoEncoder.close();
}
if(audioEncoder != null) {
audioEncoder.close();
}
if(container != null) {
container.close();
}
}
}
/**
* make audio beep sound. 440Hz
* @return
*/
private IAudioSamples samples() {
int samplingRate = 44100;
int tone = 440;
int bit = 16;
int channels = 2;
int samplesNum = 1024;
ByteBuffer buffer = ByteBuffer.allocate((int)samplesNum * bit * channels / 8);
double rad = tone * 2 * Math.PI / samplingRate; // radian for each sample.
double max = (1 << (bit - 2)) - 1; // max of ampletude
// make buffer
buffer.order(ByteOrder.LITTLE_ENDIAN); // xuggle need little endian for this.
long startPos = 1000 * audioCounter / 44100 * 1000;
for(int i = 0;i < samplesNum / 8;i ++, audioCounter ++) {
short data = (short)(Math.sin(rad * audioCounter) * max);
for(int j = 0;j < channels;j ++) {
buffer.putShort(data);
}
}
buffer.flip();
int snum = (int)(buffer.remaining() * 8/bit/channels);
// make audioSamples.
IAudioSamples samples = IAudioSamples.make(snum, channels, Format.FMT_S16);
samples.getData().put(buffer.array(), 0, 0, buffer.remaining());
samples.setComplete(true, snum, samplingRate, channels, Format.FMT_S16, 0);
// timestamp is needed.
samples.setTimeStamp(startPos);
// this seems not to be necessary, however, just do it.
samples.setPts(startPos);
return samples;
}
/**
* make picture data. with random number.
* @return
*/
private IVideoPicture image() {
// for instance, make random image with 10 fps.
BufferedImage base = new BufferedImage(320, 240, BufferedImage.TYPE_3BYTE_BGR);
String message = Integer.toString((int)(Math.random() * 1000));
Graphics g = base.getGraphics();
g.setColor(Color.white);
g.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 24));
g.drawString(message, 100, 100);
g.dispose();
// make xuggle picture.
IConverter converter = ConverterFactory.createConverter(base, IPixelFormat.Type.YUV420P);
IVideoPicture picture = converter.toPicture(base, 25000 * videoCounter);
// setPts do setTimestamp inside.
picture.setPts(25000 * videoCounter);
videoCounter ++;
return picture;
}
/**
* make target file.
* @param path
* @param file
* @return
*/
protected String getTargetFile(String project, String file) {
String target = "../" + project + "/src/test/resources/" + file;
String[] data = target.split("/");
File f = new File(".");
f = new File(f.getAbsolutePath());
f = f.getParentFile().getParentFile();
for(String path : data) {
f = new File(f.getAbsolutePath(), path);
}
f.getParentFile().mkdirs();
return f.getAbsolutePath();
}
}