STM32F407使用Helix库软解MP3并通过DAC输出,最精简的STM32+SD卡实现MP3播放器
只用STM32单片机+SD卡+耳机插座,实现播放MP3播放器!
看过很多STM32软解MP3的方案,即不通过类似VS1053之类的解码器芯片,直接用STM32和软件库解码MP3文件,通常使用了labmad或者Helix解码库实现,Helix相对labmad占用的RAM更少。但是大多数参考的方案还是用了外接IIS接口WM98xx之类的音频DAC芯片播放音频,稍显复杂繁琐。STM32F407Vx本身就自带了2路12位DAC输出,最高刷新速度333kHz,除了分辨率差点意思,速度上对于MP3通常44.1kHz采样率来说,用来播放音频绰绰有余了。本文给的方案和源码,直接用STM32软解码MP3并使用自带的2个DAC输出引脚输出音频左右声道。
原理:STM32从SD读取MP3文件原始数据,发送给Helix库解码,Helix解码后输出PCM数据流,将此数据进一步处理转换后,按照左右声道分别存入DAC输出1和2缓存,通过定时器以MP3文件的采样率的频率提供DAC触发节拍,通过DMA取缓存中高12位数据给DAC,在DAC1和2引脚产生音频波形,通过电容耦合到耳机的左右声道上。
MP3源文件是一种经过若干算法,将原始音频数据压缩得来的,软件解码的过程是逆过程,将压缩的音频反向转换为记录了左右声道、幅值的数据流,通常是PCM格式。
PCM:是模拟信号以固定的采样频率转换成数字信号后的表现形式。记录了音频采样的数据,双通道、16bit的PCM数据格式是以0轴为中心,范围为-32768~32767的数值,每个数据占用2字节,左声道和右声道交替存储,如图。

软解码得到的PCM数据到STM32的DAC缓存需要进一步处理。STM32的DAC是12位的,其输入范围0~4095,而双通道16位的PCM音频数据是左右声道交替存储,且数据范围-32768~32767,因此PCM到STM32的DAC缓存要按照顺序一拆为二,分为左右声道,每个数据再加上32768,使其由short int的范围转换为unsigned short int,即0~65535。由于PCM数据是对音频的采样,因此调节音量(幅值)可以在此步骤一并处理,即音频数据 x 音量 /最大音量。至于DAC是12位,只需将DAC模式设置为左对齐12位,舍弃低4位即可。
到此,STM32的DAC输出引脚上应该已经有音频信号了,通常DAC引脚上串联一个1~10uF的电容用来耦合音频信号,电容越大音质越好,电容另一端接耳机插座的左声道/右声道,插上耳机就可以欣赏音乐啦!音质嘛,反正我是听不出来好不好,跟商品MP3播放器差不多。如果不串联电容,DAC引脚直连耳机插座左右声道也能听到声音,就是有些数字信号噪声也会传进来。如果希望噪声小一些,DAC引脚输出端加一个下图的低通滤波电路也是可以的。


Helix移植:
Helix源码的官网我没找到,直接用了野火的例程里面的代码,移植也很简单,不用改任何代码,只需要将Helix文件夹拷贝到工程目录里,然后在Keil中添加好文件,以及添加头文件途径,编译即可。工程目录如图。

源码:dac配置
dac.c
/********************************************************************************* @file dac.c* @author ZL* @version V0.0.1* @date September-20-2019* @brief DAC configuration.******************************************************************************
*/
/* Includes ------------------------------------------------------------------*/
#include "dac.h"/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
#define CNT_FREQ 84000000 // TIM6 counter clock (prescaled APB1)/* DHR registers offsets */
#define DHR12R1_OFFSET ((uint32_t)0x00000008)
#define DHR12R2_OFFSET ((uint32_t)0x00000014)
#define DHR12RD_OFFSET ((uint32_t)0x00000020)/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
uint32_t DAC_DHR12R1_ADDR = (uint32_t)DAC_BASE + DHR12R1_OFFSET + DAC_Align_12b_L;
uint32_t DAC_DHR12R2_ADDR = (uint32_t)DAC_BASE + DHR12R2_OFFSET + DAC_Align_12b_L;uint16_t DAC_buff[2][DAC_BUF_LEN]; //DAC1、DAC2输出缓冲/* Private function prototypes -----------------------------------------------*/
static void TIM6_Config(void);/* Private functions ---------------------------------------------------------*/
/*** @brief DAC初始化* @param none* @retval none
*/
void DAC_Config(void)
{GPIO_InitTypeDef GPIO_InitStructure;DAC_InitTypeDef DAC_InitStructure;RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None;DAC_InitStructure.DAC_Trigger = DAC_Trigger_T6_TRGO;DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Enable;DAC_Init(DAC_Channel_1, &DAC_InitStructure);DAC_Init(DAC_Channel_2, &DAC_InitStructure);//配置DMADMA_InitTypeDef DMA_InitStruct;DMA_StructInit(&DMA_InitStruct);RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);DMA_InitStruct.DMA_PeripheralBaseAddr = (u32)DAC_DHR12R1_ADDR;DMA_InitStruct.DMA_Memory0BaseAddr = (u32)&DAC_buff[0];//DAC1DMA_InitStruct.DMA_DIR = DMA_DIR_MemoryToPeripheral;DMA_InitStruct.DMA_BufferSize = DAC_BUF_LEN;DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;DMA_InitStruct.DMA_Mode = DMA_Mode_Circular;DMA_InitStruct.DMA_Priority = DMA_Priority_High;DMA_InitStruct.DMA_Channel = DMA_Channel_7;DMA_InitStruct.DMA_FIFOMode = DMA_FIFOMode_Disable;DMA_InitStruct.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;DMA_InitStruct.DMA_MemoryBurst = DMA_MemoryBurst_Single;DMA_InitStruct.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;DMA_Init(DMA1_Stream5, &DMA_InitStruct);DMA_InitStruct.DMA_PeripheralBaseAddr = (u32)DAC_DHR12R2_ADDR;DMA_InitStruct.DMA_Memory0BaseAddr = (u32)&DAC_buff[1];//DAC2DMA_Init(DMA1_Stream6, &DMA_InitStruct);//开启DMA传输完成中断NVIC_InitTypeDef NVIC_InitStructure;NVIC_InitStructure.NVIC_IRQChannel = DMA1_Stream6_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);DMA_ClearITPendingBit(DMA1_Stream6, DMA_IT_TCIF6);DMA_ClearITPendingBit(DMA1_Stream6, DMA_IT_HTIF6);DMA_ITConfig(DMA1_Stream6, DMA_IT_TC, ENABLE);DMA_ITConfig(DMA1_Stream6, DMA_IT_HT, ENABLE);// DMA_Cmd(DMA1_Stream5, ENABLE);
// DMA_Cmd(DMA1_Stream6, ENABLE);DAC_Cmd(DAC_Channel_1, ENABLE);DAC_Cmd(DAC_Channel_2, ENABLE);DAC_DMACmd(DAC_Channel_1, ENABLE);DAC_DMACmd(DAC_Channel_2, ENABLE);TIM6_Config();
}//配置DAC采样率和DMA数据长度,并启动DMA DAC
void DAC_DMA_Start(uint32_t freq, uint16_t len)
{//设置DMA缓冲长度需要停止DMADAC_DMA_Stop();//设置DMA DAC缓冲长度DMA_SetCurrDataCounter(DMA1_Stream5, len);DMA_SetCurrDataCounter(DMA1_Stream6, len);//设置定时器TIM_SetAutoreload(TIM6, (uint16_t)((CNT_FREQ)/freq));//启动DMA_Cmd(DMA1_Stream5, ENABLE);DMA_Cmd(DMA1_Stream6, ENABLE);
}//停止DMA DAC
void DAC_DMA_Stop(void)
{DMA_Cmd(DMA1_Stream5, DISABLE);DMA_Cmd(DMA1_Stream6, DISABLE);
}//定时器6用于设置DAC刷新率
static void TIM6_Config(void)
{TIM_TimeBaseInitTypeDef TIM6_TimeBase;RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);TIM_TimeBaseStructInit(&TIM6_TimeBase); TIM6_TimeBase.TIM_Period = (uint16_t)((CNT_FREQ)/44100);TIM6_TimeBase.TIM_Prescaler = 0;TIM6_TimeBase.TIM_ClockDivision = 0;TIM6_TimeBase.TIM_CounterMode = TIM_CounterMode_Up;TIM_TimeBaseInit(TIM6, &TIM6_TimeBase);TIM_SelectOutputTrigger(TIM6, TIM_TRGOSource_Update);TIM_Cmd(TIM6, ENABLE);
}/*** @brief DAC out1 PA4输出电压* @param dat:dac数值:,0~4095* @retval none
*/
void DAC_Out1(uint16_t dat)
{DAC_SetChannel1Data(DAC_Align_12b_R, dat);DAC_SoftwareTriggerCmd(DAC_Channel_1, ENABLE);
}/*** @brief DAC out2 PA5输出电压* @param dat:dac数值:,0~4095* @retval none
*/
void DAC_Out2(uint16_t dat)
{DAC_SetChannel2Data(DAC_Align_12b_R, dat);DAC_SoftwareTriggerCmd(DAC_Channel_2, ENABLE);
}/********************************************* *****END OF FILE****/
源码:MP3播放流程 (原创野火,参考了野火的例程,本人进行整理和修改)
MP3player.c
/*
******************************************************************************
* @file mp3Player.c
* @author fire
* @version V1.0
* @date 2023-08-13
* @brief mp3解码
******************************************************************************
*/
#include <stdio.h>
#include <string.h>
#include "ff.h"
#include "mp3Player.h"
#include "mp3dec.h"
#include "dac.h"
#include "led.h"/* 推荐使用以下格式mp3文件:* 采样率:44100Hz* 声 道:2* 比特率:320kbps*//* 处理立体声音频数据时,输出缓冲区需要的最大大小为2304*16/8字节(16为PCM数据为16位),* 这里我们定义MP3BUFFER_SIZE为2304*/
#define MP3BUFFER_SIZE 2304
#define INPUTBUF_SIZE 3000static HMP3Decoder Mp3Decoder; /* mp3解码器指针 */
static MP3FrameInfo Mp3FrameInfo; /* mP3帧信息 */
static MP3_TYPE mp3player; /* mp3播放设备 */
volatile uint8_t Isread = 0; /* DMA传输完成标志 */
volatile uint8_t dac_ht = 0; //DAC dma 半传输标志uint32_t led_delay = 0;uint8_t inputbuf[INPUTBUF_SIZE]={0}; /* 解码输入缓冲区,1940字节为最大MP3帧大小 */
static short outbuffer[MP3BUFFER_SIZE]; /* 解码输出缓冲区*/static FIL file; /* file objects */
static UINT bw; /* File R/W count */
FRESULT result; //从SD卡读取MP3源文件进行解码,并传入DAC缓冲区
int MP3DataDecoder(uint8_t **read_ptr, int *bytes_left)
{int err = 0, i = 0, outputSamps = 0;//bufflag开始解码 参数:mp3解码结构体、输入流指针、输入流大小、输出流指针、数据格式err = MP3Decode(Mp3Decoder, read_ptr, bytes_left, outbuffer, 0);if (err != ERR_MP3_NONE) //错误处理{switch (err){case ERR_MP3_INDATA_UNDERFLOW:printf("ERR_MP3_INDATA_UNDERFLOW\r\n");result = f_read(&file, inputbuf, INPUTBUF_SIZE, &bw);*read_ptr = inputbuf;*bytes_left = bw;break; case ERR_MP3_MAINDATA_UNDERFLOW:/* do nothing - next call to decode will provide more mainData */printf("ERR_MP3_MAINDATA_UNDERFLOW\r\n");break; default:printf("UNKNOWN ERROR:%d\r\n", err); // 跳过此帧if (*bytes_left > 0){(*bytes_left) --;read_ptr ++;}break;}return 0;}else //解码无错误,准备把数据输出到PCM{MP3GetLastFrameInfo(Mp3Decoder, &Mp3FrameInfo); //获取解码信息 /* 输出到DAC */outputSamps = Mp3FrameInfo.outputSamps; //PCM数据个数if (outputSamps > 0){if (Mp3FrameInfo.nChans == 1) //单声道{//单声道数据需要复制一份到另一个声道for (i = outputSamps - 1; i >= 0; i--){outbuffer[i * 2] = outbuffer[i];outbuffer[i * 2 + 1] = outbuffer[i];}outputSamps *= 2;}//if (Mp3FrameInfo.nChans == 1) //单声道}//if (outputSamps > 0)//将数据传送至DMA DAC缓冲区for (i = 0; i < outputSamps/2; i++){if(dac_ht == 1){DAC_buff[0][i] = outbuffer[2*i] * mp3player.ucVolume /100 + 32768;DAC_buff[1][i] = outbuffer[2*i+1] * mp3player.ucVolume /100 + 32768;}else{DAC_buff[0][i+outputSamps/2] = outbuffer[2*i] * mp3player.ucVolume /100 + 32768;DAC_buff[1][i+outputSamps/2] = outbuffer[2*i+1] * mp3player.ucVolume /100 + 32768;}}return 1;}//else 解码正常
}//读取一段MP3数据,并把读取的指针赋值read_ptr,长度赋值bytes_left
uint8_t read_file(const char *mp3file, uint8_t **read_ptr, int *bytes_left)
{result = f_read(&file, inputbuf, INPUTBUF_SIZE, &bw);if(result != FR_OK){printf("读取%s失败 -> %d\r\n", mp3file, result);return 0;}else{*read_ptr = inputbuf;*bytes_left = bw;return 1;}
}/*** @brief MP3格式音频播放主程序* @param mp3file MP3文件路径* @retval 无*/
void mp3PlayerDemo(const char *mp3file)
{uint8_t *read_ptr = inputbuf;int read_offset = 0; /* 读偏移指针 */int bytes_left = 0; /* 剩余字节数 */ mp3player.ucStatus = STA_IDLE;mp3player.ucVolume = 15; //音量值,100满//尝试打开MP3文件result = f_open(&file, mp3file, FA_READ);if(result != FR_OK){printf("Open mp3file :%s fail!!!->%d\r\n", mp3file, result);result = f_close (&file);return; /* 停止播放 */}printf("当前播放文件 -> %s\n", mp3file);//初始化MP3解码器Mp3Decoder = MP3InitDecoder(); if(Mp3Decoder == 0){printf("初始化helix解码库设备失败!\r\n");return; /* 停止播放 */}else{printf("初始化helix解码库完成\r\n");}//尝试读取一段MP3数据,并把读取的指针赋值read_ptr,长度赋值bytes_leftif(!read_file(mp3file, &read_ptr, &bytes_left)){MP3FreeDecoder(Mp3Decoder);return; /* 停止播放 */}//尝试解码成功if(MP3DataDecoder(&read_ptr, &bytes_left)){//打印MP3信息printf(" \r\n Bitrate %dKbps", Mp3FrameInfo.bitrate/1000);printf(" \r\n Samprate %dHz", Mp3FrameInfo.samprate);printf(" \r\n BitsPerSample %db", Mp3FrameInfo.bitsPerSample);printf(" \r\n nChans %d", Mp3FrameInfo.nChans);printf(" \r\n Layer %d", Mp3FrameInfo.layer);printf(" \r\n Version %d", Mp3FrameInfo.version);printf(" \r\n OutputSamps %d", Mp3FrameInfo.outputSamps);printf("\r\n");//启动DAC,开始发声if (Mp3FrameInfo.nChans == 1) //单声道要将outputSamps*2{DAC_DMA_Start(Mp3FrameInfo.samprate, 2 * Mp3FrameInfo.outputSamps);}else//双声道直接用Mp3FrameInfo.outputSamps{DAC_DMA_Start(Mp3FrameInfo.samprate, Mp3FrameInfo.outputSamps);}}else //解码失败{MP3FreeDecoder(Mp3Decoder);return;}/* 放音状态 */mp3player.ucStatus = STA_PLAYING;/* 进入主程序循环体 */while(mp3player.ucStatus == STA_PLAYING){//寻找帧同步,返回第一个同步字的位置read_offset = MP3FindSyncWord(read_ptr, bytes_left);if(read_offset < 0) //没有找到同步字{if(!read_file(mp3file, &read_ptr, &bytes_left))//重新读取一次文件再找{continue;//回到while(mp3player.ucStatus == STA_PLAYING)后面}}else//找到同步字{ read_ptr += read_offset; //偏移至同步字的位置bytes_left -= read_offset; //同步字之后的数据大小 if(bytes_left < 1024) //如果剩余的数据小于1024字节,补充数据{/* 注意这个地方因为采用的是DMA读取,所以一定要4字节对齐 */u16 i = (uint32_t)(bytes_left)&3; //判断多余的字节if(i) i=4-i; //需要补充的字节memcpy(inputbuf+i, read_ptr, bytes_left); //从对齐位置开始复制read_ptr = inputbuf+i; //指向数据对齐位置result = f_read(&file, inputbuf+bytes_left+i, INPUTBUF_SIZE-bytes_left-i, &bw);//补充数据if(result != FR_OK){printf("读取%s失败 -> %d\r\n",mp3file,result);break;}bytes_left += bw; //有效数据流大小}}//MP3数据解码并送入DAC缓存if(!MP3DataDecoder(&read_ptr, &bytes_left)){//如果播放出错,Isread置1,避免卡住死循环Isread = 1;}//mp3文件读取完成,退出if(file.fptr == file.fsize){printf("单曲播放完毕\r\n");break;} //等待DAC发送一半或全部中断while(Isread == 0){led_delay++;if(led_delay == 0xffffff){led_delay=0;LED1_TROG;}//Input_scan(); //等待DMA传输完成,此间可以运行按键扫描及处理事件}Isread = 0;}//运行到此处,说明单曲播放完成,收尾工作DAC_DMA_Stop();//停止喂DAC数据 mp3player.ucStatus = STA_IDLE;MP3FreeDecoder(Mp3Decoder);//清理缓存f_close(&file);
}void DMA1_Stream6_IRQHandler(void)
{if(DMA_GetITStatus(DMA1_Stream6, DMA_IT_HTIF6) != RESET) //半传输{ dac_ht = 1; Isread=1;DMA_ClearITPendingBit(DMA1_Stream6, DMA_IT_HTIF6);}if(DMA_GetITStatus(DMA1_Stream6, DMA_IT_TCIF6) != RESET) //全传输{dac_ht = 0;Isread=1;DMA_ClearITPendingBit(DMA1_Stream6, DMA_IT_TCIF6);}
}/***************************** (END OF FILE) *********************************/
源码:main.c
/********************************************************************************* @file ../User/main.c * @author ZL* @version V1.0* @date 2015-12-26* @brief Main program body******************************************************************************
**//* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "hw_includes.h"
#include "ff.h"
#include "exfuns.h"
#include "mp3Player.h"//遍历目录文件并打印输出
u8 scan_files(u8 * path)
{FRESULT res;char buf[512] = {0}; char *fn;#if _USE_LFNfileinfo.lfsize = _MAX_LFN * 2 + 1;fileinfo.lfname = buf;
#endifres = f_opendir(&dir,(const TCHAR*)path);if (res == FR_OK) { printf("\r\n"); while(1){res = f_readdir(&dir, &fileinfo); if (res != FR_OK || fileinfo.fname[0] == 0) break; #if _USE_LFNfn = *fileinfo.lfname ? fileinfo.lfname : fileinfo.fname;
#else fn = fileinfo.fname;
#endif printf("%s/", path); printf("%s\r\n", fn); } } return res;
}/*** @brief Main program* @param None* @retval None*/
int main(void)
{ delay_init(168);usart1_Init(115200);LED_Init();DAC_Config();if(!SD_Init()){exfuns_init(); //为fatfs相关变量申请内存 f_mount(fs[0],"0:",1); //挂载SD卡 }//打印SD目录和文件scan_files("0:");LED0_ON;while (1){mp3PlayerDemo("0:/断桥残雪.MP3");mp3PlayerDemo("0:/张国荣-玻璃之情.MP3");delay_ms(50);}
}
为方便调试测试,使用usart1打印数据。实测效果:


程序源码与原理图,测试音频:
链接:https://pan.baidu.com/s/10hYXkrqnuBQgs0DWKLUUOA?pwd=iatt
提取码:iatt
知道这里下载要积分登录什么的麻烦得很,所以程序放到百度网盘了,假如连接失效,记得在评论区喊我更新!
理论上STM32F1或者其他系列也能用这个方案,要自己改改测试喽,本文把思路分享出来抛砖引玉。
相关文章:
STM32F407使用Helix库软解MP3并通过DAC输出,最精简的STM32+SD卡实现MP3播放器
只用STM32单片机SD卡耳机插座,实现播放MP3播放器! 看过很多STM32软解MP3的方案,即不通过类似VS1053之类的解码器芯片,直接用STM32和软件库解码MP3文件,通常使用了labmad或者Helix解码库实现,Helix相对labm…...
STM32 CAN 过滤器设置
做个笔记吧 ,免得以后忘记了 芯片是stm32F207 ,用cubeMX 6.80 版本生成 CAN 的使用总体包含4个部分 第一步:CAN初始化,配置波特率 (cubeMX 里面配置好后自动生成,不需要手动添加) MX_CAN1_Init(); 第二步&#…...
日常BUG—— maven编译报错
😜作 者:是江迪呀✒️本文关键词:日常BUG、BUG、问题分析☀️每日 一言 :存在错误说明你在进步! 一、问题描述 一个maven项目在由于在代码中书写了如下代码: public static ConcurrentMap<…...
Unity 工具 之 Azure 微软SSML语音合成TTS流式获取音频数据的简单整理
Unity 工具 之 Azure 微软SSML语音合成TTS流式获取音频数据的简单整理 目录 Unity 工具 之 Azure 微软SSML语音合成TTS流式获取音频数据的简单整理 一、简单介绍 二、实现原理 三、实现步骤 四、关键代码 一、简单介绍 Unity 工具类,自己整理的一些游戏开发可…...
学习Vue:插值表达式和指令
在 Vue.js 中,Vue 实例与数据绑定是构建动态交互界面的关键。在这篇文章中,我们将重点介绍 Vue 实例中两种实现数据绑定的方式:插值表达式和指令。这些机制允许您将数据无缝地渲染到界面上,实现实时的数据更新和展示。 插值表达式…...
echart 3d立体颜色渐变柱状图
如果可以实现记得点赞分享,谢谢老铁~ 1.需求描述 根据业务需求将不同的法律法规,展示不同的3d立体渐变柱状图。 2.先看下效果图 3. 确定三面的颜色,这里我是自定义的颜色 // 右面生成颜色const rightColorArr ref(["#79D…...
linux shell变量
linux shell变量 1、变量命名规则2、只读变量3、删除变量 1、变量命名规则 变量名不能加$命名只能使用英文字母、数字和下划线,首个字母不能以数字开头中间不能有空格。可以有下划线不能使用标点符号不能使用bash中的关键字 username"tom"引用 $userna…...
Linux 发行版 Debian 12.1 发布
在今年 6 月初,Debian 12“bookworm”发布,而日前 Debian 迎来了 12.1 版本,主要修复系统用户创建等多个安全问题。 Debian 是最古老的 GNU / Linux 发行版之一,也是许多其他基于 Linux 的操作系统的基础,包括 Ubuntu…...
【Rust】Rust学习 第七章使用包、Crate和模块管理不断增长的项目
目前为止,我们编写的程序都在一个文件的一个模块中。伴随着项目的增长,你可以通过将代码分解为多个模块和多个文件来组织代码。一个包可以包含多个二进制 crate 项和一个可选的 crate 库。伴随着包的增长,你可以将包中的部分代码提取出来&…...
网站SSL安全证书是什么及其重要性
网站SSL安全证书具体来说是一个数字文件,是由受信任的数字证书颁发机构(CA机构)进行审核颁发的,其中包含CA发布的信息,该信息表明该网站已使用加密连接进行了安全保护。 网站SSL安全证书也被称为SSL证书、https证书和…...
Android Alarm闹钟API使用心得
前言 有什么办法可以在不打开App的时候,也能够触发一些操作呢?比如说发送通知,解决这个需求的办法有很多种选择,比如说官方推荐的WorkManager API,可以在后台执行一次性、耗时、定时的任务,但WorkManager是…...
什么是业务敏捷,如何实现业务敏捷?
点击链接了解详情 作者介绍 前言 随着越来越多行业的企业开始关注敏捷,业务敏捷(Business Agility)成为一个新的热点。毕竟大部分的行业和组织与软件无关,但是依然要实现业务上的敏捷,所以这个系列会主要谈两点&#…...
ATF(TF-A)安全通告 TFV-7 (CVE-2018-3639)
安全之安全(security)博客目录导读 ATF(TF-A)安全通告汇总 目录 一、ATF(TF-A)安全通告 TFV-7 (CVE-2018-3639) 二、静态缓解(Static mitigation) 三、动态缓解(Dynamic mitigation) 一、ATF(TF-A)安全通告 TFV-7 (CVE-2018…...
第三天课程下
1.项目目录介绍和运行流程 工程化开发模式中:这里不再直接编写模板语法,通过 App.vue 提供结构渲染 main.js文件 // 文件核心作用:导入App.vue,基于App.vue创建结构渲染index.html // 1. 导入 Vue 核心包 import Vue from vue// …...
嵌入式编译FFmpeg6.0版本并且组合x264
下载直通车:我用的是6.0版本的 1.准备编译: 2.进入ffmpeg源码目录,修改Makefile,添加编译选项: CFLAGS -fPIC 不加会报错 3.使用命令直接编译 ./configure --cross-prefix/home/xxx/bin/arm-linux-gnueabihf- --enable-cross-compile --targ…...
原子css 和 组件化css如何搭配使用
如果让你来实现下面这种页面,该怎么实现呢 原子化和css组件化方式写法,可以搭配起来使用,常用的css 原子css 比如 下面这些类似flex 布局,lstn curser-pointer 等常用的或者 具备一定规律性的padding margin 样式可以抽取为单独…...
Python 实现Selenium录屏的一种方法(图片整合成动态图)
由于UI层自动化的不稳定性,经常会遇到执行中断或用例失败的问题,以下是一些常见的措施。 1.详细的日志 2.定位出错时截图 3.Pytest的缓存机制(可以记录成功了哪些失败了哪些) 4.自动重试机制(如pytest-rerunfailures) 5.用例录像 用例录像是最直观的一…...
【设计模式——学习笔记】23种设计模式——策略模式Strategy(原理讲解+应用场景介绍+案例介绍+Java代码实现)
文章目录 案例引入传统方案实现实现分析 介绍基本介绍登场角色 案例实现案例一类图实现 案例二类图实现问答 策略模式在JDK源码中的使用总结文章说明 案例引入 有各种鸭子,比如野鸭、北京鸭、水鸭等。 鸭子有各种行为,比如走路、叫、飞行等。不同鸭子的…...
通讯商二要素Api接口验证真伪
随着互联网的普及和各种社交平台、电商平台、金融平台的发展,许多业务都需要用户进行实名认证,这也就涉及到了手机号码和姓名的验证问题。为了解决这个问题,现在有很多运营商提供的二要素API接口能够进行手机号码和姓名的验证,本文…...
React源码解析18(6)------ 实现useState
摘要 在上一篇文章中,我们已经实现了函数组件。同时可以正常通过render进行渲染。 而通过之前的文章,beginWork和completeWork也已经有了基本的架子。现在我们可以去实现useState了。 实现之前,我们要先修改一下我们的index.js文件&#x…...
React Native 开发环境搭建(全平台详解)
React Native 开发环境搭建(全平台详解) 在开始使用 React Native 开发移动应用之前,正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南,涵盖 macOS 和 Windows 平台的配置步骤,如何在 Android 和 iOS…...
React第五十七节 Router中RouterProvider使用详解及注意事项
前言 在 React Router v6.4 中,RouterProvider 是一个核心组件,用于提供基于数据路由(data routers)的新型路由方案。 它替代了传统的 <BrowserRouter>,支持更强大的数据加载和操作功能(如 loader 和…...
.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...
解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八
现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet,点击确认后如下提示 最终上报fail 解决方法 内核升级导致,需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...
[10-3]软件I2C读写MPU6050 江协科技学习笔记(16个知识点)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一)
宇树机器人多姿态起立控制强化学习框架论文解析 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一) 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化…...
鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/
使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题:docker pull 失败 网络不同,需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...
什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南
文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果