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或者其他系列也能用这个方案,要自己改改测试喽,本文把思路分享出来抛砖引玉。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/43882.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

WebRTC音视频通话-WebRTC视频自定义RTCVideoCapturer相机

WebRTC音视频通话-WebRTC视频自定义RTCVideoCapturer相机 在之前已经实现了WebRTC调用ossrs服务&#xff0c;实现直播视频通话功能。但是在使用过程中&#xff0c;RTCCameraVideoCapturer类提供的方法不能修改及调节相机的灯光等设置&#xff0c;那就需要自定义RTCVideoCaptur…

到江西赣州ibm维修服务器之旅-联想X3850 x6黄灯故障

2023年08月15日&#xff0c;一位江西赣州工厂客户通过朋友介绍与冠峰售前工程师取得联系&#xff0c;双方对产品故障前后原因沟通的大致情况如下&#xff1a; 服务器型号&#xff1a;Lenovo system x3850 x6 为用户公司erp仓库服务器 服务器故障&#xff1a;正常使用过程中业…

<数据结构与算法>二叉树堆的实现

目录 前言 一、树的概念及结构 1 树的概念 2 树的相关概念 二、二叉树的概念及结构 1.二叉树的概念 2. 特殊的二叉树 3. 二叉树的性质 4.二叉树的存储结构 三、二叉树的顺序结构及实现 1.堆的性质 2.堆的插入 3.堆的实现 堆的结构体 HeapInit 初始化 HeapPush 插入 HeapPop 删…

【C++进阶】继承、多态的详解(多态篇)

【C进阶】继承、多态的详解&#xff08;多态篇&#xff09; 目录 【C进阶】继承、多态的详解&#xff08;多态篇&#xff09;多态的概念多态的定义及实现多态的构成条件&#xff08;重点&#xff09;虚函数虚函数的重写&#xff08;覆盖、一种接口继承&#xff09;C11 override…

solr快速上手:聚合分组查询|嵌套分组指南(十二)

0. 引言 solr作为搜索引擎经常用于各类查询场景&#xff0c;我们之前讲解了solr的查询语法&#xff0c;而除了普通的查询语法&#xff0c;有时我们还需要实现聚合查询来统计一些指标&#xff0c;所以今天我们接着来查看solr的聚合查询语法 1. 常用聚合查询语法 以下演示我们…

面试题-React(一):React是什么?它的主要特点是什么?

探索React&#xff1a;前端开发中的重要角色与主要特点 引言&#xff1a; 在现代前端开发领域&#xff0c;React已经成为最受欢迎和广泛使用的JavaScript库之一。它由Facebook开发并于2013年首次发布。随着时间的推移&#xff0c;React在开发社区中获得了强大的支持和认可。本…

画质提升+带宽优化,小红书音视频团队端云结合超分落地实践

随着视频业务和短视频播放规模不断增长&#xff0c;小红书一直致力于研究&#xff1a;如何在保证提升用户体验质量的同时降低视频带宽成本&#xff1f; 在近日结束的音视频技术大会「LiveVideoStackCon 2023」上海站中&#xff0c;小红书音视频架构视频图像处理算法负责人剑寒向…

基于注意力神经网络的深度强化学习探索方法:ARiADNE

ARiADNE:A Reinforcement learning approach using Attention-based Deep Networks for Exploration 文章目录 ARiADNE:A Reinforcement learning approach using Attention-based Deep Networks for Exploration机器人自主探索(ARE)ARE的传统边界法非短视路径深度强化学习的方…

Python | Package | Python的三种包安装方式(pip/whl/tar.gz)

文章目录 PIP 安装与卸载Source 安装与卸载Whell 安装与卸载 PIP 安装与卸载 pip install xxx pip install xxxversion_numberpip install captcha pip install captcha0.4# XXX/anaconda3/envs/py373/lib/python3.7/site-packages pip uninstall captchaSource 安装与卸载 p…

C++音乐播放系统

C音乐播放系统 音乐的好处c发出声音乐谱与赫兹对照把歌打到c上 学习c的同学们都知道&#xff0c;c是一个一本正经的编程语言&#xff0c;因该没有人用它来做游戏、做病毒、做…做…做音乐播放系统吧&#xff01;&#xff01; 音乐的好处 提升情绪&#xff1a;音乐能够影响我们…

java语言B/S架构云HIS医院信息系统源码【springboot】

医院云HIS全称为基于云计算的医疗卫生信息系统( Cloud- Based Healthcare Information System)&#xff0c;是运用云计算、大数据、物联网等新兴信息技术&#xff0c;按照现代医疗卫生管理要求&#xff0c;在一定区域范围内以数字化形式提供医疗卫生行业数据收集、存储、传递、…

动手学深度学习--基础知识上篇

&#x1f388;动手学deep learning ☁️本专栏会定期更新关于动手学深度学习的每章知识点的讲解&#xff0c;题目答案 &#x1f47b;如果喜欢&#xff0c;欢迎点赞&#xff0c;收藏 动手学深度学习-预备知识篇 线性代数篇 1-3题讲解 证明一个矩阵 A \mathbf{A} A的转置的转置…

安卓手机跑 vins slam (2)

既然选择把vins的代码移植到新工程&#xff0c;那么就需要先确定自己电脑的Android Studio的C开发环节是OK的&#xff0c;可以通过创建C的示例工程&#xff0c;能正常跑通做验证。 选择Native C 需要选择用C哪个版本&#xff0c; 这里通过百度搜索&#xff0c;slam 编译需要C 1…

电脑提示丢失(或找不到)msvcp120.dll解决办法

msvcp140.dll是Microsoft Visual C Redistributable的一部分&#xff0c;它是Windows操作系统中的一个动态链接库文件。这个文件包含了许多C标准库函数的实现&#xff0c;对于一些依赖C标准库的应用程序来说&#xff0c;msvcp140.dll是非常重要的。msvcp140.dll的主要用途是提供…

热电联产在综合能源系统中的选址定容研究(matlab代码)

目录 1 主要内容 目标函数 程序模型 2 部分代码 3 程序结果 1 主要内容 该程序参考《热电联产在区域综合能源系统中的定容选址研究》&#xff0c;主要针对电热综合能源系统进行优化&#xff0c;确定热电联产机组的位置和容量&#xff0c;程序以33节点电网和17节点热网为例…

CI/CD入门(二)

CI/CD入门(二) 目录 CI/CD入门(二) 1、代码上线方案 1.1 早期手动部署代码1.2 合理化上线方案1.3 大型企业上线制度和流程1.4 php程序代码上线的具体方案1.5 Java程序代码上线的具体方案1.6 代码上线解决方案注意事项2、理解持续集成、持续交付、持续部署 2.1 持续集成2.2 持续…

小白到运维工程师自学之路 第七十五集 (Kubernetes 企业级高可用部署)2

8、添加master节点 在k8s-master2和k8s-master3节点创建文件夹 mkdir -p /etc/kubernetes/pki/etcd在k8s-master1节点执行 从k8s-master1复制密钥和相关文件到k8s-master2和k8s-master3 scp /etc/kubernetes/admin.conf root192.168.77.15:/etc/kubernetes scp /etc/kubernet…

UAF释放后重引用原理

原地址&#xff1a;https://blog.csdn.net/qq_31481187/article/details/73612451 原作者代码是基于linux系统的演示代码&#xff0c;因为windows和Linux 内存管理机制上略有不同&#xff0c;该程序在Windows需要稍微做些改动。 Windows上执行free释放malloc函数分配的内存后…

Docker碎碎念

docker和虚拟机的区别 虚拟机&#xff08;VM&#xff09;是通过在物理硬件上运行一个完整的操作系统来实现的。 每个虚拟机都有自己的内核、设备驱动程序和用户空间&#xff0c;它们是相互独立且完全隔离的。 虚拟机可以在不同的物理服务器之间迁移&#xff0c;因为它们是以整…

淘宝搜索店铺列表API:关键字搜索店铺信息 获取店铺主页 店铺所在地 服务评级

接口名称&#xff1a;item_search_seller 基本功能介绍 该API可以通过传入关键字&#xff0c;获取到淘宝商城的店铺列表&#xff0c;支持翻页显示。指定参数page获取到指定页的数据。返回的店铺信息包括&#xff1a;店铺名、店铺ID、店铺主页、宝贝图片、掌柜名字、店铺所在地…