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