一.tinyplay播放
操作命令:tinyplay /sdcard/test.wav
Tinyplay.c (external\tinyalsa)
file = fopen(filename, "rb"); //對應的音頻文件
fread(&riff_wave_header, sizeof(riff_wave_header), 1, file); //讀取音頻文件頭部
/* parse command line arguments */
處理一些參數的問題
//參數分別是播放的文件,聲卡,通道,采樣率,位數,DMA一次傳輸的數據量,傳輸次數
play_sample(file, card, device, chunk_fmt.num_channels, chunk_fmt.sample_rate,
chunk_fmt.bits_per_sample, period_size, period_count);
struct pcm_config config; //用來記錄音頻信息
config.channels = channels;
config.rate = rate;
config.period_size = period_size;
config.period_count = period_count;
if (bits == 32) //格式
config.format = PCM_FORMAT_S32_LE;
else if (bits == 16)
config.format = PCM_FORMAT_S16_LE;
if (!sample_is_playable(card, device, channels, rate, bits, period_size, period_count))
struct pcm_params *params;
params = pcm_params_get(card, device, PCM_OUT);
//我們這里是/dev/snd/pcmC0D0p
snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device, flags & PCM_IN ? 'c' : 'p');
fd = open(fn, O_RDWR); //打開播放dev,驅動分析一
//分配參數結構體
params = calloc(1, sizeof(struct snd_pcm_hw_params));
param_init(params); //初始化
ioctl(fd, SNDRV_PCM_IOCTL_HW_REFINE, params) //會調用到驅動,獲取參數,驅動分析二
pcm = pcm_open(card, device, PCM_OUT, &config);
pcm->config = *config;
snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device, flags & PCM_IN ? 'c' : 'p');
pcm->flags = flags;
pcm->fd = open(fn, O_RDWR); //打開/dev/snd/pcmC0D0p節點,已經分析了
////獲取pcm的信息,包括card,流,dma各種信息
ioctl(pcm->fd, SNDRV_PCM_IOCTL_INFO, &info) //獲取PCM信息,驅動分析三
param_init(¶ms); 初始化參數
//包括format,channels等
param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_FORMAT,pcm_format_to_alsa(config->format));
。。。。。。很多設置。。。。。
ioctl(pcm->fd, SNDRV_PCM_IOCTL_HW_PARAMS, ¶ms) //驅動分析四
/* get our refined hw_params */
pcm->buffer_size = config->period_count * config->period_size; //緩沖區buf大小
if (flags & PCM_MMAP) //如果支持mmap
//mmap將一個文件或者其它對象映射進內存,調用底層的mmap函數,驅動分析五
起始地址 長度
pcm->mmap_buffer = mmap(NULL, pcm_frames_to_bytes(pcm, pcm->buffer_size),
表示頁可以讀寫,與其它進程共享映射空間 有效的文件描述詞
PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, pcm->fd, 0);
params.tstamp_mode = SNDRV_PCM_TSTAMP_ENABLE;
sparams.period_step = 1;
//這種開始和定制閾值
pcm->config.start_threshold = sparams.start_threshold = config->period_count * config->period_size / 2;
pcm->config.stop_threshold = sparams.stop_threshold = config->period_count * config->period_size;
if (pcm->flags & PCM_MMAP)
pcm->config.avail_min = sparams.avail_min = pcm->config.period_size;
sparams.xfer_align = config->period_size / 2; /* needed for old kernels */
ioctl(pcm->fd, SNDRV_PCM_IOCTL_SW_PARAMS, &sparams) ////主要是把上層的參數賦值給snd_pcm_runtime,驅動分析六
rc = pcm_hw_mmap_status(pcm); //單獨分析
#ifdef SNDRV_PCM_IOCTL_TTSTAMP
int arg = SNDRV_PCM_TSTAMP_TYPE_MONOTONIC;
rc = ioctl(pcm->fd, SNDRV_PCM_IOCTL_TTSTAMP, &arg); //設置為絕對時間
#endif
size = pcm_frames_to_bytes(pcm, pcm_get_buffer_size(pcm));
//frames * pcm->config.channels * byte
return frames * pcm->config.channels *(pcm_format_to_bits(pcm->config.format) >> 3);
buffer = malloc(size); //分配內存
do {
num_read = fread(buffer, 1, size, file); //把文件的內容讀到buffer里
if (num_read > 0)
if (pcm_write(pcm, buffer, num_read)) //然后寫入pcm,單獨分析
} while (!close && num_read > 0);
1.static int pcm_hw_mmap_status(struct pcm *pcm)
pcm->mmap_status = mmap(NULL, page_size, PROT_READ, MAP_FILE | MAP_SHARED, pcm->fd, SNDRV_PCM_MMAP_OFFSET_STATUS); //見驅動分析五,就是SNDRV_PCM_MMAP_OFFSET_STATUS這個不一樣
if (pcm->flags & PCM_MMAP) //最少的可以用的大小
pcm->mmap_control->avail_min = pcm->config.avail_min;
2.pcm_write(pcm, buffer, num_read)
x.buf = (void*)data;
x.frames = count / (pcm->config.channels * pcm_format_to_bits(pcm->config.format) / 8); //得到多少幀
for (;;)
if (!pcm->running)
int prepare_error = pcm_prepare(pcm);
ioctl(pcm->fd, SNDRV_PCM_IOCTL_PREPARE) //調用驅動,驅動分析七
pcm->prepared = 1;
if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x)) //調用驅動,驅動分析八
二.驅動分析一
Pcm_native.c (kernel\sound\core)
const struct file_operations snd_pcm_f_ops[2] = {
{
.owner = THIS_MODULE,
.write = snd_pcm_write,
.aio_write = snd_pcm_aio_write,
.open = snd_pcm_playback_open,
.release = snd_pcm_release,
.llseek = no_llseek,
.poll = snd_pcm_playback_poll,
.unlocked_ioctl = snd_pcm_playback_ioctl,
.compat_ioctl = snd_pcm_ioctl_compat,
.mmap = snd_pcm_mmap,
.fasync = snd_pcm_fasync,
.get_unmapped_area = snd_pcm_get_unmapped_area,
},
{
.owner = THIS_MODULE,
.read = snd_pcm_read,
.aio_read = snd_pcm_aio_read,
.open = snd_pcm_capture_open,
.release = snd_pcm_release,
.llseek = no_llseek,
.poll = snd_pcm_capture_poll,
.unlocked_ioctl = snd_pcm_capture_ioctl,
.compat_ioctl = snd_pcm_ioctl_compat,
.mmap = snd_pcm_mmap,
.fasync = snd_pcm_fasync,
.get_unmapped_area = snd_pcm_get_unmapped_area,
}
};
1.打開播放dev,驅動分析1
在_snd_pcm_new函數中注冊snd_pcm_dev_register函數,后面snd_device_register_all中統一調用dev->ops->dev_register。
在snd_pcm_dev_register函數中會注冊/dev/snd/pcmC0D0p和/dev/snd/pcmC0D0c,並提供操作函數,我們這里分析open
static int snd_pcm_playback_open(struct inode *inode, struct file *file)
//通過次設備號和SNDRV_DEVICE_TYPE_PCM_PLAYBACK獲取snd_pcm指針
pcm = snd_lookup_minor_data(iminor(inode), SNDRV_DEVICE_TYPE_PCM_PLAYBACK);
err = snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_PLAYBACK);
//This function adds the file to the file linked-list of the card. This linked-list is used to keep tracking the connection state,
// and to avoid the release of busy resources by hotplug.
//這個函數把file 加入到cardlinked-list ,這個linked-list 是用來持續追蹤鏈接狀態的,可以避免很忙的資源被hotplug釋放
err = snd_card_file_add(pcm->card, file);
struct snd_monitor_file *mfile;
mfile = kmalloc(sizeof(*mfile), GFP_KERNEL);
mfile->file = file;
list_add(&mfile->list, &card->files_list); //加入card->files_list鏈表
try_module_get(pcm->card->module)
init_waitqueue_entry(&wait, current); //初始化等待隊列,當前的進程
add_wait_queue(&pcm->open_wait, &wait); //把等待隊列加入到pcm->open_wait里面
while (1)
err = snd_pcm_open_file(file, pcm, stream);
err = snd_pcm_open_substream(pcm, stream, file, &substream); //主要是初始化snd_pcm_runtime相關的東西,單獨分析2
pcm_file = kzalloc(sizeof(*pcm_file), GFP_KERNEL); //分配snd_pcm_file
pcm_file->substream = substream;
if (substream->ref_count == 1) //因為snd_pcm_open_substream已經初始化,這里是1
substream->file = pcm_file;
set_current_state(TASK_INTERRUPTIBLE); //設置當前進程可被中斷喚醒substream->pcm_release = pcm_release_private;file->private_data = pcm_file;
schedule(); //放棄cpu
if (signal_pending(current)) //檢查當前進程是否有信號處理,返回不為0表示有信號需要處理。remove_wait_queue(&pcm->open_wait, &wait);
2.//單獨分析2
err = snd_pcm_open_substream(pcm, stream, file, &substream);
struct snd_pcm_substream *substream;
err = snd_pcm_attach_substream(pcm, stream, file, &substream);
struct snd_pcm_str * pstr;
pstr = &pcm->streams[stream]; //這里是SNDRV_PCM_STREAM_PLAYBACK,找到對應的snd_pcm_str
card = pcm->card;
list_for_each_entry(kctl, &card->ctl_files, list) //遍歷snd_ctl_file
if (kctl->pid == task_pid(current)) //找到與當前進程pid一樣的snd_ctl_file
prefer_subdevice = kctl->prefer_pcm_subdevice;
switch (stream)
case SNDRV_PCM_STREAM_PLAYBACK:
if (pcm->info_flags & SNDRV_PCM_INFO_HALF_DUPLEX) //如果是半雙工
for (substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; substream; substream = substream->next)
if (SUBSTREAM_BUSY(substream)) //如果正常忙,就是正在工作,就返回
case SNDRV_PCM_STREAM_CAPTURE: //錄音和播放一樣,有在工作的就返回
。。。。。中間有很多判斷設備是不是在忙的代碼。。。。。。。
runtime = kzalloc(sizeof(*runtime), GFP_KERNEL); //分配snd_pcm_runtime結構體
size = PAGE_ALIGN(sizeof(struct snd_pcm_mmap_status)); //環形緩沖區結構體大小,需要字節對齊
runtime->status = snd_malloc_pages(size, GFP_KERNEL); //分配頁
size = PAGE_ALIGN(sizeof(struct snd_pcm_mmap_control)); //映射控制
runtime->control = snd_malloc_pages(size, GFP_KERNEL);
init_waitqueue_head(&runtime->sleep); //初始化睡眠等待隊列
init_waitqueue_head(&runtime->tsleep);
runtime->status->state = SNDRV_PCM_STATE_OPEN; //狀態為開啟
substream->runtime = runtime; //賦值給substream
substream->private_data = pcm->private_data;
substream->ref_count = 1;
substream->pid = get_pid(task_pid(current));
pstr->substream_opened++;
*rsubstream = substream;
//給pcm加一些限制,// 為該runtime追加hw硬件rules規則,來最終約束substream所屬聲卡支持的參數特性
err = snd_pcm_hw_constraints_init(substream); //設置一些限制,比較繁瑣,暫不分析
err = substream->ops->open(substream) //調用到pcm的open,主要是初始化code,platform,dai等,單獨分析3
substream->hw_opened = 1; //說明已經打開
err = snd_pcm_hw_constraints_complete(substream); //主要設置一些限制,包括權限,格式,channels等
err = snd_pcm_hw_constraint_mask(runtime, SNDRV_PCM_HW_PARAM_ACCESS, mask);
err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_CHANNELS,
hw->channels_min, hw->channels_max);
3.pcm的open,單獨分析3
static int soc_pcm_open(struct snd_pcm_substream *substream)
pm_runtime_get_sync(cpu_dai->dev);
pm_runtime_get_sync(codec_dai->dev);
pm_runtime_get_sync(platform->dev);
/* startup the audio subsystem */
if (cpu_dai->driver->ops->startup) //這里沒有,建立cpu dai鏈接
ret = cpu_dai->driver->ops->startup(substream, cpu_dai);
if (platform->driver->ops && platform->driver->ops->open)
ret = platform->driver->ops->open(substream); //打開platform,也就是設置DMA相關參數,單獨分析4
//Soc-generic-dmaengine-pcm.c (kernel\sound\soc),調用dmaengine_pcm_open函數
if (codec_dai->driver->ops->startup) //調用codec_dai的startup函數,只是設置了速度限制,單獨分析5
ret = codec_dai->driver->ops->startup(substream, codec_dai);
if (rtd->dai_link->ops && rtd->dai_link->ops->startup) //dai_link的startup函數,暫時沒有
ret = rtd->dai_link->ops->startup(substream);
/* Check that the codec and cpu DAIs are compatible */
//檢查codec和cpu DAIs會不會兼容,檢查的有rate,channels,formats,rates,
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) //這里播放
//取讀取速率的最大值,也就是codec和cpu DAIs都支持的值
runtime->hw.rate_min = max(codec_dai_drv->playback.rate_min, cpu_dai_drv->playback.rate_min);
runtime->hw.rate_max = min(codec_dai_drv->playback.rate_max, cpu_dai_drv->playback.rate_max);
。。。。。。。。
else //錄音,與播放大致一樣的檢查步驟
snd_pcm_limit_hw_rates(runtime); //determine rate_min/rate_max fields,設置最大最小速度
soc_pcm_apply_msb(substream, codec_dai); //設置多少位的限制,codec_dai和cpu_dai一樣
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
bits = dai->driver->playback.sig_bits; //內容的比特數
for (i = 0; i < ARRAY_SIZE(sample_sizes); i++) //這里判斷是24位的還是32位
if (bits >= sample_sizes[i])
continue
//設置限制
ret = snd_pcm_hw_constraint_msbits(substream->runtime, 0, sample_sizes[i], bits);
soc_pcm_apply_msb(substream, cpu_dai);
/* Symmetry only applies if we've already got an active stream. */
//只有當我們已經有了一個活動流時才會出現對稱性
if (cpu_dai->active) //設置對稱性
ret = soc_pcm_apply_symmetry(substream, cpu_dai);
if (codec_dai->active) {
ret = soc_pcm_apply_symmetry(substream, codec_dai);
4.打開platform,也就是DMA相關的東西,單獨分析4
platform相關的參數
static const struct snd_pcm_hardware rockchip_pcm_hardware = {
.info = SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_RESUME,
.formats = SNDRV_PCM_FMTBIT_S24_LE |
SNDRV_PCM_FMTBIT_S20_3LE |
SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S32_LE ,
.channels_min = 2,
.channels_max = 8,
.buffer_bytes_max = 2*1024*1024,/*128*1024,*/
.period_bytes_min = 64,
.period_bytes_max = 512*1024,/*32*1024,//2048*4,///PAGE_SIZE*2,*/
.periods_min = 3,
.periods_max = 128,
.fifo_size = 16,
};
static int dmaengine_pcm_open(struct snd_pcm_substream *substream)
//首先注冊上敘的參數,這個在注冊的platform的會賦值rockchip_pcm_hardware 結構體
ret = snd_soc_set_runtime_hwparams(substream, pcm->config->pcm_hardware);
struct snd_pcm_runtime *runtime = substream->runtime;
runtime->hw.info = hw->info;
runtime->hw.formats = hw->formats;
runtime->hw.period_bytes_min = hw->period_bytes_min;
runtime->hw.period_bytes_max = hw->period_bytes_max;
runtime->hw.periods_min = hw->periods_min;
runtime->hw.periods_max = hw->periods_max;
runtime->hw.buffer_bytes_max = hw->buffer_bytes_max;
runtime->hw.fifo_size = hw->fifo_size;
return snd_dmaengine_pcm_open(substream, chan);
struct dmaengine_pcm_runtime_data *prtd;
//設置限制參數,暫不分析
ret = snd_pcm_hw_constraint_integer(substream->runtime, SNDRV_PCM_HW_PARAM_PERIODS);
prtd = kzalloc(sizeof(*prtd), GFP_KERNEL); //分配dmaengine_pcm_runtime_data,用於記錄DMA數據
prtd->dma_chan = chan;
substream->runtime->private_data = prtd; //把數據賦值給runtime的private_data指針
5.調用codec_dai的startup函數,單獨分析5
Es8323.c (kernel\sound\soc\codecs)
static int es8323_pcm_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
//設置約束,主要是大約的速度,暫不具體分析
snd_pcm_hw_constraint_list(substream->runtime, 0, SNDRV_PCM_HW_PARAM_RATE, es8323->sysclk_constraints);
return snd_pcm_hw_rule_add(runtime, cond, var, snd_pcm_hw_rule_list, (void *)l, var, -1);
三.驅動分析二
snd_pcm_common_ioctl1
case SNDRV_PCM_IOCTL_HW_REFINE:
return snd_pcm_hw_refine_user(substream, arg);
params = memdup_user(_params, sizeof(*params)); //用戶態到內核態的拷貝
err = snd_pcm_hw_refine(substream, params); //根據底層的限制,重新給params 賦值
struct snd_pcm_hw_constraints *constrs = &substream->runtime->hw_constraints;
m = hw_param_mask(params, k);
copy_to_user(_params, params, sizeof(*params)) //拷貝給用戶空間
四.驅動分析三
static int snd_pcm_common_ioctl1(struct file *file, struct snd_pcm_substream *substream, unsigned int cmd, void __user *arg)
case SNDRV_PCM_IOCTL_INFO:
return snd_pcm_info_user(substream, arg); //獲取pcm的信息,包括card,流,dma各種信息
info = kmalloc(sizeof(*info), GFP_KERNEL); //分配snd_pcm_info
info->card = pcm->card->number;
info->device = pcm->device;
info->stream = substream->stream;
info->subdevice = substream->number;
strlcpy(info->id, pcm->id, sizeof(info->id));
strlcpy(info->name, pcm->name, sizeof(info->name));
info->dev_class = pcm->dev_class;
info->dev_subclass = pcm->dev_subclass;
info->subdevices_count = pstr->substream_count;
info->subdevices_avail = pstr->substream_count - pstr->substream_opened;
strlcpy(info->subname, substream->name, sizeof(info->subname));
runtime = substream->runtime;
if (runtime) { //這里沒有什么用
info->sync = runtime->sync;
substream->ops->ioctl(substream, SNDRV_PCM_IOCTL1_INFO, info);
copy_to_user(_info, info, sizeof(*info)) //賦值給用戶空間
五.驅動分析四
case SNDRV_PCM_IOCTL_HW_PARAMS:
return snd_pcm_hw_params_user(substream, arg);
params = memdup_user(_params, sizeof(*params)); //從用戶空間獲取
err = snd_pcm_hw_params(substream, params);
runtime = substream->runtime;
err = snd_pcm_hw_refine(substream, params); //已經分析了,根據底層的限制,重新給params 賦值
//choose a configuration defined by @params
err = snd_pcm_hw_params_choose(substream, params);
//對於所有的參數,包括FORMAT,CHANNELS,根據底層都返回最小值
for (v = vars; *v != -1; v++) // refine config space and return minimum value
err = snd_pcm_hw_param_first(pcm, params, *v, NULL);
if (substream->ops->hw_params != NULL)
//這里只有是設置cpu dai和codec dai的類型和時鍾等
err = substream->ops->hw_params(substream, params); //調用到rk音頻驅動分析之machine的soc_pcm_hw_params函數
copy_to_user(_params, params, sizeof(*params)) //拷貝給用戶空間
六.驅動分析五
DMA內存分配
static const struct vm_operations_struct snd_pcm_vm_ops_data_fault = {
.open = snd_pcm_mmap_data_open,
.close = snd_pcm_mmap_data_close,
.fault = snd_pcm_mmap_data_fault,
};
static int snd_pcm_mmap(struct file *file, struct vm_area_struct *area)
pcm_file = file->private_data;
substream = pcm_file->substream;
offset = area->vm_pgoff << PAGE_SHIFT;
switch (offset) //我們這里應該是snd_pcm_mmap_data
case SNDRV_PCM_MMAP_OFFSET_STATUS:
if (pcm_file->no_compat_mmap)
return snd_pcm_mmap_status(substream, file, area); //只是加了一個操作函數
size = area->vm_end - area->vm_start;
area->vm_ops = &snd_pcm_vm_ops_status;
area->vm_private_data = substream;
area->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP;
case SNDRV_PCM_MMAP_OFFSET_CONTROL:
return snd_pcm_mmap_control(substream, file, area);
default:
return snd_pcm_mmap_data(substream, file, area);
//mmap DMA buffer,分配DMA內存
size = area->vm_end - area->vm_start;
offset = area->vm_pgoff << PAGE_SHIFT;
dma_bytes = PAGE_ALIGN(runtime->dma_bytes); //需要字節對齊
area->vm_ops = &snd_pcm_vm_ops_data; //操作函數
area->vm_private_data = substream; //是substream指針
if (substream->ops->mmap) //調用platform->driver->ops->mmap;,這個有在soc_new_pcm賦值
err = substream->ops->mmap(substream, area); //這里沒有
else
err = snd_pcm_lib_default_mmap(substream, area); //mmap the DMA buffer on RAM
area->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP;
//分配DMA緩沖區
return dma_mmap_coherent(substream->dma_buffer.dev.dev, area, substream->runtime->dma_area,
substream->runtime->dma_addr, area->vm_end - area->vm_start);
/* mmap with fault handler */
area->vm_ops = &snd_pcm_vm_ops_data_fault; //操作函數
七.驅動分析六
//主要是把上層的參數賦值給snd_pcm_runtime
static int snd_pcm_sw_params_user(struct snd_pcm_substream *substream, struct snd_pcm_sw_params __user * _params)
copy_from_user(¶ms, _params, sizeof(params))
err = snd_pcm_sw_params(substream, ¶ms);
struct snd_pcm_runtime *runtime;
.......一些判斷......
runtime->tstamp_mode = params->tstamp_mode; //給runtime相關賦值
runtime->period_step = params->period_step;
runtime->control->avail_min = params->avail_min;
runtime->start_threshold = params->start_threshold;
runtime->stop_threshold = params->stop_threshold;
runtime->silence_threshold = params->silence_threshold;
runtime->silence_size = params->silence_size;
params->boundary = runtime->boundary;
if (snd_pcm_running(substream)) //如果正在運行
//fill ring buffer with silence,用安靜填滿環形緩沖區
snd_pcm_playback_silence(substream, ULONG_MAX);
err = snd_pcm_update_state(substream, runtime); //更新狀態
copy_to_user(_params, ¶ms, sizeof(params))
八.驅動分析七
//prepare the PCM substream to be triggerable,預備pcm可以觸發
static struct action_ops snd_pcm_action_prepare = {
.pre_action = snd_pcm_pre_prepare,
.do_action = snd_pcm_do_prepare,
.post_action = snd_pcm_post_prepare
};
static int snd_pcm_prepare(struct snd_pcm_substream *substream, struct file *file)
// wait until the power-state is changed.等待電源標志變化
if ((res = snd_power_wait(card, SNDRV_CTL_POWER_D0))
init_waitqueue_entry(&wait, current); //這個函數的作用是用新進程來初始化隊列
add_wait_queue(&card->power_sleep, &wait); //把等待元素wait,加到 card->power_slee隊列前面
while (1)
set_current_state(TASK_UNINTERRUPTIBLE);
schedule_timeout(30 * HZ);
res = snd_pcm_action_nonatomic(&snd_pcm_action_prepare, substream, f_flags);
if (snd_pcm_stream_linked(substream)) //查看有沒有連接其他的substream
res = snd_pcm_action_group(ops, substream, state, 0); //暫不分析
else
res = snd_pcm_action_single(ops, substream, state);
res = ops->pre_action(substream, state); //調用snd_pcm_action_prepare ,前面有賦值
//we use the second argument for updating f_flags,這里只是更新f_flags
//這些是文件標志, 例如 O_RDONLY, O_NONBLOCK,驅動應當檢查O_NONBLOCK 標志來看是否是請求非阻塞操作
substream->f_flags = f_flags;
res = ops->do_action(substream, state);
//主要是通知widget更新電源狀態,然后設置聲卡打開播放
err = substream->ops->prepare(substream); //初始化賦值soc_pcm_prepare,單獨分析
return snd_pcm_do_reset(substream, 0);
int err = substream->ops->ioctl(substream, SNDRV_PCM_IOCTL1_RESET, NULL);
if (platform->driver->ops->ioctl)
return platform->driver->ops->ioctl(substream, cmd, arg); //這里調用rk音頻驅動之platform的snd_pcm_lib_ioctl
return snd_pcm_lib_ioctl(substream, cmd, arg); //這里不會到這里
runtime->hw_ptr_base = 0;
.....環形緩沖區指針狀態相關的reset...
ops->post_action(substream, state); //初始化賦值snd_pcm_post_prepare
runtime->control->appl_ptr = runtime->status->hw_ptr;
snd_pcm_set_state(substream, SNDRV_PCM_STATE_PREPARED); //更新狀態/為* stream is ready to start */
1.soc_pcm_prepare
/*
* Called by ALSA when the PCM substream is prepared, can set format, sample
* rate, etc. This function is non atomic and can be called multiple times,
* it can refer to the runtime info.
*/
if (rtd->dai_link->ops && rtd->dai_link->ops->prepare)
ret = rtd->dai_link->ops->prepare(substream); //我們這里沒有
if (platform->driver->ops && platform->driver->ops->prepare) //這里沒有
ret = codec_dai->driver->ops->prepare(substream, codec_dai);
if (cpu_dai->driver->ops->prepare) //這里沒有
ret = cpu_dai->driver->ops->prepare(substream, cpu_dai);
//Sends a stream event to the dapm core. The core then makes any necessary widget power changes.
//這里會調動到widget
snd_soc_dapm_stream_event(rtd, substream->stream, SND_SOC_DAPM_STREAM_START);
soc_dapm_stream_event(rtd, stream, event);
if (stream == SNDRV_PCM_STREAM_PLAYBACK)
w_cpu = cpu_dai->playback_widget;
w_codec = codec_dai->playback_widget;
else
......
if (w_cpu)
dapm_mark_dirty(w_cpu, "stream event"); //加入dapm_dirty鏈表
list_add_tail(&w->dirty, &w->dapm->card->dapm_dirty);
if (w_codec)
dapm_mark_dirty(w_codec, "stream event");
//Scan each dapm widget for complete audio path,這里就是搜索path
dapm_power_widgets(&rtd->card->dapm, event);
snd_soc_dai_digital_mute(codec_dai, 0, substream->stream);
//Mutes the DAI DAC.
if (dai->driver->ops->mute_stream)
return dai->driver->ops->mute_stream(dai, mute, direction); //我們這里沒有
else if (direction == SNDRV_PCM_STREAM_PLAYBACK && dai->driver->ops->digital_mute)
return dai->driver->ops->digital_mute(dai, mute); //調用rk音頻驅動分析之codec里面的es8323_mute,進行播放准備
九.驅動分析八
snd_pcm_playback_ioctl里面的snd_pcm_playback_ioctl1的
static int snd_pcm_playback_ioctl1(struct file *file, struct snd_pcm_substream *substream, unsigned int cmd, void __user *arg)
switch (cmd)
case SNDRV_PCM_IOCTL_WRITEI_FRAMES:
if (put_user(0, &_xferi->result)) //給應用發送0的返回值
copy_from_user(&xferi, _xferi, sizeof(xferi)) //從用戶拷貝數據
result = snd_pcm_lib_write(substream, xferi.buf, xferi.frames);
/* sanity-check for read/write methods */
err = pcm_sanity_check(substream); //檢查有不有讀寫函數
runtime = substream->runtime;
nonblock = !!(substream->f_flags & O_NONBLOCK);
//傳輸函數,單獨分析, snd_pcm_lib_write_transfer這個也要單獨分析
return snd_pcm_lib_write1(substream, (unsigned long)buf, size, nonblock, snd_pcm_lib_write_transfer);
1.snd_pcm_lib_write1
runtime->twake = runtime->control->avail_min ? : 1;
if (runtime->status->state == SNDRV_PCM_STATE_RUNNING) //如果正在進行
snd_pcm_update_hw_ptr(substream); ///* CAUTION: call it with irq disabled */
return snd_pcm_update_hw_ptr0(substream, 0); //暫不分析
avail = snd_pcm_playback_avail(runtime); //獲取當前ring buf的可用大小
// 讀指針+總的buf size-寫指針的數值
//上圖白色部分avail ,灰色是已經用掉的
snd_pcm_sframes_t avail = snd_pcm_sframes_t avail = runtime->status->hw_ptr + runtime->buffer_size - runtime->control->appl_ptr;;
if (avail < 0) //讀寫指針換位的時候
avail += runtime->boundary; // avail=runtime->boundary + avail;加負數,還是等於可以用的
else if ((snd_pcm_uframes_t) avail >= runtime->boundary) //溢出了,無可用的,已經是負的
avail -= runtime->boundary; //avail = runtime->boundary - avail ; //返回負的可以的大小
while (size > 0)
snd_pcm_uframes_t frames, appl_ptr, appl_ofs; //每塊buf
snd_pcm_uframes_t cont;
if (!avail) //無可用的緩沖區
if (nonblock) //非阻塞模式,返回
err = -EAGAIN;
//如果非0,就喚醒,然后開始傳輸
runtime->twake = min_t(snd_pcm_uframes_t, size, runtime->control->avail_min ? : 1);
//Wait until avail_min data becomes available,等待avail_min 可以用
err = wait_for_avail(substream, &avail);
frames = size > avail ? avail : size; //得到frames 大小
cont = runtime->buffer_size - runtime->control->appl_ptr % runtime->buffer_size;
appl_ptr = runtime->control->appl_ptr; //HW BUF寫指針的位置
appl_ofs = appl_ptr % runtime->buffer_size; //hw_ofs是讀指針在當前HW buffer中的位置。
//傳輸,這個是傳進來的snd_pcm_lib_write_transfer函數指針,單獨分析2
err = transfer(substream, appl_ofs, data, offset, frames); //這里只是從用戶空間把數據拷貝到DMA
appl_ptr += frames; //寫指針移位
if (appl_ptr >= runtime->boundary) //如果已經越位
appl_ptr -= runtime->boundary; //回到環形緩沖區頭部
runtime->control->appl_ptr = appl_ptr;
if (substream->ops->ack) //這里沒有
substream->ops->ack(substream);
offset += frames; //offset = offset + frames //偏移增加
size -= frames; // size = size - frames;
xfer += frames; //xfer = xfer + frames;
avail -= frames; //avail = avail - frames;
if (runtime->status->state == SNDRV_PCM_STATE_PREPARED && //之前已經准備好傳輸了
snd_pcm_playback_hw_avail(runtime) >= (snd_pcm_sframes_t)runtime->start_threshold) //可以讀的界限>可以傳輸的界限
err = snd_pcm_start(substream); //開始傳輸,單獨分析3
2.單獨分析2:snd_pcm_lib_write_transfer
static int snd_pcm_lib_write_transfer(struct snd_pcm_substream *substream, unsigned int hwoff,
unsigned long data, unsigned int off, snd_pcm_uframes_t frames)
if (substream->ops->copy) //這里沒有
err = substream->ops->copy(substream, -1, hwoff, buf, frames) //這里會調用platform->driver->ops->copy;
platform->driver->ops->copy; //沒有
else
char *hwbuf = runtime->dma_area + frames_to_bytes(runtime, hwoff);
copy_from_user(hwbuf, buf, frames_to_bytes(runtime, frames)) //從用戶空間拷貝
單獨分析3:snd_pcm_start
static struct action_ops snd_pcm_action_start = {
.pre_action = snd_pcm_pre_start,
.do_action = snd_pcm_do_start,
.undo_action = snd_pcm_undo_start,
.post_action = snd_pcm_post_start
};
int snd_pcm_start(struct snd_pcm_substream *substream)
return snd_pcm_action(&snd_pcm_action_start, substream, SNDRV_PCM_STATE_RUNNING);
res = snd_pcm_action_single(ops, substream, state);
res = ops->pre_action(substream, state); //調用snd_pcm_pre_start
//check whether any data exists on the playback buffer
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && !snd_pcm_playback_data(substream))
if (runtime->stop_threshold >= runtime->boundary)
return 1;
//如果可用的buf少於總的的buf,說明有數據,就是可以的
return snd_pcm_playback_avail(runtime) < runtime->buffer_size;
runtime->trigger_master = substream;
res = ops->do_action(substream, state); \\調用snd_pcm_do_start
return substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_START); //開始傳輸,單獨分析4
ops->post_action(substream, state); \\調用snd_pcm_post_start
snd_pcm_trigger_tstamp(substream); //獲取時間
runtime->hw_ptr_jiffies = jiffies; //系統時間
runtime->hw_ptr_buffer_jiffies = (runtime->buffer_size * HZ) / runtime->rate; //傳輸需要的時間
runtime->status->state = state; //SNDRV_PCM_STATE_RUNNING
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && runtime->silence_size > 0)
//when runtime->silence_size >= runtime->boundary - fill processed area with silence immediately
snd_pcm_playback_silence(substream, ULONG_MAX); //如果有silence_size ,就把他填到緩沖區里
if (substream->timer)
snd_timer_notify(substream->timer, SNDRV_TIMER_EVENT_MSTART, &runtime->trigger_tstamp);
單獨分析4:soc_pcm_trigger
static int soc_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
if (codec_dai->driver->ops->trigger) //這里沒有
ret = codec_dai->driver->ops->trigger(substream, cmd, codec_dai);
if (platform->driver->ops && platform->driver->ops->trigger) //進行DMA傳輸到I2S控制器
ret = platform->driver->ops->trigger(substream, cmd); //rk音頻驅動之platform的snd_dmaengine_pcm_trigger
if (cpu_dai->driver->ops->trigger) //調用rk音頻驅動分析之codec的rockchip_i2s_trigger
ret = cpu_dai->driver->ops->trigger(substream, cmd, cpu_dai); //從i2s寄存器到codec的傳輸
