上一篇文章介紹了btif層中A2DP角色管理以及狀態(tài)機(jī)肉盹,本文將介紹A2DP音頻相關(guān)的內(nèi)容罕扎,包括音頻流、解碼等沪么。
概述
音頻流向如下圖:
建立AVDTP協(xié)議連接之后,當(dāng)Source端需要播放時(shí)會(huì)通過AVDTP協(xié)議發(fā)送通過RTP格式封裝的音頻數(shù)據(jù)包锌半,收到數(shù)據(jù)包之后協(xié)議棧中選用連接時(shí)約定的編碼器以及參數(shù)進(jìn)行解碼禽车,解碼成PCM數(shù)據(jù)之后寫入到音頻模塊進(jìn)行播放。A2DP Profile連接建立過程如下:
- Source端會(huì)獲取Sink端支持幾個(gè)解碼器(SEP, Stream End Point)刊殉。
- Source端獲取每個(gè)SEP的配置(Capabilites)殉摔。
-
根據(jù)Source端支持的配置情況選擇一個(gè)配置設(shè)置給Sink端。
此時(shí)记焊,已經(jīng)為后面?zhèn)鬏斠纛l流做好了準(zhǔn)備钦勘,需要播放時(shí)即可發(fā)送音頻流進(jìn)行播放。
編解碼器
管理
在Fluoride協(xié)議棧中亚亲,解碼器配置彻采、解碼等的實(shí)現(xiàn)在stack/a2dp目錄中。A2DP Profile中規(guī)定藍(lán)牙播放必須支持SBC編解碼器捌归,其他的可以選肛响,現(xiàn)在常見的有編解碼器有:SBC、AAC惜索、APTx特笋、LDAC、LHDC等巾兆,除SBC和AAC在profile協(xié)議中有定義猎物,其他都都是自定義編解碼器。不同編解碼器由不同的人/廠商實(shí)現(xiàn)角塑,因此在A2DP協(xié)議上使用以下類型區(qū)分不同的編解碼器:
// 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蔫磨,具體細(xì)分在自定義的capabilities中用vendor id和codec id區(qū)分,capabilities結(jié)構(gòu)中圃伶,前面兩個(gè)字段為vendor id和codec id堤如,如下圖:
其中每個(gè)廠商的vendor id不能重復(fù),一個(gè)廠商多個(gè)codec的codec id也不能重復(fù)窒朋,如LDAC:
static const tA2DP_LDAC_CIE a2dp_ldac_source_caps = {
A2DP_LDAC_VENDOR_ID, // vendorId
A2DP_LDAC_CODEC_ID, // codecId
...
};
在代碼邏輯中為了區(qū)分不同角色的編解碼器搀罢,使用以下枚舉表示不同的codec(每個(gè)codec的index的值應(yīng)該是不同的):
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;
核心的類之間的關(guān)系如下:
A2dpCodecs
是管理編解碼器的核心數(shù)據(jù)結(jié)構(gòu),管理所有編輯器的配置侥猩,初始化A2dpCodecs
時(shí)榔至,根據(jù)btav_a2dp_codec_index_t
中定義的枚舉調(diào)用A2dpCodecConfig
中的工廠方法createCodec
創(chuàng)建各個(gè)編解碼器的配置,然后添加列表中欺劳,其中indexed_codecs_
是所有未被禁用的編解碼器的配置唧取,ordered_source_codecs_
是所有source使用的編碼器的配置铅鲤,ordered_sink_codecs_
是所有sink使用的解碼器的配置。
API接口
不同編解碼器的接口都不相同兵怯,為了調(diào)用方的接口統(tǒng)一彩匕,fluoride中定義了編解碼器的API,有新的編解碼器需要適配時(shí)只需要實(shí)現(xiàn)這些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);
// 刷新編碼器驼仪,丟掉讀取的數(shù)據(jù)包
void (*feeding_flush)(void);
// 獲取編碼間隔
uint64_t (*get_encoder_interval_ms)(void);
// 編碼并通過enqueue_callback上報(bào)編碼后數(shù)據(jù)幀
void (*send_frames)(uint64_t timestamp_us);
// 設(shè)置 A2DP 編碼器的傳輸隊(duì)列長度
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上報(bào)解碼后的數(shù)據(jù)幀
bool (*decode_packet)(BT_HDR* p_buf);
// 啟動(dòng)解碼器
void (*decoder_start)();
// 暫停解碼器
void (*decoder_suspend)();
// 配置解碼器
void (*decoder_configure)(const uint8_t* p_codec_info);
} tA2DP_DECODER_INTERFACE;
從源碼中可以看到,分別實(shí)現(xiàn)了sbc袜漩、aac绪爸、aptx、ldac的接口(具體的接口實(shí)現(xiàn)參考源碼)宙攻,并可以根據(jù)當(dāng)前A2DP連接協(xié)商的編解碼器類型獲取接口:
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相關(guān)的文件適配對(duì)應(yīng)的API桩引。
配置
同樣的匿垄,不同編碼器的配置不相同,管理起來十分混亂佑惠,因此在代碼中抽象出了A2dpCodecConfig
接口用于統(tǒng)一管理編解碼器的配置溢陪。在fluoride中有3中形式的編碼器配置萍虽,分別是btav_a2dp_codec_config_t
、tA2DP_SBC_CIE
形真、byte sequence
杉编,它們之間的關(guān)系如下圖:
圖中具體編碼器相關(guān)的都以SBC為例,其他編碼器將其中的sbc替換為相應(yīng)的編碼器名稱即可咆霜,如:
tA2DP_AAC_CIE
邓馒、A2DP_BuildInfoAac
、A2DP_ParseInfoAac
蛾坯,以此類推)光酣,往后不再特別說明
三種形式用于不同地方,btav_a2dp_codec_config_t
用于音頻相關(guān)配置以及更應(yīng)用層偿衰,它們主要關(guān)注音頻格式挂疆,如采樣率、位寬下翎、聲道數(shù)等信息,它們不需要關(guān)注具體編碼器相關(guān)的配置宝当;tA2DP_SBC_CIE
用于fluoride協(xié)議棧代碼中對(duì)編碼器配置的描述视事,以結(jié)構(gòu)體的形式表示可讀性更高更方便計(jì)算,它表示了具體某個(gè)編碼器的詳細(xì)的配置庆揩,每個(gè)編碼器的這個(gè)結(jié)構(gòu)體是不同的俐东;byte sequence
用于A2DP協(xié)議通信時(shí)對(duì)編解碼器配置的描述跌穗,每個(gè)編解碼器的字節(jié)序都在A2DP協(xié)議中有規(guī)定(具體可參考A2DP profile spec),代碼中通過A2DP_BuildInfoSbc
和A2DP_ParseInfoSbc
實(shí)現(xiàn)tA2DP_SBC_CIE
和byte sequence
的相互轉(zhuǎn)換虏辫, 在A2dpCodecConfigSbcBase::setCodecConfig
中實(shí)現(xiàn)btav_a2dp_codec_config_t
和tA2DP_SBC_CIE
的轉(zhuǎn)換蚌吸。
A2dpCodecConfig
的核心成員和方法如下:
class A2dpCodecConfig {
public:
// 根據(jù)codec_index創(chuàng)建對(duì)應(yīng)的codec實(shí)例
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和對(duì)端codec能力的交集
btav_a2dp_codec_config_t getCodecSelectableCapability();
// 獲取codec用戶配置
btav_a2dp_codec_config_t getCodecUserConfig();
// 獲取編碼器音頻配置
btav_a2dp_codec_config_t getCodecAudioConfig();
protected:
// 設(shè)置Source和Sink共同使用的codec的配置
virtual bool setCodecConfig(const uint8_t* p_peer_codec_info,
bool is_capability,
uint8_t* p_result_codec_config) = 0;
// 設(shè)置用戶首選的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;
// 設(shè)置對(duì)端設(shè)備的編解碼能力砌庄。
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_;
// 用戶配置羹唠。當(dāng)有選擇時(shí),這些值(如果已設(shè)置)將作為首選項(xiàng)使用娄昆。如果本地或遠(yuǎn)程設(shè)備不支持某一特定值佩微,該值將被忽略。
btav_a2dp_codec_config_t codec_user_config_;
// 協(xié)商的音頻配置
btav_a2dp_codec_config_t codec_audio_config_;
// 雙方協(xié)商的codec配置萌焰,A2DP協(xié)議中使用的配置
uint8_t ota_codec_config_[AVDT_CODEC_SIZE];
// 對(duì)端codec的能力
uint8_t ota_codec_peer_capability_[AVDT_CODEC_SIZE];
// 對(duì)端codec的配置
uint8_t ota_codec_peer_config_[AVDT_CODEC_SIZE];
};
在A2dpCodecConfig
中看到有config
和capability
相關(guān)成員哺眯,它們關(guān)系是:capability
表示codec可以支持的所有config
,以采樣率為例扒俯,codec可以支持44.1KHz奶卓、48KHz,因此采樣率的capability
是A2DP_SBC_IE_SAMP_FREQ_44 | BTAV_A2DP_CODEC_SAMPLE_RATE_48000
撼玄,但codec在編碼時(shí)夺姑,采樣率要么為44.1KHz(A2DP_SBC_IE_SAMP_FREQ_44
)要么是48KHz(BTAV_A2DP_CODEC_SAMPLE_RATE_48000
),兩個(gè)不能同時(shí)存在互纯。以下是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 */
};
參數(shù)協(xié)商
想要Source和Sink可以正確的編解碼數(shù)據(jù)瑟幕,那么需要雙方的編碼器類型和編碼器參數(shù)配置相同,因此在傳輸音頻流之前會(huì)協(xié)商編解碼器參數(shù)留潦,即Source獲取Sink的codec的capabilities只盹,然后計(jì)算出自己codec和對(duì)端codec capabilities的交集,最后從交集中選擇合適的config設(shè)置到Sink兔院。這個(gè)過程中會(huì)使用的一個(gè)關(guān)鍵的函數(shù)A2dpCodecConfig::setCodecConfig
殖卑,它的作用是選擇出Source和Sink共同的codec的配置,其步驟如下:
- 判斷用戶是否設(shè)置了參數(shù)坊萝,如有且本地和對(duì)端都支持這些參數(shù)孵稽,則使用該參數(shù),否則進(jìn)入第2步十偶;
- 本地和對(duì)端都設(shè)置了內(nèi)部默認(rèn)值菩鲜,則使用該參數(shù),否則進(jìn)入第3步惦积;
- 在本地和對(duì)段都支持的配置中選擇最佳匹配值接校,到此參數(shù)選擇完成。
參數(shù)選擇完成后需要同步更新編解碼器內(nèi)部參數(shù)狮崩,同時(shí)向應(yīng)用層以及媒體框架上報(bào)該參數(shù)蛛勉,如果用戶設(shè)置了參數(shù)鹿寻,則只會(huì)上報(bào)用戶設(shè)置的參數(shù),否則將上報(bào)所有可以選擇的參數(shù)(本地和對(duì)端都支持的配置)诽凌。
雖然每個(gè)codec協(xié)商參數(shù)時(shí)都是執(zhí)行這些步驟毡熏,但由于各個(gè)編碼器的配置不一樣,所以這個(gè)函數(shù)會(huì)在具體的codec中實(shí)現(xiàn)侣诵。下面是SBC中(在A2dpCodecConfigSbcBase::setCodecConfig
中實(shí)現(xiàn))該函數(shù)的程序流程:
流程圖中只畫出了一個(gè)config項(xiàng)痢法,實(shí)際上每個(gè)codec都有多個(gè)config項(xiàng),但它們執(zhí)行的流程完全一樣窝趣,因此在圖中沒有話出來疯暑。支持用戶設(shè)置的config(如:采樣率、位寬哑舒、聲道)會(huì)加入計(jì)算中妇拯,而其他不支持用戶設(shè)置的config(SBC中如:sub-band
bitpool等)則會(huì)直接跳過對(duì)用戶設(shè)置的計(jì)算。在這個(gè)函數(shù)中除了執(zhí)行上面的動(dòng)作外洗鸵,還會(huì)更新`A2dpCodecConfig`中定義的字段越锈,如:
-
ota_codec_config_
: 保存計(jì)算出codec參數(shù),即雙方編解碼器最終使用的配置膘滨。 -
ota_codec_peer_config_
: 保存對(duì)端的編解碼器的配置甘凭,如果setCodecConfig
中參數(shù)is_capability
為false
,這個(gè)字段會(huì)更新為參數(shù)p_peer_codec_info
中的內(nèi)容 -
ota_codec_peer_capability_
: 保存對(duì)端的編解碼器的能力火邓,如果setCodecConfig
中參數(shù)is_capability
為true
丹弱,這個(gè)字段會(huì)更新為參數(shù)p_peer_codec_info
中的內(nèi)容 - 其他的如:
codec_config_
、codec_capability_
铲咨、codec_selectable_capability_
都會(huì)在計(jì)算過程中更新
如果計(jì)算過程中發(fā)現(xiàn)參數(shù)不合法或者沒有共同的配置躲胳,則還原成計(jì)算前的配置。
除了setCodecConfig
這個(gè)公共的函數(shù)外纤勒,還有一個(gè)公共的函數(shù)A2dpCodecConfig::setCodecUserConfig
坯苹,它的內(nèi)部也是調(diào)用setCodecConfig
來實(shí)現(xiàn)的,函數(shù)流程如下:
基于這兩個(gè)函數(shù)摇天,A2dpCodecs
中分別實(shí)現(xiàn)了用于各個(gè)場景下設(shè)置codec參數(shù)的接口:A2dpCodecs::setCodecConfig
粹湃、A2dpCodecs::setSinkCodecConfig
、A2dpCodecs::setCodecUserConfig
泉坐、A2dpCodecs::setCodecAudioConfig
为鳄、A2dpCodecs::setCodecOtaConfig
,應(yīng)這幾個(gè)函數(shù)相對(duì)比較簡單腕让,這里就不一一贅述济赎,詳細(xì)實(shí)現(xiàn)可以參考源碼。