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