【十四】【vlc-android】aout音频输出模块源码实现分析【Part 2】
作者:互联网
该章节承接上一章节内容继续分析
上一章节:【十四】【vlc-android】aout音频输出模块源码实现分析【Part 1】
3、Stop实现分析:【停止AudioTrack线程等相关操作】
// [vlc/modules/audio_output/audiotrack.c]
static void
Stop( audio_output_t *p_aout )
{
aout_sys_t *p_sys = p_aout->sys;
JNIEnv *env;
if( !( env = GET_ENV() ) )
return;
/* 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 );
// 以阻塞的方式等待thread指定的线程结束
vlc_join( p_sys->thread, NULL );
}
else
vlc_mutex_unlock( &p_sys->lock );
// 释放AudioTrack对象引用,后面类似处理释放内存或初始化
/* Release the AudioTrack object */
if( p_sys->p_audiotrack )
{
if( !p_sys->b_audiotrack_exception )
{// 若没有异常,调用java层AudioTrack的stop和release方法
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;
}
/* Release the timestamp object */
if( p_sys->timestamp.p_obj )
{
(*env)->DeleteGlobalRef( env, p_sys->timestamp.p_obj );
p_sys->timestamp.p_obj = NULL;
}
/* Release the Circular buffer data */
switch( p_sys->i_write_type )
{
case WRITE_BYTEARRAY:
case WRITE_BYTEARRAYV23:
if( p_sys->circular.u.p_bytearray )
{
(*env)->DeleteGlobalRef( env, p_sys->circular.u.p_bytearray );
p_sys->circular.u.p_bytearray = NULL;
}
break;
case WRITE_SHORTARRAYV23:
if( p_sys->circular.u.p_shortarray )
{
(*env)->DeleteGlobalRef( env, p_sys->circular.u.p_shortarray );
p_sys->circular.u.p_shortarray = NULL;
}
break;
case WRITE_FLOATARRAY:
if( p_sys->circular.u.p_floatarray )
{
(*env)->DeleteGlobalRef( env, p_sys->circular.u.p_floatarray );
p_sys->circular.u.p_floatarray = NULL;
}
break;
case WRITE_BYTEBUFFER:
free( p_sys->circular.u.bytebuffer.p_data );
p_sys->circular.u.bytebuffer.p_data = NULL;
break;
}
p_sys->b_audiotrack_exception = false;
p_sys->b_error = false;
p_sys->b_passthrough = false;
}
4、Play实现分析:【播放已解码音频buffer数据】
// [vlc/modules/audio_output/audiotrack.c]
static void
Play( audio_output_t *p_aout, block_t *p_buffer )
{
JNIEnv *env = NULL;
size_t i_buffer_offset = 0;
aout_sys_t *p_sys = p_aout->sys;
// IEC61937 的数据格式可以包含象MPEG2那种多声道, AC3 或DTS。
// 当IEC61937的数据流可以在保持原有采样率的情况下被转换为S/PDIF信号,
// 声道标识信息只占1bit (仅1),代表数据在S/PDIF帧是数字音频还是其他数据 (DTS, AC3, MPEG audio etc.)。
// 这个bit会说明标准数字音频设备不用尝试以他们的采样率回放这些数据。
// S/PDIF(Sony/Philips Digital Interface Format)是一种数字传输接口,可使用光纤或同轴电缆输出,
// 把音频输出至解码器上,能保持高保真度的输出结果。
if( p_sys->b_passthrough && p_sys->fmt.i_format == VLC_CODEC_SPDIFB
&& ConvertFromIEC61937( p_aout, p_buffer ) != 0 )
{
block_Release(p_buffer);
return;
}
vlc_mutex_lock( &p_sys->lock );
if( p_sys->b_error || !( env = GET_ENV() ) )
goto bailout;
if( p_sys->i_chans_to_reorder )
// channel表重排序
// 在一个线性音频交错样本块内重新排序音频样本。
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 );
// [i_buffer_offset]该值根据下面的实现可知作用:即记录当前已从block buffer数据读取的数据量
// 即定位block中已读取到当前的位置
while( i_buffer_offset < p_buffer->i_buffer && !p_sys->b_error )
{
size_t i_circular_free;
size_t i_data_offset;
size_t i_data_size;
// 判断在buffer循环缓冲区队列中是否有足够内存数据空间可写入新的待播放数据
// 即若写入数据已满占整个循环缓冲区内存则必须wait解码器解码端线程,
// 否则数据会造成溢出来不及播放就被覆盖了。并等待aout播放端进行播放后唤醒此处
/* Wait for enough room in circular buffer */
while( !p_sys->b_error && ( i_circular_free = p_sys->circular.i_size -
( p_sys->circular.i_write - p_sys->circular.i_read ) ) == 0 )
// 若没有释放【播放后】的内存空间则wait当前播放方法的调用端线程。
// 【第七章音频decoder层分析中3.1小节分析调用了audio播放流程】
// 作用就是:当前被写入【解码时】的buffer内存大小已使用完整个buffer循环缓冲区大小,
// 导致没有足够已释放空间内存写入当前待播放的block数据则wait当前play操作
// 而唤醒此处的操作是在下面的【AudioTrack_Thread】第12小节分析中
vlc_cond_wait( &p_sys->aout_cond, &p_sys->lock );
if( p_sys->b_error )
goto bailout;
// 取余获取当前已写入的数据偏移量
i_data_offset = p_sys->circular.i_write % p_sys->circular.i_size;
// 计算当前能够写入已释放空间内存的block数据大小
i_data_size = __MIN( p_buffer->i_buffer - i_buffer_offset,
p_sys->circular.i_size - i_data_offset );
i_data_size = __MIN( i_data_size, i_circular_free );
// 以下为根据写入数据类型来设置
switch( p_sys->i_write_type )
{
case WRITE_BYTEARRAY:
case WRITE_BYTEARRAYV23:
// i_buffer_offset根据偏移量定位block写入数据的开始位置,写入的数据长度为i_data_size
(*env)->SetByteArrayRegion( env, p_sys->circular.u.p_bytearray,
i_data_offset, i_data_size,
(jbyte *)p_buffer->p_buffer
+ i_buffer_offset);
break;
case WRITE_SHORTARRAYV23:
// ~取反算术运算符表示二进制位取反,即~1 = 1111 1110
// &与运算:因此如【size &= ~1】作用为,若size的最后一位为1则将其变为0,其他位保持不变
// 即最终size可能减小1
i_data_offset &= ~1;
i_data_size &= ~1;
// 其实就是将size作为2的整数倍用于此处的写入计算,因为变小了2倍,因此必须使其能保持整数大小写入数据
(*env)->SetShortArrayRegion( env, p_sys->circular.u.p_shortarray,
i_data_offset / 2, i_data_size / 2,
(jshort *)p_buffer->p_buffer
+ i_buffer_offset / 2);
break;
case WRITE_FLOATARRAY:
// 同上类似处理,即将size二进制表示的第1和第2位值为0,使其保持是4的整数倍大小写入数据
i_data_offset &= ~3;
i_data_size &= ~3;
(*env)->SetFloatArrayRegion( env, p_sys->circular.u.p_floatarray,
i_data_offset / 4, i_data_size / 4,
(jfloat *)p_buffer->p_buffer
+ i_buffer_offset / 4);
break;
case WRITE_BYTEBUFFER:
// 从对应偏移量数据的位置写入数据
memcpy( p_sys->circular.u.bytebuffer.p_data + i_data_offset,
p_buffer->p_buffer + i_buffer_offset, i_data_size );
break;
}
// 根据写入数据大小改变写入数据的偏移量
i_buffer_offset += i_data_size;
p_sys->circular.i_write += i_data_size;
if( !p_sys->b_thread_waiting )
// 唤醒当前可能沉睡的线程
vlc_cond_signal( &p_sys->thread_cond );
}
bailout:
vlc_mutex_unlock( &p_sys->lock );
block_Release( p_buffer );
}
5、Pause实现分析:【其实该功能不止是暂停功能,还有继续恢复播放功能即b_pause为true要求暂停,为false则要求恢复播放】
// [vlc/modules/audio_output/audiotrack.c]
static void
Pause( audio_output_t *p_aout, bool b_pause, mtime_t i_date )
{
aout_sys_t *p_sys = p_aout->sys;
JNIEnv *env;
VLC_UNUSED( i_date );
vlc_mutex_lock( &p_sys->lock );
if( p_sys->b_error || !( env = GET_ENV() ) )
goto bailout;
if( b_pause )
{
// 修改播放状态,并调用AudioTrack的pause暂停方法
p_sys->b_thread_paused = true;
JNI_AT_CALL_VOID( pause );
CHECK_AT_EXCEPTION( "pause" );
} else
{
// 修改播放状态,并调用AudioTrack的play方法
p_sys->b_thread_paused = false;
// 重置AudioTrack平滑位置和时间戳位置
AudioTrack_ResetPositions( env, p_aout );
JNI_AT_CALL_VOID( play );
CHECK_AT_EXCEPTION( "play" );
}
bailout:
vlc_mutex_unlock( &p_sys->lock );
}
6、Flush实现分析:【清空刷新此前存在的循环缓冲区数据,并根据参数【b_wait】判断是否需要等待已存在的数据播放完毕后才清空,true表示需要等待播放完毕才清空】
// [vlc/modules/audio_output/audiotrack.c]
static void
Flush( audio_output_t *p_aout, bool b_wait )
{
aout_sys_t *p_sys = p_aout->sys;
JNIEnv *env;
vlc_mutex_lock( &p_sys->lock );
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.
*/
if( b_wait )
{
// 若当前还存在待播放数据则wait当前flush方法的调用端线程。
// 【前面相关章节有分析到调用该方法的流程】
// 而唤醒此处的操作是在下面的【AudioTrack_Thread】第12小节分析中
/* Wait for the thread to process the circular buffer */
while( !p_sys->b_error
&& p_sys->circular.i_read != p_sys->circular.i_write )
vlc_cond_wait( &p_sys->aout_cond, &p_sys->lock );
if( p_sys->b_error )
goto bailout;
// 执行java层AudioTrack的stop方法
JNI_AT_CALL_VOID( stop );
if( CHECK_AT_EXCEPTION( "stop" ) )
goto bailout;
} else
{
// 执行java层AudioTrack的pause方法,会直接暂停声音
JNI_AT_CALL_VOID( pause );
if( CHECK_AT_EXCEPTION( "pause" ) )
goto bailout;
// 立即刷新清空AudioTrack的缓冲区数据
JNI_AT_CALL_VOID( flush );
}
// 初始化为0
p_sys->circular.i_read = p_sys->circular.i_write = 0;
// 注译:Android 4.4之前的head position兼容性处理
/* 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( AudioTrack_Recreate( env, p_aout ) != 0 )
{
p_sys->b_error = true;
goto bailout;
}
}
// 重置AudioTrack平滑位置和时间戳位置
AudioTrack_Reset( env, p_aout );
// 调用AudioTrack的play方法重新进行播放
JNI_AT_CALL_VOID( play );
CHECK_AT_EXCEPTION( "play" );
bailout:
vlc_mutex_unlock( &p_sys->lock );
}
7、TimeGet实现分析:
// [vlc/modules/audio_output/audiotrack.c]
static int
TimeGet( audio_output_t *p_aout, mtime_t *restrict p_delay )
{
aout_sys_t *p_sys = p_aout->sys;
mtime_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;
// 注:US微秒单位,MS毫秒单位
// 获取当前已播放时长【即当前播放时间点】
// 见下面的分析
i_audiotrack_us = AudioTrack_GetTimestampPositionUs( env, p_aout );
if( i_audiotrack_us <= 0 )
// 若小于0则经过平滑处理获取播放时长
// 见下面的分析
i_audiotrack_us = AudioTrack_GetSmoothPositionUs(env, p_aout );
/* 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
if( i_audiotrack_us > 0 )
{
// 计算AudioTra delay时长,即当前已写入样本数减去当前已播放时长的差作为播放延迟时间
/* AudioTrack delay */
mtime_t i_delay = FRAMES_TO_US( p_sys->i_samples_written )
- i_audiotrack_us;
if( i_delay >= 0 )
{// 不小于0则表示当前写入数据【解码】速度正常即解码速度基本跟得上播放速度,
// 计算循环缓冲区写入数据【解码】端和读取数据【播放】端的差值作为循环缓冲区延迟时间
/* Circular buffer delay */
i_delay += BYTES_TO_US( p_sys->circular.i_write
- p_sys->circular.i_read );
*p_delay = i_delay;
vlc_mutex_unlock( &p_sys->lock );
return 0;
}
else
{
// 否则时间错误,并重置相关当前播放时间戳值
msg_Warn( p_aout, "timing screwed, reset positions" );
AudioTrack_ResetPositions( env, p_aout );
}
}
bailout:
vlc_mutex_unlock( &p_sys->lock );
return -1;
}
// [vlc/modules/audio_output/audiotrack.c]
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.
*/
// AudioTimestamp java类会每隔(大于)500毫秒轮询一次获取当前播放时间
// 对getTimeStamp方法的调用是以500ms为间隔
/* 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;
// 调用AudioTrack的该方法
if( JNI_AT_CALL_BOOL( getTimestamp, p_sys->timestamp.p_obj ) )
{
// 获取AudioTimestamp的字段nanoTime纳米时间值,并转为微秒时间值
// AudioTimestamp.nanoTime是上次调用时拿到的结果
p_sys->timestamp.i_frame_us = JNI_AUDIOTIMESTAMP_GET_LONG( nanoTime ) / 1000;
// 获取AudioTimestamp的字段framePosition值
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) )
{
// 此处功能:计算当前数据帧时间差即已经过去多长时间了,再计算当前时间差对应的帧数据个数差
// 如此计算已播放帧数据对应的时长【已播放样本个数/采样率即可得已播放的时长】
// i_now – AudioTimestamp.nanoTime 得到的就是距离上次调用所经过的系统时间,
// FRAMES_TO_US( p_sys->timestamp.i_frame_pos)代表的是上次调用时获取到的“Audio当前播放的时间”,
// 二者相加即为当前系统时间下的“Audio当前播放的时间”。
// 此处是将帧数相加获取总时间即FRAMES_TO_US( p_sys->timestamp.i_frame_pos + i_frames_diff )
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;
}
// [vlc/modules/audio_output/audiotrack.c]
/**
* 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 )
{
// 因为 getPlayheadPositionUs() 的粒度只有约20ms【旧设备】, 若直接拿来用的话精度不够
// 要进行采样和平滑演算得到playback position,因此计算已播放时长。
// 因为getPlayheadPositionUs的精度不足以用来做音视频同步,
// 所以这里通过计算每次getPlayheadPositionUs拿到的值与系统时钟的offset,
// 并且取平均值,来解决精度不足的问题,平滑后的值即为smoothedPlayheadOffsetUs,
// 再加上系统时钟即为“Audio当前播放的时间”。
// 当然,最后要减去通过AudioTrack.getOutputLatency方法获取到的底层delay值,才是最终的结果。
// 用audioTrack.getPlaybackHeadPosition方法来计算, 但是因为这个值的粒度只有20ms,
// 可能存在一些抖动, 所以做了一些平滑处理。
i_audiotrack_us = FRAMES_TO_US( AudioTrack_getPlaybackHeadPosition( env, p_aout ) );
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++;
// 根据当前时间计算平均playback position
/* 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( jfields.AudioSystem.getOutputLatency )
{
// 获取底层delay时间值
int i_latency_ms = JNI_CALL( CallStaticIntMethod,
jfields.AudioSystem.clazz,
jfields.AudioSystem.getOutputLatency,
jfields.AudioManager.STREAM_MUSIC );
// delay值大于0则转换为微秒
p_sys->smoothpos.i_latency_us = i_latency_ms > 0 ?
i_latency_ms * 1000L : 0;
}
}
if( p_sys->smoothpos.i_us != 0 )
// 计算当前播放时间:平均时差时长 + 当前时间 - 设备播放输出延迟时长
return p_sys->smoothpos.i_us + i_now - p_sys->smoothpos.i_latency_us;
else
return 0;
}
8、DeviceSelect实现分析:【音频播放设备选择】
// [vlc/modules/audio_output/audiotrack.c]
static int DeviceSelect(audio_output_t *p_aout, const char *p_id)
{
aout_sys_t *p_sys = p_aout->sys;
enum at_dev at_dev = AT_DEV_DEFAULT;
// 设备ID有:stereo、pcm、encoded,【AT_DEV_STEREO】默认。
if( p_id )
{
for( unsigned int i = 0; at_devs[i].id; ++i )
{
// 匹配设备类型
if( strncmp( p_id, at_devs[i].id, strlen( at_devs[i].id ) ) == 0 )
{
at_dev = at_devs[i].at_dev;
break;
}
}
}
long long i_encoding_flags = 0;
if( at_dev == AT_DEV_ENCODED )
{
const size_t i_prefix_size = strlen( "encoded:" );
if( strncmp( p_id, "encoded:", i_prefix_size ) == 0 )
i_encoding_flags = atoll( p_id + i_prefix_size );
}
if( at_dev != p_sys->at_dev || i_encoding_flags != p_sys->i_encoding_flags )
{
p_sys->at_dev = at_dev;
p_sys->i_encoding_flags = i_encoding_flags;
// 该方法内部调用了【aout->event.restart_request(aout, mode);】,
// 而该方法的赋值在第九章节第2小节分析中赋值的,
// 其功能为:将该变化事件通知给java层
aout_RestartRequest( p_aout, AOUT_RESTART_OUTPUT );
msg_Dbg( p_aout, "selected device: %s", p_id );
if( at_dev == AT_DEV_ENCODED )
{
static const vlc_fourcc_t enc_fourccs[] = {
VLC_CODEC_DTS, VLC_CODEC_A52, VLC_CODEC_EAC3, VLC_CODEC_TRUEHD,
};
for( size_t i = 0;
i < sizeof( enc_fourccs ) / sizeof( enc_fourccs[0] ); ++i )
{
// DTS_HD表示高码率的音频
bool b_dtshd;
if( AudioTrack_HasEncoding( p_aout, enc_fourccs[i], &b_dtshd ) )
msg_Dbg( p_aout, "device has %4.4s passthrough support",
b_dtshd ? "dtsh" : (const char *)&enc_fourccs[i] );
}
}
}
// 该方法内部调用了【aout->event.device_report(aout, id);】,
// 而该方法的赋值在第九章节第2小节分析中赋值的,
// 其功能为:将该变化事件通知给java层
aout_DeviceReport( p_aout, p_id );
return VLC_SUCCESS;
}
9、VolumeSet实现分析:【音量调节功能】
// [vlc/modules/audio_output/audiotrack.c]
static int
VolumeSet( audio_output_t *p_aout, float volume )
{
aout_sys_t *p_sys = p_aout->sys;
JNIEnv *env;
// 音量增益值
// 增益简单可理解为:增大信号的倍数【但可能会导致声音失真】
float gain = 1.0f;
if (volume > 1.f)
{
// 音量比例设置大于1时,设置音量最大比例为1,然后进行增益操作
p_sys->volume = 1.f;
gain = volume;
}
else
p_sys->volume = volume;
if( !p_sys->b_error && p_sys->p_audiotrack != NULL && ( env = GET_ENV() ) )
{
// 调用AudioTrack的对应音量调节方法
if( jfields.AudioTrack.setVolume )
{
JNI_AT_CALL_INT( setVolume, volume );
CHECK_AT_EXCEPTION( "setVolume" );
} else
{
JNI_AT_CALL_INT( setStereoVolume, volume, volume );
CHECK_AT_EXCEPTION( "setStereoVolume" );
}
}
// 该方法内部调用了【aout->event.volume_report(aout, volume);】,
// 而该方法的赋值在第九章节第2小节分析中赋值的,
// 其功能为:将该变化事件通知给java层
aout_VolumeReport(p_aout, volume);
// 该方法内部调用了【aout->event.gain_request(aout, gain);】,
// 而该方法的赋值在第九章节第2小节分析中赋值的,
// 其功能为:将该变化事件通知给java层
// 注:gain * gain * gain的值若大于1则会造成声音过载或失真效果
aout_GainRequest(p_aout, gain * gain * gain);
return 0;
}
10、MuteSet实现分析:【静音开启或关闭功能】
// [vlc/modules/audio_output/audiotrack.c]
static int
MuteSet( audio_output_t *p_aout, bool mute )
{
aout_sys_t *p_sys = p_aout->sys;
JNIEnv *env;
p_sys->mute = mute;
if( !p_sys->b_error && p_sys->p_audiotrack != NULL && ( env = GET_ENV() ) )
{
// 设置AudioTrack音量值为0即为静音效果,无声音输出
if( jfields.AudioTrack.setVolume )
{
JNI_AT_CALL_INT( setVolume, mute ? 0.0f : p_sys->volume );
CHECK_AT_EXCEPTION( "setVolume" );
} else
{
JNI_AT_CALL_INT( setStereoVolume, mute ? 0.0f : p_sys->volume, mute ? 0.0f : p_sys->volume );
CHECK_AT_EXCEPTION( "setStereoVolume" );
}
}
// 该方法内部调用了【aout->event.mute_report(aout, mute);】,
// 而该方法的赋值在第九章节第2小节分析中赋值的,
// 其功能为:将该变化事件通知给java层
aout_MuteReport(p_aout, mute);
return 0;
}
11、AudioTrack_Thread线程实现:【从循环缓冲区中读取buffer数据来进行播放请求】
// [vlc/modules/audio_output/audiotrack.c]
/**
* This thread will play the data coming from the circular buffer.
*/
static void *
AudioTrack_Thread( void *p_data )
{
audio_output_t *p_aout = p_data;
aout_sys_t *p_sys = p_aout->sys;
JNIEnv *env = GET_ENV();
mtime_t i_play_deadline = 0;
mtime_t i_last_time_blocked = 0;
if( !env )
return NULL;
for( ;; )
{
int i_ret = 0;
bool b_forced;
size_t i_data_offset;
size_t i_data_size;
vlc_mutex_lock( &p_sys->lock );
// 若播放时间还未到,等待AudioTrack内部缓冲区释放空间内存
// 注译:就算有新的数据写入循环缓冲区时也不能唤醒该线程,
// 必须等待在AudioTrack内部缓冲区中有更多可用的内存空间
/* Wait for free space in Audiotrack internal buffer */
if( i_play_deadline != 0 && mdate() < i_play_deadline )
{
/* Don't wake up the thread when there is new data since we are
* waiting for more space */
// 标记当前线程状态,照应了前面若在该状态下则不能换成该线程,
// 必须得等待播放时间到期时自动唤醒
p_sys->b_thread_waiting = true;
// wait指定时间【i_play_deadline】后自动唤醒
while( p_sys->b_thread_running && i_ret != ETIMEDOUT )
i_ret = vlc_cond_timedwait( &p_sys->thread_cond,
&p_sys->lock,
i_play_deadline );
// 重置
i_play_deadline = 0;
p_sys->b_thread_waiting = false;
}
// 若【上面Pause播放暂停功能】当前请求了线程暂停状态,则进行wait,不继续播放
/* 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 );
}
// 该条件【读取数据大小大于等于写入数据大小】成立则等待更多被写入的待播放数据
/* Wait for more data in the circular buffer */
while( p_sys->b_thread_running
&& p_sys->circular.i_read >= p_sys->circular.i_write )
vlc_cond_wait( &p_sys->thread_cond, &p_sys->lock );
if( !p_sys->b_thread_running || p_sys->b_error )
{// 若线程要求停止或异常则退出播放
vlc_mutex_unlock( &p_sys->lock );
break;
}
// 此处根据英文注释可知,有个兼容性上的处理:即android 4.4.2以后若快速发送帧数据
// 则会造成AudioFlinger丢弃数据,因此vlc在一定延迟后强制写入AudioTrack缓冲区
/* 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 )
// 由下面的分析可知:【i_last_time_blocked】记录的是上次未成功写入数据
// 到AudioTrack缓冲区的时间,此处判断是否应该需要强制写入数据到AudioTrack缓冲区中进行播放。
// 作用解决如上问题,此处的延迟时间定为缓冲区满时总时长的两倍
b_forced = mdate() - i_last_time_blocked >
FRAMES_TO_US( p_sys->i_max_audiotrack_samples ) * 2;
else
b_forced = false;
// 记录读取数据偏移量,此处为i_read大小
i_data_offset = p_sys->circular.i_read % p_sys->circular.i_size;
// 计算可读数据大小,此处取最小值,
// 而[p_sys->circular.i_size - i_data_offset]这个计算是防止越界
i_data_size = __MIN( p_sys->circular.i_size - i_data_offset,
p_sys->circular.i_write - p_sys->circular.i_read );
// AudioTrack写入数据进行播放
// 见下面的分析
i_ret = AudioTrack_Play( env, p_aout, i_data_size, i_data_offset,
b_forced );
// 大于0时表示真正往AudioTrack中成功写入数据大小
if( i_ret >= 0 )
{// 此处进入表示基本成功【AudioTrack_Play分析中可知有些异常时也返回0】
if( p_sys->i_write_type == WRITE_BYTEARRAY )
{
// 注意:此处讲的阻塞时间一般情况下代表的真正含义是:
// 当前AudioTrack内部的缓冲区数据已写入满了,导致不能再往里面写入数据
if( i_ret != 0 )
// 此处执行时表示真正写入数据成功了,因此将最近阻塞时间重置
i_last_time_blocked = 0;
else if( i_last_time_blocked == 0 )
// 往AudioTrack写入数据异常时返回0情况,因此记录最近阻塞时间为当前时间
i_last_time_blocked = mdate();
}
if( i_ret == 0 )
// 往AudioTrack写入数据异常时返回0情况,如上原因,AudioTrack内部缓冲区已满,
// 因此有必要延迟一定时间写入【播放】数据到AudioTrack即等待AudioTrack中有更多的缓冲区可用内存
// 此处延迟时间定为10毫秒内【循环缓冲区最大时长的五分之一】
i_play_deadline = mdate() + __MAX( 10000, FRAMES_TO_US(
p_sys->i_max_audiotrack_samples / 5 ) );
else
// 往AudioTrack缓冲区中写入数据成功
// 更新循环缓冲区已被读取数据大小:加上往AudioTrack中成功写入数据大小
p_sys->circular.i_read += i_ret;
}
// [aout_cond]该线程锁表示:
// 1、解码端线程往循环缓冲区写入数据时,缓冲区已满必须等待播放后释放。
// 2、解码端线程请求flush执行时若需要等待播放完毕,也wait。
// 因此此处唤醒解码端线程,使其继续解码并放入解码后数据到循环缓存中
vlc_cond_signal( &p_sys->aout_cond );
vlc_mutex_unlock( &p_sys->lock );
}
if( p_sys->circular.u.bytebuffer.p_obj )
{
(*env)->DeleteLocalRef( env, p_sys->circular.u.bytebuffer.p_obj );
p_sys->circular.u.bytebuffer.p_obj = NULL;
}
return NULL;
}
// [vlc/modules/audio_output/audiotrack.c]
static int
AudioTrack_Play( JNIEnv *env, audio_output_t *p_aout, size_t i_data_size,
size_t i_data_offset, bool b_force )
{
aout_sys_t *p_sys = p_aout->sys;
int i_ret;
// 根据写入数据类型来调用AudioTrack不同的接口,此处只分析【WRITE_BYTEARRAY】,其他类似处理
switch( p_sys->i_write_type )
{
case WRITE_BYTEARRAYV23:
i_ret = AudioTrack_PlayByteArrayV23( env, p_aout, i_data_size,
i_data_offset );
break;
case WRITE_BYTEBUFFER:
i_ret = AudioTrack_PlayByteBuffer( env, p_aout, i_data_size,
i_data_offset );
break;
case WRITE_SHORTARRAYV23:
i_ret = AudioTrack_PlayShortArrayV23( env, p_aout, i_data_size,
i_data_offset );
break;
case WRITE_BYTEARRAY:
// 读取byte类型数据
// 见下面的分析
i_ret = AudioTrack_PlayByteArray( env, p_aout, i_data_size,
i_data_offset, b_force );
break;
case WRITE_FLOATARRAY:
i_ret = AudioTrack_PlayFloatArray( env, p_aout, i_data_size,
i_data_offset );
break;
default:
vlc_assert_unreachable();
}
if( i_ret < 0 ) {// 发生错误
if( jfields.AudioManager.has_ERROR_DEAD_OBJECT
&& i_ret == jfields.AudioManager.ERROR_DEAD_OBJECT )
{// AudioTrack死掉了,重新创建并调用play方法等待输入数据播放
msg_Warn( p_aout, "ERROR_DEAD_OBJECT: "
"try recreating AudioTrack" );
if( ( i_ret = AudioTrack_Recreate( env, p_aout ) ) == 0 )
{
AudioTrack_Reset( env, p_aout );
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( p_aout, "Write failed: %s", str );
p_sys->b_error = true;
}
} else
// 播放【成功】,如下分析可能有异常但也返回了0,但此处不影响写入数据记录大小
p_sys->i_samples_written += BYTES_TO_FRAMES( i_ret );
return i_ret;
}
// [vlc/modules/audio_output/audiotrack.c]
/**
* 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.
*/
// 注译:AudioTrack_Thread线程中运行的非阻塞写入方法,
// 在当前position和AudioTrack position之间计算确保不用等待
// AudioTrack.write()方法执行完毕
static int
AudioTrack_PlayByteArray( JNIEnv *env, audio_output_t *p_aout,
size_t i_data_size, size_t i_data_offset,
bool b_force )
{
aout_sys_t *p_sys = p_aout->sys;
uint64_t i_samples;
uint64_t i_audiotrack_pos;
uint64_t i_samples_pending;
i_audiotrack_pos = AudioTrack_getPlaybackHeadPosition( env, p_aout );
assert( i_audiotrack_pos <= p_sys->i_samples_written );
if( i_audiotrack_pos > p_sys->i_samples_written )
{// 错误:播放音频sample位置不能比所有写入【已播放】sample的数据长度大
msg_Err( p_aout, "audiotrack position is ahead. Should NOT happen" );
p_sys->i_samples_written = 0;
p_sys->b_error = true;
// 但是默认返回0成功标识
return 0;
}
// java类AudioTrack缓冲区中待播放音频sample数大小:已写入【播放】sample数 减去 当前播放位置
i_samples_pending = p_sys->i_samples_written - i_audiotrack_pos;
// 在写入【此处即播放】之前,检查AudioTrack缓冲区是否已满
/* check if audiotrack buffer is not full before writing on it. */
if( b_force )
{// true时该处理表示,强制数据写入AudioTrack中,但可能导致阻塞
msg_Warn( p_aout, "Force write. It may block..." );
// 将 AudioTrack缓冲区中待播放音频sample数大小记录置为0
i_samples_pending = 0;
} else if( i_samples_pending >= p_sys->i_max_audiotrack_samples )
// 此条件成立为:当前java类AudioTrack缓冲区中待播放音频sample数大小已最大了,
// 即估算AudioTrack缓冲区已满,则啥事不做
return 0;
// 计算可读sample个数,此处取最小值【防止往AudioTrack缓冲区写入(i_data_size)数据大小时造成溢出】
i_samples = __MIN( p_sys->i_max_audiotrack_samples - i_samples_pending,
BYTES_TO_FRAMES( i_data_size ) );
// 重新计算可读数据大小【可能大小不变】
i_data_size = FRAMES_TO_BYTES( i_samples );
// 调用AudioTrack的write对应方法写入数据到AudioTrack进行播放
return JNI_AT_CALL_INT( write, p_sys->circular.u.p_bytearray,
i_data_offset, i_data_size );
}
本章节内容分析比较多,不过还算比较清晰,而这些分析的功能在前面章节中预留的关于audio输出播放调用实现基本已分析完毕。
vlc-android其他内容分析可见其他章节
标签:aout,Part,AudioTrack,sys,源码,env,data,size 来源: https://blog.csdn.net/u012430727/article/details/111368399