制作录音文件转PCM格式单片机播放

编程入门 行业动态 更新时间:2024-10-28 10:29:06

制作录音文件转PCM格式<a href=https://www.elefans.com/category/jswz/34/1769836.html style=单片机播放"/>

制作录音文件转PCM格式单片机播放

制作录音文件到单片机播放

  • 准备相关工具软件
  • 制作录音文件
    • 将WAV文件转换为PCM数据
    • 用ffmpeg播放pcm文件:
    • 制作录音数据播放

准备相关工具软件

录音工具:Audacity
格式转换工具:ffmpeg
工具下载地址

制作录音文件

将WAV文件转换为PCM数据

ffmpeg -i xxx.wav -f s16le -ar 16000 -ac 1 -acodec pcm_s16le pcm16k.pcm

其中:

  • -f为存储类型
  • s16le指的是16位整形数据
  • le代表的是小端序,对应的是be大端序,一般默认是le小端序。如果搞错了,生成的pcm文件是一串噪音
  • -ar 是音频采样率,一般有8k,16k等各种不同的采样率
  • -ac: 通道数,1指单通道
  • -acodec:生成文件格式,pcm_s16le指的是pcm文件,s16le对应前面**-f**部分

用ffmpeg播放pcm文件:

ffplay -ar 16000 -channels 1 -f s16le -i output.pcm

制作录音数据播放

制作流程:

  1. 使用FFMPEG将WAV文件转为PCM文件
  2. 使用读取PCM文件导出16进制数组
  3. 在程序中定义这个常量数组
  4. 调用接口输出数据

PCM数据导出数组工具:

/** Includes -----------------------------------------------------------------*/
#include <stdint.h> /**< need definition of uint8_t */
#include <stddef.h> /**< need definition of NULL    */
#include <stdbool.h>/**< need definition of BOOL    */
#include <stdio.h>  /**< if need printf             */
#include <stdlib.h>
#include <string.h>
#include <errno.h>
/** Private includes ---------------------------------------------------------*/
#include <fcntl.h>
#include <unistd.h>
#include <ctype.h>
#include <sys/stat.h>
/** Private defines ----------------------------------------------------------*/
#ifndef OUT_FILE_NAME#define OUT_FILE_NAME "default.file"
#endif
/** Exported typedefines -----------------------------------------------------*//*文件打开权限*/
typedef enum
{READ_ONLY = 0,READ_WRITE_ONLY,WRITE_CREAT_CLEAR,READ_WRITE_CREAT_CLEAR,WRITE_APPEND_CREAT,READ_WRITE_APPEND_CREAT,
}FILE_OPEN_MODE;/*文件分割方式*/
typedef enum
{SPACE_SPLIT = 0,COMMA_SPLIT,
}FILE_SPLIT_MODE;/** Exported constants -------------------------------------------------------*//** Exported macros-----------------------------------------------------------*/
#define PRINT_ERRMSG(STR) fprintf(stderr,"line:%d,msg:%s,eMsg:%s\n", __LINE__, STR, strerror(errno))
/** Exported variables -------------------------------------------------------*/
/** Exported functions prototypes --------------------------------------------*/
/*返回指定文件是否存在*/
int file_is_exist(const char *fimename);/*打开指定文件返回文件描述符*/
FILE *file_open(const char *filename ,FILE_OPEN_MODE mode);/*读取指定打开的文件,返回总行数*/
int file_get_line_cnt(const char *filename);/*获取文件大小*/
int get_file_size(const char *filename);/*读取指定打开的文件指定行的内容到缓冲区*/
size_t file_read(const char *filename ,char *destbuf ,size_t size ,int linenum);/*替换字符*/
size_t file_replace_ch(char *sourcebuf ,char sourcech,char destch);/*清除字符串空格*/
char *strtriml(char *pstr);
char *strtrimr(char *pstr);
char *strtrim(char *pstr);/*清除文本中空格,忽略注释标识行*/
bool file_strip_comments(char *string, char comment);
/********************************************************************* @brief   判断文件是否存在* @param   [in]fimename 文件名* @retval  返回0文件存在* @author  aron566* @version V1.0* @date    2020-08-28*******************************************************************/
int file_is_exist(const char *fimename)
{return access(fimename ,F_OK | W_OK | R_OK);
}/********************************************************************* @brief   打开指定文件返回文件描述符,追加模式下fseek(fp, 0, SEEK_SET)无效* @param   [in]fimename 文件名* @param   [in]mode 打开文件的模式选择* @retval  返回0文件存在* @author  aron566* @version V1.0* @date    2020-08-28*******************************************************************/
FILE *file_open(const char *filename ,FILE_OPEN_MODE mode)
{FILE *fp = NULL;switch(mode){case READ_ONLY:fp = fopen(filename ,"r");break;case READ_WRITE_ONLY:fp = fopen(filename ,"r+");break;case WRITE_CREAT_CLEAR:fp = fopen(filename ,"w");break;case READ_WRITE_CREAT_CLEAR:fp = fopen(filename ,"w+");break;case WRITE_APPEND_CREAT:fp = fopen(filename ,"a");break;case READ_WRITE_APPEND_CREAT:fp = fopen(filename ,"a+");/**< 首次读取时,从文件头部开始读*/break;}return fp;
}/********************************************************************* @brief   打开读取指定打开的文件,返回总行数* @param   [in]fp 文件指针* @param   [in]filename 文件名称* @retval  返回-1读取失败* @author  aron566* @version V1.0* @date    2020-08-28*******************************************************************/
int file_get_line_cnt(const char *filename)
{int cnt = 0;char buf[256];FILE *fp = file_open(filename ,READ_ONLY);if(fp == NULL){PRINT_ERRMSG("fopen");printf("read file name :%s error.\n" ,filename);return -1;}char *ret = NULL;/*读取文件流中的内容*/while((fgets(buf ,256 ,fp)) != NULL){ret = strchr(buf ,'\n');if(ret != NULL){cnt++;}}/*关闭文件*/fclose(fp);return cnt;
}/********************************************************************* @brief   获取文件大小* @param   [in]fileName* @return  文件大小字节数* @author  aron566* @version V1.0* @date    2020-12-13*******************************************************************/
int get_file_size(const char *filename)
{ struct stat st;stat(filename, &st);return st.st_size;
}/********************************************************************* @brief   读取指定打开的文件指定行的内容到缓冲区* @param   [in]filename 文件名称* @param   [in]读取到的数据存储区* @param   [in]限制长度* @param 	 [in]需读取的行* @retval  执行结果,读取到字节数* @author  aron566* @version V1.0* @date    2020-08-28*******************************************************************/
size_t file_read(const char *filename ,char *destbuf ,size_t size ,int linenum)
{int cnt = 0;char buf[1024];/*初始化缓冲区*/if(strlen(destbuf) > 0){destbuf[0] = '\0';}/*打开文件流*/FILE *fp = file_open(filename ,READ_ONLY);if(fp == NULL){PRINT_ERRMSG("fopen");return -1;}/*读取文件流中的内容*/char *ret = NULL;size_t len = 0;while((fgets(buf ,1024 ,fp)) != NULL){ret = strchr(buf ,'\n');if(ret != NULL){cnt++;if(cnt == linenum){if(len == 0){strncpy(destbuf ,buf ,1024);if(size > 1024){destbuf[1024] = '\0';}else{destbuf[1023] = '\0';}}else{/*追加字符串*/len += strlen(buf);if(len > size){break;}else{strcat(destbuf ,buf);}}fclose(fp);return strlen(destbuf);}//cnt == linenum}else{/*判断是否超出缓冲区大小*/if(cnt == linenum-1){/*目标缓冲区过小直接退出*/if(size <= 1024){break;}len = strlen(destbuf);if(len == 0){strncpy(destbuf ,buf ,1024);destbuf[1024] = '\0';}else{/*追加字符串*/len += strlen(buf);if(len > size){break;}else{strcat(destbuf ,buf);}}}}}fclose(fp);return strlen(destbuf);
}/********************************************************************* @brief   写入指定的内容到文件* @param   [in]filename 文件名称* @param   [in]buffer数据存储区* @param   [in]size写入的元素占总字节数* @param 	 [in]count写入元素数目* @param 	 [in]mode文件写入模式* @retval  执行结果,写入元素的总数* @author  aron566* @version V1.0* @date    2020-10-09*******************************************************************/
size_t file_write(const char *filename ,const void* buffer ,size_t size ,size_t count ,FILE_OPEN_MODE mode)
{/*打开文件流*/FILE *fp = file_open(filename ,mode);if(fp == NULL){return 0;}size_t cnt = fwrite(buffer ,size ,count ,fp);/*同步到文件中*/fflush(fp);fclose(fp);return cnt;
}/********************************************************************* @brief   替换目标字符串中字符* @param   [in]目标字符串* @param   [in]源字符* @param   [in]目标字符* @retval  执行结果,当前字符长度* @author  aron566* @version V1.0* @date    2020-08-28*******************************************************************/
size_t file_replace_ch(char *sourcebuf ,char sourcech,char destch)
{int i;i = strlen(sourcebuf) - 1;while (sourcebuf[i] == sourcech && (i >= 0))sourcebuf[i--] = destch;return strlen(sourcebuf);
}/********************************************************************* @brief   去除字符串右端空格* @param   [in]字符串指针* @retval  修剪后的字符串地址* @author  aron566* @version V1.0* @date    2020-08-28*******************************************************************/
char *strtrimr(char *pstr)
{int i;i = strlen(pstr) - 1;while (isspace(pstr[i]) && (i >= 0))pstr[i--] = '\0';return pstr;
}/********************************************************************* @brief   去除字符串左端空格* @param   [in]字符串指针* @retval  修剪后的字符串地址* @author  aron566* @version V1.0* @date    2020-08-28*******************************************************************/
char *strtriml(char *pstr)
{int i = 0,j;j = strlen(pstr) - 1;while (isspace(pstr[i]) && (i <= j))i++;if (0<i)strcpy(pstr, &pstr[i]);return pstr;
}/********************************************************************* @brief   去除字符串两端空格* @param   [in]字符串指针* @retval  修剪后的字符串地址* @author  aron566* @version V1.0* @date    2020-08-28*******************************************************************/
char *strtrim(char *pstr)
{char *p;p = strtrimr(pstr);return strtriml(p);
}/********************************************************************* @brief   去掉字符串内所有空白,且忽略注释部分,最终得到没有空白的字符串* @param   [in]string:字符串* @param   [in]comment:注释标识* @retval  true表示数据可用* @author  aron566* @version V1.0* @date    2020-08-31*******************************************************************/
bool file_strip_comments(char *string, char comment)
{if (NULL == string || '\n' == *string || '\r' == *string) {return false; /* 第一个字符为回车或换行,表示空行 */}char *p, *q;/* 下面去掉字符串中所有空白字符 */for (p = q = string; *p != '\0' && *p != comment; p++) {/* 不是空白字符则重写字符串 */if (0 == isspace(*p)) {*q++ = *p;}}*q = '\0';return 0 != strlen(string); /* 字符串长度不为0,表示数据可用 */
}/*** @brief 16进制字符转为数值* * @param ch 16进制字符* @return uint8_t 数值*/
uint8_t hex_char_to_value(uint8_t ch)
{uint8_t result = 0;/*获取16进制的高字节位数据*/if (ch >= '0' && ch <= '9'){result = ch - '0';}else if (ch >= 'a' && ch <= 'z'){result = ch - 'a' + 10;}else if (ch >= 'A' && ch <= 'Z'){result = ch - 'A' + 10;}else{result = 0;}return result;
}/*** @brief 将大写字母转换成小写字母* * @param ch 大写字母* @return uint8_t 小写字母*/
uint8_t ch_tolower(uint8_t ch)
{if(ch >= 'A' && ch <= 'Z'){return ch + 'a' - 'A';}else{return ch;}
}/*** @brief 16进制的字符串转换成整数* * @param s 16进制字符串* @return int 数值*/
int hextoi(char s[])
{int i = 0;int ret = 0;if(s[0] == '0' && (s[1]=='x' || s[1]=='X')){  i = 2;}else{i = 0;}for(;(s[i] >= '0' && s[i] <= '9') || (s[i] >= 'a' && s[i] <= 'z') || (s[i] >='A' && s[i] <= 'Z');++i){if(ch_tolower(s[i]) > '9'){ret = 16 * ret + (10 + ch_tolower(s[i]) - 'a');}else{ret = 16 * ret + (ch_tolower(s[i]) - '0');}}return ret;
}/*** @brief main入口* * @param argc 参数个数* @return argv[] 参数*/
int main(int argc ,char *argv[])
{
#if defined (ENABLE_PRINTF_HEX)if(argc < 2){printf("Usage:%s filename\n" ,argv[0]);return -1;}
#elseif(argc < 4){printf("Usage:%s filename strlen split_num\n" ,argv[0]);return -1;}int buf_len = atoi(argv[2]);char *strbuf = (char*)malloc(sizeof(char)*buf_len);if(strbuf == NULL){printf("malloc error!\n");return -1;}memset(strbuf ,0 ,buf_len);
#endif/*打开文件*/FILE *fp = file_open(argv[1] ,READ_ONLY);if(fp == NULL){printf("can't open file.\n");return -1;}
#if defined (ENABLE_PRINTF_HEX)/*获取文件字节数*/int file_size = get_file_size(argv[1]);uint8_t byte = 0;char hex_str[64];sprintf(hex_str, "static const uint8_t hex_data[%d] = \n{\n", file_size);file_write("hex_file.txt" ,hex_str ,1 ,strlen(hex_str) ,WRITE_APPEND_CREAT);for(int index = 0; index < file_size; index++){fread(&byte, 1, 1, fp);if(index == file_size-1){sprintf(hex_str, "0x%02X", byte);}else{sprintf(hex_str, "0x%02X, ", byte);}file_write("hex_file.txt" ,hex_str ,1 ,strlen(hex_str) ,WRITE_APPEND_CREAT);}sprintf(hex_str, "\n};\n");file_write("hex_file.txt" ,hex_str ,1 ,strlen(hex_str) ,WRITE_APPEND_CREAT);return 0;
#else/*读取文件内容*/char txt[64] = {0};while((fgets(txt ,64 ,fp)) != NULL){/* 去掉字符串所有空白,注释也忽略 */if (file_strip_comments(txt ,' ')){strcat(strbuf ,txt);}}int split_num = atoi(argv[3]);uint8_t temp = 0;for(int i = 0;i < buf_len;){
#if defined (ENABLE_PRINTF_CH)if(i < split_num){printf("%c%c" ,strbuf[i] ,strbuf[i+1]);}else{printf(" ""%c%c" ,strbuf[i] ,strbuf[i+1]);}
#endif
#if defined (ENABLE_OUT_FILE)temp = hex_char_to_value(strbuf[i]);temp <<= 4;temp |= hex_char_to_value(strbuf[i+1]);file_write(OUT_FILE_NAME ,&temp ,1 ,1 ,WRITE_APPEND_CREAT);
#endifi += split_num;}
#endifreturn 0;
}

以上代码保存为cov.c

linux平台下直接编译:

已编译好的工具位于:DebugTool/cov_tool

#编译
gcc split.c -DENABLE_PRINTF_HEX -o cov_tool
#添加执行权限
sudo chmod +x cov_tool#执行转换
./cov_tool <pcm文件名>#输出
hex_file.txt

复制hex_file.txt中文本,在程序中定义音频常量

更多推荐

制作录音文件转PCM格式单片机播放

本文发布于:2024-03-23 21:09:13,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1742834.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:单片机   格式   文件   PCM

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!