| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401 |
- PROJECT = "pcm32_spectrum"
- VERSION = "1.0.0"
- _G.sys = require("sys")
- -- 32bit PCM频谱分析
- function generate_32bit_spectrum(pcm_filename, output_filename)
- local sample_rate = 24000
- local fft_points = 256 -- 使用较小的FFT点数节省内存
- local df = sample_rate / fft_points
- log.info("分析", "32bit PCM频谱分析")
- log.info("参数", string.format("采样率: %dHz, FFT点数: %d", sample_rate, fft_points))
- -- 检查文件是否存在
- if not io.exists(pcm_filename) then
- log.error("文件", "PCM文件不存在: " .. pcm_filename)
- return false
- end
- -- 获取文件大小
- local file_size = io.fileSize(pcm_filename)
- if not file_size then
- log.error("文件", "无法获取文件大小")
- return false
- end
- log.info("文件", string.format("文件大小: %d 字节", file_size))
- log.info("文件", string.format("约 %d 个32bit样本", file_size // 4))
- -- 准备FFT缓冲区
- local real_buf = zbuff.create(fft_points * 2)
- local imag_buf = zbuff.create(fft_points * 2)
- local Wc = zbuff.create((fft_points // 2) * 2)
- local Ws = zbuff.create((fft_points // 2) * 2)
- fft.generate_twiddles_q15_to_zbuff(fft_points, Wc, Ws)
- -- 读取文件
- local file = io.open(pcm_filename, "rb")
- if not file then
- log.error("文件", "无法打开PCM文件")
- return false
- end
- -- 读取一个片段(32bit = 4字节 per sample)
- local bytes_to_read = fft_points * 4
- local chunk = file:read(bytes_to_read)
- file:close()
- if not chunk or #chunk < 4 then
- log.error("读取", "文件数据不足")
- return false
- end
- log.info("读取", string.format("读取了 %d 字节", #chunk))
- -- 处理32bit PCM数据
- local samples_processed = 0
- for i = 1, math.floor(#chunk / 4) do
- local byte_pos = (i - 1) * 4 + 1
- -- 32bit小端序解析
- local b1 = chunk:byte(byte_pos) -- 最低字节
- local b2 = chunk:byte(byte_pos + 1)
- local b3 = chunk:byte(byte_pos + 2)
- local b4 = chunk:byte(byte_pos + 3) -- 最高字节
- -- 组合成32位整数
- local sample = b1 + b2 * 256 + b3 * 65536 + b4 * 16777216
- -- 32bit有符号转换 (范围: -2147483648 到 2147483647)
- if sample >= 2147483648 then
- sample = sample - 4294967296
- end
- -- 32bit转U12格式
- local normalized = (sample / 2147483648.0) -- 归一化到 -1.0 到 1.0
- local u12_val = math.floor((normalized + 1.0) * 2047.5 + 0.5) -- 转到 0-4095
- if u12_val < 0 then u12_val = 0 end
- if u12_val > 4095 then u12_val = 4095 end
- real_buf:seek((i - 1) * 2, zbuff.SEEK_SET)
- real_buf:writeU16(u12_val)
- imag_buf:seek((i - 1) * 2, zbuff.SEEK_SET)
- imag_buf:writeU16(0)
- samples_processed = samples_processed + 1
- -- 每处理64个样本输出一次进度
- if i % 64 == 0 then
- log.info("处理", string.format("已处理 %d/%d 样本", i, math.floor(#chunk / 4)))
- end
- end
- -- 如果数据不足,用0填充
- for i = samples_processed + 1, fft_points do
- real_buf:seek((i - 1) * 2, zbuff.SEEK_SET)
- real_buf:writeU16(2048) -- 中间值
- imag_buf:seek((i - 1) * 2, zbuff.SEEK_SET)
- imag_buf:writeU16(0)
- end
- log.info("FFT", "开始执行FFT计算...")
- -- 执行FFT
- fft.run(real_buf, imag_buf, fft_points, Wc, Ws, {
- core = "q15",
- input_format = "u12"
- })
- -- 生成输出文件
- local out_file = io.open(output_filename, "w")
- if not out_file then
- log.error("文件", "无法创建输出文件")
- return false
- end
- -- 写入文件头
- local ret, err = out_file:write('{\n')
- if not ret then
- log.error("写入", "写入文件头失败:", err)
- out_file:close()
- return false
- end
- out_file:write(' "sample_rate": 24000,\n')
- out_file:write(' "fft_points": 256,\n')
- out_file:write(' "frequency_resolution": ' .. df .. ',\n')
- out_file:write(' "max_frequency": 12000,\n')
- out_file:write(' "bit_depth": 32,\n')
- out_file:write(' "spectrum_data": [\n')
- -- 收集频谱数据
- local freq_data = {}
- local max_magnitude = 0
- -- 先扫描找到最大值用于归一化
- for k = 1, fft_points // 2 do
- real_buf:seek(k * 2, zbuff.SEEK_SET)
- imag_buf:seek(k * 2, zbuff.SEEK_SET)
- local r = real_buf:readI16()
- local i = imag_buf:readI16()
- local magnitude = math.sqrt(r * r + i * i)
- if magnitude > max_magnitude then
- max_magnitude = magnitude
- end
- end
- -- 生成归一化的频谱数据
- for k = 1, fft_points // 2 do
- real_buf:seek(k * 2, zbuff.SEEK_SET)
- imag_buf:seek(k * 2, zbuff.SEEK_SET)
- local r = real_buf:readI16()
- local i = imag_buf:readI16()
- local magnitude = math.sqrt(r * r + i * i)
- -- 归一化到0-100范围
- local normalized = 0
- if max_magnitude > 0 then
- normalized = (magnitude / max_magnitude) * 100
- end
- table.insert(freq_data, string.format("%.2f", normalized))
- -- 输出主要频点信息
- if normalized > 10 then -- 只显示能量大于10%的频点
- local freq = (k - 1) * df
- log.info("频点", string.format("%.0fHz: %.1f%%", freq, normalized))
- end
- end
- -- 写入频谱数据
- out_file:write(' { "time": 0.0, "freq": [')
- out_file:write(table.concat(freq_data, ", "))
- out_file:write('] }\n')
- out_file:write(' ]\n')
- out_file:write('}\n')
- out_file:close()
- log.info("完成", "32bit PCM频谱分析完成")
- log.info("文件", "频谱数据已保存到: " .. output_filename)
- return true
- end
- -- 快速测试版本(不生成文件,直接输出)
- function quick_32bit_test(pcm_filename)
- local sample_rate = 24000
- local fft_points = 128
- log.info("快速测试", "32bit PCM快速频谱分析")
- -- 检查文件是否存在
- if not io.exists(pcm_filename) then
- log.error("文件", "PCM文件不存在")
- return false
- end
- -- 最小内存分配
- local real_buf = zbuff.create(fft_points * 2)
- local imag_buf = zbuff.create(fft_points * 2)
- local Wc = zbuff.create((fft_points // 2) * 2)
- local Ws = zbuff.create((fft_points // 2) * 2)
- fft.generate_twiddles_q15_to_zbuff(fft_points, Wc, Ws)
- local file = io.open(pcm_filename, "rb")
- if not file then
- log.error("文件", "无法打开文件")
- return false
- end
- local chunk = file:read(512) -- 只读512字节
- file:close()
- if not chunk then
- log.error("读取", "读取文件失败")
- return false
- end
- log.info("读取", string.format("读取了 %d 字节", #chunk))
- -- 处理32bit数据
- local samples_processed = 0
- for i = 1, math.min(fft_points, math.floor(#chunk / 4)) do
- local byte_pos = (i - 1) * 4 + 1
- if byte_pos + 3 <= #chunk then
- local b1 = chunk:byte(byte_pos)
- local b2 = chunk:byte(byte_pos + 1)
- local b3 = chunk:byte(byte_pos + 2)
- local b4 = chunk:byte(byte_pos + 3)
- local sample = b1 + b2 * 256 + b3 * 65536 + b4 * 16777216
- if sample >= 2147483648 then sample = sample - 4294967296 end
- -- 简化转换
- local normalized = sample / 2147483648.0
- local u12_val = math.floor((normalized + 1.0) * 2047.5)
- if u12_val < 0 then u12_val = 0 end
- if u12_val > 4095 then u12_val = 4095 end
- real_buf:seek((i - 1) * 2, zbuff.SEEK_SET)
- real_buf:writeU16(u12_val)
- imag_buf:seek((i - 1) * 2, zbuff.SEEK_SET)
- imag_buf:writeU16(0)
- samples_processed = samples_processed + 1
- end
- end
- -- 填充剩余数据
- for i = samples_processed + 1, fft_points do
- real_buf:seek((i - 1) * 2, zbuff.SEEK_SET)
- real_buf:writeU16(2048)
- imag_buf:seek((i - 1) * 2, zbuff.SEEK_SET)
- imag_buf:writeU16(0)
- end
- -- FFT计算
- fft.run(real_buf, imag_buf, fft_points, Wc, Ws, {
- core = "q15",
- input_format = "u12"
- })
- -- 直接输出结果
- log.info("=== 频谱结果 ===")
- local df = sample_rate / fft_points
- local peaks = {}
- for k = 2, fft_points // 2 do -- 跳过直流分量
- real_buf:seek(k * 2, zbuff.SEEK_SET)
- imag_buf:seek(k * 2, zbuff.SEEK_SET)
- local r = real_buf:readI16()
- local i = imag_buf:readI16()
- local magnitude = math.sqrt(r * r + i * i)
- local freq = (k - 1) * df
- if magnitude > 100 then
- table.insert(peaks, { freq = freq, mag = magnitude })
- end
- end
- -- 按幅度排序,显示前10个
- table.sort(peaks, function(a, b) return a.mag > b.mag end)
- for i = 1, math.min(10, #peaks) do
- log.info("峰值" .. i, string.format("%.0fHz (%.1f)", peaks[i].freq, peaks[i].mag))
- end
- if #peaks == 0 then
- log.info("结果", "未检测到明显频率峰值")
- end
- return true
- end
- -- 极简版本:只分析前几个样本
- function minimal_32bit_test(pcm_filename)
- log.info("极简测试", "32bit PCM极简分析")
- if not io.exists(pcm_filename) then
- log.error("文件", "文件不存在")
- return false
- end
- local file_size = io.fileSize(pcm_filename)
- if not file_size or file_size < 16 then
- log.error("文件", "文件太小")
- return false
- end
- log.info("文件", string.format("文件大小: %d 字节", file_size))
- -- 只读取前16个字节(4个样本)
- local data = io.readFile(pcm_filename, "rb", 0, 16)
- if not data then
- log.error("读取", "读取文件失败")
- return false
- end
- log.info("数据", string.format("读取了 %d 字节", #data))
- -- 分析前4个样本
- for i = 1, math.min(4, math.floor(#data / 4)) do
- local byte_pos = (i - 1) * 4 + 1
- if byte_pos + 3 <= #data then
- local b1 = data:byte(byte_pos)
- local b2 = data:byte(byte_pos + 1)
- local b3 = data:byte(byte_pos + 2)
- local b4 = data:byte(byte_pos + 3)
- local sample = b1 + b2 * 256 + b3 * 65536 + b4 * 16777216
- if sample >= 2147483648 then sample = sample - 4294967296 end
- local normalized = sample / 2147483648.0
- log.info("样本" .. i, string.format("原始值: %d, 归一化: %.6f", sample, normalized))
- end
- end
- return true
- end
- -- 主程序
- sys.taskInit(function()
- if not fft then
- log.error("FFT", "不支持FFT库")
- return
- end
- sys.wait(3000) -- 等待系统稳定
- local pcm_file = "/luadb/qlx_13sec.pcm"
- local spectrum_file = "/spectrum.json"
- if not io.exists(pcm_file) then
- log.warn("文件", "PCM文件不存在: " .. pcm_file)
- log.info("提示", "请将32bit PCM文件命名为 audio.pcm 并放入文件系统")
- return
- end
- log.info("开始", "32bit PCM频谱分析...")
- -- 先尝试极简测试
- if minimal_32bit_test(pcm_file) then
- log.info("成功", "极简测试完成")
- -- 然后尝试快速测试
- sys.wait(1000)
- if quick_32bit_test(pcm_file) then
- log.info("成功", "快速测试完成")
- -- 最后进行完整分析
- sys.wait(1000)
- if generate_32bit_spectrum(pcm_file, spectrum_file) then
- log.info("完成", "完整频谱分析完成!")
- -- 检查输出文件
- if io.exists(spectrum_file) then
- local out_size = io.fileSize(spectrum_file)
- if out_size then
- log.info("输出", string.format("频谱文件大小: %d 字节", out_size))
- end
- end
- else
- log.error("失败", "完整频谱分析失败")
- end
- else
- log.error("失败", "快速测试失败")
- end
- else
- log.error("失败", "极简测试失败")
- end
- log.info("程序", "分析流程结束")
- require("tcp_test")
- dtuDemo(1, "112.125.89.8", 32062)
- end)
- require("test")
|