Home | History | Annotate | Download | only in media
      1 /*
      2  * Copyright (C) 2008 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.media;
     18 
     19 
     20 import java.io.FileDescriptor;
     21 import java.lang.ref.WeakReference;
     22 import java.lang.CloneNotSupportedException;
     23 
     24 import android.content.res.AssetFileDescriptor;
     25 import android.os.Looper;
     26 import android.os.Handler;
     27 import android.os.Message;
     28 import android.util.AndroidRuntimeException;
     29 import android.util.Log;
     30 
     31 /**
     32  * JetPlayer provides access to JET content playback and control.
     33  *
     34  * <p>Please refer to the JET Creator User Manual for a presentation of the JET interactive
     35  * music concept and how to use the JetCreator tool to create content to be player by JetPlayer.
     36  *
     37  * <p>Use of the JetPlayer class is based around the playback of a number of JET segments
     38  * sequentially added to a playback FIFO queue. The rendering of the MIDI content stored in each
     39  * segment can be dynamically affected by two mechanisms:
     40  * <ul>
     41  * <li>tracks in a segment can be muted or unmuted at any moment, individually or through
     42  *    a mask (to change the mute state of multiple tracks at once)</li>
     43  * <li>parts of tracks in a segment can be played at predefined points in the segment, in order
     44  *    to maintain synchronization with the other tracks in the segment. This is achieved through
     45  *    the notion of "clips", which can be triggered at any time, but that will play only at the
     46  *    right time, as authored in the corresponding JET file.</li>
     47  * </ul>
     48  * As a result of the rendering and playback of the JET segments, the user of the JetPlayer instance
     49  * can receive notifications from the JET engine relative to:
     50  * <ul>
     51  * <li>the playback state,</li>
     52  * <li>the number of segments left to play in the queue,</li>
     53  * <li>application controller events (CC80-83) to mark points in the MIDI segments.</li>
     54  * </ul>
     55  * Use {@link #getJetPlayer()} to construct a JetPlayer instance. JetPlayer is a singleton class.
     56  * </p>
     57  *
     58  * <div class="special reference">
     59  * <h3>Developer Guides</h3>
     60  * <p>For more information about how to use JetPlayer, read the
     61  * <a href="{@docRoot}guide/topics/media/jetplayer.html">JetPlayer</a> developer guide.</p></div>
     62  */
     63 public class JetPlayer
     64 {
     65     //--------------------------------------------
     66     // Constants
     67     //------------------------
     68     /**
     69      * The maximum number of simultaneous tracks. Use {@link #getMaxTracks()} to
     70      * access this value.
     71      */
     72     private static int MAXTRACKS = 32;
     73 
     74     // to keep in sync with the JetPlayer class constants
     75     // defined in frameworks/base/include/media/JetPlayer.h
     76     private static final int JET_EVENT                   = 1;
     77     private static final int JET_USERID_UPDATE           = 2;
     78     private static final int JET_NUMQUEUEDSEGMENT_UPDATE = 3;
     79     private static final int JET_PAUSE_UPDATE            = 4;
     80 
     81     // to keep in sync with external/sonivox/arm-wt-22k/lib_src/jet_data.h
     82     // Encoding of event information on 32 bits
     83     private static final int JET_EVENT_VAL_MASK    = 0x0000007f; // mask for value
     84     private static final int JET_EVENT_CTRL_MASK   = 0x00003f80; // mask for controller
     85     private static final int JET_EVENT_CHAN_MASK   = 0x0003c000; // mask for channel
     86     private static final int JET_EVENT_TRACK_MASK  = 0x00fc0000; // mask for track number
     87     private static final int JET_EVENT_SEG_MASK    = 0xff000000; // mask for segment ID
     88     private static final int JET_EVENT_CTRL_SHIFT  = 7;  // shift to get controller number to bit 0
     89     private static final int JET_EVENT_CHAN_SHIFT  = 14; // shift to get MIDI channel to bit 0
     90     private static final int JET_EVENT_TRACK_SHIFT = 18; // shift to get track ID to bit 0
     91     private static final int JET_EVENT_SEG_SHIFT   = 24; // shift to get segment ID to bit 0
     92 
     93     // to keep in sync with values used in external/sonivox/arm-wt-22k/Android.mk
     94     // Jet rendering audio parameters
     95     private static final int JET_OUTPUT_RATE = 22050; // _SAMPLE_RATE_22050 in Android.mk
     96     private static final int JET_OUTPUT_CHANNEL_CONFIG =
     97             AudioFormat.CHANNEL_OUT_STEREO; // NUM_OUTPUT_CHANNELS=2 in Android.mk
     98 
     99 
    100     //--------------------------------------------
    101     // Member variables
    102     //------------------------
    103     /**
    104      * Handler for jet events and status updates coming from the native code
    105      */
    106     private NativeEventHandler mEventHandler = null;
    107 
    108     /**
    109      * Looper associated with the thread that creates the AudioTrack instance
    110      */
    111     private Looper mInitializationLooper = null;
    112 
    113     /**
    114      * Lock to protect the event listener updates against event notifications
    115      */
    116     private final Object mEventListenerLock = new Object();
    117 
    118     private OnJetEventListener mJetEventListener = null;
    119 
    120     private static JetPlayer singletonRef;
    121 
    122 
    123     //--------------------------------
    124     // Used exclusively by native code
    125     //--------------------
    126     /**
    127      * Accessed by native methods: provides access to C++ JetPlayer object
    128      */
    129     @SuppressWarnings("unused")
    130     private long mNativePlayerInJavaObj;
    131 
    132 
    133     //--------------------------------------------
    134     // Constructor, finalize
    135     //------------------------
    136     /**
    137      * Factory method for the JetPlayer class.
    138      * @return the singleton JetPlayer instance
    139      */
    140     public static JetPlayer getJetPlayer() {
    141         if (singletonRef == null) {
    142             singletonRef = new JetPlayer();
    143         }
    144         return singletonRef;
    145     }
    146 
    147     /**
    148      * Cloning a JetPlayer instance is not supported. Calling clone() will generate an exception.
    149      */
    150     public Object clone() throws CloneNotSupportedException {
    151         // JetPlayer is a singleton class,
    152         // so you can't clone a JetPlayer instance
    153         throw new CloneNotSupportedException();
    154     }
    155 
    156 
    157     private JetPlayer() {
    158 
    159         // remember which looper is associated with the JetPlayer instanciation
    160         if ((mInitializationLooper = Looper.myLooper()) == null) {
    161             mInitializationLooper = Looper.getMainLooper();
    162         }
    163 
    164         int buffSizeInBytes = AudioTrack.getMinBufferSize(JET_OUTPUT_RATE,
    165                 JET_OUTPUT_CHANNEL_CONFIG, AudioFormat.ENCODING_PCM_16BIT);
    166 
    167         if ((buffSizeInBytes != AudioTrack.ERROR)
    168                 && (buffSizeInBytes != AudioTrack.ERROR_BAD_VALUE)) {
    169 
    170             native_setup(new WeakReference<JetPlayer>(this),
    171                     JetPlayer.getMaxTracks(),
    172                     // bytes to frame conversion:
    173                     // 1200 == minimum buffer size in frames on generation 1 hardware
    174                     Math.max(1200, buffSizeInBytes /
    175                             (AudioFormat.getBytesPerSample(AudioFormat.ENCODING_PCM_16BIT) *
    176                             2 /*channels*/)));
    177         }
    178     }
    179 
    180 
    181     protected void finalize() {
    182         native_finalize();
    183     }
    184 
    185 
    186     /**
    187      * Stops the current JET playback, and releases all associated native resources.
    188      * The object can no longer be used and the reference should be set to null
    189      * after a call to release().
    190      */
    191     public void release() {
    192         native_release();
    193         singletonRef = null;
    194     }
    195 
    196 
    197     //--------------------------------------------
    198     // Getters
    199     //------------------------
    200     /**
    201      * Returns the maximum number of simultaneous MIDI tracks supported by JetPlayer
    202      */
    203     public static int getMaxTracks() {
    204         return JetPlayer.MAXTRACKS;
    205     }
    206 
    207 
    208     //--------------------------------------------
    209     // Jet functionality
    210     //------------------------
    211     /**
    212      * Loads a .jet file from a given path.
    213      * @param path the path to the .jet file, for instance "/sdcard/mygame/music.jet".
    214      * @return true if loading the .jet file was successful, false if loading failed.
    215      */
    216     public boolean loadJetFile(String path) {
    217         return native_loadJetFromFile(path);
    218     }
    219 
    220 
    221     /**
    222      * Loads a .jet file from an asset file descriptor.
    223      * @param afd the asset file descriptor.
    224      * @return true if loading the .jet file was successful, false if loading failed.
    225      */
    226     public boolean loadJetFile(AssetFileDescriptor afd) {
    227         long len = afd.getLength();
    228         if (len < 0) {
    229             throw new AndroidRuntimeException("no length for fd");
    230         }
    231         return native_loadJetFromFileD(
    232                 afd.getFileDescriptor(), afd.getStartOffset(), len);
    233     }
    234 
    235     /**
    236      * Closes the resource containing the JET content.
    237      * @return true if successfully closed, false otherwise.
    238      */
    239     public boolean closeJetFile() {
    240         return native_closeJetFile();
    241     }
    242 
    243 
    244     /**
    245      * Starts playing the JET segment queue.
    246      * @return true if rendering and playback is successfully started, false otherwise.
    247      */
    248     public boolean play() {
    249         return native_playJet();
    250     }
    251 
    252 
    253     /**
    254      * Pauses the playback of the JET segment queue.
    255      * @return true if rendering and playback is successfully paused, false otherwise.
    256      */
    257     public boolean pause() {
    258         return native_pauseJet();
    259     }
    260 
    261 
    262     /**
    263      * Queues the specified segment in the JET queue.
    264      * @param segmentNum the identifier of the segment.
    265      * @param libNum the index of the sound bank associated with the segment. Use -1 to indicate
    266      *    that no sound bank (DLS file) is associated with this segment, in which case JET will use
    267      *    the General MIDI library.
    268      * @param repeatCount the number of times the segment will be repeated. 0 means the segment will
    269      *    only play once. -1 means the segment will repeat indefinitely.
    270      * @param transpose the amount of pitch transposition. Set to 0 for normal playback.
    271      *    Range is -12 to +12.
    272      * @param muteFlags a bitmask to specify which MIDI tracks will be muted during playback. Bit 0
    273      *    affects track 0, bit 1 affects track 1 etc.
    274      * @param userID a value specified by the application that uniquely identifies the segment.
    275      *    this value is received in the
    276      *    {@link OnJetEventListener#onJetUserIdUpdate(JetPlayer, int, int)} event listener method.
    277      *    Normally, the application will keep a byte value that is incremented each time a new
    278      *    segment is queued up. This can be used to look up any special characteristics of that
    279      *    track including trigger clips and mute flags.
    280      * @return true if the segment was successfully queued, false if the queue is full or if the
    281      *    parameters are invalid.
    282      */
    283     public boolean queueJetSegment(int segmentNum, int libNum, int repeatCount,
    284         int transpose, int muteFlags, byte userID) {
    285         return native_queueJetSegment(segmentNum, libNum, repeatCount,
    286                 transpose, muteFlags, userID);
    287     }
    288 
    289 
    290     /**
    291      * Queues the specified segment in the JET queue.
    292      * @param segmentNum the identifier of the segment.
    293      * @param libNum the index of the soundbank associated with the segment. Use -1 to indicate that
    294      *    no sound bank (DLS file) is associated with this segment, in which case JET will use
    295      *    the General MIDI library.
    296      * @param repeatCount the number of times the segment will be repeated. 0 means the segment will
    297      *    only play once. -1 means the segment will repeat indefinitely.
    298      * @param transpose the amount of pitch transposition. Set to 0 for normal playback.
    299      *    Range is -12 to +12.
    300      * @param muteArray an array of booleans to specify which MIDI tracks will be muted during
    301      *    playback. The value at index 0 affects track 0, value at index 1 affects track 1 etc.
    302      *    The length of the array must be {@link #getMaxTracks()} for the call to succeed.
    303      * @param userID a value specified by the application that uniquely identifies the segment.
    304      *    this value is received in the
    305      *    {@link OnJetEventListener#onJetUserIdUpdate(JetPlayer, int, int)} event listener method.
    306      *    Normally, the application will keep a byte value that is incremented each time a new
    307      *    segment is queued up. This can be used to look up any special characteristics of that
    308      *    track including trigger clips and mute flags.
    309      * @return true if the segment was successfully queued, false if the queue is full or if the
    310      *    parameters are invalid.
    311      */
    312     public boolean queueJetSegmentMuteArray(int segmentNum, int libNum, int repeatCount,
    313             int transpose, boolean[] muteArray, byte userID) {
    314         if (muteArray.length != JetPlayer.getMaxTracks()) {
    315             return false;
    316         }
    317         return native_queueJetSegmentMuteArray(segmentNum, libNum, repeatCount,
    318                 transpose, muteArray, userID);
    319     }
    320 
    321 
    322     /**
    323      * Modifies the mute flags.
    324      * @param muteFlags a bitmask to specify which MIDI tracks are muted. Bit 0 affects track 0,
    325      *    bit 1 affects track 1 etc.
    326      * @param sync if false, the new mute flags will be applied as soon as possible by the JET
    327      *    render and playback engine. If true, the mute flags will be updated at the start of the
    328      *    next segment. If the segment is repeated, the flags will take effect the next time
    329      *    segment is repeated.
    330      * @return true if the mute flags were successfully updated, false otherwise.
    331      */
    332     public boolean setMuteFlags(int muteFlags, boolean sync) {
    333         return native_setMuteFlags(muteFlags, sync);
    334     }
    335 
    336 
    337     /**
    338      * Modifies the mute flags for the current active segment.
    339      * @param muteArray an array of booleans to specify which MIDI tracks are muted. The value at
    340      *    index 0 affects track 0, value at index 1 affects track 1 etc.
    341      *    The length of the array must be {@link #getMaxTracks()} for the call to succeed.
    342      * @param sync if false, the new mute flags will be applied as soon as possible by the JET
    343      *    render and playback engine. If true, the mute flags will be updated at the start of the
    344      *    next segment. If the segment is repeated, the flags will take effect the next time
    345      *    segment is repeated.
    346      * @return true if the mute flags were successfully updated, false otherwise.
    347      */
    348     public boolean setMuteArray(boolean[] muteArray, boolean sync) {
    349         if(muteArray.length != JetPlayer.getMaxTracks())
    350             return false;
    351         return native_setMuteArray(muteArray, sync);
    352     }
    353 
    354 
    355     /**
    356      * Mutes or unmutes a single track.
    357      * @param trackId the index of the track to mute.
    358      * @param muteFlag set to true to mute, false to unmute.
    359      * @param sync if false, the new mute flags will be applied as soon as possible by the JET
    360      *    render and playback engine. If true, the mute flag will be updated at the start of the
    361      *    next segment. If the segment is repeated, the flag will take effect the next time
    362      *    segment is repeated.
    363      * @return true if the mute flag was successfully updated, false otherwise.
    364      */
    365     public boolean setMuteFlag(int trackId, boolean muteFlag, boolean sync) {
    366         return native_setMuteFlag(trackId, muteFlag, sync);
    367     }
    368 
    369 
    370     /**
    371      * Schedules the playback of a clip.
    372      * This will automatically update the mute flags in sync with the JET Clip Marker (controller
    373      * 103). The parameter clipID must be in the range of 0-63. After the call to triggerClip, when
    374      * JET next encounters a controller event 103 with bits 0-5 of the value equal to clipID and
    375      * bit 6 set to 1, it will automatically unmute the track containing the controller event.
    376      * When JET encounters the complementary controller event 103 with bits 0-5 of the value equal
    377      * to clipID and bit 6 set to 0, it will mute the track again.
    378      * @param clipId the identifier of the clip to trigger.
    379      * @return true if the clip was successfully triggered, false otherwise.
    380      */
    381     public boolean triggerClip(int clipId) {
    382         return native_triggerClip(clipId);
    383     }
    384 
    385 
    386     /**
    387      * Empties the segment queue, and clears all clips that are scheduled for playback.
    388      * @return true if the queue was successfully cleared, false otherwise.
    389      */
    390     public boolean clearQueue() {
    391         return native_clearQueue();
    392     }
    393 
    394 
    395     //---------------------------------------------------------
    396     // Internal class to handle events posted from native code
    397     //------------------------
    398     private class NativeEventHandler extends Handler
    399     {
    400         private JetPlayer mJet;
    401 
    402         public NativeEventHandler(JetPlayer jet, Looper looper) {
    403             super(looper);
    404             mJet = jet;
    405         }
    406 
    407         @Override
    408         public void handleMessage(Message msg) {
    409             OnJetEventListener listener = null;
    410             synchronized (mEventListenerLock) {
    411                 listener = mJet.mJetEventListener;
    412             }
    413             switch(msg.what) {
    414             case JET_EVENT:
    415                 if (listener != null) {
    416                     // call the appropriate listener after decoding the event parameters
    417                     // encoded in msg.arg1
    418                     mJetEventListener.onJetEvent(
    419                             mJet,
    420                             (short)((msg.arg1 & JET_EVENT_SEG_MASK)   >> JET_EVENT_SEG_SHIFT),
    421                             (byte) ((msg.arg1 & JET_EVENT_TRACK_MASK) >> JET_EVENT_TRACK_SHIFT),
    422                             // JETCreator channel numbers start at 1, but the index starts at 0
    423                             // in the .jet files
    424                             (byte)(((msg.arg1 & JET_EVENT_CHAN_MASK)  >> JET_EVENT_CHAN_SHIFT) + 1),
    425                             (byte) ((msg.arg1 & JET_EVENT_CTRL_MASK)  >> JET_EVENT_CTRL_SHIFT),
    426                             (byte)  (msg.arg1 & JET_EVENT_VAL_MASK) );
    427                 }
    428                 return;
    429             case JET_USERID_UPDATE:
    430                 if (listener != null) {
    431                     listener.onJetUserIdUpdate(mJet, msg.arg1, msg.arg2);
    432                 }
    433                 return;
    434             case JET_NUMQUEUEDSEGMENT_UPDATE:
    435                 if (listener != null) {
    436                     listener.onJetNumQueuedSegmentUpdate(mJet, msg.arg1);
    437                 }
    438                 return;
    439             case JET_PAUSE_UPDATE:
    440                 if (listener != null)
    441                     listener.onJetPauseUpdate(mJet, msg.arg1);
    442                 return;
    443 
    444             default:
    445                 loge("Unknown message type " + msg.what);
    446                 return;
    447             }
    448         }
    449     }
    450 
    451 
    452     //--------------------------------------------
    453     // Jet event listener
    454     //------------------------
    455     /**
    456      * Sets the listener JetPlayer notifies when a JET event is generated by the rendering and
    457      * playback engine.
    458      * Notifications will be received in the same thread as the one in which the JetPlayer
    459      * instance was created.
    460      * @param listener
    461      */
    462     public void setEventListener(OnJetEventListener listener) {
    463         setEventListener(listener, null);
    464     }
    465 
    466     /**
    467      * Sets the listener JetPlayer notifies when a JET event is generated by the rendering and
    468      * playback engine.
    469      * Use this method to receive JET events in the Handler associated with another
    470      * thread than the one in which you created the JetPlayer instance.
    471      * @param listener
    472      * @param handler the Handler that will receive the event notification messages.
    473      */
    474     public void setEventListener(OnJetEventListener listener, Handler handler) {
    475         synchronized(mEventListenerLock) {
    476 
    477             mJetEventListener = listener;
    478 
    479             if (listener != null) {
    480                 if (handler != null) {
    481                     mEventHandler = new NativeEventHandler(this, handler.getLooper());
    482                 } else {
    483                     // no given handler, use the looper the AudioTrack was created in
    484                     mEventHandler = new NativeEventHandler(this, mInitializationLooper);
    485                 }
    486             } else {
    487                 mEventHandler = null;
    488             }
    489 
    490         }
    491     }
    492 
    493 
    494     /**
    495      * Handles the notification when the JET engine generates an event.
    496      */
    497     public interface OnJetEventListener {
    498         /**
    499          * Callback for when the JET engine generates a new event.
    500          *
    501          * @param player the JET player the event is coming from
    502          * @param segment 8 bit unsigned value
    503          * @param track 6 bit unsigned value
    504          * @param channel 4 bit unsigned value
    505          * @param controller 7 bit unsigned value
    506          * @param value 7 bit unsigned value
    507          */
    508         void onJetEvent(JetPlayer player,
    509                 short segment, byte track, byte channel, byte controller, byte value);
    510         /**
    511          * Callback for when JET's currently playing segment's userID is updated.
    512          *
    513          * @param player the JET player the status update is coming from
    514          * @param userId the ID of the currently playing segment
    515          * @param repeatCount the repetition count for the segment (0 means it plays once)
    516          */
    517         void onJetUserIdUpdate(JetPlayer player, int userId, int repeatCount);
    518 
    519         /**
    520          * Callback for when JET's number of queued segments is updated.
    521          *
    522          * @param player the JET player the status update is coming from
    523          * @param nbSegments the number of segments in the JET queue
    524          */
    525         void onJetNumQueuedSegmentUpdate(JetPlayer player, int nbSegments);
    526 
    527         /**
    528          * Callback for when JET pause state is updated.
    529          *
    530          * @param player the JET player the status update is coming from
    531          * @param paused indicates whether JET is paused (1) or not (0)
    532          */
    533         void onJetPauseUpdate(JetPlayer player, int paused);
    534     }
    535 
    536 
    537     //--------------------------------------------
    538     // Native methods
    539     //------------------------
    540     private native final boolean native_setup(Object Jet_this,
    541                 int maxTracks, int trackBufferSize);
    542     private native final void    native_finalize();
    543     private native final void    native_release();
    544     private native final boolean native_loadJetFromFile(String pathToJetFile);
    545     private native final boolean native_loadJetFromFileD(FileDescriptor fd, long offset, long len);
    546     private native final boolean native_closeJetFile();
    547     private native final boolean native_playJet();
    548     private native final boolean native_pauseJet();
    549     private native final boolean native_queueJetSegment(int segmentNum, int libNum,
    550             int repeatCount, int transpose, int muteFlags, byte userID);
    551     private native final boolean native_queueJetSegmentMuteArray(int segmentNum, int libNum,
    552             int repeatCount, int transpose, boolean[] muteArray, byte userID);
    553     private native final boolean native_setMuteFlags(int muteFlags, boolean sync);
    554     private native final boolean native_setMuteArray(boolean[]muteArray, boolean sync);
    555     private native final boolean native_setMuteFlag(int trackId, boolean muteFlag, boolean sync);
    556     private native final boolean native_triggerClip(int clipId);
    557     private native final boolean native_clearQueue();
    558 
    559     //---------------------------------------------------------
    560     // Called exclusively by native code
    561     //--------------------
    562     @SuppressWarnings("unused")
    563     private static void postEventFromNative(Object jetplayer_ref,
    564             int what, int arg1, int arg2) {
    565         //logd("Event posted from the native side: event="+ what + " args="+ arg1+" "+arg2);
    566         JetPlayer jet = (JetPlayer)((WeakReference)jetplayer_ref).get();
    567 
    568         if ((jet != null) && (jet.mEventHandler != null)) {
    569             Message m =
    570                 jet.mEventHandler.obtainMessage(what, arg1, arg2, null);
    571             jet.mEventHandler.sendMessage(m);
    572         }
    573 
    574     }
    575 
    576 
    577     //---------------------------------------------------------
    578     // Utils
    579     //--------------------
    580     private final static String TAG = "JetPlayer-J";
    581 
    582     private static void logd(String msg) {
    583         Log.d(TAG, "[ android.media.JetPlayer ] " + msg);
    584     }
    585 
    586     private static void loge(String msg) {
    587         Log.e(TAG, "[ android.media.JetPlayer ] " + msg);
    588     }
    589 
    590 }
    591