rk音頻驅動分析之tinycap錄音


一.Tinycap分析
使用命令:tinycap /sdcard/test.wav -D card0 -d device0 -c 2 -r 48000 -b 16 -p 1024 -n 16
Tinycap.c (external\tinyalsa)
錄音流程:
    #define ID_RIFF 0x46464952 //這個值是RIFF的ASCII值
    #define ID_WAVE 0x45564157 //這個值是WAVE的ASCII值
    #define ID_FMT 0x20746d66
    #define ID_DATA 0x61746164 //這個值是DATA的ASCII值

main函數
    header.riff_id = ID_RIFF;  //這里是RIFF,標志符
    header.riff_sz = 0;
    header.riff_fmt = ID_WAVE; // 格式類型WAVE
    header.fmt_id = ID_FMT; //波形文件標志:FMT(最后一位空格符)
    header.fmt_sz = 16; //數值是16或者18,18是有附帶了信息
    header.audio_format = FORMAT_PCM; //編碼方式,一般是1
    header.num_channels = channels;  //聲道數值,1是單聲道,2是雙聲道
    header.sample_rate = rate; //采樣頻率
    format = PCM_FORMAT_S24_LE; //每個采樣需要的bytes
    header.bits_per_sample = pcm_format_to_bits(format); //轉化為bit數
    header.byte_rate = (header.bits_per_sample / 8) * channels * rate; //每秒傳輸的byte數
    header.block_align = channels * (header.bits_per_sample / 8);    //每個采樣的byte數
    header.data_id = ID_DATA; //Data Chunk,數據塊
    
    /* leave enough room for header */
    fseek(file, sizeof(struct wav_header), SEEK_SET); //給header足夠的空間
    /* install signal handler and begin capturing */
    signal(SIGINT, sigint_handler); //SIGINT   :來自鍵盤的中斷信號 ( ctrl + c ) .
    //開始錄制,單獨分析1
    frames = capture_sample(file, card, device, header.num_channels,  header.sample_rate, format,  period_size, period_count);


1.單獨分析1
unsigned int capture_sample(FILE *file, unsigned int card, unsigned int device, unsigned int channels, unsigned int rate,
                            enum pcm_format format, unsigned int period_size, unsigned int period_count)
    pcm = pcm_open(card, device, PCM_IN, &config);
        pcm->config = *config;
        
        snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device, flags & PCM_IN ? 'c' : 'p');
        pcm->fd = open(fn, O_RDWR); //打開/dev/snd/pcmC0D0c,與rk音頻驅動分析之tinyplay播放類似
        //獲取pcm的信息,包括card,流,dma各種信息,與rk音頻驅動分析之tinyplay播放類似
        ioctl(pcm->fd, SNDRV_PCM_IOCTL_INFO, &info)
        param_init(&params); //限制參數初始化
        .............一些限制參數設置..............
         param_set_flag(&params, config->flag);
        //根據底層的限制,重新給params賦值,設置cpu dai和codec dai的類型和時鍾等
        ioctl(pcm->fd, SNDRV_PCM_IOCTL_HW_PARAMS, &params) //與rk音頻驅動分析之tinyplay播放類似
        /* get our refined hw_params */
        config->period_size = param_get_int(&params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE); //得到period大小
        config->period_count = param_get_int(&params, SNDRV_PCM_HW_PARAM_PERIODS); //數量
        pcm->buffer_size = config->period_count * config->period_size; //總的buf大小
        if (flags & PCM_MMAP) //如果是映射,
            //mmap將一個文件或者其它對象映射進內存,調用底層的mmap函數,與rk音頻驅動分析之tinyplay播放類似
             pcm->mmap_buffer = mmap(NULL, pcm_frames_to_bytes(pcm, pcm->buffer_size),
                                PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, pcm->fd, 0);
        //包括一些period_step ,silence_threshold 等參數,與rk音頻驅動分析之tinyplay播放類似
        ioctl(pcm->fd, SNDRV_PCM_IOCTL_SW_PARAMS, &sparams)  //主要是把上層的參數賦值給snd_pcm_runtime
        //與rk音頻驅動分析之tinyplay播放類似
        rc = pcm_hw_mmap_status(pcm); //分配SNDRV_PCM_MMAP_OFFSET_CONTROL的內存映射,
        #ifdef SNDRV_PCM_IOCTL_TTSTAMP
        int arg = SNDRV_PCM_TSTAMP_TYPE_MONOTONIC;
        rc = ioctl(pcm->fd, SNDRV_PCM_IOCTL_TTSTAMP, &arg); //設置為絕對時間
    size = pcm_frames_to_bytes(pcm, pcm_get_buffer_size(pcm)); //得到ring buf的大小
    buffer = malloc(size); //分配
    while (capturing && !pcm_read(pcm, buffer, size)) //從PCM里面讀取數據到buffer里面,單獨分析2
        if (fwrite(buffer, 1, size, file) != size) //把buf的數據寫入到file
        bytes_read += size; //+size

單獨分析2
int pcm_read(struct pcm *pcm, void *data, unsigned int count)
    x.buf = data; //buf的地址
    x.frames = count / (pcm->config.channels * pcm_format_to_bits(pcm->config.format) / 8); //表示有多少幀
    for (;;)
        if (!pcm->running)
            if (pcm_start(pcm) < 0) //調用驅動預備pcm可以觸發,這里就是搜索path,widgets相關,
                ioctl(pcm->fd, SNDRV_PCM_IOCTL_PREPARE) //與rk音頻驅動分析之tinyplay播放類似
            ioctl(pcm->fd, SNDRV_PCM_IOCTL_READI_FRAMES, &x) //這里調用驅動進行讀取操作,驅動分析一


二.驅動分析一
調用snd_pcm_capture_ioctl的snd_pcm_capture_ioctl1函數
static int snd_pcm_capture_ioctl1(struct file *file, struct snd_pcm_substream *substream, unsigned int cmd, void __user *arg)
    switch (cmd)
    case SNDRV_PCM_IOCTL_READI_FRAMES:
        struct snd_xferi __user *_xferi = arg; //獲取應用傳來的參數
        copy_from_user(&xferi, _xferi, sizeof(xferi)) //把_xferi參數結構體從用戶空間拷貝過來
        result = snd_pcm_lib_read(substream, xferi.buf, xferi.frames); //讀取數據
            err = pcm_sanity_check(substream);  //檢查有不有讀寫函數
            //用snd_pcm_lib_read_transfer函數讀取數據,單獨分析1
            snd_pcm_lib_read1(substream, (unsigned long)buf, size, nonblock, snd_pcm_lib_read_transfer);


單獨分析1
static snd_pcm_sframes_t snd_pcm_lib_read1(struct snd_pcm_substream *substream, unsigned long data,
        snd_pcm_uframes_t size, int nonblock,  transfer_f transfer)
    switch (runtime->status->state)
    case SNDRV_PCM_STATE_PREPARED:
        //開始傳輸,與rk音頻驅動分析之tinyplay播放相似,我們這里是錄音,通路方向不一樣
        //主要是啟動DMA開始讀取數據,然后啟動I2S開始讀取codec的數據
        err = snd_pcm_start(substream); //start all linked streams
    ........
    if (runtime->status->state == SNDRV_PCM_STATE_RUNNING)
        snd_pcm_update_hw_ptr(substream); //更新ring buf相關的位置
    avail = snd_pcm_capture_avail(runtime);  //獲取DMA已經寫入的ring緩沖區大小,就是可讀數據的大小
        snd_pcm_sframes_t avail = runtime->status->hw_ptr - runtime->control->appl_ptr; 
    while (size > 0)
        if (!avail) //沒有可讀的數據
            if (runtime->status->state == SNDRV_PCM_STATE_DRAINING) //暫時不知道
                snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP); //停止
            if (nonblock) //如果是非阻塞,直接返回
                err = -EAGAIN;
            err = wait_for_avail(substream, &avail); //等待
        frames = size > avail ? avail : size; //讀取的大小
        //這里還沒有搞懂
        cont = runtime->buffer_size - runtime->control->appl_ptr % runtime->buffer_size;
        if (frames > cont)
            frames = cont;
        appl_ptr = runtime->control->appl_ptr; //讀指針的值
        appl_ofs = appl_ptr % runtime->buffer_size; //讀指針在buf里面的偏移
        err = transfer(substream, appl_ofs, data, offset, frames); //單獨分析2,這里是把數據拷貝給user
        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; //偏移增加
        size -= frames; //size減少
        xfer += frames; //傳輸xfer增加
        avail -= frames; //可讀的減少


2.單獨分析2
static int snd_pcm_lib_read_transfer(struct snd_pcm_substream *substream, unsigned int hwoff,
         unsigned long data, unsigned int off, snd_pcm_uframes_t frames)
    //獲取buf的地址,這里off是偏移地址
    char __user *buf = (char __user *) data + frames_to_bytes(runtime, off);
    if (substream->ops->copy) //這里沒有
        err = substream->ops->copy(substream, -1, hwoff, buf, frames)
    else
        char *hwbuf = runtime->dma_area + frames_to_bytes(runtime, hwoff); //獲取ring buf讀的地址
        copy_to_user(buf, hwbuf, frames_to_bytes(runtime, frames)) //把dma的從hwbuf開始的數據拷貝到user,大小為frames


        
        
            

        

    



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM