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