/*
* myLib - https://github.com/taktod/myLib
* Copyright (c) 2014 ttProject. All rights reserved.
*
* Licensed under The MIT license.
*/
package com.ttProject.test;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import org.apache.log4j.Logger;
import org.junit.Test;
import com.ttProject.container.flv.FlvHeaderTag;
import com.ttProject.container.flv.FlvTagWriter;
import com.ttProject.frame.AudioFrame;
import com.ttProject.frame.IFrame;
import com.ttProject.frame.pcmalaw.PcmalawFrameAnalyzer;
import com.ttProject.nio.channels.ByteReadChannel;
import com.ttProject.nio.channels.IReadChannel;
import com.xuggle.xuggler.IAudioResampler;
import com.xuggle.xuggler.IAudioSamples;
import com.xuggle.xuggler.ICodec;
import com.xuggle.xuggler.IPacket;
import com.xuggle.xuggler.IStreamCoder;
import com.xuggle.xuggler.IAudioSamples.Format;
import com.xuggle.xuggler.IStreamCoder.Direction;
/**
* beep音からG711のflvを作りたい
* @author taktod
*/
public class MakeTest {
/** ロガー */
private Logger logger = Logger.getLogger(MakeTest.class);
/** audioデータの動作カウンター */
private int audioCounter = 0;
@Test
public void test() {
try {
logger.info("開始");
init();
IStreamCoder coder = IStreamCoder.make(Direction.ENCODING, ICodec.ID.CODEC_ID_PCM_ALAW);
coder.setSampleRate(8000);
coder.setChannels(1);
processConvert(coder);
logger.info("おわり");
}
catch(Exception e) {
logger.error("例外発生", e);
}
}
/**
* 初期化
*/
private void init() {
audioCounter = 0;
}
/**
* 変換実施
*/
private void processConvert(IStreamCoder audioCoder) {
IAudioResampler resampler = null;
try {
if(audioCoder != null) {
ICodec codec = audioCoder.getCodec();
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("対応している音声フォーマットが不明です。");
}
audioCoder.setSampleFormat(findFormat);
if(audioCoder.open(null, null) < 0) {
throw new Exception("音声コーダーが開けませんでした。");
}
}
IPacket packet = IPacket.make();
PcmalawFrameAnalyzer analyzer = new PcmalawFrameAnalyzer();
FlvTagWriter writer = new FlvTagWriter("output.flv");
FlvHeaderTag headerTag = new FlvHeaderTag();
headerTag.setAudioFlag(true);
headerTag.setVideoFlag(false);
writer.addContainer(headerTag);
long pts = 0;
while(true) {
if(audioCoder != null) {
IAudioSamples samples = samples();
if(samples.getSampleRate() != audioCoder.getSampleRate()
|| samples.getFormat() != audioCoder.getSampleFormat()
|| samples.getChannels() != audioCoder.getChannels()) {
if(resampler == null) {
// resamplerを作る必要あり。
resampler = IAudioResampler.make(
audioCoder.getChannels(), samples.getChannels(),
audioCoder.getSampleRate(), samples.getSampleRate(),
audioCoder.getSampleFormat(), samples.getFormat());
}
IAudioSamples spl = IAudioSamples.make(1024, audioCoder.getChannels());
int retVal = resampler.resample(spl, samples, samples.getNumSamples());
if(retVal <= 0) {
throw new Exception("音声サンプル失敗しました。");
}
samples = spl;
}
int samplesConsumed = 0;
while(samplesConsumed < samples.getNumSamples()) {
int retval = audioCoder.encodeAudio(packet, samples, samplesConsumed);
if(retval < 0) {
throw new Exception("変換失敗");
}
samplesConsumed += retval;
if(packet.isComplete()) {
packet.setDts(packet.getPts());
logger.info(packet.getPts());
logger.info(1 / packet.getTimeBase().getDouble());
IReadChannel readChannel = new ByteReadChannel(packet.getByteBuffer());
IFrame frame = null;
while((frame = analyzer.analyze(readChannel)) != null) {
logger.info(frame);
if(frame instanceof AudioFrame) {
AudioFrame f = (AudioFrame)frame;
f.setPts(pts);
f.setTimebase(f.getSampleRate());
pts += f.getSampleNum();
writer.addFrame(8, frame);
}
}
frame = analyzer.getRemainFrame();
if(frame != null) {
logger.info(frame);
if(frame instanceof AudioFrame) {
AudioFrame f = (AudioFrame)frame;
f.setPts(pts);
f.setTimebase(f.getSampleRate());
pts += f.getSampleNum();
writer.addFrame(8, frame);
}
}
}
}
if(samples.getPts() > 1000000) {
break;
}
}
else {
break;
}
}
writer.prepareTailer();
}
catch(Exception e) {
logger.error("例外発生", e);
}
finally {
if(audioCoder.isOpen()) {
audioCoder.close();
}
}
}
/**
* ラの音のaudioデータをつくって応答する。
* @return
*/
private IAudioSamples samples() {
// とりあえずラの音で1024サンプル数つくることにする。
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; // 各deltaごとの回転数
double max = (1 << (bit - 2)) - 1; // 振幅の大きさ(音の大きさ)
buffer.order(ByteOrder.LITTLE_ENDIAN); // xuggleで利用するデータはlittleEndianなのでlittleEndianを使うようにする。
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);
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の設定は必要っぽい
samples.setTimeStamp(startPos);
// こっちはいらないっぽい。ただし別の関数っぽいので、やっとくにこしたことはなさそうな・・・
samples.setPts(startPos);
return samples;
}
}