31.Linux-wm9876音效卡驅動(移植+測試)

来源:http://www.cnblogs.com/lifexy/archive/2017/11/20/7867782.html
-Advertisement-
Play Games

本節學習目的 1)分析Linux中的OSS音效卡系統 2)移植wm9876音效卡 3)使用madplay應用程式播放mp3 1.聲音三要素 採樣頻率 音頻採樣率是指錄音設備在一秒鐘內對聲音信號的採樣次數, 常用的採樣率有: 8KHz - 電話所用採樣率, 對於人的說話已經足夠清除 22.05KHz - ...


本節學習目的

  • 1)分析Linux中的OSS音效卡系統
  • 2)移植wm9876音效卡
  • 3)使用madplay應用程式播放mp3

 


1.聲音三要素

採樣頻率

音頻採樣率是指錄音設備在一秒鐘內對聲音信號的採樣次數, 常用的採樣率有:

  • 8KHz      - 電話所用採樣率, 對於人的說話已經足夠清除
  • 22.05KHz - 無線電廣播所用採樣率
  • 32KHz   -  miniDV 數位視頻、DAT所用採樣率
  • 44.1KHz - 音頻 CD, 也常用於 MPEG-1 音頻(VCD, SVCD, MP3)所用採樣率
  • 48KHz   - miniDV、數字電視、DVD、DAT、電影和專業音頻所用的數字聲音所用採樣率
  • 50KHz   - 商用數字錄音機所用採樣率
  • 96K     -   BD-ROM(藍光碟)音軌、和 HD-DVD (高清晰度 DVD)音軌等所用採樣率

而2440開發板的採樣頻率IISRCK最高可以達到96KHz,滿足了很多常用的採樣場合,如下圖所示:

量化位數

指每個採樣點里傳輸的數字信號次數,如下圖所示, 其中藍線表示模擬信號,紅線表示數字信號,量化位越高,數字信號就越可能接近原始信號,音質越好

 

一般的量化位數為:

  • 8位:  分成 256 次;
  • 16位: 分為65536次, 已到 CD 標準;
  • 32位: 分為 4294967296次,很少用到

2440的開發板只支持8位,16位,如下圖所示:

其中LRCK就是採樣頻率,當LRCK為低時,表示傳輸的採樣數據是左聲道,當LRCK為高時,表示傳輸的採樣數據是右聲道,每個採樣點,SD(serial data)都可以傳輸8位,或16位數字信號(從低位到高位傳輸)

 

聲道數

常有單聲道和立體聲之分,(有的也處理成兩個喇叭輸出同一個聲道的聲音),而立體聲更能感受到空間效果,但數據量翻倍

 

所以,聲音的每秒數據量(位元組/s)= (採樣頻率 × 量化位數 × 聲道數) / 8;

2. WM9876音效卡硬體分析

音效卡是負責錄音、播音、調節音量和聲音合成等的一種多媒體板卡 

本節使用的音效卡是2440板上自帶的WM9876音效卡

 

 

當我們播放聲音時 ,將數字信號傳入I2SDO腳,音效卡便通過解碼,產生模擬信號到喇叭/耳機

錄音時,音效卡便獲取麥克風的模擬信號,編碼出數字信號到I2SDI引腳上

WM8976介面分為兩種:I2S介面(提供音頻接收和發送)、控制介面(控制音量大小,使能各個輸出通道等)

IIS介面相關的引腳如下    

  • CDCLK : 為編解碼晶元提供系統同步時鐘 (系統時鐘) 
  • I2SSCLK: IIS控制器提供的串列時鐘信號 (位數據傳輸時鐘) 
  • I2SLRCK: 採樣頻率信號,當為低電平時是採樣的是左聲道信號,為高電平是右聲道信號
  • I2SDI : ADC數據輸入
  • I2SDO :DAC數據輸出

控制介面相關的引腳如下

  • CSB/GPIO1: 3線 控制數據使能引腳
  • SCLK: 3線/2線 時鐘引腳
  • SDIN: 3線/2線 數據輸入輸出引腳
  • MODE: 3線/2線 控制選擇,當MODE為高,表示為3線控制,MODE位低,表示2線控制,如下圖所示:

                           

其它引腳如下:

  • R/LOUT1:音頻左/右輸出通道1,外接耳機插孔
  • R/LOUT2:音頻左/右輸出通道2,未接
  • OUT3:單聲道輸出通道3,未接
  • OUT4:單聲道輸出通道4,未接
  • LIP/LIN:音頻輸入通道,外接麥克風

 

那麼3線和2線的控制引腳又有什麼區別?

3線控制:

如下圖所示,3線控制,每周期都要傳輸16位數據(7位寄存器地址+9位寄存器數據),傳輸完成後,給CSB一個上升沿便完成一次數據的傳輸

 

2線控制:

如下圖所示,2線控制就是I2C通信方式

 

本節的WM8976的MODE腳接的高電平,所以是3線控制

 

3.接下來便來分析linux內核的音效卡系統

在linux音效卡中存在兩種音效卡系統,一種是OSS(開放聲音系統),一種是ALSA(先

進Linux聲音架構)。本節系統以OSS(Open Sound System)為例 ,

內核以linux-2.6.22.6版本為例,位於:linux-2.6.22.6\sound\Sound_core.c

3.1首先進入入口函數

如下圖所示:

 

入口函數里,只註冊了一個主設備號為(SOUND_MAJOR)14的“sound”字元設備和class類,這裡為什麼沒有創建設備節點?

是因為, 當註冊音效卡系統的驅動後,才會有設備節點,此時這裡的代碼是沒有驅動的,後面會分析到

3.2 再來看看“sound”字元設備的file_perations:

 

這裡只有個.open,為什麼沒有.read,.write函數?

顯然在.open函數里做了某些處理,我們進入soundcore_open()來看看

3.2 soundcore_open()代碼如下:

int soundcore_open(struct inode *inode, struct file *file)
{
       int chain;
       int unit = iminor(inode);              //獲取次設備號,通過次設備號來找音效卡驅動
       struct sound_unit *s;
       const struct file_operations *new_fops = NULL; //定義一個新的file_operations
       chain=unit&0x0F;  
       if(chain==4 || chain==5)       /* dsp/audio/dsp16 */
       {
              unit&=0xF0;
              unit|=3;
              chain=3;                             
       }

       spin_lock(&sound_loader_lock);          
       s = __look_for_unit(chain, unit);     //裡面通過chains[chain]數組裡找到sound_unit結構體
                                             //一個sound_unit對應一個音效卡驅動

       if (s)
              new_fops = fops_get(s->unit_fops);    //通過sound_unit,獲取對應的file_operations
              ... ...

       if (new_fops) {                                           //當找到file_operations
              int err = 0;
              const struct file_operations *old_fops = file->f_op;//設上次的file_operations等於當前的

              file->f_op = new_fops;                //設置系統的file_operations等於s-> unit_fops

              spin_unlock(&sound_loader_lock);
              if(file->f_op->open)
                     err = file->f_op->open(inode,file);

              if (err) {
                     fops_put(file->f_op);
                     file->f_op = fops_get(old_fops);
              }
              fops_put(old_fops);
              return err;
       }
       spin_unlock(&sound_loader_lock);
       return -ENODEV;
}

通過上面的代碼和註釋分析到,系統音效卡之所以只有一個open(),裡面是通過次設備號來調用__look_for_unit()函數,找到chains[chain]數組裡的驅動音效卡sound_unit結構體,然後來替換系統音效卡的file_operations,實現偷天換日的效果。

__look_for_unit()函數如下圖所示:

 

其中chains[]數組定義如下所示:

 

其中, chains[0]存放的Mixers,實現調節音量,高音等,就是我們VM8976的控制介面

chains[3]存放的DSP,用來實現音頻輸入輸出,就是我們VM8976的I2S介面

顯然VM8976的驅動有2個,需要將2個file_operations放入chains[0]和chains[3]數組裡,供給系統的open()來調用

3.3 我們以DSP為例,搜索chains[3]來看看

 

如上圖所示,顯然register_sound_dsp()函數就是被我們音效卡驅動調用的,用來註冊dsp設備節點,繼續進入sound_insert_unit()函數看看

3.4 sound_insert_unit()函數如下

static int sound_insert_unit(struct sound_unit **list, const struct file_operations *fops, int index, int low, int top, const char *name, umode_t mode, struct device *dev)
{

       struct sound_unit *s = kmalloc(sizeof(*s), GFP_KERNEL);   //分配個新的sound_unit
       int r;
       if (!s)
              return -ENOMEM;
       
       spin_lock(&sound_loader_lock);

// __sound_insert_unit()里主要實現:將分配的新的s插入到chains[3]里,然後並放入fops操作結構體
       r = __sound_insert_unit(s, list, fops, index, low, top); 

       spin_unlock(&sound_loader_lock);
       if (r < 0)
              goto fail;

       else if (r < SOUND_STEP)
              sprintf(s->name, "sound/%s", name);         //s->name="sound/dsp"
       else
              sprintf(s->name, "sound/%s%d", name, r / SOUND_STEP);      

       device_create(sound_class, dev, MKDEV(SOUND_MAJOR, s->unit_minor),s->name+6);      
                         //s->name+6="dsp",也就是在/dev下創建"dsp"的設備節點 return r; fail: kfree(s); return r; }

所以,register_sound_dsp()函數用來創建/dev/dsp 設備節點,同時將dsp相關的file_operations放入chains[3]裡面

3.5 同樣, Mixers的驅動流程也是這樣,它的函數是register_sound_mixer(),如下圖所示

 

也是創建/dev/mixer設備節點, 同時將dsp相關的file_operations放入chains[0]裡面

 

3.6 接下來,我們便搜索register_sound_dsp()函數,看看被哪些音效卡驅動調用

如下圖所示,找到一個支持s3c24xx板卡的音效卡驅動uda1341

uda1341音效卡和WM8976音效卡非常相似,音頻都是I2S介面,就只有控制部分不一樣

uda1341音效卡的硬體,如下圖所示:

 

它的控制引腳只有3個:

L3MODE:模式引腳,為高表示傳輸的是數據,為低表示傳輸的是寄存器地址

L3CLOCK:時鐘引腳

L3DATA:   數據輸入/輸出引腳

控制介面的時序如下所示:

 

和WM8976的控制時序完全不一樣,WM8976控制時序如下所示:

 

 

所以接下來,便修改S3c2410-uda1341.c的控制部分,來移植為wm8976驅動

4.移植wm8976驅動

 首先進入uda1341的probe函數

static int s3c2410iis_probe(struct device *dev)
{
       struct platform_device *pdev = to_platform_device(dev);
       struct resource *res;
       unsigned long flags;
       printk ("s3c2410iis_probe...\n");
       /*獲取資源*/
       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
       if (res == NULL) {
              printk(KERN_INFO PFX "failed to get memory region resouce\n");
              return -ENOENT;
       }

       iis_base = (void *)S3C24XX_VA_IIS ;
       if (iis_base == 0) {
              printk(KERN_INFO PFX "failed to ioremap() region\n");
              return -EINVAL;
       }

/*獲取I2S時鐘,並使能*/
       iis_clock = clk_get(dev, "iis");
       if (iis_clock == NULL) {
              printk(KERN_INFO PFX "failed to find clock source\n");
              return -ENOENT;
       }
       clk_enable(iis_clock);

       /*進入臨界區, 禁止中斷,並保存中斷狀態*/
       local_irq_save(flags);

       /*設置管腳功能*/
       /* GPB 4: L3CLOCK, OUTPUT */
       s3c2410_gpio_cfgpin(S3C2410_GPB4, S3C2410_GPB4_OUTP);
       s3c2410_gpio_pullup(S3C2410_GPB4,1);
       /* GPB 3: L3DATA, OUTPUT */
       s3c2410_gpio_cfgpin(S3C2410_GPB3,S3C2410_GPB3_OUTP);
       /* GPB 2: L3MODE, OUTPUT */
       s3c2410_gpio_cfgpin(S3C2410_GPB2,S3C2410_GPB2_OUTP);
       s3c2410_gpio_pullup(S3C2410_GPB2,1);
       /* GPE 3: I2SSDI */
       s3c2410_gpio_cfgpin(S3C2410_GPE3,S3C2410_GPE3_I2SSDI);
       s3c2410_gpio_pullup(S3C2410_GPE3,0);
       /* GPE 0: I2SLRCK */
       s3c2410_gpio_cfgpin(S3C2410_GPE0,S3C2410_GPE0_I2SLRCK);
       s3c2410_gpio_pullup(S3C2410_GPE0,0);
       /* GPE 1: I2SSCLK */
       s3c2410_gpio_cfgpin(S3C2410_GPE1,S3C2410_GPE1_I2SSCLK);
       s3c2410_gpio_pullup(S3C2410_GPE1,0);
       /* GPE 2: CDCLK */
       s3c2410_gpio_cfgpin(S3C2410_GPE2,S3C2410_GPE2_CDCLK);
       s3c2410_gpio_pullup(S3C2410_GPE2,0);
       /* GPE 4: I2SSDO */
       s3c2410_gpio_cfgpin(S3C2410_GPE4,S3C2410_GPE4_I2SSDO);
       s3c2410_gpio_pullup(S3C2410_GPE4,0);

       /*退出臨界區,使能中斷,並恢復之前保存的flags中斷狀態*/
       local_irq_restore(flags);

       /*設置2440的I2S寄存器*/
       init_s3c2410_iis_bus();

       /*初始化uda1341音效卡的控制部分*/
       init_uda1341();

 

       /*設置DMA輸出通道,用來接收聲音*/
       output_stream.dma_ch = DMACH_I2S_OUT;
       if (audio_init_dma(&output_stream, "UDA1341 out")) {
              audio_clear_dma(&output_stream,&s3c2410iis_dma_out);
              printk( KERN_WARNING AUDIO_NAME_VERBOSE": unable to get DMA channels\n" );
              return -EBUSY;
       }

    /*設置DMA輸入通道,用來接收聲音*/
       input_stream.dma_ch = DMACH_I2S_IN;
       if (audio_init_dma(&input_stream, "UDA1341 in")) {
              audio_clear_dma(&input_stream,&s3c2410iis_dma_in);
              printk( KERN_WARNING AUDIO_NAME_VERBOSE": unable to get DMA channels\n" );
              return -EBUSY;
       }
   

  /*創建/dev/dsp,/dev/mixer兩個設備節點,
並將smdk2410_audio_fops和smdk2410_mixer_fops 兩個file_operations放入chains[0]和chains[3]里,供給內核的音效卡系統調用
*/ audio_dev_dsp = register_sound_dsp(&smdk2410_audio_fops, -1); audio_dev_mixer = register_sound_mixer(&smdk2410_mixer_fops, -1); printk(AUDIO_NAME_VERBOSE " initialized\n"); return 0; }

從上面的代碼來看, uda1341的管腳和wm8976的管腳連接都是一樣的,只有init_uda1341()不一樣,裡面是初始化uda1341的控制引腳介面,所以需要屏蔽,然後自己來寫個init_wm8976()函數

 

4.1寫init_wm8976()函數之前需要先寫一個寄存器操作函數

參考wm8976晶元手冊時序圖:

 

所以,代碼如下: 

static void wm8976_write_reg(unsigned char reg, unsigned int data)
{
int i;
unsigned long flags;
//對於wm8976來說,數據的高七位表示寄存器地址,低9位表示寄存器的值
unsigned short val = (reg << 9) | (data & 0x1ff);
/*wm8976引腳csb,dat,clk分別對應2440晶元的GPB2,3,4引腳*/ s3c2410_gpio_setpin(S3C2410_GPB2,1); s3c2410_gpio_setpin(S3C2410_GPB3,1); s3c2410_gpio_setpin(S3C2410_GPB4,1); /*退出臨界區,使能中斷,並恢復之前保存的flags中斷狀態*/ local_irq_save(flags);
/*把val值寫入wm8976,共16位,從高到低傳輸*/ for (i = 0; i < 16; i++){ if (val & (1<<15)) { s3c2410_gpio_setpin(S3C2410_GPB4,0); s3c2410_gpio_setpin(S3C2410_GPB3,1); udelay(1); s3c2410_gpio_setpin(S3C2410_GPB4,1); } else { s3c2410_gpio_setpin(S3C2410_GPB4,0); s3c2410_gpio_setpin(S3C2410_GPB3,0); udelay(1); s3c2410_gpio_setpin(S3C2410_GPB4,1); } val = val << 1; } //傳輸完成,需要讓csb信號產生低脈衝,寫入wm8976 s3c2410_gpio_setpin(S3C2410_GPB2,0); udelay(1); //引腳恢復到高電平狀態 s3c2410_gpio_setpin(S3C2410_GPB2,1); s3c2410_gpio_setpin(S3C2410_GPB3,1); s3c2410_gpio_setpin(S3C2410_GPB4,1); local_irq_restore(flags); }

 

4.2.參考wm8976g.pdf第87頁,來初始化wm8976,使能輸出聲道1,2,混響器等

static void init_wm8976(void)
{
       uda1341_volume = 57;         // wm8976的音量預設值,後面會講到
       uda1341_boost = 0;

       /* software reset */
       wm8976_write_reg(0, 0);

       /* BIT[6-5]:使能音頻的輸出左右通道2
        * BIT[3]: 使能mixer混音器的輸出右通道  
        * BIT[2]: 使能mixer混音器的輸出右通道  
        * BIT[1]: 使能DAC傳輸的右通道
     * BIT[0]: 使能DAC傳輸的左通道
        */
       wm8976_write_reg(0x3, 0x6f);
     /* BIT[4]: 使能輸出麥克風電壓
        */
       wm8976_write_reg(0x1, 0x1f);  
       wm8976_write_reg(0x2, 0x185);//ROUT1EN LOUT1EN, inpu PGA enable ,ADC enable
       wm8976_write_reg(0x6, 0x0);//SYSCLK=MCLK 
       wm8976_write_reg(0x4, 0x10); // [4:3]=10:使用I2S介面傳輸                   
       wm8976_write_reg(0x2B,0x10);//BTL OUTPUT 
       wm8976_write_reg(0x9, 0x50);//Jack detect enable 
       wm8976_write_reg(0xD, 0x21);//Jack detect 
       wm8976_write_reg(0x7, 0x01);//Jack detect
}

wm8976初始化修改完成後,還需要修改音量控制等函數,之前就分析了uda1341的probe函數,裡面會註冊dsp、mixer設備節點:

/dev/dsp

用來播發和錄音,由於uda1341和wm8976都用了I2S介面,所以dsp的file_operations不需要修改,

/dev/mixer

用來控制音量,調低音,高音等,由於wm8976的控制介面不一樣,所以需要修改mixer的file_operations->ioctl函數

4.3 mixer的file_operations->ioctl函數如下所示:

static int smdk2410_mixer_ioctl(struct inode *inode, struct file *file,unsigned int cmd, unsigned long arg)
{
       int ret;
       long val = 0; 

switch (cmd) {
       case SOUND_MIXER_INFO:             //CASE : 獲取音效卡的描述信息
{
       mixer_info info;
       strncpy(info.id, "UDA1341", sizeof(info.id));
       strncpy(info.name,"Philips UDA1341", sizeof(info.name));
       info.modify_counter = audio_mix_modcnt;        
       return copy_to_user((void *)arg, &info, sizeof(info));  //上傳用戶層
}
    ... ...

case SOUND_MIXER_WRITE_VOLUME:                //CASE: 寫音量,音量值為0~99
ret = get_user(val, (long *) arg);  //讀用戶層的數據,並放在val里
            if (ret)
                   return ret;

     uda1341_volume = 63 - (((val & 0xff) + 1) * 63) / 100; //轉換為寄存器音量值
     uda1341_l3_address(UDA1341_REG_DATA0); //寫入音量的寄存器地址
     uda1341_l3_data(uda1341_volume);          //寫入轉換後的寄存器值數據          
     break;

case SOUND_MIXER_READ_VOLUME:                   //CASE:  讀音量,音量值為0~100
     val = ((63 - uda1341_volume) * 100) / 63;  //將寄存器音量值轉換為原始數據
     val |= val << 8;                  
     return put_user(val, (long *) arg);                     //上傳音量值

case SOUND_MIXER_READ_IGAIN:                     //CASE: 讀(in gain)混音輸入增益
     val = ((31- mixer_igain) * 100) / 31;                 
     return put_user(val, (int *) arg);
case SOUND_MIXER_WRITE_IGAIN:     //CASE: 寫(in gain)混音輸入增益 ret = get_user(val, (int *) arg); if (ret) return ret; mixer_igain = 31 - (val * 31 / 100); /* use mixer gain channel 1*/ uda1341_l3_address(UDA1341_REG_DATA0); uda1341_l3_data(EXTADDR(EXT0)); uda1341_l3_data(EXTDATA(EXT0_CH1_GAIN(mixer_igain))); break; default: DPRINTK("mixer ioctl %u unknown\n", cmd); return -ENOSYS; } return 0; }

從上面的代碼來看,顯然接下還要修改以下幾個與控制介面相關的case:

  • case SOUND_MIXER_WRITE_VOLUME:            //寫音量
  • case SOUND_MIXER_READ_VOLUME:             //讀音量
  • case SOUND_MIXER_READ_IGAIN:                 //讀(in gain)混音輸入增益
  • case SOUND_MIXER_WRITE_IGAIN:                //寫(in gain)混音輸入增益

 

4.4修改“case SOUND_MIXER_WRITE_VOLUME:”和“case SOUND_MIXER_READ_VOLUME:”

如下圖所示(參考wm8976手冊的P86頁):

  

其中52,53對應的輸出左右通道1的音量,54,55對應的輸出左右通道2的音量

而我們耳機位於輸出左右通道1,如下圖所示,所以我們需要設置52,53的寄存器

 

接下來,便來看看寄存器,如何讀寫音量

我們以53通道1寄存器為例:

 

如上圖所示:

  • bit8:  為1,表示每次寫入音量值,即立刻更新音量
  • bit7:  位1,表示通道1的左右聲道都靜音
  • bit6:       位1,表示通道1的右聲道靜音
  • bit5~0:   表示音量大小,預設值為57(111001),最大值為63

所以修改的內容如下所示:

case SOUND_MIXER_WRITE_VOLUME:     //音量0~100
    ret = get_user(val, (long *) arg);          //讀取應用數據,存到val里
    if (ret)
            return ret;
    uda1341_volume = (((val & 0xff)) * 63) / 100;       //最大值為63,最小值為0

wm8976_write_reg(52, (1<<8)| uda1341_volume);
wm8976_write_reg(53, (1<<8)| uda1341_volume);
    break;


case SOUND_MIXER_READ_VOLUME:
val = (uda1341_volume * 100) / 63;       //最大值為99
     return put_user(val, (long *) arg);

 

4.5修改“case SOUND_MIXER_READ_IGAIN:”和“case SOUND_MIXER_WRITE_IGAIN:”

參考wm8976手冊的P86頁,如下圖所示:

 

其中50,51對應的就是左右混音控制寄存器

我們以50左聲道混音寄存器為例:

  

如上圖所示:

bit8~6: 混音輸入增益,預設值為0,最大值為7

所以修改的內容如下所示:

1)首先修改混音輸入增益的初始預設值為0,如下圖所示

 

2)修改“case SOUND_MIXER_READ_IGAIN:”和“case SOUND_MIXER_WRITE_IGAIN:”

case SOUND_MIXER_READ_IGAIN:   //混音輸入:0~100

val = (mixer_igain* 100) / 7;
return put_user(val, (int *) arg);

 

case SOUND_MIXER_WRITE_IGAIN:
ret = get_user(val, (int *) arg);
if (ret)
return ret;

mixer_igain = val * 7 / 100;
/* use mixer gain channel 1*/
wm8976_write_reg(50, mixer_igain<<6);
wm8976_write_reg(51, mixer_igain<<6);
break;

 

5.配置,修改內核文件

5.1 make menuconfig 配置內核

-> Device Drivers

  -> Sound

    -> Advanced Linux Sound Architecture  // 相容OSS

      -> Advanced Linux Sound Architecture

        -> System on Chip audio support

        <*> I2S of the Samsung S3C24XX chips              //*:將/linux-2.6.22.6/sound/soc/s3c24xx下的makefile指定的文件加入內核里

5.2 將修改好的s3c-wm8976.c放入/linux-2.6.22.6/sound/soc/s3c24xx目錄下

5.3修改該目錄下的makefile

obj-y += s3c2410-uda1341.o

改為:

obj-y += s3c-wm8976.o  

5.4 make uImage,如下圖所示,可以看到內核已經被編譯

 

最後下載並啟動內核,如下圖所示,可以看到該兩個設備節點

 

6.測試與運行

6.1使用wav測試音效卡

wav是屬於一個未經壓縮的音頻文件,所以可以直接調用給我們音效卡播放

播放:

     cat Windows.wav > /dev/dsp

錄音(還需要修改下驅動才行):

   cat /dev/dsp > sound.bin 

   //然後對著麥克風說話

   ctrl+c    //退出

   cat sound.bin > /dev/dsp  // 就可以聽到錄下的聲音

 

6.2使用madplay應用程式測試音效卡

Madplay是一個根據MAD演算法寫的MP3播放器,而MP3屬於高壓縮比(11:1)的文件,所以需要madplay解碼後才能給我們音效卡播放,使用之前,需要先來移植madplay

步驟如下:

1)首先下載並解壓3個文件

  • libid3tag-0.15.1b.tar.gz                      //mp3的解碼庫
  • libmad-0.15.1b.tar.gz                         //madplay的文件庫
  • madplay-0.15.2b.tar.gz                      //madplay播放器的源碼

2)先創建安裝目錄mkdir tmp

3)接下來先安裝2個庫(./configure使用參考: http://www.cnblogs.com/lifexy/p/7866453.html

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.數值四捨五入,小數點後保留2位 round() 函數是四捨五入用,第一個參數是我們要被操作的數據,第二個參數是設置我們四捨五入之後小數點後顯示幾位。 numeric 函數的2個參數,第一個表示數據長度,第二個參數表示小數點後位數。 示例如下: ...
  • 環境描述:主redis:192.168.10.1 6379從redis:192.168.10.2 6380 一、主從配置 1、將主從redis配置文件redis.conf中的aemonize no 改為 yes 2、修改從redis配置文件redis.conf中的port 6379 改為 6380, ...
  • 一、Redis的誕生 Redis創建者,出生於西西里島的義大利人(antirez)發明的,個人網站,http://invece.org。早年是系統管理員,2004-2006 年做嵌入式方面的工作,之後接觸 WEB,2007 年和朋友共同創建了一個網站 LLOOGG.com,併為瞭解決這個網站的負載問 ...
  • [20171120]11G關閉直接路徑讀.txt--//今天做filesystemio_options參數測試時,遇到一個關於直接路徑讀的問題.--//如果看以前的博客介紹,設置"_serial_direct_read"=never或者events '10949 trace name context ...
  • 要使用Linux系統很重要的一個操作就是使Linux系統能夠訪問互聯網,只有Linux系統能夠訪問互聯網才能夠去下載很多自己所需要的資源,如果不能訪問互聯網那麼使用Linux系統往往會卡在這一步,假設你裝的是一個minimal版本的CentOS,那麼很多Linux系統下麵的工具都是沒有被安裝的,這個 ...
  • 第1章 批量添加3個用戶stu01-stu03,設置密碼為123456. 1.1 預備知識 前的產生的命令通過管道後可以交給bash運行 1.2 命令的樣子 目標命令的形狀,想讓命令變成這個樣子執行。 1.3 命令的生成 1.3.1 第一步 生成名字 1.3.2 第二步 拼接出想要的形狀 兩條命令之 ...
  • 引用原文地址 : https://msdn.microsoft.com/en-us/library/x98tx3cf.aspx 1. 在program中嚴格按下麵順序include 2. 必須是Debug版的build Q : LiteServer遍Debug可能會遇到一些問題 A : 可以用來驗證 ...
  • && : 邏輯與,都為真則真 ||: 邏輯或,一個為真則為真 !:邏輯非, &:按位與 |:按位或 ~:按位取反 ^:按位異或,相異為1.例如A=0b11001100,B=0b11110000,F=A^B=0b00111100 時鐘周期:晶振就是時鐘源,就是晶振分之一。 機器周期:完成一個操作的最短 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...