VLC.java
package vlc.android; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.ActivityInfo; import android.opengl.GLSurfaceView; import android.os.Bundle; import android.preference.PreferenceManager; import android.widget.Button; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.Window; import android.view.WindowManager; public class VLC extends Activity { private static final String TAG = "VLC_Activity"; private static VLC sInstance; private LibVLC mLibVlc; private Vout mVout; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); sInstance = this; Log.v(TAG, "Starting VLC media player..."); /* Force orientation... */ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); if (prefs.getBoolean(VlcPreferences.ORIENTATION_MODE, true)) { setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); } else { setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); } /* ... and then load the layout */ setContentView(R.layout.main); final GLSurfaceView surfaceView = (GLSurfaceView) findViewById(R.id.surface_view); mVout = new Vout(this); // For debug purpose. /* surfaceView.setDebugFlags(GLSurfaceView.DEBUG_CHECK_GL_ERROR | GLSurfaceView.DEBUG_LOG_GL_CALLS);*/ surfaceView.setRenderer(mVout); surfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); mLibVlc = new LibVLC(surfaceView, mVout); try { mLibVlc.init(); } catch (LibVlcException lve) { Log.e(TAG, "Could not load underlying libvlc library: " + lve); mLibVlc = null; /// FIXME Abort cleanly, alert user System.exit(1); } } /** Resume the application */ public void onResume() { super.onResume(); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); // FIXME Make sure we have the requested orientation boolean preferredMode = !prefs.getBoolean(VlcPreferences.ORIENTATION_MODE, true); boolean currentMode = this.getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; if (currentMode != preferredMode) { // This will probably recreate the activity if (preferredMode) { setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); } else { setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); } } // FIXME // if (prefs.getBoolean("hud lock", false)) } /** Called when the activity is finally destroyed */ @Override public void onDestroy() { Log.v (TAG, "VLC is exiting"); if (mLibVlc != null) { mLibVlc.destroy(); } super.onDestroy(); } /** Handle menu key * Note: this is called only once * Implement onPrepareOptionsMenu() to recreate the menu every time it is * displayed. */ public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.stop_menu, menu); return true; } /** Handle menu item selection */ public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { // Main menu entries case R.id.menuOpen: // FIXME showAboutBox Intent i = new Intent(this, SimpleFileBrowser.class); startActivityForResult(i, 0); return true; case R.id.menuAbout: // FIXME showAboutBox return true; case R.id.menuQuit: finish(); return true; // Options menu case R.id.menuOptions: startActivity(new Intent(this, VlcPreferences.class)); return true; default: // Handle submenus... return super.onOptionsItemSelected(item); } } /** Get the global Activity context for use elsewhere */ public static Context getActivityContext() { // The Activity is a context itself return sInstance; } /** Activity result callback */ protected void onActivityResult (int requestCode, int resultCode, Intent data) { Bundle extras = data.getExtras(); if (requestCode == 0 && resultCode == RESULT_OK) { String filePath = extras.getString("filePath"); mLibVlc.readMedia(filePath); } } }
LibVLC.java
package vlc.android; import android.opengl.GLSurfaceView; import android.util.Log; import android.view.Surface; public class LibVLC { private static final String TAG = "LibVLC"; /** libVLC instance C pointer */ private int mLibVlcInstance = 0; // Read-only, reserved for JNI private int mMediaPlayerInstance = 0; // Read-only, reserved for JNI private GLSurfaceView mSurfaceView; private Vout mVout; private Aout mAout; /* Load library before object instantiation */ static { try { System.loadLibrary("vlcjni"); } catch (UnsatisfiedLinkError ule) { Log.e(TAG, "Can't load vlcjni library: " + ule); /// FIXME Alert user System.exit(1); } catch (SecurityException se) { Log.e(TAG, "Encountered a security issue when loading vlcjni library: " + se); /// FIXME Alert user System.exit(1); } } /** * Constructor */ public LibVLC(GLSurfaceView s, Vout v) { mSurfaceView = s; mVout = v; mAout = new Aout(); }; /** * Destructor: * It is bad practice to rely on them, so please don't forget to call * destroy() before exiting. */ public void finalize() { if (mLibVlcInstance != 0) { Log.d(TAG, "LibVLC is was destroyed yet before finalize()"); destroy(); } } /** * Give to LibVLC the surface to draw the video. * @param f the surface to draw */ public native void setSurface(Surface f); /** * Initialize the libVLC class */ public void init() throws LibVlcException { Log.v(TAG, "Initializing LibVLC"); nativeInit(); } /** * Destroy this libVLC instance * @note You must call it before exiting */ public void destroy() { Log.v(TAG, "Destroying LibVLC instance"); nativeDestroy(); } /** * Transmit to the renderer the size of the video. * This function is called by the native code. * @param frameWidth * @param frameHeight */ public void setVoutSize(int frameWidth, int frameHeight) { mVout.frameWidth = frameWidth; mVout.frameHeight = frameHeight; mVout.mustInit = true; } /** * Transmit the image given by VLC to the renderer. * This function is called by the native code. * @param image the image data. */ public void displayCallback(byte[] image) { mVout.image = image; mVout.hasReceivedFrame = true; mSurfaceView.requestRender(); } /** * Open the Java audio output. * This function is called by the native code */ public void initAout(int sampleRateInHz, int channels, int samples) { Log.d(TAG, "Opening the java audio output"); mAout.init(sampleRateInHz, channels, samples); } /** * Play an audio buffer taken from the native code * This function is called by the native code */ public void playAudio(byte[] audioData, int bufferSize, int nbSamples) { Log.d(TAG, "Playing an audio buffer in Java"); mAout.playBuffer(audioData, bufferSize, nbSamples); } /** * Close the Java audio output * This function is called by the native code */ public void closeAout() { Log.d(TAG, "Closing the java audio output"); mAout.release(); } /** * Read a media. */ public void readMedia(String mrl) { Log.v(TAG, "Reading " + mrl); readMedia(mLibVlcInstance, mrl); } /** * Initialize the libvlc C library * @return a pointer to the libvlc instance */ private native void nativeInit() throws LibVlcException; /** * Close the libvlc C library * @note mLibVlcInstance should be 0 after a call to destroy() */ private native void nativeDestroy(); /** * Read a media * @param instance: the instance of libVLC * @param mrl: the media mrl */ private native void readMedia(int instance, String mrl); /** * Get the libVLC version * @return the libVLC version string */ public native String version(); /** * Get the libVLC compiler * @return the libVLC compiler string */ public native String compiler(); /** * Get the libVLC changeset * @return the libVLC changeset string */ public native String changeset(); }
Aout.java
package vlc.android; import android.util.Log; import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioTrack; public class Aout { /** * Java side of the audio output module for Android. * Uses an AudioTrack to play decoded audio buffers. * * TODO Use MODE_STATIC instead of MODE_STREAM with a MemoryFile (ashmem) */ private AudioTrack mAudioTrack; private static final String TAG = "LibVLC/aout"; public void init(int sampleRateInHz, int channels, int samples) { Log.d(TAG, sampleRateInHz + ", " + channels + ", " + samples + "=>" + channels*samples); mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRateInHz, AudioFormat.CHANNEL_CONFIGURATION_STEREO, AudioFormat.ENCODING_PCM_16BIT, channels * samples * 2, AudioTrack.MODE_STREAM); } public void release() { Log.d(TAG, "Stopping audio playback"); // mAudioTrack.stop(); mAudioTrack.release(); mAudioTrack = null; } public void playBuffer(byte[] audioData, int bufferSize, int nbSamples) { Log.d(TAG, "Buffer size: " + bufferSize + " nb samples: " + nbSamples); if (mAudioTrack.write(audioData, 0, bufferSize) != bufferSize) { Log.w(TAG, "Could not write all the samples to the audio device"); } mAudioTrack.play(); } }
Vout.java
/* * Copyright (C) 2008 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package vlc.android; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; import java.nio.ShortBuffer; import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.opengl.GLSurfaceView; import android.opengl.GLU; import android.opengl.GLUtils; import android.util.Log; public class Vout implements GLSurfaceView.Renderer{ private static final String TAG = "LibVLC/vout"; /* Video orientation parameters */ public enum Orientation { HORIZONTAL, VERTICAL } public Vout(Context context) { mContext = context; mQuad = new Quad(); } public void onSurfaceCreated(GL10 gl, EGLConfig config) { /* * By default, OpenGL enables features that improve quality * but reduce performance. One might want to tweak that * especially on software renderer. */ gl.glDisable(GL10.GL_DITHER); /* * Some one-time OpenGL initialization can be made here * probably based on features of this particular context */ gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST); gl.glClearColor(.5f, .5f, .5f, 1); gl.glShadeModel(GL10.GL_SMOOTH); gl.glEnable(GL10.GL_DEPTH_TEST); gl.glEnable(GL10.GL_TEXTURE_2D); /* * Create our texture. This has to be done each time the * surface is created. */ int[] textures = new int[1]; gl.glGenTextures(1, textures, 0); mTextureID = textures[0]; gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureID); gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST); gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR); gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE); gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE); gl.glTexEnvf(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE, GL10.GL_REPLACE); InputStream is = mContext.getResources() .openRawResource(R.raw.cone); Bitmap bitmap; try { bitmap = BitmapFactory.decodeStream(is); } finally { try { is.close(); } catch(IOException e) { // Ignore. } } GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0); bitmap.recycle(); } public void onDrawFrame(GL10 gl) { gl.glTexEnvx(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE, GL10.GL_MODULATE); gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY); if (mustInit) { texWidth = getAlignedSize(frameWidth); texHeight = getAlignedSize(frameHeight); byte[] texData = new byte[texWidth * texHeight * 2]; ByteBuffer byteBufferInit = ByteBuffer.wrap(texData); gl.glTexImage2D(GL10.GL_TEXTURE_2D, 0, GL10.GL_RGB, texWidth, texHeight, 0, GL10.GL_RGB, GL10.GL_UNSIGNED_SHORT_5_6_5, byteBufferInit); mQuad.computeTexCoord(texWidth, texHeight, frameWidth, frameHeight); onSurfaceChanged(gl, surfaceWidth, surfaceHeight); // Compute AspectRatio /* Wrap the image buffer to the byte buffer. */ byteBuffer = ByteBuffer.wrap(image); mustInit = false; } if (hasReceivedFrame) { gl.glTexSubImage2D(GL10.GL_TEXTURE_2D, 0, 0, 0, frameWidth, frameHeight, GL10.GL_RGB, GL10.GL_UNSIGNED_SHORT_5_6_5, byteBuffer); } mQuad.draw(gl); } public void onSurfaceChanged(GL10 gl, int w, int h) { gl.glViewport(0, 0, w, h); /* * Retain surface size, to be able to correct the AspectRatio * on demand when a new video starts */ surfaceWidth = w; surfaceHeight = h; /* * Set our projection matrix. This doesn't have to be done * each time we draw, but usually a new projection needs to * be set when the viewport is resized. */ gl.glMatrixMode(GL10.GL_PROJECTION); gl.glLoadIdentity(); /* * glFrustumf expand the clipping volume vertically or horizontally : it adds bars. * how big those bars should be depends on both the video ratio and the surface ratio */ float vRatio = frameHeight > 0 ? (float) frameWidth / frameHeight : 1f; float sRatio = (float) surfaceWidth / surfaceHeight; if (sRatio > vRatio) { float ratio = sRatio / vRatio; gl.glFrustumf(-ratio, ratio, -1f, 1f, 1f, 2f); } else { float ratio = vRatio / sRatio; gl.glFrustumf(-1f, 1f, -ratio, ratio, 1f, 2f); } } private int getAlignedSize(int i_size) { /* Return the nearest power of 2 */ int i_result = 1; while(i_result < i_size) i_result *= 2; return i_result; } /* Manually change video orientation */ public void setOrientation (Orientation orient) { // FIXME // preferences.set() ... Util.toaster("Orientation:/nNot implemented yet"); } private Context mContext; private Quad mQuad; private int mTextureID; public boolean mustInit = false; public boolean hasReceivedFrame = false; private int surfaceWidth; private int surfaceHeight; public int frameWidth; public int frameHeight; private int texWidth; private int texHeight; public byte[] image; private ByteBuffer byteBuffer; } class Quad { public Quad() { computeTexCoord(1, 1, 1, 1); } public void computeTexCoord(int texWidth, int texHeight, int frameWidth, int frameHeight) { // Buffers to be passed to gl*Pointer() functions // must be direct, i.e., they must be placed on the // native heap where the garbage collector cannot // move them. // // Buffers with multi-byte datatypes (e.g., short, int, float) // must have their byte order set to native order ByteBuffer vbb = ByteBuffer.allocateDirect(VERTS * 3 * 4); vbb.order(ByteOrder.nativeOrder()); mFVertexBuffer = vbb.asFloatBuffer(); ByteBuffer tbb = ByteBuffer.allocateDirect(VERTS * 2 * 4); tbb.order(ByteOrder.nativeOrder()); mTexBuffer = tbb.asFloatBuffer(); ByteBuffer ibb = ByteBuffer.allocateDirect(VERTS * 2); ibb.order(ByteOrder.nativeOrder()); mIndexBuffer = ibb.asShortBuffer(); float[] coords = { // X, Y, Z -1f, -1f, -1f, 1f, -1f, -1f, -1f, 1f, -1f, 1f, 1f, -1f }; float f_width = (float)frameWidth / texWidth; float f_height = (float)frameHeight / texHeight; float[] tex_coords = { // X, Y, Z 0f, f_height, -1f, f_width, f_height, -1f, 0f, 0f, -1f, f_width, 0f, -1f }; for (int i = 0; i < VERTS; i++) { for(int j = 0; j < 3; j++) { mFVertexBuffer.put(coords[i*3+j]); } } for (int i = 0; i < VERTS; i++) { for(int j = 0; j < 2; j++) { mTexBuffer.put(tex_coords[i*3+j]); } } for(int i = 0; i < VERTS; i++) { mIndexBuffer.put((short) i); } mFVertexBuffer.position(0); mTexBuffer.position(0); mIndexBuffer.position(0); } public void draw(GL10 gl) { gl.glFrontFace(GL10.GL_CCW); gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mFVertexBuffer); gl.glEnable(GL10.GL_TEXTURE_2D); gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, mTexBuffer); gl.glDrawElements(GL10.GL_TRIANGLE_STRIP, VERTS, GL10.GL_UNSIGNED_SHORT, mIndexBuffer); } private final static int VERTS = 4; private FloatBuffer mFVertexBuffer; private FloatBuffer mTexBuffer; private ShortBuffer mIndexBuffer; }
libvlcjni.c
#include <stdio.h> #include <string.h> #include <assert.h> #include <jni.h> #include <vlc/vlc.h> #include "libvlcjni.h" #include "aout.h" #include "vout.h" #define LOG_TAG "VLC/JNI/main" #include "log.h" /* Pointer to the Java virtual machine * Note: It's okay to use a static variable for the VM pointer since there * can only be one instance of this shared library in a single VM */ JavaVM *myVm; jint JNI_OnLoad(JavaVM *vm, void *reserved) { // Keep a reference on the Java VM. myVm = vm; LOGD("JNI interface loaded."); return JNI_VERSION_1_2; } void Java_vlc_android_LibVLC_nativeInit(JNIEnv *env, jobject thiz) { const char *argv[] = {"-I", "dummy", "-vvv", "--no-plugins-cache", "--no-drop-late-frames", "--aout", "amem"}; libvlc_instance_t *instance = libvlc_new_with_builtins(sizeof(argv) / sizeof(*argv), argv, vlc_builtins_modules); jclass clazz = (*env)->GetObjectClass(env, thiz); jfieldID field = (*env)->GetFieldID(env, clazz, "mLibVlcInstance", "I"); (*env)->SetIntField(env, thiz, field, (jint) instance); if (!instance) { jclass exc = (*env)->FindClass(env, "vlc/android/LibVlcException"); (*env)->ThrowNew(env, exc, "Unable to instantiate LibVLC"); } LOGI("LibVLC initialized: %p", instance); return; } void Java_vlc_android_LibVLC_nativeDestroy(JNIEnv *env, jobject thiz) { jclass clazz = (*env)->GetObjectClass(env, thiz); jfieldID fieldMP = (*env)->GetFieldID(env, clazz, "mMediaPlayerInstance", "I"); jint mediaPlayer = (*env)->GetIntField(env, thiz, fieldMP); if (mediaPlayer != 0) { libvlc_media_player_t *mp = (libvlc_media_player_t*) mediaPlayer; libvlc_media_player_stop(mp); libvlc_media_player_release(mp); (*env)->SetIntField(env, thiz, fieldMP, 0); } jfieldID field = (*env)->GetFieldID(env, clazz, "mLibVlcInstance", "I"); jint libVlcInstance = (*env)->GetIntField(env, thiz, field); if (!libVlcInstance) return; // Already destroyed libvlc_instance_t *instance = (libvlc_instance_t*) libVlcInstance; libvlc_release(instance); (*env)->SetIntField(env, thiz, field, 0); } void Java_vlc_android_LibVLC_readMedia(JNIEnv *env, jobject thiz, jint instance, jstring mrl) { jboolean isCopy; const char *psz_mrl = (*env)->GetStringUTFChars(env, mrl, &isCopy); /* Create a new item */ libvlc_media_t *m = libvlc_media_new_path((libvlc_instance_t*)instance, psz_mrl); /* Create a media player playing environment */ libvlc_media_player_t *mp = libvlc_media_player_new((libvlc_instance_t*)instance); jobject myJavaLibVLC = (*env)->NewGlobalRef(env, thiz); libvlc_media_player_set_media(mp, m); libvlc_video_set_format_callbacks(mp, vout_format, vout_cleanup); libvlc_video_set_callbacks(mp, vout_lock, vout_unlock, vout_display, (void*) myJavaLibVLC); libvlc_audio_set_callbacks(mp, aout_open, aout_play, aout_close, (void*) myJavaLibVLC); /* No need to keep the media now */ libvlc_media_release(m); /* Keep a pointer to this media player */ jclass clazz = (*env)->GetObjectClass(env, thiz); jfieldID field = (*env)->GetFieldID(env, clazz, "mMediaPlayerInstance", "I"); (*env)->SetIntField(env, thiz, field, (jint) mp); /* Play the media. */ libvlc_media_player_play(mp); //libvlc_media_player_release(mp); (*env)->ReleaseStringUTFChars(env, mrl, psz_mrl); } jstring Java_vlc_android_LibVLC_version(JNIEnv* env, jobject thiz) { return (*env)->NewStringUTF(env, libvlc_get_version()); } jstring Java_vlc_android_LibVLC_compiler(JNIEnv* env, jobject thiz) { return (*env)->NewStringUTF(env, libvlc_get_compiler()); } jstring Java_vlc_android_LibVLC_changeset(JNIEnv* env, jobject thiz) { return (*env)->NewStringUTF(env, libvlc_get_changeset()); }
aout.c
#include <stdio.h> #include <assert.h> #include <vlc/vlc.h> #include "aout.h" #define LOG_TAG "VLC/JNI/aout" #include "log.h" // An audio frame will contain FRAME_SIZE samples #define FRAME_SIZE (4096*2) /** Unique Java VM instance, as defined in libvlcjni.c */ extern JavaVM *myVm; void aout_open (void **opaque, unsigned int *rate, unsigned int *nb_channels, unsigned int *fourCCFormat, unsigned int *nb_samples) { LOGV("Opening the JNI audio output"); aout_sys_t *p_sys; JNIEnv *p_env; // Replace opaque by p_sys and keep the libvlc pointe p_sys = calloc (1, sizeof (*p_sys)); if (!p_sys) { *opaque = NULL; return; // OOM } // Keep a reference to our Java object and return aout_sys_t p_sys->j_libVlc = (jobject) *opaque; *opaque = (void*) p_sys; LOGV ("Parameters: %u channels, %u samples/frame, FOURCC '%4.4s', " "sample rate: %uHz", *nb_channels, *nb_samples, (const char *) fourCCFormat, *rate); // Attach the thread to the VM. Keep this JNIEnv for aout_close() if ((*myVm)->AttachCurrentThread (myVm, &p_env, NULL) != 0) { LOGE("Couldn't attach the display thread to the JVM !"); return; } p_sys->p_env = p_env; // Call the init function. jclass cls = (*p_env)->GetObjectClass (p_env, p_sys->j_libVlc); jmethodID methodIdInitAout = (*p_env)->GetMethodID (p_env, cls, "initAout", "(III)V"); if (!methodIdInitAout) { LOGE ("Method initAout() could not be found!"); (*myVm)->DetachCurrentThread (myVm); *opaque = NULL; free (p_sys); return; } LOGV ("Fixed number of channels to 2, number of samples to %d", FRAME_SIZE); *nb_channels = 2; *nb_samples = FRAME_SIZE; (*p_env)->CallVoidMethod (p_env, p_sys->j_libVlc, methodIdInitAout, *rate, *nb_channels, *nb_samples); if ((*p_env)->ExceptionCheck (p_env)) { LOGE ("Unable to create audio player!"); #ifndef NDEBUG (*p_env)->ExceptionDescribe (p_env); #endif (*p_env)->ExceptionClear (p_env); *opaque = NULL; free (p_sys); return; } /* Create a new byte array to store the audio data. */ jbyteArray byteArray = (*p_env)->NewByteArray (p_env, *nb_channels * *nb_samples * sizeof (uint16_t) /* =2 */); if (byteArray == NULL) { LOGE("Couldn't allocate the Java byte array to store the audio data!"); (*myVm)->DetachCurrentThread (myVm); *opaque = NULL; free (p_sys); return; } /* Use a global reference to not reallocate memory each time we run the display function. */ p_sys->byteArray = (*p_env)->NewGlobalRef (p_env, byteArray); if (p_sys->byteArray == NULL) { LOGE ("Couldn't create the global reference!"); (*myVm)->DetachCurrentThread (myVm); *opaque = NULL; free (p_sys); return; } /* The local reference is no longer useful. */ (*p_env)->DeleteLocalRef (p_env, byteArray); // Get the play methodId p_sys->play = (*p_env)->GetMethodID (p_env, cls, "playAudio", "([BII)V"); assert (p_sys->play != NULL); } /** * Play an audio sample **/ void aout_play (void *opaque, unsigned char *buffer, size_t bufferSize, unsigned int nb_samples) { aout_sys_t *p_sys = (aout_sys_t*) opaque; JNIEnv *p_env; /* How ugly: we constantly attach/detach this thread to/from the JVM * because it will be killed before aout_close is called. * aout_close will actually be called in an different thread! */ (*myVm)->AttachCurrentThread (myVm, &p_env, NULL); (*p_env)->SetByteArrayRegion (p_env, p_sys->byteArray, 0, bufferSize, (jbyte*) buffer); if ((*p_env)->ExceptionCheck (p_env)) { // This can happen if for some reason the size of the input buffer // is larger than the size of the output buffer LOGE ("An exception occurred while calling SetByteArrayRegion"); (*p_env)->ExceptionDescribe (p_env); (*p_env)->ExceptionClear (p_env); return; } (*p_env)->CallVoidMethod (p_env, p_sys->j_libVlc, p_sys->play, p_sys->byteArray, bufferSize, nb_samples); // FIXME: check for errors (*myVm)->DetachCurrentThread (myVm); } void aout_close(void *opaque) { LOGI ("Closing audio output"); aout_sys_t *p_sys = (aout_sys_t*) opaque; if (!p_sys) return; if (p_sys->byteArray) { // Want a crash? Call this function! But whyyyyy??? // Anyway, one more good reason to create the buffer in pure Java //(*p_sys->p_env)->DeleteGlobalRef (p_sys->p_env, p_sys->byteArray); } (*myVm)->DetachCurrentThread (myVm); free (p_sys); }
vout.c
#include <stdio.h> #include <string.h> #include <assert.h> #include <jni.h> #include <vlc/vlc.h> #include "vout.h" #define LOG_TAG "LibVLC/JNI/vout" #include "log.h" /** Unique Java VM instance, as defined in libvlcjni.c */ extern JavaVM *myVm; unsigned vout_format(void **opaque, char *chroma, unsigned *width, unsigned *height, unsigned *pitches, unsigned *lines) { /* So far, *opaque is a pointer to the libvlc instance */ jobject libVlc = (jobject) *opaque; assert (libVlc != NULL); /* Let's replace opaque by p_sys and store the pointer to libVlc */ vout_sys_t **pp_sys = (vout_sys_t **) opaque; *pp_sys = calloc(1, sizeof(vout_sys_t)); if (*pp_sys == NULL) return 0; vout_sys_t *p_sys = *pp_sys; p_sys->j_libVlc = libVlc; p_sys->i_frameWidth = *width; p_sys->i_frameHeight = *height; p_sys->i_frameSize = p_sys->i_frameWidth * p_sys->i_frameHeight * 2; p_sys->p_frameData = calloc(1, p_sys->i_frameSize); if (p_sys->p_frameData == NULL) return 0; strcpy(chroma, "RV16"); *pitches = p_sys->i_frameWidth * 2; *lines = p_sys->i_frameHeight; return 1; } void vout_cleanup(void *opaque) { vout_sys_t *p_sys = opaque; if (p_sys->byteArray != NULL) (*p_sys->p_env)->DeleteLocalRef(p_sys->p_env, p_sys->byteArray); if (p_sys->b_attached) { if ((*myVm)->DetachCurrentThread(myVm) != 0) { LOGE("Couldn't detach the display thread from the JVM!"); return; } } free(p_sys); } void *vout_lock(void *opaque, void **pixels) { vout_sys_t *p_sys = (vout_sys_t *)opaque; *pixels = p_sys->p_frameData; return NULL; } void vout_unlock(void *opaque, void *picture, void *const *p_pixels) { // Nothing to do here until now. } void vout_display(void *opaque, void *picture) { vout_sys_t *p_sys = (vout_sys_t *)opaque; JNIEnv *p_env = p_sys->p_env; if (!p_sys->b_attached) { if ((*myVm)->AttachCurrentThread(myVm, &p_env, NULL) != 0) { LOGE("Couldn't attach the display thread to the JVM !"); return; } p_sys->b_attached = 1; // Save the environment refernce. p_sys->p_env = p_env; jclass cls = (*p_env)->GetObjectClass(p_env, p_sys->j_libVlc); jmethodID methodIdSetVoutSize = (*p_env)->GetMethodID(p_env, cls, "setVoutSize", "(II)V"); if(methodIdSetVoutSize == 0) { LOGE("Method setVoutParams not found !"); return; } // Transmit to Java the vout size. (*p_env)->CallVoidMethod(p_env, p_sys->j_libVlc, methodIdSetVoutSize, p_sys->i_frameWidth, p_sys->i_frameHeight); p_sys->methodIdDisplay = (*p_env)->GetMethodID(p_env, cls, "displayCallback", "([B)V"); if (!p_sys->methodIdDisplay) { LOGE("Method displayCallback not found !"); return; } /* Create a new byte array to store the frame data. */ jbyteArray byteArray = (*p_env)->NewByteArray(p_env, p_sys->i_frameSize); if (byteArray == NULL) { LOGE("Couldn't allocate the Java byte array to store the frame !"); return; } /* Use a global reference to not reallocate memory each time we run the display function. */ p_sys->byteArray = (*p_env)->NewGlobalRef(p_env, byteArray); if (byteArray == NULL) { LOGE("Couldn't create the global reference !"); return; } /* The local reference is no longer useful. */ (*p_env)->DeleteLocalRef(p_env, byteArray); } // Fill the image buffer for debug purpose. //memset(p_sys->p_imageData, 255, p_sys->i_texSize / 2); (*p_env)->SetByteArrayRegion(p_env, p_sys->byteArray, 0, p_sys->i_frameSize, (jbyte *)p_sys->p_frameData); (*p_env)->CallVoidMethod(p_env, p_sys->j_libVlc, p_sys->methodIdDisplay, p_sys->byteArray); }