智能路由器操作系統openwrt 之三 音頻開發

Linux 音頻架構

音視頻的好壞 直接影響 產品體驗

音頻架構圖

openWRT 採用 ALSA 層次圖,如下
在這裏插入圖片描述

  1. Application: 上層應用 主要調用alsa-lib 中的接口 實現業務邏輯。使用alsa-util中aplay,arecord,amixer,speaker-test進行相關測試。
  2. HAL層 : 移植alsa-lib 和 alsa-utils. 在openwrt中 feeds 自帶了alsa-lib 1.1.01
  3. 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-project4 查詢。

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

  1. codec DAI 與 PCM 配置
  2. 使用I2C 或者SPI控制IO
  3. mixer audio 控制
  4. codec 音頻操作
  5. DAPM 描述
  6. DAPM 事件處理
    可選
  7. 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 = 0if (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 = 0if (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);

關注我們

在這裏插入圖片描述


  1. feeds ↩︎

  2. alsadriver ↩︎

  3. initscript ↩︎

  4. alsa-api ↩︎

  5. alsa-drvier ↩︎ ↩︎ ↩︎

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章