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