/**
* 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 com.jogamp.opengl.GL;
import com.jogamp.opengl.GL2ES2;
import com.jogamp.opengl.GLException;
import com.jogamp.common.util.IOUtil;
import com.jogamp.common.util.PropertyAccess;
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.common.os.PlatformPropsImpl;
import jogamp.opengl.GLContextImpl;
import jogamp.opengl.util.av.AudioSampleFormat;
import jogamp.opengl.util.av.GLMediaPlayerImpl;
import jogamp.opengl.util.av.VideoPixelFormat;
/***
* 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.
*
* 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:
*
*
*/
public class FFMPEGMediaPlayer extends GLMediaPlayerImpl {
/**
* Defaults to {@code true} for now.
* However, in case we ship our own ffmpeg library this may change.
*
* Property {@code jogl.ffmpeg.lib} set to {@code internal}
* will set {@code PREFER_SYSTEM_LIBS} to {@code false}.
*
*
* Non system internal libraries are named 'internal_',
* e.g. 'internal_avutil'.
*
*
* System default libraries are named '',
* e.g. 'avutil'.
*
*
* If {@code PREFER_SYSTEM_LIBS} is {@code true} (default),
* we lookup the default library first,
* then the versioned library names and last the internal library.
*
*
* If {@code PREFER_SYSTEM_LIBS} is {@code false},
* we lookup the internal library first,
* then the versioned library names and last the default library.
*
*/
/* pp */ static final boolean PREFER_SYSTEM_LIBS;
/** 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;
private static final boolean enableAvResample;
private static final boolean enableSwResample;
static {
// PREFER_SYSTEM_LIBS default on all systems is true for now!
final String choice = PropertyAccess.getProperty("jogl.ffmpeg.lib", true);
PREFER_SYSTEM_LIBS = null == choice || !choice.equals("internal");
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();
if( DEBUG ) {
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"));
}
final int avCodecMajor = avCodecVersion.getMajor();
final int avFormatMajor = avFormatVersion.getMajor();
final int avUtilMajor = avUtilVersion.getMajor();
libAVVersionGood = avCodecMajorVersionCC == avCodecMajor &&
avFormatMajorVersionCC == avFormatMajor &&
( avUtilMajorVersionCC == avUtilMajor ||
55 == avCodecMajorVersionCC && 53 == avUtilMajorVersionCC && 52 == avUtilMajor /* ffmpeg 2.x */
);
enableAvResample = avResampleLoaded && avResampleMajorVersionCC == avResampleVersion.getMajor();
enableSwResample = swResampleLoaded && swResampleMajorVersionCC == swResampleVersion.getMajor();
if( DEBUG ) {
System.err.println("LIB_AV Resample: enabled "+enableAvResample);
System.err.println("LIB_SW Resample: enabled "+enableSwResample);
}
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;
enableAvResample = false;
enableSwResample = 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 VideoPixelFormat 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 final 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, enableAvResample, enableSwResample, DEBUG_NATIVE);
if(0==moviePtr) {
throw new GLException("Couldn't create FFMPEGInstance");
}
psm = new GLPixelStorageModes();
audioSink = null;
}
@Override
protected final void destroyImpl(final 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(final int vid, final 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.getUriFilePathOrASCII(getUri());
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(PlatformPropsImpl.OS_TYPE) {
case ANDROID:
// ??
case FREEBSD:
case HPUX:
case LINUX:
case SUNOS:
resStreamLocS = dev_video_linux + cameraPath.decode();
break;
case WINDOWS:
case MACOS:
case OPENKODE:
default:
resStreamLocS = cameraPath.decode();
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
if(DEBUG) {
System.err.println("initStream: p3 cameraPath "+cameraPath+", isCameraInput "+isCameraInput);
System.err.println("initStream: p3 stream "+getUri()+" -> "+streamLocS+" -> "+resStreamLocS);
System.err.println("initStream: p3 vid "+vid+", sizes "+sizes+", reqVideo "+rw+"x"+rh+"@"+rr+", aid "+aid+", aMaxChannelCount "+aMaxChannelCount+", aPrefSampleRate "+aPrefSampleRate);
}
natives.setStream0(moviePtr, resStreamLocS, isCameraInput, vid, sizes, rw, rh, rr, aid, aMaxChannelCount, aPrefSampleRate);
}
@Override
protected final void initGLImpl(final 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 != getVID() ) {
final GLContextImpl ctx = (GLContextImpl)gl.getContext();
AccessController.doPrivileged(new PrivilegedAction