如何从 iOS 中的十六进制/二进制(原始数据)值生成音频文件?

Posted

技术标签:

【中文标题】如何从 iOS 中的十六进制/二进制(原始数据)值生成音频文件?【英文标题】:How to generate audio file from Hex/Binary(raw data) value in iOS? 【发布时间】:2018-03-21 14:09:23 【问题描述】:

我正在开发 BLE 项目,其中音频记录器硬件不断流式传输数据并发送到 ios 应用程序。从 iOS 应用端,我需要读取传输的数据。

硬件向iOS应用发送HEX数据,我们需要创建.mp3/.wav文件

有人想从二进制/十六进制输入数据创建音频文件吗?

注意:我必须使用原始数据(十六进制)来创建音频文件。

谢谢

【问题讨论】:

设备流式传输的格式是什么,您希望最终文件是什么格式?它是否向您发送原始 PCM 数据?如果是这样,它是作为有符号或无符号整数还是浮点数发送?每个样本有多少字节?还是发送非 PCM 格式? (这个问题用 AVAssetWriter 很容易解决,但是你必须知道你有什么作为输入和你想要什么作为输出。) @RobNapier,感谢您的指导。您是否有任何参考链接或示例代码,我从中了解如何使用 AVAssetWriter 从 HEX 数据创建音频文件? 十六进制编码不是“原始数据”。它是数据的字符串编码。第一步是将其转换为Data 对象,但这与此问题无关。见***.com/questions/40276322/… 【参考方案1】:

从您的问题中不清楚数据是如何进入的,但我现在假设您定期有一个 Data 的线性 PCM 数据作为您想要附加的有符号整数。如果是其他格式,则必须更改设置。这只是通用的东西;您几乎可以肯定必须根据您的具体问题对其进行修改。

(此代码大部分基于Create a silent audio CMSampleBufferRef)

首先你需要一个作家:

let writer = try AVAssetWriter(outputURL: outputURL, fileType: .wav)

然后你需要知道你的数据是如何格式化的(这是假设数据是帧大小的倍数;如果这不是真的,你需要跟踪部分帧):

let numChannels = 1
let sampleRate = 44100
let bytesPerFrame = MemoryLayout<Int16>.size * numChannels
let frames = data.count / bytesPerFrame
let duration = Double(frames) / Double(sampleRate)
let blockSize = frames * bytesPerFrame

那么你需要知道当前帧是什么。这将随着时间的推移而更新。

var currentFrame: Int64 = 0

现在您需要对您的数据进行描述:

var asbd = AudioStreamBasicDescription(
    mSampleRate: Float64(sampleRate),
    mFormatID: kAudioFormatLinearPCM,
    mFormatFlags: kLinearPCMFormatFlagIsSignedInteger,
    mBytesPerPacket: UInt32(bytesPerFrame),
    mFramesPerPacket: 1,
    mBytesPerFrame: UInt32(bytesPerFrame),
    mChannelsPerFrame: UInt32(numChannels),
    mBitsPerChannel: UInt32(MemoryLayout<Int16>.size*8),
    mReserved: 0
)

var formatDesc: CMAudioFormatDescription?
status = CMAudioFormatDescriptionCreate(kCFAllocatorDefault, &asbd, 0, nil, 0, nil, nil, &formatDesc)
assert(status == noErr)

并创建您的输入适配器并将其添加到编写器

let settings:[String : Any] = [ AVFormatIDKey : kAudioFormatLinearPCM,
                                AVNumberOfChannelsKey : numChannels,
                                AVSampleRateKey : sampleRate ]
let input = AVAssetWriterInput(mediaType: .audio, outputSettings: settings, sourceFormatHint: formatDesc)

writer.add(input)

这就是一次性设置,是时候开始编写器了:

writer.startWriting()
writer.startSession(atSourceTime: kCMTimeZero)

如果你所有的数据大小相同,你可以创建一个可重复使用的缓冲区(或者你可以每次都创建一个新的):

var block: CMBlockBuffer?
var status = CMBlockBufferCreateWithMemoryBlock(
    kCFAllocatorDefault,
    nil,
    blockSize,  // blockLength
    nil,        // blockAllocator
    nil,        // customBlockSource
    0,          // offsetToData
    blockSize,  // dataLength
    0,          // flags
    &block
)
assert(status == kCMBlockBufferNoErr)

当数据进来时,将其复制到缓冲区中:

status = CMBlockBufferReplaceDataBytes(&inputData, block!, 0, blockSize)
assert(status == kCMBlockBufferNoErr)

现在从缓冲区创建一个示例缓冲区并将其附加到写入器输入:

var sampleBuffer: CMSampleBuffer?

status = CMAudioSampleBufferCreateReadyWithPacketDescriptions(
    kCFAllocatorDefault,
    block,      // dataBuffer
    formatDesc!,
    frames,    // numSamples
    CMTimeMake(currentFrame, Int32(sampleRate)),    // sbufPTS
    nil,        // packetDescriptions
    &sampleBuffer
)
assert(status == noErr)

input.append(sampleBuffer!)

一切都完成后,敲定作者就完成了:

input.markAsFinished()
writer.finishWriting

【讨论】:

以上是关于如何从 iOS 中的十六进制/二进制(原始数据)值生成音频文件?的主要内容,如果未能解决你的问题,请参考以下文章

如何从 BluetoothChat 的输入/输出流中读取/写入原始十六进制字节?

从 mysqldb 查询中获取原始十进制值

哈希算法:你会如何存储重要数据

从原始像素创建图像,但颜色无法根据 RGBA 十六进制值显示

算法笔记 11 哈希算法

哈希算法七大常见应用