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
留言列表