Android蓝牙协议栈fluoride(十) - 音乐播放与控制(3)
上一篇文章介绍了btif层中A2DP角色管理以及状态机,本文将介绍A2DP音频相关的内容,包括音频流、解码等。
概述
音频流向如下图:
建立AVDTP协议连接之后,当Source端需要播放时会通过AVDTP协议发送通过RTP格式封装的音频数据包,收到数据包之后协议栈中选用连接时约定的编码器以及参数进行解码,解码成PCM数据之后写入到音频模块进行播放。A2DP Profile连接建立过程如下:
- Source端会获取Sink端支持几个解码器(SEP, Stream End Point)。
- Source端获取每个SEP的配置(Capabilites)。
- 根据Source端支持的配置情况选择一个配置设置给Sink端。
此时,已经为后面传输音频流做好了准备,需要播放时即可发送音频流进行播放。
编解码器
管理
在Fluoride协议栈中,解码器配置、解码等的实现在stack/a2dp目录中。A2DP Profile中规定蓝牙播放必须支持SBC编解码器,其他的可以选,现在常见的有编解码器有:SBC、AAC、APTx、LDAC、LHDC等,除SBC和AAC在profile协议中有定义,其他都都是自定义编解码器。不同编解码器由不同的人/厂商实现,因此在A2DP协议上使用以下类型区分不同的编解码器:
// SBC codec
#define A2DP_MEDIA_CT_SBC 0x00
// AAC codec
#define A2DP_MEDIA_CT_AAC 0x02
// 自定义codec
#define A2DP_MEDIA_CT_NON_A2DP 0xFF
所有自定义的codec都是FF,具体细分在自定义的capabilities中用vendor id和codec id区分,capabilities结构中,前面两个字段为vendor id和codec id,如下图:
其中每个厂商的vendor id不能重复,一个厂商多个codec的codec id也不能重复,如LDAC:
static const tA2DP_LDAC_CIE a2dp_ldac_source_caps = {
A2DP_LDAC_VENDOR_ID, // vendorId
A2DP_LDAC_CODEC_ID, // codecId
...
};
在代码逻辑中为了区分不同角色的编解码器,使用以下枚举表示不同的codec(每个codec的index的值应该是不同的):
typedef enum {
BTAV_A2DP_CODEC_INDEX_SOURCE_MIN = 0,
// Add an entry for each source codec here.
// NOTE: The values should be same as those listed in the following file:
// BluetoothCodecConfig.java
BTAV_A2DP_CODEC_INDEX_SOURCE_SBC = 0,
BTAV_A2DP_CODEC_INDEX_SOURCE_AAC,
BTAV_A2DP_CODEC_INDEX_SOURCE_APTX,
BTAV_A2DP_CODEC_INDEX_SOURCE_APTX_HD,
BTAV_A2DP_CODEC_INDEX_SOURCE_LDAC,
BTAV_A2DP_CODEC_INDEX_SOURCE_MAX,
BTAV_A2DP_CODEC_INDEX_SINK_MIN = BTAV_A2DP_CODEC_INDEX_SOURCE_MAX,
// Add an entry for each sink codec here
BTAV_A2DP_CODEC_INDEX_SINK_SBC = BTAV_A2DP_CODEC_INDEX_SINK_MIN,
BTAV_A2DP_CODEC_INDEX_SINK_AAC,
BTAV_A2DP_CODEC_INDEX_SINK_LDAC,
BTAV_A2DP_CODEC_INDEX_SINK_MAX,
BTAV_A2DP_CODEC_INDEX_MIN = BTAV_A2DP_CODEC_INDEX_SOURCE_MIN,
BTAV_A2DP_CODEC_INDEX_MAX = BTAV_A2DP_CODEC_INDEX_SINK_MAX
} btav_a2dp_codec_index_t;
核心的类之间的关系如下:
A2dpCodecs
是管理编解码器的核心数据结构,管理所有编辑器的配置,初始化A2dpCodecs
时,根据btav_a2dp_codec_index_t
中定义的枚举调用A2dpCodecConfig
中的工厂方法createCodec
创建各个编解码器的配置,然后添加列表中,其中indexed_codecs_
是所有未被禁用的编解码器的配置,ordered_source_codecs_
是所有source使用的编码器的配置,ordered_sink_codecs_
是所有sink使用的解码器的配置。
API接口
不同编解码器的接口都不相同,为了调用方的接口统一,fluoride中定义了编解码器的API,有新的编解码器需要适配时只需要实现这些API即可,API原型定义如下:
// 编码器API
typedef struct {
// 编码器初始化
void (*encoder_init)(const tA2DP_ENCODER_INIT_PEER_PARAMS* p_peer_params, A2dpCodecConfig* a2dp_codec_config, a2dp_source_read_callback_t read_callback, a2dp_source_enqueue_callback_t enqueue_callback);
// 清理编码器资源
void (*encoder_cleanup)(void);
// 重置编码器
void (*feeding_reset)(void);
// 刷新编码器,丢掉读取的数据包
void (*feeding_flush)(void);
// 获取编码间隔
uint64_t (*get_encoder_interval_ms)(void);
// 编码并通过enqueue_callback上报编码后数据帧
void (*send_frames)(uint64_t timestamp_us);
// 设置 A2DP 编码器的传输队列长度
void (*set_transmit_queue_length)(size_t transmit_queue_length);
} tA2DP_ENCODER_INTERFACE;
//解码器API
typedef struct {
// 初始化解码器
bool (*decoder_init)(decoded_data_callback_t decode_callback);
// 清理解码器
void (*decoder_cleanup)();
// 解码并通过decode_callback上报解码后的数据帧
bool (*decode_packet)(BT_HDR* p_buf);
// 启动解码器
void (*decoder_start)();
// 暂停解码器
void (*decoder_suspend)();
// 配置解码器
void (*decoder_configure)(const uint8_t* p_codec_info);
} tA2DP_DECODER_INTERFACE;
从源码中可以看到,分别实现了sbc、aac、aptx、ldac的接口(具体的接口实现参考源码),并可以根据当前A2DP连接协商的编解码器类型获取接口:
const tA2DP_ENCODER_INTERFACE* A2DP_GetEncoderInterface(const uint8_t* p_codec_info) {
tA2DP_CODEC_TYPE codec_type = A2DP_GetCodecType(p_codec_info);
LOG_VERBOSE("%s: codec_type = 0x%x", __func__, codec_type);
switch (codec_type) {
case A2DP_MEDIA_CT_SBC:
return A2DP_GetEncoderInterfaceSbc(p_codec_info);
#if !defined(EXCLUDE_NONSTANDARD_CODECS)
case A2DP_MEDIA_CT_AAC:
return A2DP_GetEncoderInterfaceAac(p_codec_info);
case A2DP_MEDIA_CT_NON_A2DP:
return A2DP_VendorGetEncoderInterface(p_codec_info);
#endif
default:
break;
}
LOG_ERROR("%s: unsupported codec type 0x%x", __func__, codec_type);
return NULL;
}
const tA2DP_DECODER_INTERFACE* A2DP_GetDecoderInterface(const uint8_t* p_codec_info) {
tA2DP_CODEC_TYPE codec_type = A2DP_GetCodecType(p_codec_info);
LOG_VERBOSE("%s: codec_type = 0x%x", __func__, codec_type);
switch (codec_type) {
case A2DP_MEDIA_CT_SBC:
return A2DP_GetDecoderInterfaceSbc(p_codec_info);
#if !defined(EXCLUDE_NONSTANDARD_CODECS)
case A2DP_MEDIA_CT_AAC:
return A2DP_GetDecoderInterfaceAac(p_codec_info);
case A2DP_MEDIA_CT_NON_A2DP:
return A2DP_VendorGetDecoderInterface(p_codec_info);
#endif
default:
break;
}
如果有新增编解码器的需求,可以在stack/a2dp目录中参考vendor相关的文件适配对应的API。
配置
同样的,不同编码器的配置不相同,管理起来十分混乱,因此在代码中抽象出了A2dpCodecConfig
接口用于统一管理编解码器的配置。在fluoride中有3中形式的编码器配置,分别是btav_a2dp_codec_config_t
、tA2DP_SBC_CIE
、byte sequence
,它们之间的关系如下图:
图中具体编码器相关的都以SBC为例,其他编码器将其中的sbc替换为相应的编码器名称即可,如:
tA2DP_AAC_CIE
、A2DP_BuildInfoAac
、A2DP_ParseInfoAac
,以此类推),往后不再特别说明
三种形式用于不同地方,btav_a2dp_codec_config_t
用于音频相关配置以及更应用层,它们主要关注音频格式,如采样率、位宽、声道数等信息,它们不需要关注具体编码器相关的配置;tA2DP_SBC_CIE
用于fluoride协议栈代码中对编码器配置的描述,以结构体的形式表示可读性更高更方便计算,它表示了具体某个编码器的详细的配置,每个编码器的这个结构体是不同的;byte sequence
用于A2DP协议通信时对编解码器配置的描述,每个编解码器的字节序都在A2DP协议中有规定(具体可参考A2DP profile spec),代码中通过A2DP_BuildInfoSbc
和A2DP_ParseInfoSbc
实现tA2DP_SBC_CIE
和byte sequence
的相互转换, 在A2dpCodecConfigSbcBase::setCodecConfig
中实现btav_a2dp_codec_config_t
和tA2DP_SBC_CIE
的转换。
A2dpCodecConfig
的核心成员和方法如下:
class A2dpCodecConfig {
public:
// 根据codec_index创建对应的codec实例
static A2dpCodecConfig* createCodec(btav_a2dp_codec_index_t codec_index,
btav_a2dp_codec_priority_t codec_priority = BTAV_A2DP_CODEC_PRIORITY_DEFAULT);
// 获取codec的特定配置
bool getCodecSpecificConfig(tBT_A2DP_OFFLOAD* p_a2dp_offload);
// 获取codec的配置
btav_a2dp_codec_config_t getCodecConfig();
// 获取codec的能力
btav_a2dp_codec_config_t getCodecCapability();
// 获取本地编码器的能力
btav_a2dp_codec_config_t getCodecLocalCapability();
// 获取codec可供选择的能力,本地codec和对端codec能力的交集
btav_a2dp_codec_config_t getCodecSelectableCapability();
// 获取codec用户配置
btav_a2dp_codec_config_t getCodecUserConfig();
// 获取编码器音频配置
btav_a2dp_codec_config_t getCodecAudioConfig();
protected:
// 设置Source和Sink共同使用的codec的配置
virtual bool setCodecConfig(const uint8_t* p_peer_codec_info,
bool is_capability,
uint8_t* p_result_codec_config) = 0;
// 设置用户首选的codec的配置
virtual bool setCodecUserConfig(
const btav_a2dp_codec_config_t& codec_user_config,
const btav_a2dp_codec_config_t& codec_audio_config,
const tA2DP_ENCODER_INIT_PEER_PARAMS* p_peer_params,
const uint8_t* p_peer_codec_info, bool is_capability,
uint8_t* p_result_codec_config, bool* p_restart_input,
bool* p_restart_output, bool* p_config_updated);
// 用用户首选编解码器配置更新编码器
virtual bool updateEncoderUserConfig(
const tA2DP_ENCODER_INIT_PEER_PARAMS* p_peer_params,
bool* p_restart_input, bool* p_restart_output,
bool* p_config_updated) = 0;
// 设置对端设备的编解码能力。
virtual bool setPeerCodecCapabilities(
const uint8_t* p_peer_codec_capabilities) = 0;
// codec index
const btav_a2dp_codec_index_t codec_index_;
// codec配置
btav_a2dp_codec_config_t codec_config_;
// codec能力
btav_a2dp_codec_config_t codec_capability_;
// 本地codec的能力
btav_a2dp_codec_config_t codec_local_capability_;
// codec可供选择的能力
btav_a2dp_codec_config_t codec_selectable_capability_;
// 用户配置。当有选择时,这些值(如果已设置)将作为首选项使用。如果本地或远程设备不支持某一特定值,该值将被忽略。
btav_a2dp_codec_config_t codec_user_config_;
// 协商的音频配置
btav_a2dp_codec_config_t codec_audio_config_;
// 双方协商的codec配置,A2DP协议中使用的配置
uint8_t ota_codec_config_[AVDT_CODEC_SIZE];
// 对端codec的能力
uint8_t ota_codec_peer_capability_[AVDT_CODEC_SIZE];
// 对端codec的配置
uint8_t ota_codec_peer_config_[AVDT_CODEC_SIZE];
};
在A2dpCodecConfig
中看到有config
和capability
相关成员,它们关系是:capability
表示codec可以支持的所有config
,以采样率为例,codec可以支持44.1KHz、48KHz,因此采样率的capability
是A2DP_SBC_IE_SAMP_FREQ_44 | BTAV_A2DP_CODEC_SAMPLE_RATE_48000
,但codec在编码时,采样率要么为44.1KHz(A2DP_SBC_IE_SAMP_FREQ_44
)要么是48KHz(BTAV_A2DP_CODEC_SAMPLE_RATE_48000
),两个不能同时存在。以下是SBC 的capability和config:
/* SBC Source codec capabilities */
static const tA2DP_SBC_CIE a2dp_sbc_source_caps = {
(A2DP_SBC_IE_SAMP_FREQ_44), /* samp_freq */
(A2DP_SBC_IE_CH_MD_MONO | A2DP_SBC_IE_CH_MD_JOINT), /* ch_mode */
(A2DP_SBC_IE_BLOCKS_16 | A2DP_SBC_IE_BLOCKS_12 | A2DP_SBC_IE_BLOCKS_8 |
A2DP_SBC_IE_BLOCKS_4), /* block_len */
A2DP_SBC_IE_SUBBAND_8, /* num_subbands */
A2DP_SBC_IE_ALLOC_MD_L, /* alloc_method */
A2DP_SBC_IE_MIN_BITPOOL, /* min_bitpool */
A2DP_SBC_MAX_BITPOOL, /* max_bitpool */
BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16 /* bits_per_sample */
};
/* SBC Sink codec capabilities */
static const tA2DP_SBC_CIE a2dp_sbc_sink_caps = {
(A2DP_SBC_IE_SAMP_FREQ_48 | A2DP_SBC_IE_SAMP_FREQ_44), /* samp_freq */
(A2DP_SBC_IE_CH_MD_MONO | A2DP_SBC_IE_CH_MD_STEREO | A2DP_SBC_IE_CH_MD_JOINT | A2DP_SBC_IE_CH_MD_DUAL), /* ch_mode */
(A2DP_SBC_IE_BLOCKS_16 | A2DP_SBC_IE_BLOCKS_12 | A2DP_SBC_IE_BLOCKS_8 | A2DP_SBC_IE_BLOCKS_4), /* block_len */
(A2DP_SBC_IE_SUBBAND_4 | A2DP_SBC_IE_SUBBAND_8), /* num_subbands */
(A2DP_SBC_IE_ALLOC_MD_L | A2DP_SBC_IE_ALLOC_MD_S), /* alloc_method */
A2DP_SBC_IE_MIN_BITPOOL, /* min_bitpool */
A2DP_SBC_MAX_BITPOOL, /* max_bitpool */
BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16 /* bits_per_sample */
};
/* Default SBC codec configuration */
const tA2DP_SBC_CIE a2dp_sbc_default_config = {
A2DP_SBC_IE_SAMP_FREQ_44, /* samp_freq */
A2DP_SBC_IE_CH_MD_JOINT, /* ch_mode */
A2DP_SBC_IE_BLOCKS_16, /* block_len */
A2DP_SBC_IE_SUBBAND_8, /* num_subbands */
A2DP_SBC_IE_ALLOC_MD_L, /* alloc_method */
A2DP_SBC_IE_MIN_BITPOOL, /* min_bitpool */
A2DP_SBC_MAX_BITPOOL, /* max_bitpool */
BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16 /* bits_per_sample */
};
参数协商
想要Source和Sink可以正确的编解码数据,那么需要双方的编码器类型和编码器参数配置相同,因此在传输音频流之前会协商编解码器参数,即Source获取Sink的codec的capabilities,然后计算出自己codec和对端codec capabilities的交集,最后从交集中选择合适的config设置到Sink。这个过程中会使用的一个关键的函数A2dpCodecConfig::setCodecConfig
,它的作用是选择出Source和Sink共同的codec的配置,其步骤如下:
- 判断用户是否设置了参数,如有且本地和对端都支持这些参数,则使用该参数,否则进入第2步;
- 本地和对端都设置了内部默认值,则使用该参数,否则进入第3步;
- 在本地和对段都支持的配置中选择最佳匹配值,到此参数选择完成。
参数选择完成后需要同步更新编解码器内部参数,同时向应用层以及媒体框架上报该参数,如果用户设置了参数,则只会上报用户设置的参数,否则将上报所有可以选择的参数(本地和对端都支持的配置)。
虽然每个codec协商参数时都是执行这些步骤,但由于各个编码器的配置不一样,所以这个函数会在具体的codec中实现。下面是SBC中(在A2dpCodecConfigSbcBase::setCodecConfig
中实现)该函数的程序流程:
流程图中只画出了一个config项,实际上每个codec都有多个config项,但它们执行的流程完全一样,因此在图中没有话出来。支持用户设置的config(如:采样率、位宽、声道)会加入计算中,而其他不支持用户设置的config(SBC中如:sub-band
bitpool等)则会直接跳过对用户设置的计算。在这个函数中除了执行上面的动作外,还会更新A2dpCodecConfig
中定义的字段,如:
ota_codec_config_
: 保存计算出codec参数,即双方编解码器最终使用的配置。ota_codec_peer_config_
: 保存对端的编解码器的配置,如果setCodecConfig
中参数is_capability
为false
,这个字段会更新为参数p_peer_codec_info
中的内容ota_codec_peer_capability_
: 保存对端的编解码器的能力,如果setCodecConfig
中参数is_capability
为true
,这个字段会更新为参数p_peer_codec_info
中的内容- 其他的如:
codec_config_
、codec_capability_
、codec_selectable_capability_
都会在计算过程中更新
如果计算过程中发现参数不合法或者没有共同的配置,则还原成计算前的配置。
除了setCodecConfig
这个公共的函数外,还有一个公共的函数A2dpCodecConfig::setCodecUserConfig
,它的内部也是调用setCodecConfig
来实现的,函数流程如下:
基于这两个函数,A2dpCodecs
中分别实现了用于各个场景下设置codec参数的接口:A2dpCodecs::setCodecConfig
、A2dpCodecs::setSinkCodecConfig
、A2dpCodecs::setCodecUserConfig
、A2dpCodecs::setCodecAudioConfig
、A2dpCodecs::setCodecOtaConfig
,应这几个函数相对比较简单,这里就不一一赘述,详细实现可以参考源码。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!