OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 321|回复: 0

《ESP32-P4开发指南— V1.0》第四十四章 录音实验

[复制链接]

1262

主题

1276

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
5418
金钱
5418
注册时间
2019-5-8
在线时间
1404 小时
发表于 2026-2-9 09:22:30 | 显示全部楼层 |阅读模式
第四十四章 录音实验

1)实验平台:正点原子DNESP32P4开发板

2)章节摘自【正点原子】ESP32-P4开发指南— V1.0

3)购买链接:https://detail.tmall.com/item.htm?id=873309579825

4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/esp32/ATK-DNESP32P4.html

5)正点原子官方B站:https://space.bilibili.com/394620890

6)正点原子DNESP32S3开发板技术交流群:132780729


2.jpg

3.png

上一章,我们实现了一个简单的音乐播放器,本章我们将在上一章的基础上,继续用ES8388实现一个简单的录音机,录制WAV格式的录音。
本章分为如下几个部分:
44.1 I2S录音简介
44.2 硬件设计
44.3 软件设计
44.4 下载验证


44.1 I2S录音简介
本章涉及的知识点基本上在上一章都有介绍。本章要实现WAV录音,还是和上一章一样,要了解:WAV文件格式、ES8388和I2S接口。这些知识,我们在上一章已经做了详细介绍了,这里就不作介绍了。

44.2 硬件设计

44.2.1 程序功能
本章实验功能简介:开机后,先初始化各外设,然后检测字库是否存在,如果检测无问题,再检测SD卡根目录是否存在RECORDER文件夹,如果不存在则创建,如果创建失败,则报错。在找到SD卡的RECORDER文件夹后,即进入录音模式(包括配置ES8388和I2S等),此时可以在耳机(或喇叭)听到采集到的音频。KEY0用于开始/暂停录音,KEY1用于保存并停止录音,KEY2用于播放最近一次的录音。
当我们按下KEY0的时候,可以在屏幕上看到录音文件的名字、码率以及录音时间等,然后通过KEY1可以保存该文件,同时停止录音(文件名和时间也都将清零),在完成一段录音后,我们可以通过按KEY2按键,来试听刚刚的录音。LED0用于提示程序正在运行。

44.2.2 硬件资源
本实验,大家需要准备1个TF卡和一个耳机(非必备),分别插入SD卡接口和耳机接口,然后下载本实验就可以实现录音机的效果。实验用到的硬件资源如下:
1)LED灯
        LED        0                - IO51
2)ES8388音频CODEC芯片,通过I2S驱动
3)I2S音频接口
        I2S_BCK_IO                - IO47
        I2S_WS_IO                - IO48
        I2S_DO_IO                - IO49
        I2S_DI_IO                - IO50
        I2S_MCK_IO                - IO46
4)XL9555
        IIC_INT                - IO36
        IIC_SDA                - IO33
        IIC_SCL                - IO32
        EXIO_8                - KEY0
        EXIO_9                - KEY1
        EXIO_10                - KEY2
5)RGBLCD/MIPILCD(引脚太多,不罗列出来)
6)SPIFFS
7)SD卡
        CMD                  -  IO44
        CLK                -  IO43
        D0                      -  IO39
        D1                      -  IO40
        D2                     -  IO41
        D3                      -  IO42

44.2.3 原理图
ES8388原理图已在43.2.3小节中详细阐述,为避免重复,此处不再赘述。

44.3 程序设计

44.3.1 I2S的IDF驱动
I2S相关函数,笔者已经在上一章节中讲解了,此处不再赘述。

44.3.2 程序流程图

第四十四章 录音实验1242.png
图44.3.2.1 录音实验程序流程图

44.3.3 程序解析
在35_recoding例程是基于35_music实验编写的,所以笔者着重讲解有区别的文件驱动。
1,recorder驱动代码
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码,main/APP/ AUDIO文件夹下recorder.c和recorder.h。
音乐播放器实验中,我们已经学习过配置ES8388的方法。本实验中,我们来学习ES8388的录音模式,进入PCM 录音模式函数如下:
  1. /**
  2. * @brief            进入PCM 录音模式
  3. * [url=home.php?mod=space&uid=271674]@param[/url]              无
  4. * @retval            无
  5. */
  6. void recoder_enter_rec_mode(void)
  7. {
  8.     myi2s_init();
  9.     es8388_adda_cfg(0, 1);          /* 开启ADC */
  10.     es8388_input_cfg(0);            /* 开启输入通道(通道1,MIC所在通道) */
  11.     es8388_mic_gain(8);             /* MIC增益设置为最大 */
  12.     es8388_alc_ctrl(3, 4, 4);       /* 开启立体声ALC控制,以提高录音音量 */
  13.     es8388_output_cfg(0, 0);        /* 关闭通道1和2的输出 */
  14.     es8388_spkvol_set(0);           /* 关闭喇叭. */
  15.     es8388_i2s_cfg(0, 3);           /* 飞利浦标准,16位数据长度 */
  16.     i2s_set_samplerate_bits_sample(I2S_SAMPLE_RATE,I2S_BITS_PER_SAMPLE_16BIT);   
  17.     i2s_trx_start();                /* 开启I2S */
  18.     recoder_remindmsg_show(0);
  19. }
复制代码
该函数就是用我们前面介绍的方法,激活ES8388的PCM模式,本章,我们使用的是44.1Khz采样率,16位单声道线性PCM模式。
由于最后要把录音写入到文件,这里需要准备wav的文件头,为方便,我们定义了一个__WaveHeader结构体来定义文件头的数据字节,这个结构体包含了前面提到的wav文件的数据结构块:

  1. typedef struct
  2. {
  3.     ChunkRIFF riff;             /* riff块 */
  4.     ChunkFMT fmt;               /* fmt块 */
  5. //    ChunkFACT fact;           /* fact块 线性PCM,没有这个结构体 */
  6.     ChunkDATA data;             /* data块 */
  7. } __WaveHeader;                 /* wav头 */
复制代码
我们定义一个recoder_wav_init函数方便初始化文件信息,代码如下:
  1. /**
  2. * @brief             初始化WAV头
  3. * @param             wavhead : wav文件头指针
  4. * @retval           无
  5. */
  6. void recoder_wav_init(__WaveHeader *wavhead)
  7. {
  8.     wavhead->riff.ChunkID = 0x46464952;                        /* "RIFF" */
  9.     wavhead->riff.ChunkSize = 0;                               /* 还未确定,最后需要计算 */
  10.     wavhead->riff.Format = 0x45564157;                            /* "WAVE" */
  11.     wavhead->fmt.ChunkID = 0x20746D66;                            /* "fmt " */
  12.     wavhead->fmt.ChunkSize = 16;                /* PCM格式块大小为16个字节 */
  13.     wavhead->fmt.AudioFormat = 0x01;                /* 0x01,表示PCM; 0x00,表示IMA ADPCM */
  14.     wavhead->fmt.NumOfChannels = 2;                            /* 双声道 */
  15.     wavhead->fmt.SampleRate = 16000;                            /* 采样速率为16000Hz */
  16. wavhead->fmt.BitsPerSample = 16;                           /* 16位PCM */
  17. /* 块对齐=通道数*每个样本的字节数 */
  18. wavhead->fmt.BlockAlign = wavhead->fmt.NumOfChannels
  19. * (wavhead->fmt.BitsPerSample / 8);  
  20. /* 字节速率=采样率*块对齐 */
  21.     wavhead->fmt.ByteRate = wavhead->fmt.SampleRate * wavhead->fmt.BlockAlign;
  22.     wavhead->data.ChunkID = 0x61746164;                  /* "data" */
  23.     wavhead->data.ChunkSize = 0;                         /* 数据大小,最后需要计算 */
  24. }
复制代码
录音完成我们还要重新计算录音文件的大小写入文件头,以保证音频文件能正常被解析。我们把这些数据直接按顺序写入文件即可完成录音操作,结合文件操作和按键功能定义,我们用wav_recoder函数实现录音过程,代码如下:
  1. /**
  2. * @brief             WAV录音任务
  3. * @param              arg:传入任务句柄
  4. * @retval            无
  5. */
  6. static void recorder_task(void * arg)
  7. {
  8.     QueueSetMemberHandle_t activate_member = NULL;
  9.     UINT bw;
  10.     size_t bytes_read = 0;                  /* 读取写入录音文件大小 */
  11.     uint8_t file_write_res = 0;             /* 上一次写入完成标志 */
  12.     uint8_t rval = 0;                       /* 获取TF状态 */
  13.     FF_DIR recdir;                          /* 目录 */
  14.     FIL *f_rec = NULL;                      /* 录音文件 */
  15.     static uint8_t *pdatabuf = NULL;        /* 数据缓存指针 */
  16.     static uint8_t *pdatabuf1 = NULL;       /* 数据缓存指针 */
  17.     uint8_t *pname = NULL;                  /* 文件名称存储buf */
  18.     FSIZE_t recorder_file_read_pos = 0;     /* 记录当前录音文件读取位置 */
  19.     uint32_t g_wav_size_tatol = 0;          /* wav数据大小(字节数,不包括文件头!!) */

  20.     /* 资源申请内存 */
  21.     pdatabuf = malloc(REC_RX_BUF_SIZE);                            /* 存储录音数据 */
  22.     pdatabuf1 = malloc(REC_RX_BUF_SIZE);                              /* 存储录音数据 */
  23.     f_rec = (FIL*)malloc(sizeof(FIL));                              /* 文件指针 */
  24.     wavhead = (__WaveHeader *)malloc(sizeof(__WaveHeader)); /* wav头部 */
  25.     pname = malloc(255);                                       /* 存储文件名称buf */

  26.     if (!f_rec || !wavhead || !pname || !pdatabuf || !pdatabuf1)
  27.     {
  28.         goto exit;
  29.     }
  30.    
  31.     memset(pdatabuf,0,REC_RX_BUF_SIZE);
  32.     memset(pdatabuf1,0,REC_RX_BUF_SIZE);

  33.     current_buffer = pdatabuf;
  34.     next_buffer = pdatabuf1;

  35.     /* 打开文件夹,若没有,则自动创建 */
  36.     while (f_opendir(&recdir, "0:/RECORDER"))
  37.     {
  38.         f_mkdir("0:/RECORDER"); /* 创建该目录 */
  39.     }

  40.     xTaskNotifyGive(arg);
  41.     /* 创建互斥锁,防止数据存储干扰 */
  42.     i2s_mutex = xSemaphoreCreateRecursiveMutex();
  43.     xSemaphoreGiveRecursive(i2s_mutex);

  44.     recoder_enter_rec_mode();   /* 进入录音模式 */
  45.     pname[0] = 0;               /* pname没有任何文件名 */

  46.     while (rval == 0)
  47. {
  48. /* 等待队列集中的队列接收到消息 */
  49.         activate_member = xQueueSelectFromSet(xQueueSet, 10);

  50.         if (activate_member == key0_xSemaphore)
  51.         {
  52.             xSemaphoreTake(activate_member, portMAX_DELAY);

  53.             if (g_rec_sta & 0x01 && recorder_file_read_pos != 0
  54. && file_write_res == 1) /* 暂停录音 */
  55.             {
  56.                 g_rec_sta &= 0xFE;          /* 恢复录音 */
  57.             }
  58.             else if (g_rec_sta & 0x80 && file_write_res == 1)   /* 正在录音 */
  59.             {
  60.                 g_rec_sta |= 0x01;          /* 暂停录音 */
  61.                 recorder_file_read_pos = f_tell(f_rec);/* 记录当前文件写到位置 */
  62.             }
  63.             else                            /* 未开始录音 */
  64.             {
  65.                 recsec = 0;
  66.                 recoder_new_pathname(pname);
  67.                 text_show_string(30, 190, lcddev.width, 16, "录制:", 16, 0, RED);
  68.                 text_show_string(30 + 40, 190, lcddev.width, 16,
  69. (char *)pname + 11, 16, 0, RED);
  70.                 recoder_wav_init(wavhead);  /* 初始化wav头 */
  71.                 i2s_set_samplerate_bits_sample(wavhead->fmt.SampleRate,
  72. I2S_BITS_PER_SAMPLE_16BIT);   
  73.                 i2s_trx_start();            /* 开启I2S */
  74.                 rval =f_open(f_rec,(const TCHAR *)pname,FA_CREATE_ALWAYS
  75. | FA_WRITE);
  76.                 /* 打开文件失败 */
  77.                 if (rval != FR_OK)
  78.                 {
  79.                     g_rec_sta = 0;          /* 关闭录音 */
  80.                     rval = 0xFE;            /* 提示SD卡问题 */
  81.                 }
  82.                 else
  83.                 {
  84.                     f_lseek(f_rec, sizeof(__WaveHeader));  /* 偏移到数据存储地址 */
  85.                     recoder_msg_show(0, 0); /* 提示录音时长 */
  86.                     g_rec_sta |= 0x80;      /* 开始录音 */
  87.                 }
  88.             }
  89.         }
  90.         else if (activate_member == key1_xSemaphore)
  91.         {
  92.             xSemaphoreTake(activate_member, portMAX_DELAY);
  93.             if ((g_rec_sta & 0x80 )&& (file_write_res == 1)
  94. &&(g_wav_size_tatol == g_wav_size)) /* 有录音且上一次录音写入完成 */
  95.             {
  96.                 xSemaphoreTakeRecursive(i2s_mutex, portMAX_DELAY);
  97.                 f_lseek(f_rec, 0);                          /* 偏移到文件头 */
  98.                 g_rec_sta = 0;
  99.                 wavhead->riff.ChunkSize = g_wav_size + 36;  /* 整个文件的大小-8; */
  100.                 wavhead->data.ChunkSize = g_wav_size;       /* 数据大小 */
  101.                 f_write(f_rec, (const void *)wavhead, sizeof(__WaveHeader),
  102. (UINT *)&bw); /* 写入头数据 */
  103.                 f_close(f_rec);
  104.                 g_wav_size_tatol = 0;
  105.                 g_wav_size = 0;
  106.                 xSemaphoreGiveRecursive(i2s_mutex);
  107.             }
  108.             g_rec_sta = 0;
  109.             recsec = 0;
  110.             lcd_fill(30, 190, lcddev.width, lcddev.height, WHITE); /* 清除显示 */
  111.         }
  112.         else if (activate_member == key2_xSemaphore)
  113.         {
  114.             xSemaphoreTake(activate_member, portMAX_DELAY);
  115.             if (g_rec_sta == 0 && file_write_res == 1)
  116.             {
  117.                 text_show_string(30, 190, lcddev.width, 16, "播放:", 16, 0, RED);
  118.                 text_show_string(30 + 40, 190, lcddev.width, 16,
  119. (char *)pname + 11, 16, 0, RED);
  120.                 recoder_enter_play_mode();  /* 进入播放模式 */
  121.                 audio_play_song(pname);     /* 播放录音 */
  122.                 lcd_fill(30, 190, lcddev.width, lcddev.height, WHITE)
  123.                 recoder_enter_rec_mode();   /* 回到录音模式 */
  124.                 g_rec_sta = 0;
  125.                 bytes_read = 0;
  126.                 memset(current_buffer,0,REC_RX_BUF_SIZE);
  127.             }
  128.         }

  129.         /* 处理录音中数据写入与状态显示 */
  130.         if (g_rec_sta & 0x80 && !(g_rec_sta & 0x01))
  131.         {
  132.             xSemaphoreTakeRecursive(i2s_mutex, portMAX_DELAY);
  133.             file_write_res = 0;
  134.             bytes_read = i2s_rx_read(current_buffer, REC_RX_BUF_SIZE);   
  135.             g_wav_size_tatol += bytes_read;

  136.             if (bytes_read == REC_RX_BUF_SIZE)
  137.             {
  138.                 if (recorder_file_read_pos != 0)
  139.                 {
  140.                     f_lseek(f_rec, recorder_file_read_pos);
  141.                     recorder_file_read_pos = 0;
  142.                 }
  143.                 /* 写入到文件当中 */
  144.                 rval = f_write(f_rec, current_buffer, REC_RX_BUF_SIZE,
  145.                               (UINT*)&bw);

  146.                 if (rval != FR_OK)
  147.                 {
  148.                     printf("write error:%d\r\n", rval);
  149.                 }
  150.                 else
  151.                 {
  152.                     file_write_res = 1;
  153.                     g_wav_size += bytes_read;
  154.                     /* 切换缓冲区 */
  155.                     uint8_t *temp = current_buffer;
  156.                     current_buffer = next_buffer;
  157.                     next_buffer = temp;
  158.                 }
  159.             }

  160.             xSemaphoreGiveRecursive(i2s_mutex);
  161.         }

  162.     }

  163. exit:
  164.     /* 清理资源 */
  165.     free(pdatabuf);
  166.     free(f_rec);
  167.     free(wavhead);
  168.     free(pname);
  169.     /* 删除任务 */
  170.     vTaskDelete(NULL);
  171. }

  172. /**
  173. * @brief             WAV录音
  174. * @param             无
  175. * @retval            无
  176. */
  177. void wav_recorder(void)
  178. {
  179.     uint8_t key = 0;
  180.     uint8_t timecnt = 0;       /* 计时器 */

  181.     /* 创建录音线程 */
  182.     BaseType_t task_created = xTaskCreatePinnedToCore(recorder_task,
  183.                                                      "recorder_task",
  184.                                                      4096,
  185.                                                     xTaskGetCurrentTaskHandle(),
  186.                                                      10, NULL, 0);
  187.     assert(task_created == pdTRUE);

  188.     /* 等待录音线程的通知继续 */
  189. ulTaskNotifyTake(false, portMAX_DELAY);

  190.     /* 创建队列集 */
  191. xQueueSet = xQueueCreateSet(3);

  192.     /* 创建信号量 */
  193.     key0_xSemaphore = xSemaphoreCreateBinary();
  194.     key1_xSemaphore = xSemaphoreCreateBinary();
  195. key2_xSemaphore = xSemaphoreCreateBinary();

  196.     /* 把信号量加入到队列集 */
  197.     xQueueAddToSet(key0_xSemaphore, xQueueSet);
  198.     xQueueAddToSet(key1_xSemaphore, xQueueSet);
  199.     xQueueAddToSet(key2_xSemaphore, xQueueSet);

  200.     while (1)
  201.     {
  202.         key = xl9555_key_scan(0);

  203.         switch (key)
  204.         {
  205.             case KEY0_PRES:
  206.                 xSemaphoreGive(key0_xSemaphore);
  207.                 break;

  208.             case KEY1_PRES:
  209.                 xSemaphoreGive(key1_xSemaphore);
  210.                 break;

  211.             case KEY2_PRES:
  212.                 xSemaphoreGive(key2_xSemaphore);
  213.                 break;

  214.             default:
  215.                 break;
  216.         }

  217.         uint32_t current_rec_time = g_wav_size / wavhead->fmt.ByteRate;

  218.         timecnt++;

  219.         if (timecnt % 20 == 0)
  220.         {
  221.             LED0_TOGGLE();  /* 闪烁提示 */
  222.         }

  223.         if (recsec != current_rec_time)
  224.         {
  225.             recsec = current_rec_time;
  226.             recoder_msg_show(recsec, wavhead->fmt.SampleRate
  227. * wavhead->fmt.NumOfChannels * wavhead->fmt.BitsPerSample);
  228.         }

  229.         vTaskDelay(10);
  230.     }
  231. }
复制代码
上述代码实现了一个基于FreeRTOS的WAV格式录音功能。通过recorder_task任务管理录音数据的采集、存储和播放。录音数据通过I2S接口从硬件采集,并使用缓冲区进行数据存储。录音文件采用WAV格式,包含头部信息,并存储在SD卡的指定目录下。用户可以通过按键控制录音的开始、暂停和停止,同时显示录音时长。
在wav_recorder函数中,创建并启动录音任务,初始化相关资源,并使用队列和信号量机制响应按键事件。任务通过读取I2S接口的数据、切换缓冲区并写入文件,实时更新录音状态和时长显示。
2,CMakeLists.txt文件
本例程的CMakeLists.txt跟音乐播放器实验是一致的,代码如下:

  1. set(src_dirs
  2.                     LED
  3.                     KEY
  4.                     MYIIC
  5.                     XL9555
  6.                     LCD
  7.                     ES8388
  8.                     MYI2S
  9.                     SDMMC)

  10. set(include_dirs
  11.                     LED
  12.                     KEY
  13.                     MYIIC
  14.                     XL9555
  15.                     LCD
  16.                     ES8388
  17.                     MYI2S
  18.                     SDMMC)

  19. set(requires
  20.                     driver
  21.                     esp_lcd
  22.                     esp_common
  23.                     fatfs)

  24. idf_component_register(SRC_DIRS ${src_dirs} INCLUDE_DIRS ${include_dirs}
  25.                        REQUIRES ${requires})

  26. component_compile_options(-ffast-math -O3 -Wno-error=format=-Wno-format)
复制代码
3,main.c代码
在main.c里面编写如下代码。

  1. void app_main(void)
  2. {
  3.     esp_err_t ret;
  4.     uint8_t key = 0;

  5.     ret = nvs_flash_init();         /* 初始化NVS */

  6.     if(ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
  7.     {
  8.         ESP_ERROR_CHECK(nvs_flash_erase());
  9.         ESP_ERROR_CHECK(nvs_flash_init());
  10. }

  11.     led_init();                         /* LED初始化 */
  12.     key_init();                         /* KEY初始化 */
  13.     myiic_init();                       /* IIC0初始化 */
  14.     xl9555_init();                      /* XL9555初始化 */
  15.     es8388_init();                      /* es8388初始化 */
  16.     lcd_init();                         /* LCD屏初始化 */
  17.     xl9555_pin_write(SPK_EN_IO,1);  /* 关闭喇叭 */

  18.     while (sdmmc_init())            /* 检测不到SD卡 */
  19.     {
  20.         lcd_show_string(30, 110, 200, 16, 16, "SD Card Error!", RED);
  21.         vTaskDelay(pdMS_TO_TICKS(500));
  22.         lcd_show_string(30, 130, 200, 16, 16, "Please Check! ", RED);
  23.         vTaskDelay(pdMS_TO_TICKS(500));
  24.     }

  25.     while (fonts_init())            /* 检查字库 */
  26.     {
  27.         lcd_clear(WHITE);           /* 清屏 */
  28.         lcd_show_string(30, 30, 200, 16, 16, "ESP32-P3", RED);
  29.         
  30.         key = fonts_update_font(30, 50, 16, (uint8_t *)"0:", RED);  /* 更新字库 */

  31.         while (key)                 /* 更新失败 */
  32.         {
  33.             lcd_show_string(30, 50, 200, 16, 16, "Font Update Failed!", RED);
  34.             vTaskDelay(pdMS_TO_TICKS(200));
  35.         }

  36.         lcd_show_string(30, 50, 200, 16, 16, "Font Update Success!   ", RED);
  37.         vTaskDelay(pdMS_TO_TICKS(1500));

  38.         lcd_clear(WHITE);           /* 清屏 */
  39.     }

  40. ret = exfuns_init();            /* 为fatfs相关变量申请内存 */

  41. vTaskDelay(pdMS_TO_TICKS(500)); /* 实验信息显示延时 */

  42.     text_show_string(30, 50, 200, 16, "正点原子ESP32开发板", 16, 0, RED);
  43.     text_show_string(30, 70, 200, 16, "WAV 录音机 实验", 16, 0, RED);
  44.     text_show_string(30, 90, 200, 16, "正点原子@ALIENTEK", 16, 0, RED);

  45.     while (1)
  46.     {
  47.         wav_recorder();                                   /* 录音 */
  48.     }
  49. }
复制代码
该函数就相对简单了,在初始化各个外设后,通过wav_recorder函数,开始音频播放,到这里本实验的代码基本就编写完成了。。

44.4 下载验证
在代码编译成功之后,我们下载代码到正点原子DNESP32P4开发板上,先初始化各外设,然后检测字库是否存在,如果检测无问题,再检测SD卡根目录是否存在RECORDER文件夹,如果不存在则创建,如果创建失败,则报错。在找到SD卡的RECORDER文件夹后,即进入录音模式(包括配置ES8388和I2S等),此时可以在耳机(或喇叭)听到采集到的音频。KEY0用于开始/暂停录音,KEY1用于保存并停止录音,KEY2用于播放最近一次的录音。

第四十四章 录音实验15322.png
图44.4.1 录音机实验界面

此时,我们按下KEY0就开始录音了,此时看到屏幕显示录音文件的名字以及录音时长,如下图所示:

第四十四章 录音实验15387.png
图44.4.2 录音进行中

在录音的时候按下KEY0则执行暂停/继续录音的切换,通过LED1指示录音暂停。通过按下KEY1,可以停止当前录音,并保存录音文件。在完成一次录音文件保存之后,我们可以通过按KEY2按键,来实现播放这个录音文件(即播放最近一次的录音文件),实现试听。
我们可以把录音完成的wav文件放到电脑上,可以通过一些播放软件播放并查看详细的音频编码信息,本例程使用的是KMPlayer播放,查看到的信息如下图所示:


第四十四章 录音实验15605.png
图44.4.3 录音文件属性

这和我们程序设计时的效果一样,通过电脑端的播放器可以直接播放我们所录的音频。经实测,效果还是非常不错的。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则


关闭

原子哥极力推荐上一条 /1 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2026-2-18 13:22

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表