[vlc-commits] [Git][videolan/vlc][master] 4 commits: audiotrack: reorder to avoid forward declaration
Steve Lhomme (@robUx4)
gitlab at videolan.org
Fri Mar 17 21:03:24 UTC 2023
Steve Lhomme pushed to branch master at VideoLAN / VLC
Commits:
6202abd9 by Thomas Guillem at 2023-03-17T20:44:54+00:00
audiotrack: reorder to avoid forward declaration
No functional changes.
- - - - -
8d0819f5 by Thomas Guillem at 2023-03-17T20:44:54+00:00
audiotrack: remove unused cond
Leftover from 582570aad10b0dda1a725039ab6c35dc3e9d51c3
- - - - -
4bb250cd by Thomas Guillem at 2023-03-17T20:44:54+00:00
audiotrack: use TimingReport API
Remove the smoothpos hack that was only needed for time_get.
- - - - -
e476328e by Thomas Guillem at 2023-03-17T20:44:54+00:00
audiotrack: use precise latency
AudioTrack#getTimestamp, present since API 19, is the most precise,
[-20ms;0ms] delay tested with the Sync-One2.
- - - - -
1 changed file:
- modules/audio_output/android/audiotrack.c
Changes:
=====================================
modules/audio_output/android/audiotrack.c
=====================================
@@ -45,13 +45,7 @@
#define AUDIOTRACK_MAX_BUFFER_US VLC_TICK_FROM_MS(750) // 750ms
-static void Stop( aout_stream_t * );
-static void Play( aout_stream_t *, block_t *, vlc_tick_t );
-static void Flush( aout_stream_t * );
-static void Pause( aout_stream_t *, bool, vlc_tick_t );
-static void VolumeSet( aout_stream_t *, float );
-static void MuteSet( aout_stream_t *, bool );
-static void *AudioTrack_Thread( void * );
+#define TIMING_REPORT_DELAY_TICKS VLC_TICK_FROM_MS(1000)
typedef struct
{
@@ -78,24 +72,11 @@ typedef struct
/* Used by AudioTrack_GetTimestampPositionUs */
struct {
jobject p_obj; /* AudioTimestamp ref */
- vlc_tick_t i_frame_us;
jlong i_frame_post_last;
uint64_t i_frame_wrap_count;
- uint64_t i_frame_pos;
-
- vlc_tick_t i_last_time;
} timestamp;
- /* Used by AudioTrack_GetSmoothPositionUs */
- struct {
- uint32_t i_idx;
- uint32_t i_count;
- vlc_tick_t p_us[SMOOTHPOS_SAMPLE_COUNT];
- vlc_tick_t i_us;
- vlc_tick_t i_last_time;
- } smoothpos;
-
uint32_t i_max_audiotrack_samples;
long long i_encoding_flags;
bool b_passthrough;
@@ -112,7 +93,6 @@ typedef struct
vlc_thread_t thread; /* AudioTrack_Thread */
vlc_mutex_t lock;
- vlc_cond_t aout_cond; /* cond owned by aout */
vlc_cond_t thread_cond; /* cond owned by AudioTrack_Thread */
/* These variables need locking on read and write */
@@ -133,12 +113,16 @@ typedef struct
vlc_frame_t *frame_chain;
vlc_frame_t **frame_last;
+
+ /* Bytes written since the last timing report */
+ size_t timing_report_last_written_bytes;
+ /* Number of bytes to write before sending a timing report */
+ size_t timing_report_delay_bytes;
} aout_sys_t;
// Don't use Float for now since 5.1/7.1 Float is down sampled to Stereo Float
//#define AUDIOTRACK_USE_FLOAT
-//#define AUDIOTRACK_HW_LATENCY
/* Get AudioTrack native sample rate: if activated, most of the resampling
* will be done by VLC */
@@ -316,9 +300,7 @@ AudioTrack_InitJNI( vlc_object_t *p_aout)
GET_ID( GetMethodID, AudioTrack.getBufferSizeInFrames,
"getBufferSizeInFrames", "()I", false );
-#ifdef AUDIOTRACK_HW_LATENCY
GET_ID( GetMethodID, AudioTrack.getLatency, "getLatency", "()I", false );
-#endif
GET_ID( GetMethodID, AudioTrack.getTimestamp,
"getTimestamp", "(Landroid/media/AudioTimestamp;)Z", false );
@@ -561,25 +543,6 @@ AudioTrack_ResetWrapCount( JNIEnv *env, aout_stream_t *stream )
p_sys->timestamp.i_frame_wrap_count = 0;
}
-/**
- * Reset AudioTrack SmoothPosition and TimestampPosition
- */
-static void
-AudioTrack_ResetPositions( JNIEnv *env, aout_stream_t *stream )
-{
- aout_sys_t *p_sys = stream->sys;
- VLC_UNUSED( env );
-
- p_sys->timestamp.i_last_time = 0;
- p_sys->timestamp.i_frame_us = 0;
- p_sys->timestamp.i_frame_pos = 0;
-
- 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;
-}
-
/**
* Reset all AudioTrack positions and internal state
*/
@@ -588,9 +551,10 @@ AudioTrack_Reset( JNIEnv *env, aout_stream_t *stream )
{
aout_sys_t *p_sys = stream->sys;
- AudioTrack_ResetPositions( env, stream );
AudioTrack_ResetWrapCount( env, stream );
p_sys->i_samples_written = 0;
+ p_sys->timing_report_last_written_bytes = 0;
+ p_sys->timing_report_delay_bytes = 0;
}
static vlc_tick_t
@@ -616,179 +580,6 @@ AudioTrack_GetLatencyUs( JNIEnv *env, aout_stream_t *stream )
return 0;
}
-/**
- * Get a smooth AudioTrack position
- *
- * This function smooth out the AudioTrack position since it has a very bad
- * precision (+/- 20ms on old devices).
- */
-static vlc_tick_t
-AudioTrack_GetSmoothPositionUs( JNIEnv *env, aout_stream_t *stream )
-{
- aout_sys_t *p_sys = stream->sys;
- uint64_t i_audiotrack_us;
- vlc_tick_t i_now = vlc_tick_now();
-
- /* Fetch an AudioTrack position every SMOOTHPOS_INTERVAL_US (30ms) */
- if( i_now - p_sys->smoothpos.i_last_time >= SMOOTHPOS_INTERVAL_US )
- {
- i_audiotrack_us = FRAMES_TO_US( AudioTrack_getPlaybackHeadPosition( env, stream ) );
- if( i_audiotrack_us == 0 )
- goto bailout;
-
- 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;
-
- }
- if( p_sys->smoothpos.i_us != 0 )
- return p_sys->smoothpos.i_us + i_now - AudioTrack_GetLatencyUs( env, stream );
-
-bailout:
- return 0;
-}
-
-static vlc_tick_t
-AudioTrack_GetTimestampPositionUs( JNIEnv *env, aout_stream_t *stream )
-{
- aout_sys_t *p_sys = stream->sys;
- vlc_tick_t i_now;
-
- if( !p_sys->timestamp.p_obj )
- return 0;
-
- i_now = vlc_tick_now();
-
- /* 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 )
- {
- if( JNI_AT_CALL_BOOL( getTimestamp, p_sys->timestamp.p_obj ) )
- {
- p_sys->timestamp.i_frame_us = VLC_TICK_FROM_NS(JNI_AUDIOTIMESTAMP_GET_LONG( nanoTime ));
-
- /* the low-order 32 bits of position is in wrapping frame units
- * similar to AudioTrack#getPlaybackHeadPosition. */
- jlong i_frame_post_last = JNI_AUDIOTIMESTAMP_GET_LONG( framePosition );
- if( p_sys->timestamp.i_frame_post_last > i_frame_post_last )
- p_sys->timestamp.i_frame_wrap_count++;
- p_sys->timestamp.i_frame_post_last = i_frame_post_last;
- p_sys->timestamp.i_frame_pos = i_frame_post_last
- + (p_sys->timestamp.i_frame_wrap_count << 32);
-
- /* 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
- && i_now > p_sys->timestamp.i_frame_us
- && ( i_now - p_sys->timestamp.i_frame_us ) <= VLC_TICK_FROM_SEC(10) )
- p_sys->timestamp.i_last_time = i_now;
- else
- {
- p_sys->timestamp.i_last_time = 0;
- p_sys->timestamp.i_frame_us = 0;
- }
- }
- else
- p_sys->timestamp.i_frame_us = 0;
- }
-
- if( p_sys->timestamp.i_frame_us != 0 )
- {
- vlc_tick_t i_time_diff = i_now - p_sys->timestamp.i_frame_us;
- jlong i_frames_diff = samples_from_vlc_tick(i_time_diff, p_sys->fmt.i_rate);
- return FRAMES_TO_US( p_sys->timestamp.i_frame_pos + i_frames_diff );
- } else
- return 0;
-}
-
-static int
-TimeGet( aout_stream_t *stream, vlc_tick_t *restrict p_delay )
-{
- aout_sys_t *p_sys = stream->sys;
- vlc_tick_t i_audiotrack_us;
- JNIEnv *env;
-
- if( p_sys->b_passthrough )
- return -1;
-
- vlc_mutex_lock( &p_sys->lock );
-
- if( p_sys->b_error || !p_sys->i_samples_written || !( env = GET_ENV() ) )
- goto bailout;
-
- i_audiotrack_us = AudioTrack_GetTimestampPositionUs( env, stream );
-
- if( i_audiotrack_us <= 0 )
- i_audiotrack_us = AudioTrack_GetSmoothPositionUs(env, stream );
-
-/* Debug log for both delays */
-#if 0
-{
- vlc_tick_t i_ts_us = AudioTrack_GetTimestampPositionUs( env, stream );
- vlc_tick_t i_smooth_us = AudioTrack_GetSmoothPositionUs(env, stream );
- vlc_tick_t i_latency_us = AudioTrack_GetLatencyUs( env, stream );
-
- msg_Err( stream, "TimeGet: TimeStamp: %"PRId64", Smooth: %"PRId64" (latency: %"PRId64")",
- i_ts_us, i_smooth_us, i_latency_us );
-}
-#endif
-
- if( i_audiotrack_us > 0 )
- {
- /* AudioTrack delay */
- vlc_tick_t i_delay = FRAMES_TO_US( p_sys->i_samples_written )
- - i_audiotrack_us;
- if( i_delay >= 0 )
- {
- /* Frame FIFO + jarray delay */
- size_t total_size;
- vlc_frame_ChainProperties(p_sys->frame_chain, NULL, &total_size, NULL);
- i_delay += BYTES_TO_US(total_size + p_sys->jbuffer.size
- - p_sys->jbuffer.offset);
-
- *p_delay = i_delay;
- vlc_mutex_unlock( &p_sys->lock );
- return 0;
- }
- else
- {
- msg_Warn( stream, "timing screwed, reset positions" );
- AudioTrack_ResetPositions( env, stream );
- }
- }
-
-bailout:
- vlc_mutex_unlock( &p_sys->lock );
- return -1;
-}
-
static void
AudioTrack_GetChanOrder( uint16_t i_physical_channels, uint32_t p_chans_out[] )
{
@@ -1058,1033 +849,1083 @@ AudioTrack_Create( JNIEnv *env, aout_stream_t *stream,
return 0;
}
-static int GetPassthroughFmt( bool compat, audio_sample_format_t *fmt, int *at_format )
+static void
+AudioTrack_ConsumeFrame(aout_stream_t *stream, vlc_frame_t *f)
{
- if( !compat && jfields.AudioFormat.has_ENCODING_IEC61937 )
- {
- *at_format = jfields.AudioFormat.ENCODING_IEC61937;
- switch( fmt->i_format )
- {
- case VLC_CODEC_TRUEHD:
- case VLC_CODEC_MLP:
- fmt->i_rate = 192000;
- fmt->i_bytes_per_frame = 16;
+ aout_sys_t *p_sys = stream->sys;
+ assert(f != NULL && f == p_sys->frame_chain);
- /* AudioFormat.ENCODING_IEC61937 documentation says that the
- * channel layout must be stereo. Well, not for TrueHD
- * apparently */
- fmt->i_physical_channels = AOUT_CHANS_7_1;
- break;
- case VLC_CODEC_DTS:
- fmt->i_bytes_per_frame = 4;
- fmt->i_physical_channels = AOUT_CHANS_STEREO;
- break;
- case VLC_CODEC_DTSHD:
- fmt->i_bytes_per_frame = 4;
- fmt->i_physical_channels = AOUT_CHANS_STEREO;
- fmt->i_rate = 192000;
- fmt->i_bytes_per_frame = 16;
- break;
- case VLC_CODEC_EAC3:
- fmt->i_rate = 192000;
- case VLC_CODEC_A52:
- fmt->i_physical_channels = AOUT_CHANS_STEREO;
- fmt->i_bytes_per_frame = 4;
- break;
- default:
- return VLC_EGENERIC;
- }
- fmt->i_frame_length = 1;
- fmt->i_channels = aout_FormatNbChannels( fmt );
- fmt->i_format = VLC_CODEC_SPDIFL;
- }
- else
- {
- if( vlc_android_AudioFormat_FourCCToEncoding( fmt->i_format, at_format )
- != VLC_SUCCESS )
- return VLC_EGENERIC;
- fmt->i_bytes_per_frame = 4;
- fmt->i_frame_length = 1;
- fmt->i_physical_channels = AOUT_CHANS_STEREO;
- fmt->i_channels = 2;
- fmt->i_format = VLC_CODEC_SPDIFB;
- }
+ p_sys->frame_chain = f->p_next;
+ if (p_sys->frame_chain == NULL)
+ p_sys->frame_last = &p_sys->frame_chain;
- return VLC_SUCCESS;
+ vlc_frame_Release(f);
}
static int
-StartPassthrough( JNIEnv *env, aout_stream_t *stream )
+AudioTrack_AllocJArray(JNIEnv *env, aout_stream_t *stream, size_t size,
+ jarray (*new)(JNIEnv *env, jsize size))
{
aout_sys_t *p_sys = stream->sys;
- /* Try ENCODING_IEC61937 first, then fallback to ENCODING_[AC3|DTS|...] */
- unsigned nb_fmt = jfields.AudioFormat.has_ENCODING_IEC61937 ? 2 : 1;
- int i_ret;
- for( unsigned i = 0; i < nb_fmt; ++i )
+ if (size > p_sys->jbuffer.maxsize)
{
- int i_at_format;
- bool compat = i == 1;
- audio_sample_format_t fmt = p_sys->fmt;
+ p_sys->jbuffer.maxsize = 0;
+ if (p_sys->jbuffer.array != NULL)
+ (*env)->DeleteLocalRef(env, p_sys->jbuffer.array);
- i_ret = GetPassthroughFmt( compat, &fmt, &i_at_format );
- if( i_ret != VLC_SUCCESS )
- return i_ret;
+ p_sys->jbuffer.array = new(env, size);
+ if (p_sys->jbuffer.array != NULL)
+ p_sys->jbuffer.maxsize = size;
+ else
+ msg_Err(stream, "jarray allocation failed");
+ }
- p_sys->b_passthrough = true;
- i_ret = AudioTrack_Create( env, stream, fmt.i_rate, i_at_format,
- fmt.i_physical_channels );
+ if (p_sys->jbuffer.array == NULL)
+ return jfields.AudioTrack.ERROR;
- if( i_ret == VLC_SUCCESS )
- {
- msg_Dbg( stream, "Using passthrough format: %d", i_at_format );
- p_sys->i_chans_to_reorder = 0;
- p_sys->fmt = fmt;
- return VLC_SUCCESS;
- }
- }
+ p_sys->jbuffer.size = size;
+ p_sys->jbuffer.offset = 0;
- p_sys->b_passthrough = false;
- msg_Warn( stream, "SPDIF configuration failed" );
- return VLC_EGENERIC;
+ return 0;
}
+/**
+ * Non blocking write function, run from AudioTrack_Thread.
+ * Do a calculation between current position and audiotrack position and assure
+ * that we won't wait in AudioTrack.write() method.
+ */
static int
-StartPCM( JNIEnv *env, aout_stream_t *stream, unsigned i_max_channels )
+AudioTrack_WriteByteArray( JNIEnv *env, aout_stream_t *stream, bool b_force )
{
aout_sys_t *p_sys = stream->sys;
- unsigned i_nb_channels;
- int i_at_format, i_ret;
-
- if (jfields.AudioTrack.getNativeOutputSampleRate)
- p_sys->fmt.i_rate =
- JNI_AT_CALL_STATIC_INT( getNativeOutputSampleRate,
- jfields.AudioManager.STREAM_MUSIC );
- else
- p_sys->fmt.i_rate = VLC_CLIP( p_sys->fmt.i_rate, 4000, 48000 );
+ uint64_t i_samples;
+ uint64_t i_audiotrack_pos;
+ uint64_t i_samples_pending;
- do
+ int ret;
+ if (p_sys->jbuffer.offset == p_sys->jbuffer.size)
{
- /* We can only accept U8, S16N, FL32, and AC3 */
- switch( p_sys->fmt.i_format )
- {
- case VLC_CODEC_U8:
- i_at_format = jfields.AudioFormat.ENCODING_PCM_8BIT;
- break;
- case VLC_CODEC_S16N:
- i_at_format = jfields.AudioFormat.ENCODING_PCM_16BIT;
- break;
- case VLC_CODEC_FL32:
- if( jfields.AudioFormat.has_ENCODING_PCM_FLOAT )
- i_at_format = jfields.AudioFormat.ENCODING_PCM_FLOAT;
- else
- {
- p_sys->fmt.i_format = VLC_CODEC_S16N;
- i_at_format = jfields.AudioFormat.ENCODING_PCM_16BIT;
- }
- break;
- default:
- p_sys->fmt.i_format = VLC_CODEC_S16N;
- i_at_format = jfields.AudioFormat.ENCODING_PCM_16BIT;
- break;
- }
+ vlc_frame_t *f = p_sys->frame_chain;
+ assert(f != NULL);
+ ret = AudioTrack_AllocJArray(env, stream, f->i_buffer,
+ (*env)->NewByteArray);
+ if (ret != 0)
+ return ret;
- /* 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
- */
+ (*env)->SetByteArrayRegion(env, p_sys->jbuffer.array,
+ 0, f->i_buffer, (jbyte *)f->p_buffer);
+ AudioTrack_ConsumeFrame(stream, f);
+ }
- i_nb_channels = aout_FormatNbChannels( &p_sys->fmt );
- if( i_nb_channels == 0 )
- return VLC_EGENERIC;
- if( AOUT_FMT_LINEAR( &p_sys->fmt ) )
- i_nb_channels = __MIN( i_max_channels, i_nb_channels );
- 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
- p_sys->fmt.i_physical_channels = AOUT_CHANS_STEREO;
- }
+ i_audiotrack_pos = AudioTrack_getPlaybackHeadPosition( env, stream );
- /* Try to create an AudioTrack with the most advanced channel and
- * 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, stream, p_sys->fmt.i_rate, i_at_format,
- p_sys->fmt.i_physical_channels );
- if( i_ret != 0 )
- {
- if( p_sys->fmt.i_format == VLC_CODEC_FL32 )
- {
- msg_Warn( stream, "FL32 configuration failed, "
- "fallback to S16N PCM" );
- p_sys->fmt.i_format = VLC_CODEC_S16N;
- }
- else if( p_sys->fmt.i_physical_channels & AOUT_CHANS_5_1 )
- {
- msg_Warn( stream, "5.1 or 7.1 configuration failed, "
- "fallback to Stereo" );
- p_sys->fmt.i_physical_channels = AOUT_CHANS_STEREO;
- }
- else
- break;
- }
- } while( i_ret != 0 );
+ assert( i_audiotrack_pos <= p_sys->i_samples_written );
+ if( i_audiotrack_pos > p_sys->i_samples_written )
+ {
+ msg_Err( stream, "audiotrack position is ahead. Should NOT happen" );
+ p_sys->i_samples_written = 0;
+ p_sys->b_error = true;
+ return 0;
+ }
+ i_samples_pending = p_sys->i_samples_written - i_audiotrack_pos;
- if( i_ret != VLC_SUCCESS )
- return i_ret;
+ /* check if audiotrack buffer is not full before writing on it. */
+ if( b_force )
+ {
+ msg_Warn( stream, "Force write. It may block..." );
+ i_samples_pending = 0;
+ } else if( i_samples_pending >= p_sys->i_max_audiotrack_samples )
+ return 0;
- 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 );
- aout_FormatPrepare( &p_sys->fmt );
- return VLC_SUCCESS;
+ i_samples = __MIN( p_sys->i_max_audiotrack_samples - i_samples_pending,
+ BYTES_TO_FRAMES(p_sys->jbuffer.size - p_sys->jbuffer.offset));
+
+ ret = JNI_AT_CALL_INT( write, p_sys->jbuffer.array, p_sys->jbuffer.offset,
+ FRAMES_TO_BYTES( i_samples ) );
+ if (ret > 0)
+ p_sys->jbuffer.offset += ret;
+
+ return ret;
}
+/**
+ * Non blocking write function for Android M and after, run from
+ * AudioTrack_Thread. It calls a new write method with WRITE_NON_BLOCKING
+ * flags.
+ */
static int
-Start( aout_stream_t *stream, audio_sample_format_t *restrict p_fmt,
- enum android_audio_device_type adev )
+AudioTrack_WriteByteArrayV23(JNIEnv *env, aout_stream_t *stream)
{
- JNIEnv *env;
- int i_ret;
- bool b_try_passthrough;
- unsigned i_max_channels;
+ aout_sys_t *p_sys = stream->sys;
- if( adev == ANDROID_AUDIO_DEVICE_ENCODED )
+ int ret;
+ if (p_sys->jbuffer.offset == p_sys->jbuffer.size)
{
- b_try_passthrough = true;
- i_max_channels = ANDROID_AUDIO_DEVICE_MAX_CHANNELS;
+ vlc_frame_t *f = p_sys->frame_chain;
+ assert(f != NULL);
+ ret = AudioTrack_AllocJArray(env, stream, f->i_buffer,
+ (*env)->NewByteArray);
+ if (ret != 0)
+ return ret;
+
+ (*env)->SetByteArrayRegion(env, p_sys->jbuffer.array,
+ 0, f->i_buffer, (jbyte *)f->p_buffer);
+ AudioTrack_ConsumeFrame(stream, f);
}
- else
+
+ ret = JNI_AT_CALL_INT( writeV23, p_sys->jbuffer.array,
+ p_sys->jbuffer.offset,
+ p_sys->jbuffer.size - p_sys->jbuffer.offset,
+ jfields.AudioTrack.WRITE_NON_BLOCKING );
+ if (ret > 0)
+ p_sys->jbuffer.offset += ret;
+
+ return ret;
+}
+
+/**
+ * Non blocking play function for Lollipop and after, run from
+ * AudioTrack_Thread. It calls a new write method with WRITE_NON_BLOCKING
+ * flags.
+ */
+static int
+AudioTrack_WriteByteBuffer(JNIEnv *env, aout_stream_t *stream)
+{
+ aout_sys_t *p_sys = stream->sys;
+
+ if (p_sys->jbuffer.offset == p_sys->jbuffer.size)
{
- b_try_passthrough = false;
- i_max_channels = adev == ANDROID_AUDIO_DEVICE_STEREO ? 2 : ANDROID_AUDIO_DEVICE_MAX_CHANNELS;
+ vlc_frame_t *f = p_sys->frame_chain;
+ assert(f != NULL);
+ if (p_sys->jbuffer.array != NULL)
+ (*env)->DeleteLocalRef(env, p_sys->jbuffer.array);
+ p_sys->jbuffer.array = (*env)->NewDirectByteBuffer(env,
+ f->p_buffer,
+ f->i_buffer);
+ if (p_sys->jbuffer.array == NULL)
+ {
+ if ((*env)->ExceptionCheck(env))
+ (*env)->ExceptionClear(env);
+ return jfields.AudioTrack.ERROR;
+ }
+ p_sys->jbuffer.offset = 0;
+ p_sys->jbuffer.size = f->i_buffer;
+ /* Don't take account of the current frame for the delay */
+ f->i_buffer = 0;
}
- if( !( env = GET_ENV() ) )
- return VLC_EGENERIC;
+ int ret = JNI_AT_CALL_INT(writeBufferV21, p_sys->jbuffer.array,
+ p_sys->jbuffer.size - p_sys->jbuffer.offset,
+ jfields.AudioTrack.WRITE_NON_BLOCKING);
+ if (ret > 0)
+ {
+ p_sys->jbuffer.offset += ret;
+ /* The ByteBuffer reference directly the data from the frame, so it
+ * should stay allocated until all the data is written */
+ if (p_sys->jbuffer.offset == p_sys->jbuffer.size)
+ AudioTrack_ConsumeFrame(stream, p_sys->frame_chain);
+ }
- AudioTrack_InitJNI(VLC_OBJECT(stream));
+ return ret;
+}
- aout_sys_t *p_sys = stream->sys = calloc( 1, sizeof (aout_sys_t) );
+/**
+ * Non blocking short write function for Android M and after, run from
+ * AudioTrack_Thread. It calls a new write method with WRITE_NON_BLOCKING
+ * flags.
+ */
+static int
+AudioTrack_WriteShortArrayV23(JNIEnv *env, aout_stream_t *stream)
+{
+ aout_sys_t *p_sys = stream->sys;
- if( unlikely( p_sys == NULL ) )
- return VLC_ENOMEM;
+ int ret;
+ if (p_sys->jbuffer.offset == p_sys->jbuffer.size)
+ {
+ vlc_frame_t *f = p_sys->frame_chain;
+ assert(f != NULL);
+ ret = AudioTrack_AllocJArray(env, stream, f->i_buffer / 2,
+ (*env)->NewShortArray);
+ if (ret != 0)
+ return ret;
- vlc_mutex_init(&p_sys->lock);
- vlc_cond_init(&p_sys->aout_cond);
- vlc_cond_init(&p_sys->thread_cond);
+ (*env)->SetShortArrayRegion(env, p_sys->jbuffer.array,
+ 0, f->i_buffer / 2, (jshort *)f->p_buffer);
+ AudioTrack_ConsumeFrame(stream, f);
+ }
- p_sys->volume = 1.0f;
- p_sys->mute = false;
+ ret = JNI_AT_CALL_INT( writeShortV23, p_sys->jbuffer.array,
+ p_sys->jbuffer.offset,
+ p_sys->jbuffer.size - p_sys->jbuffer.offset,
+ jfields.AudioTrack.WRITE_NON_BLOCKING );
+ if (ret > 0)
+ {
+ p_sys->jbuffer.offset += ret;
+ ret *= 2;
+ }
- p_sys->fmt = *p_fmt;
+ return ret;
+}
- aout_FormatPrint( stream, "VLC is looking for:", &p_sys->fmt );
+/**
+ * Non blocking play float function for Lollipop and after, run from
+ * AudioTrack_Thread. It calls a new write method with WRITE_NON_BLOCKING
+ * flags.
+ */
+static int
+AudioTrack_WriteFloatArray(JNIEnv *env, aout_stream_t *stream)
+{
+ aout_sys_t *p_sys = stream->sys;
+ int ret;
- if (p_sys->fmt.channel_type == AUDIO_CHANNEL_TYPE_AMBISONICS)
+ if (p_sys->jbuffer.offset == p_sys->jbuffer.size)
{
- p_sys->fmt.channel_type = AUDIO_CHANNEL_TYPE_BITMAP;
+ vlc_frame_t *f = p_sys->frame_chain;
+ assert(f != NULL);
+ ret = AudioTrack_AllocJArray(env, stream, f->i_buffer / 4,
+ (*env)->NewFloatArray);
+ if (ret != 0)
+ return ret;
- /* TODO: detect sink channel layout */
- p_sys->fmt.i_physical_channels = AOUT_CHANS_STEREO;
- aout_FormatPrepare(&p_sys->fmt);
+ (*env)->SetFloatArrayRegion(env, p_sys->jbuffer.array,
+ 0, f->i_buffer / 4, (jfloat *)f->p_buffer);
+ AudioTrack_ConsumeFrame(stream, f);
}
- if( AOUT_FMT_LINEAR( &p_sys->fmt ) )
- i_ret = StartPCM( env, stream, i_max_channels );
- else if( b_try_passthrough )
- i_ret = StartPassthrough( env, stream );
- else
- i_ret = VLC_EGENERIC;
+ ret = JNI_AT_CALL_INT(writeFloat, p_sys->jbuffer.array,
+ p_sys->jbuffer.offset,
+ p_sys->jbuffer.size - p_sys->jbuffer.offset,
+ jfields.AudioTrack.WRITE_NON_BLOCKING);
- if( i_ret != 0 )
+ if (ret > 0)
{
- free( p_sys );
- return VLC_EGENERIC;
+ p_sys->jbuffer.offset += ret;
+ ret *= 4;
}
- if( jfields.AudioTrack.getBufferSizeInFrames )
- p_sys->i_max_audiotrack_samples = JNI_AT_CALL_INT( getBufferSizeInFrames );
- else
- p_sys->i_max_audiotrack_samples = BYTES_TO_FRAMES( p_sys->audiotrack_args.i_size );
+ return ret;
+}
-#ifdef AUDIOTRACK_HW_LATENCY
- if( jfields.AudioTimestamp.clazz )
+static int
+AudioTrack_Write( JNIEnv *env, aout_stream_t *stream, bool b_force )
+{
+ aout_sys_t *p_sys = stream->sys;
+ int i_ret;
+
+ switch( p_sys->i_write_type )
{
- /* create AudioTimestamp object */
- jobject p_obj = JNI_CALL( NewObject, jfields.AudioTimestamp.clazz,
- jfields.AudioTimestamp.ctor );
- if( p_obj )
+ case WRITE_BYTEARRAYV23:
+ i_ret = AudioTrack_WriteByteArrayV23(env, stream);
+ break;
+ case WRITE_BYTEBUFFER:
+ i_ret = AudioTrack_WriteByteBuffer(env, stream);
+ break;
+ case WRITE_SHORTARRAYV23:
+ i_ret = AudioTrack_WriteShortArrayV23(env, stream);
+ break;
+ case WRITE_BYTEARRAY:
+ i_ret = AudioTrack_WriteByteArray(env, stream, b_force);
+ break;
+ case WRITE_FLOATARRAY:
+ i_ret = AudioTrack_WriteFloatArray(env, stream);
+ break;
+ default:
+ vlc_assert_unreachable();
+ }
+
+ if( i_ret < 0 ) {
+ if( jfields.AudioManager.has_ERROR_DEAD_OBJECT
+ && i_ret == jfields.AudioManager.ERROR_DEAD_OBJECT )
{
- p_sys->timestamp.p_obj = (*env)->NewGlobalRef( env, p_obj );
- (*env)->DeleteLocalRef( env, p_obj );
+ msg_Warn( stream, "ERROR_DEAD_OBJECT: "
+ "try recreating AudioTrack" );
+ if( ( i_ret = AudioTrack_Recreate( env, stream ) ) == 0 )
+ {
+ AudioTrack_Reset( env, stream );
+ JNI_AT_CALL_VOID( play );
+ CHECK_AT_EXCEPTION( "play" );
+ }
+ } else
+ {
+ const char *str;
+ if( i_ret == jfields.AudioTrack.ERROR_INVALID_OPERATION )
+ str = "ERROR_INVALID_OPERATION";
+ else if( i_ret == jfields.AudioTrack.ERROR_BAD_VALUE )
+ str = "ERROR_BAD_VALUE";
+ else
+ str = "ERROR";
+ msg_Err( stream, "Write failed: %s", str );
+ p_sys->b_error = true;
}
- if( !p_sys->timestamp.p_obj )
- goto error;
- }
-#endif
+ } else
+ p_sys->i_samples_written += BYTES_TO_FRAMES( i_ret );
+ return i_ret;
+}
- p_sys->jbuffer.array = NULL;
- p_sys->jbuffer.size = p_sys->jbuffer.offset = 0;
- p_sys->jbuffer.maxsize = 0;
- p_sys->frame_chain = NULL;
- p_sys->frame_last = &p_sys->frame_chain;
- AudioTrack_Reset( env, stream );
+static int
+AudioTrack_ReportTiming( JNIEnv *env, aout_stream_t *stream )
+{
+ aout_sys_t *p_sys = stream->sys;
- if( p_sys->fmt.i_format == VLC_CODEC_FL32 )
- {
- msg_Dbg( stream, "using WRITE_FLOATARRAY");
- p_sys->i_write_type = WRITE_FLOATARRAY;
- }
- else if( p_sys->fmt.i_format == VLC_CODEC_SPDIFL )
- {
- assert( jfields.AudioFormat.has_ENCODING_IEC61937 );
- msg_Dbg( stream, "using WRITE_SHORTARRAYV23");
- p_sys->i_write_type = WRITE_SHORTARRAYV23;
- }
- else if( jfields.AudioTrack.writeV23 )
+ if( p_sys->timestamp.p_obj != NULL
+ && JNI_AT_CALL_BOOL( getTimestamp, p_sys->timestamp.p_obj ) )
{
- msg_Dbg( stream, "using WRITE_BYTEARRAYV23");
- p_sys->i_write_type = WRITE_BYTEARRAYV23;
- }
- else if( jfields.AudioTrack.writeBufferV21 )
- {
- msg_Dbg( stream, "using WRITE_BYTEBUFFER");
- p_sys->i_write_type = WRITE_BYTEBUFFER;
- }
- else
- {
- msg_Dbg( stream, "using WRITE_BYTEARRAY");
- p_sys->i_write_type = WRITE_BYTEARRAY;
+ vlc_tick_t frame_date_us =
+ VLC_TICK_FROM_NS(JNI_AUDIOTIMESTAMP_GET_LONG( nanoTime ));
+ jlong frame_post_last = JNI_AUDIOTIMESTAMP_GET_LONG( framePosition );
+
+ /* the low-order 32 bits of position is in wrapping frame units
+ * similar to AudioTrack#getPlaybackHeadPosition. */
+ if( p_sys->timestamp.i_frame_post_last > frame_post_last )
+ p_sys->timestamp.i_frame_wrap_count++;
+ p_sys->timestamp.i_frame_post_last = frame_post_last;
+ uint64_t frame_pos = frame_post_last
+ + (p_sys->timestamp.i_frame_wrap_count << 32);
+
+ aout_stream_TimingReport( stream, frame_date_us,
+ FRAMES_TO_US( frame_pos ) );
+ return VLC_SUCCESS;
}
- /* Run AudioTrack_Thread */
- p_sys->b_thread_running = true;
- p_sys->b_thread_paused = false;
- if ( vlc_clone( &p_sys->thread, AudioTrack_Thread, stream ) )
+ vlc_tick_t now = vlc_tick_now();
+ vlc_tick_t frame_us = FRAMES_TO_US( AudioTrack_getPlaybackHeadPosition( env, stream ) );
+ if( frame_us == 0 )
+ return VLC_EGENERIC;
+
+ vlc_tick_t latency_us = AudioTrack_GetLatencyUs( env, stream );
+ frame_us -= latency_us;
+
+ if( frame_us <= 0 )
+ return VLC_EGENERIC;
+ aout_stream_TimingReport( stream, now, frame_us );
+
+ return VLC_SUCCESS;
+}
+
+/**
+ * This thread will play the data coming from the jbuffer buffer.
+ */
+static void *
+AudioTrack_Thread( void *p_data )
+{
+ aout_stream_t *stream = p_data;
+ aout_sys_t *p_sys = stream->sys;
+ JNIEnv *env = GET_ENV();
+ vlc_tick_t i_last_time_blocked = 0;
+
+ vlc_thread_set_name("vlc-audiotrack");
+
+ if( !env )
+ return NULL;
+
+ for( ;; )
{
- msg_Err(stream, "vlc clone failed");
- goto error;
- }
+ int i_ret = 0;
+ bool b_forced;
- JNI_AT_CALL_VOID( play );
- CHECK_AT_EXCEPTION( "play" );
+ vlc_mutex_lock( &p_sys->lock );
- *p_fmt = p_sys->fmt;
+ /* Wait for not paused state */
+ while( p_sys->b_thread_running && p_sys->b_thread_paused )
+ {
+ i_last_time_blocked = 0;
+ vlc_cond_wait( &p_sys->thread_cond, &p_sys->lock );
+ }
- aout_FormatPrint( stream, "VLC will output:", &p_sys->fmt );
+ /* Wait for more data in the jbuffer buffer */
+ while (p_sys->b_thread_running
+ && p_sys->jbuffer.offset == p_sys->jbuffer.size
+ && p_sys->frame_chain == NULL)
+ vlc_cond_wait( &p_sys->thread_cond, &p_sys->lock );
- stream->stop = Stop;
- stream->play = Play;
- stream->pause = Pause;
- stream->flush = Flush;
- stream->time_get = TimeGet;
- stream->volume_set = VolumeSet;
- stream->mute_set = MuteSet;
+ if( !p_sys->b_thread_running || p_sys->b_error )
+ {
+ vlc_mutex_unlock( &p_sys->lock );
+ break;
+ }
- msg_Dbg(stream, "using AudioTrack API");
- return VLC_SUCCESS;
+ /* HACK: AudioFlinger can drop frames without notifying us and there is
+ * no way to know it. If it happens, i_audiotrack_pos won't move and
+ * the current code will be stuck because it'll assume that audiotrack
+ * internal buffer is full when it's not. It may happen only after
+ * Android 4.4.2 if we send frames too quickly. To fix this issue,
+ * force the writing of the buffer after a certain delay. */
+ if( i_last_time_blocked != 0 )
+ b_forced = vlc_tick_now() - i_last_time_blocked >
+ FRAMES_TO_US( p_sys->i_max_audiotrack_samples ) * 2;
+ else
+ b_forced = false;
+
+ i_ret = AudioTrack_Write( env, stream, b_forced );
+ if( i_ret >= 0 )
+ {
+ if( i_ret == 0 )
+ {
+ vlc_tick_t i_now = vlc_tick_now();
+
+ /* cf. b_forced HACK comment */
+ if( p_sys->i_write_type == WRITE_BYTEARRAY && i_last_time_blocked == 0 )
+ i_last_time_blocked = i_now;
+
+ /* Wait for free space in Audiotrack internal buffer */
+ vlc_tick_t i_play_deadline = i_now + __MAX( 10000,
+ FRAMES_TO_US( p_sys->i_max_audiotrack_samples / 5 ) );
-error:
- Stop( stream );
- return VLC_EGENERIC;
-}
+ while( p_sys->b_thread_running && i_ret == 0 )
+ i_ret = vlc_cond_timedwait( &p_sys->thread_cond, &p_sys->lock,
+ i_play_deadline );
-static void
-Stop( aout_stream_t *stream )
-{
- aout_sys_t *p_sys = stream->sys;
- JNIEnv *env;
+ }
+ else
+ {
+ i_last_time_blocked = 0;
- if( !( env = GET_ENV() ) )
- return;
+ if( !p_sys->b_passthrough )
+ {
+ p_sys->timing_report_last_written_bytes += i_ret;
+
+ if( p_sys->timing_report_last_written_bytes >=
+ p_sys->timing_report_delay_bytes
+ && AudioTrack_ReportTiming( env, stream ) == VLC_SUCCESS )
+ {
+ p_sys->timing_report_last_written_bytes = 0;
+ /* From now on, fetch the timestamp every 1 seconds */
+ p_sys->timing_report_delay_bytes =
+ US_TO_BYTES( TIMING_REPORT_DELAY_TICKS );
+ }
+ }
+ }
+ }
- /* Stop the AudioTrack thread */
- vlc_mutex_lock( &p_sys->lock );
- if( p_sys->b_thread_running )
- {
- p_sys->b_thread_running = false;
- vlc_cond_signal( &p_sys->thread_cond );
vlc_mutex_unlock( &p_sys->lock );
- vlc_join( p_sys->thread, NULL );
}
- else
- vlc_mutex_unlock( &p_sys->lock );
- /* Release the AudioTrack object */
- if( p_sys->p_audiotrack )
+ if (p_sys->jbuffer.array != NULL)
{
- 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 );
+ (*env)->DeleteLocalRef(env, p_sys->jbuffer.array);
+ p_sys->jbuffer.array = NULL;
}
- if( p_sys->p_dp != NULL )
- DynamicsProcessing_Delete( stream, p_sys->p_dp );
-
- /* Release the timestamp object */
- if( p_sys->timestamp.p_obj )
- (*env)->DeleteGlobalRef( env, p_sys->timestamp.p_obj );
-
- free( p_sys );
+ return NULL;
}
-static void
-AudioTrack_ConsumeFrame(aout_stream_t *stream, vlc_frame_t *f)
+static int
+ConvertFromIEC61937( aout_stream_t *stream, block_t *p_buffer )
{
- aout_sys_t *p_sys = stream->sys;
- assert(f != NULL && f == p_sys->frame_chain);
-
- p_sys->frame_chain = f->p_next;
- if (p_sys->frame_chain == NULL)
- p_sys->frame_last = &p_sys->frame_chain;
+ /* This function is only used for Android API 23 when AudioTrack is
+ * configured with ENCODING_ AC3/E_AC3/DTS. In that case, only the codec
+ * data is needed (without the IEC61937 encapsulation). This function
+ * recovers the codec data from an EC61937 frame. It is the opposite of the
+ * code found in converter/tospdif.c. We could also request VLC core to
+ * send us the codec data directly, but in that case, we wouldn't benefit
+ * from the eac3 block merger of tospdif.c. */
- vlc_frame_Release(f);
-}
+ VLC_UNUSED( stream );
+ uint8_t i_length_mul;
-static int
-AudioTrack_AllocJArray(JNIEnv *env, aout_stream_t *stream, size_t size,
- jarray (*new)(JNIEnv *env, jsize size))
-{
- aout_sys_t *p_sys = stream->sys;
+ if( p_buffer->i_buffer < 6 )
+ return -1;
- if (size > p_sys->jbuffer.maxsize)
+ switch( GetWBE( &p_buffer->p_buffer[4] ) & 0xFF )
{
- p_sys->jbuffer.maxsize = 0;
- if (p_sys->jbuffer.array != NULL)
- (*env)->DeleteLocalRef(env, p_sys->jbuffer.array);
-
- p_sys->jbuffer.array = new(env, size);
- if (p_sys->jbuffer.array != NULL)
- p_sys->jbuffer.maxsize = size;
- else
- msg_Err(stream, "jarray allocation failed");
+ case 0x01: /* IEC61937_AC3 */
+ i_length_mul = 8;
+ break;
+ case 0x15: /* IEC61937_EAC3 */
+ i_length_mul = 1;
+ break;
+ case 0x0B: /* IEC61937_DTS1 */
+ case 0x0C: /* IEC61937_DTS2 */
+ case 0x0D: /* IEC61937_DTS3 */
+ i_length_mul = 8;
+ break;
+ case 0x11: /* IEC61937_DTSHD */
+ i_length_mul = 1;
+ break;
+ default:
+ vlc_assert_unreachable();
}
+ uint16_t i_length = GetWBE( &p_buffer->p_buffer[6] );
+ if( i_length == 0 )
+ return -1;
- if (p_sys->jbuffer.array == NULL)
- return jfields.AudioTrack.ERROR;
+ i_length /= i_length_mul;
+ if( i_length > p_buffer->i_buffer - 8 )
+ return -1;
- p_sys->jbuffer.size = size;
- p_sys->jbuffer.offset = 0;
+ p_buffer->p_buffer += 8; /* SPDIF_HEADER_SIZE */
+ p_buffer->i_buffer = i_length;
return 0;
}
-/**
- * Non blocking write function, run from AudioTrack_Thread.
- * Do a calculation between current position and audiotrack position and assure
- * that we won't wait in AudioTrack.write() method.
- */
-static int
-AudioTrack_WriteByteArray( JNIEnv *env, aout_stream_t *stream, bool b_force )
+static void
+Play( aout_stream_t *stream, block_t *p_buffer, vlc_tick_t i_date )
{
+ JNIEnv *env = NULL;
aout_sys_t *p_sys = stream->sys;
- uint64_t i_samples;
- uint64_t i_audiotrack_pos;
- uint64_t i_samples_pending;
- int ret;
- if (p_sys->jbuffer.offset == p_sys->jbuffer.size)
+ if( p_sys->b_passthrough && p_sys->fmt.i_format == VLC_CODEC_SPDIFB
+ && ConvertFromIEC61937( stream, p_buffer ) != 0 )
{
- vlc_frame_t *f = p_sys->frame_chain;
- assert(f != NULL);
- ret = AudioTrack_AllocJArray(env, stream, f->i_buffer,
- (*env)->NewByteArray);
- if (ret != 0)
- return ret;
-
- (*env)->SetByteArrayRegion(env, p_sys->jbuffer.array,
- 0, f->i_buffer, (jbyte *)f->p_buffer);
- AudioTrack_ConsumeFrame(stream, f);
+ block_Release(p_buffer);
+ return;
}
- i_audiotrack_pos = AudioTrack_getPlaybackHeadPosition( env, stream );
+ vlc_mutex_lock( &p_sys->lock );
- assert( i_audiotrack_pos <= p_sys->i_samples_written );
- if( i_audiotrack_pos > p_sys->i_samples_written )
+ if( p_sys->b_error || !( env = GET_ENV() ) )
{
- msg_Err( stream, "audiotrack position is ahead. Should NOT happen" );
- p_sys->i_samples_written = 0;
- p_sys->b_error = true;
- return 0;
+ block_Release( p_buffer );
+ goto bailout;
}
- i_samples_pending = p_sys->i_samples_written - i_audiotrack_pos;
- /* check if audiotrack buffer is not full before writing on it. */
- if( b_force )
- {
- msg_Warn( stream, "Force write. It may block..." );
- i_samples_pending = 0;
- } else if( i_samples_pending >= p_sys->i_max_audiotrack_samples )
- return 0;
-
- i_samples = __MIN( p_sys->i_max_audiotrack_samples - i_samples_pending,
- BYTES_TO_FRAMES(p_sys->jbuffer.size - p_sys->jbuffer.offset));
+ 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 );
- ret = JNI_AT_CALL_INT( write, p_sys->jbuffer.array, p_sys->jbuffer.offset,
- FRAMES_TO_BYTES( i_samples ) );
- if (ret > 0)
- p_sys->jbuffer.offset += ret;
+ vlc_frame_ChainLastAppend(&p_sys->frame_last, p_buffer);
+ vlc_cond_signal(&p_sys->thread_cond);
- return ret;
+bailout:
+ vlc_mutex_unlock( &p_sys->lock );
+ (void) i_date;
}
-/**
- * Non blocking write function for Android M and after, run from
- * AudioTrack_Thread. It calls a new write method with WRITE_NON_BLOCKING
- * flags.
- */
-static int
-AudioTrack_WriteByteArrayV23(JNIEnv *env, aout_stream_t *stream)
+static void
+Pause( aout_stream_t *stream, bool b_pause, vlc_tick_t i_date )
{
aout_sys_t *p_sys = stream->sys;
+ JNIEnv *env;
+ VLC_UNUSED( i_date );
- int ret;
- if (p_sys->jbuffer.offset == p_sys->jbuffer.size)
- {
- vlc_frame_t *f = p_sys->frame_chain;
- assert(f != NULL);
- ret = AudioTrack_AllocJArray(env, stream, f->i_buffer,
- (*env)->NewByteArray);
- if (ret != 0)
- return ret;
-
- (*env)->SetByteArrayRegion(env, p_sys->jbuffer.array,
- 0, f->i_buffer, (jbyte *)f->p_buffer);
- AudioTrack_ConsumeFrame(stream, f);
- }
-
- ret = JNI_AT_CALL_INT( writeV23, p_sys->jbuffer.array,
- p_sys->jbuffer.offset,
- p_sys->jbuffer.size - p_sys->jbuffer.offset,
- jfields.AudioTrack.WRITE_NON_BLOCKING );
- if (ret > 0)
- p_sys->jbuffer.offset += ret;
-
- return ret;
-}
-
-/**
- * Non blocking play function for Lollipop and after, run from
- * AudioTrack_Thread. It calls a new write method with WRITE_NON_BLOCKING
- * flags.
- */
-static int
-AudioTrack_WriteByteBuffer(JNIEnv *env, aout_stream_t *stream)
-{
- aout_sys_t *p_sys = stream->sys;
+ vlc_mutex_lock( &p_sys->lock );
- if (p_sys->jbuffer.offset == p_sys->jbuffer.size)
- {
- vlc_frame_t *f = p_sys->frame_chain;
- assert(f != NULL);
- if (p_sys->jbuffer.array != NULL)
- (*env)->DeleteLocalRef(env, p_sys->jbuffer.array);
- p_sys->jbuffer.array = (*env)->NewDirectByteBuffer(env,
- f->p_buffer,
- f->i_buffer);
- if (p_sys->jbuffer.array == NULL)
- {
- if ((*env)->ExceptionCheck(env))
- (*env)->ExceptionClear(env);
- return jfields.AudioTrack.ERROR;
- }
- p_sys->jbuffer.offset = 0;
- p_sys->jbuffer.size = f->i_buffer;
- /* Don't take account of the current frame for the delay */
- f->i_buffer = 0;
- }
+ if( p_sys->b_error || !( env = GET_ENV() ) )
+ goto bailout;
- int ret = JNI_AT_CALL_INT(writeBufferV21, p_sys->jbuffer.array,
- p_sys->jbuffer.size - p_sys->jbuffer.offset,
- jfields.AudioTrack.WRITE_NON_BLOCKING);
- if (ret > 0)
+ if( b_pause )
{
- p_sys->jbuffer.offset += ret;
- /* The ByteBuffer reference directly the data from the frame, so it
- * should stay allocated until all the data is written */
- if (p_sys->jbuffer.offset == p_sys->jbuffer.size)
- AudioTrack_ConsumeFrame(stream, p_sys->frame_chain);
+ p_sys->b_thread_paused = true;
+ JNI_AT_CALL_VOID( pause );
+ CHECK_AT_EXCEPTION( "pause" );
+ } else
+ {
+ p_sys->b_thread_paused = false;
+ JNI_AT_CALL_VOID( play );
+ CHECK_AT_EXCEPTION( "play" );
}
- return ret;
+bailout:
+ vlc_mutex_unlock( &p_sys->lock );
}
-/**
- * Non blocking short write function for Android M and after, run from
- * AudioTrack_Thread. It calls a new write method with WRITE_NON_BLOCKING
- * flags.
- */
-static int
-AudioTrack_WriteShortArrayV23(JNIEnv *env, aout_stream_t *stream)
+static void
+Flush( aout_stream_t *stream )
{
aout_sys_t *p_sys = stream->sys;
+ JNIEnv *env;
- int ret;
- if (p_sys->jbuffer.offset == p_sys->jbuffer.size)
- {
- vlc_frame_t *f = p_sys->frame_chain;
- assert(f != NULL);
- ret = AudioTrack_AllocJArray(env, stream, f->i_buffer / 2,
- (*env)->NewShortArray);
- if (ret != 0)
- return ret;
+ vlc_mutex_lock( &p_sys->lock );
- (*env)->SetShortArrayRegion(env, p_sys->jbuffer.array,
- 0, f->i_buffer / 2, (jshort *)f->p_buffer);
- AudioTrack_ConsumeFrame(stream, f);
- }
+ p_sys->jbuffer.size = p_sys->jbuffer.offset = 0;
+ vlc_frame_ChainRelease(p_sys->frame_chain);
+ p_sys->frame_chain = NULL;
+ p_sys->frame_last = &p_sys->frame_chain;
- ret = JNI_AT_CALL_INT( writeShortV23, p_sys->jbuffer.array,
- p_sys->jbuffer.offset,
- p_sys->jbuffer.size - p_sys->jbuffer.offset,
- jfields.AudioTrack.WRITE_NON_BLOCKING );
- if (ret > 0)
+ if( p_sys->b_error || !( env = GET_ENV() ) )
+ goto bailout;
+
+ /* Android doc:
+ * stop(): Stops playing the audio data. When used on an instance created
+ * in MODE_STREAM mode, audio will stop playing after the last buffer that
+ * was written has been played. For an immediate stop, use pause(),
+ * followed by flush() to discard audio data that hasn't been played back
+ * yet.
+ *
+ * flush(): Flushes the audio data currently queued for playback. Any data
+ * that has not been played back will be discarded. No-op if not stopped
+ * or paused, or if the track's creation mode is not MODE_STREAM.
+ */
+ JNI_AT_CALL_VOID( pause );
+ if( CHECK_AT_EXCEPTION( "pause" ) )
+ goto bailout;
+ JNI_AT_CALL_VOID( flush );
+
+ /* HACK: Before Android 4.4, the head position is not reset to zero and is
+ * still moving after a flush or a stop. This prevents to get a precise
+ * head position and there is no way to know when it stabilizes. Therefore
+ * recreate an AudioTrack object in that case. The AudioTimestamp class was
+ * added in API Level 19, so if this class is not found, the Android
+ * Version is 4.3 or before */
+ if( !jfields.AudioTimestamp.clazz && p_sys->i_samples_written > 0 )
{
- p_sys->jbuffer.offset += ret;
- ret *= 2;
+ if( AudioTrack_Recreate( env, stream ) != 0 )
+ {
+ p_sys->b_error = true;
+ goto bailout;
+ }
}
+ AudioTrack_Reset( env, stream );
+ JNI_AT_CALL_VOID( play );
+ CHECK_AT_EXCEPTION( "play" );
- return ret;
+bailout:
+ vlc_mutex_unlock( &p_sys->lock );
}
-/**
- * Non blocking play float function for Lollipop and after, run from
- * AudioTrack_Thread. It calls a new write method with WRITE_NON_BLOCKING
- * flags.
- */
-static int
-AudioTrack_WriteFloatArray(JNIEnv *env, aout_stream_t *stream)
+static void
+AudioTrack_SetVolume( JNIEnv *env, aout_stream_t *stream, float volume )
{
aout_sys_t *p_sys = stream->sys;
- int ret;
- if (p_sys->jbuffer.offset == p_sys->jbuffer.size)
+ if( jfields.AudioTrack.setVolume )
{
- vlc_frame_t *f = p_sys->frame_chain;
- assert(f != NULL);
- ret = AudioTrack_AllocJArray(env, stream, f->i_buffer / 4,
- (*env)->NewFloatArray);
- if (ret != 0)
- return ret;
-
- (*env)->SetFloatArrayRegion(env, p_sys->jbuffer.array,
- 0, f->i_buffer / 4, (jfloat *)f->p_buffer);
- AudioTrack_ConsumeFrame(stream, f);
- }
-
- ret = JNI_AT_CALL_INT(writeFloat, p_sys->jbuffer.array,
- p_sys->jbuffer.offset,
- p_sys->jbuffer.size - p_sys->jbuffer.offset,
- jfields.AudioTrack.WRITE_NON_BLOCKING);
-
- if (ret > 0)
+ JNI_AT_CALL_INT( setVolume, volume );
+ CHECK_AT_EXCEPTION( "setVolume" );
+ } else
{
- p_sys->jbuffer.offset += ret;
- ret *= 4;
+ JNI_AT_CALL_INT( setStereoVolume, volume, volume );
+ CHECK_AT_EXCEPTION( "setStereoVolume" );
}
-
- return ret;
}
-static int
-AudioTrack_Write( JNIEnv *env, aout_stream_t *stream, bool b_force )
+static void
+VolumeSet( aout_stream_t *stream, float volume )
{
aout_sys_t *p_sys = stream->sys;
- int i_ret;
+ JNIEnv *env;
+ float gain = 1.0f;
- switch( p_sys->i_write_type )
+ p_sys->volume = volume;
+ if (volume > 1.f)
{
- case WRITE_BYTEARRAYV23:
- i_ret = AudioTrack_WriteByteArrayV23(env, stream);
- break;
- case WRITE_BYTEBUFFER:
- i_ret = AudioTrack_WriteByteBuffer(env, stream);
- break;
- case WRITE_SHORTARRAYV23:
- i_ret = AudioTrack_WriteShortArrayV23(env, stream);
- break;
- case WRITE_BYTEARRAY:
- i_ret = AudioTrack_WriteByteArray(env, stream, b_force);
- break;
- case WRITE_FLOATARRAY:
- i_ret = AudioTrack_WriteFloatArray(env, stream);
- break;
- default:
- vlc_assert_unreachable();
+ gain = volume * volume * volume;
+ volume = 1.f;
}
- if( i_ret < 0 ) {
- if( jfields.AudioManager.has_ERROR_DEAD_OBJECT
- && i_ret == jfields.AudioManager.ERROR_DEAD_OBJECT )
+ if( !p_sys->b_error && p_sys->p_audiotrack != NULL && ( env = GET_ENV() ) )
+ {
+ AudioTrack_SetVolume( env, stream, volume );
+
+ /* Apply gain > 1.0f via DynamicsProcessing if possible */
+ if( p_sys->p_dp != NULL )
{
- msg_Warn( stream, "ERROR_DEAD_OBJECT: "
- "try recreating AudioTrack" );
- if( ( i_ret = AudioTrack_Recreate( env, stream ) ) == 0 )
+ if( gain <= 1.0f )
{
- AudioTrack_Reset( env, stream );
- JNI_AT_CALL_VOID( play );
- CHECK_AT_EXCEPTION( "play" );
+ /* DynamicsProcessing is not needed anymore (using AudioTrack
+ * volume) */
+ DynamicsProcessing_Disable( stream, p_sys->p_dp );
}
- } else
- {
- const char *str;
- if( i_ret == jfields.AudioTrack.ERROR_INVALID_OPERATION )
- str = "ERROR_INVALID_OPERATION";
- else if( i_ret == jfields.AudioTrack.ERROR_BAD_VALUE )
- str = "ERROR_BAD_VALUE";
else
- str = "ERROR";
- msg_Err( stream, "Write failed: %s", str );
- p_sys->b_error = true;
+ {
+ int ret = DynamicsProcessing_SetVolume( stream, p_sys->p_dp, gain );
+
+ if( ret == VLC_SUCCESS )
+ gain = 1.0; /* reset sw gain */
+ else
+ msg_Warn( stream, "failed to set gain via DynamicsProcessing, fallback to sw gain");
+ }
}
- } else
- p_sys->i_samples_written += BYTES_TO_FRAMES( i_ret );
- return i_ret;
+ }
+
+ aout_stream_GainRequest(stream, gain);
+}
+
+static void
+MuteSet( aout_stream_t *stream, bool mute )
+{
+ aout_sys_t *p_sys = stream->sys;
+ JNIEnv *env;
+ p_sys->mute = mute;
+
+ if( !p_sys->b_error && p_sys->p_audiotrack != NULL && ( env = GET_ENV() ) )
+ AudioTrack_SetVolume( env, stream, mute ? 0.0f : p_sys->volume );
+}
+
+static int GetPassthroughFmt( bool compat, audio_sample_format_t *fmt, int *at_format )
+{
+ if( !compat && jfields.AudioFormat.has_ENCODING_IEC61937 )
+ {
+ *at_format = jfields.AudioFormat.ENCODING_IEC61937;
+ switch( fmt->i_format )
+ {
+ case VLC_CODEC_TRUEHD:
+ case VLC_CODEC_MLP:
+ fmt->i_rate = 192000;
+ fmt->i_bytes_per_frame = 16;
+
+ /* AudioFormat.ENCODING_IEC61937 documentation says that the
+ * channel layout must be stereo. Well, not for TrueHD
+ * apparently */
+ fmt->i_physical_channels = AOUT_CHANS_7_1;
+ break;
+ case VLC_CODEC_DTS:
+ fmt->i_bytes_per_frame = 4;
+ fmt->i_physical_channels = AOUT_CHANS_STEREO;
+ break;
+ case VLC_CODEC_DTSHD:
+ fmt->i_bytes_per_frame = 4;
+ fmt->i_physical_channels = AOUT_CHANS_STEREO;
+ fmt->i_rate = 192000;
+ fmt->i_bytes_per_frame = 16;
+ break;
+ case VLC_CODEC_EAC3:
+ fmt->i_rate = 192000;
+ case VLC_CODEC_A52:
+ fmt->i_physical_channels = AOUT_CHANS_STEREO;
+ fmt->i_bytes_per_frame = 4;
+ break;
+ default:
+ return VLC_EGENERIC;
+ }
+ fmt->i_frame_length = 1;
+ fmt->i_channels = aout_FormatNbChannels( fmt );
+ fmt->i_format = VLC_CODEC_SPDIFL;
+ }
+ else
+ {
+ if( vlc_android_AudioFormat_FourCCToEncoding( fmt->i_format, at_format )
+ != VLC_SUCCESS )
+ return VLC_EGENERIC;
+ fmt->i_bytes_per_frame = 4;
+ fmt->i_frame_length = 1;
+ fmt->i_physical_channels = AOUT_CHANS_STEREO;
+ fmt->i_channels = 2;
+ fmt->i_format = VLC_CODEC_SPDIFB;
+ }
+
+ return VLC_SUCCESS;
}
-/**
- * This thread will play the data coming from the jbuffer buffer.
- */
-static void *
-AudioTrack_Thread( void *p_data )
+static int
+StartPassthrough( JNIEnv *env, aout_stream_t *stream )
{
- aout_stream_t *stream = p_data;
aout_sys_t *p_sys = stream->sys;
- JNIEnv *env = GET_ENV();
- vlc_tick_t i_last_time_blocked = 0;
-
- vlc_thread_set_name("vlc-audiotrack");
-
- if( !env )
- return NULL;
- for( ;; )
+ /* Try ENCODING_IEC61937 first, then fallback to ENCODING_[AC3|DTS|...] */
+ unsigned nb_fmt = jfields.AudioFormat.has_ENCODING_IEC61937 ? 2 : 1;
+ int i_ret;
+ for( unsigned i = 0; i < nb_fmt; ++i )
{
- int i_ret = 0;
- bool b_forced;
+ int i_at_format;
+ bool compat = i == 1;
+ audio_sample_format_t fmt = p_sys->fmt;
- vlc_mutex_lock( &p_sys->lock );
+ i_ret = GetPassthroughFmt( compat, &fmt, &i_at_format );
+ if( i_ret != VLC_SUCCESS )
+ return i_ret;
- /* Wait for not paused state */
- while( p_sys->b_thread_running && p_sys->b_thread_paused )
+ p_sys->b_passthrough = true;
+ i_ret = AudioTrack_Create( env, stream, fmt.i_rate, i_at_format,
+ fmt.i_physical_channels );
+
+ if( i_ret == VLC_SUCCESS )
{
- i_last_time_blocked = 0;
- vlc_cond_wait( &p_sys->thread_cond, &p_sys->lock );
+ msg_Dbg( stream, "Using passthrough format: %d", i_at_format );
+ p_sys->i_chans_to_reorder = 0;
+ p_sys->fmt = fmt;
+ return VLC_SUCCESS;
}
+ }
- /* Wait for more data in the jbuffer buffer */
- while (p_sys->b_thread_running
- && p_sys->jbuffer.offset == p_sys->jbuffer.size
- && p_sys->frame_chain == NULL)
- vlc_cond_wait( &p_sys->thread_cond, &p_sys->lock );
+ p_sys->b_passthrough = false;
+ msg_Warn( stream, "SPDIF configuration failed" );
+ return VLC_EGENERIC;
+}
- if( !p_sys->b_thread_running || p_sys->b_error )
- {
- vlc_mutex_unlock( &p_sys->lock );
- break;
- }
+static int
+StartPCM( JNIEnv *env, aout_stream_t *stream, unsigned i_max_channels )
+{
+ aout_sys_t *p_sys = stream->sys;
+ unsigned i_nb_channels;
+ int i_at_format, i_ret;
- /* HACK: AudioFlinger can drop frames without notifying us and there is
- * no way to know it. If it happens, i_audiotrack_pos won't move and
- * the current code will be stuck because it'll assume that audiotrack
- * internal buffer is full when it's not. It may happen only after
- * Android 4.4.2 if we send frames too quickly. To fix this issue,
- * force the writing of the buffer after a certain delay. */
- if( i_last_time_blocked != 0 )
- b_forced = vlc_tick_now() - i_last_time_blocked >
- FRAMES_TO_US( p_sys->i_max_audiotrack_samples ) * 2;
- else
- b_forced = false;
+ if (jfields.AudioTrack.getNativeOutputSampleRate)
+ p_sys->fmt.i_rate =
+ JNI_AT_CALL_STATIC_INT( getNativeOutputSampleRate,
+ jfields.AudioManager.STREAM_MUSIC );
+ else
+ p_sys->fmt.i_rate = VLC_CLIP( p_sys->fmt.i_rate, 4000, 48000 );
- i_ret = AudioTrack_Write( env, stream, b_forced );
- if( i_ret >= 0 )
+ do
+ {
+ /* We can only accept U8, S16N, FL32, and AC3 */
+ switch( p_sys->fmt.i_format )
{
- if( i_ret == 0 )
+ case VLC_CODEC_U8:
+ i_at_format = jfields.AudioFormat.ENCODING_PCM_8BIT;
+ break;
+ case VLC_CODEC_S16N:
+ i_at_format = jfields.AudioFormat.ENCODING_PCM_16BIT;
+ break;
+ case VLC_CODEC_FL32:
+ if( jfields.AudioFormat.has_ENCODING_PCM_FLOAT )
+ i_at_format = jfields.AudioFormat.ENCODING_PCM_FLOAT;
+ else
{
- vlc_tick_t i_now = vlc_tick_now();
-
- /* cf. b_forced HACK comment */
- if( p_sys->i_write_type == WRITE_BYTEARRAY && i_last_time_blocked == 0 )
- i_last_time_blocked = i_now;
+ p_sys->fmt.i_format = VLC_CODEC_S16N;
+ i_at_format = jfields.AudioFormat.ENCODING_PCM_16BIT;
+ }
+ break;
+ default:
+ p_sys->fmt.i_format = VLC_CODEC_S16N;
+ i_at_format = jfields.AudioFormat.ENCODING_PCM_16BIT;
+ break;
+ }
- /* Wait for free space in Audiotrack internal buffer */
- vlc_tick_t i_play_deadline = i_now + __MAX( 10000,
- FRAMES_TO_US( p_sys->i_max_audiotrack_samples / 5 ) );
+ /* 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
+ */
- while( p_sys->b_thread_running && i_ret == 0 )
- i_ret = vlc_cond_timedwait( &p_sys->thread_cond, &p_sys->lock,
- i_play_deadline );
+ i_nb_channels = aout_FormatNbChannels( &p_sys->fmt );
+ if( i_nb_channels == 0 )
+ return VLC_EGENERIC;
+ if( AOUT_FMT_LINEAR( &p_sys->fmt ) )
+ i_nb_channels = __MIN( i_max_channels, i_nb_channels );
+ 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
+ p_sys->fmt.i_physical_channels = AOUT_CHANS_STEREO;
+ }
+ /* Try to create an AudioTrack with the most advanced channel and
+ * 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, stream, p_sys->fmt.i_rate, i_at_format,
+ p_sys->fmt.i_physical_channels );
+ if( i_ret != 0 )
+ {
+ if( p_sys->fmt.i_format == VLC_CODEC_FL32 )
+ {
+ msg_Warn( stream, "FL32 configuration failed, "
+ "fallback to S16N PCM" );
+ p_sys->fmt.i_format = VLC_CODEC_S16N;
}
- else
+ else if( p_sys->fmt.i_physical_channels & AOUT_CHANS_5_1 )
{
- i_last_time_blocked = 0;
- vlc_cond_signal( &p_sys->aout_cond );
+ msg_Warn( stream, "5.1 or 7.1 configuration failed, "
+ "fallback to Stereo" );
+ p_sys->fmt.i_physical_channels = AOUT_CHANS_STEREO;
}
+ else
+ break;
}
+ } while( i_ret != 0 );
- vlc_mutex_unlock( &p_sys->lock );
- }
-
- if (p_sys->jbuffer.array != NULL)
- {
- (*env)->DeleteLocalRef(env, p_sys->jbuffer.array);
- p_sys->jbuffer.array = NULL;
- }
+ if( i_ret != VLC_SUCCESS )
+ return i_ret;
- return NULL;
+ 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 );
+ aout_FormatPrepare( &p_sys->fmt );
+ return VLC_SUCCESS;
}
-static int
-ConvertFromIEC61937( aout_stream_t *stream, block_t *p_buffer )
+static void
+Stop( aout_stream_t *stream )
{
- /* This function is only used for Android API 23 when AudioTrack is
- * configured with ENCODING_ AC3/E_AC3/DTS. In that case, only the codec
- * data is needed (without the IEC61937 encapsulation). This function
- * recovers the codec data from an EC61937 frame. It is the opposite of the
- * code found in converter/tospdif.c. We could also request VLC core to
- * send us the codec data directly, but in that case, we wouldn't benefit
- * from the eac3 block merger of tospdif.c. */
+ aout_sys_t *p_sys = stream->sys;
+ JNIEnv *env;
- VLC_UNUSED( stream );
- uint8_t i_length_mul;
+ if( !( env = GET_ENV() ) )
+ return;
- if( p_buffer->i_buffer < 6 )
- return -1;
+ /* Stop the AudioTrack thread */
+ vlc_mutex_lock( &p_sys->lock );
+ if( p_sys->b_thread_running )
+ {
+ p_sys->b_thread_running = false;
+ vlc_cond_signal( &p_sys->thread_cond );
+ vlc_mutex_unlock( &p_sys->lock );
+ vlc_join( p_sys->thread, NULL );
+ }
+ else
+ vlc_mutex_unlock( &p_sys->lock );
- switch( GetWBE( &p_buffer->p_buffer[4] ) & 0xFF )
+ /* Release the AudioTrack object */
+ if( p_sys->p_audiotrack )
{
- case 0x01: /* IEC61937_AC3 */
- i_length_mul = 8;
- break;
- case 0x15: /* IEC61937_EAC3 */
- i_length_mul = 1;
- break;
- case 0x0B: /* IEC61937_DTS1 */
- case 0x0C: /* IEC61937_DTS2 */
- case 0x0D: /* IEC61937_DTS3 */
- i_length_mul = 8;
- break;
- case 0x11: /* IEC61937_DTSHD */
- i_length_mul = 1;
- break;
- default:
- vlc_assert_unreachable();
+ 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 );
}
- uint16_t i_length = GetWBE( &p_buffer->p_buffer[6] );
- if( i_length == 0 )
- return -1;
- i_length /= i_length_mul;
- if( i_length > p_buffer->i_buffer - 8 )
- return -1;
+ if( p_sys->p_dp != NULL )
+ DynamicsProcessing_Delete( stream, p_sys->p_dp );
- p_buffer->p_buffer += 8; /* SPDIF_HEADER_SIZE */
- p_buffer->i_buffer = i_length;
+ /* Release the timestamp object */
+ if( p_sys->timestamp.p_obj )
+ (*env)->DeleteGlobalRef( env, p_sys->timestamp.p_obj );
- return 0;
+ free( p_sys );
}
-static void
-Play( aout_stream_t *stream, block_t *p_buffer, vlc_tick_t i_date )
+static int
+Start( aout_stream_t *stream, audio_sample_format_t *restrict p_fmt,
+ enum android_audio_device_type adev )
{
- JNIEnv *env = NULL;
- aout_sys_t *p_sys = stream->sys;
+ JNIEnv *env;
+ int i_ret;
+ bool b_try_passthrough;
+ unsigned i_max_channels;
- if( p_sys->b_passthrough && p_sys->fmt.i_format == VLC_CODEC_SPDIFB
- && ConvertFromIEC61937( stream, p_buffer ) != 0 )
+ if( adev == ANDROID_AUDIO_DEVICE_ENCODED )
{
- block_Release(p_buffer);
- return;
+ b_try_passthrough = true;
+ i_max_channels = ANDROID_AUDIO_DEVICE_MAX_CHANNELS;
}
-
- vlc_mutex_lock( &p_sys->lock );
-
- if( p_sys->b_error || !( env = GET_ENV() ) )
+ else
{
- block_Release( p_buffer );
- goto bailout;
+ b_try_passthrough = false;
+ i_max_channels = adev == ANDROID_AUDIO_DEVICE_STEREO ? 2 : ANDROID_AUDIO_DEVICE_MAX_CHANNELS;
}
- 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 );
+ if( !( env = GET_ENV() ) )
+ return VLC_EGENERIC;
- vlc_frame_ChainLastAppend(&p_sys->frame_last, p_buffer);
- vlc_cond_signal(&p_sys->thread_cond);
+ AudioTrack_InitJNI(VLC_OBJECT(stream));
-bailout:
- vlc_mutex_unlock( &p_sys->lock );
- (void) i_date;
-}
+ aout_sys_t *p_sys = stream->sys = calloc( 1, sizeof (aout_sys_t) );
-static void
-Pause( aout_stream_t *stream, bool b_pause, vlc_tick_t i_date )
-{
- aout_sys_t *p_sys = stream->sys;
- JNIEnv *env;
- VLC_UNUSED( i_date );
+ if( unlikely( p_sys == NULL ) )
+ return VLC_ENOMEM;
- vlc_mutex_lock( &p_sys->lock );
+ vlc_mutex_init(&p_sys->lock);
+ vlc_cond_init(&p_sys->thread_cond);
- if( p_sys->b_error || !( env = GET_ENV() ) )
- goto bailout;
+ p_sys->volume = 1.0f;
+ p_sys->mute = false;
- if( b_pause )
- {
- p_sys->b_thread_paused = true;
- JNI_AT_CALL_VOID( pause );
- CHECK_AT_EXCEPTION( "pause" );
- } else
- {
- p_sys->b_thread_paused = false;
- AudioTrack_ResetPositions( env, stream );
- JNI_AT_CALL_VOID( play );
- CHECK_AT_EXCEPTION( "play" );
- }
+ p_sys->fmt = *p_fmt;
-bailout:
- vlc_mutex_unlock( &p_sys->lock );
-}
+ aout_FormatPrint( stream, "VLC is looking for:", &p_sys->fmt );
-static void
-Flush( aout_stream_t *stream )
-{
- aout_sys_t *p_sys = stream->sys;
- JNIEnv *env;
+ if (p_sys->fmt.channel_type == AUDIO_CHANNEL_TYPE_AMBISONICS)
+ {
+ p_sys->fmt.channel_type = AUDIO_CHANNEL_TYPE_BITMAP;
- vlc_mutex_lock( &p_sys->lock );
+ /* TODO: detect sink channel layout */
+ p_sys->fmt.i_physical_channels = AOUT_CHANS_STEREO;
+ aout_FormatPrepare(&p_sys->fmt);
+ }
- p_sys->jbuffer.size = p_sys->jbuffer.offset = 0;
- vlc_frame_ChainRelease(p_sys->frame_chain);
- p_sys->frame_chain = NULL;
- p_sys->frame_last = &p_sys->frame_chain;
+ if( AOUT_FMT_LINEAR( &p_sys->fmt ) )
+ i_ret = StartPCM( env, stream, i_max_channels );
+ else if( b_try_passthrough )
+ i_ret = StartPassthrough( env, stream );
+ else
+ i_ret = VLC_EGENERIC;
- if( p_sys->b_error || !( env = GET_ENV() ) )
- goto bailout;
+ if( i_ret != 0 )
+ {
+ free( p_sys );
+ return VLC_EGENERIC;
+ }
- /* Android doc:
- * stop(): Stops playing the audio data. When used on an instance created
- * in MODE_STREAM mode, audio will stop playing after the last buffer that
- * was written has been played. For an immediate stop, use pause(),
- * followed by flush() to discard audio data that hasn't been played back
- * yet.
- *
- * flush(): Flushes the audio data currently queued for playback. Any data
- * that has not been played back will be discarded. No-op if not stopped
- * or paused, or if the track's creation mode is not MODE_STREAM.
- */
- JNI_AT_CALL_VOID( pause );
- if( CHECK_AT_EXCEPTION( "pause" ) )
- goto bailout;
- JNI_AT_CALL_VOID( flush );
+ if( jfields.AudioTrack.getBufferSizeInFrames )
+ p_sys->i_max_audiotrack_samples = JNI_AT_CALL_INT( getBufferSizeInFrames );
+ else
+ p_sys->i_max_audiotrack_samples = BYTES_TO_FRAMES( p_sys->audiotrack_args.i_size );
- /* HACK: Before Android 4.4, the head position is not reset to zero and is
- * still moving after a flush or a stop. This prevents to get a precise
- * head position and there is no way to know when it stabilizes. Therefore
- * recreate an AudioTrack object in that case. The AudioTimestamp class was
- * added in API Level 19, so if this class is not found, the Android
- * Version is 4.3 or before */
- if( !jfields.AudioTimestamp.clazz && p_sys->i_samples_written > 0 )
+ if( jfields.AudioTimestamp.clazz )
{
- if( AudioTrack_Recreate( env, stream ) != 0 )
+ /* create AudioTimestamp object */
+ jobject p_obj = JNI_CALL( NewObject, jfields.AudioTimestamp.clazz,
+ jfields.AudioTimestamp.ctor );
+ if( p_obj )
{
- p_sys->b_error = true;
- goto bailout;
+ p_sys->timestamp.p_obj = (*env)->NewGlobalRef( env, p_obj );
+ (*env)->DeleteLocalRef( env, p_obj );
}
+ if( !p_sys->timestamp.p_obj )
+ goto error;
}
- AudioTrack_Reset( env, stream );
- JNI_AT_CALL_VOID( play );
- CHECK_AT_EXCEPTION( "play" );
-
-bailout:
- vlc_mutex_unlock( &p_sys->lock );
-}
-static void
-AudioTrack_SetVolume( JNIEnv *env, aout_stream_t *stream, float volume )
-{
- aout_sys_t *p_sys = stream->sys;
+ p_sys->jbuffer.array = NULL;
+ p_sys->jbuffer.size = p_sys->jbuffer.offset = 0;
+ p_sys->jbuffer.maxsize = 0;
+ p_sys->frame_chain = NULL;
+ p_sys->frame_last = &p_sys->frame_chain;
+ AudioTrack_Reset( env, stream );
- if( jfields.AudioTrack.setVolume )
+ if( p_sys->fmt.i_format == VLC_CODEC_FL32 )
{
- JNI_AT_CALL_INT( setVolume, volume );
- CHECK_AT_EXCEPTION( "setVolume" );
- } else
+ msg_Dbg( stream, "using WRITE_FLOATARRAY");
+ p_sys->i_write_type = WRITE_FLOATARRAY;
+ }
+ else if( p_sys->fmt.i_format == VLC_CODEC_SPDIFL )
{
- JNI_AT_CALL_INT( setStereoVolume, volume, volume );
- CHECK_AT_EXCEPTION( "setStereoVolume" );
+ assert( jfields.AudioFormat.has_ENCODING_IEC61937 );
+ msg_Dbg( stream, "using WRITE_SHORTARRAYV23");
+ p_sys->i_write_type = WRITE_SHORTARRAYV23;
}
-}
-
-static void
-VolumeSet( aout_stream_t *stream, float volume )
-{
- aout_sys_t *p_sys = stream->sys;
- JNIEnv *env;
- float gain = 1.0f;
-
- p_sys->volume = volume;
- if (volume > 1.f)
+ else if( jfields.AudioTrack.writeV23 )
{
- gain = volume * volume * volume;
- volume = 1.f;
+ msg_Dbg( stream, "using WRITE_BYTEARRAYV23");
+ p_sys->i_write_type = WRITE_BYTEARRAYV23;
+ }
+ else if( jfields.AudioTrack.writeBufferV21 )
+ {
+ msg_Dbg( stream, "using WRITE_BYTEBUFFER");
+ p_sys->i_write_type = WRITE_BYTEBUFFER;
+ }
+ else
+ {
+ msg_Dbg( stream, "using WRITE_BYTEARRAY");
+ p_sys->i_write_type = WRITE_BYTEARRAY;
}
- if( !p_sys->b_error && p_sys->p_audiotrack != NULL && ( env = GET_ENV() ) )
+ /* Run AudioTrack_Thread */
+ p_sys->b_thread_running = true;
+ p_sys->b_thread_paused = false;
+ if ( vlc_clone( &p_sys->thread, AudioTrack_Thread, stream ) )
{
- AudioTrack_SetVolume( env, stream, volume );
+ msg_Err(stream, "vlc clone failed");
+ goto error;
+ }
- /* Apply gain > 1.0f via DynamicsProcessing if possible */
- if( p_sys->p_dp != NULL )
- {
- if( gain <= 1.0f )
- {
- /* DynamicsProcessing is not needed anymore (using AudioTrack
- * volume) */
- DynamicsProcessing_Disable( stream, p_sys->p_dp );
- }
- else
- {
- int ret = DynamicsProcessing_SetVolume( stream, p_sys->p_dp, gain );
+ JNI_AT_CALL_VOID( play );
+ CHECK_AT_EXCEPTION( "play" );
- if( ret == VLC_SUCCESS )
- gain = 1.0; /* reset sw gain */
- else
- msg_Warn( stream, "failed to set gain via DynamicsProcessing, fallback to sw gain");
- }
- }
- }
+ *p_fmt = p_sys->fmt;
- aout_stream_GainRequest(stream, gain);
-}
+ aout_FormatPrint( stream, "VLC will output:", &p_sys->fmt );
-static void
-MuteSet( aout_stream_t *stream, bool mute )
-{
- aout_sys_t *p_sys = stream->sys;
- JNIEnv *env;
- p_sys->mute = mute;
+ stream->stop = Stop;
+ stream->play = Play;
+ stream->pause = Pause;
+ stream->flush = Flush;
+ stream->time_get = NULL;
+ stream->volume_set = VolumeSet;
+ stream->mute_set = MuteSet;
- if( !p_sys->b_error && p_sys->p_audiotrack != NULL && ( env = GET_ENV() ) )
- AudioTrack_SetVolume( env, stream, mute ? 0.0f : p_sys->volume );
+ msg_Dbg(stream, "using AudioTrack API");
+ return VLC_SUCCESS;
+
+error:
+ Stop( stream );
+ return VLC_EGENERIC;
}
vlc_module_begin ()
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/cb8d5930131fac647a08b035352b628edcda8d85...e476328eb6fb6acd93cb0791f3d0f807a85c9254
--
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/cb8d5930131fac647a08b035352b628edcda8d85...e476328eb6fb6acd93cb0791f3d0f807a85c9254
You're receiving this email because of your account on code.videolan.org.
VideoLAN code repository instance
More information about the vlc-commits
mailing list