1 /* 2 * Copyright (C) 2010 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.audiofx; 18 19 import android.annotation.SdkConstant; 20 import android.annotation.SdkConstant.SdkConstantType; 21 import android.os.Handler; 22 import android.os.Looper; 23 import android.os.Message; 24 import android.util.Log; 25 import java.io.IOException; 26 import java.lang.ref.WeakReference; 27 import java.nio.ByteOrder; 28 import java.nio.ByteBuffer; 29 import java.util.UUID; 30 31 /** 32 * AudioEffect is the base class for controlling audio effects provided by the android audio 33 * framework. 34 * <p>Applications should not use the AudioEffect class directly but one of its derived classes to 35 * control specific effects: 36 * <ul> 37 * <li> {@link android.media.audiofx.Equalizer}</li> 38 * <li> {@link android.media.audiofx.Virtualizer}</li> 39 * <li> {@link android.media.audiofx.BassBoost}</li> 40 * <li> {@link android.media.audiofx.PresetReverb}</li> 41 * <li> {@link android.media.audiofx.EnvironmentalReverb}</li> 42 * </ul> 43 * <p>To apply the audio effect to a specific AudioTrack or MediaPlayer instance, 44 * the application must specify the audio session ID of that instance when creating the AudioEffect. 45 * (see {@link android.media.MediaPlayer#getAudioSessionId()} for details on audio sessions). 46 * <p>NOTE: attaching insert effects (equalizer, bass boost, virtualizer) to the global audio output 47 * mix by use of session 0 is deprecated. 48 * <p>Creating an AudioEffect object will create the corresponding effect engine in the audio 49 * framework if no instance of the same effect type exists in the specified audio session. 50 * If one exists, this instance will be used. 51 * <p>The application creating the AudioEffect object (or a derived class) will either receive 52 * control of the effect engine or not depending on the priority parameter. If priority is higher 53 * than the priority used by the current effect engine owner, the control will be transfered to the 54 * new object. Otherwise control will remain with the previous object. In this case, the new 55 * application will be notified of changes in effect engine state or control ownership by the 56 * appropiate listener. 57 */ 58 59 public class AudioEffect { 60 static { 61 System.loadLibrary("audioeffect_jni"); native_init()62 native_init(); 63 } 64 65 private final static String TAG = "AudioEffect-JAVA"; 66 67 // effect type UUIDs are taken from hardware/libhardware/include/hardware/audio_effect.h 68 69 /** 70 * The following UUIDs define effect types corresponding to standard audio 71 * effects whose implementation and interface conform to the OpenSL ES 72 * specification. The definitions match the corresponding interface IDs in 73 * OpenSLES_IID.h 74 */ 75 76 /** 77 * UUID for environmental reverb effect 78 * @hide 79 */ 80 public static final UUID EFFECT_TYPE_ENV_REVERB = UUID 81 .fromString("c2e5d5f0-94bd-4763-9cac-4e234d06839e"); 82 /** 83 * UUID for preset reverb effect 84 * @hide 85 */ 86 public static final UUID EFFECT_TYPE_PRESET_REVERB = UUID 87 .fromString("47382d60-ddd8-11db-bf3a-0002a5d5c51b"); 88 /** 89 * UUID for equalizer effect 90 * @hide 91 */ 92 public static final UUID EFFECT_TYPE_EQUALIZER = UUID 93 .fromString("0bed4300-ddd6-11db-8f34-0002a5d5c51b"); 94 /** 95 * UUID for bass boost effect 96 * @hide 97 */ 98 public static final UUID EFFECT_TYPE_BASS_BOOST = UUID 99 .fromString("0634f220-ddd4-11db-a0fc-0002a5d5c51b"); 100 /** 101 * UUID for virtualizer effect 102 * @hide 103 */ 104 public static final UUID EFFECT_TYPE_VIRTUALIZER = UUID 105 .fromString("37cc2c00-dddd-11db-8577-0002a5d5c51b"); 106 107 /** 108 * UUID for Automatic Gain Control (AGC) audio pre-processing 109 * @hide 110 */ 111 public static final UUID EFFECT_TYPE_AGC = UUID 112 .fromString("0a8abfe0-654c-11e0-ba26-0002a5d5c51b"); 113 114 /** 115 * UUID for Acoustic Echo Canceler (AEC) audio pre-processing 116 * @hide 117 */ 118 public static final UUID EFFECT_TYPE_AEC = UUID 119 .fromString("7b491460-8d4d-11e0-bd61-0002a5d5c51b"); 120 121 /** 122 * UUID for Noise Suppressor (NS) audio pre-processing 123 * @hide 124 */ 125 public static final UUID EFFECT_TYPE_NS = UUID 126 .fromString("58b4b260-8e06-11e0-aa8e-0002a5d5c51b"); 127 128 /** 129 * Null effect UUID. Used when the UUID for effect type of 130 * @hide 131 */ 132 public static final UUID EFFECT_TYPE_NULL = UUID 133 .fromString("ec7178ec-e5e1-4432-a3f4-4657e6795210"); 134 135 /** 136 * State of an AudioEffect object that was not successfully initialized upon 137 * creation 138 * @hide 139 */ 140 public static final int STATE_UNINITIALIZED = 0; 141 /** 142 * State of an AudioEffect object that is ready to be used. 143 * @hide 144 */ 145 public static final int STATE_INITIALIZED = 1; 146 147 // to keep in sync with 148 // frameworks/base/include/media/AudioEffect.h 149 /** 150 * Event id for engine control ownership change notification. 151 * @hide 152 */ 153 public static final int NATIVE_EVENT_CONTROL_STATUS = 0; 154 /** 155 * Event id for engine state change notification. 156 * @hide 157 */ 158 public static final int NATIVE_EVENT_ENABLED_STATUS = 1; 159 /** 160 * Event id for engine parameter change notification. 161 * @hide 162 */ 163 public static final int NATIVE_EVENT_PARAMETER_CHANGED = 2; 164 165 /** 166 * Successful operation. 167 */ 168 public static final int SUCCESS = 0; 169 /** 170 * Unspecified error. 171 */ 172 public static final int ERROR = -1; 173 /** 174 * Internal operation status. Not returned by any method. 175 */ 176 public static final int ALREADY_EXISTS = -2; 177 /** 178 * Operation failed due to bad object initialization. 179 */ 180 public static final int ERROR_NO_INIT = -3; 181 /** 182 * Operation failed due to bad parameter value. 183 */ 184 public static final int ERROR_BAD_VALUE = -4; 185 /** 186 * Operation failed because it was requested in wrong state. 187 */ 188 public static final int ERROR_INVALID_OPERATION = -5; 189 /** 190 * Operation failed due to lack of memory. 191 */ 192 public static final int ERROR_NO_MEMORY = -6; 193 /** 194 * Operation failed due to dead remote object. 195 */ 196 public static final int ERROR_DEAD_OBJECT = -7; 197 198 /** 199 * The effect descriptor contains information on a particular effect implemented in the 200 * audio framework:<br> 201 * <ul> 202 * <li>type: UUID corresponding to the OpenSL ES interface implemented by this effect</li> 203 * <li>uuid: UUID for this particular implementation</li> 204 * <li>connectMode: {@link #EFFECT_INSERT}, {@link #EFFECT_AUXILIARY} or 205 * {at_link #EFFECT_PRE_PROCESSING}</li> 206 * <li>name: human readable effect name</li> 207 * <li>implementor: human readable effect implementor name</li> 208 * </ul> 209 * The method {@link #queryEffects()} returns an array of Descriptors to facilitate effects 210 * enumeration. 211 */ 212 public static class Descriptor { 213 Descriptor()214 public Descriptor() { 215 } 216 Descriptor(String type, String uuid, String connectMode, String name, String implementor)217 public Descriptor(String type, String uuid, String connectMode, 218 String name, String implementor) { 219 this.type = UUID.fromString(type); 220 this.uuid = UUID.fromString(uuid); 221 this.connectMode = connectMode; 222 this.name = name; 223 this.implementor = implementor; 224 } 225 226 /** 227 * Indicates the generic type of the effect (Equalizer, Bass boost ...). The UUID 228 * corresponds to the OpenSL ES Interface ID for this type of effect. 229 */ 230 public UUID type; 231 /** 232 * Indicates the particular implementation of the effect in that type. Several effects 233 * can have the same type but this uuid is unique to a given implementation. 234 */ 235 public UUID uuid; 236 /** 237 * Indicates if the effect is of insert category {@link #EFFECT_INSERT}, auxiliary 238 * category {@link #EFFECT_AUXILIARY} or pre processing category 239 * {at_link #EFFECT_PRE_PROCESSING}. Insert effects (Typically an Equalizer) are applied 240 * to the entire audio source and usually not shared by several sources. Auxiliary effects 241 * (typically a reverberator) are applied to part of the signal (wet) and the effect output 242 * is added to the original signal (dry). 243 * Audio pre processing are applied to audio captured on a particular AudioRecord. 244 */ 245 public String connectMode; 246 /** 247 * Human readable effect name 248 */ 249 public String name; 250 /** 251 * Human readable effect implementor name 252 */ 253 public String implementor; 254 }; 255 256 /** 257 * Effect connection mode is insert. Specifying an audio session ID when creating the effect 258 * will insert this effect after all players in the same audio session. 259 */ 260 public static final String EFFECT_INSERT = "Insert"; 261 /** 262 * Effect connection mode is auxiliary. 263 * <p>Auxiliary effects must be created on session 0 (global output mix). In order for a 264 * MediaPlayer or AudioTrack to be fed into this effect, they must be explicitely attached to 265 * this effect and a send level must be specified. 266 * <p>Use the effect ID returned by {@link #getId()} to designate this particular effect when 267 * attaching it to the MediaPlayer or AudioTrack. 268 */ 269 public static final String EFFECT_AUXILIARY = "Auxiliary"; 270 /** 271 * Effect connection mode is pre processing. 272 * The audio pre processing effects are attached to an audio input (AudioRecord). 273 * @hide 274 */ 275 public static final String EFFECT_PRE_PROCESSING = "Pre Processing"; 276 277 // -------------------------------------------------------------------------- 278 // Member variables 279 // -------------------- 280 /** 281 * Indicates the state of the AudioEffect instance 282 */ 283 private int mState = STATE_UNINITIALIZED; 284 /** 285 * Lock to synchronize access to mState 286 */ 287 private final Object mStateLock = new Object(); 288 /** 289 * System wide unique effect ID 290 */ 291 private int mId; 292 293 // accessed by native methods 294 private int mNativeAudioEffect; 295 private int mJniData; 296 297 /** 298 * Effect descriptor 299 */ 300 private Descriptor mDescriptor; 301 302 /** 303 * Listener for effect engine state change notifications. 304 * 305 * @see #setEnableStatusListener(OnEnableStatusChangeListener) 306 */ 307 private OnEnableStatusChangeListener mEnableStatusChangeListener = null; 308 /** 309 * Listener for effect engine control ownership change notifications. 310 * 311 * @see #setControlStatusListener(OnControlStatusChangeListener) 312 */ 313 private OnControlStatusChangeListener mControlChangeStatusListener = null; 314 /** 315 * Listener for effect engine control ownership change notifications. 316 * 317 * @see #setParameterListener(OnParameterChangeListener) 318 */ 319 private OnParameterChangeListener mParameterChangeListener = null; 320 /** 321 * Lock to protect listeners updates against event notifications 322 * @hide 323 */ 324 public final Object mListenerLock = new Object(); 325 /** 326 * Handler for events coming from the native code 327 * @hide 328 */ 329 public NativeEventHandler mNativeEventHandler = null; 330 331 // -------------------------------------------------------------------------- 332 // Constructor, Finalize 333 // -------------------- 334 /** 335 * Class constructor. 336 * 337 * @param type type of effect engine created. See {@link #EFFECT_TYPE_ENV_REVERB}, 338 * {@link #EFFECT_TYPE_EQUALIZER} ... Types corresponding to 339 * built-in effects are defined by AudioEffect class. Other types 340 * can be specified provided they correspond an existing OpenSL 341 * ES interface ID and the corresponsing effect is available on 342 * the platform. If an unspecified effect type is requested, the 343 * constructor with throw the IllegalArgumentException. This 344 * parameter can be set to {@link #EFFECT_TYPE_NULL} in which 345 * case only the uuid will be used to select the effect. 346 * @param uuid unique identifier of a particular effect implementation. 347 * Must be specified if the caller wants to use a particular 348 * implementation of an effect type. This parameter can be set to 349 * {@link #EFFECT_TYPE_NULL} in which case only the type will 350 * be used to select the effect. 351 * @param priority the priority level requested by the application for 352 * controlling the effect engine. As the same effect engine can 353 * be shared by several applications, this parameter indicates 354 * how much the requesting application needs control of effect 355 * parameters. The normal priority is 0, above normal is a 356 * positive number, below normal a negative number. 357 * @param audioSession system wide unique audio session identifier. 358 * The effect will be attached to the MediaPlayer or AudioTrack in 359 * the same audio session. 360 * 361 * @throws java.lang.IllegalArgumentException 362 * @throws java.lang.UnsupportedOperationException 363 * @throws java.lang.RuntimeException 364 * @hide 365 */ 366 AudioEffect(UUID type, UUID uuid, int priority, int audioSession)367 public AudioEffect(UUID type, UUID uuid, int priority, int audioSession) 368 throws IllegalArgumentException, UnsupportedOperationException, 369 RuntimeException { 370 int[] id = new int[1]; 371 Descriptor[] desc = new Descriptor[1]; 372 // native initialization 373 int initResult = native_setup(new WeakReference<AudioEffect>(this), 374 type.toString(), uuid.toString(), priority, audioSession, id, 375 desc); 376 if (initResult != SUCCESS && initResult != ALREADY_EXISTS) { 377 Log.e(TAG, "Error code " + initResult 378 + " when initializing AudioEffect."); 379 switch (initResult) { 380 case ERROR_BAD_VALUE: 381 throw (new IllegalArgumentException("Effect type: " + type 382 + " not supported.")); 383 case ERROR_INVALID_OPERATION: 384 throw (new UnsupportedOperationException( 385 "Effect library not loaded")); 386 default: 387 throw (new RuntimeException( 388 "Cannot initialize effect engine for type: " + type 389 + " Error: " + initResult)); 390 } 391 } 392 mId = id[0]; 393 mDescriptor = desc[0]; 394 synchronized (mStateLock) { 395 mState = STATE_INITIALIZED; 396 } 397 } 398 399 /** 400 * Releases the native AudioEffect resources. It is a good practice to 401 * release the effect engine when not in use as control can be returned to 402 * other applications or the native resources released. 403 */ release()404 public void release() { 405 synchronized (mStateLock) { 406 native_release(); 407 mState = STATE_UNINITIALIZED; 408 } 409 } 410 411 @Override finalize()412 protected void finalize() { 413 native_finalize(); 414 } 415 416 /** 417 * Get the effect descriptor. 418 * 419 * @see android.media.audiofx.AudioEffect.Descriptor 420 * @throws IllegalStateException 421 */ getDescriptor()422 public Descriptor getDescriptor() throws IllegalStateException { 423 checkState("getDescriptor()"); 424 return mDescriptor; 425 } 426 427 // -------------------------------------------------------------------------- 428 // Effects Enumeration 429 // -------------------- 430 431 /** 432 * Query all effects available on the platform. Returns an array of 433 * {@link android.media.audiofx.AudioEffect.Descriptor} objects 434 * 435 * @throws IllegalStateException 436 */ 437 queryEffects()438 static public Descriptor[] queryEffects() { 439 return (Descriptor[]) native_query_effects(); 440 } 441 442 /** 443 * Query all audio pre processing effects applied to the AudioRecord with the supplied 444 * audio session ID. Returns an array of {@link android.media.audiofx.AudioEffect.Descriptor} 445 * objects. 446 * @param audioSession system wide unique audio session identifier. 447 * @throws IllegalStateException 448 * @hide 449 */ 450 queryPreProcessings(int audioSession)451 static public Descriptor[] queryPreProcessings(int audioSession) { 452 return (Descriptor[]) native_query_pre_processing(audioSession); 453 } 454 455 /** 456 * Checks if the device implements the specified effect type. 457 * @param type the requested effect type. 458 * @return true if the device implements the specified effect type, false otherwise. 459 * @hide 460 */ isEffectTypeAvailable(UUID type)461 public static boolean isEffectTypeAvailable(UUID type) { 462 AudioEffect.Descriptor[] desc = AudioEffect.queryEffects(); 463 for (int i = 0; i < desc.length; i++) { 464 if (desc[i].type.equals(type)) { 465 return true; 466 } 467 } 468 return false; 469 } 470 471 // -------------------------------------------------------------------------- 472 // Control methods 473 // -------------------- 474 475 /** 476 * Enable or disable the effect. 477 * Creating an audio effect does not automatically apply this effect on the audio source. It 478 * creates the resources necessary to process this effect but the audio signal is still bypassed 479 * through the effect engine. Calling this method will make that the effect is actually applied 480 * or not to the audio content being played in the corresponding audio session. 481 * 482 * @param enabled the requested enable state 483 * @return {@link #SUCCESS} in case of success, {@link #ERROR_INVALID_OPERATION} 484 * or {@link #ERROR_DEAD_OBJECT} in case of failure. 485 * @throws IllegalStateException 486 */ setEnabled(boolean enabled)487 public int setEnabled(boolean enabled) throws IllegalStateException { 488 checkState("setEnabled()"); 489 return native_setEnabled(enabled); 490 } 491 492 /** 493 * Set effect parameter. The setParameter method is provided in several 494 * forms addressing most common parameter formats. This form is the most 495 * generic one where the parameter and its value are both specified as an 496 * array of bytes. The parameter and value type and length are therefore 497 * totally free. For standard effect defined by OpenSL ES, the parameter 498 * format and values must match the definitions in the corresponding OpenSL 499 * ES interface. 500 * 501 * @param param the identifier of the parameter to set 502 * @param value the new value for the specified parameter 503 * @return {@link #SUCCESS} in case of success, {@link #ERROR_BAD_VALUE}, 504 * {@link #ERROR_NO_MEMORY}, {@link #ERROR_INVALID_OPERATION} or 505 * {@link #ERROR_DEAD_OBJECT} in case of failure 506 * @throws IllegalStateException 507 * @hide 508 */ setParameter(byte[] param, byte[] value)509 public int setParameter(byte[] param, byte[] value) 510 throws IllegalStateException { 511 checkState("setParameter()"); 512 return native_setParameter(param.length, param, value.length, value); 513 } 514 515 /** 516 * Set effect parameter. The parameter and its value are integers. 517 * 518 * @see #setParameter(byte[], byte[]) 519 * @hide 520 */ setParameter(int param, int value)521 public int setParameter(int param, int value) throws IllegalStateException { 522 byte[] p = intToByteArray(param); 523 byte[] v = intToByteArray(value); 524 return setParameter(p, v); 525 } 526 527 /** 528 * Set effect parameter. The parameter is an integer and the value is a 529 * short integer. 530 * 531 * @see #setParameter(byte[], byte[]) 532 * @hide 533 */ setParameter(int param, short value)534 public int setParameter(int param, short value) 535 throws IllegalStateException { 536 byte[] p = intToByteArray(param); 537 byte[] v = shortToByteArray(value); 538 return setParameter(p, v); 539 } 540 541 /** 542 * Set effect parameter. The parameter is an integer and the value is an 543 * array of bytes. 544 * 545 * @see #setParameter(byte[], byte[]) 546 * @hide 547 */ setParameter(int param, byte[] value)548 public int setParameter(int param, byte[] value) 549 throws IllegalStateException { 550 byte[] p = intToByteArray(param); 551 return setParameter(p, value); 552 } 553 554 /** 555 * Set effect parameter. The parameter is an array of 1 or 2 integers and 556 * the value is also an array of 1 or 2 integers 557 * 558 * @see #setParameter(byte[], byte[]) 559 * @hide 560 */ setParameter(int[] param, int[] value)561 public int setParameter(int[] param, int[] value) 562 throws IllegalStateException { 563 if (param.length > 2 || value.length > 2) { 564 return ERROR_BAD_VALUE; 565 } 566 byte[] p = intToByteArray(param[0]); 567 if (param.length > 1) { 568 byte[] p2 = intToByteArray(param[1]); 569 p = concatArrays(p, p2); 570 } 571 byte[] v = intToByteArray(value[0]); 572 if (value.length > 1) { 573 byte[] v2 = intToByteArray(value[1]); 574 v = concatArrays(v, v2); 575 } 576 return setParameter(p, v); 577 } 578 579 /** 580 * Set effect parameter. The parameter is an array of 1 or 2 integers and 581 * the value is an array of 1 or 2 short integers 582 * 583 * @see #setParameter(byte[], byte[]) 584 * @hide 585 */ setParameter(int[] param, short[] value)586 public int setParameter(int[] param, short[] value) 587 throws IllegalStateException { 588 if (param.length > 2 || value.length > 2) { 589 return ERROR_BAD_VALUE; 590 } 591 byte[] p = intToByteArray(param[0]); 592 if (param.length > 1) { 593 byte[] p2 = intToByteArray(param[1]); 594 p = concatArrays(p, p2); 595 } 596 597 byte[] v = shortToByteArray(value[0]); 598 if (value.length > 1) { 599 byte[] v2 = shortToByteArray(value[1]); 600 v = concatArrays(v, v2); 601 } 602 return setParameter(p, v); 603 } 604 605 /** 606 * Set effect parameter. The parameter is an array of 1 or 2 integers and 607 * the value is an array of bytes 608 * 609 * @see #setParameter(byte[], byte[]) 610 * @hide 611 */ setParameter(int[] param, byte[] value)612 public int setParameter(int[] param, byte[] value) 613 throws IllegalStateException { 614 if (param.length > 2) { 615 return ERROR_BAD_VALUE; 616 } 617 byte[] p = intToByteArray(param[0]); 618 if (param.length > 1) { 619 byte[] p2 = intToByteArray(param[1]); 620 p = concatArrays(p, p2); 621 } 622 return setParameter(p, value); 623 } 624 625 /** 626 * Get effect parameter. The getParameter method is provided in several 627 * forms addressing most common parameter formats. This form is the most 628 * generic one where the parameter and its value are both specified as an 629 * array of bytes. The parameter and value type and length are therefore 630 * totally free. 631 * 632 * @param param the identifier of the parameter to set 633 * @param value the new value for the specified parameter 634 * @return the number of meaningful bytes in value array in case of success or 635 * {@link #ERROR_BAD_VALUE}, {@link #ERROR_NO_MEMORY}, {@link #ERROR_INVALID_OPERATION} 636 * or {@link #ERROR_DEAD_OBJECT} in case of failure. 637 * @throws IllegalStateException 638 * @hide 639 */ getParameter(byte[] param, byte[] value)640 public int getParameter(byte[] param, byte[] value) 641 throws IllegalStateException { 642 checkState("getParameter()"); 643 return native_getParameter(param.length, param, value.length, value); 644 } 645 646 /** 647 * Get effect parameter. The parameter is an integer and the value is an 648 * array of bytes. 649 * 650 * @see #getParameter(byte[], byte[]) 651 * @hide 652 */ getParameter(int param, byte[] value)653 public int getParameter(int param, byte[] value) 654 throws IllegalStateException { 655 byte[] p = intToByteArray(param); 656 657 return getParameter(p, value); 658 } 659 660 /** 661 * Get effect parameter. The parameter is an integer and the value is an 662 * array of 1 or 2 integers 663 * 664 * @see #getParameter(byte[], byte[]) 665 * In case of success, returns the number of meaningful integers in value array. 666 * @hide 667 */ getParameter(int param, int[] value)668 public int getParameter(int param, int[] value) 669 throws IllegalStateException { 670 if (value.length > 2) { 671 return ERROR_BAD_VALUE; 672 } 673 byte[] p = intToByteArray(param); 674 675 byte[] v = new byte[value.length * 4]; 676 677 int status = getParameter(p, v); 678 679 if (status == 4 || status == 8) { 680 value[0] = byteArrayToInt(v); 681 if (status == 8) { 682 value[1] = byteArrayToInt(v, 4); 683 } 684 status /= 4; 685 } else { 686 status = ERROR; 687 } 688 return status; 689 } 690 691 /** 692 * Get effect parameter. The parameter is an integer and the value is an 693 * array of 1 or 2 short integers 694 * 695 * @see #getParameter(byte[], byte[]) 696 * In case of success, returns the number of meaningful short integers in value array. 697 * @hide 698 */ getParameter(int param, short[] value)699 public int getParameter(int param, short[] value) 700 throws IllegalStateException { 701 if (value.length > 2) { 702 return ERROR_BAD_VALUE; 703 } 704 byte[] p = intToByteArray(param); 705 706 byte[] v = new byte[value.length * 2]; 707 708 int status = getParameter(p, v); 709 710 if (status == 2 || status == 4) { 711 value[0] = byteArrayToShort(v); 712 if (status == 4) { 713 value[1] = byteArrayToShort(v, 2); 714 } 715 status /= 2; 716 } else { 717 status = ERROR; 718 } 719 return status; 720 } 721 722 /** 723 * Get effect parameter. The parameter is an array of 1 or 2 integers and 724 * the value is also an array of 1 or 2 integers 725 * 726 * @see #getParameter(byte[], byte[]) 727 * In case of success, the returns the number of meaningful integers in value array. 728 * @hide 729 */ getParameter(int[] param, int[] value)730 public int getParameter(int[] param, int[] value) 731 throws IllegalStateException { 732 if (param.length > 2 || value.length > 2) { 733 return ERROR_BAD_VALUE; 734 } 735 byte[] p = intToByteArray(param[0]); 736 if (param.length > 1) { 737 byte[] p2 = intToByteArray(param[1]); 738 p = concatArrays(p, p2); 739 } 740 byte[] v = new byte[value.length * 4]; 741 742 int status = getParameter(p, v); 743 744 if (status == 4 || status == 8) { 745 value[0] = byteArrayToInt(v); 746 if (status == 8) { 747 value[1] = byteArrayToInt(v, 4); 748 } 749 status /= 4; 750 } else { 751 status = ERROR; 752 } 753 return status; 754 } 755 756 /** 757 * Get effect parameter. The parameter is an array of 1 or 2 integers and 758 * the value is an array of 1 or 2 short integers 759 * 760 * @see #getParameter(byte[], byte[]) 761 * In case of success, returns the number of meaningful short integers in value array. 762 * @hide 763 */ getParameter(int[] param, short[] value)764 public int getParameter(int[] param, short[] value) 765 throws IllegalStateException { 766 if (param.length > 2 || value.length > 2) { 767 return ERROR_BAD_VALUE; 768 } 769 byte[] p = intToByteArray(param[0]); 770 if (param.length > 1) { 771 byte[] p2 = intToByteArray(param[1]); 772 p = concatArrays(p, p2); 773 } 774 byte[] v = new byte[value.length * 2]; 775 776 int status = getParameter(p, v); 777 778 if (status == 2 || status == 4) { 779 value[0] = byteArrayToShort(v); 780 if (status == 4) { 781 value[1] = byteArrayToShort(v, 2); 782 } 783 status /= 2; 784 } else { 785 status = ERROR; 786 } 787 return status; 788 } 789 790 /** 791 * Get effect parameter. The parameter is an array of 1 or 2 integers and 792 * the value is an array of bytes 793 * 794 * @see #getParameter(byte[], byte[]) 795 * @hide 796 */ getParameter(int[] param, byte[] value)797 public int getParameter(int[] param, byte[] value) 798 throws IllegalStateException { 799 if (param.length > 2) { 800 return ERROR_BAD_VALUE; 801 } 802 byte[] p = intToByteArray(param[0]); 803 if (param.length > 1) { 804 byte[] p2 = intToByteArray(param[1]); 805 p = concatArrays(p, p2); 806 } 807 808 return getParameter(p, value); 809 } 810 811 /** 812 * Send a command to the effect engine. This method is intended to send 813 * proprietary commands to a particular effect implementation. 814 * In case of success, returns the number of meaningful bytes in reply array. 815 * In case of failure, the returned value is negative and implementation specific. 816 * @hide 817 */ command(int cmdCode, byte[] command, byte[] reply)818 public int command(int cmdCode, byte[] command, byte[] reply) 819 throws IllegalStateException { 820 checkState("command()"); 821 return native_command(cmdCode, command.length, command, reply.length, reply); 822 } 823 824 // -------------------------------------------------------------------------- 825 // Getters 826 // -------------------- 827 828 /** 829 * Returns effect unique identifier. This system wide unique identifier can 830 * be used to attach this effect to a MediaPlayer or an AudioTrack when the 831 * effect is an auxiliary effect (Reverb) 832 * 833 * @return the effect identifier. 834 * @throws IllegalStateException 835 */ getId()836 public int getId() throws IllegalStateException { 837 checkState("getId()"); 838 return mId; 839 } 840 841 /** 842 * Returns effect enabled state 843 * 844 * @return true if the effect is enabled, false otherwise. 845 * @throws IllegalStateException 846 */ getEnabled()847 public boolean getEnabled() throws IllegalStateException { 848 checkState("getEnabled()"); 849 return native_getEnabled(); 850 } 851 852 /** 853 * Checks if this AudioEffect object is controlling the effect engine. 854 * 855 * @return true if this instance has control of effect engine, false 856 * otherwise. 857 * @throws IllegalStateException 858 */ hasControl()859 public boolean hasControl() throws IllegalStateException { 860 checkState("hasControl()"); 861 return native_hasControl(); 862 } 863 864 // -------------------------------------------------------------------------- 865 // Initialization / configuration 866 // -------------------- 867 /** 868 * Sets the listener AudioEffect notifies when the effect engine is enabled 869 * or disabled. 870 * 871 * @param listener 872 */ setEnableStatusListener(OnEnableStatusChangeListener listener)873 public void setEnableStatusListener(OnEnableStatusChangeListener listener) { 874 synchronized (mListenerLock) { 875 mEnableStatusChangeListener = listener; 876 } 877 if ((listener != null) && (mNativeEventHandler == null)) { 878 createNativeEventHandler(); 879 } 880 } 881 882 /** 883 * Sets the listener AudioEffect notifies when the effect engine control is 884 * taken or returned. 885 * 886 * @param listener 887 */ setControlStatusListener(OnControlStatusChangeListener listener)888 public void setControlStatusListener(OnControlStatusChangeListener listener) { 889 synchronized (mListenerLock) { 890 mControlChangeStatusListener = listener; 891 } 892 if ((listener != null) && (mNativeEventHandler == null)) { 893 createNativeEventHandler(); 894 } 895 } 896 897 /** 898 * Sets the listener AudioEffect notifies when a parameter is changed. 899 * 900 * @param listener 901 * @hide 902 */ setParameterListener(OnParameterChangeListener listener)903 public void setParameterListener(OnParameterChangeListener listener) { 904 synchronized (mListenerLock) { 905 mParameterChangeListener = listener; 906 } 907 if ((listener != null) && (mNativeEventHandler == null)) { 908 createNativeEventHandler(); 909 } 910 } 911 912 // Convenience method for the creation of the native event handler 913 // It is called only when a non-null event listener is set. 914 // precondition: 915 // mNativeEventHandler is null createNativeEventHandler()916 private void createNativeEventHandler() { 917 Looper looper; 918 if ((looper = Looper.myLooper()) != null) { 919 mNativeEventHandler = new NativeEventHandler(this, looper); 920 } else if ((looper = Looper.getMainLooper()) != null) { 921 mNativeEventHandler = new NativeEventHandler(this, looper); 922 } else { 923 mNativeEventHandler = null; 924 } 925 } 926 927 // --------------------------------------------------------- 928 // Interface definitions 929 // -------------------- 930 /** 931 * The OnEnableStatusChangeListener interface defines a method called by the AudioEffect 932 * when a the enabled state of the effect engine was changed by the controlling application. 933 */ 934 public interface OnEnableStatusChangeListener { 935 /** 936 * Called on the listener to notify it that the effect engine has been 937 * enabled or disabled. 938 * @param effect the effect on which the interface is registered. 939 * @param enabled new effect state. 940 */ onEnableStatusChange(AudioEffect effect, boolean enabled)941 void onEnableStatusChange(AudioEffect effect, boolean enabled); 942 } 943 944 /** 945 * The OnControlStatusChangeListener interface defines a method called by the AudioEffect 946 * when a the control of the effect engine is gained or lost by the application 947 */ 948 public interface OnControlStatusChangeListener { 949 /** 950 * Called on the listener to notify it that the effect engine control 951 * has been taken or returned. 952 * @param effect the effect on which the interface is registered. 953 * @param controlGranted true if the application has been granted control of the effect 954 * engine, false otherwise. 955 */ onControlStatusChange(AudioEffect effect, boolean controlGranted)956 void onControlStatusChange(AudioEffect effect, boolean controlGranted); 957 } 958 959 /** 960 * The OnParameterChangeListener interface defines a method called by the AudioEffect 961 * when a parameter is changed in the effect engine by the controlling application. 962 * @hide 963 */ 964 public interface OnParameterChangeListener { 965 /** 966 * Called on the listener to notify it that a parameter value has changed. 967 * @param effect the effect on which the interface is registered. 968 * @param status status of the set parameter operation. 969 * @param param ID of the modified parameter. 970 * @param value the new parameter value. 971 */ onParameterChange(AudioEffect effect, int status, byte[] param, byte[] value)972 void onParameterChange(AudioEffect effect, int status, byte[] param, 973 byte[] value); 974 } 975 976 977 // ------------------------------------------------------------------------- 978 // Audio Effect Control panel intents 979 // ------------------------------------------------------------------------- 980 981 /** 982 * Intent to launch an audio effect control panel UI. 983 * <p>The goal of this intent is to enable separate implementations of music/media player 984 * applications and audio effect control application or services. 985 * This will allow platform vendors to offer more advanced control options for standard effects 986 * or control for platform specific effects. 987 * <p>The intent carries a number of extras used by the player application to communicate 988 * necessary pieces of information to the control panel application. 989 * <p>The calling application must use the 990 * {@link android.app.Activity#startActivityForResult(Intent, int)} method to launch the 991 * control panel so that its package name is indicated and used by the control panel 992 * application to keep track of changes for this particular application. 993 * <p>The {@link #EXTRA_AUDIO_SESSION} extra will indicate an audio session to which the 994 * audio effects should be applied. If no audio session is specified, either one of the 995 * follownig will happen: 996 * <p>- If an audio session was previously opened by the calling application with 997 * {@link #ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION} intent, the effect changes will 998 * be applied to that session. 999 * <p>- If no audio session is opened, the changes will be stored in the package specific 1000 * storage area and applied whenever a new audio session is opened by this application. 1001 * <p>The {@link #EXTRA_CONTENT_TYPE} extra will help the control panel application 1002 * customize both the UI layout and the default audio effect settings if none are already 1003 * stored for the calling application. 1004 */ 1005 @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) 1006 public static final String ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL = 1007 "android.media.action.DISPLAY_AUDIO_EFFECT_CONTROL_PANEL"; 1008 1009 /** 1010 * Intent to signal to the effect control application or service that a new audio session 1011 * is opened and requires audio effects to be applied. 1012 * <p>This is different from {@link #ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL} in that no 1013 * UI should be displayed in this case. Music player applications can broadcast this intent 1014 * before starting playback to make sure that any audio effect settings previously selected 1015 * by the user are applied. 1016 * <p>The effect control application receiving this intent will look for previously stored 1017 * settings for the calling application, create all required audio effects and apply the 1018 * effect settings to the specified audio session. 1019 * <p>The calling package name is indicated by the {@link #EXTRA_PACKAGE_NAME} extra and the 1020 * audio session ID by the {@link #EXTRA_AUDIO_SESSION} extra. Both extras are mandatory. 1021 * <p>If no stored settings are found for the calling application, default settings for the 1022 * content type indicated by {@link #EXTRA_CONTENT_TYPE} will be applied. The default settings 1023 * for a given content type are platform specific. 1024 */ 1025 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 1026 public static final String ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION = 1027 "android.media.action.OPEN_AUDIO_EFFECT_CONTROL_SESSION"; 1028 1029 /** 1030 * Intent to signal to the effect control application or service that an audio session 1031 * is closed and that effects should not be applied anymore. 1032 * <p>The effect control application receiving this intent will delete all effects on 1033 * this session and store current settings in package specific storage. 1034 * <p>The calling package name is indicated by the {@link #EXTRA_PACKAGE_NAME} extra and the 1035 * audio session ID by the {@link #EXTRA_AUDIO_SESSION} extra. Both extras are mandatory. 1036 * <p>It is good practice for applications to broadcast this intent when music playback stops 1037 * and/or when exiting to free system resources consumed by audio effect engines. 1038 */ 1039 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 1040 public static final String ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION = 1041 "android.media.action.CLOSE_AUDIO_EFFECT_CONTROL_SESSION"; 1042 1043 /** 1044 * Contains the ID of the audio session the effects should be applied to. 1045 * <p>This extra is for use with {@link #ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL}, 1046 * {@link #ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION} and 1047 * {@link #ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION} intents. 1048 * <p>The extra value is of type int and is the audio session ID. 1049 * @see android.media.MediaPlayer#getAudioSessionId() for details on audio sessions. 1050 */ 1051 public static final String EXTRA_AUDIO_SESSION = "android.media.extra.AUDIO_SESSION"; 1052 1053 /** 1054 * Contains the package name of the calling application. 1055 * <p>This extra is for use with {@link #ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION} and 1056 * {@link #ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION} intents. 1057 * <p>The extra value is a string containing the full package name. 1058 */ 1059 public static final String EXTRA_PACKAGE_NAME = "android.media.extra.PACKAGE_NAME"; 1060 1061 /** 1062 * Indicates which type of content is played by the application. 1063 * <p>This extra is for use with {@link #ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL} and 1064 * {@link #ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION} intents. 1065 * <p>This information is used by the effect control application to customize UI and select 1066 * appropriate default effect settings. The content type is one of the following: 1067 * <ul> 1068 * <li>{@link #CONTENT_TYPE_MUSIC}</li> 1069 * <li>{@link #CONTENT_TYPE_MOVIE}</li> 1070 * <li>{@link #CONTENT_TYPE_GAME}</li> 1071 * <li>{@link #CONTENT_TYPE_VOICE}</li> 1072 * </ul> 1073 * If omitted, the content type defaults to {@link #CONTENT_TYPE_MUSIC}. 1074 */ 1075 public static final String EXTRA_CONTENT_TYPE = "android.media.extra.CONTENT_TYPE"; 1076 1077 /** 1078 * Value for {@link #EXTRA_CONTENT_TYPE} when the type of content played is music 1079 */ 1080 public static final int CONTENT_TYPE_MUSIC = 0; 1081 /** 1082 * Value for {@link #EXTRA_CONTENT_TYPE} when the type of content played is video or movie 1083 */ 1084 public static final int CONTENT_TYPE_MOVIE = 1; 1085 /** 1086 * Value for {@link #EXTRA_CONTENT_TYPE} when the type of content played is game audio 1087 */ 1088 public static final int CONTENT_TYPE_GAME = 2; 1089 /** 1090 * Value for {@link #EXTRA_CONTENT_TYPE} when the type of content played is voice audio 1091 */ 1092 public static final int CONTENT_TYPE_VOICE = 3; 1093 1094 1095 // --------------------------------------------------------- 1096 // Inner classes 1097 // -------------------- 1098 /** 1099 * Helper class to handle the forwarding of native events to the appropriate 1100 * listeners 1101 */ 1102 private class NativeEventHandler extends Handler { 1103 private AudioEffect mAudioEffect; 1104 NativeEventHandler(AudioEffect ae, Looper looper)1105 public NativeEventHandler(AudioEffect ae, Looper looper) { 1106 super(looper); 1107 mAudioEffect = ae; 1108 } 1109 1110 @Override handleMessage(Message msg)1111 public void handleMessage(Message msg) { 1112 if (mAudioEffect == null) { 1113 return; 1114 } 1115 switch (msg.what) { 1116 case NATIVE_EVENT_ENABLED_STATUS: 1117 OnEnableStatusChangeListener enableStatusChangeListener = null; 1118 synchronized (mListenerLock) { 1119 enableStatusChangeListener = mAudioEffect.mEnableStatusChangeListener; 1120 } 1121 if (enableStatusChangeListener != null) { 1122 enableStatusChangeListener.onEnableStatusChange( 1123 mAudioEffect, (boolean) (msg.arg1 != 0)); 1124 } 1125 break; 1126 case NATIVE_EVENT_CONTROL_STATUS: 1127 OnControlStatusChangeListener controlStatusChangeListener = null; 1128 synchronized (mListenerLock) { 1129 controlStatusChangeListener = mAudioEffect.mControlChangeStatusListener; 1130 } 1131 if (controlStatusChangeListener != null) { 1132 controlStatusChangeListener.onControlStatusChange( 1133 mAudioEffect, (boolean) (msg.arg1 != 0)); 1134 } 1135 break; 1136 case NATIVE_EVENT_PARAMETER_CHANGED: 1137 OnParameterChangeListener parameterChangeListener = null; 1138 synchronized (mListenerLock) { 1139 parameterChangeListener = mAudioEffect.mParameterChangeListener; 1140 } 1141 if (parameterChangeListener != null) { 1142 // arg1 contains offset of parameter value from start of 1143 // byte array 1144 int vOffset = msg.arg1; 1145 byte[] p = (byte[]) msg.obj; 1146 // See effect_param_t in EffectApi.h for psize and vsize 1147 // fields offsets 1148 int status = byteArrayToInt(p, 0); 1149 int psize = byteArrayToInt(p, 4); 1150 int vsize = byteArrayToInt(p, 8); 1151 byte[] param = new byte[psize]; 1152 byte[] value = new byte[vsize]; 1153 System.arraycopy(p, 12, param, 0, psize); 1154 System.arraycopy(p, vOffset, value, 0, vsize); 1155 1156 parameterChangeListener.onParameterChange(mAudioEffect, 1157 status, param, value); 1158 } 1159 break; 1160 1161 default: 1162 Log.e(TAG, "handleMessage() Unknown event type: " + msg.what); 1163 break; 1164 } 1165 } 1166 } 1167 1168 // --------------------------------------------------------- 1169 // Java methods called from the native side 1170 // -------------------- 1171 @SuppressWarnings("unused") postEventFromNative(Object effect_ref, int what, int arg1, int arg2, Object obj)1172 private static void postEventFromNative(Object effect_ref, int what, 1173 int arg1, int arg2, Object obj) { 1174 AudioEffect effect = (AudioEffect) ((WeakReference) effect_ref).get(); 1175 if (effect == null) { 1176 return; 1177 } 1178 if (effect.mNativeEventHandler != null) { 1179 Message m = effect.mNativeEventHandler.obtainMessage(what, arg1, 1180 arg2, obj); 1181 effect.mNativeEventHandler.sendMessage(m); 1182 } 1183 1184 } 1185 1186 // --------------------------------------------------------- 1187 // Native methods called from the Java side 1188 // -------------------- 1189 native_init()1190 private static native final void native_init(); 1191 native_setup(Object audioeffect_this, String type, String uuid, int priority, int audioSession, int[] id, Object[] desc)1192 private native final int native_setup(Object audioeffect_this, String type, 1193 String uuid, int priority, int audioSession, int[] id, Object[] desc); 1194 native_finalize()1195 private native final void native_finalize(); 1196 native_release()1197 private native final void native_release(); 1198 native_setEnabled(boolean enabled)1199 private native final int native_setEnabled(boolean enabled); 1200 native_getEnabled()1201 private native final boolean native_getEnabled(); 1202 native_hasControl()1203 private native final boolean native_hasControl(); 1204 native_setParameter(int psize, byte[] param, int vsize, byte[] value)1205 private native final int native_setParameter(int psize, byte[] param, 1206 int vsize, byte[] value); 1207 native_getParameter(int psize, byte[] param, int vsize, byte[] value)1208 private native final int native_getParameter(int psize, byte[] param, 1209 int vsize, byte[] value); 1210 native_command(int cmdCode, int cmdSize, byte[] cmdData, int repSize, byte[] repData)1211 private native final int native_command(int cmdCode, int cmdSize, 1212 byte[] cmdData, int repSize, byte[] repData); 1213 native_query_effects()1214 private static native Object[] native_query_effects(); 1215 native_query_pre_processing(int audioSession)1216 private static native Object[] native_query_pre_processing(int audioSession); 1217 1218 // --------------------------------------------------------- 1219 // Utility methods 1220 // ------------------ 1221 1222 /** 1223 * @hide 1224 */ checkState(String methodName)1225 public void checkState(String methodName) throws IllegalStateException { 1226 synchronized (mStateLock) { 1227 if (mState != STATE_INITIALIZED) { 1228 throw (new IllegalStateException(methodName 1229 + " called on uninitialized AudioEffect.")); 1230 } 1231 } 1232 } 1233 1234 /** 1235 * @hide 1236 */ checkStatus(int status)1237 public void checkStatus(int status) { 1238 if (isError(status)) { 1239 switch (status) { 1240 case AudioEffect.ERROR_BAD_VALUE: 1241 throw (new IllegalArgumentException( 1242 "AudioEffect: bad parameter value")); 1243 case AudioEffect.ERROR_INVALID_OPERATION: 1244 throw (new UnsupportedOperationException( 1245 "AudioEffect: invalid parameter operation")); 1246 default: 1247 throw (new RuntimeException("AudioEffect: set/get parameter error")); 1248 } 1249 } 1250 } 1251 1252 /** 1253 * @hide 1254 */ isError(int status)1255 public static boolean isError(int status) { 1256 return (status < 0); 1257 } 1258 1259 /** 1260 * @hide 1261 */ byteArrayToInt(byte[] valueBuf)1262 public int byteArrayToInt(byte[] valueBuf) { 1263 return byteArrayToInt(valueBuf, 0); 1264 1265 } 1266 1267 /** 1268 * @hide 1269 */ byteArrayToInt(byte[] valueBuf, int offset)1270 public int byteArrayToInt(byte[] valueBuf, int offset) { 1271 ByteBuffer converter = ByteBuffer.wrap(valueBuf); 1272 converter.order(ByteOrder.nativeOrder()); 1273 return converter.getInt(offset); 1274 1275 } 1276 1277 /** 1278 * @hide 1279 */ intToByteArray(int value)1280 public byte[] intToByteArray(int value) { 1281 ByteBuffer converter = ByteBuffer.allocate(4); 1282 converter.order(ByteOrder.nativeOrder()); 1283 converter.putInt(value); 1284 return converter.array(); 1285 } 1286 1287 /** 1288 * @hide 1289 */ byteArrayToShort(byte[] valueBuf)1290 public short byteArrayToShort(byte[] valueBuf) { 1291 return byteArrayToShort(valueBuf, 0); 1292 } 1293 1294 /** 1295 * @hide 1296 */ byteArrayToShort(byte[] valueBuf, int offset)1297 public short byteArrayToShort(byte[] valueBuf, int offset) { 1298 ByteBuffer converter = ByteBuffer.wrap(valueBuf); 1299 converter.order(ByteOrder.nativeOrder()); 1300 return converter.getShort(offset); 1301 1302 } 1303 1304 /** 1305 * @hide 1306 */ shortToByteArray(short value)1307 public byte[] shortToByteArray(short value) { 1308 ByteBuffer converter = ByteBuffer.allocate(2); 1309 converter.order(ByteOrder.nativeOrder()); 1310 short sValue = (short) value; 1311 converter.putShort(sValue); 1312 return converter.array(); 1313 } 1314 1315 /** 1316 * @hide 1317 */ concatArrays(byte[]... arrays)1318 public byte[] concatArrays(byte[]... arrays) { 1319 int len = 0; 1320 for (byte[] a : arrays) { 1321 len += a.length; 1322 } 1323 byte[] b = new byte[len]; 1324 1325 int offs = 0; 1326 for (byte[] a : arrays) { 1327 System.arraycopy(a, 0, b, offs, a.length); 1328 offs += a.length; 1329 } 1330 return b; 1331 } 1332 } 1333