JavaCVで動画を再生する
以前の記事で説明したとおり、JavaCVのFFmpegFrameRecorderは動画をエンコードすることができます。今回はFFmpegFrameGrabberを使い、動画をデコードして表示する単純な動画プレーヤーをつくってみました。
そんなに長くもないので全ソースコード。
public class TestPlayer {
CanvasFrame videoCanvas;
SourceDataLine audioLine;
public TestPlayer() {
videoCanvas = new CanvasFrame("VideoCanvas");
videoCanvas.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE);
}
void play() throws Exception {
//FrameGrabber grabber = new FFmpegFrameGrabber("mmsh://localhost:8080/");
//grabber.setFormat("asf");
FrameGrabber grabber = new FFmpegFrameGrabber("test.mp4");
grabber.start();
startAudioLine(grabber.getSampleRate());
try {
Frame frame = grabber.grabFrame();
while (frame!=null) {
if(frame.image!=null){
onFrameVideo(frame.image);
}
if(frame.samples!=null){
onFrameAudio(frame.samples);
}
frame = grabber.grabFrame();
}
} catch (Exception e) {
e.printStackTrace();
}
stopAudioLine();
grabber.stop();
grabber.release();
}
void onFrameVideo(IplImage iplImage){
int w = iplImage.cvSize().width();
int h = iplImage.cvSize().height();
videoCanvas.setCanvasSize(w, h);
videoCanvas.showImage(iplImage);
}
void onFrameAudio(Buffer[] buffer) throws Exception{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
float left = 0, right = 0;
for (int i = 0; i < buffer[0].limit(); i++) {
if(buffer.length==1){//mono
left = ((FloatBuffer)buffer[0]).get();
right = left;
}
if(buffer.length==2){//stereo
left = ((FloatBuffer)buffer[0]).get();
right = ((FloatBuffer)buffer[1]).get();
}
baos.write(float2shortBytes(left));
baos.write(float2shortBytes(right));
}
audioLine.write(baos.toByteArray(), 0, baos.size());
}
void startAudioLine(int sampleRate) throws Exception{
AudioFormat audioFormat = new AudioFormat(sampleRate, 16, 2, true, false);
DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat);
audioLine = (SourceDataLine) AudioSystem.getLine(info);
audioLine.open(audioFormat);
audioLine.start();
}
void stopAudioLine(){
audioLine.drain();
audioLine.stop();
audioLine.close();
}
byte[] float2shortBytes(float f){
int t = (int) (32768.0f * f);
t = (t<-32768) ? -32768 : t;
t = (t> 32767) ? 32767 : t;
short s = (short) t;
byte[] bytes = new byte[2];
bytes[0] = (byte) (s & 0xff);
bytes[1] = (byte) ((s >>> 8) & 0xff);
return bytes;
}
public static void main(String[] args) throws Exception {
TestPlayer test = new TestPlayer();
test.play();
}
}
音量調節したいときはデシベル計算など
void setVolume(double gain) {
Control control = audioLine.getControl(FloatControl.Type.MASTER_GAIN);
FloatControl fcontrol = (FloatControl)control;
float dB = (float) (Math.log(gain) / Math.log(10.0) * 20.0);
fcontrol.setValue(dB);
}
これで一応動画と音声が再生されます。ただこれだけではフレームレートが安定せず、フレームスキップなどを実装する必要がありそうです。
注意点というかはまりどころ
1.JavaのサウンドAPIを利用するために32bit floatを16bit shortに変換する必要がある
2.ステレオなら2つのFloatBufferに対し出力が1つなのでLR交互に書き込まなければいけない