iOS平台从Hex/Binary原始数据生成音频文件方案咨询(BLE项目)
我刚好做过类似的BLE音频流项目,给你梳理下具体的实现步骤——核心是先把Hex数据转成原始音频二进制,再封装成WAV/MP3文件。先从最直接的WAV开始讲,因为它对原始PCM的兼容性最好:
第一步:明确硬件输出的音频参数(关键前提)
首先你必须从硬件那边确认几个核心参数,没有这些你生成的音频大概率是杂音或者播放异常:
- 采样率(比如44100Hz、16000Hz,常见语音场景用16kHz)
- 位深(比如16bit、8bit,16bit是主流)
- 声道数(单声道/立体声,BLE音频一般用单声道)
- 音频格式(是PCM原始脉冲编码,还是已经压缩过的MP3帧?如果是PCM就按下面的步骤处理;如果硬件已经发MP3帧,那Hex转二进制后直接拼接写入文件即可)
第二步:Hex字符串转二进制数据
iOS端拿到的HEX格式字符串(比如"00FF1234..."),首先要转成Data类型(Swift)或者NSData(Objective-C)。这里给你个Swift的实现示例:
func hexStringToData(_ hexString: String) -> Data? { // 先清理字符串,去掉空格并统一大写 let cleanedString = hexString.replacingOccurrences(of: " ", with: "").uppercased() var data = Data(capacity: cleanedString.count / 2) var index = cleanedString.startIndex while index < cleanedString.endIndex { let nextIndex = cleanedString.index(index, offsetBy: 2) // 每两个Hex字符转成一个字节 guard let byte = UInt8(cleanedString[index..<nextIndex], radix: 16) else { return nil } data.append(byte) index = nextIndex } return data }
第三步:封装成WAV文件(最直接的方式)
WAV文件本质是PCM原始数据 + 文件头,文件头里包含了我们第一步确认的所有参数。下面是Swift的封装示例,默认参数是语音场景常用的16kHz采样率、16bit位深、单声道:
func pcmDataToWav(data: Data, sampleRate: Int = 16000, bitDepth: Int = 16, channels: Int = 1) -> Data? { let sampleCount = data.count / (bitDepth / 8) let byteRate = sampleRate * channels * (bitDepth / 8) let blockAlign = channels * (bitDepth / 8) var wavHeader = Data() // 构造RIFF chunk wavHeader.append(Data("RIFF".utf8)) let riffSize = 4 + 24 + 8 + data.count // 固定计算逻辑 wavHeader.append(riffSize.littleEndianData) // 构造WAVE chunk wavHeader.append(Data("WAVE".utf8)) // 构造fmt子chunk wavHeader.append(Data("fmt ".utf8)) let fmtSize: UInt32 = 16 // PCM格式固定为16 wavHeader.append(fmtSize.littleEndianData) let audioFormat: UInt16 = 1 // 1代表PCM原始格式 wavHeader.append(audioFormat.littleEndianData) wavHeader.append(UInt16(channels).littleEndianData) wavHeader.append(UInt32(sampleRate).littleEndianData) wavHeader.append(UInt32(byteRate).littleEndianData) wavHeader.append(UInt16(blockAlign).littleEndianData) wavHeader.append(UInt16(bitDepth).littleEndianData) // 构造data子chunk wavHeader.append(Data("data".utf8)) wavHeader.append(UInt32(data.count).littleEndianData) // 拼接文件头和PCM原始数据 wavHeader.append(data) return wavHeader } // 辅助扩展:将数值转成小端字节序的Data(WAV要求小端) extension UInt32 { var littleEndianData: Data { var value = self.littleEndian return Data(bytes: &value, count: MemoryLayout<UInt32>.size) } } extension UInt16 { var littleEndianData: Data { var value = self.littleEndian return Data(bytes: &value, count: MemoryLayout<UInt16>.size) } }
调用示例:
// 假设receivedHexString是从BLE收到的Hex字符串 if let hexData = hexStringToData(receivedHexString), let wavData = pcmDataToWav(data: hexData) { let documentsDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] let wavURL = documentsDir.appendingPathComponent("recorded.wav") do { try wavData.write(to: wavURL) print("WAV文件生成成功:\(wavURL)") } catch { print("写入文件失败:\(error)") } }
第四步:转成MP3文件(如果需要压缩格式)
如果要生成MP3,因为MP3是有损压缩格式,不能直接像WAV那样拼头,需要用编码器处理。iOS上可以集成LAME编码器(可以通过CocoaPods引入lame-ios),核心步骤是把PCM数据喂给编码器,得到MP3二进制后写入文件。简单示例:
func pcmToMp3(pcmData: Data, sampleRate: Int = 16000, channels: Int = 1) -> Data? { let lame = lame_init() lame_set_in_samplerate(lame, Int32(sampleRate)) lame_set_num_channels(lame, Int32(channels)) lame_set_mode(lame, channels == 1 ? MPEG_mode.MONO : MPEG_mode.STEREO) lame_set_VBR(lame, vbr_default) lame_init_params(lame) let pcmBuffer = Array(pcmData) // 预分配MP3缓冲区,大小按经验值设置 var mp3Buffer = [UInt8](repeating: 0, count: pcmBuffer.count / 2 + 7200) let mp3Bytes = lame_encode_buffer_interleaved(lame, pcmBuffer, Int32(pcmBuffer.count / (2 * channels)), &mp3Buffer, Int32(mp3Buffer.count)) if mp3Bytes < 0 { lame_close(lame) return nil } let finalMp3Data = Data(mp3Buffer[0..<Int(mp3Bytes)]) lame_close(lame) return finalMp3Data }
额外注意事项
- 一定要和硬件对齐参数!采样率、位深哪怕错一个,生成的音频要么是杂音要么播放速度异常。
- BLE传输可能丢包,建议在硬件和iOS端加帧校验(比如序号、CRC校验和),避免生成的音频出现断帧。
- 如果硬件发送的已经是MP3帧,那直接把Hex转成二进制后拼接写入文件即可,不需要再编码。
内容的提问来源于stack exchange,提问作者Sagar Thummar
相关产品推荐
相关产品推荐

