From b1c95f1fa838a9e3b11084095e11cb237682fbee Mon Sep 17 00:00:00 2001 From: Samuel Audet Date: Sun, 14 May 2023 22:05:49 +0900 Subject: [PATCH] * Add `FrameRecorder.videoSideData/audioSideData` properties and `FFmpegFrameRecorder.setDisplayRotation()` for convenience (issue #1976) --- CHANGELOG.md | 1 + .../org/bytedeco/javacv/FrameGrabberTest.java | 4 +- .../bytedeco/javacv/FFmpegFrameRecorder.java | 48 ++++++++++++++++++- .../org/bytedeco/javacv/FrameRecorder.java | 33 ++++++++++++- 4 files changed, 83 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2aad287..76aaa2eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ + * Add `FrameRecorder.videoSideData/audioSideData` properties and `FFmpegFrameRecorder.setDisplayRotation()` for convenience ([issue #1976](https://github.com/bytedeco/javacv/issues/1976)) * Fix `FFmpegFrameGrabber.grab()` not returning audio frames buffered by the codec ([issue #1971](https://github.com/bytedeco/javacv/issues/1971)) * Upgrade dependencies for OpenBLAS 0.3.23, OpenCV 4.7.0, FFmpeg 6.0 ([issue #1693](https://github.com/bytedeco/javacv/issues/1693)), librealsense2 2.53.1, Leptonica 1.83.0, Tesseract 5.3.1 diff --git a/platform/src/test/java/org/bytedeco/javacv/FrameGrabberTest.java b/platform/src/test/java/org/bytedeco/javacv/FrameGrabberTest.java index d3d1e3ac..e591f3a3 100644 --- a/platform/src/test/java/org/bytedeco/javacv/FrameGrabberTest.java +++ b/platform/src/test/java/org/bytedeco/javacv/FrameGrabberTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2017 Samuel Audet + * Copyright (C) 2016-2023 Samuel Audet * * Licensed either under the Apache License, Version 2.0, or (at your option) * under the terms of the GNU General Public License as published by @@ -318,6 +318,7 @@ public void testFFmpegFrameGrabberSeeking() throws IOException { recorder.setSampleFormat(AV_SAMPLE_FMT_FLTP); recorder.setAudioCodec(AV_CODEC_ID_AAC); recorder.setAudioQuality(0); + recorder.setDisplayRotation((seektestnum - 2) * 90.0); recorder.start(); if (seektestnum!=2) { Frame frame = new Frame(640, 480, Frame.DEPTH_UBYTE, 3); @@ -361,6 +362,7 @@ public void testFFmpegFrameGrabberSeeking() throws IOException { FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(tempFile); grabber.setVideoOption("threads", "1"); // more precise without threads grabber.start(); + assertEquals((seektestnum - 2) * 90.0, grabber.getDisplayRotation(), 0); int length = (int) ( grabber.getLengthInTime() - 1000000L); diff --git a/src/main/java/org/bytedeco/javacv/FFmpegFrameRecorder.java b/src/main/java/org/bytedeco/javacv/FFmpegFrameRecorder.java index 92115a9f..6b4c9677 100644 --- a/src/main/java/org/bytedeco/javacv/FFmpegFrameRecorder.java +++ b/src/main/java/org/bytedeco/javacv/FFmpegFrameRecorder.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2022 Samuel Audet + * Copyright (C) 2009-2023 Samuel Audet * * Licensed either under the Apache License, Version 2.0, or (at your option) * under the terms of the GNU General Public License as published by @@ -206,6 +206,10 @@ public void release() throws Exception { public synchronized void releaseUnsafe() throws Exception { started = false; + if (display_matrix != null) { + display_matrix.releaseReference(); + } + if (plane_ptr != null && plane_ptr2 != null) { plane_ptr.releaseReference(); plane_ptr2.releaseReference(); @@ -388,6 +392,7 @@ static class SeekCallback extends Seek_Pointer_long_int { private AVPacket video_pkt, audio_pkt; private int[] got_video_packet, got_audio_packet; private AVFormatContext ifmt_ctx; + private IntPointer display_matrix; private volatile boolean started = false; @@ -398,6 +403,15 @@ public void setCloseOutputStream(boolean closeOutputStream) { this.closeOutputStream = closeOutputStream; } + /** Sets the rotation in degrees to the side data of the video stream. */ + public void setDisplayRotation(double angle) { + if (display_matrix == null) { + display_matrix = new IntPointer(9).retainReference(); + } + av_display_rotation_set(display_matrix, -angle); + setVideoSideData("Display Matrix", display_matrix.asByteBuffer()); + } + @Override public int getFrameNumber() { return picture == null ? super.getFrameNumber() : (int)picture.pts(); } @@ -821,6 +835,22 @@ public synchronized void startUnsafe() throws Exception { av_dict_set(metadata, new BytePointer(e.getKey(), charset), new BytePointer(e.getValue(), charset), 0); } video_st.metadata(metadata); + + for (Entry e : videoSideData.entrySet()) { + int type = -1; + for (int i = 0; i < AV_PKT_DATA_NB; i++) { + BytePointer s = av_packet_side_data_name(i); + if (s != null && !s.isNull() && e.getKey().equals(s.getString())) { + type = i; + break; + } + } + Pointer p = new Pointer(e.getValue()); + BytePointer b = av_stream_new_side_data(video_st, type, p.capacity()); + if (b != null && !b.isNull()) { + b.capacity(p.capacity()).put(p); + } + } } if (audio_st != null && inpAudioStream == null) { @@ -892,6 +922,22 @@ public synchronized void startUnsafe() throws Exception { av_dict_set(metadata, new BytePointer(e.getKey(), charset), new BytePointer(e.getValue(), charset), 0); } audio_st.metadata(metadata); + + for (Entry e : audioSideData.entrySet()) { + int type = -1; + for (int i = 0; i < AV_PKT_DATA_NB; i++) { + BytePointer s = av_packet_side_data_name(i); + if (s != null && !s.isNull() && e.getKey().equals(s.getString())) { + type = i; + break; + } + } + Pointer p = new Pointer(e.getValue()); + BytePointer b = av_stream_new_side_data(audio_st, type, p.capacity()); + if (b != null && !b.isNull()) { + b.capacity(p.capacity()).put(p); + } + } } AVDictionary options = new AVDictionary(null); diff --git a/src/main/java/org/bytedeco/javacv/FrameRecorder.java b/src/main/java/org/bytedeco/javacv/FrameRecorder.java index 49e3b1f0..f1d932db 100644 --- a/src/main/java/org/bytedeco/javacv/FrameRecorder.java +++ b/src/main/java/org/bytedeco/javacv/FrameRecorder.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2018 Samuel Audet + * Copyright (C) 2009-2023 Samuel Audet * * Licensed either under the Apache License, Version 2.0, or (at your option) * under the terms of the GNU General Public License as published by @@ -26,6 +26,7 @@ import java.io.File; import java.io.IOException; import java.lang.reflect.InvocationTargetException; +import java.nio.Buffer; import java.nio.charset.Charset; import java.util.Arrays; import java.util.HashMap; @@ -119,6 +120,8 @@ public static FrameRecorder create(String className, String filename, int width, protected Map metadata = new HashMap(); protected Map videoMetadata = new HashMap(); protected Map audioMetadata = new HashMap(); + protected Map videoSideData = new HashMap(); + protected Map audioSideData = new HashMap(); protected int frameNumber = 0; protected long timestamp = 0; protected int maxBFrames = -1; @@ -356,6 +359,34 @@ public void setAudioMetadata(String key, String value) { audioMetadata.put(key, value); } + public Map getVideoSideData() { + return videoSideData; + } + public void setVideoSideData(Map videoSideData) { + this.videoSideData = videoSideData; + } + + public Buffer getVideoSideData(String key) { + return videoSideData.get(key); + } + public void setVideoSideData(String key, Buffer value) { + videoSideData.put(key, value); + } + + public Map getAudioSideData() { + return audioSideData; + } + public void setAudioSideData(Map audioSideData) { + this.audioSideData = audioSideData; + } + + public Buffer getAudioSideData(String key) { + return audioSideData.get(key); + } + public void setAudioSideData(String key, Buffer value) { + audioSideData.put(key, value); + } + public int getFrameNumber() { return frameNumber; }