Rust驱动声卡播放声音 -- Rust开发ESP32S3物联网应用(三)

B站影视 内地电影 2025-11-13 16:03 3

摘要:fn new -> Result { // 初始化I2S驱动MAX98357 info!("========== Creating I2S driver =========="); let i2s_config = StdConfig::new(

今天我们还是用易芯联的小智AI聊天机器人进一步介绍驱动其I2S的音频播放模块,通过扬声器播放声音。

易芯联小智AI机器人使用的是I2S声卡芯片MAX98357,各引脚接线

gpio15 --> BLCKGPIO7 --> DOUTGPIO16 --> WSstruct DingDongPlayer { i2s: I2sDriver,}

I2S的接口比较简单,我们先初始化I2S接口:

fn new -> Result { // 初始化I2S驱动MAX98357 info!("========== Creating I2S driver =========="); let i2s_config = StdConfig::new( Config::default .auto_clear(false) .dma_buffer_count(2) .frames_per_buffer(512), StdClkConfig::from_sample_rate_hz(SAMPLE_RATE_HZ), StdSlotConfig::philips_slot_default(DataBitWidth::Bits16, SlotMode::Mono), StdGPIOConfig::default, ); let peripherals = Peripherals::take?; let i2s_0 = peripherals.i2s0; let bclk = peripherals.pins.gpio15; // version 1.1, version 1.0 uses let dout = peripherals.pins.gpio7; let ws = peripherals.pins.gpio16; // same as lrclk let mclk = AnyIOPin::none; let i2s = i2sDriver::::new_std_tx(i2s_0, &i2s_config, bclk, dout, mclk, ws)?; //i2s.tx_enable; info!("I2S驱动初始化成功"); info!("GPIO配置: BCLK=15, LRCLK=7, DIN=16"); Ok(Self { i2s }) }

初始化完成直接往芯片里写数据即可:

fn play_audio_data(&mut self, audio_data: &[i16]) -> Result { if audio_data.is_empty { return Err(anyhow::anyhow!("没有音频数据可播放")); } info!("开始播放音频数据: {} 样本", audio_data.len / 2); // 将i16样本转换为u8字节 let byte_data = unsafe { std::slice::from_raw_parts( audio_data.as_ptr as *const u8, audio_data.len * 2, // 每个i16是2字节 ) }; // 分块播放,避免一次性写入导致问题 let chunk_size = 1024; // 每次写入的字节数 let mut position = 0; self.i2s.tx_enable.unwrap; while position { // 写入成功 } Err(e) => { error!("I2S写入失败: {:?}", e); return Err(anyhow::anyhow!("I2S写入失败")); } } position = end; // 小延迟,让I2S有时间处理数据 thread::sleep(Duration::from_micros(100)); } let zerios = vec![0u8; 8]; let _ = self.i2s.write_all(&zerios, 1000); self.i2s.tx_disable.unwrap; info!("音频播放完成"); Ok() }

为了测试,我们生成一些测试用的音频数据,然后播放。

下面是完整的代码:

use anyhow::Result;use esp_idf_hal::gpio::*;use esp_idf_hal::prelude::*;use esp_idf_svc::log::EspLogger;use log::{error, info};use std::thread;use std::time::Duration;use { esp_idf_hal::{ i2s::{ config::{ Config, DataBitWidth, SlotMode, StdClkConfig, StdConfig, StdGpioConfig, StdSlotConfig, }, I2sDriver, I2sTx, }, },};struct DingDongPlayer { i2s: I2sDriver,}const SAMPLE_RATE_HZ: u32 = 44100;impl DingDongPlayer { fn new -> Result { // 初始化I2S驱动MAX98357 info!("========== Creating I2S driver =========="); let i2s_config = StdConfig::new( Config::default .auto_clear(false) .dma_buffer_count(2) .frames_per_buffer(512), StdClkConfig::from_sample_rate_hz(SAMPLE_RATE_HZ), StdSlotConfig::philips_slot_default(DataBitWidth::Bits16, SlotMode::Mono), StdGpioConfig::default, ); let peripherals = Peripherals::take?; let i2s_0 = peripherals.i2s0; let bclk = peripherals.pins.gpio15; // version 1.1, version 1.0 uses let dout = peripherals.pins.gpio7; let ws = peripherals.pins.gpio16; // same as lrclk let mclk = AnyIOPin::none; let i2s = I2sDriver::::new_std_tx(i2s_0, &i2s_config, bclk, dout, mclk, ws)?; //i2s.tx_enable; info!("I2S驱动初始化成功"); info!("GPIO配置: BCLK=15, LRCLK=7, DIN=16"); Ok(Self { i2s }) } // 生成"叮"声 - 高频率正弦波 (880Hz) fn generate_ding_tone(&self) -> Vec { let sample_rate = 44100; let frequency = 1400.0; // A5音符 let duration_ms = 150; let num_samples = (sample_rate as f32 * duration_ms as f32 / 1000.0) as usize; let mut samples = Vec::with_capacity(num_samples * 2); // 立体声 for i in 0..num_samples { let t = i as f32 / sample_rate as f32; let sample = (t * frequency * 2.0 * std::f32::consts::PI).sin; // 转换为16位PCM,应用包络减少爆音 let volume = self.envelope(i, num_samples); let pcm_sample = (sample * volume * 0.7 * i16::MAX as f32) as i16; // 立体声:左右声道相同 samples.push(pcm_sample); // 左声道 samples.push(pcm_sample); // 右声道 } info!( "生成'叮'声: {}Hz, {}ms, {}样本", frequency, duration_ms, num_samples ); samples } // 生成"咚"声 - 低频率正弦波 (440Hz) fn generate_dong_tone(&self) -> Vec { let sample_rate = 44100; let frequency = 1000.0; // A4音符 let duration_ms = 150; let num_samples = (sample_rate as f32 * duration_ms as f32 / 1000.0) as usize; let mut samples = Vec::with_capacity(num_samples * 2); // 立体声 for i in 0..num_samples { let t = i as f32 / sample_rate as f32; let sample = (t * frequency * 2.0 * std::f32::consts::PI).sin; // 转换为16位PCM,应用包络 let volume = self.envelope(i, num_samples); let pcm_sample = (sample * volume * 0.7 * i16::MAX as f32) as i16; // 立体声:左右声道相同 samples.push(pcm_sample); // 左声道 samples.push(pcm_sample); // 右声道 } info!( "生成'咚'声: {}Hz, {}ms, {}样本", frequency, duration_ms, num_samples ); samples } // 生成完整的"叮咚"提示音 fn generate_dingdong_sound(&self) -> Vec { let ding = self.generate_ding_tone; let dong = self.generate_dong_tone; let mut result = Vec::with_capacity(ding.len + dong.len); result.extend_from_slice(&ding); // 添加静音间隔 //let silence_samples = (44100.0 * 0.05) as usize * 2; //result.extend(std::iter::repeat(0).take(silence_samples)); result.extend_from_slice(&dong); result } // ADSR包络函数,减少爆音 fn envelope(&self, sample_index: usize, total_samples: usize) -> f32 { let attack = (total_samples as f32 * 0.1) as usize; // 10% 起音 let release = (total_samples as f32 * 0.3) as usize; // 30% 释音 if sample_index total_samples - release { // 释音阶段:从1线性减少到0 (total_samples - sample_index) as f32 / release as f32 } else { // 持续阶段:保持1 1.0 } } // 播放音频数据 fn play_audio_data(&mut self, audio_data: &[i16]) -> Result { if audio_data.is_empty { return Err(anyhow::anyhow!("没有音频数据可播放")); } info!("开始播放音频数据: {} 样本", audio_data.len / 2); // 将i16样本转换为u8字节 let byte_data = unsafe { std::slice::from_raw_parts( audio_data.as_ptr as *const u8, audio_data.len * 2, // 每个i16是2字节 ) }; // 分块播放,避免一次性写入导致问题 let chunk_size = 1024; // 每次写入的字节数 let mut position = 0; self.i2s.tx_enable.unwrap; while position { // 写入成功 } Err(e) => { error!("I2S写入失败: {:?}", e); return Err(anyhow::anyhow!("I2S写入失败")); } } position = end; // 小延迟,让I2S有时间处理数据 thread::sleep(Duration::from_micros(100)); } let zerios = vec![0u8; 8]; let _ = self.i2s.write_all(&zerios, 1000); self.i2s.tx_disable.unwrap; info!("音频播放完成"); Ok() } // 播放"叮咚"提示音 fn play_dingdong(&mut self) -> Result { info!("播放'叮咚'提示音"); let audio_data = self.generate_dingdong_sound; self.play_audio_data(&audio_data) } // 播放简单的蜂鸣声(备用方案) fn play_beep(&mut self, frequency: f32, duration_ms: u32) -> Result { info!("播放蜂鸣声: {}Hz, {}ms", frequency, duration_ms); let sample_rate = 44100; let num_samples = (sample_rate as f32 * duration_ms as f32 / 1000.0) as usize; let mut samples = Vec::with_capacity(num_samples * 2); for i in 0..num_samples { let t = i as f32 / sample_rate as f32; let sample = (t * frequency * 2.0 * std::f32::consts::PI).sin; let pcm_sample = (sample * 0.5 * i16::MAX as f32) as i16; samples.push(pcm_sample); // 左声道 samples.push(pcm_sample); // 右声道 } self.play_audio_data(&samples) }}fn main -> Result { // 初始化ESP-IDF esp_idf_svc::sys::link_patches; EspLogger::initialize_default; info!("ESP32-S3 叮咚提示音播放器启动"); // 创建播放器实例 let mut player = DingDongPlayer::new?; // 主循环:每隔5秒播放一次"叮咚" let mut counter = 0; loop { info!("播放次数: {}", counter + 1); // 播放"叮咚"提示音 match player.play_dingdong { Ok(_) => info!("播放成功"), Err(e) => { error!("播放失败: {:?}", e); // 备用方案:播放简单蜂鸣声 info!("尝试播放备用蜂鸣声"); let _ = player.play_beep(1000.0, 200); thread::sleep(Duration::from_millis(50)); let _ = player.play_beep(500.0, 400); } } counter += 1; info!("等待5秒后再次播放..."); thread::sleep(Duration::from_secs(5)); // 每播放5次后播放一个特殊提示 if counter % 5 == 0 { info!("播放特殊结束提示"); let _ = player.play_beep(2000.0, 100); thread::sleep(Duration::from_millis(50)); let _ = player.play_beep(1500.0, 100); thread::sleep(Duration::from_millis(50)); let _ = player.play_beep(1000.0, 100); } }}

来源:物联一尘

相关推荐