audiotrack.c 51.4 KB
Newer Older
Thomas Guillem's avatar
Thomas Guillem committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/*****************************************************************************
 * audiotrack.c: Android Java AudioTrack audio output module
 *****************************************************************************
 * Copyright © 2012-2015 VLC authors and VideoLAN, VideoLabs
 *
 * Authors: Thomas Guillem <thomas@gllm.fr>
 *          Ming Hu <tewilove@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <assert.h>
#include <jni.h>
#include <dlfcn.h>
31
#include <stdbool.h>
Thomas Guillem's avatar
Thomas Guillem committed
32
33
34
35

#include <vlc_common.h>
#include <vlc_plugin.h>
#include <vlc_aout.h>
36
#include "../video_output/android/utils.h"
37

38
39
40
#define MIN_AUDIOTRACK_BUFFER_US INT64_C(250000)  // 250ms
#define MAX_AUDIOTRACK_BUFFER_US INT64_C(1000000) // 1000ms

41
42
43
#define SMOOTHPOS_SAMPLE_COUNT 10
#define SMOOTHPOS_INTERVAL_US INT64_C(30000) // 30ms

44
45
#define AUDIOTIMESTAMP_INTERVAL_US INT64_C(500000) // 500ms

Thomas Guillem's avatar
Thomas Guillem committed
46
47
static int  Open( vlc_object_t * );
static void Close( vlc_object_t * );
48
49
static void Stop( audio_output_t * );
static int Start( audio_output_t *, audio_sample_format_t * );
50

51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
/* There is an undefined behavior when configuring AudioTrack with SPDIF or
 * more than 2 channels when there is no HDMI out. It may succeed and the
 * Android ressampler will be used to downmix to stereo. It may fails cleanly,
 * and this module will be able to recover and fallback to stereo. Finally, in
 * some rare cases, it may crash during init or while ressampling. Because of
 * the last case we don't try up to 8 channels and we use AT_DEV_STEREO device
 * per default */
enum at_dev {
    AT_DEV_STEREO = 0,
    AT_DEV_HDMI,
};
#define AT_DEV_DEFAULT AT_DEV_STEREO
#define AT_DEV_MAX_CHANNELS 8

static const struct {
    const char *id;
    const char *name;
    enum at_dev at_dev;
} at_devs[] = {
    { "stereo", "Up to 2 channels (compat mode).", AT_DEV_STEREO },
    { "hdmi", "Up to 8 channels, SPDIF if available.", AT_DEV_HDMI },
    {  NULL, NULL, AT_DEV_DEFAULT },
};

Thomas Guillem's avatar
Thomas Guillem committed
75
76
77
78
79
struct aout_sys_t {
    /* sw gain */
    float soft_gain;
    bool soft_mute;

80
81
    enum at_dev at_dev;

Thomas Guillem's avatar
Thomas Guillem committed
82
    jobject p_audiotrack; /* AudioTrack ref */
83
    jbyteArray p_bytearray; /* ByteArray ref (for Write) */
Thomas Guillem's avatar
Thomas Guillem committed
84
    size_t i_bytearray_size; /* size of the ByteArray */
85
86
    jfloatArray p_floatarray; /* FloatArray ref (for WriteFloat) */
    size_t i_floatarray_size; /* size of the FloatArray */
87
    jobject p_bytebuffer; /* ByteBuffer ref (for WriteV21) */
Thomas Guillem's avatar
Thomas Guillem committed
88
    audio_sample_format_t fmt; /* fmt setup by Start */
89

90
91
92
93
94
95
96
    struct {
        unsigned int i_rate;
        int i_channel_config;
        int i_format;
        int i_size;
    } audiotrack_args;

97
    /* Used by AudioTrack_getPlaybackHeadPosition */
98
99
100
101
102
    struct {
        uint32_t i_wrap_count;
        uint32_t i_last;
    } headpos;

103
104
105
106
107
108
109
110
111
    /* Used by AudioTrack_GetTimestampPositionUs */
    struct {
        jobject p_obj; /* AudioTimestamp ref */
        jlong i_frame_us;
        jlong i_frame_pos;
        mtime_t i_play_time; /* time when play was called */
        mtime_t i_last_time;
    } timestamp;

112
113
114
115
116
117
118
    /* Used by AudioTrack_GetSmoothPositionUs */
    struct {
        uint32_t i_idx;
        uint32_t i_count;
        mtime_t p_us[SMOOTHPOS_SAMPLE_COUNT];
        mtime_t i_us;
        mtime_t i_last_time;
Thomas Guillem's avatar
Thomas Guillem committed
119
        mtime_t i_latency_us;
120
121
    } smoothpos;

122
    uint64_t i_samples_written; /* number of samples written since last flush */
123
124
    uint32_t i_bytes_per_frame; /* byte per frame */
    uint32_t i_max_audiotrack_samples;
125
    bool b_audiotrack_exception; /* true if audiotrack throwed an exception */
126
    bool b_error; /* generic error */
127
    bool b_spdif;
128
129
    uint8_t i_chans_to_reorder; /* do we need channel reordering */
    uint8_t p_chan_table[AOUT_CHAN_MAX];
Thomas Guillem's avatar
Thomas Guillem committed
130
131
    enum {
        WRITE,
132
133
        WRITE_V21,
        WRITE_FLOAT
Thomas Guillem's avatar
Thomas Guillem committed
134
    } i_write_type;
Thomas Guillem's avatar
Thomas Guillem committed
135
136
137
138
139
};

/* Soft volume helper */
#include "audio_output/volume.h"

140
141
// Don't use Float for now since 5.1/7.1 Float is down sampled to Stereo Float
//#define AUDIOTRACK_USE_FLOAT
142
//#define AUDIOTRACK_HW_LATENCY
Thomas Guillem's avatar
Thomas Guillem committed
143

144
145
/* Get AudioTrack native sample rate: if activated, most of  the resampling
 * will be done by VLC */
146
#define AUDIOTRACK_NATIVE_SAMPLERATE
147

Thomas Guillem's avatar
Thomas Guillem committed
148
149
150
151
152
153
154
155
156
157
158
159
vlc_module_begin ()
    set_shortname( "AudioTrack" )
    set_description( N_( "Android AudioTrack audio output" ) )
    set_capability( "audio output", 180 )
    set_category( CAT_AUDIO )
    set_subcategory( SUBCAT_AUDIO_AOUT )
    add_sw_gain()
    add_shortcut( "audiotrack" )
    set_callbacks( Open, Close )
vlc_module_end ()

#define THREAD_NAME "android_audiotrack"
160
#define GET_ENV() android_getEnv( VLC_OBJECT(p_aout), THREAD_NAME )
Thomas Guillem's avatar
Thomas Guillem committed
161
162
163
164
165
166
167

static struct
{
    struct {
        jclass clazz;
        jmethodID ctor;
        jmethodID release;
Thomas Guillem's avatar
Thomas Guillem committed
168
        jmethodID getState;
Thomas Guillem's avatar
Thomas Guillem committed
169
170
171
172
173
        jmethodID play;
        jmethodID stop;
        jmethodID flush;
        jmethodID pause;
        jmethodID write;
174
        jmethodID writeV21;
175
        jmethodID writeFloat;
Thomas Guillem's avatar
Thomas Guillem committed
176
        jmethodID getPlaybackHeadPosition;
177
        jmethodID getTimestamp;
Thomas Guillem's avatar
Thomas Guillem committed
178
        jmethodID getMinBufferSize;
179
        jmethodID getNativeOutputSampleRate;
Thomas Guillem's avatar
Thomas Guillem committed
180
        jint STATE_INITIALIZED;
Thomas Guillem's avatar
Thomas Guillem committed
181
182
183
184
        jint MODE_STREAM;
        jint ERROR;
        jint ERROR_BAD_VALUE;
        jint ERROR_INVALID_OPERATION;
185
        jint WRITE_NON_BLOCKING;
Thomas Guillem's avatar
Thomas Guillem committed
186
187
188
189
190
191
    } AudioTrack;
    struct {
        jint ENCODING_PCM_8BIT;
        jint ENCODING_PCM_16BIT;
        jint ENCODING_PCM_FLOAT;
        bool has_ENCODING_PCM_FLOAT;
192
193
194
        jint ENCODING_AC3;
        jint ENCODING_E_AC3;
        bool has_ENCODING_AC3;
Thomas Guillem's avatar
Thomas Guillem committed
195
196
        jint CHANNEL_OUT_MONO;
        jint CHANNEL_OUT_STEREO;
197
198
        jint CHANNEL_OUT_FRONT_LEFT;
        jint CHANNEL_OUT_FRONT_RIGHT;
199
200
201
202
203
204
205
206
207
        jint CHANNEL_OUT_BACK_LEFT;
        jint CHANNEL_OUT_BACK_RIGHT;
        jint CHANNEL_OUT_FRONT_CENTER;
        jint CHANNEL_OUT_LOW_FREQUENCY;
        jint CHANNEL_OUT_BACK_CENTER;
        jint CHANNEL_OUT_5POINT1;
        jint CHANNEL_OUT_SIDE_LEFT;
        jint CHANNEL_OUT_SIDE_RIGHT;
        bool has_CHANNEL_OUT_SIDE;
Thomas Guillem's avatar
Thomas Guillem committed
208
209
210
211
212
213
    } AudioFormat;
    struct {
        jint ERROR_DEAD_OBJECT;
        bool has_ERROR_DEAD_OBJECT;
        jint STREAM_MUSIC;
    } AudioManager;
Thomas Guillem's avatar
Thomas Guillem committed
214
215
216
217
    struct {
        jclass clazz;
        jmethodID getOutputLatency;
    } AudioSystem;
218
219
220
221
222
223
    struct {
        jclass clazz;
        jmethodID ctor;
        jfieldID framePosition;
        jfieldID nanoTime;
    } AudioTimestamp;
Thomas Guillem's avatar
Thomas Guillem committed
224
225
226
227
228
} jfields;

/* init all jni fields.
 * Done only one time during the first initialisation */
static bool
229
InitJNIFields( audio_output_t *p_aout, JNIEnv* env )
Thomas Guillem's avatar
Thomas Guillem committed
230
231
232
{
    static vlc_mutex_t lock = VLC_STATIC_MUTEX;
    static int i_init_state = -1;
233
    bool ret;
Thomas Guillem's avatar
Thomas Guillem committed
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
    jclass clazz;
    jfieldID field;

    vlc_mutex_lock( &lock );

    if( i_init_state != -1 )
        goto end;

#define CHECK_EXCEPTION( what, critical ) do { \
    if( (*env)->ExceptionOccurred( env ) ) \
    { \
        msg_Err( p_aout, "%s failed", what ); \
        (*env)->ExceptionClear( env ); \
        if( (critical) ) \
        { \
            i_init_state = 0; \
            goto end; \
        } \
    } \
} while( 0 )
#define GET_CLASS( str, critical ) do { \
    clazz = (*env)->FindClass( env, (str) ); \
256
    CHECK_EXCEPTION( "FindClass(" str ")", critical ); \
Thomas Guillem's avatar
Thomas Guillem committed
257
258
259
} while( 0 )
#define GET_ID( get, id, str, args, critical ) do { \
    jfields.id = (*env)->get( env, clazz, (str), (args) ); \
260
    CHECK_EXCEPTION( #get "(" #id ")", critical ); \
Thomas Guillem's avatar
Thomas Guillem committed
261
262
263
264
} while( 0 )
#define GET_CONST_INT( id, str, critical ) do { \
    field = NULL; \
    field = (*env)->GetStaticFieldID( env, clazz, (str), "I" ); \
265
    CHECK_EXCEPTION( "GetStaticFieldID(" #id ")", critical ); \
Thomas Guillem's avatar
Thomas Guillem committed
266
267
268
269
270
271
272
    if( field ) \
    { \
        jfields.id = (*env)->GetStaticIntField( env, clazz, field ); \
        CHECK_EXCEPTION( #id, critical ); \
    } \
} while( 0 )

273
    /* AudioTrack class init */
Thomas Guillem's avatar
Thomas Guillem committed
274
275
276
277
278
279
    GET_CLASS( "android/media/AudioTrack", true );
    jfields.AudioTrack.clazz = (jclass) (*env)->NewGlobalRef( env, clazz );
    CHECK_EXCEPTION( "NewGlobalRef", true );

    GET_ID( GetMethodID, AudioTrack.ctor, "<init>", "(IIIIII)V", true );
    GET_ID( GetMethodID, AudioTrack.release, "release", "()V", true );
Thomas Guillem's avatar
Thomas Guillem committed
280
    GET_ID( GetMethodID, AudioTrack.getState, "getState", "()I", true );
Thomas Guillem's avatar
Thomas Guillem committed
281
282
283
284
    GET_ID( GetMethodID, AudioTrack.play, "play", "()V", true );
    GET_ID( GetMethodID, AudioTrack.stop, "stop", "()V", true );
    GET_ID( GetMethodID, AudioTrack.flush, "flush", "()V", true );
    GET_ID( GetMethodID, AudioTrack.pause, "pause", "()V", true );
285
286
287
288
289

    GET_ID( GetMethodID, AudioTrack.writeV21, "write", "(Ljava/nio/ByteBuffer;II)I", false );
    if( jfields.AudioTrack.writeV21 )
    {
        GET_CONST_INT( AudioTrack.WRITE_NON_BLOCKING, "WRITE_NON_BLOCKING", true );
290
291
292
#ifdef AUDIOTRACK_USE_FLOAT
        GET_ID( GetMethodID, AudioTrack.writeFloat, "write", "([FIII)I", true );
#endif
293
294
    } else
        GET_ID( GetMethodID, AudioTrack.write, "write", "([BII)I", true );
295
296
297

    GET_ID( GetMethodID, AudioTrack.getTimestamp,
            "getTimestamp", "(Landroid/media/AudioTimestamp;)Z", false );
Thomas Guillem's avatar
Thomas Guillem committed
298
299
    GET_ID( GetMethodID, AudioTrack.getPlaybackHeadPosition,
            "getPlaybackHeadPosition", "()I", true );
300

Thomas Guillem's avatar
Thomas Guillem committed
301
302
    GET_ID( GetStaticMethodID, AudioTrack.getMinBufferSize, "getMinBufferSize",
            "(III)I", true );
303
#ifdef AUDIOTRACK_NATIVE_SAMPLERATE
304
305
    GET_ID( GetStaticMethodID, AudioTrack.getNativeOutputSampleRate,
            "getNativeOutputSampleRate",  "(I)I", true );
306
#endif
Thomas Guillem's avatar
Thomas Guillem committed
307
    GET_CONST_INT( AudioTrack.STATE_INITIALIZED, "STATE_INITIALIZED", true );
Thomas Guillem's avatar
Thomas Guillem committed
308
309
310
    GET_CONST_INT( AudioTrack.MODE_STREAM, "MODE_STREAM", true );
    GET_CONST_INT( AudioTrack.ERROR, "ERROR", true );
    GET_CONST_INT( AudioTrack.ERROR_BAD_VALUE , "ERROR_BAD_VALUE", true );
311
    GET_CONST_INT( AudioTrack.ERROR_INVALID_OPERATION,
Thomas Guillem's avatar
Thomas Guillem committed
312
313
                   "ERROR_INVALID_OPERATION", true );

314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
    /* AudioTimestamp class init (if any) */
    if( jfields.AudioTrack.getTimestamp )
    {
        GET_CLASS( "android/media/AudioTimestamp", true );
        jfields.AudioTimestamp.clazz = (jclass) (*env)->NewGlobalRef( env,
                                                                      clazz );
        CHECK_EXCEPTION( "NewGlobalRef", true );

        GET_ID( GetMethodID, AudioTimestamp.ctor, "<init>", "()V", true );
        GET_ID( GetFieldID, AudioTimestamp.framePosition,
                "framePosition", "J", true );
        GET_ID( GetFieldID, AudioTimestamp.nanoTime,
                "nanoTime", "J", true );
    }

329
#ifdef AUDIOTRACK_HW_LATENCY
Thomas Guillem's avatar
Thomas Guillem committed
330
331
332
333
334
335
336
337
    /* AudioSystem class init */
    GET_CLASS( "android/media/AudioSystem", false );
    if( clazz )
    {
        jfields.AudioSystem.clazz = (jclass) (*env)->NewGlobalRef( env, clazz );
        GET_ID( GetStaticMethodID, AudioSystem.getOutputLatency,
                "getOutputLatency", "(I)I", false );
    }
338
#endif
Thomas Guillem's avatar
Thomas Guillem committed
339

340
    /* AudioFormat class init */
Thomas Guillem's avatar
Thomas Guillem committed
341
342
343
344
345
346
    GET_CLASS( "android/media/AudioFormat", true );
    GET_CONST_INT( AudioFormat.ENCODING_PCM_8BIT, "ENCODING_PCM_8BIT", true );
    GET_CONST_INT( AudioFormat.ENCODING_PCM_16BIT, "ENCODING_PCM_16BIT", true );
#ifdef AUDIOTRACK_USE_FLOAT
    GET_CONST_INT( AudioFormat.ENCODING_PCM_FLOAT, "ENCODING_PCM_FLOAT",
                   false );
347
348
    jfields.AudioFormat.has_ENCODING_PCM_FLOAT = field != NULL &&
                                                 jfields.AudioTrack.writeFloat;
Thomas Guillem's avatar
Thomas Guillem committed
349
350
351
#else
    jfields.AudioFormat.has_ENCODING_PCM_FLOAT = false;
#endif
352
353
354
355
356
357
358
359
    GET_CONST_INT( AudioFormat.ENCODING_AC3, "ENCODING_AC3", false );
    if( field != NULL )
    {
        GET_CONST_INT( AudioFormat.ENCODING_E_AC3, "ENCODING_E_AC3", false );
        jfields.AudioFormat.has_ENCODING_AC3 = field != NULL;
    } else
        jfields.AudioFormat.has_ENCODING_AC3 = false;

Thomas Guillem's avatar
Thomas Guillem committed
360
361
    GET_CONST_INT( AudioFormat.CHANNEL_OUT_MONO, "CHANNEL_OUT_MONO", true );
    GET_CONST_INT( AudioFormat.CHANNEL_OUT_STEREO, "CHANNEL_OUT_STEREO", true );
362
363
    GET_CONST_INT( AudioFormat.CHANNEL_OUT_FRONT_LEFT, "CHANNEL_OUT_FRONT_LEFT", true );
    GET_CONST_INT( AudioFormat.CHANNEL_OUT_FRONT_RIGHT, "CHANNEL_OUT_FRONT_RIGHT", true );
364
365
366
367
368
369
370
371
372
373
374
375
376
    GET_CONST_INT( AudioFormat.CHANNEL_OUT_5POINT1, "CHANNEL_OUT_5POINT1", true );
    GET_CONST_INT( AudioFormat.CHANNEL_OUT_BACK_LEFT, "CHANNEL_OUT_BACK_LEFT", true );
    GET_CONST_INT( AudioFormat.CHANNEL_OUT_BACK_RIGHT, "CHANNEL_OUT_BACK_RIGHT", true );
    GET_CONST_INT( AudioFormat.CHANNEL_OUT_FRONT_CENTER, "CHANNEL_OUT_FRONT_CENTER", true );
    GET_CONST_INT( AudioFormat.CHANNEL_OUT_LOW_FREQUENCY, "CHANNEL_OUT_LOW_FREQUENCY", true );
    GET_CONST_INT( AudioFormat.CHANNEL_OUT_BACK_CENTER, "CHANNEL_OUT_BACK_CENTER", true );
    GET_CONST_INT( AudioFormat.CHANNEL_OUT_SIDE_LEFT, "CHANNEL_OUT_SIDE_LEFT", false );
    if( field != NULL )
    {
        GET_CONST_INT( AudioFormat.CHANNEL_OUT_SIDE_RIGHT, "CHANNEL_OUT_SIDE_RIGHT", true );
        jfields.AudioFormat.has_CHANNEL_OUT_SIDE = true;
    } else
        jfields.AudioFormat.has_CHANNEL_OUT_SIDE = false;
Thomas Guillem's avatar
Thomas Guillem committed
377

378
    /* AudioManager class init */
Thomas Guillem's avatar
Thomas Guillem committed
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
    GET_CLASS( "android/media/AudioManager", true );
    GET_CONST_INT( AudioManager.ERROR_DEAD_OBJECT, "ERROR_DEAD_OBJECT", false );
    jfields.AudioManager.has_ERROR_DEAD_OBJECT = field != NULL;
    GET_CONST_INT( AudioManager.STREAM_MUSIC, "STREAM_MUSIC", true );

#undef CHECK_EXCEPTION
#undef GET_CLASS
#undef GET_ID
#undef GET_CONST_INT

    i_init_state = 1;
end:
    ret = i_init_state == 1;
    if( !ret )
        msg_Err( p_aout, "AudioTrack jni init failed" );
    vlc_mutex_unlock( &lock );
    return ret;
}

static inline bool
399
check_exception( JNIEnv *env, audio_output_t *p_aout,
Thomas Guillem's avatar
Thomas Guillem committed
400
401
402
403
                 const char *method )
{
    if( (*env)->ExceptionOccurred( env ) )
    {
404
405
406
        aout_sys_t *p_sys = p_aout->sys;

        p_sys->b_audiotrack_exception = true;
407
        p_sys->b_error = true;
408
        (*env)->ExceptionDescribe( env );
Thomas Guillem's avatar
Thomas Guillem committed
409
410
411
412
413
414
        (*env)->ExceptionClear( env );
        msg_Err( p_aout, "AudioTrack.%s triggered an exception !", method );
        return true;
    } else
        return false;
}
415
#define CHECK_AT_EXCEPTION( method ) check_exception( env, p_aout, method )
Thomas Guillem's avatar
Thomas Guillem committed
416
417
418
419

#define JNI_CALL( what, obj, method, ... ) (*env)->what( env, obj, method, ##__VA_ARGS__ )

#define JNI_CALL_INT( obj, method, ... ) JNI_CALL( CallIntMethod, obj, method, ##__VA_ARGS__ )
420
#define JNI_CALL_BOOL( obj, method, ... ) JNI_CALL( CallBooleanMethod, obj, method, ##__VA_ARGS__ )
Thomas Guillem's avatar
Thomas Guillem committed
421
422
423
424
425
#define JNI_CALL_VOID( obj, method, ... ) JNI_CALL( CallVoidMethod, obj, method, ##__VA_ARGS__ )
#define JNI_CALL_STATIC_INT( clazz, method, ... ) JNI_CALL( CallStaticIntMethod, clazz, method, ##__VA_ARGS__ )

#define JNI_AT_NEW( ... ) JNI_CALL( NewObject, jfields.AudioTrack.clazz, jfields.AudioTrack.ctor, ##__VA_ARGS__ )
#define JNI_AT_CALL_INT( method, ... ) JNI_CALL_INT( p_sys->p_audiotrack, jfields.AudioTrack.method, ##__VA_ARGS__ )
426
#define JNI_AT_CALL_BOOL( method, ... ) JNI_CALL_BOOL( p_sys->p_audiotrack, jfields.AudioTrack.method, ##__VA_ARGS__ )
Thomas Guillem's avatar
Thomas Guillem committed
427
428
429
#define JNI_AT_CALL_VOID( method, ... ) JNI_CALL_VOID( p_sys->p_audiotrack, jfields.AudioTrack.method, ##__VA_ARGS__ )
#define JNI_AT_CALL_STATIC_INT( method, ... ) JNI_CALL( CallStaticIntMethod, jfields.AudioTrack.clazz, jfields.AudioTrack.method, ##__VA_ARGS__ )

430
#define JNI_AUDIOTIMESTAMP_GET_LONG( field ) JNI_CALL( GetLongField, p_sys->timestamp.p_obj, jfields.AudioTimestamp.field )
431
432

static inline mtime_t
433
frames_to_us( aout_sys_t *p_sys, uint64_t i_nb_frames )
Thomas Guillem's avatar
Thomas Guillem committed
434
{
435
436
437
    return  i_nb_frames * CLOCK_FREQ / p_sys->fmt.i_rate;
}
#define FRAMES_TO_US(x) frames_to_us( p_sys, (x) )
Thomas Guillem's avatar
Thomas Guillem committed
438

439
static inline uint64_t
440
441
442
443
444
445
446
447
448
449
bytes_to_frames( aout_sys_t *p_sys, size_t i_bytes )
{
    if( p_sys->b_spdif )
        return i_bytes * A52_FRAME_NB / p_sys->i_bytes_per_frame;
    else
        return i_bytes / p_sys->i_bytes_per_frame;
}
#define BYTES_TO_FRAMES(x) bytes_to_frames( p_sys, (x) )

static inline size_t
450
frames_to_bytes( aout_sys_t *p_sys, uint64_t i_frames )
451
452
453
454
455
456
457
458
{
    if( p_sys->b_spdif )
        return i_frames * p_sys->i_bytes_per_frame / A52_FRAME_NB;
    else
        return i_frames * p_sys->i_bytes_per_frame;
}
#define FRAMES_TO_BYTES(x) frames_to_bytes( p_sys, (x) )

459
460
461
462
/**
 * Get the AudioTrack position
 *
 * The doc says that the position is reset to zero after flush but it's not
463
 * true for all devices or Android versions.
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
 */
static uint64_t
AudioTrack_getPlaybackHeadPosition( JNIEnv *env, audio_output_t *p_aout )
{
    /* Android doc:
     * getPlaybackHeadPosition: Returns the playback head position expressed in
     * frames. Though the "int" type is signed 32-bits, the value should be
     * reinterpreted as if it is unsigned 32-bits. That is, the next position
     * after 0x7FFFFFFF is (int) 0x80000000. This is a continuously advancing
     * counter. It will wrap (overflow) periodically, for example approximately
     * once every 27:03:11 hours:minutes:seconds at 44.1 kHz. It is reset to
     * zero by flush(), reload(), and stop().
     */

    aout_sys_t *p_sys = p_aout->sys;
    uint32_t i_pos;

    /* int32_t to uint32_t */
    i_pos = 0xFFFFFFFFL & JNI_AT_CALL_INT( getPlaybackHeadPosition );

    /* uint32_t to uint64_t */
    if( p_sys->headpos.i_last > i_pos )
        p_sys->headpos.i_wrap_count++;
    p_sys->headpos.i_last = i_pos;
    return p_sys->headpos.i_last + ((uint64_t)p_sys->headpos.i_wrap_count << 32);
}

Thomas Guillem's avatar
Thomas Guillem committed
491
492
493
494
495
/**
 * Reset AudioTrack position
 *
 * Called after flush, or start
 */
496
static void
Thomas Guillem's avatar
Thomas Guillem committed
497
AudioTrack_ResetPlaybackHeadPosition( JNIEnv *env, audio_output_t *p_aout )
498
{
499
    (void) env;
500
501
    aout_sys_t *p_sys = p_aout->sys;

502
503
    p_sys->headpos.i_last = 0;
    p_sys->headpos.i_wrap_count = 0;
504
505
}

506
/**
507
 * Reset AudioTrack SmoothPosition and TimestampPosition
508
509
510
511
512
513
514
515
516
517
518
 */
static void
AudioTrack_ResetPositions( JNIEnv *env, audio_output_t *p_aout )
{
    aout_sys_t *p_sys = p_aout->sys;
    VLC_UNUSED( env );

    p_sys->timestamp.i_play_time = mdate();
    p_sys->timestamp.i_last_time = 0;
    p_sys->timestamp.i_frame_us = 0;
    p_sys->timestamp.i_frame_pos = 0;
519
520
521
522
523

    p_sys->smoothpos.i_count = 0;
    p_sys->smoothpos.i_idx = 0;
    p_sys->smoothpos.i_last_time = 0;
    p_sys->smoothpos.i_us = 0;
Thomas Guillem's avatar
Thomas Guillem committed
524
    p_sys->smoothpos.i_latency_us = 0;
525
526
}

527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
/**
 * Reset all AudioTrack positions and internal state
 */
static void
AudioTrack_Reset( JNIEnv *env, audio_output_t *p_aout )
{
    aout_sys_t *p_sys = p_aout->sys;

    if( p_sys->p_bytebuffer )
    {
        (*env)->DeleteGlobalRef( env, p_sys->p_bytebuffer );
        p_sys->p_bytebuffer = NULL;
    }

    AudioTrack_ResetPositions( env, p_aout );
    AudioTrack_ResetPlaybackHeadPosition( env, p_aout );
    p_sys->i_samples_written = 0;
}

546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
/**
 * Get a smooth AudioTrack position
 *
 * This function smooth out the AudioTrack position since it has a very bad
 * precision (+/- 20ms on old devices).
 */
static mtime_t
AudioTrack_GetSmoothPositionUs( JNIEnv *env, audio_output_t *p_aout )
{
    aout_sys_t *p_sys = p_aout->sys;
    uint64_t i_audiotrack_us;
    mtime_t i_now = mdate();

    /* Fetch an AudioTrack position every SMOOTHPOS_INTERVAL_US (30ms) */
    if( i_now - p_sys->smoothpos.i_last_time >= SMOOTHPOS_INTERVAL_US )
    {
562
        i_audiotrack_us = FRAMES_TO_US( AudioTrack_getPlaybackHeadPosition( env, p_aout ) );
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577

        p_sys->smoothpos.i_last_time = i_now;

        /* Base the position off the current time */
        p_sys->smoothpos.p_us[p_sys->smoothpos.i_idx] = i_audiotrack_us - i_now;
        p_sys->smoothpos.i_idx = (p_sys->smoothpos.i_idx + 1)
                                 % SMOOTHPOS_SAMPLE_COUNT;
        if( p_sys->smoothpos.i_count < SMOOTHPOS_SAMPLE_COUNT )
            p_sys->smoothpos.i_count++;

        /* Calculate the average position based off the current time */
        p_sys->smoothpos.i_us = 0;
        for( uint32_t i = 0; i < p_sys->smoothpos.i_count; ++i )
            p_sys->smoothpos.i_us += p_sys->smoothpos.p_us[i];
        p_sys->smoothpos.i_us /= p_sys->smoothpos.i_count;
Thomas Guillem's avatar
Thomas Guillem committed
578
579
580
581
582
583
584
585
586
587
588

        if( jfields.AudioSystem.getOutputLatency )
        {
            int i_latency_ms = JNI_CALL( CallStaticIntMethod,
                                         jfields.AudioSystem.clazz,
                                         jfields.AudioSystem.getOutputLatency,
                                         jfields.AudioManager.STREAM_MUSIC );

            p_sys->smoothpos.i_latency_us = i_latency_ms > 0 ?
                                            i_latency_ms * 1000L : 0;
        }
589
590
    }
    if( p_sys->smoothpos.i_us != 0 )
Thomas Guillem's avatar
Thomas Guillem committed
591
        return p_sys->smoothpos.i_us + i_now - p_sys->smoothpos.i_latency_us;
592
593
    else
        return 0;
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
}

static mtime_t
AudioTrack_GetTimestampPositionUs( JNIEnv *env, audio_output_t *p_aout )
{
    aout_sys_t *p_sys = p_aout->sys;
    mtime_t i_now;

    if( !p_sys->timestamp.p_obj )
        return 0;

    i_now = mdate();

    /* Android doc:
     * getTimestamp: Poll for a timestamp on demand.
     *
     * If you need to track timestamps during initial warmup or after a
     * routing or mode change, you should request a new timestamp once per
     * second until the reported timestamps show that the audio clock is
     * stable. Thereafter, query for a new timestamp approximately once
     * every 10 seconds to once per minute. Calling this method more often
     * is inefficient. It is also counter-productive to call this method
     * more often than recommended, because the short-term differences
     * between successive timestamp reports are not meaningful. If you need
     * a high-resolution mapping between frame position and presentation
     * time, consider implementing that at application level, based on
     * low-resolution timestamps.
     */

    /* Fetch an AudioTrack timestamp every AUDIOTIMESTAMP_INTERVAL_US (500ms) */
    if( i_now - p_sys->timestamp.i_last_time >= AUDIOTIMESTAMP_INTERVAL_US )
    {
        p_sys->timestamp.i_last_time = i_now;

        if( JNI_AT_CALL_BOOL( getTimestamp, p_sys->timestamp.p_obj ) )
        {
            p_sys->timestamp.i_frame_us = JNI_AUDIOTIMESTAMP_GET_LONG( nanoTime ) / 1000;
            p_sys->timestamp.i_frame_pos = JNI_AUDIOTIMESTAMP_GET_LONG( framePosition );
        }
        else
        {
            p_sys->timestamp.i_frame_us = 0;
            p_sys->timestamp.i_frame_pos = 0;
        }
    }

    /* frame time should be after last play time
     * frame time shouldn't be in the future
     * frame time should be less than 10 seconds old */
    if( p_sys->timestamp.i_frame_us != 0 && p_sys->timestamp.i_frame_pos != 0
     && p_sys->timestamp.i_frame_us > p_sys->timestamp.i_play_time
     && i_now > p_sys->timestamp.i_frame_us
     && ( i_now - p_sys->timestamp.i_frame_us ) <= INT64_C(10000000) )
    {
        jlong i_time_diff = i_now - p_sys->timestamp.i_frame_us;
        jlong i_frames_diff = i_time_diff * p_sys->fmt.i_rate / CLOCK_FREQ;
        return FRAMES_TO_US( p_sys->timestamp.i_frame_pos + i_frames_diff );
    } else
        return 0;
}

655
static int
656
TimeGet( audio_output_t *p_aout, mtime_t *restrict p_delay )
657
658
{
    aout_sys_t *p_sys = p_aout->sys;
659
    mtime_t i_audiotrack_us;
660
661
    JNIEnv *env;

662
    if( p_sys->b_error || !( env = GET_ENV() ) )
663
        return -1;
Thomas Guillem's avatar
Thomas Guillem committed
664

665
    if( p_sys->b_spdif )
666
        return -1;
667
668
669

    i_audiotrack_us = AudioTrack_GetTimestampPositionUs( env, p_aout );

670
671
    if( i_audiotrack_us <= 0 )
        i_audiotrack_us = AudioTrack_GetSmoothPositionUs(env, p_aout );
Thomas Guillem's avatar
Thomas Guillem committed
672

Thomas Guillem's avatar
Thomas Guillem committed
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
/* Debug log for both delays */
#if 0
{
    mtime_t i_written_us = FRAMES_TO_US( p_sys->i_samples_written );
    mtime_t i_ts_us = AudioTrack_GetTimestampPositionUs( env, p_aout );
    mtime_t i_smooth_us = 0;

    if( i_ts_us > 0 )
        i_smooth_us = AudioTrack_GetSmoothPositionUs(env, p_aout );
    else if ( p_sys->smoothpos.i_us != 0 )
        i_smooth_us = p_sys->smoothpos.i_us + mdate()
            - p_sys->smoothpos.i_latency_us;

    msg_Err( p_aout, "TimeGet: TimeStamp: %lld, Smooth: %lld (latency: %lld)",
                    i_ts_us ? i_written_us - i_ts_us : 0,
                    i_smooth_us ? i_written_us - i_smooth_us : 0,
                    p_sys->smoothpos.i_latency_us );
}
#endif

693
    if( i_audiotrack_us > 0 )
694
    {
695
696
697
698
699
700
701
702
        mtime_t i_delay = FRAMES_TO_US( p_sys->i_samples_written )
                          - i_audiotrack_us;
        if( i_delay >= 0 )
        {
            *p_delay = i_delay;
            return 0;
        }
        else
703
704
705
706
        {
            msg_Warn( p_aout, "timing screwed, reset positions" );
            AudioTrack_ResetPositions( env, p_aout );
        }
707
708
    }
    return -1;
Thomas Guillem's avatar
Thomas Guillem committed
709
710
}

711
static void
712
AudioTrack_GetChanOrder( uint16_t i_physical_channels, uint32_t p_chans_out[] )
713
{
714
#define HAS_CHAN( x ) ( ( i_physical_channels & (x) ) == (x) )
715
716
    /* samples will be in the following order: FL FR FC LFE BL BR BC SL SR */
    int i = 0;
717

718
    if( HAS_CHAN( AOUT_CHAN_LEFT ) )
719
        p_chans_out[i++] = AOUT_CHAN_LEFT;
720
    if( HAS_CHAN( AOUT_CHAN_RIGHT ) )
721
        p_chans_out[i++] = AOUT_CHAN_RIGHT;
722

723
    if( HAS_CHAN( AOUT_CHAN_CENTER ) )
724
        p_chans_out[i++] = AOUT_CHAN_CENTER;
725

726
    if( HAS_CHAN( AOUT_CHAN_LFE ) )
727
728
        p_chans_out[i++] = AOUT_CHAN_LFE;

729
    if( HAS_CHAN( AOUT_CHAN_REARLEFT ) )
730
        p_chans_out[i++] = AOUT_CHAN_REARLEFT;
731
    if( HAS_CHAN( AOUT_CHAN_REARRIGHT ) )
732
733
        p_chans_out[i++] = AOUT_CHAN_REARRIGHT;

734
    if( HAS_CHAN( AOUT_CHAN_REARCENTER ) )
735
736
        p_chans_out[i++] = AOUT_CHAN_REARCENTER;

737
738
739
740
741
    if( HAS_CHAN( AOUT_CHAN_MIDDLELEFT ) )
        p_chans_out[i++] = AOUT_CHAN_MIDDLELEFT;
    if( HAS_CHAN( AOUT_CHAN_MIDDLERIGHT ) )
        p_chans_out[i++] = AOUT_CHAN_MIDDLERIGHT;

742
    assert( i <= AOUT_CHAN_MAX );
743
744
745
#undef HAS_CHAN
}

746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
/**
 * Create an Android AudioTrack.
 * returns -1 on error, 0 on success.
 */
static int
AudioTrack_New( JNIEnv *env, audio_output_t *p_aout, unsigned int i_rate,
                int i_channel_config, int i_format, int i_size )
{
    aout_sys_t *p_sys = p_aout->sys;
    jobject p_audiotrack = JNI_AT_NEW( jfields.AudioManager.STREAM_MUSIC,
                                       i_rate, i_channel_config, i_format,
                                       i_size, jfields.AudioTrack.MODE_STREAM );
    if( CHECK_AT_EXCEPTION( "AudioTrack<init>" ) || !p_audiotrack )
    {
        msg_Warn( p_aout, "AudioTrack Init failed" ) ;
        return -1;
    }
    if( JNI_CALL_INT( p_audiotrack, jfields.AudioTrack.getState )
        != jfields.AudioTrack.STATE_INITIALIZED )
    {
        JNI_CALL_VOID( p_audiotrack, jfields.AudioTrack.release );
        (*env)->DeleteLocalRef( env, p_audiotrack );
        msg_Err( p_aout, "AudioTrack getState failed" );
        return -1;
    }

    p_sys->p_audiotrack = (*env)->NewGlobalRef( env, p_audiotrack );
    (*env)->DeleteLocalRef( env, p_audiotrack );
    if( !p_sys->p_audiotrack )
        return -1;

    return 0;
}

/**
 * Destroy and recreate an Android AudioTrack using the same arguments.
 * returns -1 on error, 0 on success.
 */
static int
785
AudioTrack_Recreate( JNIEnv *env, audio_output_t *p_aout )
786
787
788
789
790
{
    aout_sys_t *p_sys = p_aout->sys;

    JNI_AT_CALL_VOID( release );
    (*env)->DeleteGlobalRef( env, p_sys->p_audiotrack );
791
    p_sys->p_audiotrack = NULL;
792
793
794
795
796
797
    return AudioTrack_New( env, p_aout, p_sys->audiotrack_args.i_rate,
                           p_sys->audiotrack_args.i_channel_config,
                           p_sys->audiotrack_args.i_format,
                           p_sys->audiotrack_args.i_size );
}

798
799
/**
 * Configure and create an Android AudioTrack.
800
 * returns -1 on configuration error, 0 on success.
801
 */
802
803
804
805
806
807
static int
AudioTrack_Create( JNIEnv *env, audio_output_t *p_aout,
                   unsigned int i_rate,
                   vlc_fourcc_t i_vlc_format,
                   uint16_t i_physical_channels,
                   int i_bytes_per_frame )
Thomas Guillem's avatar
Thomas Guillem committed
808
{
809
    aout_sys_t *p_sys = p_aout->sys;
810
    int i_size, i_min_buffer_size, i_channel_config, i_format;
811

812
    switch( i_vlc_format )
813
814
815
816
817
818
819
820
821
822
    {
        case VLC_CODEC_U8:
            i_format = jfields.AudioFormat.ENCODING_PCM_8BIT;
            break;
        case VLC_CODEC_S16N:
            i_format = jfields.AudioFormat.ENCODING_PCM_16BIT;
            break;
        case VLC_CODEC_FL32:
            i_format = jfields.AudioFormat.ENCODING_PCM_FLOAT;
            break;
823
824
825
        case VLC_CODEC_SPDIFB:
            i_format = jfields.AudioFormat.ENCODING_AC3;
            break;
826
827
828
829
        default:
            vlc_assert_unreachable();
    }

830
    switch( i_physical_channels )
831
    {
832
        case AOUT_CHANS_7_1:
Thomas Guillem's avatar
Thomas Guillem committed
833
            /* bitmask of CHANNEL_OUT_7POINT1 doesn't correspond to 5POINT1 and
834
835
836
837
             * SIDES */
            i_channel_config = jfields.AudioFormat.CHANNEL_OUT_5POINT1 |
                               jfields.AudioFormat.CHANNEL_OUT_SIDE_LEFT |
                               jfields.AudioFormat.CHANNEL_OUT_SIDE_RIGHT;
838
839
            break;
        case AOUT_CHANS_5_1:
840
            i_channel_config = jfields.AudioFormat.CHANNEL_OUT_5POINT1;
841
842
            break;
        case AOUT_CHAN_LEFT:
843
            i_channel_config = jfields.AudioFormat.CHANNEL_OUT_MONO;
844
845
846
            break;
        default:
        case AOUT_CHANS_STEREO:
847
            i_channel_config = jfields.AudioFormat.CHANNEL_OUT_STEREO;
848
            break;
Thomas Guillem's avatar
Thomas Guillem committed
849
    }
850

851
    i_min_buffer_size = JNI_AT_CALL_STATIC_INT( getMinBufferSize, i_rate,
Thomas Guillem's avatar
Thomas Guillem committed
852
853
854
855
                                                i_channel_config, i_format );
    if( i_min_buffer_size <= 0 )
    {
        msg_Warn( p_aout, "getMinBufferSize returned an invalid size" ) ;
856
        return -1;
Thomas Guillem's avatar
Thomas Guillem committed
857
    }
858
859
860
    if( i_vlc_format == VLC_CODEC_SPDIFB )
        i_size = ( i_min_buffer_size / AOUT_SPDIF_SIZE + 1 ) * AOUT_SPDIF_SIZE;
    else
861
862
863
864
865
    {
        /* Optimal buffer size: i_min_buffer_size * 4 but between 250ms and
         * 1000ms */
        mtime_t i_time, i_clipped_time;

866
        i_size = i_min_buffer_size * 4;
867
868
869
870
871
        i_time = (i_size / i_bytes_per_frame) * CLOCK_FREQ / i_rate;

        i_clipped_time = VLC_CLIP( i_time, MIN_AUDIOTRACK_BUFFER_US,
                         MAX_AUDIOTRACK_BUFFER_US );
        if( i_clipped_time != i_time )
872
            i_size = (int)i_rate * i_clipped_time * i_bytes_per_frame / CLOCK_FREQ;
873
    }
Thomas Guillem's avatar
Thomas Guillem committed
874

875
    /* create AudioTrack object */
876
877
878
    if( AudioTrack_New( env, p_aout, i_rate, i_channel_config,
                        i_format , i_size ) != 0 )
        return -1;
879

880
881
882
883
884
885
    p_sys->audiotrack_args.i_rate = i_rate;
    p_sys->audiotrack_args.i_channel_config = i_channel_config;
    p_sys->audiotrack_args.i_format = i_format;
    p_sys->audiotrack_args.i_size = i_size;

    return 0;
886
887
888
}

static int
889
Start( audio_output_t *p_aout, audio_sample_format_t *restrict p_fmt )
890
891
{
    aout_sys_t *p_sys = p_aout->sys;
892
    JNIEnv *env;
893
894
    int i_nb_channels, i_max_channels, i_bytes_per_frame, i_native_rate = 0,
        i_ret;
895
    unsigned int i_rate;
896
897
    bool b_spdif;

898
899
900
901
902
903
904
905
906
907
    if( p_sys->at_dev == AT_DEV_HDMI )
    {
        b_spdif = true;
        i_max_channels = AT_DEV_MAX_CHANNELS;
    }
    else
    {
        b_spdif = var_InheritBool( p_aout, "spdif" );
        i_max_channels = 2;
    }
908

909
    if( !( env = GET_ENV() ) )
910
911
912
913
        return VLC_EGENERIC;

    p_sys->fmt = *p_fmt;

914
    aout_FormatPrint( p_aout, "VLC is looking for:", &p_sys->fmt );
915

916
917
    p_sys->fmt.i_original_channels = p_sys->fmt.i_physical_channels;

918
    if( b_spdif )
919
    {
920
921
922
923
        i_native_rate = p_sys->fmt.i_rate;
    }
    else
    {
924
925
926
        if (jfields.AudioTrack.getNativeOutputSampleRate)
            i_native_rate = JNI_AT_CALL_STATIC_INT( getNativeOutputSampleRate,
                                                    jfields.AudioManager.STREAM_MUSIC );
927
928
        if( i_native_rate <= 0 )
            i_native_rate = VLC_CLIP( p_sys->fmt.i_rate, 4000, 48000 );
929
    }
930
931
932

    /* We can only accept U8, S16N, FL32, and AC3 */
    switch( p_sys->fmt.i_format )
933
    {
934
935
936
937
938
939
940
941
        case VLC_CODEC_U8:
            break;
        case VLC_CODEC_S16N:
            break;
        case VLC_CODEC_FL32:
            if( !jfields.AudioFormat.has_ENCODING_PCM_FLOAT )
                p_sys->fmt.i_format = VLC_CODEC_S16N;
            break;
942
        case VLC_CODEC_A52:
943
            if( jfields.AudioFormat.has_ENCODING_AC3 && b_spdif )
944
945
946
947
948
949
                p_sys->fmt.i_format = VLC_CODEC_SPDIFB;
            else if( jfields.AudioFormat.has_ENCODING_PCM_FLOAT )
                p_sys->fmt.i_format = VLC_CODEC_FL32;
            else
                p_sys->fmt.i_format = VLC_CODEC_S16N;
            break;
950
        default:
951
            p_sys->fmt.i_format = VLC_CODEC_S16N;
952
953
954
955
956
957
958
            break;
    }

    /* Android AudioTrack supports only mono, stereo, 5.1 and 7.1.
     * Android will downmix to stereo if audio output doesn't handle 5.1 or 7.1
     */
    i_nb_channels = aout_FormatNbChannels( &p_sys->fmt );
959
960
    if( p_sys->fmt.i_format != VLC_CODEC_SPDIFB )
        i_nb_channels = __MIN( i_max_channels, i_nb_channels );
961
962
963
964
965
966
967
968
969
970
971
    if( i_nb_channels > 5 )
    {
        if( i_nb_channels > 7 && jfields.AudioFormat.has_CHANNEL_OUT_SIDE )
            p_sys->fmt.i_physical_channels = AOUT_CHANS_7_1;
        else
            p_sys->fmt.i_physical_channels = AOUT_CHANS_5_1;
    } else
    {
        if( i_nb_channels == 1 )
            p_sys->fmt.i_physical_channels = AOUT_CHAN_LEFT;
        else
972
            p_sys->fmt.i_physical_channels = AOUT_CHANS_STEREO;
973
    }
974

975
976
    do
    {
977
        i_nb_channels = aout_FormatNbChannels( &p_sys->fmt );
978
979
        i_bytes_per_frame = i_nb_channels *
                            aout_BitsPerSample( p_sys->fmt.i_format ) / 8;
980
981
982
        i_rate = p_sys->fmt.i_format == VLC_CODEC_SPDIFB ?
                                        VLC_CLIP( p_sys->fmt.i_rate, 32000, 48000 )
                                        : (unsigned int) i_native_rate;
983

984
        /* Try to create an AudioTrack with the most advanced channel and
985
986
987
988
989
990
991
         * format configuration. If AudioTrack_Create fails, try again with a
         * less advanced format (PCM S16N). If it fails again, try again with
         * Stereo channels. */
        i_ret = AudioTrack_Create( env, p_aout, i_rate, p_sys->fmt.i_format,
                                   p_sys->fmt.i_physical_channels,
                                   i_bytes_per_frame );
        if( i_ret != 0 )
992
        {
993
994
995
996
997
998
999
1000
1001
1002
            if( p_sys->fmt.i_format == VLC_CODEC_SPDIFB )
            {
                msg_Warn( p_aout, "SPDIF configuration failed, "
                                  "fallback to PCM" );
                if( jfields.AudioFormat.has_ENCODING_PCM_FLOAT )
                    p_sys->fmt.i_format = VLC_CODEC_FL32;
                else
                    p_sys->fmt.i_format = VLC_CODEC_S16N;
            }
            else if( p_sys->fmt.i_format == VLC_CODEC_FL32 )
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
            {
                msg_Warn( p_aout, "FL32 configuration failed, "
                                  "fallback to S16N PCM" );
                p_sys->fmt.i_format = VLC_CODEC_S16N;
            }
            else if( i_nb_channels > 5 )
            {
                msg_Warn( p_aout, "5.1 or 7.1 configuration failed, "
                                  "fallback to Stereo" );
                p_sys->fmt.i_physical_channels = AOUT_CHANS_STEREO;
            }
            else
                break;
1016
        }
1017
    } while( i_ret != 0 );
1018

1019
    if( i_ret != 0 )
1020
        return VLC_EGENERIC;
1021

1022
    p_sys->fmt.i_rate = i_rate;
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
    p_sys->b_spdif = p_sys->fmt.i_format == VLC_CODEC_SPDIFB;
    if( p_sys->b_spdif )
    {
        p_sys->fmt.i_bytes_per_frame = AOUT_SPDIF_SIZE;
        p_sys->fmt.i_frame_length = A52_FRAME_NB;
        p_sys->i_bytes_per_frame = p_sys->fmt.i_bytes_per_frame;
    }
    else
    {
        uint32_t p_chans_out[AOUT_CHAN_MAX];

        memset( p_chans_out, 0, sizeof(p_chans_out) );
        AudioTrack_GetChanOrder( p_sys->fmt.i_physical_channels, p_chans_out );
        p_sys->i_chans_to_reorder =
            aout_CheckChannelReorder( NULL, p_chans_out,
                                      p_sys->fmt.i_physical_channels,
                                      p_sys->p_chan_table );
1040
        p_sys->i_bytes_per_frame = i_bytes_per_frame;
1041
    }
1042
    p_sys->i_max_audiotrack_samples = BYTES_TO_FRAMES( p_sys->audiotrack_args.i_size );
Thomas Guillem's avatar
Thomas Guillem committed
1043

1044
#ifdef AUDIOTRACK_HW_LATENCY
1045
1046
1047
    if( jfields.AudioTimestamp.clazz )
    {
        /* create AudioTimestamp object */
1048
1049
1050
        jobject p_obj = JNI_CALL( NewObject, jfields.AudioTimestamp.clazz,
                                 jfields.AudioTimestamp.ctor );
        if( p_obj )
1051
        {
1052
1053
            p_sys->timestamp.p_obj = (*env)->NewGlobalRef( env, p_obj );
            (*env)->DeleteLocalRef( env, p_obj );
1054
        }
1055
        if( !p_sys->timestamp.p_obj )
1056
        {
1057
            Stop( p_aout );
1058
1059
            return VLC_EGENERIC;
        }
1060
    }
1061
#endif
Thomas Guillem's avatar
Thomas Guillem committed
1062

1063
    if( p_sys->fmt.i_format == VLC_CODEC_FL32 )
Thomas Guillem's avatar
Thomas Guillem committed
1064
    {
1065
1066
1067
1068
        msg_Dbg( p_aout, "using WRITE_FLOAT");
        p_sys->i_write_type = WRITE_FLOAT;
    }
    else if( jfields.AudioTrack.writeV21 )
1069
    {
Thomas Guillem's avatar
Thomas Guillem committed
1070
1071
1072
1073
1074
1075
1076
1077
1078
        msg_Dbg( p_aout, "using WRITE_V21");
        p_sys->i_write_type = WRITE_V21;
    }
    else
    {
        msg_Dbg( p_aout, "using WRITE");
        p_sys->i_write_type = WRITE;
    }

1079
    AudioTrack_Reset( env, p_aout );
Thomas Guillem's avatar
Thomas Guillem committed
1080
    JNI_AT_CALL_VOID( play );
1081
    CHECK_AT_EXCEPTION( "play" );
Thomas Guillem's avatar
Thomas Guillem committed
1082

1083
1084
1085
    *p_fmt = p_sys->fmt;
    aout_SoftVolumeStart( p_aout );

1086
1087
    aout_FormatPrint( p_aout, "VLC will output:", &p_sys->fmt );

Thomas Guillem's avatar
Thomas Guillem committed
1088
1089
1090
1091
    return VLC_SUCCESS;
}

static void
1092
Stop( audio_output_t *p_aout )
Thomas Guillem's avatar
Thomas Guillem committed
1093
1094
{
    aout_sys_t *p_sys = p_aout->sys;
1095
1096
    JNIEnv *env;

1097
    if( !( env = GET_ENV() ) )
1098
        return;
Thomas Guillem's avatar
Thomas Guillem committed
1099

1100
    if( p_sys->p_audiotrack )
1101
    {
1102
1103
1104
1105
1106
1107
1108
1109
        if( !p_sys->b_audiotrack_exception )
        {
            JNI_AT_CALL_VOID( stop );
            if( !CHECK_AT_EXCEPTION( "stop" ) )
                JNI_AT_CALL_VOID( release );
        }
        (*env)->DeleteGlobalRef( env, p_sys->p_audiotrack );
        p_sys->p_audiotrack = NULL;
1110
    }
1111
    if( p_sys->timestamp.p_obj )
1112
    {
1113
1114
        (*env)->DeleteGlobalRef( env, p_sys->timestamp.p_obj );
        p_sys->timestamp.p_obj = NULL;
1115
    }
1116
1117
1118

    p_sys->b_audiotrack_exception = false;
    p_sys->b_error = false;
Thomas Guillem's avatar
Thomas Guillem committed
1119
1120
}

1121
1122
1123
1124
1125
/**
 * Non blocking write function.
 * Do a calculation between current position and audiotrack position and assure
 * that we won't wait in AudioTrack.write() method
 */
1126
static int
1127
1128
AudioTrack_Write( JNIEnv *env, audio_output_t *p_aout, block_t *p_buffer,
                  size_t i_buffer_offset, bool b_force )
Thomas Guillem's avatar
Thomas Guillem committed
1129
1130
{
    aout_sys_t *p_sys = p_aout->sys;
1131
    size_t i_data;
1132
1133
1134
    uint64_t i_samples;
    uint64_t i_audiotrack_pos;
    uint64_t i_samples_pending;
1135

Thomas Guillem's avatar
Thomas Guillem committed
1136
    i_data = p_buffer->i_buffer - i_buffer_offset;
1137
1138
1139
    i_audiotrack_pos = AudioTrack_getPlaybackHeadPosition( env, p_aout );

    assert( i_audiotrack_pos <= p_sys->i_samples_written );
1140
1141
    if( i_audiotrack_pos > p_sys->i_samples_written )
    {
1142
        msg_Err( p_aout, "audiotrack position is ahead. Should NOT happen" );
Thomas Guillem's avatar
Thomas Guillem committed
1143
        p_sys->i_samples_written = 0;
1144
        p_sys->b_error = true;
1145
1146
1147
1148
1149
        return 0;
    }
    i_samples_pending = p_sys->i_samples_written - i_audiotrack_pos;

    /* check if audiotrack buffer is not full before writing on it. */
1150
    if( b_force )
1151
    {
1152
1153
1154
1155
        msg_Warn( p_aout, "Force write. It may block..." );
        i_samples_pending = 0;
    } else if( i_samples_pending >= p_sys->i_max_audiotrack_samples )
        return 0;
1156

1157
    i_samples = __MIN( p_sys->i_max_audiotrack_samples - i_samples_pending,
Thomas Guillem's avatar
Thomas Guillem committed
1158
                       BYTES_TO_FRAMES( i_data ) );
1159

1160
    i_data = FRAMES_TO_BYTES( i_samples );
Thomas Guillem's avatar
Thomas Guillem committed
1161

Thomas Guillem's avatar
Thomas Guillem committed
1162
1163
    return JNI_AT_CALL_INT( write, p_sys->p_bytearray,
                            i_buffer_offset, i_data );
1164
1165
}

1166
/**
1167
1168
 * Non blocking write function for Lollipop and after.
 * It calls a new write method with WRITE_NON_BLOCKING flags.
1169
1170
 */
static int
1171
1172
AudioTrack_WriteV21( JNIEnv *env, audio_output_t *p_aout, block_t *p_buffer,
                     size_t i_buffer_offset )
1173
1174
1175
{
    aout_sys_t *p_sys = p_aout->sys;
    int i_ret;
Thomas Guillem's avatar
Thomas Guillem committed
1176
1177
    size_t i_data = p_buffer->i_buffer - i_buffer_offset;
    uint8_t *p_data = p_buffer->p_buffer + i_buffer_offset;
1178
1179
1180
1181
1182

    if( !p_sys->p_bytebuffer )
    {
        jobject p_bytebuffer;

Thomas Guillem's avatar
Thomas Guillem committed
1183
        p_bytebuffer = (*env)->NewDirectByteBuffer( env, p_data, i_data );
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
        if( !p_bytebuffer )
            return jfields.AudioTrack.ERROR_BAD_VALUE;

        p_sys->p_bytebuffer = (*env)->NewGlobalRef( env, p_bytebuffer );
        (*env)->DeleteLocalRef( env, p_bytebuffer );

        if( !p_sys->p_bytebuffer || (*env)->ExceptionOccurred( env ) )
        {
            p_sys->p_bytebuffer = NULL;
            (*env)->ExceptionClear( env );
            return jfields.AudioTrack.ERROR_BAD_VALUE;
        }
    }

Thomas Guillem's avatar
Thomas Guillem committed
1198
    i_ret = JNI_AT_CALL_INT( writeV21, p_sys->p_bytebuffer, i_data,
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
                             jfields.AudioTrack.WRITE_NON_BLOCKING );
    if( i_ret > 0 )
    {
        /* don't delete the bytebuffer if we wrote nothing, keep it for next
         * call */
        (*env)->DeleteGlobalRef( env, p_sys->p_bytebuffer );
        p_sys->p_bytebuffer = NULL;
    }
    return i_ret;
}

1210
1211
1212
1213
1214
/**
 * Non blocking write float function for Lollipop and after.
 * It calls a new write method with WRITE_NON_BLOCKING flags.
 */
static int
1215
1216
AudioTrack_WriteFloat( JNIEnv *env, audio_output_t *p_aout, block_t *p_buffer,
                       size_t i_buffer_offset )
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
{
    aout_sys_t *p_sys = p_aout->sys;
    int i_ret;
    size_t i_data;

    i_buffer_offset /= 4;
    i_data = p_buffer->i_buffer / 4 - i_buffer_offset;

    i_ret = JNI_AT_CALL_INT( writeFloat, p_sys->p_floatarray,
                             i_buffer_offset, i_data,
                             jfields.AudioTrack.WRITE_NON_BLOCKING );
    if( i_ret < 0 )
        return i_ret;
    else
        return i_ret * 4;
}

Thomas Guillem's avatar
Thomas Guillem committed
1234
static int
1235
1236
AudioTrack_PreparePlay( JNIEnv *env, audio_output_t *p_aout,
                         block_t *p_buffer )
Thomas Guillem's avatar
Thomas Guillem committed
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
{
    aout_sys_t *p_sys = p_aout->sys;

    if( p_sys->i_chans_to_reorder )
       aout_ChannelReorder( p_buffer->p_buffer, p_buffer->i_buffer,
                            p_sys->i_chans_to_reorder, p_sys->p_chan_table,
                            p_sys->fmt.i_format );

    switch( p_sys->i_write_type )
    {
    case WRITE:
        /* check if we need to realloc a ByteArray */
        if( p_buffer->i_buffer > p_sys->i_bytearray_size )
        {
            jbyteArray p_bytearray;

            if( p_sys->p_bytearray )
            {
                (*env)->DeleteGlobalRef( env, p_sys->p_bytearray );
                p_sys->p_bytearray = NULL;
            }

            p_bytearray = (*env)->NewByteArray( env, p_buffer->i_buffer );
            if( p_bytearray )
            {
                p_sys->p_bytearray = (*env)->NewGlobalRef( env, p_bytearray );
                (*env)->DeleteLocalRef( env, p_bytearray );
            }
            p_sys->i_bytearray_size = p_buffer->i_buffer;