采样 I2S 数据并以 wav 压缩原始声音的形式存储到 SD 卡?

如何解决采样 I2S 数据并以 wav 压缩原始声音的形式存储到 SD 卡?

我担心使用 ESP32 WROOM 将来自 2 个 I2S MEMS 麦克风的音频数据存储到 SD 卡,但似乎没有运气这样做...

我的想法: 通过轮询 espressif I2S-API 提供的中断标志(在 DMA 缓冲区已满时激活),我触发了 I2S_read 调用并将获取的数据保存到更大的缓冲区。随着时间的推移,这个缓冲区被填满并被传送到 SD 卡,一个 wav 头已经在那里等待数据。标题中的长度信息由最后一个写入任务操作,当按下记录按钮时,该任务从操作系统时间函数millis() 和按下按钮时的另一个调用millis() 的减法中获取其信息再次。

我的资源:

  • ESP32 WROOM
  • 2 个 ADAFRUIT SPH0645 MEMS 麦克风(I2S 立体声)
  • ADAFRUIT 微型 SD 卡适配器 (SPI)
  • 外部中断按钮
  • platformIO
  • freeRTOS
  • 采样率 44100 Hz
  • 位深度 32 位
  • 立体声

我的问题: 除了数据丢失和其他噪音外,还有一个更让我担心的问题:我的文件代表的声音速度提高了两倍,但不是音调的两倍,这意味着它不应该只是一个 wav-标题问题。查看 HxD 中的标题会显示标题中的正确数字,因此我的采样有一个基本缺陷,我没有看到。正如我所说,采样充满噪音,但这应该可以通过巧妙的缓冲区调整来解决。然而,时间问题不是。有人能够发现这个基本问题吗?我确定它只是在某个地方改变了一个数字,但在我看来,一切都应该计算......

我尝试了什么: 改变采样率或切换到单声道确实会改变音高,但速度似乎随之线性变化,总是比预期的两倍,这表明这确实不是标题问题。即使在从超级循环到基于任务的代码完全重做之后,仍然存在的一个错误是双速问题,这对我来说很奇怪。

我的代码:

这个任务在大数组中读取和存储数据以供进一步处理

void readI2STask(void* param){

  //DEBUG
  digitalWrite(WHITE_LED_PIN,LOW);

  //init I2S
  I2S_Init(I2S_MODE_RX,I2S_BITS_PER_SAMPLE_32BIT);

  //fix for SPH645-specific timing-error:
  REG_SET_BIT(I2S_TIMING_REG(I2S_NUM_0),BIT(9));
  REG_SET_BIT(I2S_CONF_REG(I2S_NUM_0),I2S_RX_MSB_SHIFT);

  //index for globally accessible array
  uint16_t bigBufferIndex = 0;

  while(!powerOff_flag){

    i2s_event_t evt;
    //poll for ready-flag in queue
    if (xQueueReceive(_i2sSampleQueue,&evt,portMAX_DELAY) == pdPASS){
      if (evt.type == I2S_EVENT_RX_DONE){
        size_t bytesRead = 0;

        do{
          uint8_t i2sData[SMALL_BUF_LEN];
          i2s_read(I2S_NUM_0,i2sData,SMALL_BUF_LEN,&bytesRead,10);
          int32_t* samples = (int32_t*)i2sData;

          for (int i = 0; i < bytesRead / 4; i++){
            SDBufMem1[bigBufferIndex] = samples[i];
            bigBufferIndex++;
            if (bigBufferIndex == SD_BUF_LEN_32BIT){
              std::swap(SDBufMem1,SDBufMem2);
              bigBufferIndex = 0;
              bufFlag = (!bufFlag); //shows other functions which buffer is ready
              if(recButton_flag){
                xTaskNotify(_sendToSDTask,1,eIncrement);
              }
              else{
                //xTaskNotify(_LCD_displayVUTask,eIncrement);
              }
            }
          }
        } while (bytesRead > 0);
      }
    }
  }
}

此任务将可用数据存储到 SD

void sendToSDTask(void* param){

  vTaskSuspend(_LCD_displayVUTask);

  static char filename[] = "/rec000.wav";
  const int headerSize = 512;

  //default file-time = 1 second
  const int waveDataSize = 2 * 44100 * 4;

  byte header[headerSize];
  File file;
  const TickType_t xMaxBlockTime = pdMS_TO_TICKS(100);

  //create RIFF-WAV-Header
  CreateWavHeader(header,waveDataSize);

  //find valid name
  findName(filename);

  //open/make file on SD card with given name
  file = SD.open(filename,FILE_WRITE);
  if(!file){

    //TODO: exception handler for abrupt disconnection of SD-card before recording

  }

  //induce wav-header into start of file
  file.write(header,headerSize);
  
  //red means recording!
  digitalWrite(RED_LED_PIN,HIGH);

  unsigned long startTime = millis();
  unsigned long endTime;

  while (recButton_flag)
  {

    //wait for notofication from 16k array
    uint32_t ulNotificationValue = ulTaskNotifyTake(pdTRUE,xMaxBlockTime);
    if (ulNotificationValue > 0)
    {


      if(bufFlag){
        //1st buffer ready
        file.write((const uint8_t*)SDBufMem1,(size_t)(SD_BUF_LEN_32BIT));
      }
      else{
        //2nd buffer ready
        file.write((const uint8_t*)SDBufMem2,(size_t)(SD_BUF_LEN_32BIT));
      }
      


    }
  }
  //calculate recording-lenght and save it to header at the start of the file
  endTime = millis();
  digitalWrite(RED_LED_PIN,LOW);
  CreateWavHeader(header,((endTime-startTime) * 2* 4 * 44100 / 1000));
  file.seek(0);
  file.write(header,headerSize);
  file.close();
  
  vTaskResume(_LCD_displayVUTask);
  vTaskDelete(NULL);
}

wav-header-creater

void CreateWavHeader(byte* header,int waveDataSize){
  header[0] = 'R';
  header[1] = 'I';
  header[2] = 'F';
  header[3] = 'F';
  unsigned int fileSizeMinus8 = waveDataSize + 512 - 8;
  header[4] = (byte)(fileSizeMinus8 & 0xFF);
  header[5] = (byte)((fileSizeMinus8 >> 8) & 0xFF);
  header[6] = (byte)((fileSizeMinus8 >> 16) & 0xFF);
  header[7] = (byte)((fileSizeMinus8 >> 24) & 0xFF);
  header[8] = 'W';
  header[9] = 'A';
  header[10] = 'V';
  header[11] = 'E';
  header[12] = 'f';
  header[13] = 'm';
  header[14] = 't';
  header[15] = ' ';
  header[16] = 0x10;  // fmt lenght = 16byte
  header[17] = 0x00;  // 
  header[18] = 0x00;  //
  header[19] = 0x00;  //
  header[20] = 0x01;  // format tag = 1 = PCM
  header[21] = 0x00;  //
  header[22] = 0x02;  // channels = 2 = stereo
  header[23] = 0x00;  //
  header[24] = 0x44;  // sampling rate = 44100
  header[25] = 0xAC;  //
  header[26] = 0x00;  //
  header[27] = 0x00;  //
  header[28] = 0x20;  // Byte/sec = 44100 * 2 * 4 = 352800
  header[29] = 0x62;  //
  header[30] = 0x05;  //
  header[31] = 0x00;  //
  header[32] = 0x08;  // block align (n channels = 2 * (bits/sample of one channel = 32 + 7)/8) = 8
  header[33] = 0x00;  //
  header[34] = 0x20;  // bits/sample = 32
  header[35] = 0x00;  //
  header[36] = 'p';   //new padding sub-chunk. Increases SD-performance since it writes in 512 byte sized blocks naturally
  header[37] = 'a';   //
  header[38] = 'd';   //
  header[39] = 'd';   //
  header[40] = 0xCC;  //padlen is rest of header - 512 = 460
  header[41] = 0x01;  //
  header[42] = 0x00;  //
  header[43] = 0x00;  //
  for(int i = 44; i < 504; i++){
    header[i] = 0x00;
  }
  header[504] = 'd';  //data sub-chunk
  header[505] = 'a';  //
  header[506] = 't';  // 
  header[507] = 'a';  //
  header[508] = (byte)(waveDataSize & 0xFF);
  header[509] = (byte)((waveDataSize >> 8) & 0xFF);
  header[510] = (byte)((waveDataSize >> 16) & 0xFF);
  header[511] = (byte)((waveDataSize >> 24) & 0xFF);
  //real samples get appended here
}

I2S 初始化函数

void I2S_Init(i2s_mode_t MODE,i2s_bits_per_sample_t BPS)
{
    //general config of I2S
    //const: 44100 sample rate,stereo
    i2s_config_t i2s_config = {
        .mode = (i2s_mode_t)(I2S_MODE_MASTER | MODE),.sample_rate = SAMPLE_RATE,.bits_per_sample = BPS,.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S),.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,.dma_buf_count = 4,.dma_buf_len = 1024,.use_apll = false,.tx_desc_auto_clear = false,.fixed_mclk = 0
    };

    //pin format,adjust in i2s.h if needed:
    i2s_pin_config_t pin_config = {
        .bck_io_num = PIN_I2S_BCLK,.ws_io_num = PIN_I2S_LRC,};
    //i tried putting this into statement above,threw an error (?)
    pin_config.data_in_num = PIN_I2S_DIN;



    //init I2S-driver/peripherals
    i2s_driver_install(I2S_NUM_0,&i2s_config,8,&_i2sSampleQueue);
    i2s_set_pin(I2S_NUM_0,&pin_config);
    i2s_set_clk(I2S_NUM_0,SAMPLE_RATE,BPS,I2S_CHANNEL_STEREO);
}

感谢您的帮助,非常感谢。 ~杰克

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-
参考1 参考2 解决方案 # 点击安装源 协议选择 http:// 路径填写 mirrors.aliyun.com/centos/8.3.2011/BaseOS/x86_64/os URL类型 软件库URL 其他路径 # 版本 7 mirrors.aliyun.com/centos/7/os/x86
报错1 [root@slave1 data_mocker]# kafka-console-consumer.sh --bootstrap-server slave1:9092 --topic topic_db [2023-12-19 18:31:12,770] WARN [Consumer clie
错误1 # 重写数据 hive (edu)&gt; insert overwrite table dwd_trade_cart_add_inc &gt; select data.id, &gt; data.user_id, &gt; data.course_id, &gt; date_format(
错误1 hive (edu)&gt; insert into huanhuan values(1,&#39;haoge&#39;); Query ID = root_20240110071417_fe1517ad-3607-41f4-bdcf-d00b98ac443e Total jobs = 1
报错1:执行到如下就不执行了,没有显示Successfully registered new MBean. [root@slave1 bin]# /usr/local/software/flume-1.9.0/bin/flume-ng agent -n a1 -c /usr/local/softwa
虚拟及没有启动任何服务器查看jps会显示jps,如果没有显示任何东西 [root@slave2 ~]# jps 9647 Jps 解决方案 # 进入/tmp查看 [root@slave1 dfs]# cd /tmp [root@slave1 tmp]# ll 总用量 48 drwxr-xr-x. 2
报错1 hive&gt; show databases; OK Failed with exception java.io.IOException:java.lang.RuntimeException: Error in configuring object Time taken: 0.474 se
报错1 [root@localhost ~]# vim -bash: vim: 未找到命令 安装vim yum -y install vim* # 查看是否安装成功 [root@hadoop01 hadoop]# rpm -qa |grep vim vim-X11-7.4.629-8.el7_9.x
修改hadoop配置 vi /usr/local/software/hadoop-2.9.2/etc/hadoop/yarn-site.xml # 添加如下 &lt;configuration&gt; &lt;property&gt; &lt;name&gt;yarn.nodemanager.res