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 int 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: sample format is ENCODING_PCM_16BIT, 2 channels
    173                     // 1200 == minimum buffer size in frames on generation 1 hardware
    174                     Math.max(1200, buffSizeInBytes / 4));
    175         }
    176     }
    177 
    178 
    179     protected void finalize() {
    180         native_finalize();
    181     }
    182 
    183 
    184     /**
    185      * Stops the current JET playback, and releases all associated native resources.
    186      * The object can no longer be used and the reference should be set to null
    187      * after a call to release().
    188      */
    189     public void release() {
    190         native_release();
    191         singletonRef = null;
    192     }
    193 
    194 
    195     //--------------------------------------------
    196     // Getters
    197     //------------------------
    198     /**
    199      * Returns the maximum number of simultaneous MIDI tracks supported by JetPlayer
    200      */
    201     public static int getMaxTracks() {
    202         return JetPlayer.MAXTRACKS;
    203     }
    204 
    205 
    206     //--------------------------------------------
    207     // Jet functionality
    208     //------------------------
    209     /**
    210      * Loads a .jet file from a given path.
    211      * @param path the path to the .jet file, for instance "/sdcard/mygame/music.jet".
    212      * @return true if loading the .jet file was successful, false if loading failed.
    213      */
    214     public boolean loadJetFile(String path) {
    215         return native_loadJetFromFile(path);
    216     }
    217 
    218 
    219     /**
    220      * Loads a .jet file from an asset file descriptor.
    221      * @param afd the asset file descriptor.
    222      * @return true if loading the .jet file was successful, false if loading failed.
    223      */
    224     public boolean loadJetFile(AssetFileDescriptor afd) {
    225         long len = afd.getLength();
    226         if (len < 0) {
    227             throw new AndroidRuntimeException("no length for fd");
    228         }
    229         return native_loadJetFromFileD(
    230                 afd.getFileDescriptor(), afd.getStartOffset(), len);
    231     }
    232 
    233     /**
    234      * Closes the resource containing the JET content.
    235      * @return true if successfully closed, false otherwise.
    236      */
    237     public boolean closeJetFile() {
    238         return native_closeJetFile();
    239     }
    240 
    241 
    242     /**
    243      * Starts playing the JET segment queue.
    244      * @return true if rendering and playback is successfully started, false otherwise.
    245      */
    246     public boolean play() {
    247         return native_playJet();
    248     }
    249 
    250 
    251     /**
    252      * Pauses the playback of the JET segment queue.
    253      * @return true if rendering and playback is successfully paused, false otherwise.
    254      */
    255     public boolean pause() {
    256         return native_pauseJet();
    257     }
    258 
    259 
    260     /**
    261      * Queues the specified segment in the JET queue.
    262      * @param segmentNum the identifier of the segment.
    263      * @param libNum the index of the sound bank associated with the segment. Use -1 to indicate
    264      *    that no sound bank (DLS file) is associated with this segment, in which case JET will use
    265      *    the General MIDI library.
    266      * @param repeatCount the number of times the segment will be repeated. 0 means the segment will
    267      *    only play once. -1 means the segment will repeat indefinitely.
    268      * @param transpose the amount of pitch transposition. Set to 0 for normal playback.
    269      *    Range is -12 to +12.
    270      * @param muteFlags a bitmask to specify which MIDI tracks will be muted during playback. Bit 0
    271      *    affects track 0, bit 1 affects track 1 etc.
    272      * @param userID a value specified by the application that uniquely identifies the segment.
    273      *    this value is received in the
    274      *    {@link OnJetEventListener#onJetUserIdUpdate(JetPlayer, int, int)} event listener method.
    275      *    Normally, the application will keep a byte value that is incremented each time a new
    276      *    segment is queued up. This can be used to look up any special characteristics of that
    277      *    track including trigger clips and mute flags.
    278      * @return true if the segment was successfully queued, false if the queue is full or if the
    279      *    parameters are invalid.
    280      */
    281     public boolean queueJetSegment(int segmentNum, int libNum, int repeatCount,
    282         int transpose, int muteFlags, byte userID) {
    283         return native_queueJetSegment(segmentNum, libNum, repeatCount,
    284                 transpose, muteFlags, userID);
    285     }
    286 
    287 
    288     /**
    289      * Queues the specified segment in the JET queue.
    290      * @param segmentNum the identifier of the segment.
    291      * @param libNum the index of the soundbank associated with the segment. Use -1 to indicate that
    292      *    no sound bank (DLS file) is associated with this segment, in which case JET will use
    293      *    the General MIDI library.
    294      * @param repeatCount the number of times the segment will be repeated. 0 means the segment will
    295      *    only play once. -1 means the segment will repeat indefinitely.
    296      * @param transpose the amount of pitch transposition. Set to 0 for normal playback.
    297      *    Range is -12 to +12.
    298      * @param muteArray an array of booleans to specify which MIDI tracks will be muted during
    299      *    playback. The value at index 0 affects track 0, value at index 1 affects track 1 etc.
    300      *    The length of the array must be {@link #getMaxTracks()} for the call to succeed.
    301      * @param userID a value specified by the application that uniquely identifies the segment.
    302      *    this value is received in the
    303      *    {@link OnJetEventListener#onJetUserIdUpdate(JetPlayer, int, int)} event listener method.
    304      *    Normally, the application will keep a byte value that is incremented each time a new
    305      *    segment is queued up. This can be used to look up any special characteristics of that
    306      *    track including trigger clips and mute flags.
    307      * @return true if the segment was successfully queued, false if the queue is full or if the
    308      *    parameters are invalid.
    309      */
    310     public boolean queueJetSegmentMuteArray(int segmentNum, int libNum, int repeatCount,
    311             int transpose, boolean[] muteArray, byte userID) {
    312         if (muteArray.length != JetPlayer.getMaxTracks()) {
    313             return false;
    314         }
    315         return native_queueJetSegmentMuteArray(segmentNum, libNum, repeatCount,
    316                 transpose, muteArray, userID);
    317     }
    318 
    319 
    320     /**
    321      * Modifies the mute flags.
    322      * @param muteFlags a bitmask to specify which MIDI tracks are muted. Bit 0 affects track 0,
    323      *    bit 1 affects track 1 etc.
    324      * @param sync if false, the new mute flags will be applied as soon as possible by the JET
    325      *    render and playback engine. If true, the mute flags will be updated at the start of the
    326      *    next segment. If the segment is repeated, the flags will take effect the next time
    327      *    segment is repeated.
    328      * @return true if the mute flags were successfully updated, false otherwise.
    329      */
    330     public boolean setMuteFlags(int muteFlags, boolean sync) {
    331         return native_setMuteFlags(muteFlags, sync);
    332     }
    333 
    334 
    335     /**
    336      * Modifies the mute flags for the current active segment.
    337      * @param muteArray an array of booleans to specify which MIDI tracks are muted. The value at
    338      *    index 0 affects track 0, value at index 1 affects track 1 etc.
    339      *    The length of the array must be {@link #getMaxTracks()} for the call to succeed.
    340      * @param sync if false, the new mute flags will be applied as soon as possible by the JET
    341      *    render and playback engine. If true, the mute flags will be updated at the start of the
    342      *    next segment. If the segment is repeated, the flags will take effect the next time
    343      *    segment is repeated.
    344      * @return true if the mute flags were successfully updated, false otherwise.
    345      */
    346     public boolean setMuteArray(boolean[] muteArray, boolean sync) {
    347         if(muteArray.length != JetPlayer.getMaxTracks())
    348             return false;
    349         return native_setMuteArray(muteArray, sync);
    350     }
    351 
    352 
    353     /**
    354      * Mutes or unmutes a single track.
    355      * @param trackId the index of the track to mute.
    356      * @param muteFlag set to true to mute, false to unmute.
    357      * @param sync if false, the new mute flags will be applied as soon as possible by the JET
    358      *    render and playback engine. If true, the mute flag will be updated at the start of the
    359      *    next segment. If the segment is repeated, the flag will take effect the next time
    360      *    segment is repeated.
    361      * @return true if the mute flag was successfully updated, false otherwise.
    362      */
    363     public boolean setMuteFlag(int trackId, boolean muteFlag, boolean sync) {
    364         return native_setMuteFlag(trackId, muteFlag, sync);
    365     }
    366 
    367 
    368     /**
    369      * Schedules the playback of a clip.
    370      * This will automatically update the mute flags in sync with the JET Clip Marker (controller
    371      * 103). The parameter clipID must be in the range of 0-63. After the call to triggerClip, when
    372      * JET next encounters a controller event 103 with bits 0-5 of the value equal to clipID and
    373      * bit 6 set to 1, it will automatically unmute the track containing the controller event.
    374      * When JET encounters the complementary controller event 103 with bits 0-5 of the value equal
    375      * to clipID and bit 6 set to 0, it will mute the track again.
    376      * @param clipId the identifier of the clip to trigger.
    377      * @return true if the clip was successfully triggered, false otherwise.
    378      */
    379     public boolean triggerClip(int clipId) {
    380         return native_triggerClip(clipId);
    381     }
    382 
    383 
    384     /**
    385      * Empties the segment queue, and clears all clips that are scheduled for playback.
    386      * @return true if the queue was successfully cleared, false otherwise.
    387      */
    388     public boolean clearQueue() {
    389         return native_clearQueue();
    390     }
    391 
    392 
    393     //---------------------------------------------------------
    394     // Internal class to handle events posted from native code
    395     //------------------------
    396     private class NativeEventHandler extends Handler
    397     {
    398         private JetPlayer mJet;
    399 
    400         public NativeEventHandler(JetPlayer jet, Looper looper) {
    401             super(looper);
    402             mJet = jet;
    403         }
    404 
    405         @Override
    406         public void handleMessage(Message msg) {
    407             OnJetEventListener listener = null;
    408             synchronized (mEventListenerLock) {
    409                 listener = mJet.mJetEventListener;
    410             }
    411             switch(msg.what) {
    412             case JET_EVENT:
    413                 if (listener != null) {
    414                     // call the appropriate listener after decoding the event parameters
    415                     // encoded in msg.arg1
    416                     mJetEventListener.onJetEvent(
    417                             mJet,
    418                             (short)((msg.arg1 & JET_EVENT_SEG_MASK)   >> JET_EVENT_SEG_SHIFT),
    419                             (byte) ((msg.arg1 & JET_EVENT_TRACK_MASK) >> JET_EVENT_TRACK_SHIFT),
    420                             // JETCreator channel numbers start at 1, but the index starts at 0
    421                             // in the .jet files
    422                             (byte)(((msg.arg1 & JET_EVENT_CHAN_MASK)  >> JET_EVENT_CHAN_SHIFT) + 1),
    423                             (byte) ((msg.arg1 & JET_EVENT_CTRL_MASK)  >> JET_EVENT_CTRL_SHIFT),
    424                             (byte)  (msg.arg1 & JET_EVENT_VAL_MASK) );
    425                 }
    426                 return;
    427             case JET_USERID_UPDATE:
    428                 if (listener != null) {
    429                     listener.onJetUserIdUpdate(mJet, msg.arg1, msg.arg2);
    430                 }
    431                 return;
    432             case JET_NUMQUEUEDSEGMENT_UPDATE:
    433                 if (listener != null) {
    434                     listener.onJetNumQueuedSegmentUpdate(mJet, msg.arg1);
    435                 }
    436                 return;
    437             case JET_PAUSE_UPDATE:
    438                 if (listener != null)
    439                     listener.onJetPauseUpdate(mJet, msg.arg1);
    440                 return;
    441 
    442             default:
    443                 loge("Unknown message type " + msg.what);
    444                 return;
    445             }
    446         }
    447     }
    448 
    449 
    450     //--------------------------------------------
    451     // Jet event listener
    452     //------------------------
    453     /**
    454      * Sets the listener JetPlayer notifies when a JET event is generated by the rendering and
    455      * playback engine.
    456      * Notifications will be received in the same thread as the one in which the JetPlayer
    457      * instance was created.
    458      * @param listener
    459      */
    460     public void setEventListener(OnJetEventListener listener) {
    461         setEventListener(listener, null);
    462     }
    463 
    464     /**
    465      * Sets the listener JetPlayer notifies when a JET event is generated by the rendering and
    466      * playback engine.
    467      * Use this method to receive JET events in the Handler associated with another
    468      * thread than the one in which you created the JetPlayer instance.
    469      * @param listener
    470      * @param handler the Handler that will receive the event notification messages.
    471      */
    472     public void setEventListener(OnJetEventListener listener, Handler handler) {
    473         synchronized(mEventListenerLock) {
    474 
    475             mJetEventListener = listener;
    476 
    477             if (listener != null) {
    478                 if (handler != null) {
    479                     mEventHandler = new NativeEventHandler(this, handler.getLooper());
    480                 } else {
    481                     // no given handler, use the looper the AudioTrack was created in
    482                     mEventHandler = new NativeEventHandler(this, mInitializationLooper);
    483                 }
    484             } else {
    485                 mEventHandler = null;
    486             }
    487 
    488         }
    489     }
    490 
    491 
    492     /**
    493      * Handles the notification when the JET engine generates an event.
    494      */
    495     public interface OnJetEventListener {
    496         /**
    497          * Callback for when the JET engine generates a new event.
    498          *
    499          * @param player the JET player the event is coming from
    500          * @param segment 8 bit unsigned value
    501          * @param track 6 bit unsigned value
    502          * @param channel 4 bit unsigned value
    503          * @param controller 7 bit unsigned value
    504          * @param value 7 bit unsigned value
    505          */
    506         void onJetEvent(JetPlayer player,
    507                 short segment, byte track, byte channel, byte controller, byte value);
    508         /**
    509          * Callback for when JET's currently playing segment's userID is updated.
    510          *
    511          * @param player the JET player the status update is coming from
    512          * @param userId the ID of the currently playing segment
    513          * @param repeatCount the repetition count for the segment (0 means it plays once)
    514          */
    515         void onJetUserIdUpdate(JetPlayer player, int userId, int repeatCount);
    516 
    517         /**
    518          * Callback for when JET's number of queued segments is updated.
    519          *
    520          * @param player the JET player the status update is coming from
    521          * @param nbSegments the number of segments in the JET queue
    522          */
    523         void onJetNumQueuedSegmentUpdate(JetPlayer player, int nbSegments);
    524 
    525         /**
    526          * Callback for when JET pause state is updated.
    527          *
    528          * @param player the JET player the status update is coming from
    529          * @param paused indicates whether JET is paused (1) or not (0)
    530          */
    531         void onJetPauseUpdate(JetPlayer player, int paused);
    532     }
    533 
    534 
    535     //--------------------------------------------
    536     // Native methods
    537     //------------------------
    538     private native final boolean native_setup(Object Jet_this,
    539                 int maxTracks, int trackBufferSize);
    540     private native final void    native_finalize();
    541     private native final void    native_release();
    542     private native final boolean native_loadJetFromFile(String pathToJetFile);
    543     private native final boolean native_loadJetFromFileD(FileDescriptor fd, long offset, long len);
    544     private native final boolean native_closeJetFile();
    545     private native final boolean native_playJet();
    546     private native final boolean native_pauseJet();
    547     private native final boolean native_queueJetSegment(int segmentNum, int libNum,
    548             int repeatCount, int transpose, int muteFlags, byte userID);
    549     private native final boolean native_queueJetSegmentMuteArray(int segmentNum, int libNum,
    550             int repeatCount, int transpose, boolean[] muteArray, byte userID);
    551     private native final boolean native_setMuteFlags(int muteFlags, boolean sync);
    552     private native final boolean native_setMuteArray(boolean[]muteArray, boolean sync);
    553     private native final boolean native_setMuteFlag(int trackId, boolean muteFlag, boolean sync);
    554     private native final boolean native_triggerClip(int clipId);
    555     private native final boolean native_clearQueue();
    556 
    557     //---------------------------------------------------------
    558     // Called exclusively by native code
    559     //--------------------
    560     @SuppressWarnings("unused")
    561     private static void postEventFromNative(Object jetplayer_ref,
    562             int what, int arg1, int arg2) {
    563         //logd("Event posted from the native side: event="+ what + " args="+ arg1+" "+arg2);
    564         JetPlayer jet = (JetPlayer)((WeakReference)jetplayer_ref).get();
    565 
    566         if ((jet != null) && (jet.mEventHandler != null)) {
    567             Message m =
    568                 jet.mEventHandler.obtainMessage(what, arg1, arg2, null);
    569             jet.mEventHandler.sendMessage(m);
    570         }
    571 
    572     }
    573 
    574 
    575     //---------------------------------------------------------
    576     // Utils
    577     //--------------------
    578     private final static String TAG = "JetPlayer-J";
    579 
    580     private static void logd(String msg) {
    581         Log.d(TAG, "[ android.media.JetPlayer ] " + msg);
    582     }
    583 
    584     private static void loge(String msg) {
    585         Log.e(TAG, "[ android.media.JetPlayer ] " + msg);
    586     }
    587 
    588 }
    589