如何在Java环境中解码H.264video帧

有谁知道如何在Java环境中解码H.264video帧?

我的网络摄像机产品支持RTP / RTSP Streaming。

我的网络摄像机提供服务标准RTP / RTSP,它还支持“RTP / RTSP over HTTP”。

RTSP:TCP 554 RTP启动端口:UDP 5000

或者使用Xuggler 。 与RTP,RTMP,HTTP或其他协议一起使用,可以解码和编码H264和大多数其他编解码器。 并且是积极维护,免费和开源(LGPL)。

我认为最好的解决方案是使用“JNI + ffmpeg”。 在我目前的项目中,我需要在基于libgdx的java openGL游戏中同时播放几个全屏video。 我已经尝试了几乎所有的免费库,但没有一个具有可接受的性能。 所以最后我决定编写自己的jni C代码来使用ffmpeg。 这是我笔记本电脑的最终性能:

  • 环境:CPU:酷睿i7 Q740 @ 1.73G,video:nVidia GeForce GT 435M,操作系统:Windows 7 64位,Java:Java7u60 64位
  • video: h264rgb / h264编码,没有声音,分辨率: 1366 * 768
  • 解决方案:解码:JNI + ffmpeg v2.2.2,上传到GPU:使用lwjgl更新openGL纹理
  • 性能: 解码速度: 700-800FPS ,纹理上传每帧1ms

我只用了几天就完成了第一个版本。 但第一版的解码速度仅为120FPS左右,上传时间约为每帧5ms。 经过几个月的优化,我获得了最终的性能和一些额外的function。 现在我可以同时播放多个高清video而不会有任何缓慢。

我游戏中的大多数video都具有透明背景。 这种透明video是一个带有2个video流的mp4文件,一个流存储h264rgb编码的rgb数据,另一个流存储h264编码的alpha数据。 因此,为了播放alphavideo,我需要解码2个video流并将它们合并在一起,然后上传到GPU。 因此,我可以在游戏中同时在不透明高清video上方播放几个透明高清video。

您可以使用名为JCodec的纯Java库( http://jcodec.org )。
解码一个H.264帧非常简单:

 ByteBuffer bb = ... // Your frame data is stored in this buffer H264Decoder decoder = new H264Decoder(); Picture out = Picture.create(1920, 1088, ColorSpace.YUV_420); // Allocate output frame of max size Picture real = decoder.decodeFrame(bb, out.getData()); BufferedImage bi = JCodecUtil.toBufferedImage(real); // If you prefere AWT image 

如果你想从容器(如MP4)中读取一个from,你可以使用一个方便的帮助类FrameGrab:

 int frameNumber = 150; BufferedImage frame = FrameGrab.getFrame(new File("filename.mp4"), frameNumber); ImageIO.write(frame, "png", new File("frame_150.png")); 

最后,这是一个完整复杂的样本:

 private static void avc2png(String in, String out) throws IOException { SeekableByteChannel sink = null; SeekableByteChannel source = null; try { source = readableFileChannel(in); sink = writableFileChannel(out); MP4Demuxer demux = new MP4Demuxer(source); H264Decoder decoder = new H264Decoder(); Transform transform = new Yuv420pToRgb(0, 0); MP4DemuxerTrack inTrack = demux.getVideoTrack(); VideoSampleEntry ine = (VideoSampleEntry) inTrack.getSampleEntries()[0]; Picture target1 = Picture.create((ine.getWidth() + 15) & ~0xf, (ine.getHeight() + 15) & ~0xf, ColorSpace.YUV420); Picture rgb = Picture.create(ine.getWidth(), ine.getHeight(), ColorSpace.RGB); ByteBuffer _out = ByteBuffer.allocate(ine.getWidth() * ine.getHeight() * 6); BufferedImage bi = new BufferedImage(ine.getWidth(), ine.getHeight(), BufferedImage.TYPE_3BYTE_BGR); AvcCBox avcC = Box.as(AvcCBox.class, Box.findFirst(ine, LeafBox.class, "avcC")); decoder.addSps(avcC.getSpsList()); decoder.addPps(avcC.getPpsList()); Packet inFrame; int totalFrames = (int) inTrack.getFrameCount(); for (int i = 0; (inFrame = inTrack.getFrames(1)) != null; i++) { ByteBuffer data = inFrame.getData(); Picture dec = decoder.decodeFrame(splitMOVPacket(data, avcC), target1.getData()); transform.transform(dec, rgb); _out.clear(); AWTUtil.toBufferedImage(rgb, bi); ImageIO.write(bi, "png", new File(format(out, i))); if (i % 100 == 0) System.out.println((i * 100 / totalFrames) + "%"); } } finally { if (sink != null) sink.close(); if (source != null) source.close(); } } 

我刚试过JCodec( http://jcodec.org )。 这是不可接受的缓慢。

  • 测试环境:CPU:酷睿i7 Q740 @ 1.73G,操作系统:Windows 7 64位,Java 1.7.0_25 32位
  • 测试video:Galaxy S3 mobile拍摄的1080p MPEG-4video
  • 测试结果:平均帧提取时间: 每帧760ms

测试代码:

 FrameGrab grab = new FrameGrab(new FileChannelWrapper(new RandomAccessFile("test.mp4", "r").getChannel())); long time = System.currentTimeMillis(); for (int i = 0; i < 50; i++) { grab.getFrame(); } System.out.println("Time Used:" + (System.currentTimeMillis() - time)); 

看一下Java Media Framework(JMF) – http://java.sun.com/javase/technologies/desktop/media/jmf/2.1.1/formats.html

我曾经使用它一段时间,它有点不成熟,但从那时起它们可能已经加强了它。

我发现了一个基于JavaCV的FFmpegFrameGrabber类的非常简单直接的解决方案,它允许您通过将ffmpeg包装在Java中来播放流媒体。

这里有一个StreamingClient类,它调用一个具有Thread来播放video的SimplePlayer类。

 public class StreamingClient extends Application implements GrabberListener { public static void main(String[] args) { launch(args); } private Stage primaryStage; private ImageView imageView; private SimplePlayer simplePlayer; @Override public void start(Stage stage) throws Exception { String source = "rtsp://184.72.239.149/vod/mp4:BigBuckBunny_115k.mov"; // the video is weird for 1 minute then becomes stable primaryStage = stage; imageView = new ImageView(); StackPane root = new StackPane(); root.getChildren().add(imageView); imageView.fitWidthProperty().bind(primaryStage.widthProperty()); imageView.fitHeightProperty().bind(primaryStage.heightProperty()); Scene scene = new Scene(root, 640, 480); primaryStage.setTitle("Streaming Player"); primaryStage.setScene(scene); primaryStage.show(); simplePlayer = new SimplePlayer(source, this); } @Override public void onMediaGrabbed(int width, int height) { primaryStage.setWidth(width); primaryStage.setHeight(height); } @Override public void onImageProcessed(Image image) { LogHelper.e(TAG, "image: " + image); Platform.runLater(() -> { imageView.setImage(image); }); } @Override public void onPlaying() {} @Override public void onGainControl(FloatControl gainControl) {} @Override public void stop() throws Exception { simplePlayer.stop(); } } 

SimplePlayer类使用FFmpegFrameGrabber来解码转换为图像并在舞台中显示的frame

 public class SimplePlayer { private static volatile Thread playThread; private AnimationTimer timer; private SourceDataLine soundLine; private int counter; public SimplePlayer(String source, GrabberListener grabberListener) { if (grabberListener == null) return; if (source.isEmpty()) return; counter = 0; playThread = new Thread(() -> { try { FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(source); grabber.start(); grabberListener.onMediaGrabbed(grabber.getImageWidth(), grabber.getImageHeight()); if (grabber.getSampleRate() > 0 && grabber.getAudioChannels() > 0) { AudioFormat audioFormat = new AudioFormat(grabber.getSampleRate(), 16, grabber.getAudioChannels(), true, true); DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat); soundLine = (SourceDataLine) AudioSystem.getLine(info); soundLine.open(audioFormat); soundLine.start(); } Java2DFrameConverter converter = new Java2DFrameConverter(); while (!Thread.interrupted()) { Frame frame = grabber.grab(); if (frame == null) { break; } if (frame.image != null) { Image image = SwingFXUtils.toFXImage(converter.convert(frame), null); Platform.runLater(() -> { grabberListener.onImageProcessed(image); }); } else if (frame.samples != null) { ShortBuffer channelSamplesFloatBuffer = (ShortBuffer) frame.samples[0]; channelSamplesFloatBuffer.rewind(); ByteBuffer outBuffer = ByteBuffer.allocate(channelSamplesFloatBuffer.capacity() * 2); for (int i = 0; i < channelSamplesFloatBuffer.capacity(); i++) { short val = channelSamplesFloatBuffer.get(i); outBuffer.putShort(val); } } } grabber.stop(); grabber.release(); Platform.exit(); } catch (Exception exception) { System.exit(1); } }); playThread.start(); } public void stop() { playThread.interrupt(); } }