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,它提供了更现代、更精确的音频控制方式。

相关推荐
EasyDSS4 小时前
视频推流平台EasyDSS无人机推流直播技术在野外监测中的智能应用
音视频·无人机
韩立学长4 小时前
【开题答辩实录分享】以《智慧酒店管理——手机预订和住宿管理》为例进行选题答辩实录分享
android·java·后端
QT 小鲜肉4 小时前
【Linux命令大全】001.文件管理之chgrp命令(实操篇)
android·linux·运维·笔记
_李小白4 小时前
【Android FrameWork】第三十一天:Surface创建流程解析
android
柯南二号4 小时前
【大前端】【Android】 Android 手机上导出已安装 App 的 APK
android·智能手机
Just_Paranoid5 小时前
【Android UI】Android Tint 用法指南
android·ui·tint·porterduff·colorfilter
咕噜企业分发小米5 小时前
腾讯云游戏音视频方案如何助力初创公司提升用户粘性?
游戏·音视频·腾讯云
Android系统攻城狮5 小时前
Android16之交叉编译系统压力测试利器:stress-ng(二百六十六)
android·压力测试·android16·系统调试
杨忆5 小时前
导航栏左右拖动切换
android