Как декодировать видеокадр H.264 в среде Java - PullRequest
19 голосов
/ 30 января 2010

Кто-нибудь знает, как декодировать видеокадр H.264 в среде Java?

Мои сетевые камеры поддерживают потоковую передачу RTP / RTSP.

Сервисный стандарт RTP / RTSP от моей сетевой камеры обслуживается, и он также поддерживает «RTP / RTSP over HTTP».

RTSP: TCP 554 Начальный порт RTP: UDP 5000

Ответы [ 5 ]

6 голосов
/ 03 февраля 2010

Или используйте Xuggler . Работает с RTP, RTMP, HTTP или другими протоколами и может декодировать и кодировать H264 и большинство других кодеков. И активно поддерживается, бесплатно и с открытым исходным кодом (LGPL).

4 голосов
/ 14 июня 2014

Я думаю, что лучшим решением является использование "JNI + ffmpeg". В моем текущем проекте мне нужно воспроизвести несколько полноэкранных видео одновременно в java-игре openGL, основанной на libgdx. Я перепробовал почти все бесплатные библиотеки, но ни один из них не имеет приемлемой производительности. Итак, в конце концов я решил написать свои собственные коды jni C для работы с ffmpeg. Вот окончательный результат на моем ноутбуке:

  • Среда: процессор: Core i7 Q740 @ 1.73G, видео: nVidia GeForce GT 435M, ОС: Windows 7 64bit, Java: Java7u60 64bit
  • Видео: h264rgb / h264, без звука, разрешение: 1366 * 768
  • Решение: Расшифровать: JNI + ffmpeg v2.2.2, загрузить на графический процессор: обновить текстуру openGL, используя lwjgl
  • Производительность: Декодирование Скорость: 700-800FPS , текстура Загрузка : около 1 мс на кадр .

Я потратил всего несколько дней, чтобы завершить первую версию. Но скорость декодирования первой версии составляла всего около 120 кадров в секунду, а время загрузки составляло около 5 мс на кадр. После нескольких месяцев оптимизации я получил это окончательное исполнение и некоторые дополнительные функции. Теперь я могу воспроизводить несколько HD-видео одновременно без каких-либо замедлений.

Большинство видео в моей игре имеют прозрачный фон. Этот вид прозрачного видео представляет собой файл mp4 с 2 видеопотоками, один поток хранит кодированные данные h264rgb rgb, другой поток хранит кодированные данные h264 альфа. Поэтому для воспроизведения альфа-видео мне нужно декодировать 2 видеопотока, объединить их вместе и затем загрузить в графический процессор. В результате в моей игре можно одновременно воспроизводить несколько прозрачных HD-видео поверх непрозрачного HD-видео.

3 голосов
/ 15 марта 2018

Я нашел очень простое и понятное решение, основанное на классе JavaFV FFmpegFrameGrabber . Эта библиотека позволяет воспроизводить потоковое мультимедиа, поместив ffmpeg в Java.

Как это использовать?

Сначала вы можете загрузить и установить библиотеку, используя Maven или Gradle.

Здесь у вас есть класс StreamingClient, который вызывает класс 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();
    }
}
2 голосов
/ 13 мая 2013

Вы можете использовать чистую библиотеку Java под названием JCodec (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), вы можете использовать удобный вспомогательный класс 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();
    }
}
0 голосов
/ 30 января 2010

Посмотрите на Java Media Framework (JMF) - http://java.sun.com/javase/technologies/desktop/media/jmf/2.1.1/formats.html

Я использовал это некоторое время назад, и это было немного незрелым, но они, возможно, усилили это с тех пор.

...