close

Step 1. 設定 capture steam數目 在alsa_xxx_init()
         err = snd_pcm_new(xxx[pcm_idx].card,
                            pcm_names[pcm_idx],   // PCM ID string
                            pcm_idx,              // Index of this PCM, zero based
                            1,                    // playback substreams
                            1,                    // capture substreams  PJ: add capture stream
                           
&pcm );

Step 2.增加ops for capture 在alsa_xxx_init()
/* PJ add capture function */
static struct snd_pcm_ops alsa_xxx_capture_ops = {
   .open      = alsa_xxx_open,
   .close     = alsa_xxx_close,
   .ioctl     = snd_pcm_lib_ioctl,
   .hw_params = alsa_xxx_hw_params,
   .hw_free   = alsa_xxx_hw_free,
   .prepare   = alsa_xxx_prepare,
   .trigger   = alsa_xxx_trigger,
   .pointer   = alsa_xxx_pointer,
   .ack       = alsa_xxx_ack,
};
         /* PJ: add capture function */
         snd_pcm_set_ops( pcm,
                          SNDRV_PCM_STREAM_CAPTURE, 
                          &alsa_xxx_capture_ops );
完成上面的宣告基本上kernel insert就會出現 /dev/snd/pcmC0D0c

Step3. 在alsa_xxx_open時要指定不同的hw
/* PJ add capture function */
static struct snd_pcm_hardware alsa_xxx_capture_hw = {
   .info             = SNDRV_PCM_INFO_MMAP |
                       SNDRV_PCM_INFO_INTERLEAVED |
                       SNDRV_PCM_INFO_BLOCK_TRANSFER |
                       SNDRV_PCM_INFO_MMAP_VALID,  
   // Minimum Alsa support: 16-bit little-endian, 48kHz/96kHz, Stereo
   .formats          = SNDRV_PCM_FMTBIT_S16_LE,
   .rates            = SNDRV_PCM_RATE_48000 |
                       SNDRV_PCM_RATE_96000
,
   .rate_min         = 48000,
   .rate_max         = 96000
   .channels_min     = 2,
   .channels_max     = 2, 
   .buffer_bytes_max = CAPTURE_ALSA_BUF_SIZE, 
   .period_bytes_min = CAPTURE_PERIOD_SIZE_MIN,
   .period_bytes_max = CAPTURE_PERIOD_SIZE_MAX

   .periods_min      = PERIODS_PER_BUFFER_MIN,
   .periods_max      = PERIODS_PER_BUFFER_MAX,
   .fifo_size        = 0,
};

    runtime->hw = alsa_xxx_capture_hw;

這裡提到的peroid表示dma buffer內一次存取的大小,如果user mode程式搬得不夠快而設定值
又太小的話,或出現overrun的訊息,就是會有斷音的情況。

Step 4. 建立kernel thread負責polling抓取硬體來的資料,有很多driver是使用interrupt方式
          osal_res = create_prioritized_group_thread( &(chip_data->filesink_thread),
                                                      alsa_xxx_sink_thread,
                                                      (void *)substream,
                                                      0,
                                                      "filesink thread",
                                                      "Alsa_xxx" );
Step 5. 上層的程式會透過prepare來啟動input開始將資料送入而再trigger來啟動我們使用
的thread來開始monitor輸入的資料


Step 6. 接下來就是重頭戲,如何抓資料
                  virtual_buffer_address = OS_MAP_IO_TO_MEM_CACHE(buffer_desc.phys.base, buffer_desc.phys.size);
                  OS_ASSERT(virtual_buffer_address);                 

                  // PJ: prepare a dma data size is peroid_size_bytes
                  while ((int) amount_to_write > 0) {
                      if (amount_to_write < (period_size_bytes - (chip_data->dma_read_offset % period_size_bytes)))
                          copy_size = amount_to_write;                   
                      else
                          copy_size = period_size_bytes - (chip_data->dma_read_offset % period_size_bytes);                    
                      
                      memcpy( (substream->runtime)->dma_area+ (chip_data->dma_read_offset % buffer_size_bytes), virtual_buffer_address + offset, copy_size );

                      chip_data->dma_read_offset += copy_size;
                      offset += copy_size;
                      amount_to_write -= copy_size;
                     
                      if (chip_data->dma_read_offset % period_size_bytes == 0)  
                          snd_pcm_period_elapsed( substream );
                  }                   
                  OS_UNMAP_IO_FROM_MEM(virtual_buffer_address, buffer_desc.phys.size);

這裡的處理大多就是將硬體的buffer資料塞到dma buffer中,當完成一個週期後,就使用
snd_pcm_period_elapsed通知上層的alsa lib.

Step 7. 之後上層的程式收到interrupt後,會透過pointer來可讀取dma的offset位置
alsa_xxx_pointer ( my_snd_pcm_substream_t *substream )
{
...
   ret = bytes_to_frames( substream->runtime, chip_data->dma_read_offset );
...
}

Step 8. 之後thread回不斷地移動這個offset並通知上層的程式dma buffer新的位置將資料搬
回去

 

原文網址
http://blog.yam.com/pjsun/article/21826041

 

arrow
arrow
    全站熱搜

    立你斯 發表在 痞客邦 留言(0) 人氣()