2025年【十四】【vlc-android】aout音频输出模块源码实现分析【Part 2】

【十四】【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

大家好,我是讯享网,很高兴认识大家。

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() ) { 
    // 此处功能:计算当前数据帧时间差即已经过去多长时间了,再计算当前时间差对应的帧数据个数差 // 如此计算已播放帧数据对应的时长【已播放样本个数/采样率即可得已播放的时长】 // 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其他内容分析可见其他章节

小讯
上一篇 2025-04-02 09:14
下一篇 2025-02-23 17:44

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/45837.html