初始提交卡包 USB 工程
纳入固件源码、屏幕组件、nand_xfer 工具和文档。忽略本机构建产物、sdkconfig,以及含本机路径的依赖锁。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
|
||||
class AudioSourceFile {
|
||||
public:
|
||||
FILE* fp;
|
||||
|
||||
AudioSourceFile(const char* path) {
|
||||
fp = fopen(path, "rb");
|
||||
}
|
||||
|
||||
int read(uint8_t* buf, int len) {
|
||||
if (!fp) return 0;
|
||||
return fread(buf, 1, len, fp);
|
||||
}
|
||||
|
||||
void seek(int offset) {
|
||||
if (fp) fseek(fp, offset, SEEK_SET);
|
||||
}
|
||||
|
||||
bool isOpen() {
|
||||
return fp != nullptr;
|
||||
}
|
||||
|
||||
~AudioSourceFile() {
|
||||
if (fp) fclose(fp);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
#include "micro_decoder/decoder_source.h"
|
||||
#include "micro_decoder/types.h"
|
||||
#include <stdio.h>
|
||||
|
||||
static FILE* g_fp = nullptr;
|
||||
/*
|
||||
extern "C" {
|
||||
|
||||
void md_init(const char* path)
|
||||
{
|
||||
g_fp = fopen(path, "rb");
|
||||
}
|
||||
|
||||
int md_read(uint8_t* buf, int len)
|
||||
{
|
||||
if (!g_fp) return 0;
|
||||
return fread(buf, 1, len, g_fp);
|
||||
}
|
||||
|
||||
void md_deinit()
|
||||
{
|
||||
if (g_fp) {
|
||||
fclose(g_fp);
|
||||
g_fp = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
*/
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
//void md_init(const char* path);
|
||||
//int md_read(uint8_t* buf, int len);
|
||||
//void md_deinit();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,132 @@
|
||||
#include "ns4168.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/i2s.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_random.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
static const char *TAG = "ns4168";
|
||||
|
||||
static void ns4168_white_noise_task(void *arg) //白噪音测试
|
||||
{
|
||||
uint8_t buffer[256];
|
||||
size_t bytes_written = 0;
|
||||
|
||||
while (1) {
|
||||
for (int i = 0; i < (int)sizeof(buffer); i += 2) {
|
||||
uint16_t sample = (uint16_t)(esp_random() & 0xFFFF);
|
||||
buffer[i] = (uint8_t)(sample & 0xFF);
|
||||
buffer[i + 1] = (uint8_t)((sample >> 8) & 0xFF);
|
||||
}
|
||||
|
||||
esp_err_t ret = i2s_write(NS4168_I2S_PORT, buffer, sizeof(buffer), &bytes_written, portMAX_DELAY);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "i2s_write failed: %s", esp_err_to_name(ret));
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief 初始化 NS4168 的 CTRL 引脚并将其拉高(选择右声道,且退出关断模式)
|
||||
*/
|
||||
void ns4168_ctrl_init(void)
|
||||
{
|
||||
// 1. 配置 GPIO 参数结构体
|
||||
gpio_config_t io_conf = {
|
||||
.pin_bit_mask = (1ULL << NS4168_CTRL_GPIO), // 选中 GPIO33
|
||||
.mode = GPIO_MODE_OUTPUT, // 设置为推挽输出
|
||||
.intr_type = GPIO_INTR_DISABLE, // 禁止中断
|
||||
.pull_up_en = GPIO_PULLUP_DISABLE, // 关闭内部上拉(不需要)
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE, // 关闭内部下拉
|
||||
};
|
||||
|
||||
// 2. 应用配置
|
||||
esp_err_t ret = gpio_config(&io_conf);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE("NS4168", "GPIO33 配置失败!");
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 输出高电平(3.3V)
|
||||
gpio_set_level(NS4168_CTRL_GPIO, 1);
|
||||
|
||||
ESP_LOGI("NS4168", "NS4168 CTRL 引脚已拉高 (GPIO33),选择右声道");
|
||||
}
|
||||
|
||||
|
||||
esp_err_t ns4168_i2s_init(void)
|
||||
{
|
||||
ns4168_ctrl_init();
|
||||
esp_err_t ret = ESP_OK;
|
||||
|
||||
i2s_config_t i2s_cfg = {
|
||||
.mode = I2S_MODE_MASTER | I2S_MODE_TX,
|
||||
.sample_rate = NS4168_I2S_SAMPLE_RATE,
|
||||
.bits_per_sample = 16,
|
||||
.channel_format = NS4168_I2S_CHANNELS,
|
||||
.communication_format = I2S_COMM_FORMAT_STAND_I2S,
|
||||
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
|
||||
.dma_buf_count = 16,
|
||||
.dma_buf_len = 1024,
|
||||
.tx_desc_auto_clear = true,
|
||||
.use_apll = false,
|
||||
.fixed_mclk = 0,
|
||||
};
|
||||
|
||||
i2s_pin_config_t pin_cfg = {
|
||||
.mck_io_num = -1,
|
||||
.bck_io_num = NS4168_I2S_BCK_PIN,
|
||||
.ws_io_num = NS4168_I2S_WS_PIN,
|
||||
.data_out_num = NS4168_I2S_DOUT_PIN,
|
||||
.data_in_num = I2S_PIN_NO_CHANGE,
|
||||
};
|
||||
|
||||
ret = i2s_driver_install(NS4168_I2S_PORT, &i2s_cfg, 0, NULL);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "i2s_driver_install failed: %s", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = i2s_set_pin(NS4168_I2S_PORT, &pin_cfg);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "i2s_set_pin failed: %s", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = i2s_zero_dma_buffer(NS4168_I2S_PORT);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "i2s_zero_dma_buffer failed: %s", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "NS4168 I2S init OK");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t ns4168_start_white_noise_test(void)
|
||||
{
|
||||
esp_err_t ret = ns4168_i2s_init();
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
BaseType_t task_ret = xTaskCreatePinnedToCore(ns4168_white_noise_task, "ns4168_noise", 4096, NULL, 5, NULL, 1);
|
||||
if (task_ret != pdPASS) {
|
||||
ESP_LOGE(TAG, "create white noise task failed");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
#ifndef __NS4168_H__
|
||||
#define __NS4168_H__
|
||||
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/i2s.h"
|
||||
#include "esp_err.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* 先用宏定义占位引脚,后续再改成实际板子上的 GPIO */
|
||||
#define NS4168_I2S_PORT I2S_NUM_1
|
||||
#define NS4168_CTRL_GPIO GPIO_NUM_33
|
||||
#define NS4168_I2S_BCK_PIN GPIO_NUM_31
|
||||
#define NS4168_I2S_WS_PIN GPIO_NUM_32
|
||||
#define NS4168_I2S_DOUT_PIN GPIO_NUM_30
|
||||
#define NS4168_I2S_SAMPLE_RATE 24000
|
||||
#define NS4168_I2S_BITS I2S_BITS_PER_SAMPLE_16BIT
|
||||
#define NS4168_I2S_CHANNELS I2S_CHANNEL_FMT_ONLY_RIGHT
|
||||
|
||||
esp_err_t ns4168_i2s_init(void);
|
||||
esp_err_t ns4168_start_white_noise_test(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __NS4168_H__ */
|
||||
@@ -0,0 +1,58 @@
|
||||
#include "ogg_demux.h"
|
||||
|
||||
static uint32_t le32(const uint8_t* p)
|
||||
{
|
||||
return (uint32_t)p[0]
|
||||
| ((uint32_t)p[1] << 8)
|
||||
| ((uint32_t)p[2] << 16)
|
||||
| ((uint32_t)p[3] << 24);
|
||||
}
|
||||
|
||||
int ogg_parse_page(const uint8_t* buf, int buf_len, ogg_page_t* page)
|
||||
{
|
||||
if (buf_len < 27)
|
||||
return -1;
|
||||
|
||||
/* 校验 "OggS" */
|
||||
if (buf[0] != 'O' || buf[1] != 'g' || buf[2] != 'g' || buf[3] != 'S')
|
||||
return -2;
|
||||
|
||||
/* 拷贝页头 */
|
||||
for (int i = 0; i < 27; ++i)
|
||||
page->header[i] = buf[i];
|
||||
|
||||
page->seg_count = buf[26];
|
||||
|
||||
if (buf_len < 27 + page->seg_count)
|
||||
return -3;
|
||||
|
||||
/* 读取 segment table */
|
||||
for (int i = 0; i < page->seg_count; ++i)
|
||||
page->seg_table[i] = buf[27 + i];
|
||||
|
||||
/* 计算 body 大小 */
|
||||
uint32_t body_size = 0;
|
||||
for (int i = 0; i < page->seg_count; ++i)
|
||||
body_size += page->seg_table[i];
|
||||
|
||||
page->body_size = body_size;
|
||||
|
||||
if (buf_len < 27 + page->seg_count + body_size)
|
||||
return -4;
|
||||
|
||||
return (int)body_size;
|
||||
}
|
||||
|
||||
int ogg_extract_packet(const uint8_t* body, const ogg_page_t* page,
|
||||
uint8_t* packet, int packet_buf_size)
|
||||
{
|
||||
/* 极简版:假设一个页里只有一个完整 packet(常见于 OpusHead / OpusTags) */
|
||||
uint32_t total = page->body_size;
|
||||
if (total > (uint32_t)packet_buf_size)
|
||||
return -1;
|
||||
|
||||
for (uint32_t i = 0; i < total; ++i)
|
||||
packet[i] = body[i];
|
||||
|
||||
return (int)total;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#ifndef OGG_DEMUX_H
|
||||
#define OGG_DEMUX_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct {
|
||||
uint8_t header[27]; /* Ogg 页头 */
|
||||
uint8_t seg_count;
|
||||
uint8_t seg_table[255];
|
||||
uint32_t body_size;
|
||||
} ogg_page_t;
|
||||
|
||||
/* 从数据缓冲中解析一页 Ogg,返回页体大小,<0 表示错误 */
|
||||
int ogg_parse_page(const uint8_t* buf, int buf_len, ogg_page_t* page);
|
||||
|
||||
/* 根据 seg_table 把页体里的数据拼成一个 packet
|
||||
返回 packet 长度,<0 表示错误 */
|
||||
int ogg_extract_packet(const uint8_t* body, const ogg_page_t* page,
|
||||
uint8_t* packet, int packet_buf_size);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,379 @@
|
||||
#include <stdint.h>
|
||||
#include "esp_log.h"
|
||||
#include "NS4168/micro_decoder_wrapper.h"
|
||||
#include "NS4168/wav_demux.h"
|
||||
#include "NS4168/ns4168.h"
|
||||
#include <math.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
|
||||
static FILE* g_fp = NULL;
|
||||
|
||||
void md_init(const char* path)
|
||||
{
|
||||
g_fp = fopen(path, "rb"); // 标准 C
|
||||
}
|
||||
|
||||
int md_read(uint8_t* buf, int len)
|
||||
{
|
||||
if (!g_fp) return 0;
|
||||
return fread(buf, 1, len, g_fp); // 标准 C
|
||||
}
|
||||
|
||||
void md_deinit()
|
||||
{
|
||||
if (g_fp) {
|
||||
fclose(g_fp); // 标准 C
|
||||
g_fp = NULL; // 标准 C(替换 nullptr)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void play_wav(void)
|
||||
{
|
||||
ns4168_i2s_init();
|
||||
uint8_t header[70];
|
||||
size_t written = 0;
|
||||
int ret;
|
||||
|
||||
md_init("/extflash/ring2.wav");
|
||||
md_read(header, sizeof(header));
|
||||
ESP_LOG_BUFFER_HEX("WAV", header, sizeof(header));
|
||||
ESP_LOGI("WAV", "WAV解析成功,开始播放");
|
||||
uint8_t buf[1024];
|
||||
int n;
|
||||
while ((n = md_read(buf, sizeof(buf))) > 0) {
|
||||
i2s_write(NS4168_I2S_PORT, buf, n, &written, portMAX_DELAY);
|
||||
}
|
||||
|
||||
md_deinit();
|
||||
ESP_LOGI("WAV", "WAV播放结束");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param volume 音量系数,0.0=静音,1.0=原始音量,推荐 0.0~1.0
|
||||
*/
|
||||
void play_wav2(float volume)
|
||||
{
|
||||
|
||||
uint8_t header[70];
|
||||
size_t written = 0;
|
||||
|
||||
md_init("/extflash/ring2.wav");
|
||||
md_read(header, sizeof(header));
|
||||
ESP_LOG_BUFFER_HEX("WAV", header, sizeof(header));
|
||||
ESP_LOGI("WAV", "WAV解析成功,开始播放");
|
||||
|
||||
uint8_t buf[1024];
|
||||
int n;
|
||||
while ((n = md_read(buf, sizeof(buf))) > 0) {
|
||||
// 仅当音量不是 1.0 时才缩放(优化性能)
|
||||
if (volume != 1.0f) {
|
||||
int16_t *samples = (int16_t *)buf;
|
||||
int num_samples = n / 2; // 假定为 16-bit PCM
|
||||
for (int i = 0; i < num_samples; i++) {
|
||||
int32_t val = (int32_t)(samples[i] * volume);
|
||||
// 饱和限幅,防止溢出破音
|
||||
if (val > 32767) val = 32767;
|
||||
if (val < -32768) val = -32768;
|
||||
samples[i] = (int16_t)val;
|
||||
}
|
||||
}
|
||||
i2s_write(NS4168_I2S_PORT, buf, n, &written, portMAX_DELAY);
|
||||
}
|
||||
|
||||
md_deinit();
|
||||
ESP_LOGI("WAV", "WAV播放结束");
|
||||
}
|
||||
|
||||
|
||||
void play_wav3(float volume)
|
||||
{
|
||||
// 1. 打开文件
|
||||
md_init("/extflash/ring2.wav");
|
||||
// 2. 读取头部(沿用 70 字节,兼容你的原逻辑)
|
||||
uint8_t header[70];
|
||||
md_read(header, sizeof(header));
|
||||
ESP_LOG_BUFFER_HEX("WAV", header, sizeof(header));
|
||||
ESP_LOGI("WAV", "WAV解析成功,开始播放,音量=%.2f", volume);
|
||||
|
||||
// 3. 将浮点音量转换为 Q15 定点数 (范围 0 ~ 32768)
|
||||
int32_t vol_fixed = (int32_t)(volume * 32768.0f + 0.5f);
|
||||
if (vol_fixed > 32768) vol_fixed = 32768;
|
||||
if (vol_fixed < 0) vol_fixed = 0;
|
||||
|
||||
size_t written = 0;
|
||||
uint8_t buf[1024];
|
||||
int n;
|
||||
|
||||
// 4. 循环读取音频数据并处理
|
||||
while ((n = md_read(buf, sizeof(buf))) > 0) {
|
||||
// 确保处理偶数个字节,防止破坏样本对齐
|
||||
int bytes_to_process = (n / 2) * 2;
|
||||
int num_samples = bytes_to_process / 2;
|
||||
int16_t *samples = (int16_t *)buf;
|
||||
|
||||
// 音量接近静音时直接填零(避免处理微小信号产生的噪声)
|
||||
if (vol_fixed < 32) {
|
||||
memset(buf, 0, bytes_to_process);
|
||||
}
|
||||
else if (vol_fixed != 32768) { // 不等于 1.0 时才缩放
|
||||
for (int i = 0; i < num_samples; i++) {
|
||||
// 整数定点缩放 + 四舍五入
|
||||
int32_t val = ((int32_t)samples[i] * vol_fixed + 16384) >> 15;
|
||||
// 饱和限幅(实际不会溢出,但保留保护)
|
||||
if (val > 32767) val = 32767;
|
||||
if (val < -32768) val = -32768;
|
||||
samples[i] = (int16_t)val;
|
||||
}
|
||||
}
|
||||
// 发送对齐后的数据(丢弃奇数尾字节)
|
||||
i2s_write(NS4168_I2S_PORT, buf, bytes_to_process, &written, portMAX_DELAY);
|
||||
}
|
||||
|
||||
// 5. 关闭文件
|
||||
md_deinit();
|
||||
ESP_LOGI("WAV", "WAV播放结束");
|
||||
}
|
||||
|
||||
void play_wav4(float volume)
|
||||
{
|
||||
uint8_t header[70];
|
||||
size_t written = 0;
|
||||
|
||||
md_init("/extflash/ring2.wav");
|
||||
md_read(header, sizeof(header));
|
||||
ESP_LOG_BUFFER_HEX("WAV", header, sizeof(header));
|
||||
ESP_LOGI("WAV", "WAV解析成功,开始播放");
|
||||
|
||||
uint8_t buf[1024];
|
||||
int n;
|
||||
|
||||
while ((n = md_read(buf, sizeof(buf))) > 0) {
|
||||
|
||||
if (volume != 1.0f) {
|
||||
|
||||
int16_t *samples = (int16_t *)buf;
|
||||
int num_samples = n / 2; // 16-bit PCM
|
||||
|
||||
for (int i = 0; i < num_samples; i++) {
|
||||
|
||||
// 浮点缩放
|
||||
float scaled = (float)samples[i] * volume;
|
||||
|
||||
// 四舍五入(关键)
|
||||
int32_t val = lrintf(scaled);
|
||||
|
||||
// 饱和限幅
|
||||
if (val > 32767) val = 32767;
|
||||
if (val < -32768) val = -32768;
|
||||
|
||||
samples[i] = (int16_t)val;
|
||||
}
|
||||
}
|
||||
|
||||
i2s_write(NS4168_I2S_PORT, buf, n, &written, portMAX_DELAY);
|
||||
}
|
||||
|
||||
md_deinit();
|
||||
ESP_LOGI("WAV", "WAV播放结束");
|
||||
}
|
||||
|
||||
|
||||
void play_wav5(float volume)
|
||||
{
|
||||
uint8_t header[70];
|
||||
size_t written = 0;
|
||||
|
||||
md_init("/extflash/ring2.wav");
|
||||
md_read(header, sizeof(header));
|
||||
ESP_LOG_BUFFER_HEX("WAV", header, sizeof(header));
|
||||
ESP_LOGI("WAV", "WAV解析成功,开始播放");
|
||||
|
||||
uint8_t buf[1024];
|
||||
int n;
|
||||
|
||||
// 将浮点音量转换为 8-bit 定点(0~256)
|
||||
int32_t vol_fixed = (int32_t)(volume * 256.0f);
|
||||
|
||||
while ((n = md_read(buf, sizeof(buf))) > 0) {
|
||||
|
||||
if (volume != 1.0f) {
|
||||
|
||||
int16_t *samples = (int16_t *)buf;
|
||||
int num_samples = n / 2; // 16-bit PCM
|
||||
|
||||
for (int i = 0; i < num_samples; i++) {
|
||||
|
||||
// 原始 16-bit PCM
|
||||
int32_t v = samples[i];
|
||||
|
||||
// 定点缩放:扩大到 24-bit 精度再缩回
|
||||
v = (v * vol_fixed) >> 8;
|
||||
|
||||
// 饱和限幅
|
||||
if (v > 32767) v = 32767;
|
||||
if (v < -32768) v = -32768;
|
||||
|
||||
samples[i] = (int16_t)v;
|
||||
}
|
||||
}
|
||||
|
||||
i2s_write(NS4168_I2S_PORT, buf, n, &written, portMAX_DELAY);
|
||||
}
|
||||
|
||||
md_deinit();
|
||||
ESP_LOGI("WAV", "WAV播放结束");
|
||||
}
|
||||
|
||||
|
||||
void play_wav_p4(float volume)
|
||||
{
|
||||
uint8_t header[70];
|
||||
size_t written = 0;
|
||||
|
||||
md_init("/extflash/ring2.wav");
|
||||
md_read(header, sizeof(header));
|
||||
ESP_LOG_BUFFER_HEX("WAV", header, sizeof(header));
|
||||
ESP_LOGI("WAV", "WAV解析成功,开始播放");
|
||||
|
||||
uint8_t buf[1024];
|
||||
int n;
|
||||
|
||||
// 如果音量是 1.0,直接原样播放,完全无损
|
||||
bool bypass = (fabsf(volume - 1.0f) < 0.0001f);
|
||||
|
||||
// 简单 dither 强度(可以微调)
|
||||
const float dither_amp = 1.0f / 512.0f; // 很小的抖动
|
||||
|
||||
while ((n = md_read(buf, sizeof(buf))) > 0) {
|
||||
|
||||
if (!bypass) {
|
||||
|
||||
int16_t *samples = (int16_t *)buf;
|
||||
int num_samples = n / 2; // 16-bit PCM
|
||||
|
||||
for (int i = 0; i < num_samples; i++) {
|
||||
|
||||
// 提升到 float 32-bit 精度
|
||||
float v = (float)samples[i];
|
||||
|
||||
// 音量缩放
|
||||
v *= volume;
|
||||
|
||||
// 加一点点 dither,打散量化误差
|
||||
// 简单线性同余随机数(不依赖 rand())
|
||||
static uint32_t rnd = 0x12345678;
|
||||
rnd = rnd * 1664525u + 1013904223u;
|
||||
float noise = ((int32_t)(rnd >> 16) - 32768) * (dither_amp / 32768.0f);
|
||||
v += noise;
|
||||
|
||||
// 高精度四舍五入
|
||||
float v_rounded = floorf(v + 0.5f * (v >= 0 ? 1.0f : -1.0f));
|
||||
|
||||
// 饱和限幅
|
||||
if (v_rounded > 32767.0f) v_rounded = 32767.0f;
|
||||
if (v_rounded < -32768.0f) v_rounded = -32768.0f;
|
||||
|
||||
samples[i] = (int16_t)v_rounded;
|
||||
}
|
||||
}
|
||||
|
||||
i2s_write(NS4168_I2S_PORT, buf, n, &written, portMAX_DELAY);
|
||||
}
|
||||
|
||||
md_deinit();
|
||||
ESP_LOGI("WAV", "WAV播放结束");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "driver/i2s.h"
|
||||
|
||||
// 简单的 32-bit 伪随机数,用于 dither
|
||||
static inline uint32_t prng32(void)
|
||||
{
|
||||
static uint32_t s = 0x12345678u;
|
||||
s = s * 1664525u + 1013904223u;
|
||||
return s;
|
||||
}
|
||||
|
||||
void play_wav_ring_with_volume(float volume)
|
||||
{
|
||||
const char *TAG = "WAV_RING";
|
||||
|
||||
uint8_t header[70];
|
||||
size_t written = 0;
|
||||
|
||||
// 初始化并读取 WAV 头
|
||||
md_init("/extflash/ring2.wav");
|
||||
md_read(header, sizeof(header));
|
||||
ESP_LOG_BUFFER_HEX(TAG, header, sizeof(header));
|
||||
ESP_LOGI(TAG, "WAV解析成功,开始播放");
|
||||
|
||||
uint8_t buf[1024];
|
||||
int n;
|
||||
|
||||
// 音量为 1.0 时完全绕过处理,保证绝对无损
|
||||
bool bypass = (fabsf(volume - 1.0f) < 0.0001f);
|
||||
|
||||
// dither 强度(非常小,人耳几乎感知不到)
|
||||
const float dither_amp = 1.0f / 4096.0f; // 可微调
|
||||
|
||||
while ((n = md_read(buf, sizeof(buf))) > 0) {
|
||||
|
||||
if (!bypass) {
|
||||
|
||||
int16_t *samples = (int16_t *)buf;
|
||||
int num_samples = n / 2; // 16-bit PCM
|
||||
|
||||
for (int i = 0; i < num_samples; i++) {
|
||||
|
||||
// 提升到 float 32-bit 精度
|
||||
float v = (float)samples[i];
|
||||
|
||||
// 音量缩放
|
||||
v *= volume;
|
||||
|
||||
// 三角 dither(TDPF):(rand1 - rand2) * scale
|
||||
uint32_t r1 = prng32();
|
||||
uint32_t r2 = prng32();
|
||||
float noise = ((int32_t)(r1 & 0xFFFF) - (int32_t)(r2 & 0xFFFF))
|
||||
* (dither_amp / 65536.0f);
|
||||
v += noise;
|
||||
|
||||
// 高精度四舍五入
|
||||
float v_rounded = floorf(v + 0.5f * (v >= 0.0f ? 1.0f : -1.0f));
|
||||
|
||||
// 轻微 soft-clip,避免极端削波毛刺
|
||||
if (v_rounded > 32767.0f) {
|
||||
float over = v_rounded - 32767.0f;
|
||||
v_rounded = 32767.0f + over * 0.01f; // 把硬削波变成软削波
|
||||
} else if (v_rounded < -32768.0f) {
|
||||
float under = v_rounded + 32768.0f;
|
||||
v_rounded = -32768.0f + under * 0.01f;
|
||||
}
|
||||
|
||||
// 最终饱和限幅
|
||||
if (v_rounded > 32767.0f) v_rounded = 32767.0f;
|
||||
if (v_rounded < -32768.0f) v_rounded = -32768.0f;
|
||||
|
||||
samples[i] = (int16_t)v_rounded;
|
||||
}
|
||||
}
|
||||
|
||||
// 写入 I2S
|
||||
i2s_write(NS4168_I2S_PORT, buf, n, &written, portMAX_DELAY);
|
||||
}
|
||||
|
||||
md_deinit();
|
||||
ESP_LOGI(TAG, "WAV播放结束");
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
#ifndef WAV_DEMUX_H
|
||||
#define WAV_DEMUX_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
void play_wav(void);
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user