關于音頻的輸出通路,可以有多重選擇:HDMI-out,喇叭,耳機,LINE-in,USB聲霸卡,藍牙等,切換不同的通路音頻就從不同的通路輸出或者錄入,這裡主要以HDMIin為例來簡單說一下相關AUDIO通路方面的内容。
RK3399 HDMI IN聲霸卡通路選擇
目前RK3399有三路i2s通道,HDMIOUT音頻通路晶片内置為i2s2。目前RK3399 開發闆上的音頻晶片還有藍牙、rt5651、tc358749,音頻通路配置如下:
RK3399 I2S2 沒有使用來作為藍牙通話,則可以 TC358749 I2S 接口接到 RK3399的單獨一個 I2S 上(I2S/PCM 不能跟其他 I2S 裝置共用,否則造成 I2S 信号的幹擾,聲音有雜音),另外的 I2S 接口接 codec 通過功放輸出 ANDROID 系統聲音。
RK3399 HDMI IN 聲霸卡通路配置
通路1:HDMIIn –> RK3399 I2S1- > RK3399 HDMI TX -> HDMI 電視機
通路2:HDMIIn –> RK3399 I2S1- > RK3399 I2S0 -> CODEC ->hp/Speaker
TC358749 I2S 信号送給 RK3399 錄音,然後 RK3399 在通過播放給 HDMI TX 輸出,需要注冊兩個聲霸卡,TC358749 聲霸卡,以及HDMI audio out 聲霸卡,系統預設已經有HDMI audio out 注冊,需要dts中開啟即可,TC358749 需要再重新寫一個聲霸卡驅動。
RK3399 HDMI IN核心實作方案
該部分的總體思路是,注冊東芝 tc358749x 晶片(約定以下簡稱 749 側)聲霸卡,當聲霸卡成功注冊後,打開 hdmiin apk 時,能用 tinyalsa 工具正常的進行錄(tinycap)播(tinyplay)時,此部分即可調通。
rk3399 具有三組I2S 控制器,是以在硬體上連接配接方式有所不同。根據應用場景的不同,當 749 側的 I2S 連接配接到cpu 或 codec 的 I2S 時,需區分 I2S 的主從模式,一般來說,由于 749 側隻能作為 master 模式,是以當和 749 側連接配接的另一側,則需要配置為 slave 模式
Android 7.1/ kernel 4.4 的 的 I2S M/S
4.4 核心使用了 simple-card 通用的 machine 驅動進行聲霸卡的注冊,為了配置主要的 I2S為 slave 模式。
配置檔案:arch/arm64/boot/dts/rockchip/rk3399-box-rev2-ne4000.dts
tc358749x_sound:tc358749x-sound {
compatible = "simple-audio-card";
simple-audio-card,format = "i2s";
simple-audio-card,name = "rockchip,tc358749x-codec";
simple-audio-card,bitclock-master = <&sound0_master>;
simple-audio-card,frame-master = <&sound0_master>;
simple-audio-card,cpu {
sound-dai = <&spdif>;
sound-dai = <&i2s1>;
};
sound0_master: simple-audio-card,codec {
sound-dai = <&tc358749x>;
};
};
對于rk3399具有多組 I2S 的平台,注冊聲霸卡的方式是把 749 和 codec注冊成一張聲霸卡,壓縮包更新檔預設是用這種方式注冊的。
rt5651-sound {
status = "disabled";
};
hdmiin-sound {
compatible = "rockchip,rockchip-rt5651-tc358749x-sound";
rockchip,cpu = <&i2s0 &i2s1>;
rockchip,codec = <&rt5651 &tc358749x>;
status = "okay";
};
原dts中未對tc358749對應的i2s做配置,添加i2s1配置
+&i2s1 {
status = "okay";
rockchip,i2s-broken-burst-len;
rockchip,playback-channels = <2>;
rockchip,capture-channels = <2>;
#sound-dai-cells = <0>;
};
目前tc358749晶片挂在i2c1下面,在i2c1下面配置tc358749
tc358749x: [email protected]0f {
#sound-dai-cells = <0>;
compatible = "toshiba,tc358749x";
reg = <0x0f>;
power-gpios = <&gpio4 7 GPIO_ACTIVE_HIGH>; //GPIO4_A7
stanby-gpios = <&gpio1 13 GPIO_ACTIVE_HIGH>; //GPIO3_C0 change to GPIO1_B5
reset-gpios = <&gpio3 30 GPIO_ACTIVE_HIGH>; //GPIO3_D6
int-gpios = <&gpio4 5 GPIO_ACTIVE_HIGH>; //GPIO4_A5
pinctrl-names = "default";
pinctrl-0 = <&hdmiin_gpios>;
status = "okay";
};
添加目前音頻配置方案,需修改音頻驅動代碼,根據RK提供修改方案進行修改
#測試驅動是否正常
1、tool & cmds :
mmm external/tinyalsa/ [ tinymix tinyplay tinycap ] //從sdk源碼中編譯出tinyalsa測試工具
目前系統提供tinyalsa音頻測試工具可直接用于音頻測試
tinymix 音頻通路配置
tinypcminfo 用于檢視pcm通道的相關資訊
tinyplay 播放音頻
tinycap 錄音(預設情況下該工具不安裝,需在external/tinyalsa目錄下編譯才會生成)
錄音:tinycap 001.wav -D 1 -d 1
001.wav 音頻檔案(tinycap隻能錄到wav格式的音頻檔案)
-D 聲霸卡 number
-d pcm number
結束錄音用 ctrl+c 組合鍵結束
播放: tinyplay 001.wav -D 0 -d 0
調試注意事項
在測試HDMI IN錄音時出現無法錄音的情況,檢查後發現在i2s1的配置中出現GPIO口複用,需将複用的GPIO口注掉:
i2s1 {
i2s1_2ch_bus: i2s1-2ch-bus {
rockchip,pins =
<4 3 RK_FUNC_1 &pcfg_pull_none>,
<4 4 RK_FUNC_1 &pcfg_pull_none>,
//<4 5 RK_FUNC_1 &pcfg_pull_none>,
<4 6 RK_FUNC_1 &pcfg_pull_none>;
//<4 7 RK_FUNC_1 &pcfg_pull_none>;
};
};
問題調試排查的一些方法
1、通過cat /proc/asound/cards确認聲霸卡有沒有注冊上
rk3399_mid:/ # cat /proc/asound/cards
0 [rkhdmidpsound ]: rk-hdmi-dp-soun - rk-hdmi-dp-sound
rk-hdmi-dp-sound
1 [realtekrt5651co]: realtekrt5651co - realtekrt5651codec_hdmiin
realtekrt5651codec_hdmiin
2、檢視目前聲霸卡裝置:
rk3399_mid:/ # ls /dev/snd/
controlC0 controlC1 pcmC0D0p pcmC1D0c pcmC1D0p pcmC1D1c timer
p 播放裝置
c 錄音裝置
HDMI out:pcmC0D0p
codec : pcmC1D0c pcmC1D0p
HDMI IN : pcmC1D1c
3、tc358749 連接配接到cpu的某組i2s,其對應的pcm裝置在某聲霸卡某pcm下,比如”pcmC1D0c”,說明749對應的pcm裝置在聲霸卡1,pcm号為0,則先打開 hdmiin apk,可以這樣錄音:
tinycap /sdcard/test.wav -D 1 -d 0 -c 2 -r 44100 -b 16
-D:聲霸卡 number
-d:pcm number
結束錄音用 ctrl+c 組合鍵結束。 注:不能用 windows 的 cmd 指令視窗進行 ctrl+c 結束 。錄音結束後,拷貝 test.wav 出來看看是否正常錄到音。如果沒有的話,确認聲霸卡注冊成功,用示波器量 tc358749 端的 i2s 信号,SDO 腳是否有信号,沒有的找東芝的 FAE 咨詢。
4、常用檢視聲霸卡狀态和資訊info 的指令操作:
cat /proc/asound/card*/pcm*/sub*/status |grep 'stat|close' -EC1
cat /proc/asound/card*/pcm*/sub*/info|grep id -C1|grep name -v
cat /proc/asound/card*/pcm*/sub*/info
rk3288:/ $ cat /proc/asound/card*/pcm*/sub*/info
card: 0
device: 0 //輸入裝置類型
subdevice: 0
stream: CAPTURE //錄音裝置
id: RT5651 PCM rt5651-aif1-0
name:
subname: subdevice #0
class: 0
subclass: 0
subdevices_count: 1
subdevices_avail: 1
card: 0
device: 0 //輸出裝置類型
subdevice: 0
stream: PLAYBACK //放音裝置
id: RT5651 PCM rt5651-aif1-0
name:
subname: subdevice #0
class: 0
subclass: 0
subdevices_count: 1
subdevices_avail: 1
RK3399 HDMIIN HAL層實作方案
HAL 層的實作是基于音頻驅動已經調好的情況下來實作的,其基本思路是用 alsa-soc lib的API 來做的,主要API如下:
1. pcm_open
打開指定聲霸卡下的 pcm 裝置
2. pcm_frames_to_bytes
傳回讀回幀數的總大小用位元組
3. pcm_read
讀音頻資料
4. pcm_wirte
寫音頻資料
5. pcm_close
關閉 pcm 裝置
一般的程式設計步驟都是按 linux 的程式設計習慣統一調用界面來的,也即:pcm_open - > pcm_write/
pcm_read -> pcm_close
綜上,HAL 層要做的便是按上面順序進行的,pcm_read 從某聲霸卡 pcm 裝置讀取音頻資料,也即是對 749 聲霸卡進行錄音,pcm_write 把讀到的音頻資料寫到某聲霸卡中去,也即是把得到 749的音頻資料通過這張聲霸卡播放,具體的業務邏輯依據實際需求而定。
HAL層對HDMIIN的音頻通路做了單獨的處理,同時對上層apk進行了修改,根據RK提供的更新檔處理上層音頻通路
分析HAL層log
logcat -s AudioHardwareTiny audio_hw_hdmiin alsa_route
RK3399 HDMIIN音頻采樣率
目前HDMIIN可讀取的音頻采樣率為44.1khz,還無法适配其他采樣率,當其他音頻采樣率接入時,錄取的聲音會出現斷斷續續的狀态,根據RK提供的更新檔進行修改
hal部分 相關函數說明
調試階段,單獨編譯mmm hardware/rockchip/audio/tinyalsa_hal/ 得到
audio.primary.rk30board.so,push進機器驗證即可。
out/target/product/rk3288/vendor/lib/hw/audio.primary.rk30board.so
hardware/rockchip/audio/tinyalsa_hal/audio_hw.c檔案中start_output_stream函數為判斷輸出(聲音輸出)裝置類型,選擇音頻輸出通路:
根據out-> device 類型判斷
if (out->device & (AUDIO_DEVICE_OUT_AUX_DIGITAL)
然後選擇使用音頻路由輸出:
card = adev->out_card[SND_OUT_SOUND_CARD_HDMI];
然後對應打開哪張聲霸卡:
out->pcm[SND_OUT_SOUND_CARD_HDMI]=pcm_open(card,PCM_DEVICE_HDMIOUT, PCM_OUT | PCM_MONOTONIC, &out->config);
hardware/rockchip/audio/tinyalsa_hal/audio_hw.c檔案中start_input_stream函數為判斷輸入(聲音錄入)裝置類型,選擇音頻輸入通路。
audio_hw.c檔案中read_in_sound_card 接口 //從節點擷取聲霸卡資訊:
file = fopen(SND_CARDS_NODE,“r”);
while(get_line(file,buf,sizeof(buf)) >= 0){
if(is_mic_in_sound_card(buf)){
device->in_card[SND_IN_SOUND_CARD_MIC] = get_card_number(buf);
}
audio_hw.c檔案的 adev_set_parameters 函數會去擷取一些屬性參數,然後設定走哪個route,route的宏定義在alsa_audio.h檔案。
然後在adev_set_parameters中調用str_parms_get_str擷取對應字元串的屬性(下面的例子是擷取字元串HDMIin_enable的屬性,該屬性在apk中調用原生的接口
AudioManager.setParameters("HDMIin_enable=true")):
3060 /* HDMIin enable/disable */
3061 val = str_parms_get_str(parms, "HDMIin_enable", value, sizeof(value));
3062 if (0 <= val) {
3063 if (strcmp(value, "true") == 0) {
3064 ALOGD("\n##[czd]%s:-------- HDMIin_enable(%s) ---------##\n\n", __func__, value);
3065 adev->hdmiin_state = true;
3066 //route_pcm_open(HDMI_IN_NORMAL_ROUTE);
3067 route_pcm_open(HDMI_IN_CAPTURE_ROUTE);
3068 ALOGD("Enable HDMIin");
3069 } else if (strcmp(value, "false") == 0) {
3070 ALOGD("\n##[czd]%s:-------- HDMIin_disable(%s) ---------##\n\n", __func__, value);
3071 route_pcm_open(HDMI_IN_OFF_ROUTE);
3072 adev->hdmiin_state = false;
3073 ALOGD("Disable HDMIin");
3074 } else {
3075 ALOGD("\n##[czd]%s:-------- HDMIin_enable(%s) ---------##\n\n", __func__, value);
3076 ALOGE("Unknown HDMIin state %s!!!", value);
3077 ret = -EINVAL;
3078 }
3079 }