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 */ getJetPlayer()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 */ clone()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 JetPlayer()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 finalize()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 */ release()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 */ getMaxTracks()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 */ loadJetFile(String path)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 */ loadJetFile(AssetFileDescriptor afd)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 */ closeJetFile()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 */ play()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 */ pause()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 */ queueJetSegment(int segmentNum, int libNum, int repeatCount, int transpose, int muteFlags, byte userID)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 */ queueJetSegmentMuteArray(int segmentNum, int libNum, int repeatCount, int transpose, boolean[] muteArray, byte userID)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 */ setMuteFlags(int muteFlags, boolean sync)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 */ setMuteArray(boolean[] muteArray, boolean sync)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 */ setMuteFlag(int trackId, boolean muteFlag, boolean sync)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 */ triggerClip(int clipId)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 */ clearQueue()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 NativeEventHandler(JetPlayer jet, Looper looper)402 public NativeEventHandler(JetPlayer jet, Looper looper) { 403 super(looper); 404 mJet = jet; 405 } 406 407 @Override handleMessage(Message msg)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 */ setEventListener(OnJetEventListener listener)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 */ setEventListener(OnJetEventListener listener, Handler handler)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 */ onJetEvent(JetPlayer player, short segment, byte track, byte channel, byte controller, byte value)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 */ onJetUserIdUpdate(JetPlayer player, int userId, int repeatCount)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 */ onJetNumQueuedSegmentUpdate(JetPlayer player, int nbSegments)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 */ onJetPauseUpdate(JetPlayer player, int paused)533 void onJetPauseUpdate(JetPlayer player, int paused); 534 } 535 536 537 //-------------------------------------------- 538 // Native methods 539 //------------------------ native_setup(Object Jet_this, int maxTracks, int trackBufferSize)540 private native final boolean native_setup(Object Jet_this, 541 int maxTracks, int trackBufferSize); native_finalize()542 private native final void native_finalize(); native_release()543 private native final void native_release(); native_loadJetFromFile(String pathToJetFile)544 private native final boolean native_loadJetFromFile(String pathToJetFile); native_loadJetFromFileD(FileDescriptor fd, long offset, long len)545 private native final boolean native_loadJetFromFileD(FileDescriptor fd, long offset, long len); native_closeJetFile()546 private native final boolean native_closeJetFile(); native_playJet()547 private native final boolean native_playJet(); native_pauseJet()548 private native final boolean native_pauseJet(); native_queueJetSegment(int segmentNum, int libNum, int repeatCount, int transpose, int muteFlags, byte userID)549 private native final boolean native_queueJetSegment(int segmentNum, int libNum, 550 int repeatCount, int transpose, int muteFlags, byte userID); native_queueJetSegmentMuteArray(int segmentNum, int libNum, int repeatCount, int transpose, boolean[] muteArray, byte userID)551 private native final boolean native_queueJetSegmentMuteArray(int segmentNum, int libNum, 552 int repeatCount, int transpose, boolean[] muteArray, byte userID); native_setMuteFlags(int muteFlags, boolean sync)553 private native final boolean native_setMuteFlags(int muteFlags, boolean sync); native_setMuteArray(boolean[]muteArray, boolean sync)554 private native final boolean native_setMuteArray(boolean[]muteArray, boolean sync); native_setMuteFlag(int trackId, boolean muteFlag, boolean sync)555 private native final boolean native_setMuteFlag(int trackId, boolean muteFlag, boolean sync); native_triggerClip(int clipId)556 private native final boolean native_triggerClip(int clipId); native_clearQueue()557 private native final boolean native_clearQueue(); 558 559 //--------------------------------------------------------- 560 // Called exclusively by native code 561 //-------------------- 562 @SuppressWarnings("unused") postEventFromNative(Object jetplayer_ref, int what, int arg1, int arg2)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 logd(String msg)582 private static void logd(String msg) { 583 Log.d(TAG, "[ android.media.JetPlayer ] " + msg); 584 } 585 loge(String msg)586 private static void loge(String msg) { 587 Log.e(TAG, "[ android.media.JetPlayer ] " + msg); 588 } 589 590 } 591