Android 开机动画音频播放优化方案

Android 平台的开机动画实现位于 frameworks/base/cmds/bootanimation,其动画效果定制自由度较高,整体分为三个部分:enter(入场)、loop(循环)、exit(退场)。

播放逻辑如下:

  1. 先播放 enter 部分;
  2. 循环播放 loop 部分,直到 Home 界面显示完毕后,WMS 通过 SystemProperties.set("service.bootanim.exit", "1") 通知开机动画,此时会播放完当前 loop 的剩余部分;
  3. 最后播放 exit 部分。

由于系统开机时间不固定,loop 部分会循环多次以保证三部分动画的完美衔接。但当前开机动画对音频播放的支持存在明显缺陷,其音频播放逻辑位于 frameworks/base/cmds/bootanimation/audioplay.cpp,核心流程包括:

  • init:检测是否存在音频文件,若有则初始化音频播放器;
  • play:播放音频;
  • shutdown:开机动画退出时,停止音频播放。

现有问题分析

  1. 音频与动画匹配度低:动画分为三部分,但音频只能关联到第一部分且会一直播放至结束。由于开机时间不确定而音频时长固定,导致两者无法完全同步;
  2. 音频停止突兀:开机动画结束时,音频会被立即停止,体验生硬。

优化方案:音频渐弱淡出

针对上述问题,优化思路为:使用足够长的音频文件,在开机动画结束后,使音频播放音量逐渐减小至 0。

实现步骤

1. 计算音频文件播放时长

通过解析音频文件信息,计算其总播放时长,代码实现如下:

rust 复制代码
bool parseClipBuf(const uint8_t* clipBuf, int clipBufSize, const ChunkFormat** oChunkFormat,
                  const uint8_t** oSoundBuf, unsigned* oSoundBufSize) {
    '''''
        ALOGD("num_channels: %d, sample_rate: %d, bits_pre_sample: %d",
         (*oChunkFormat)->num_channels,
         (*oChunkFormat)->sample_rate,
         (*oChunkFormat)->bits_per_sample);
         audioLengthSeconds = (*oSoundBufSize) /
         ((*oChunkFormat)->num_channels * (*oChunkFormat)->sample_rate * (*oChunkFormat)->bits_per_sample / 8);
    ALOGD("audio length seconds %d", audioLengthSeconds);
    return true;
}

2. 实现音量渐弱线程(VolumeFadeOutThread)

新增线程类负责音量的逐步减小,直至音量为 0。该线程包含两个参数:timeout(淡出总时长)和 interval(音量调整间隔),并通过条件变量实现线程同步。播放结束后,通过 audioplay::setPlaying(false) 停止播放。

cpp 复制代码
static std::mutex g_playing_mutex;
static std::condition_variable g_playing_cv;
static bool g_playing = false;

class VolumeFadeOutThread : public Thread {
public:
    VolumeFadeOutThread(int timeout, int interval)
        : Thread(false),
          mTimeout(timeout),
          mInterval(interval) {}
private:
    virtual bool threadLoop() {
        ALOGD("Volume fade out timeout: %d, step %d", mTimeout, mInterval);
        SLresult result;
        int count = mTimeout / mInterval;
        SLmillibel volume = 0; // 0 is the max volume
        SLmillibel volume_step = 3000 / count; // -3000 is the min volume
        while (bqPlayerVolume!= nullptr && count > 0) {
            result = (*bqPlayerVolume)->SetVolumeLevel(bqPlayerVolume, volume);
            if (result != SL_RESULT_SUCCESS) {
                ALOGE("set volume failed with result %d", result);
                return false;
            }
            //ALOGD("set volume %d", volume);
            volume = volume - volume_step;
            count = count - 1;
            usleep(mInterval*1000);
        }
        ALOGD("Volume fade out end.");
        audioplay::setPlaying(false);
        return false;
    }

    int mTimeout;
    int mInterval;
};

3. 修改shutdown逻辑

在开机动画退出逻辑中,根据剩余时长或固定时长启动 VolumeFadeOutThread,并通过 g_playing_cv 等待音频淡出完成。

cpp 复制代码
    void shutdown() override {
        // we've finally played everything we're going to play
        // audioplay::setPlaying(false);
        ALOGD("shutdown");

        std::unique_lock<std::mutex> lock(g_playing_mutex);
        if (g_playing) {
            volumeFadeOutThread = new VolumeFadeOutThread(VOLUME_TIMEOUT, VOLUME_STEP_INTERVAL);
            volumeFadeOutThread->run("BootAnimation::VolumeFadeOutThread", PRIORITY_NORMAL);

            g_playing_cv.wait(lock);
            ALOGD("shutdown waiting");
        }
        ALOGD("shutdown destory");
        audioplay::destroy();
    };

4. 播放状态同步与通知

在播放状态变更时,通过 g_playing_cv.notify_one() 通知阻塞的 shutdown 线程继续执行退出逻辑。

cpp 复制代码
// set the playing state for the buffer queue audio player
void setPlaying(bool isPlaying) {
    if (!hasPlayer()) return;

    SLresult result;

    if (nullptr != bqPlayerPlay) {
        // set the player's state
        result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay,
            isPlaying ? SL_PLAYSTATE_PLAYING : SL_PLAYSTATE_STOPPED);
        std::unique_lock<std::mutex> lock(g_playing_mutex);
        g_playing = isPlaying;
        g_playing_cv.notify_one();
    }
}

最终效果

开机动画退出、Home 界面显示后,开机音效会继续播放 VOLUME_TIMEOUT 时长,且音量逐渐减小至 0,避免了音频的突兀停止,提升了开机体验。

相关推荐
whysqwhw2 小时前
安卓实现屏幕共享
android
深盾科技3 小时前
Kotlin Data Classes 快速上手
android·开发语言·kotlin
一条上岸小咸鱼3 小时前
Kotlin 基本数据类型(五):Array
android·前端·kotlin
whysqwhw3 小时前
Room&Paging
android
whysqwhw3 小时前
RecyclerView超长列表优化
android
Tiger_Hu3 小时前
Android系统日历探索
android
whysqwhw4 小时前
RecyclerView卡顿
android
whysqwhw4 小时前
RecyclerView 与 ListView 在性能优化方面
android
檀越剑指大厂7 小时前
容器化 Android 开发效率:cpolar 内网穿透服务优化远程协作流程
android