web-dev-qa-db-fra.com

Comment décoder une image vidéo H.264 dans un environnement Java

Est-ce que quelqu'un sait comment décoder une image vidéo H.264 dans un environnement Java?

Mes produits de caméra réseau prennent en charge le streaming RTP/RTSP.

La norme de service RTP/RTSP de la caméra réseau est prise en charge et prend également en charge «RTP/RTSP sur HTTP».

RTSP: TCP 554 Port de départ RTP: UDP 5000

19
user262186

Ou utilisez Xuggler . Fonctionne avec les protocoles RTP, RTMP, HTTP ou autres, et peut décoder et coder H264 et la plupart des autres codecs. Et est activement maintenu, gratuit et open-source (LGPL).

6
Art Clarke

Je pense que la meilleure solution utilise "JNI + ffmpeg". Dans mon projet actuel, je dois lire plusieurs vidéos en plein écran simultanément dans un jeu Java openGL basé sur libgdx. J'ai essayé presque toutes les bibliothèques libres, mais aucune d'entre elles n'a des performances acceptables. Alors finalement, j'ai décidé d'écrire mes propres codes jni C pour travailler avec ffmpeg. Voici la performance finale sur mon ordinateur portable:

  • Environnement: CPU: Core i7 Q740 @ 1.73G, Vidéo: nVidia GeForce GT 435M, OS: Windows 7 64bits, Java: Java7u60 64bits 
  • Vidéo: h264rgb / h264 codé, pas de son, résolution: 1366 * 768  
  • Solution: Décoder: JNI + ffmpeg v2.2.2, télécharger sur le GPU: Mettre à jour la texture openGL à l'aide de lwjgl 
  • Performances: Décodage vitesse: 700-800FPS , Texture Chargement : environ 1 ms par image .

Je n'ai passé que quelques jours pour terminer la première version. Mais la vitesse de décodage de la première version n’était que d’environ 120 images par seconde et le temps de téléchargement était d’environ 5 ms par image. Après plusieurs mois d'optimisation, j'ai obtenu cette performance finale et quelques fonctionnalités supplémentaires. Maintenant, je peux lire plusieurs vidéos HD en même temps sans aucune lenteur. 

La plupart des vidéos de mon jeu ont un fond transparent. Ce type de vidéo transparente est un fichier mp4 avec 2 flux vidéo, un flux stocke des données RVB codées h264rgb, l’autre flux stocke des données alpha codées H264. Donc, pour lire une vidéo alpha, je dois décoder 2 flux vidéo et les fusionner, puis les transférer sur GPU. En conséquence, je peux lire plusieurs vidéos HD transparentes au-dessus d’une vidéo HD opaque en même temps dans mon jeu. 

4
Yao

J'ai trouvé une solution très simple et directe basée sur la classe FFmpegFrameGrabber de JavaCV . Cette bibliothèque vous permet de lire un contenu multimédia en continu en encapsulant ffmpeg en Java.

Comment l'utiliser?

Tout d'abord, vous pouvez télécharger et installer la bibliothèque à l'aide de Maven ou de Gradle.

Ici, vous avez une classe StreamingClient qui appelle une classe SimplePlayer qui a Thread pour lire la vidéo.

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 class utilise FFmpegFrameGrabber pour décoder une frame qui est convertie en image et affichée dans votre scène

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();
    }
}
3
Teocci

Vous pouvez utiliser une bibliothèque Java pure appelée JCodec ( http://jcodec.org ).
Décoder une image H.264 est aussi simple que: 

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

Si vous voulez lire un depuis un conteneur (comme MP4), vous pouvez utiliser une classe d'aide pratique FrameGrab: 

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

Enfin, voici un exemple sophistiqué complet: 

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();
    }
}
3

Jetez un coup d'œil au Java Media Framework (JMF) - http://Java.Sun.com/javase/technologies/desktop/media/jmf/2.1.1/formats.html

Je l'ai utilisé il y a quelque temps et c'était un peu immature, mais ils l'ont peut-être renforcé depuis.

0
ptsw