在 iOS 中,如何从数据创建音频文件(.wav、.mp3)文件?
In iOS, How to create audio file(.wav, .mp3) file from data?
我正在从事 BLE 项目,硬件记录音频数据并发送到 iOS 应用程序。我写了一个逻辑来从数据转换 mp3/wav 文件。
在这里,我从数据中编写了 mp3 文件转换逻辑,如下所示:
func storeMusicFile(data: Data) {
let fileName = "Record-1"
guard mediaDirectoryURL != nil else {
print("Error: Failed to fetch mediaDirectoryURL")
return
}
let filePath = mediaDirectoryURL!.appendingPathComponent("/\(fileName).mp3")
do {
try data.write(to: filePath, options: .atomic)
} catch {
print("Failed while storing files.")
}
}
但是在 AVAudioPlayer 中播放音频文件时,出现“操作无法完成。(OSStatus 错误 1954115647。)”错误。
所以,困惑是音频文件转换逻辑错误还是硬件数据还需要解码?
要创建音频文件 (.mp3/.wav),您必须动态计算头文件数据,并且需要在该头文件后附加来自硬件的实际传输音频数据。
这里,下面我添加了Swift4个代码片段供参考
//MARK: Logic for Creating Audio file
class ARFileManager {
static let shared = ARFileManager()
let fileManager = FileManager.default
var documentDirectoryURL: URL? {
return fileManager.urls(for: .documentDirectory, in: .userDomainMask).first
}
func createWavFile(using rawData: Data) throws -> URL {
//Prepare Wav file header
let waveHeaderFormate = createWaveHeader(data: rawData) as Data
//Prepare Final Wav File Data
let waveFileData = waveHeaderFormate + rawData
//Store Wav file in document directory.
return try storeMusicFile(data: waveFileData)
}
private func createWaveHeader(data: Data) -> NSData {
let sampleRate:Int32 = 2000
let chunkSize:Int32 = 36 + Int32(data.count)
let subChunkSize:Int32 = 16
let format:Int16 = 1
let channels:Int16 = 1
let bitsPerSample:Int16 = 8
let byteRate:Int32 = sampleRate * Int32(channels * bitsPerSample / 8)
let blockAlign: Int16 = channels * bitsPerSample / 8
let dataSize:Int32 = Int32(data.count)
let header = NSMutableData()
header.append([UInt8]("RIFF".utf8), length: 4)
header.append(intToByteArray(chunkSize), length: 4)
//WAVE
header.append([UInt8]("WAVE".utf8), length: 4)
//FMT
header.append([UInt8]("fmt ".utf8), length: 4)
header.append(intToByteArray(subChunkSize), length: 4)
header.append(shortToByteArray(format), length: 2)
header.append(shortToByteArray(channels), length: 2)
header.append(intToByteArray(sampleRate), length: 4)
header.append(intToByteArray(byteRate), length: 4)
header.append(shortToByteArray(blockAlign), length: 2)
header.append(shortToByteArray(bitsPerSample), length: 2)
header.append([UInt8]("data".utf8), length: 4)
header.append(intToByteArray(dataSize), length: 4)
return header
}
private func intToByteArray(_ i: Int32) -> [UInt8] {
return [
//little endian
UInt8(truncatingIfNeeded: (i ) & 0xff),
UInt8(truncatingIfNeeded: (i >> 8) & 0xff),
UInt8(truncatingIfNeeded: (i >> 16) & 0xff),
UInt8(truncatingIfNeeded: (i >> 24) & 0xff)
]
}
private func shortToByteArray(_ i: Int16) -> [UInt8] {
return [
//little endian
UInt8(truncatingIfNeeded: (i ) & 0xff),
UInt8(truncatingIfNeeded: (i >> 8) & 0xff)
]
}
func storeMusicFile(data: Data) throws -> URL {
let fileName = "Record \(Date().dateFileName)"
guard mediaDirectoryURL != nil else {
debugPrint("Error: Failed to fetch mediaDirectoryURL")
throw ARError(localizedDescription: AlertMessage.medioDirectoryPathNotAvaiable)
}
let filePath = mediaDirectoryURL!.appendingPathComponent("\(fileName).wav")
debugPrint("File Path: \(filePath.path)")
try data.write(to: filePath)
return filePath //Save file's path respected to document directory.
}
}
@sagar-thummar 之前的回答为我节省了大量时间。不幸的是,我不能对其进行投票或评论。
我需要做的一些更正是:
- 将 mediaDirectoryURL 更改为 documentDirectoryURL
- 创建 ARError 异常
- 根据我的设置调整采样率和每个样本的位数
我正在从事 BLE 项目,硬件记录音频数据并发送到 iOS 应用程序。我写了一个逻辑来从数据转换 mp3/wav 文件。
在这里,我从数据中编写了 mp3 文件转换逻辑,如下所示:
func storeMusicFile(data: Data) {
let fileName = "Record-1"
guard mediaDirectoryURL != nil else {
print("Error: Failed to fetch mediaDirectoryURL")
return
}
let filePath = mediaDirectoryURL!.appendingPathComponent("/\(fileName).mp3")
do {
try data.write(to: filePath, options: .atomic)
} catch {
print("Failed while storing files.")
}
}
但是在 AVAudioPlayer 中播放音频文件时,出现“操作无法完成。(OSStatus 错误 1954115647。)”错误。
所以,困惑是音频文件转换逻辑错误还是硬件数据还需要解码?
要创建音频文件 (.mp3/.wav),您必须动态计算头文件数据,并且需要在该头文件后附加来自硬件的实际传输音频数据。
这里,下面我添加了Swift4个代码片段供参考
//MARK: Logic for Creating Audio file
class ARFileManager {
static let shared = ARFileManager()
let fileManager = FileManager.default
var documentDirectoryURL: URL? {
return fileManager.urls(for: .documentDirectory, in: .userDomainMask).first
}
func createWavFile(using rawData: Data) throws -> URL {
//Prepare Wav file header
let waveHeaderFormate = createWaveHeader(data: rawData) as Data
//Prepare Final Wav File Data
let waveFileData = waveHeaderFormate + rawData
//Store Wav file in document directory.
return try storeMusicFile(data: waveFileData)
}
private func createWaveHeader(data: Data) -> NSData {
let sampleRate:Int32 = 2000
let chunkSize:Int32 = 36 + Int32(data.count)
let subChunkSize:Int32 = 16
let format:Int16 = 1
let channels:Int16 = 1
let bitsPerSample:Int16 = 8
let byteRate:Int32 = sampleRate * Int32(channels * bitsPerSample / 8)
let blockAlign: Int16 = channels * bitsPerSample / 8
let dataSize:Int32 = Int32(data.count)
let header = NSMutableData()
header.append([UInt8]("RIFF".utf8), length: 4)
header.append(intToByteArray(chunkSize), length: 4)
//WAVE
header.append([UInt8]("WAVE".utf8), length: 4)
//FMT
header.append([UInt8]("fmt ".utf8), length: 4)
header.append(intToByteArray(subChunkSize), length: 4)
header.append(shortToByteArray(format), length: 2)
header.append(shortToByteArray(channels), length: 2)
header.append(intToByteArray(sampleRate), length: 4)
header.append(intToByteArray(byteRate), length: 4)
header.append(shortToByteArray(blockAlign), length: 2)
header.append(shortToByteArray(bitsPerSample), length: 2)
header.append([UInt8]("data".utf8), length: 4)
header.append(intToByteArray(dataSize), length: 4)
return header
}
private func intToByteArray(_ i: Int32) -> [UInt8] {
return [
//little endian
UInt8(truncatingIfNeeded: (i ) & 0xff),
UInt8(truncatingIfNeeded: (i >> 8) & 0xff),
UInt8(truncatingIfNeeded: (i >> 16) & 0xff),
UInt8(truncatingIfNeeded: (i >> 24) & 0xff)
]
}
private func shortToByteArray(_ i: Int16) -> [UInt8] {
return [
//little endian
UInt8(truncatingIfNeeded: (i ) & 0xff),
UInt8(truncatingIfNeeded: (i >> 8) & 0xff)
]
}
func storeMusicFile(data: Data) throws -> URL {
let fileName = "Record \(Date().dateFileName)"
guard mediaDirectoryURL != nil else {
debugPrint("Error: Failed to fetch mediaDirectoryURL")
throw ARError(localizedDescription: AlertMessage.medioDirectoryPathNotAvaiable)
}
let filePath = mediaDirectoryURL!.appendingPathComponent("\(fileName).wav")
debugPrint("File Path: \(filePath.path)")
try data.write(to: filePath)
return filePath //Save file's path respected to document directory.
}
}
@sagar-thummar 之前的回答为我节省了大量时间。不幸的是,我不能对其进行投票或评论。 我需要做的一些更正是:
- 将 mediaDirectoryURL 更改为 documentDirectoryURL
- 创建 ARError 异常
- 根据我的设置调整采样率和每个样本的位数