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