OSDN Git Service

Merge "Metadata keys for PCM decoding"
authorJean-Michel Trivi <jmtrivi@google.com>
Fri, 26 Aug 2011 16:49:36 +0000 (09:49 -0700)
committerAndroid (Google) Code Review <android-gerrit@google.com>
Fri, 26 Aug 2011 16:49:36 +0000 (09:49 -0700)
25 files changed:
mca/effect/java/android/media/effect/Effect.java
mca/effect/java/android/media/effect/EffectContext.java
mca/effect/java/android/media/effect/EffectFactory.java
mca/effect/java/android/media/effect/FilterEffect.java
mca/effect/java/android/media/effect/FilterGraphEffect.java
mca/effect/java/android/media/effect/SingleFilterEffect.java
mca/effect/java/android/media/effect/SizeChangeEffect.java
mca/effect/java/android/media/effect/effects/IdentityEffect.java [new file with mode: 0644]
mca/filterfw/java/android/filterfw/core/OneShotScheduler.java
wilhelm/src/android/CallbackProtector.cpp
wilhelm/src/android/CallbackProtector.h
wilhelm/src/android/MediaPlayer_to_android.cpp
wilhelm/src/android/MediaPlayer_to_android.h
wilhelm/src/android/android_GenericMediaPlayer.cpp
wilhelm/src/android/android_GenericPlayer.cpp
wilhelm/src/android/android_GenericPlayer.h
wilhelm/src/android/android_StreamPlayer.cpp
wilhelm/src/android/android_StreamPlayer.h
wilhelm/src/classes.h
wilhelm/src/itf/IEngine.c
wilhelm/src/objects/CMediaPlayer.c
wilhelm/tests/sandbox/Android.mk
wilhelm/tests/sandbox/nativewindow.cpp [new file with mode: 0644]
wilhelm/tests/sandbox/nativewindow.h [new file with mode: 0644]
wilhelm/tests/sandbox/xaplay.c [new file with mode: 0644]

index ba5e0e6..b2b4427 100644 (file)
@@ -19,11 +19,20 @@ package android.media.effect;
 
 
 /**
- * Effects are high-performance transformations that can be applied to image frames. These are
- * passed in the form of OpenGLES2 texture names. Typical frames could be images loaded from disk,
- * or frames from the camera or other video stream.
+ * <p>Effects are high-performance transformations that can be applied to image frames. These are
+ * passed in the form of OpenGL ES 2.0 texture names. Typical frames could be images loaded from
+ * disk, or frames from the camera or other video streams.</p>
+ *
+ * <p>To create an Effect you must first create an EffectContext. You can obtain an instance of the
+ * context's EffectFactory by calling
+ * {@link android.media.effect.EffectContext#getFactory() getFactory()}. The EffectFactory allows
+ * you to instantiate specific Effects.</p>
+ *
+ * <p>The application is responsible for creating an EGL context, and making it current before
+ * applying an effect. An effect is bound to a single EffectContext, which in turn is bound to a
+ * single EGL context. If your EGL context is destroyed, the EffectContext becomes invalid and any
+ * effects bound to this context can no longer be used.</p>
  *
- * Effects are created using the EffectFactory class.
  */
 public abstract class Effect {
 
@@ -40,14 +49,24 @@ public abstract class Effect {
     /**
      * Apply an effect to GL textures.
      *
-     * Apply the Effect on the specified input GL texture, and write the result into the
-     * output GL texture. The texture names passed must be valid in the current GL context. The
-     * input texture must be a fully allocated texture with the given width and height. If the
-     * output texture has not been allocated, it will be allocated by the effect, and have the same
-     * size as the input. If the output texture was allocated already, and its size does not match
-     * the input texture size, the result will be stretched to fit.
+     * <p>Apply the Effect on the specified input GL texture, and write the result into the
+     * output GL texture. The texture names passed must be valid in the current GL context.</p>
+     *
+     * <p>The input texture must be a valid texture name with the given width and height and must be
+     * bound to a GL_TEXTURE_2D texture image (usually done by calling the glTexImage2D() function).
+     * Multiple mipmap levels may be provided.</p>
      *
-     * @param inputTexId The GL texture name of the allocated input texture.
+     * <p>If the output texture has not been bound to a texture image, it will be automatically
+     * bound by the effect as a GL_TEXTURE_2D. It will contain one mipmap level (0), which will have
+     * the same size as the input. No other mipmap levels are defined. If the output texture was
+     * bound already, and its size does not match the input texture size, the result may be clipped
+     * or only partially fill the texture.</p>
+     *
+     * <p>Note, that regardless of whether a texture image was originally provided or not, both the
+     * input and output textures are owned by the caller. That is, the caller is responsible for
+     * calling glDeleteTextures() to deallocate the input and output textures.</p>
+     *
+     * @param inputTexId The GL texture name of a valid and bound input texture.
      * @param width The width of the input texture in pixels.
      * @param height The height of the input texture in pixels.
      * @param outputTexId The GL texture name of the output texture.
@@ -80,9 +99,12 @@ public abstract class Effect {
     /**
      * Release an effect.
      *
-     * Releases the effect and any resources associated with it. You may call this if you need to
+     * <p>Releases the effect and any resources associated with it. You may call this if you need to
      * make sure acquired resources are no longer held by the effect. Releasing an effect makes it
-     * invalid for reuse.
+     * invalid for reuse.</p>
+     *
+     * <p>Note that this method must be called with the EffectContext and EGL context current, as
+     * the effect may release internal GL resources.</p>
      */
     public abstract void release();
 }
index ef8c388..ef03229 100644 (file)
@@ -26,9 +26,14 @@ import android.filterfw.core.FrameManager;
 import android.opengl.GLES20;
 
 /**
- * An EffectContext keeps all necessary state information to run Effects within a OpenGLES2 context.
- * Every EffectContext is bound to one GL context. If you switch to another GL context, you must
- * create a new EffectContext. An EffectContext is required to create and execute effects.
+ * <p>An EffectContext keeps all necessary state information to run Effects within a Open GL ES 2.0
+ * context.</p>
+ *
+ * <p>Every EffectContext is bound to one GL context. The application is responsible for creating
+ * this EGL context, and making it current before applying any effect. If your EGL context is
+ * destroyed, the EffectContext becomes invalid and any effects bound to this context can no longer
+ * be used. If you switch to another EGL context, you must create a new EffectContext. Each Effect
+ * is bound to a single EffectContext, and can only be executed in that context.</p>
  */
 public class EffectContext {
 
@@ -46,10 +51,10 @@ public class EffectContext {
     /**
      * Creates a context within the current GL context.
      *
-     * Binds the EffectContext to the current OpenGL context. All subsequent calls to the
+     * <p>Binds the EffectContext to the current OpenGL context. All subsequent calls to the
      * EffectContext must be made in the GL context that was active during creation.
      * When you have finished using a context, you must call {@link #release()}. to dispose of all
-     * resources associated with this context.
+     * resources associated with this context.</p>
      */
     public static EffectContext createWithCurrentGlContext() {
         EffectContext result = new EffectContext();
@@ -60,8 +65,8 @@ public class EffectContext {
     /**
      * Returns the EffectFactory for this context.
      *
-     * The EffectFactory returned from this method allows instantiating new effects within this
-     * context.
+     * <p>The EffectFactory returned from this method allows instantiating new effects within this
+     * context.</p>
      *
      * @return The EffectFactory instance for this context.
      */
@@ -72,8 +77,12 @@ public class EffectContext {
     /**
      * Releases the context.
      *
-     * Releases all the resources associated with the EffectContext. This renders the context
-     * invalid. You must no longer use the context after calling release().
+     * <p>Releases all the resources and effects associated with the EffectContext. This renders the
+     * context and all the effects bound to this context invalid. You must no longer use the context
+     * or any of its bound effects after calling release().</p>
+     *
+     * <p>Note that this method must be called with the proper EGL context made current, as the
+     * EffectContext and its effects may release internal GL resources.</p>
      */
     public void release() {
         mFilterContext.tearDown();
index 1ab84e9..1cc69e3 100644 (file)
@@ -21,10 +21,10 @@ import java.lang.reflect.Constructor;
 import java.util.HashMap;
 
 /**
- * The EffectFactory class defines the list of available Effects, and provides functionality to
+ * <p>The EffectFactory class defines the list of available Effects, and provides functionality to
  * inspect and instantiate them. Some effects may not be available on all platforms, so before
  * creating a certain effect, the application should confirm that the effect is supported on this
- * platform by calling {@link #isEffectSupported(String)}.
+ * platform by calling {@link #isEffectSupported(String)}.</p>
  */
 public class EffectFactory {
 
@@ -36,6 +36,11 @@ public class EffectFactory {
     };
 
     /** List of Effects */
+    /**
+     * <p>Copies the input texture to the output.</p>
+     * @hide
+     */
+    public final static String EFFECT_IDENTITY = "IdentityEffect";
 
     /**
      * <p>Adjusts the brightness of the image.</p>
@@ -276,7 +281,11 @@ public class EffectFactory {
     /**
      * Instantiate a new effect with the given effect name.
      *
-     * The effect's parameters will be set to their default values.
+     * <p>The effect's parameters will be set to their default values.</p>
+     *
+     * <p>Note that the EGL context associated with the current EffectContext need not be made
+     * current when creating an effect. This allows the host application to instantiate effects
+     * before any EGL context has become current.</p>
      *
      * @param effectName The name of the effect to create.
      * @return A new Effect instance.
@@ -295,8 +304,8 @@ public class EffectFactory {
     /**
      * Check if an effect is supported on this platform.
      *
-     * Some effects may only be available on certain platforms. Use this method before
-     * instantiating an effect to make sure it is supported.
+     * <p>Some effects may only be available on certain platforms. Use this method before
+     * instantiating an effect to make sure it is supported.</p>
      *
      * @param effectName The name of the effect.
      * @return true, if the effect is supported on this platform.
index 158f1f4..d7c319e 100644 (file)
@@ -60,6 +60,22 @@ public abstract class FilterEffect extends Effect {
 
     // Helper Methods for subclasses ///////////////////////////////////////////////////////////////
     /**
+     * Call this before manipulating the GL context. Will assert that the GL environment is in a
+     * valid state, and save it.
+     */
+    protected void beginGLEffect() {
+        mEffectContext.assertValidGLState();
+        mEffectContext.saveGLState();
+    }
+
+    /**
+     * Call this after manipulating the GL context. Restores the previous GL state.
+     */
+    protected void endGLEffect() {
+        mEffectContext.restoreGLState();
+    }
+
+    /**
      * Returns the active filter context for this effect.
      */
     protected FilterContext getFilterContext() {
index a4d698d..b18bea8 100644 (file)
@@ -85,8 +85,7 @@ public class FilterGraphEffect extends FilterEffect {
 
     @Override
     public void apply(int inputTexId, int width, int height, int outputTexId) {
-        mEffectContext.assertValidGLState();
-        mEffectContext.saveGLState();
+        beginGLEffect();
         Filter src = mGraph.getFilter(mInputName);
         if (src != null) {
             src.setInputValue("texId", inputTexId);
@@ -106,7 +105,7 @@ public class FilterGraphEffect extends FilterEffect {
         } catch (RuntimeException e) {
             throw new RuntimeException("Internal error applying effect: ", e);
         }
-        mEffectContext.restoreGLState();
+        endGLEffect();
     }
 
     @Override
index 2b127af..ca8f03f 100644 (file)
@@ -69,8 +69,7 @@ public class SingleFilterEffect extends FilterEffect {
 
     @Override
     public void apply(int inputTexId, int width, int height, int outputTexId) {
-        mEffectContext.assertValidGLState();
-        mEffectContext.saveGLState();
+        beginGLEffect();
 
         Frame inputFrame = frameFromTexture(inputTexId, width, height);
         Frame outputFrame = frameFromTexture(outputTexId, width, height);
@@ -83,7 +82,7 @@ public class SingleFilterEffect extends FilterEffect {
         outputFrame.release();
         resultFrame.release();
 
-        mEffectContext.restoreGLState();
+        endGLEffect();
     }
 
     @Override
index 4d3a4bd..4d27bae 100644 (file)
@@ -45,8 +45,7 @@ public class SizeChangeEffect extends SingleFilterEffect {
 
     @Override
     public void apply(int inputTexId, int width, int height, int outputTexId) {
-        mEffectContext.assertValidGLState();
-        mEffectContext.saveGLState();
+        beginGLEffect();
 
         Frame inputFrame = frameFromTexture(inputTexId, width, height);
         Frame resultFrame = mFunction.executeWithArgList(mInputName, inputFrame);
@@ -61,6 +60,6 @@ public class SizeChangeEffect extends SingleFilterEffect {
         outputFrame.release();
         resultFrame.release();
 
-        mEffectContext.restoreGLState();
+        endGLEffect();
     }
 }
diff --git a/mca/effect/java/android/media/effect/effects/IdentityEffect.java b/mca/effect/java/android/media/effect/effects/IdentityEffect.java
new file mode 100644 (file)
index 0000000..d07779e
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2011 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 android.media.effect.effects;
+
+import android.filterfw.core.Frame;
+import android.media.effect.EffectContext;
+import android.media.effect.FilterEffect;
+
+/**
+ * @hide
+ */
+public class IdentityEffect extends FilterEffect {
+
+    public IdentityEffect(EffectContext context, String name) {
+        super(context, name);
+    }
+
+    @Override
+    public void apply(int inputTexId, int width, int height, int outputTexId) {
+        beginGLEffect();
+
+        Frame inputFrame = frameFromTexture(inputTexId, width, height);
+        Frame outputFrame = frameFromTexture(outputTexId, width, height);
+
+        outputFrame.setDataFromFrame(inputFrame);
+
+        inputFrame.release();
+        outputFrame.release();
+
+        endGLEffect();
+    }
+
+    @Override
+    public void setParameter(String parameterKey, Object value) {
+        throw new IllegalArgumentException("Unknown parameter " + parameterKey
+            + " for IdentityEffect!");
+    }
+
+    @Override
+    public void release() {
+    }
+}
+
index cd0c8ec..dbc8d16 100644 (file)
@@ -24,8 +24,11 @@ import android.util.Log;
 
 import java.util.HashMap;
 
-// This OneShotScheduler only schedules filters at most once.
 /**
+ * This OneShotScheduler only schedules source filters at most once. All other
+ * filters will be scheduled, and possibly repeatedly, until there is no filter
+ * that can be scheduled.
+ *
  * @hide
  */
 public class OneShotScheduler extends RoundRobinScheduler {
@@ -57,7 +60,8 @@ public class OneShotScheduler extends RoundRobinScheduler {
                 return null;
             }
             if (!scheduled.containsKey(filter.getName())) {
-                scheduled.put(filter.getName(),1);
+                if (filter.getNumberOfConnectedInputs() == 0)
+                    scheduled.put(filter.getName(),1);
                 if (mLogVerbose) Log.v(TAG, "Scheduling filter \"" + filter.getName() + "\" of type " + filter.getFilterClassName());
                 return filter;
             }
index 33467bf..9732265 100644 (file)
@@ -33,6 +33,7 @@ CallbackProtector::~CallbackProtector() {
 }
 
 
+// static
 bool CallbackProtector::enterCbIfOk(const sp<CallbackProtector> &protector) {
     if (protector != 0) {
         return protector->enterCb();
index e63510c..0c56939 100644 (file)
@@ -26,13 +26,13 @@ public:
     virtual ~CallbackProtector();
 
     /**
-     * Indicates whether the CallbackProtector is valid and it's safe to enter the callback.
+     * Indicates whether the CallbackProtector is non-NULL and it's safe to enter the callback.
      */
     static bool enterCbIfOk(const sp<CallbackProtector> &protector);
 
     /**
-     * Indicates whether it's safe to enter the callback. It would typically return
-     * false if the AudioTrack or AudioPlayer is about to be destroyed
+     * Indicates whether it's safe to enter the callback. It would typically return false
+     * if the associated object (AudioTrack, AudioPlayer, MediaPlayer) is about to be destroyed.
      */
     bool enterCb();
 
@@ -43,13 +43,13 @@ public:
     void exitCb();
 
     /**
-     * Called to signal the track is about to be destroyed, so whenever a callback is
+     * Called to signal the associated object is about to be destroyed, so whenever a callback is
      * entered (see enterCb) it will be notified it is pointless to process the callback. This will
      * return immediately if there are no callbacks, and will block until current callbacks exit.
      */
     void requestCbExitAndWait();
 
-protected:
+private:
     Mutex mLock;
     Condition mCbExitedCondition;
 
@@ -58,7 +58,6 @@ protected:
     /** Counts the number of callbacks actively locking the associated AudioPlayer */
     unsigned int mCbCount;
 
-private:
     // disallow "evil" constructors
     CallbackProtector(const CallbackProtector &);
     CallbackProtector &operator=(const CallbackProtector &);
index 0a3c62f..00e3ee7 100644 (file)
@@ -347,6 +347,8 @@ XAresult android_Player_create(CMediaPlayer *mp) {
     mp->mStreamType = ANDROID_DEFAULT_OUTPUT_STREAM_TYPE;
     mp->mSessionId = android::AudioSystem::newAudioSessionId();
 
+    mp->mCallbackProtector = new android::CallbackProtector();
+
     return result;
 }
 
@@ -405,6 +407,9 @@ XAresult android_Player_realize(CMediaPlayer *mp, SLboolean async) {
 
     if (XA_RESULT_SUCCESS == result) {
 
+        // inform GenericPlayer of the associated callback protector
+        mp->mAVPlayer->setCallbackProtector(mp->mCallbackProtector);
+
         // if there is a video sink
         if (XA_DATALOCATOR_NATIVEDISPLAY ==
                 mp->mImageVideoSink.mLocator.mLocatorType) {
@@ -420,16 +425,35 @@ XAresult android_Player_realize(CMediaPlayer *mp, SLboolean async) {
     return result;
 }
 
+// Called with a lock on MediaPlayer, and blocks until safe to destroy
+XAresult android_Player_preDestroy(CMediaPlayer *mp) {
+    SL_LOGV("android_Player_preDestroy(%p)", mp);
+    if (mp->mAVPlayer != 0) {
+        mp->mAVPlayer->preDestroy();
+    }
+    SL_LOGV("android_Player_preDestroy(%p) after mAVPlayer->preDestroy()", mp);
+
+    object_unlock_exclusive(&mp->mObject);
+    if (mp->mCallbackProtector != 0) {
+        mp->mCallbackProtector->requestCbExitAndWait();
+    }
+    object_lock_exclusive(&mp->mObject);
+
+    return XA_RESULT_SUCCESS;
+}
+
 //-----------------------------------------------------------------------------
 XAresult android_Player_destroy(CMediaPlayer *mp) {
     SL_LOGV("android_Player_destroy(%p)", mp);
-    XAresult result = XA_RESULT_SUCCESS;
 
-    if (mp->mAVPlayer != 0) {
-        mp->mAVPlayer.clear();
-    }
+    mp->mAVPlayer.clear();
+    mp->mCallbackProtector.clear();
 
-    return result;
+    // explicit destructor
+    mp->mAVPlayer.~sp();
+    mp->mCallbackProtector.~sp();
+
+    return XA_RESULT_SUCCESS;
 }
 
 
index 6316acd..cea4c3a 100644 (file)
@@ -29,6 +29,8 @@ extern XAresult android_Player_realize(CMediaPlayer *mp, SLboolean async);
 
 extern XAresult android_Player_destroy(CMediaPlayer *mp);
 
+extern XAresult android_Player_preDestroy(CMediaPlayer *mp);
+
 
 /**************************************************************************************************
  * Configuration
index 2abf4f9..8cca0d8 100644 (file)
@@ -168,6 +168,9 @@ GenericMediaPlayer::~GenericMediaPlayer() {
 
 void GenericMediaPlayer::preDestroy() {
     SL_LOGD("GenericMediaPlayer::preDestroy()");
+    if (mPlayer != 0) {
+        mPlayer->stop();
+    }
     GenericPlayer::preDestroy();
 }
 
index c5e7e0e..703249f 100644 (file)
@@ -666,4 +666,10 @@ void GenericPlayer::updateOneShot(int positionMs)
 
 }
 
+void GenericPlayer::setCallbackProtector(const sp<CallbackProtector> &callbackProtector)
+{
+    assert(mCallbackProtector == NULL && callbackProtector != NULL);
+    mCallbackProtector = callbackProtector;
+}
+
 } // namespace android
index 2341a04..75e1f62 100644 (file)
@@ -86,6 +86,10 @@ public:
     // Call after changing any of the IPlay settings related to SL_PLAYEVENT_*
     void setPlayEvents(int32_t eventFlags, int32_t markerPosition, int32_t positionUpdatePeriod);
 
+    // Inform us of the callback protector associated with this object; call at most once
+    // immediately after object construction, from the same thread that did the construction.
+    void setCallbackProtector(const sp<CallbackProtector> &callbackProtector);
+
 protected:
     // mutex used for set vs use of volume and cache (fill, threshold) settings
     Mutex mSettingsLock;
@@ -185,6 +189,9 @@ protected:
     int16_t mLastNotifiedCacheFill; // last cache fill level communicated to the listener
     int16_t mCacheFillNotifThreshold; // threshold in cache fill level for cache fill to be reported
 
+    // set at most once immediately after construction, and then read-only
+    sp<CallbackProtector> mCallbackProtector;
+
 private:
 
     // Our copy of some important IPlay member variables, except in Android units
index 2cb178d..75fed25 100644 (file)
@@ -45,12 +45,13 @@ namespace android {
 
 StreamSourceAppProxy::StreamSourceAppProxy(
         const void* user, bool userIsAudioPlayer,
-        void *context, const void *caller) :
+        void *context, const void *caller, const sp<CallbackProtector> &callbackProtector) :
     mUser(user),
     mUserIsAudioPlayer(userIsAudioPlayer),
     mAndroidBufferQueue(NULL),
     mAppContext(context),
-    mCaller(caller)
+    mCaller(caller),
+    mCallbackProtector(callbackProtector)
 {
     SL_LOGV("StreamSourceAppProxy::StreamSourceAppProxy()");
 
@@ -117,6 +118,8 @@ void StreamSourceAppProxy::receivedBuffer_l(size_t buffIndex, size_t buffLength)
 // consumption from ABQ
 void StreamSourceAppProxy::pullFromBuffQueue() {
 
+  if (android::CallbackProtector::enterCbIfOk(mCallbackProtector)) {
+
     size_t bufferId;
     void* bufferLoc;
     size_t buffSize;
@@ -245,6 +248,9 @@ void StreamSourceAppProxy::pullFromBuffQueue() {
                 (SLAndroidBufferItem*)(&kItemProcessed) /* pItems */,
                 3*sizeof(SLuint32) /* itemsLength */ );
     }
+
+    mCallbackProtector->exitCb();
+  } // enterCbIfOk
 }
 
 
@@ -288,7 +294,7 @@ void StreamPlayer::registerQueueCallback(
 
     mAppProxy = new StreamSourceAppProxy(
             user, userIsAudioPlayer,
-            context, caller);
+            context, caller, mCallbackProtector);
 
     CHECK(mAppProxy != 0);
     SL_LOGD("StreamPlayer::registerQueueCallback end");
index 2aa7424..edfb983 100644 (file)
@@ -31,7 +31,8 @@ public:
     StreamSourceAppProxy(
             const void* user, bool userIsAudioPlayer,
             void *appContext,
-            const void *caller);
+            const void *caller,
+            const sp<CallbackProtector> &callbackProtector);
     virtual ~StreamSourceAppProxy();
 
     // store an item structure to indicate a processed buffer
@@ -65,6 +66,8 @@ private:
     void *mAppContext;
     const void *mCaller;
 
+    sp<CallbackProtector> mCallbackProtector;
+
     DISALLOW_EVIL_CONSTRUCTORS(StreamSourceAppProxy);
 };
 
index ced5a14..4a64e01 100644 (file)
@@ -335,6 +335,7 @@ typedef struct CMediaPlayer_struct {
                             // range 1 <= x <= 8
 #ifdef ANDROID
     android::sp<android::GenericPlayer> mAVPlayer;
+    android::sp<android::CallbackProtector> mCallbackProtector;
     enum AndroidObjectType mAndroidObjType;
     /** identifies the initialization and preparation state */
     enum AndroidObjectState mAndroidObjState;
index 3dc6f4a..8603fdc 100644 (file)
@@ -1112,9 +1112,12 @@ static XAresult IEngine_CreateMediaPlayer(XAEngineItf self, XAObjectItf *pPlayer
                     // (assume calloc or memset 0 during allocation)
                     // placement new
 #ifdef ANDROID
+                    // placement new (explicit constructor)
                     // FIXME unnecessary once those fields are encapsulated in one class, rather
                     //   than a structure
                     (void) new (&thiz->mAVPlayer) android::sp<android::GenericPlayer>();
+                    (void) new (&thiz->mCallbackProtector)
+                            android::sp<android::CallbackProtector>();
 #endif
 
                     // Check the source and sink parameters against generic constraints
index e0a6bbb..63285d8 100644 (file)
@@ -64,5 +64,9 @@ void CMediaPlayer_Destroy(void *self)
 
 predestroy_t CMediaPlayer_PreDestroy(void *self)
 {
+    CMediaPlayer *thiz = (CMediaPlayer *) self;
+#ifdef ANDROID
+    android_Player_preDestroy(thiz);
+#endif
     return predestroy_ok;
 }
index 3a383a8..53c4fd0 100644 (file)
@@ -433,3 +433,35 @@ LOCAL_CFLAGS += -UNDEBUG
 LOCAL_MODULE:= slesTest_dual
 
 include $(BUILD_EXECUTABLE)
+
+# xaplay
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_C_INCLUDES:= \
+       system/media/wilhelm/include
+
+LOCAL_SRC_FILES:= \
+       xaplay.c nativewindow.cpp
+
+LOCAL_SHARED_LIBRARIES := \
+       libutils \
+       libOpenMAXAL \
+    libgui \
+    libbinder \
+    libandroid
+
+LOCAL_STATIC_LIBRARIES := \
+    libOpenSLESUT
+
+ifeq ($(TARGET_OS),linux)
+       LOCAL_CFLAGS += -DXP_UNIX
+endif
+
+LOCAL_CFLAGS += -UNDEBUG
+
+LOCAL_MODULE:= xaplay
+
+include $(BUILD_EXECUTABLE)
diff --git a/wilhelm/tests/sandbox/nativewindow.cpp b/wilhelm/tests/sandbox/nativewindow.cpp
new file mode 100644 (file)
index 0000000..21eb0f3
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+// Copied from frameworks/base/cmds/stagefright/stream.cpp
+
+// Note to NDK developers who happen to see this: this module uses Android internal platform APIs
+// that are not part of the NDK supported APIs, and are subject to change at any time.
+
+#include <binder/ProcessState.h>
+#include <surfaceflinger/ISurfaceComposer.h>
+#include <surfaceflinger/SurfaceComposerClient.h>
+#include <utils/String8.h>
+
+#include "nativewindow.h"
+
+#if 1
+#include <assert.h>
+#define CHECK assert
+#define CHECK_EQ(a,b) CHECK((a)==(b))
+#else
+#include <media/stagefright/foundation/ADebug.h>
+#endif
+
+namespace android {
+
+sp<Surface> gSurface;
+sp<SurfaceComposerClient> gComposerClient;
+sp<SurfaceControl> gControl;
+
+ANativeWindow *getNativeWindow_()
+{
+#if 0
+    android::ProcessState::self()->startThreadPool();
+#endif
+
+    sp<SurfaceComposerClient> composerClient = new SurfaceComposerClient;
+    CHECK_EQ(composerClient->initCheck(), (status_t)OK);
+
+    sp<SurfaceControl> control =
+        composerClient->createSurface(
+                String8("A Surface"),
+                0,
+                1280,
+                800,
+                PIXEL_FORMAT_RGB_565,
+                0);
+
+    CHECK(control != NULL);
+    CHECK(control->isValid());
+
+    SurfaceComposerClient::openGlobalTransaction();
+    CHECK_EQ(control->setLayer(30000), (status_t)OK);
+    CHECK_EQ(control->show(), (status_t)OK);
+    SurfaceComposerClient::closeGlobalTransaction();
+
+    sp<Surface> surface = control->getSurface();
+    CHECK(surface != NULL);
+
+    gSurface = surface;
+    gComposerClient = composerClient;
+    gControl = control;
+    // composerClient->dispose() at exit
+    return surface.get();
+}
+
+} // namespace android
+
+ANativeWindow *getNativeWindow()
+{
+    return android::getNativeWindow_();
+}
diff --git a/wilhelm/tests/sandbox/nativewindow.h b/wilhelm/tests/sandbox/nativewindow.h
new file mode 100644 (file)
index 0000000..8903d8e
--- /dev/null
@@ -0,0 +1,11 @@
+#include <android/native_window_jni.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern ANativeWindow *getNativeWindow();
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/wilhelm/tests/sandbox/xaplay.c b/wilhelm/tests/sandbox/xaplay.c
new file mode 100644 (file)
index 0000000..18a3e54
--- /dev/null
@@ -0,0 +1,537 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+// OpenMAX AL MediaPlayer command-line player
+
+#include <assert.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <OMXAL/OpenMAXAL.h>
+#include <OMXAL/OpenMAXAL_Android.h>
+#include "nativewindow.h"
+
+#define MPEG2TS_PACKET_SIZE 188  // MPEG-2 transport stream packet size in bytes
+#define PACKETS_PER_BUFFER 20    // Number of MPEG-2 transport stream packets per buffer
+
+#define NB_BUFFERS 2    // Number of buffers in Android buffer queue
+
+// MPEG-2 transport stream packet
+typedef struct {
+    char data[MPEG2TS_PACKET_SIZE];
+} MPEG2TS_Packet;
+
+#if 0
+// Each buffer in Android buffer queue
+typedef struct {
+    MPEG2TS_Packet packets[PACKETS_PER_BUFFER];
+} Buffer;
+#endif
+
+// Globals shared between main thread and buffer queue callback
+MPEG2TS_Packet *packets;
+size_t numPackets;
+size_t curPacket;
+size_t discPacket;
+
+// These are extensions to OpenMAX AL 1.0.1 values
+
+#define PREFETCHSTATUS_UNKNOWN ((XAuint32) 0)
+#define PREFETCHSTATUS_ERROR   ((XAuint32) (-1))
+
+// Mutex and condition shared with main program to protect prefetch_status
+
+static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
+XAuint32 prefetch_status = PREFETCHSTATUS_UNKNOWN;
+
+/* used to detect errors likely to have occured when the OpenMAX AL framework fails to open
+ * a resource, for instance because a file URI is invalid, or an HTTP server doesn't respond.
+ */
+#define PREFETCHEVENT_ERROR_CANDIDATE \
+        (XA_PREFETCHEVENT_STATUSCHANGE | XA_PREFETCHEVENT_FILLLEVELCHANGE)
+
+// stream event change callback
+void streamEventChangeCallback(XAStreamInformationItf caller, XAuint32 eventId,
+        XAuint32 streamIndex, void *pEventData, void *pContext)
+{
+    // context parameter is specified as NULL and is unused here
+    assert(NULL == pContext);
+    switch (eventId) {
+    case XA_STREAMCBEVENT_PROPERTYCHANGE:
+        printf("XA_STREAMCBEVENT_PROPERTYCHANGE on stream index %u, pEventData %p\n", streamIndex,
+                pEventData);
+        break;
+    default:
+        printf("Unknown stream event ID %u\n", eventId);
+        break;
+    }
+}
+
+// prefetch status callback
+void prefetchStatusCallback(XAPrefetchStatusItf caller,  void *pContext, XAuint32 event)
+{
+    // pContext is unused here, so we pass NULL
+    assert(pContext == NULL);
+    XApermille level = 0;
+    XAresult result;
+    result = (*caller)->GetFillLevel(caller, &level);
+    assert(XA_RESULT_SUCCESS == result);
+    XAuint32 status;
+    result = (*caller)->GetPrefetchStatus(caller, &status);
+    assert(XA_RESULT_SUCCESS == result);
+    if (event & XA_PREFETCHEVENT_FILLLEVELCHANGE) {
+        printf("PrefetchEventCallback: Buffer fill level is = %d\n", level);
+    }
+    if (event & XA_PREFETCHEVENT_STATUSCHANGE) {
+        printf("PrefetchEventCallback: Prefetch Status is = %u\n", status);
+    }
+    XAuint32 new_prefetch_status;
+    if ((PREFETCHEVENT_ERROR_CANDIDATE == (event & PREFETCHEVENT_ERROR_CANDIDATE))
+            && (level == 0) && (status == XA_PREFETCHSTATUS_UNDERFLOW)) {
+        printf("PrefetchEventCallback: Error while prefetching data, exiting\n");
+        new_prefetch_status = PREFETCHSTATUS_ERROR;
+    } else if (event == XA_PREFETCHEVENT_STATUSCHANGE) {
+        new_prefetch_status = status;
+    } else {
+        return;
+    }
+    int ok;
+    ok = pthread_mutex_lock(&mutex);
+    assert(ok == 0);
+    prefetch_status = new_prefetch_status;
+    ok = pthread_cond_signal(&cond);
+    assert(ok == 0);
+    ok = pthread_mutex_unlock(&mutex);
+    assert(ok == 0);
+}
+
+// playback event callback
+void playEventCallback(XAPlayItf caller, void *pContext, XAuint32 event)
+{
+    // pContext is unused here, so we pass NULL
+    assert(NULL == pContext);
+
+    XAmillisecond position;
+
+    if (XA_PLAYEVENT_HEADATEND & event) {
+        printf("XA_PLAYEVENT_HEADATEND reached\n");
+        //SignalEos();
+    }
+
+    XAresult result;
+    if (XA_PLAYEVENT_HEADATNEWPOS & event) {
+        result = (*caller)->GetPosition(caller, &position);
+        assert(XA_RESULT_SUCCESS == result);
+        printf("XA_PLAYEVENT_HEADATNEWPOS current position=%ums\n", position);
+    }
+
+    if (XA_PLAYEVENT_HEADATMARKER & event) {
+        result = (*caller)->GetPosition(caller, &position);
+        assert(XA_RESULT_SUCCESS == result);
+        printf("XA_PLAYEVENT_HEADATMARKER current position=%ums\n", position);
+    }
+}
+
+// Android buffer queue callback
+XAresult bufferQueueCallback(
+        XAAndroidBufferQueueItf caller,
+        void *pCallbackContext,
+        void *pBufferContext,
+        void *pBufferData,
+        XAuint32 dataSize,
+        XAuint32 dataUsed,
+        const XAAndroidBufferItem *pItems,
+        XAuint32 itemsLength)
+{
+    // enqueue the .ts data directly from mapped memory, so ignore the empty buffer pBufferData
+    if (curPacket <= numPackets) {
+        static const XAAndroidBufferItem discontinuity = {XA_ANDROID_ITEMKEY_DISCONTINUITY, 0};
+        static const XAAndroidBufferItem eos = {XA_ANDROID_ITEMKEY_EOS, 0};
+        const XAAndroidBufferItem *items;
+        XAuint32 itemSize;
+        // compute number of packets to be enqueued in this buffer
+        XAuint32 packetsThisBuffer = numPackets - curPacket;
+        if (packetsThisBuffer > PACKETS_PER_BUFFER) {
+            packetsThisBuffer = PACKETS_PER_BUFFER;
+        }
+        // last packet? this should only happen once
+        if (curPacket == numPackets) {
+            (void) write(1, "e", 1);
+            items = &eos;
+            itemSize = sizeof(eos);
+        // discontinuity requested?
+        } else if (curPacket == discPacket) {
+            printf("sending discontinuity, rewinding from beginning of stream\n");
+            items = &discontinuity;
+            itemSize = sizeof(discontinuity);
+            curPacket = 0;
+        // pure data with no items
+        } else {
+            items = NULL;
+            itemSize = 0;
+        }
+        XAresult result;
+        // enqueue the optional data and optional items; there is always at least one or the other
+        assert(packetsThisBuffer > 0 || itemSize > 0);
+        result = (*caller)->Enqueue(caller, NULL, &packets[curPacket],
+                sizeof(MPEG2TS_Packet) * packetsThisBuffer, items, itemSize);
+        assert(XA_RESULT_SUCCESS == result);
+        curPacket += packetsThisBuffer;
+    }
+    return XA_RESULT_SUCCESS;
+}
+
+// main program
+int main(int argc, char **argv)
+{
+    const char *prog = argv[0];
+    int i;
+
+    XAboolean abq = XA_BOOLEAN_FALSE;   // use AndroidBufferQueue, default is URI
+    XAboolean looping = XA_BOOLEAN_FALSE;
+#ifdef REINITIALIZE
+    int reinit_counter = 0;
+#endif
+    for (i = 1; i < argc; ++i) {
+        const char *arg = argv[i];
+        if (arg[0] != '-')
+            break;
+        switch (arg[1]) {
+        case 'a':
+            abq = XA_BOOLEAN_TRUE;
+            break;
+        case 'd':
+            discPacket = atoi(&arg[2]);
+            break;
+        case 'l':
+            looping = XA_BOOLEAN_TRUE;
+            break;
+#ifdef REINITIALIZE
+        case 'r':
+            reinit_counter = atoi(&arg[2]);
+            break;
+#endif
+        default:
+            fprintf(stderr, "%s: unknown option %s\n", prog, arg);
+            break;
+        }
+    }
+
+    // check that exactly one URI was specified
+    if (argc - i != 1) {
+        fprintf(stderr, "usage: %s [-a] [-d#] [-l] uri\n", prog);
+        return EXIT_FAILURE;
+    }
+    const char *uri = argv[i];
+
+    // for AndroidBufferQueue, interpret URI as a filename and open
+    int fd = -1;
+    if (abq) {
+        fd = open(uri, O_RDONLY);
+        if (fd < 0) {
+            perror(uri);
+            goto close;
+        }
+        int ok;
+        struct stat statbuf;
+        ok = fstat(fd, &statbuf);
+        if (ok < 0) {
+            perror(uri);
+            goto close;
+        }
+        if (!S_ISREG(statbuf.st_mode)) {
+            fprintf(stderr, "%s: not an ordinary file\n", uri);
+            goto close;
+        }
+        void *ptr;
+        ptr = mmap(NULL, statbuf.st_size, PROT_READ, MAP_FILE | MAP_PRIVATE, fd, (off_t) 0);
+        if (ptr == MAP_FAILED) {
+            perror(uri);
+            goto close;
+        }
+        size_t filelen = statbuf.st_size;
+        if ((filelen % MPEG2TS_PACKET_SIZE) != 0) {
+            fprintf(stderr, "%s: warning file length %zu is not a multiple of %d\n", uri, filelen,
+                    MPEG2TS_PACKET_SIZE);
+        }
+        packets = (MPEG2TS_Packet *) ptr;
+        numPackets = filelen / MPEG2TS_PACKET_SIZE;
+        printf("%s has %zu packets\n", uri, numPackets);
+    }
+
+    ANativeWindow *nativeWindow;
+
+#ifdef REINITIALIZE
+reinitialize:    ;
+#endif
+
+    XAresult result;
+    XAObjectItf engineObject;
+
+    // create engine
+    result = xaCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
+    assert(XA_RESULT_SUCCESS == result);
+    result = (*engineObject)->Realize(engineObject, XA_BOOLEAN_FALSE);
+    assert(XA_RESULT_SUCCESS == result);
+    XAEngineItf engineEngine;
+    result = (*engineObject)->GetInterface(engineObject, XA_IID_ENGINE, &engineEngine);
+    assert(XA_RESULT_SUCCESS == result);
+
+    // create output mix
+    XAObjectItf outputMixObject;
+    result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 0, NULL, NULL);
+    assert(XA_RESULT_SUCCESS == result);
+    result = (*outputMixObject)->Realize(outputMixObject, XA_BOOLEAN_FALSE);
+    assert(XA_RESULT_SUCCESS == result);
+
+    // configure media source
+    XADataLocator_URI locUri;
+    locUri.locatorType = XA_DATALOCATOR_URI;
+    locUri.URI = (XAchar *) uri;
+    XADataFormat_MIME fmtMime;
+    fmtMime.formatType = XA_DATAFORMAT_MIME;
+    if (abq) {
+        fmtMime.mimeType = (XAchar *) "video/mp2ts";
+        fmtMime.containerType = XA_CONTAINERTYPE_MPEG_TS;
+    } else {
+        fmtMime.mimeType = NULL;
+        fmtMime.containerType = XA_CONTAINERTYPE_UNSPECIFIED;
+    }
+    XADataLocator_AndroidBufferQueue locABQ;
+    locABQ.locatorType = XA_DATALOCATOR_ANDROIDBUFFERQUEUE;
+    locABQ.numBuffers = NB_BUFFERS;
+    XADataSource dataSrc;
+    if (abq) {
+        dataSrc.pLocator = &locABQ;
+    } else {
+        dataSrc.pLocator = &locUri;
+    }
+    dataSrc.pFormat = &fmtMime;
+
+    // configure audio sink
+    XADataLocator_OutputMix locOM;
+    locOM.locatorType = XA_DATALOCATOR_OUTPUTMIX;
+    locOM.outputMix = outputMixObject;
+    XADataSink audioSnk;
+    audioSnk.pLocator = &locOM;
+    audioSnk.pFormat = NULL;
+
+    // configure video sink
+    nativeWindow = getNativeWindow();
+    XADataLocator_NativeDisplay locND;
+    locND.locatorType = XA_DATALOCATOR_NATIVEDISPLAY;
+    locND.hWindow = nativeWindow;
+    locND.hDisplay = NULL;
+    XADataSink imageVideoSink;
+    imageVideoSink.pLocator = &locND;
+    imageVideoSink.pFormat = NULL;
+
+    // create media player
+    XAObjectItf playerObject;
+    XAInterfaceID ids[4] = {XA_IID_STREAMINFORMATION, XA_IID_PREFETCHSTATUS, XA_IID_SEEK,
+            XA_IID_ANDROIDBUFFERQUEUE};
+    XAboolean req[4] = {XA_BOOLEAN_TRUE, XA_BOOLEAN_TRUE, XA_BOOLEAN_TRUE, XA_BOOLEAN_TRUE};
+    result = (*engineEngine)->CreateMediaPlayer(engineEngine, &playerObject, &dataSrc, NULL,
+            &audioSnk, nativeWindow != NULL ? &imageVideoSink : NULL, NULL, NULL, abq ? 4 : 3, ids,
+            req);
+    assert(XA_RESULT_SUCCESS == result);
+
+    // realize the player
+    result = (*playerObject)->Realize(playerObject, XA_BOOLEAN_FALSE);
+    assert(XA_RESULT_SUCCESS == result);
+
+    if (abq) {
+
+        // get the Android buffer queue interface
+        XAAndroidBufferQueueItf playerAndroidBufferQueue;
+        result = (*playerObject)->GetInterface(playerObject, XA_IID_ANDROIDBUFFERQUEUE,
+                &playerAndroidBufferQueue);
+        assert(XA_RESULT_SUCCESS == result);
+
+        // register the buffer queue callback
+        result = (*playerAndroidBufferQueue)->RegisterCallback(playerAndroidBufferQueue,
+                bufferQueueCallback, NULL);
+        assert(XA_RESULT_SUCCESS == result);
+        result = (*playerAndroidBufferQueue)->SetCallbackEventsMask(playerAndroidBufferQueue,
+                XA_ANDROIDBUFFERQUEUEEVENT_PROCESSED);
+        assert(XA_RESULT_SUCCESS == result);
+
+        // enqueue the initial buffers until buffer queue is full
+        XAuint32 packetsThisBuffer;
+        for (curPacket = 0; curPacket < numPackets; curPacket += packetsThisBuffer) {
+            // handle the unlikely case of a very short .ts
+            packetsThisBuffer = numPackets - curPacket;
+            if (packetsThisBuffer > PACKETS_PER_BUFFER) {
+                packetsThisBuffer = PACKETS_PER_BUFFER;
+            }
+            result = (*playerAndroidBufferQueue)->Enqueue(playerAndroidBufferQueue, NULL,
+                    &packets[curPacket], MPEG2TS_PACKET_SIZE * packetsThisBuffer, NULL, 0);
+            if (XA_RESULT_BUFFER_INSUFFICIENT == result) {
+                printf("Enqueued initial %u packets in %u buffers\n", curPacket, curPacket / PACKETS_PER_BUFFER);
+                break;
+            }
+            assert(XA_RESULT_SUCCESS == result);
+        }
+
+    }
+
+    // get the stream information interface
+    XAStreamInformationItf playerStreamInformation;
+    result = (*playerObject)->GetInterface(playerObject, XA_IID_STREAMINFORMATION,
+            &playerStreamInformation);
+    assert(XA_RESULT_SUCCESS == result);
+
+    // register the stream event change callback
+    result = (*playerStreamInformation)->RegisterStreamChangeCallback(playerStreamInformation,
+            streamEventChangeCallback, NULL);
+    assert(XA_RESULT_SUCCESS == result);
+
+    // get the prefetch status interface
+    XAPrefetchStatusItf playerPrefetchStatus;
+    result = (*playerObject)->GetInterface(playerObject, XA_IID_PREFETCHSTATUS,
+            &playerPrefetchStatus);
+    assert(XA_RESULT_SUCCESS == result);
+
+    // register prefetch status callback
+    result = (*playerPrefetchStatus)->RegisterCallback(playerPrefetchStatus, prefetchStatusCallback,
+            NULL);
+    assert(XA_RESULT_SUCCESS == result);
+    result = (*playerPrefetchStatus)->SetCallbackEventsMask(playerPrefetchStatus,
+            XA_PREFETCHEVENT_FILLLEVELCHANGE | XA_PREFETCHEVENT_STATUSCHANGE);
+    assert(XA_RESULT_SUCCESS == result);
+
+    // get the seek interface
+    if (looping) {
+        XASeekItf playerSeek;
+        result = (*playerObject)->GetInterface(playerObject, XA_IID_SEEK, &playerSeek);
+        assert(XA_RESULT_SUCCESS == result);
+        result = (*playerSeek)->SetLoop(playerSeek, XA_BOOLEAN_TRUE, (XAmillisecond) 0,
+                XA_TIME_UNKNOWN);
+        assert(XA_RESULT_SUCCESS == result);
+    }
+
+    // get the play interface
+    XAPlayItf playerPlay;
+    result = (*playerObject)->GetInterface(playerObject, XA_IID_PLAY, &playerPlay);
+    assert(XA_RESULT_SUCCESS == result);
+
+    // register play event callback
+    result = (*playerPlay)->RegisterCallback(playerPlay, playEventCallback, NULL);
+    assert(XA_RESULT_SUCCESS == result);
+#if 0 // FIXME broken
+    result = (*playerPlay)->SetCallbackEventsMask(playerPlay,
+            XA_PLAYEVENT_HEADATEND | XA_PLAYEVENT_HEADATMARKER | XA_PLAYEVENT_HEADATNEWPOS);
+    assert(XA_RESULT_SUCCESS == result);
+#endif
+
+    // set a marker
+    result = (*playerPlay)->SetMarkerPosition(playerPlay, 10000);
+    assert(XA_RESULT_SUCCESS == result);
+
+    // set position update period
+    result = (*playerPlay)->SetPositionUpdatePeriod(playerPlay, 1000);
+    assert(XA_RESULT_SUCCESS == result);
+
+    // get the duration
+    XAmillisecond duration;
+    result = (*playerPlay)->GetDuration(playerPlay, &duration);
+    assert(XA_RESULT_SUCCESS == result);
+    if (XA_TIME_UNKNOWN == duration)
+        printf("Duration: unknown\n");
+    else
+        printf("Duration: %.1f\n", duration / 1000.0f);
+
+    // set the player's state to paused, to start prefetching
+    printf("start prefetch\n");
+    result = (*playerPlay)->SetPlayState(playerPlay, XA_PLAYSTATE_PAUSED);
+    assert(XA_RESULT_SUCCESS == result);
+
+    // wait for prefetch status callback to indicate either sufficient data or error
+    pthread_mutex_lock(&mutex);
+    while (prefetch_status == PREFETCHSTATUS_UNKNOWN) {
+        pthread_cond_wait(&cond, &mutex);
+    }
+    pthread_mutex_unlock(&mutex);
+    if (prefetch_status == PREFETCHSTATUS_ERROR) {
+        fprintf(stderr, "Error during prefetch, exiting\n");
+        goto destroyRes;
+    }
+
+    // get duration again, now it should be known
+    result = (*playerPlay)->GetDuration(playerPlay, &duration);
+    assert(XA_RESULT_SUCCESS == result);
+    if (duration == XA_TIME_UNKNOWN) {
+        fprintf(stdout, "Content duration is unknown (after prefetch completed)\n");
+    } else {
+        fprintf(stdout, "Content duration is %u ms (after prefetch completed)\n", duration);
+    }
+
+    // start playing
+    printf("starting to play\n");
+    result = (*playerPlay)->SetPlayState(playerPlay, XA_PLAYSTATE_PLAYING);
+    assert(XA_RESULT_SUCCESS == result);
+
+    // continue playing until end of media
+    for (;;) {
+        XAuint32 status;
+        result = (*playerPlay)->GetPlayState(playerPlay, &status);
+        assert(XA_RESULT_SUCCESS == result);
+        if (status == XA_PLAYSTATE_PAUSED)
+            break;
+        assert(status == XA_PLAYSTATE_PLAYING);
+        sleep(1);
+    }
+
+    // wait a bit more in case of additional callbacks
+    printf("end of media\n");
+    sleep(3);
+
+destroyRes:
+
+    // destroy the player
+    (*playerObject)->Destroy(playerObject);
+
+    // destroy the output mix
+    (*outputMixObject)->Destroy(outputMixObject);
+
+    // destroy the engine
+    (*engineObject)->Destroy(engineObject);
+
+#ifdef REINITIALIZE
+    if (--reinit_count > 0) {
+        prefetch_status = PREFETCHSTATUS_UNKNOWN;
+        goto reinitialize;
+    }
+#endif
+
+#if 0
+    if (nativeWindow != NULL) {
+        ANativeWindow_release(nativeWindow);
+    }
+#endif
+
+close:
+    if (fd >= 0) {
+        (void) close(fd);
+    }
+
+    return EXIT_SUCCESS;
+}