9.25. AUDIO_VFS_OPS

概述

AUDIO_VFS_OPS使用说明,编码输出数据或解码输入数据;编码输出非文件形式或解码输入非文件形式下使用

9.25.1. 应用实例

  • 可查看 apps/demo/demo_audio/demo/recorder.c 中recorder_play_to_dac(),该函数实现由mic通道采样的数据进行编码输出pcm数据,然后pcm数据再作为解码器的输入数据进行解码dac播放,实现边录音边播放

9.25.2. 详细过程

/**
* @brief 音频虚拟文件操作句柄
*/
struct audio_vfs_ops {
    void *(*fopen)(const char *path, const char *mode);  /*!< 打开创建路径文件 */
    int (*fread)(void *file, void *buf, u32 len);        /*!< 读文件 */
    int (*fwrite)(void *file, void *buf, u32 len);       /*!< 写文件 */
    int (*fseek)(void *file, u32 offset, int seek_mode); /*!< 寻址文件 */
    int (*ftell)(void *file);                            /*!< 返回给定流stream的当前文件位置 */
    int (*flen)(void *file);                             /*!< 获取文件长度 */
    int (*fclose)(void *file);                           /*!< 关闭文件 */
};

//例程中注册的操作句柄
static const struct audio_vfs_ops recorder_vfs_ops = {
.fwrite = recorder_vfs_fwrite,
.fread  = recorder_vfs_fread,
.fclose = recorder_vfs_fclose,
.flen   = recorder_vfs_flen,
};
  • 1.例程中流程是先打开解码器,再打开编码器,为的是防止反过来打开编码器打开了有编码数据输出然后解码器还没打开完成造成一开始的边录边播数据丢失

  • 2.编码器部分。 编码参数使用req.enc.vfs_ops = &recorder_vfs_ops; //编码使用vfs_ops输出数据,编码完成之后的数据就会输出到vfs_ops操作句柄的fwrite

//编码器输出PCM数据
static int recorder_vfs_fwrite(void *file, void *data, u32 len)
{
    cbuffer_t *cbuf = (cbuffer_t *)file;
    if (0 == cbuf_write(cbuf, data, len)) {
        //上层buf写不进去时清空一下,避免出现声音滞后的情况
        cbuf_clear(cbuf);
    }
    os_sem_set(&__this->r_sem, 0);
    os_sem_post(&__this->r_sem);
    ...
     ...
    return len;
}

/****************打开编码器*******************/
memset(&req, 0, sizeof(union audio_req));

//BIT(x)用来区分上层需要获取哪个通道的数据
if (channel == 2) {
    req.enc.channel_bit_map = BIT(CONFIG_AUDIO_ADC_CHANNEL_L) | BIT(CONFIG_AUDIO_ADC_CHANNEL_R);
} else {
    req.enc.channel_bit_map = BIT(CONFIG_AUDIO_ADC_CHANNEL_L);
}
req.enc.frame_size = sample_rate / 100 * 4 * channel;       //收集够多少字节PCM数据就回调一次fwrite
req.enc.output_buf_len = req.enc.frame_size * 3; //底层缓冲buf至少设成3倍frame_size
req.enc.cmd = AUDIO_ENC_OPEN;
req.enc.channel = channel;
req.enc.volume = __this->gain;
req.enc.sample_rate = sample_rate;
req.enc.format = "pcm";
req.enc.sample_source = __this->sample_source;
req.enc.vfs_ops = &recorder_vfs_ops;             //编码使用vfs_ops输出数据
req.enc.file = (FILE *)&__this->save_cbuf;
if (channel == 1 && !strcmp(__this->sample_source, "mic") && (sample_rate == 8000 || sample_rate == 16000)) {
    req.enc.use_vad = 1; //打开VAD断句功能
    req.enc.dns_enable = 1; //打开降噪功能
    req.enc.vad_auto_refresh = 1; //VAD自动刷新
}

err = server_request(__this->enc_server, AUDIO_REQ_ENC, &req);
if (err) {
    goto __err1;
}
  • 3.解码器部分。 解码参数使用req.enc.vfs_ops = &recorder_vfs_ops; //解码使用vfs_ops输入数据,解码开始后在vfs_ops的fread回调中读取数据到解码器进行解码

//解码器读取PCM数据
static int recorder_vfs_fread(void *file, void *data, u32 len)
{
    cbuffer_t *cbuf = (cbuffer_t *)file;
    u32 rlen;

    do {
        rlen = cbuf_get_data_size(cbuf);
        rlen = rlen > len ? len : rlen;
        if (cbuf_read(cbuf, data, rlen) > 0) {
            len = rlen;
            break;
        }
        //此处等待信号量是为了防止解码器因为读不到数而一直空转
        os_sem_pend(&__this->r_sem, 0);
        if (!__this->run_flag) {
            return 0;
        }
    } while (__this->run_flag);

    //返回成功读取的字节数
    return len;
}

/****************打开解码DAC器*******************/
req.dec.cmd             = AUDIO_DEC_OPEN;
req.dec.volume          = __this->volume;
req.dec.output_buf_len  = 4 * 1024;
req.dec.channel         = channel;
req.dec.sample_rate     = sample_rate;
req.dec.vfs_ops         = &recorder_vfs_ops;  //解码使用vfs_ops输入数据
req.dec.dec_type            = "pcm";
req.dec.sample_source   = CONFIG_AUDIO_DEC_PLAY_SOURCE;
req.dec.file            = (FILE *)&__this->save_cbuf;
/* req.dec.attr            = AUDIO_ATTR_LR_ADD; */          //左右声道数据合在一起,封装只有DACL但需要测试两个MIC时可以打开此功能

err = server_request(__this->dec_server, AUDIO_REQ_DEC, &req);
if (err) {
    goto __err;
}

req.dec.cmd = AUDIO_DEC_START;
req.dec.attr = AUDIO_ATTR_NO_WAIT_READY;
server_request(__this->dec_server, AUDIO_REQ_DEC, &req);