jogamp.opengl.util.av.impl.FFMPEGMediaPlayer Maven / Gradle / Ivy
/**
* Copyright 2012 JogAmp Community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of JogAmp Community.
*/
package jogamp.opengl.util.av.impl;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.security.AccessController;
import java.security.PrivilegedAction;
import javax.media.opengl.GL;
import javax.media.opengl.GL2ES2;
import javax.media.opengl.GLException;
import com.jogamp.common.os.Platform;
import com.jogamp.common.util.IOUtil;
import com.jogamp.common.util.VersionNumber;
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.AudioFormat;
import com.jogamp.opengl.util.av.AudioSinkFactory;
import com.jogamp.opengl.util.av.GLMediaPlayer;
import com.jogamp.opengl.util.texture.Texture;
import jogamp.opengl.GLContextImpl;
import jogamp.opengl.util.av.GLMediaPlayerImpl;
import jogamp.opengl.util.av.impl.FFMPEGNatives.PixelFormat;
import jogamp.opengl.util.av.impl.FFMPEGNatives.SampleFormat;
/***
* Implementation utilizes Libav
* or FFmpeg which are ubiquitous
* available and usually pre-installed on Unix platforms.
*
* Due to legal reasons we cannot deploy binaries of it, which contains patented codecs.
*
*
* Besides the default BSD/Linux/.. repositories and installations,
* precompiled binaries can be found at the
* listed location below.
*
*
* Implementation specifics
*
* The decoded video frame is written directly into an OpenGL texture
* on the GPU in it's native format. A custom fragment shader converts
* the native pixelformat to a usable RGB format if required.
* Hence only 1 copy is required before bloating the picture
* from YUV* to RGB, for example.
*
*
* Implements pixel format conversion to RGB via
* fragment shader texture-lookup functions:
*
* - {@link PixelFormat#YUV420P}
* - {@link PixelFormat#YUVJ420P}
* - {@link PixelFormat#YUV422P}
* - {@link PixelFormat#YUVJ422P}
* - {@link PixelFormat#YUYV422}
* - {@link PixelFormat#BGR24}
*
*
*
*
* Libav Specifics
*
* Utilizes a slim dynamic and native binding to the Lib_av
* libraries:
*
* - libavcodec
* - libavformat
* - libavutil
* - libavresample (opt)
* - libavdevice (opt)
*
*
*
* LibAV Compatibility
*
* Currently we are binary compatible w/:
*
* libav / ffmpeg lavc lavf lavu lavr FFMPEG* class
* 0.8 53 53 51 FFMPEGv08
* 9.0 / 1.2 54 54 52 01/00 FFMPEGv09
* 10 / 2 55 55 52 01/00 FFMPEGv10
*
*
*
* See http://upstream-tracker.org/versions/libav.html
*
*
* Check tag 'FIXME: Add more planar formats !'
* here and in the corresponding native code
* jogl/src/jogl/native/libav/ffmpeg_impl_template.c
*
*
*
* TODO:
*
*
* - better audio synchronization handling? (video is synchronized)
*
*
*
* FFMPEG / LibAV Availability
*
*
* - GNU/Linux: ffmpeg or libav are deployed in most distributions.
* - Windows:
*
* - http://ffmpeg.zeranoe.com/builds/ (ffmpeg) recommended, works w/ dshow
* - http://win32.libav.org/releases/ (libav)
*
* - MacOSX: http://ffmpegmac.net/
* - OpenIndiana/Solaris:
* pkg set-publisher -p http://pkg.openindiana.org/sfe-encumbered.
* pkt install pkg:/video/ffmpeg
*
*
*
*/
public class FFMPEGMediaPlayer extends GLMediaPlayerImpl {
/** POSIX ENOSYS {@value}: Function not implemented. FIXME: Move to GlueGen ?!*/
private static final int ENOSYS = 38;
// Instance data
private static final FFMPEGNatives natives;
private static final int avUtilMajorVersionCC;
private static final int avFormatMajorVersionCC;
private static final int avCodecMajorVersionCC;
private static final int avResampleMajorVersionCC;
private static final int swResampleMajorVersionCC;
private static final boolean available;
static {
final boolean libAVGood = FFMPEGDynamicLibraryBundleInfo.initSingleton();
final boolean libAVVersionGood;
if( FFMPEGDynamicLibraryBundleInfo.libsLoaded() ) {
natives = FFMPEGDynamicLibraryBundleInfo.getNatives();
if( null != natives ) {
avCodecMajorVersionCC = natives.getAvCodecMajorVersionCC0();
avFormatMajorVersionCC = natives.getAvFormatMajorVersionCC0();
avUtilMajorVersionCC = natives.getAvUtilMajorVersionCC0();
avResampleMajorVersionCC = natives.getAvResampleMajorVersionCC0();
swResampleMajorVersionCC = natives.getSwResampleMajorVersionCC0();
} else {
avUtilMajorVersionCC = 0;
avFormatMajorVersionCC = 0;
avCodecMajorVersionCC = 0;
avResampleMajorVersionCC = 0;
swResampleMajorVersionCC = 0;
}
final VersionNumber avCodecVersion = FFMPEGDynamicLibraryBundleInfo.avCodecVersion;
final VersionNumber avFormatVersion = FFMPEGDynamicLibraryBundleInfo.avFormatVersion;
final VersionNumber avUtilVersion = FFMPEGDynamicLibraryBundleInfo.avUtilVersion;
final VersionNumber avResampleVersion = FFMPEGDynamicLibraryBundleInfo.avResampleVersion;
final boolean avResampleLoaded = FFMPEGDynamicLibraryBundleInfo.avResampleLoaded();
final VersionNumber swResampleVersion = FFMPEGDynamicLibraryBundleInfo.swResampleVersion;
final boolean swResampleLoaded = FFMPEGDynamicLibraryBundleInfo.swResampleLoaded();
System.err.println("LIB_AV Codec : "+avCodecVersion+" [cc "+avCodecMajorVersionCC+"]");
System.err.println("LIB_AV Format : "+avFormatVersion+" [cc "+avFormatMajorVersionCC+"]");
System.err.println("LIB_AV Util : "+avUtilVersion+" [cc "+avUtilMajorVersionCC+"]");
System.err.println("LIB_AV Resample: "+avResampleVersion+" [cc "+avResampleMajorVersionCC+", loaded "+avResampleLoaded+"]");
System.err.println("LIB_SW Resample: "+swResampleVersion+" [cc "+swResampleMajorVersionCC+", loaded "+swResampleLoaded+"]");
System.err.println("LIB_AV Device : [loaded "+FFMPEGDynamicLibraryBundleInfo.avDeviceLoaded()+"]");
System.err.println("LIB_AV Class : "+(null!= natives ? natives.getClass().getSimpleName() : "n/a"));
libAVVersionGood = avCodecMajorVersionCC == avCodecVersion.getMajor() &&
avFormatMajorVersionCC == avFormatVersion.getMajor() &&
avUtilMajorVersionCC == avUtilVersion.getMajor() &&
( !avResampleLoaded || avResampleMajorVersionCC < 0 || avResampleMajorVersionCC == avResampleVersion.getMajor() ) &&
( !swResampleLoaded || swResampleMajorVersionCC < 0 || swResampleMajorVersionCC == swResampleVersion.getMajor() ) ;
if( !libAVVersionGood ) {
System.err.println("LIB_AV Not Matching Compile-Time / Runtime Major-Version");
}
} else {
natives = null;
avUtilMajorVersionCC = 0;
avFormatMajorVersionCC = 0;
avCodecMajorVersionCC = 0;
avResampleMajorVersionCC = 0;
swResampleMajorVersionCC = 0;
libAVVersionGood = false;
}
available = libAVGood && libAVVersionGood && null != natives;
}
public static final boolean isAvailable() { return available; }
//
// General
//
private long moviePtr = 0;
//
// Video
//
private String texLookupFuncName = "ffmpegTexture2D";
private boolean usesTexLookupShader = false;
private PixelFormat vPixelFmt = null;
private int vPlanes = 0;
private int vBitsPerPixel = 0;
private int vBytesPerPixelPerPlane = 0;
private int texWidth, texHeight; // overall (stuffing planes in one texture)
private String singleTexComp = "r";
private GLPixelStorageModes psm;
//
// Audio
//
private AudioSink.AudioFormat avChosenAudioFormat;
private int audioSamplesPerFrameAndChannel = 0;
public FFMPEGMediaPlayer() {
if(!available) {
throw new RuntimeException("FFMPEGMediaPlayer not available");
}
moviePtr = natives.createInstance0(this, DEBUG_NATIVE);
if(0==moviePtr) {
throw new GLException("Couldn't create FFMPEGInstance");
}
psm = new GLPixelStorageModes();
audioSink = null;
}
@Override
protected final void destroyImpl(GL gl) {
if (moviePtr != 0) {
natives.destroyInstance0(moviePtr);
moviePtr = 0;
}
destroyAudioSink();
}
private final void destroyAudioSink() {
final AudioSink _audioSink = audioSink;
if( null != _audioSink ) {
audioSink = null;
_audioSink.destroy();
}
}
public static final String dev_video_linux = "/dev/video";
@Override
protected final void initStreamImpl(int vid, int aid) throws IOException {
if(0==moviePtr) {
throw new GLException("FFMPEG native instance null");
}
if(DEBUG) {
System.err.println("initStream: p1 "+this);
}
final String streamLocS=IOUtil.decodeFromURI(streamLoc.toString());
destroyAudioSink();
if( GLMediaPlayer.STREAM_ID_NONE == aid ) {
audioSink = AudioSinkFactory.createNull();
} else {
audioSink = AudioSinkFactory.createDefault();
}
final AudioFormat preferredAudioFormat = audioSink.getPreferredFormat();
if(DEBUG) {
System.err.println("initStream: p2 preferred "+preferredAudioFormat+", "+this);
}
final boolean isCameraInput = null != cameraPath;
final String resStreamLocS;
// int rw=640, rh=480, rr=15;
int rw=-1, rh=-1, rr=-1;
String sizes = null;
if( isCameraInput ) {
switch(Platform.OS_TYPE) {
case ANDROID:
// ??
case FREEBSD:
case HPUX:
case LINUX:
case SUNOS:
resStreamLocS = dev_video_linux + cameraPath;
break;
case WINDOWS:
resStreamLocS = cameraPath;
break;
case MACOS:
case OPENKODE:
default:
resStreamLocS = streamLocS; // FIXME: ??
break;
}
if( null != cameraProps ) {
sizes = cameraProps.get(CameraPropSizeS);
int v = getPropIntVal(cameraProps, CameraPropWidth);
if( v > 0 ) { rw = v; }
v = getPropIntVal(cameraProps, CameraPropHeight);
if( v > 0 ) { rh = v; }
v = getPropIntVal(cameraProps, CameraPropRate);
if( v > 0 ) { rr = v; }
}
} else {
resStreamLocS = streamLocS;
}
final int aMaxChannelCount = audioSink.getMaxSupportedChannels();
final int aPrefSampleRate = preferredAudioFormat.sampleRate;
// setStream(..) issues updateAttributes*(..), and defines avChosenAudioFormat, vid, aid, .. etc
natives.setStream0(moviePtr, resStreamLocS, isCameraInput, vid, sizes, rw, rh, rr, aid, aMaxChannelCount, aPrefSampleRate);
}
@Override
protected final void initGLImpl(GL gl) throws IOException, GLException {
if(0==moviePtr) {
throw new GLException("FFMPEG native instance null");
}
if(null == audioSink) {
throw new GLException("AudioSink null");
}
final int audioQueueLimit;
if( null != gl && STREAM_ID_NONE != vid ) {
final GLContextImpl ctx = (GLContextImpl)gl.getContext();
AccessController.doPrivileged(new PrivilegedAction