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

【十四】【vlc-android】aout音频输出模块源码实现分析【Part 2】该章节承接上一章节内容继续分析上一章节:3、Stop实现分析:【停止AudioTrack线程等相关操作】//[vlc/modules/audio_output/audiotrack.c]staticvoidStop(

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

该章节承接上一章节内容继续分析
上一章节:【十四】【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其他内容分析可见其他章节

今天的文章【十四】【vlc-android】aout音频输出模块源码实现分析【Part 2】分享到此就结束了,感谢您的阅读。

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

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注