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