From 248256fc8eee90f8d11f66d4b5dba8ad296653a1 Mon Sep 17 00:00:00 2001 From: Sven Gothel Date: Mon, 26 Aug 2013 10:09:33 +0200 Subject: libav/ffmpeg: version9: Add libavresample support ; Proper AudioFormat negotiation w/ AudioSink; Misc - Add libavresample support - Resample if avail && (!AV_SAMPLE_FMT_S16 || !prefSampleRate || !sinkSupported) - Resample to: prefSampleRate (if set), AV_SAMPLE_FMT_S16 and min(channelCount, maxChannelCount) - Proper AudioFormat negotiation w/ AudioSink; - Utilize AudioSink's 'isSupported(AudioFormat)' - Misc - use 'av_get_bytes_per_sample(fmt)' always, don't assume 2 --- .../av/impl/FFMPEGDynamicLibraryBundleInfo.java | 77 ++++++--- .../opengl/util/av/impl/FFMPEGMediaPlayer.java | 174 ++++++++++++++------- 2 files changed, 171 insertions(+), 80 deletions(-) (limited to 'src/jogl/classes/jogamp/opengl/util') diff --git a/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGDynamicLibraryBundleInfo.java b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGDynamicLibraryBundleInfo.java index b0181bd7d..2f92f9bf3 100644 --- a/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGDynamicLibraryBundleInfo.java +++ b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGDynamicLibraryBundleInfo.java @@ -59,12 +59,13 @@ class FFMPEGDynamicLibraryBundleInfo implements DynamicLibraryBundleInfo { private static final List glueLibNames = new ArrayList(); // none - private static final int symbolCount = 43; + private static final int symbolCount = 51; private static final String[] symbolNames = { "avcodec_version", "avformat_version", -/* 3 */ "avutil_version", - + "avutil_version", +/* 4 */ "avresample_version", + // libavcodec "avcodec_close", "avcodec_string", @@ -83,15 +84,17 @@ class FFMPEGDynamicLibraryBundleInfo implements DynamicLibraryBundleInfo { "av_free_packet", "avcodec_decode_audio4", // 53.25.0 (opt) "avcodec_decode_audio3", // 52.23.0 -/* 21 */ "avcodec_decode_video2", // 52.23.0 +/* 22 */ "avcodec_decode_video2", // 52.23.0 // libavutil "av_pix_fmt_descriptors", "av_frame_unref", // 55.0.0 (opt) "av_free", "av_get_bits_per_pixel", -/* 26 */ "av_samples_get_buffer_size", - + "av_samples_get_buffer_size", + "av_get_bytes_per_sample", // 51.4.0 +/* 29 */ "av_opt_set_int", // 51.12.0 + // libavformat "avformat_alloc_context", "avformat_free_context", // 52.96.0 (opt) @@ -109,7 +112,14 @@ class FFMPEGDynamicLibraryBundleInfo implements DynamicLibraryBundleInfo { "avformat_network_init", // 53.13.0 (opt) "avformat_network_deinit", // 53.13.0 (opt) "avformat_find_stream_info", // 53.3.0 (opt) -/* 43 */ "av_find_stream_info", +/* 46 */ "av_find_stream_info", + + // libavresample + "avresample_alloc_context", // 1.0.1 + "avresample_open", + "avresample_close", + "avresample_free", +/* 51 */ "avresample_convert" }; // alternate symbol names @@ -128,23 +138,34 @@ class FFMPEGDynamicLibraryBundleInfo implements DynamicLibraryBundleInfo { "avformat_seek_file", // ??? (opt) "avcodec_free_frame", // 54.28.0 (opt) "av_frame_unref", // 55.0.0 (opt) + + // libavresample + "avresample_version", // 1.0.1 + "avresample_alloc_context", // 1.0.1 + "avresample_open", + "avresample_close", + "avresample_free", + "avresample_convert", }; private static long[] symbolAddr; private static final boolean ready; private static final boolean libsLoaded; + private static final boolean avresampleLoaded; // optional static { // native ffmpeg media player implementation is included in jogl_desktop and jogl_mobile GLProfile.initSingleton(); boolean _ready = false; boolean[] _libsLoaded= { false }; + boolean[] _avresampleLoaded= { false }; try { - _ready = initSymbols(_libsLoaded); + _ready = initSymbols(_libsLoaded, _avresampleLoaded); } catch (Throwable t) { t.printStackTrace(); } libsLoaded = _libsLoaded[0]; + avresampleLoaded = _avresampleLoaded[0]; ready = _ready; if(!libsLoaded) { System.err.println("LIB_AV Not Available"); @@ -154,9 +175,10 @@ class FFMPEGDynamicLibraryBundleInfo implements DynamicLibraryBundleInfo { } static boolean libsLoaded() { return libsLoaded; } + static boolean avResampleLoaded() { return avresampleLoaded; } static boolean initSingleton() { return ready; } - private static final boolean initSymbols(boolean[] libsLoaded) { + private static final boolean initSymbols(boolean[] libsLoaded, boolean[] avresampleLoaded) { libsLoaded[0] = false; final DynamicLibraryBundle dl = AccessController.doPrivileged(new PrivilegedAction() { public DynamicLibraryBundle run() { @@ -167,10 +189,12 @@ class FFMPEGDynamicLibraryBundleInfo implements DynamicLibraryBundleInfo { final boolean avcodecLoaded = dl.isToolLibLoaded(2); if(!avutilLoaded || !avformatLoaded || !avcodecLoaded) { throw new RuntimeException("FFMPEG Tool library incomplete: [ avutil "+avutilLoaded+", avformat "+avformatLoaded+", avcodec "+avcodecLoaded+"]"); - } + } + avresampleLoaded[0] = dl.isToolLibLoaded(3); + /** Ignore .. due to optional libavresample if(!dl.isToolLibComplete()) { throw new RuntimeException("FFMPEG Tool libraries incomplete"); - } + } */ libsLoaded[0] = true; if(symbolNames.length != symbolCount) { throw new InternalError("XXX0 "+symbolNames.length+" != "+symbolCount); @@ -205,6 +229,7 @@ class FFMPEGDynamicLibraryBundleInfo implements DynamicLibraryBundleInfo { } } ); // validate results + boolean res = true; for(int i = 0; i: not optional, no alternatives."); - return false; + res = false; } } else if(DEBUG) { System.err.println("OK: Unresolved optional symbol <"+symbolNames[i]+">"); } } } - return initSymbols0(symbolAddr, symbolCount); + return initSymbols0(symbolAddr, symbolCount) && res; } protected FFMPEGDynamicLibraryBundleInfo() { @@ -265,11 +290,13 @@ class FFMPEGDynamicLibraryBundleInfo implements DynamicLibraryBundleInfo { final List avutil = new ArrayList(); avutil.add("avutil"); // default - avutil.add("libavutil.so.52"); // dummy future proof + avutil.add("libavutil.so.53"); // dummy future proof + avutil.add("libavutil.so.52"); // 9 avutil.add("libavutil.so.51"); // 0.8 avutil.add("libavutil.so.50"); // 0.7 - avutil.add("avutil-52"); // dummy future proof + avutil.add("avutil-53"); // dummy future proof + avutil.add("avutil-52"); // 9 avutil.add("avutil-51"); // 0.8 avutil.add("avutil-50"); // 0.7 libsList.add(avutil); @@ -278,12 +305,12 @@ class FFMPEGDynamicLibraryBundleInfo implements DynamicLibraryBundleInfo { avformat.add("avformat"); // default avformat.add("libavformat.so.55"); // dummy future proof - avformat.add("libavformat.so.54"); // 0.? + avformat.add("libavformat.so.54"); // 9 avformat.add("libavformat.so.53"); // 0.8 avformat.add("libavformat.so.52"); // 0.7 avformat.add("avformat-55"); // dummy future proof - avformat.add("avformat-54"); // 0.? + avformat.add("avformat-54"); // 9 avformat.add("avformat-53"); // 0.8 avformat.add("avformat-52"); // 0.7 libsList.add(avformat); @@ -292,16 +319,26 @@ class FFMPEGDynamicLibraryBundleInfo implements DynamicLibraryBundleInfo { avcodec.add("avcodec"); // default avcodec.add("libavcodec.so.55"); // dummy future proof - avcodec.add("libavcodec.so.54"); // 0.? + avcodec.add("libavcodec.so.54"); // 9 avcodec.add("libavcodec.so.53"); // 0.8 avcodec.add("libavcodec.so.52"); // 0.7 avcodec.add("avcodec-55"); // dummy future proof - avcodec.add("avcodec-54"); // 0.? + avcodec.add("avcodec-54"); // 9 avcodec.add("avcodec-53"); // 0.8 avcodec.add("avcodec-52"); // 0.7 libsList.add(avcodec); - + + final List avresample = new ArrayList(); + avresample.add("avresample"); // default + + avresample.add("libavresample.so.2"); // dummy future proof + avresample.add("libavresample.so.1"); // 9 + + avresample.add("avresample-2"); // dummy future proof + avresample.add("avresample-1"); // 9 + libsList.add(avresample); + return libsList; } diff --git a/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java index 78f5954e5..80b946456 100644 --- a/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java +++ b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java @@ -44,8 +44,7 @@ import com.jogamp.gluegen.runtime.ProcAddressTable; import com.jogamp.opengl.util.TimeFrameI; import com.jogamp.opengl.util.GLPixelStorageModes; import com.jogamp.opengl.util.av.AudioSink; -import com.jogamp.opengl.util.av.AudioSink.AudioDataFormat; -import com.jogamp.opengl.util.av.AudioSink.AudioDataType; +import com.jogamp.opengl.util.av.AudioSink.AudioFormat; import com.jogamp.opengl.util.av.AudioSinkFactory; import com.jogamp.opengl.util.av.GLMediaPlayer; import com.jogamp.opengl.util.texture.Texture; @@ -117,9 +116,11 @@ public class FFMPEGMediaPlayer extends GLMediaPlayerImpl { private static final int avUtilMajorVersionCC; private static final int avFormatMajorVersionCC; private static final int avCodecMajorVersionCC; + private static final int avResampleMajorVersionCC; private static final VersionNumber avUtilVersion; private static final VersionNumber avFormatVersion; private static final VersionNumber avCodecVersion; + private static final VersionNumber avResampleVersion; private static final boolean available; static { @@ -129,15 +130,19 @@ public class FFMPEGMediaPlayer extends GLMediaPlayerImpl { avUtilMajorVersionCC = getAvUtilMajorVersionCC0(); avFormatMajorVersionCC = getAvFormatMajorVersionCC0(); avCodecMajorVersionCC = getAvCodecMajorVersionCC0(); + avResampleMajorVersionCC = getAvResampleMajorVersionCC0(); avUtilVersion = getAVVersion(getAvUtilVersion0()); avFormatVersion = getAVVersion(getAvFormatVersion0()); avCodecVersion = getAVVersion(getAvCodecVersion0()); - System.err.println("LIB_AV Util : "+avUtilVersion+" [cc "+avUtilMajorVersionCC+"]"); - System.err.println("LIB_AV Format: "+avFormatVersion+" [cc "+avFormatMajorVersionCC+"]"); - System.err.println("LIB_AV Codec : "+avCodecVersion+" [cc "+avCodecMajorVersionCC+"]"); + avResampleVersion = getAVVersion(getAvResampleVersion0()); + System.err.println("LIB_AV Util : "+avUtilVersion+" [cc "+avUtilMajorVersionCC+"]"); + System.err.println("LIB_AV Format : "+avFormatVersion+" [cc "+avFormatMajorVersionCC+"]"); + System.err.println("LIB_AV Codec : "+avCodecVersion+" [cc "+avCodecMajorVersionCC+"]"); + System.err.println("LIB_AV Resample: "+avResampleVersion+" [cc "+avResampleMajorVersionCC+"]"); libAVVersionGood = avUtilMajorVersionCC == avUtilVersion.getMajor() && avFormatMajorVersionCC == avFormatVersion.getMajor() && - avCodecMajorVersionCC == avCodecVersion.getMajor(); + avCodecMajorVersionCC == avCodecVersion.getMajor() && + avResampleMajorVersionCC == avResampleVersion.getMajor(); if( !libAVVersionGood ) { System.err.println("LIB_AV Not Matching Compile-Time / Runtime Major-Version"); } @@ -145,9 +150,11 @@ public class FFMPEGMediaPlayer extends GLMediaPlayerImpl { avUtilMajorVersionCC = 0; avFormatMajorVersionCC = 0; avCodecMajorVersionCC = 0; + avResampleMajorVersionCC = 0; avUtilVersion = null; avFormatVersion = null; avCodecVersion = null; + avResampleVersion = null; libAVVersionGood = false; } available = libAVGood && libAVVersionGood ? initIDs0() : false; @@ -185,9 +192,7 @@ public class FFMPEGMediaPlayer extends GLMediaPlayerImpl { // Audio // - private SampleFormat aSampleFmt = null; - private AudioSink.AudioDataFormat avChosenAudioFormat; - private AudioSink.AudioDataFormat sinkChosenAudioFormat; + private AudioSink.AudioFormat avChosenAudioFormat; private int audioSamplesPerFrameAndChannel = 0; public FFMPEGMediaPlayer() { @@ -237,7 +242,7 @@ public class FFMPEGMediaPlayer extends GLMediaPlayerImpl { } else { audioSink = AudioSinkFactory.createDefault(); } - final AudioDataFormat preferredAudioFormat = audioSink.getPreferredFormat(); + final AudioFormat preferredAudioFormat = audioSink.getPreferredFormat(); if(DEBUG) { System.err.println("initStream: p2 preferred "+preferredAudioFormat+", "+this); } @@ -274,7 +279,9 @@ public class FFMPEGMediaPlayer extends GLMediaPlayerImpl { resStreamLocS = streamLocS; inFormat = null; } - setStream0(moviePtr, resStreamLocS, inFormat, vid, aid, snoopVideoFrameCount, preferredAudioFormat.channelCount, preferredAudioFormat.sampleRate); + final int aMaxChannelCount = audioSink.getMaxSupportedChannels(); + final int aPrefSampleRate = preferredAudioFormat.sampleRate; + setStream0(moviePtr, resStreamLocS, inFormat, vid, aid, snoopVideoFrameCount, aMaxChannelCount, aPrefSampleRate); } @Override @@ -308,20 +315,20 @@ public class FFMPEGMediaPlayer extends GLMediaPlayerImpl { } else { frameDuration = AudioSink.DefaultFrameDuration; } - - sinkChosenAudioFormat = audioSink.init(avChosenAudioFormat, frameDuration, AudioSink.DefaultInitialQueueSize, AudioSink.DefaultQueueGrowAmount, audioQueueLimit); if(DEBUG) { - System.err.println("initGL: p3 avChosen "+avChosenAudioFormat+", chosen "+sinkChosenAudioFormat); + System.err.println("initGL: p3 avChosen "+avChosenAudioFormat); } - if( null == sinkChosenAudioFormat ) { + + final boolean audioSinkOK = audioSink.init(avChosenAudioFormat, frameDuration, AudioSink.DefaultInitialQueueSize, AudioSink.DefaultQueueGrowAmount, audioQueueLimit); + if( !audioSinkOK ) { System.err.println("AudioSink "+audioSink.getClass().getName()+" does not support "+avChosenAudioFormat+", using Null"); audioSink.destroy(); audioSink = AudioSinkFactory.createNull(); - sinkChosenAudioFormat = audioSink.init(avChosenAudioFormat, 0, AudioSink.DefaultInitialQueueSize, AudioSink.DefaultQueueGrowAmount, audioQueueLimit); + audioSink.init(avChosenAudioFormat, 0, AudioSink.DefaultInitialQueueSize, AudioSink.DefaultQueueGrowAmount, audioQueueLimit); } if(DEBUG) { - System.err.println("initGL: p4 chosen "+sinkChosenAudioFormat); - System.err.println("initGL: "+audioSink); + System.err.println("initGL: p4 chosen "+avChosenAudioFormat); + System.err.println("initGL: p4 chosen "+audioSink); } if( null != gl ) { @@ -349,6 +356,84 @@ public class FFMPEGMediaPlayer extends GLMediaPlayerImpl { return new TextureFrame( createTexImageImpl(gl, texName, texWidth, texHeight, true) ); } + /** + * @param sampleRate sample rate in Hz (1/s) + * @param sampleSize sample size in bits + * @param channelCount number of channels + * @param signed true if signed number, false for unsigned + * @param fixedP true for fixed point value, false for unsigned floating point value with a sampleSize of 32 (float) or 64 (double) + * @param planar true for planar data package (each channel in own data buffer), false for packed data channels interleaved in one buffer. + * @param littleEndian true for little-endian, false for big endian + * @return + */ + + /** + * Converts the given libav/ffmpeg values to {@link AudioFormat} and returns {@link AudioSink#isSupported(AudioFormat)}. + * @param audioSampleFmt ffmpeg/libav audio-sample-format, see {@link SampleFormat}. + * @param audioSampleRate sample rate in Hz (1/s) + * @param audioChannels number of channels + */ + private final boolean isAudioFormatSupported(int audioSampleFmt, int audioSampleRate, int audioChannels) { + final AudioFormat audioFormat = avAudioFormat2Local(SampleFormat.valueOf(audioSampleFmt), audioSampleRate, audioChannels); + final boolean res = audioSink.isSupported(audioFormat); + if( DEBUG ) { + System.err.println("AudioSink.isSupported: "+res+": "+audioFormat); + } + return res; + } + + /** + * Returns {@link AudioFormat} as converted from the given libav/ffmpeg values. + * @param audioSampleFmt ffmpeg/libav audio-sample-format, see {@link SampleFormat}. + * @param audioSampleRate sample rate in Hz (1/s) + * @param audioChannels number of channels + */ + private final AudioFormat avAudioFormat2Local(SampleFormat audioSampleFmt, int audioSampleRate, int audioChannels) { + final int sampleSize; + boolean planar = true; + final boolean signed, fixedP; + switch( audioSampleFmt ) { + case S32: + planar = false; + case S32P: + sampleSize = 32; + signed = true; + fixedP = true; + break; + case S16: + planar = false; + case S16P: + sampleSize = 16; + signed = true; + fixedP = true; + break; + case U8: + planar = false; + case U8P: + sampleSize = 8; + signed = false; + fixedP = true; + break; + case DBL: + planar = false; + case DBLP: + sampleSize = 64; + signed = true; + fixedP = true; + break; + case FLT: + planar = false; + case FLTP: + sampleSize = 32; + signed = true; + fixedP = true; + break; + default: // FIXME: Add more formats ! + throw new IllegalArgumentException("Unsupported sampleformat: "+audioSampleFmt); + } + return new AudioFormat(audioSampleRate, sampleSize, audioChannels, signed, fixedP, planar, true /* littleEndian */); + } + /** * @param pixFmt * @param planes @@ -400,44 +485,9 @@ public class FFMPEGMediaPlayer extends GLMediaPlayerImpl { default: // FIXME: Add more formats ! throw new RuntimeException("Unsupported pixelformat: "+vPixelFmt); } - aSampleFmt = SampleFormat.valueOf(audioSampleFmt); - final int sampleSize; - final boolean signed, fixedP; - switch( aSampleFmt ) { - case S32: - case S32P: - sampleSize = 32; - signed = true; - fixedP = true; - break; - case S16: - case S16P: - sampleSize = 16; - signed = true; - fixedP = true; - break; - case U8: - case U8P: - sampleSize = 8; - signed = false; - fixedP = true; - break; - case DBL: - case DBLP: - sampleSize = 64; - signed = true; - fixedP = true; - break; - case FLT: - case FLTP: - sampleSize = 32; - signed = true; - fixedP = true; - break; - default: // FIXME: Add more formats ! - throw new RuntimeException("Unsupported sampleformat: "+aSampleFmt); - } - avChosenAudioFormat = new AudioDataFormat(AudioDataType.PCM, audioSampleRate, sampleSize, audioChannels, signed, fixedP, true /* littleEndian */); + final SampleFormat aSampleFmt = SampleFormat.valueOf(audioSampleFmt); + avChosenAudioFormat = avAudioFormat2Local(aSampleFmt, audioSampleRate, audioChannels); + this.audioSamplesPerFrameAndChannel = audioSamplesPerFrameAndChannel; if(DEBUG) { @@ -587,6 +637,8 @@ public class FFMPEGMediaPlayer extends GLMediaPlayerImpl { private static native int getAvFormatMajorVersionCC0(); private static native int getAvCodecVersion0(); private static native int getAvCodecMajorVersionCC0(); + private static native int getAvResampleVersion0(); + private static native int getAvResampleMajorVersionCC0(); private static native boolean initIDs0(); private native long createInstance0(boolean verbose); private native void destroyInstance0(long moviePtr); @@ -595,7 +647,7 @@ public class FFMPEGMediaPlayer extends GLMediaPlayerImpl { * Issues {@link #updateAttributes(int, int, int, int, int, int, int, float, int, int, String, String)} * and {@link #updateAttributes2(int, int, int, int, int, int, int, int, int, int)}. *

- * Always uses {@link AudioSink.AudioDataFormat}: + * Always uses {@link AudioSink.AudioFormat}: *

      *   [type PCM, sampleRate [10000(?)..44100..48000], sampleSize 16, channelCount 1-2, signed, littleEndian]
      * 
@@ -607,10 +659,10 @@ public class FFMPEGMediaPlayer extends GLMediaPlayerImpl { * @param aid * @param snoopVideoFrameCount snoop this number of video-frames to gather audio-frame-count per video-frame. * If zero, gathering audio-frame-count is disabled! - * @param aChannelCount - * @param aSampleRate + * @param aPrefChannelCount + * @param aPrefSampleRate */ - private native void setStream0(long moviePtr, String url, String inFormat, int vid, int aid, int snoopVideoFrameCount, int aChannelCount, int aSampleRate); + private native void setStream0(long moviePtr, String url, String inFormat, int vid, int aid, int snoopVideoFrameCount, int aMaxChannelCount, int aPrefSampleRate); private native void setGLFuncs0(long moviePtr, long procAddrGLTexSubImage2D, long procAddrGLGetError, long procAddrGLFlush, long procAddrGLFinish); private native int getVideoPTS0(long moviePtr); @@ -627,6 +679,7 @@ public class FFMPEGMediaPlayer extends GLMediaPlayerImpl { private native int pause0(long moviePtr); private native int seek0(long moviePtr, int position); + /** FFMPEG/libAV Audio Sample Format */ public static enum SampleFormat { // NONE = -1, U8, ///< unsigned 8 bits @@ -653,6 +706,7 @@ public class FFMPEGMediaPlayer extends GLMediaPlayerImpl { } }; + /** FFMPEG/libAV Pixel Format */ public static enum PixelFormat { // NONE= -1, YUV420P, ///< planar YUV 4:2:0, 12bpp, (1 Cr & Cb sample per 2x2 Y samples) -- cgit v1.2.3