diff --git a/CHANGELOG.md b/CHANGELOG.md index 3032b6f7..d28c4f24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ + * Give users of `FFmpegFrameGrabber` and `FFmpegFrameRecorder` access to more options and metadata ([issue #132](https://github.com/bytedeco/javacv/issues/132)) * Add the ability to specify from which video and audio streams `FFmpegFrameGrabber` should grab from ([issue #135](https://github.com/bytedeco/javacv/issues/135)) - * Fix `Java2DFrameConverter` when used with `BufferedImage.TYPE_INT_RGB` or other types based on `int` + * Fix `Java2DFrameConverter` when used with `BufferedImage.TYPE_INT_RGB` or other types based on `int` ([issue #140](https://github.com/bytedeco/javacv/issues/140)) * Add new `WebcamAndMicrophoneCapture` sample ([pull #131](https://github.com/bytedeco/javacv/pull/131)) * Add `aspectRatio` property to `FrameGrabber` and `FrameRecorder`, to be able to use pixel aspect ratios other than 1.0 ([issue #90](https://github.com/bytedeco/javacv/issues/90)) diff --git a/src/main/java/org/bytedeco/javacv/FFmpegFrameGrabber.java b/src/main/java/org/bytedeco/javacv/FFmpegFrameGrabber.java index f47269e7..a6ada8d6 100644 --- a/src/main/java/org/bytedeco/javacv/FFmpegFrameGrabber.java +++ b/src/main/java/org/bytedeco/javacv/FFmpegFrameGrabber.java @@ -292,6 +292,30 @@ public void releaseUnsafe() throws Exception { return audio_c == null ? super.getSampleRate() : audio_c.sample_rate(); } + @Override public String getMetadata(String key) { + if (oc == null) { + return super.getMetadata(key); + } + AVDictionaryEntry entry = av_dict_get(oc.metadata(), key, null, 0); + return entry == null || entry.value() == null ? null : entry.value().getString(); + } + + @Override public String getVideoMetadata(String key) { + if (video_st == null) { + return super.getVideoMetadata(key); + } + AVDictionaryEntry entry = av_dict_get(video_st.metadata(), key, null, 0); + return entry == null || entry.value() == null ? null : entry.value().getString(); + } + + @Override public String getAudioMetadata(String key) { + if (audio_st == null) { + return super.getAudioMetadata(key); + } + AVDictionaryEntry entry = av_dict_get(audio_st.metadata(), key, null, 0); + return entry == null || entry.value() == null ? null : entry.value().getString(); + } + @Override public void setFrameNumber(int frameNumber) throws Exception { // best guess, AVSEEK_FLAG_FRAME has not been implemented in FFmpeg... setTimestamp(Math.round(1000000L * frameNumber / getFrameRate())); @@ -440,8 +464,12 @@ public void startUnsafe() throws Exception { throw new Exception("avcodec_find_decoder() error: Unsupported video format or codec not found: " + video_c.codec_id() + "."); } + options = new AVDictionary(null); + for (Entry e : videoOptions.entrySet()) { + av_dict_set(options, e.getKey(), e.getValue(), 0); + } // Open video codec - if ((ret = avcodec_open2(video_c, codec, (PointerPointer)null)) < 0) { + if ((ret = avcodec_open2(video_c, codec, options)) < 0) { throw new Exception("avcodec_open2() error " + ret + ": Could not open video codec."); } @@ -496,8 +524,12 @@ public void startUnsafe() throws Exception { throw new Exception("avcodec_find_decoder() error: Unsupported audio format or codec not found: " + audio_c.codec_id() + "."); } + options = new AVDictionary(null); + for (Entry e : audioOptions.entrySet()) { + av_dict_set(options, e.getKey(), e.getValue(), 0); + } // Open audio codec - if ((ret = avcodec_open2(audio_c, codec, (PointerPointer)null)) < 0) { + if ((ret = avcodec_open2(audio_c, codec, options)) < 0) { throw new Exception("avcodec_open2() error " + ret + ": Could not open audio codec."); } diff --git a/src/main/java/org/bytedeco/javacv/FFmpegFrameRecorder.java b/src/main/java/org/bytedeco/javacv/FFmpegFrameRecorder.java index 0b3c09ad..2ec137f0 100644 --- a/src/main/java/org/bytedeco/javacv/FFmpegFrameRecorder.java +++ b/src/main/java/org/bytedeco/javacv/FFmpegFrameRecorder.java @@ -452,11 +452,16 @@ public void startUnsafe() throws Exception { audio_c.channel_layout(av_get_default_channel_layout(audioChannels)); if (sampleFormat != AV_SAMPLE_FMT_NONE) { audio_c.sample_fmt(sampleFormat); - } else if ((audio_codec.capabilities() & CODEC_CAP_EXPERIMENTAL) != 0 - && (audio_c.codec_id() == AV_CODEC_ID_VORBIS || audio_c.codec_id() == AV_CODEC_ID_AAC)) { - audio_c.sample_fmt(AV_SAMPLE_FMT_FLTP); } else { - audio_c.sample_fmt(AV_SAMPLE_FMT_S16); + // use AV_SAMPLE_FMT_S16 by default, if available + audio_c.sample_fmt(AV_SAMPLE_FMT_FLTP); + IntPointer formats = audio_c.codec().sample_fmts(); + for (int i = 0; formats.get(i) != -1; i++) { + if (formats.get(i) == AV_SAMPLE_FMT_S16) { + audio_c.sample_fmt(AV_SAMPLE_FMT_S16); + break; + } + } } audio_c.time_base().num(1).den(sampleRate); audio_st.time_base().num(1).den(sampleRate); @@ -538,6 +543,12 @@ public void startUnsafe() throws Exception { release(); throw new Exception("av_frame_alloc() error: Could not allocate temporary picture."); } + + AVDictionary metadata = new AVDictionary(null); + for (Entry e : videoMetadata.entrySet()) { + av_dict_set(metadata, e.getKey(), e.getValue(), 0); + } + video_st.metadata(metadata); } if (audio_st != null) { @@ -594,6 +605,12 @@ public void startUnsafe() throws Exception { throw new Exception("av_frame_alloc() error: Could not allocate audio frame."); } frame.pts(0); // magic required by libvorbis and webm + + AVDictionary metadata = new AVDictionary(null); + for (Entry e : audioMetadata.entrySet()) { + av_dict_set(metadata, e.getKey(), e.getValue(), 0); + } + audio_st.metadata(metadata); } /* open the output file, if needed */ @@ -606,8 +623,16 @@ public void startUnsafe() throws Exception { oc.pb(pb); } + AVDictionary options = new AVDictionary(null); + for (Entry e : this.options.entrySet()) { + av_dict_set(options, e.getKey(), e.getValue(), 0); + } + AVDictionary metadata = new AVDictionary(null); + for (Entry e : this.metadata.entrySet()) { + av_dict_set(metadata, e.getKey(), e.getValue(), 0); + } /* write the stream header, if any */ - avformat_write_header(oc, (PointerPointer)null); + avformat_write_header(oc.metadata(metadata), options); } public void stop() throws Exception { diff --git a/src/main/java/org/bytedeco/javacv/FrameGrabber.java b/src/main/java/org/bytedeco/javacv/FrameGrabber.java index 6aea5a58..6606fb3b 100644 --- a/src/main/java/org/bytedeco/javacv/FrameGrabber.java +++ b/src/main/java/org/bytedeco/javacv/FrameGrabber.java @@ -179,6 +179,11 @@ public static enum ImageMode { protected double gamma = 0.0; protected boolean deinterlace = false; protected HashMap options = new HashMap(); + protected HashMap videoOptions = new HashMap(); + protected HashMap audioOptions = new HashMap(); + protected HashMap metadata = new HashMap(); + protected HashMap videoMetadata = new HashMap(); + protected HashMap audioMetadata = new HashMap(); protected int frameNumber = 0; protected long timestamp = 0; @@ -350,6 +355,41 @@ public void setOption(String key, String value) { options.put(key, value); } + public String getVideoOption(String key) { + return videoOptions.get(key); + } + public void setVideoOption(String key, String value) { + videoOptions.put(key, value); + } + + public String getAudioOption(String key) { + return audioOptions.get(key); + } + public void setAudioOption(String key, String value) { + audioOptions.put(key, value); + } + + public String getMetadata(String key) { + return metadata.get(key); + } + public void setMetadata(String key, String value) { + metadata.put(key, value); + } + + public String getVideoMetadata(String key) { + return videoMetadata.get(key); + } + public void setVideoMetadata(String key, String value) { + videoMetadata.put(key, value); + } + + public String getAudioMetadata(String key) { + return audioMetadata.get(key); + } + public void setAudioMetadata(String key, String value) { + audioMetadata.put(key, value); + } + public int getFrameNumber() { return frameNumber; } diff --git a/src/main/java/org/bytedeco/javacv/FrameRecorder.java b/src/main/java/org/bytedeco/javacv/FrameRecorder.java index f50cedee..dcf4f243 100644 --- a/src/main/java/org/bytedeco/javacv/FrameRecorder.java +++ b/src/main/java/org/bytedeco/javacv/FrameRecorder.java @@ -106,8 +106,12 @@ public static FrameRecorder create(String className, String filename, int width, protected int sampleFormat, audioCodec, audioBitrate, sampleRate; protected double audioQuality = -1; protected boolean interleaved; + protected HashMap options = new HashMap(); protected HashMap videoOptions = new HashMap(); protected HashMap audioOptions = new HashMap(); + protected HashMap metadata = new HashMap(); + protected HashMap videoMetadata = new HashMap(); + protected HashMap audioMetadata = new HashMap(); protected int frameNumber = 0; protected long timestamp = 0; @@ -244,6 +248,13 @@ public void setInterleaved(boolean interleaved) { this.interleaved = interleaved; } + public String getOption(String key) { + return options.get(key); + } + public void setOption(String key, String value) { + options.put(key, value); + } + public String getVideoOption(String key) { return videoOptions.get(key); } @@ -258,6 +269,27 @@ public void setAudioOption(String key, String value) { audioOptions.put(key, value); } + public String getMetadata(String key) { + return metadata.get(key); + } + public void setMetadata(String key, String value) { + metadata.put(key, value); + } + + public String getVideoMetadata(String key) { + return videoMetadata.get(key); + } + public void setVideoMetadata(String key, String value) { + videoMetadata.put(key, value); + } + + public String getAudioMetadata(String key) { + return audioMetadata.get(key); + } + public void setAudioMetadata(String key, String value) { + audioMetadata.put(key, value); + } + public int getFrameNumber() { return frameNumber; }