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交互に書き込まなければいけない