Android-Audio-Usage 与 StreamType的区别

Usage 和 StreamType 是 Android 音频系统中两代不同的音频分类机制 。简单来说:StreamType 是旧的、简单的分类,音量流类型,Usage 是新的、更精细的分类,音频场景类型

核心区别总结

维度 StreamType (旧) AudioAttributes.Usage (新)
引入时间 Android 2.3 及更早 Android 5.0 (Lollipop)
设计理念 基于播放流类型 基于音频使用场景
数量 固定的 7-8 种类型 灵活扩展,标准 10+ 种
音量控制 1:1 绑定到音量条 可灵活映射到音量条
优先级 隐含在类型中 显式优先级控制
扩展性 固定,不可扩展 可自定义扩展
推荐使用 已废弃,仅兼容旧代码 Android 5.0+ 推荐

详细对比

1. 定义和范围

StreamType(有限、固定)
java 复制代码
// AudioManager.java
public static final int STREAM_VOICE_CALL = 0;      // 通话
public static final int STREAM_SYSTEM = 1;         // 系统声音
public static final int STREAM_RING = 2;           // 铃声
public static final int STREAM_MUSIC = 3;          // 媒体(音乐、视频、游戏)
public static final int STREAM_ALARM = 4;          // 闹钟
public static final int STREAM_NOTIFICATION = 5;    // 通知
public static final int STREAM_DTMF = 8;           // 双音多频
public static final int STREAM_ACCESSIBILITY = 10;  // 无障碍
// 总共只有 8 种,且不可扩展
Usage(丰富、灵活)
java 复制代码
// AudioAttributes.java
public static final int USAGE_UNKNOWN = 0;
public static final int USAGE_MEDIA = 1;                    // 媒体
public static final int USAGE_VOICE_COMMUNICATION = 2;     // 语音通话
public static final int USAGE_VOICE_COMMUNICATION_SIGNALLING = 3;  // 通话信令
public static final int USAGE_ALARM = 4;                    // 闹钟
public static final int USAGE_NOTIFICATION = 5;            // 通知
public static final int USAGE_NOTIFICATION_TELEPHONY_RINGTONE = 6;  // 电话铃声
public static final int USAGE_NOTIFICATION_COMMUNICATION_REQUEST = 7;  // 通信请求
public static final int USAGE_NOTIFICATION_COMMUNICATION_INSTANT = 8;  // 即时消息
public static final int USAGE_NOTIFICATION_COMMUNICATION_DELAYED = 9;  // 延迟消息
public static final int USAGE_NOTIFICATION_EVENT = 10;      // 事件
public static final int USAGE_ASSISTANCE_ACCESSIBILITY = 11;  // 无障碍
public static final int USAGE_ASSISTANCE_NAVIGATION_GUIDANCE = 12;  // 导航
public static final int USAGE_ASSISTANCE_SONIFICATION = 13;  // UI提示音
public static final int USAGE_GAME = 14;                   // 游戏
public static final int USAGE_VIRTUAL_SOURCE = 15;         // 虚拟声源
public static final int USAGE_ASSISTANT = 16;              // 语音助手
// 还可由厂商自定义扩展

2. 设计哲学差异

StreamType:"我在播放什么类型的音频流?"
  • 关注音频的技术来源类型
  • 简单但粗粒度,难以满足复杂场景
  • 一个 StreamType 包含多种语义场景
java 复制代码
// ❌ StreamType 的问题:过于笼统
// STREAM_MUSIC 包含了:
// 1. 背景音乐
// 2. 游戏音效  
// 3. 视频音频
// 4. 播客
// 5. UI 音效
// 系统无法区分这些场景的不同需求
Usage:"这段音频的用途和场景是什么?"
  • 关注音频的使用场景和目的
  • 细粒度,可精确描述意图
  • 系统可做更智能的决策
java 复制代码
// ✅ Usage 的精确描述
.setUsage(USAGE_GAME)              // 游戏音效:可能启用低延迟
.setUsage(USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)  // 导航:可打断音乐
.setUsage(USAGE_NOTIFICATION_EVENT)  // 日历提醒:需要确保用户听到
.setUsage(USAGE_ASSISTANT)          // 语音助手:需要特殊音频路由

3. 实际使用方式对比

StreamType 方式(旧 API)
java 复制代码
// MediaPlayer
MediaPlayer player = new MediaPlayer();
player.setAudioStreamType(AudioManager.STREAM_MUSIC);  // 设置流类型
player.setDataSource("music.mp3");
player.prepare();
player.start();

// 设置音量(全局影响)
AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
am.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);

// SoundPool
SoundPool sp = new SoundPool.Builder()
    .setMaxStreams(10)
    .setAudioAttributes(null)  // ❌ 老方式,用不了 Usage
    .build();
// SoundPool 默认只能用 STREAM_MUSIC
AudioAttributes(新 API,包含 Usage)
java 复制代码
// 1. 创建 AudioAttributes(包含 usage 和其他属性)
AudioAttributes attributes = new AudioAttributes.Builder()
    .setUsage(AudioAttributes.USAGE_GAME)           // 使用场景:游戏
    .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)  // 内容类型:音效
    .setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED)  // 标志:强制可听
    .setHapticChannelsMuted(false)                  // 触觉通道
    .build();

// 2. MediaPlayer(推荐方式)
MediaPlayer player = new MediaPlayer();
player.setAudioAttributes(attributes);  // ✅ 设置完整的音频属性
player.setDataSource("game_sound.mp3");
player.prepare();
player.start();

// 3. AudioTrack
AudioTrack track = new AudioTrack.Builder()
    .setAudioAttributes(attributes)      // ✅
    .setAudioFormat(format)
    .setBufferSizeInBytes(bufferSize)
    .setTransferMode(AudioTrack.MODE_STREAM)
    .build();

// 4. SoundPool(Android 5.0+)
SoundPool sp = new SoundPool.Builder()
    .setMaxStreams(10)
    .setAudioAttributes(attributes)      // ✅ 终于可以指定 usage 了!
    .build();

// 5. 音量控制(更精确)
// 不再直接控制 STREAM_MUSIC,而是通过 usage
int volume = audioManager.getStreamVolume(
    AudioAttributes.usageToStream(AudioAttributes.USAGE_GAME)
);

4. 映射关系

系统内部会将 Usage 映射到 StreamType 以保持兼容性:

java 复制代码
// frameworks/base/media/java/android/media/AudioAttributes.java
public static int usageToStream(int usage) {
    switch (usage) {
        case USAGE_UNKNOWN:
            return AudioManager.STREAM_MUSIC;
            
        case USAGE_MEDIA:
        case USAGE_GAME:
        case USAGE_ASSISTANCE_ACCESSIBILITY:
        case USAGE_ASSISTANCE_NAVIGATION_GUIDANCE:  // 导航映射到音乐流
            return AudioManager.STREAM_MUSIC;
            
        case USAGE_ASSISTANCE_SONIFICATION:
            return AudioManager.STREAM_SYSTEM;
            
        case USAGE_VOICE_COMMUNICATION:
            return AudioManager.STREAM_VOICE_CALL;
            
        case USAGE_VOICE_COMMUNICATION_SIGNALLING:
            return AudioManager.STREAM_DTMF;
            
        case USAGE_ALARM:
            return AudioManager.STREAM_ALARM;
            
        case USAGE_NOTIFICATION:
        case USAGE_NOTIFICATION_COMMUNICATION_REQUEST:
        case USAGE_NOTIFICATION_COMMUNICATION_INSTANT:
        case USAGE_NOTIFICATION_COMMUNICATION_DELAYED:
        case USAGE_NOTIFICATION_EVENT:
        case USAGE_NOTIFICATION_TELEPHONY_RINGTONE:
            return AudioManager.STREAM_NOTIFICATION;
            
        default:
            return AudioManager.STREAM_MUSIC;
    }
}

注意 :这个映射是有损的,丢失了 Usage 的精细信息。

5. 音频焦点处理差异

StreamType 时代的焦点
java 复制代码
// 旧方式:基于流类型的简单焦点
AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
am.requestAudioFocus(
    focusChangeListener,
    AudioManager.STREAM_MUSIC,      // 只能指定流类型
    AudioManager.AUDIOFOCUS_GAIN
);
// 问题:所有 STREAM_MUSIC 都被同样对待
// 游戏、音乐、视频、导航播报无法区分优先级
AudioAttributes 时代的焦点
java 复制代码
// 新方式:基于 Usage 的精确焦点
AudioAttributes attributes = new AudioAttributes.Builder()
    .setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)  // 明确是导航
    .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
    .build();

AudioFocusRequest focusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK)
    .setAudioAttributes(attributes)  // ✅ 携带详细的音频属性
    .setAcceptsDelayedFocusGain(true)
    .setOnAudioFocusChangeListener(focusChangeListener)
    .setWillPauseWhenDucked(false)
    .build();

int result = audioManager.requestAudioFocus(focusRequest);
// 系统可以:
// 1. 知道这是导航语音,可适当打断音乐
// 2. 但不是紧急通知,不需要完全暂停
// 3. 可采用闪避(ducking)而非暂停

6. 音频路由策略差异

StreamType 路由
java 复制代码
// 简单路由:基于设备类型
if (streamType == AudioManager.STREAM_VOICE_CALL) {
    routeTo(DEVICE_EARPIECE);  // 通话只能路由到听筒
} else if (streamType == AudioManager.STREAM_MUSIC) {
    routeTo(DEVICE_SPEAKER);   // 音乐只能路由到扬声器
}
// 不智能,无法处理复杂场景
Usage 路由
java 复制代码
// 智能路由:基于使用场景
switch (usage) {
    case USAGE_VOICE_COMMUNICATION:
        // 语音通话:优先蓝牙耳机,其次听筒
        if (bluetoothAvailable) routeTo(BLUETOOTH_HEADSET);
        else routeTo(EARPIECE);
        break;
        
    case USAGE_MEDIA:
        // 媒体播放:根据上下文选择
        if (headphonesConnected) routeTo(HEADPHONES);
        else if (carMode) routeTo(CAR_SPEAKERS);
        else routeTo(SPEAKER);
        break;
        
    case USAGE_ASSISTANCE_NAVIGATION_GUIDANCE:
        // 导航:车载系统特殊处理
        if (inCar) routeTo(CAR_FRONT_SPEAKERS);
        else routeTo(SPEAKER);
        break;
        
    case USAGE_ASSISTANT:
        // 语音助手:可能用智能音箱
        if (smartSpeakerAvailable) routeTo(SMART_SPEAKER);
        else routeTo(DEVICE_DEFAULT);
        break;
}

7. 音量控制差异

StreamType 音量控制
java 复制代码
// 7个独立的音量条
AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);

// 媒体音量(控制音乐、游戏、视频、导航等所有媒体)
am.setStreamVolume(AudioManager.STREAM_MUSIC, mediaVolume, 0);

// 铃声音量
am.setStreamVolume(AudioManager.STREAM_RING, ringVolume, 0);

// 闹钟音量
am.setStreamVolume(AudioManager.STREAM_ALARM, alarmVolume, 0);

// 通话音量
am.setStreamVolume(AudioManager.STREAM_VOICE_CALL, callVolume, 0);

// 问题:导航音量调整会影响音乐音量!
Usage 音量控制
java 复制代码
// 更细粒度的音量控制(概念上)
// 注意:Android 仍然通过 StreamType 控制音量
// 但通过 usage 到 stream 的映射,可以实现更合理控制

// 系统内部可以根据 usage 做特殊处理
if (usage == USAGE_ASSISTANCE_NAVIGATION_GUIDANCE) {
    // 导航语音:可以在媒体音量基础上做偏移
    float navigationGain = calculateNavigationGain(mediaVolume);
    applyVolume(navigationGain);
} else if (usage == USAGE_GAME) {
    // 游戏音效:可单独记忆音量
    int gameVolume = getSavedVolume(USAGE_GAME);
    am.setStreamVolume(AudioManager.STREAM_MUSIC, gameVolume, 0);
}

迁移指南

从 StreamType 迁移到 Usage

java 复制代码
// ❌ 旧的 StreamType 方式(已废弃)
player.setAudioStreamType(AudioManager.STREAM_MUSIC);
am.requestAudioFocus(listener, AudioManager.STREAM_MUSIC, 
                     AudioManager.AUDIOFOCUS_GAIN);

// ✅ 新的 AudioAttributes 方式
AudioAttributes attributes = new AudioAttributes.Builder()
    .setUsage(AudioAttributes.USAGE_MEDIA)  // 对应 STREAM_MUSIC
    .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
    .build();

player.setAudioAttributes(attributes);

AudioFocusRequest request = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
    .setAudioAttributes(attributes)
    .setOnAudioFocusChangeListener(listener)
    .build();

am.requestAudioFocus(request);

映射参考表

StreamType 建议的 Usage 说明
STREAM_MUSIC USAGE_MEDIA 普通媒体播放
STREAM_MUSIC USAGE_GAME 游戏音效
STREAM_MUSIC USAGE_ASSISTANCE_NAVIGATION_GUIDANCE 导航语音
STREAM_ALARM USAGE_ALARM 闹钟
STREAM_RING USAGE_NOTIFICATION_TELEPHONY_RINGTONE 电话铃声
STREAM_NOTIFICATION USAGE_NOTIFICATION 普通通知
STREAM_NOTIFICATION USAGE_NOTIFICATION_EVENT 事件提醒
STREAM_VOICE_CALL USAGE_VOICE_COMMUNICATION 语音通话
STREAM_SYSTEM USAGE_ASSISTANCE_SONIFICATION 系统音效
STREAM_ACCESSIBILITY USAGE_ASSISTANCE_ACCESSIBILITY 无障碍

实际建议

什么时候用 StreamType?

  • ✅ 维护遗留代码(Android 4.x 及更早)
  • ✅ 简单的音频播放需求
  • ✅ 不需要精细的音频策略控制

什么时候必须用 AudioAttributes.Usage?

  • Android 5.0+ 的新应用
  • ✅ 需要细粒度音频控制
  • ✅ 需要精确的音频焦点处理
  • ✅ 车载、电视、手表等特殊设备
  • ✅ 语音助手、导航、游戏等特殊场景
  • ✅ 需要控制音频路由(如强制蓝牙输出)

实际代码示例对比

java 复制代码
// 场景:游戏应用播放音效

// ❌ 旧方式(问题:无法区分游戏和音乐)
soundPool = new SoundPool(10, AudioManager.STREAM_MUSIC, 0);
// 用户调整媒体音量时,游戏音效和音乐一起被调整

// ✅ 新方式(正确:明确标记为游戏)
AudioAttributes gameAttrs = new AudioAttributes.Builder()
    .setUsage(AudioAttributes.USAGE_GAME)
    .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
    .build();

soundPool = new SoundPool.Builder()
    .setMaxStreams(10)
    .setAudioAttributes(gameAttrs)  // 明确是游戏
    .build();
// 系统可以:
// 1. 启用低延迟模式
// 2. 应用游戏音效优化
// 3. 在音频焦点冲突时合理处理

总结

StreamType 是"流类型"思维,Usage 是"使用场景"思维

  • StreamType 回答:"这是什么类型的音频流?"
  • Usage 回答:"这段音频是用来干什么的?"

迁移到 Usage 的优势

  1. 更精细的控制:可区分音乐、游戏、导航、通知等
  2. 更智能的路由:系统可根据场景选择最佳输出设备
  3. 更合理的焦点:不同场景有不同优先级
  4. 更好的兼容性:适应车载、电视、穿戴设备等新场景
  5. 未来扩展性:可自定义新的 usage 类型

对于新应用,强烈建议使用 AudioAttributes.Usage,它提供了更现代、更精确的音频控制方式。

相关推荐
阿巴斯甜1 天前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker1 天前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq95271 天前
Andorid Google 登录接入文档
android
黄林晴1 天前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab2 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿2 天前
Android MediaPlayer 笔记
android
Jony_2 天前
Android 启动优化方案
android
阿巴斯甜2 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇2 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_2 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android