智慧路由器作業系統openwrt 之三 音訊開發
Linux 音訊架構
音視訊的好壞 直接影響 產品體驗
音訊架構圖
openWRT 採用 ALSA 層次圖,如下
- Application: 上層應用 主要呼叫alsa-lib 中的介面 實現業務邏輯。使用alsa-util中aplay,arecord,amixer,speaker-test進行相關測試。
- HAL層 : 移植alsa-lib 和 alsa-utils. 在openwrt中 feeds 自帶了alsa-lib 1.1.01
- kernel: 按照ALSA 驅動層進行適配。2
Application層
openwrt中應用啟動需要自定義啟動指令碼,放在目錄/etc/init.d 目錄下。
啟動指令碼順序定義
05 defconfig //載入預設引數 10 boot //啟動 39 usb // 載入usbfs 40 network // 設定網絡卡引數 45 firewall // 防火牆 50 dropbear // sshd server 50 cron // … 50 telnet // 如果沒有修改root密碼,則啟動telnet server 60 dnsmasq // DHCP 和 DNS 服務端 95 done // … 96 led // 指示燈 97 watchdog // … 99 sysctl // 最後,進行必要的核心引數調整
啟動指令碼書寫
按照官方Wiki指令碼3,進行自定製。
HAL層
這一層不需要做太多改動,需要配置feeds選擇自己需要的版本即可,
具體的介面查詢,可以到alsa-project
kernel
根據ALSA驅動進行相關分析。
machine
/* SoC machine */
struct snd_soc_card {
char *name;
...
int (*probe)(struct platform_device *pdev);
int (*remove)(struct platform_device *pdev);
/* the pre and post PM functions are used to do any PM work before and
* after the codec and DAIs do any PM work. */
int (*suspend_pre)(struct platform_device *pdev, pm_message_t state);
int (*suspend_post)(struct platform_device *pdev, pm_message_t state);
int (*resume_pre)(struct platform_device *pdev);
int (*resume_post)(struct platform_device *pdev);
...
/* CPU <--> Codec DAI links */
struct snd_soc_dai_link *dai_link;
int num_links;
...
};
probe/remove 可選,主要偵測machine。 suspend/resume 在codec,DAIs和DMA suspend resume 會相應的觸發,也是可選的 machine DAI口配置,配置對應的結構體
/* digital audio interface glue - connects codec <--> CPU */
static struct snd_soc_dai_link corgi_dai = {
.name = "WM8731",
.stream_name = "WM8731",
.cpu_dai_name = "pxa-is2-dai",
.codec_dai_name = "wm8731-hifi",
.platform_name = "pxa-pcm-audio",
.codec_name = "wm8713-codec.0-001a",
.init = corgi_wm8731_init,
.ops = &corgi_ops,
};
/* audio machine driver */
static struct snd_soc_card snd_soc_corgi = {
.name = "Corgi",
.dai_link = &corgi_dai,
.num_links = 1,
};
plarform
DMA 驅動,Soc DAI 驅動5
/* SoC audio ops */
struct snd_soc_ops {
int (*startup)(struct snd_pcm_substream *);
void (*shutdown)(struct snd_pcm_substream *);
int (*hw_params)(struct snd_pcm_substream *, struct snd_pcm_hw_params *); int (*hw_free)(struct snd_pcm_substream *);
int (*prepare)(struct snd_pcm_substream *);
int (*trigger)(struct snd_pcm_substream *, int);
};
//平臺驅動通過DMA介面關聯起來
struct snd_soc_platform_driver {
char *name;
int (*probe)(struct platform_device *pdev);
int (*remove)(struct platform_device *pdev);
int (*suspend)(struct platform_device *pdev, struct snd_soc_cpu_dai *cpu_dai);
int (*resume)(struct platform_device *pdev, struct snd_soc_cpu_dai *cpu_dai);
/* pcm creation and destruction */
int (*pcm_new)(struct snd_card *, struct snd_soc_codec_dai *, struct snd_pcm *);
void (*pcm_free)(struct snd_pcm *);
/*
* For platform caused delay reporting.
* Optional.
*/
snd_pcm_sframes_t (*delay)(struct snd_pcm_substream *, struct snd_soc_dai *);
/* platform stream ops */
struct snd_pcm_ops *pcm_ops;
};
codec
每個codec codec 驅動必須具備以下功能5:
- codec DAI 與 PCM 配置
- 使用I2C 或者SPI控制IO
- mixer audio 控制
- codec 音訊操作
- DAPM 描述
- DAPM 事件處理 可選
- DAC mute處理
// DAI PCM配置
static struct snd_soc_dai_ops wm8731_dai_ops = {
.prepare = wm8731_pcm_prepare,
.hw_params = wm8731_hw_params,
.shutdown = wm8731_shutdown,
.digital_mute = wm8731_mute,
.set_sysclk = wm8731_set_dai_sysclk,
.set_fmt = wm8731_set_dai_fmt, };
struct snd_soc_dai_driver wm8731_dai = { .name = "wm8731-hifi",
.playback = { .stream_name = "Playback",
.channels_min = 1,
.channels_max = 2,
.rates = WM8731_RATES,
.formats = WM8731_FORMATS,},
.capture = { .stream_name = "Capture",
.channels_min = 1,
.channels_max = 2,
.rates = WM8731_RATES,
.formats = WM8731_FORMATS,}, .ops = &wm8731_dai_ops,
.symmetric_rates = 1, };
// i2c 控制 讀寫
i2c_write
i2c_read
// mixer audio control codec 說有mixer與control都定義在soc.h中
#define SOC_SINGLE(xname, reg, shift, mask, invert)
定義單一控制器:-
xname 控制名稱e.g. "Playback Volume"
reg = codec register
shift = control bit(s) offset in register
mask = control bit size(s) e.g. mask of 7 = 3 bits
invert = the control is inverted 是否反轉
其他巨集包括: -
#define SOC_DOUBLE (xname ,reg ,shift_left ,shift_right ,mask ,invert )
立體聲控制
#define SOC_DOUBLE_R (xname ,reg_left ,reg_right ,shift ,mask ,invert )
一個跨越2個暫存器的立體聲控制
#define SOC_ENUM_SINGLE (xreg ,xshift ,xmask ,xtexts )
xreg = 暫存器
XSHIFT = 控制位(小號)偏移在暫存器
xmask = 控制位(小號)大小
xtexts = 指向描述每個設定的字串陣列的指標
//audio 控制操作
/* SoC audio ops */
struct snd_soc_ops {
int (*startup)(struct snd_pcm_substream *);
void (*shutdown)(struct snd_pcm_substream *);
int (*hw_params)(struct snd_pcm_substream *, struct snd_pcm_hw_params *); int (*hw_free (struct snd_pcm_substream *);
int (*prepare)(struct snd_pcm_substream *);
};
//DAPM //DAPM handler 參考5
openWRT 音訊應用
上面一節介紹了音訊框架。在openWRT音訊業務主要VOIP通過 ,上/下行通過模組如下:
除錯技術
鏈路上應用層UDP擷取檔案
知道UDP 下的G711A 對應的buffer
// 應用層將buffer寫到檔案 程式碼塊
static struct file *file_g711a= NULL;
int writelen = 0;
if (NULL == fp)
{
file_g711a= file_open("/test1", O_RDWR | O_CREAT, 0777);
if(file_g711a == NULL){
printf("g711a file = NULL");
}else{
printf("g711a file open ok");
}
}
if(file_g711a != NULL){
writelen = fwrite(g711a_buffer, 1, sizeof(g711a_buffer), file_g711a);
}
printk("writelen:%d\n",writelen);
鏈路上 alsa 擷取檔案
轉換成PCM後,將對應的buffer 寫道檔案
// 應用層將buffer寫到檔案 程式碼塊
static struct file *file_pcm= NULL;
int writelen = 0;
if (NULL == fp)
{
file_pcm= file_open("/test.pcm", O_RDWR | O_CREAT, 0777);
if(file_pcm == NULL){
printf("pcm file = NULL");
}else{
printf("pcm file open ok");
}
}
if(file_pcm != NULL){
writelen = fwrite(pcm_buffer, 1, sizeof(pcm_buffer), file_pcm);
}
printk("writelen:%d\n",writelen);
鏈路上kernel 擷取檔案
在PCM與DMA通訊的函式中 將buffer 寫到檔案中,
// kernel 將buffer寫到檔案 程式碼塊
static struct file *fp = NULL;
mm_segment_t fs;
static loff_t pos = 0;
printk("hello enter\n");
if (NULL == fp)
{
fp = filp_open("/test.pcm", O_RDWR | O_CREAT, 0777);
if (IS_ERR(fp))
{
printk("create file error\n");
return -1;
}
}
fs = get_fs();
set_fs(KERNEL_DS);
int writelen = vfs_write(fp, buf, size*4, &pos);
pos += size*4;
printk("writelen:%d pos:%d\n",writelen, pos);