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