1

I am working on BLE project where an audio recorder hardware continuously streaming data and send to the iOS application. From iOS application end, I need to read transferred data.

Hardware sending HEX data to iOS application, We need to create .mp3/.wav file

Is anyone have an idea to create an audio file from binary/hex input data?

Note: I have to use raw data(Hex) to create an audio file.

Thanks

Sagar Thummar
  • 2,034
  • 15
  • 21
  • 1
    What format does the device stream in, and what format do you want your final file to be? Is it sending you raw PCM data? If so, is it sent as signed or unsigned integers or floats? How many bytes per sample? Or is it sending a non-PCM format? (This problem is very straightforward to solve with AVAssetWriter, but you have to know what you have as input and what you want as output.) – Rob Napier Mar 21 '18 at 14:19
  • @RobNapier, Thanks for your guidance. Do you have any reference link or sample code from where I understood how to create an audio file from HEX data using AVAssetWriter? – Sagar Thummar Mar 22 '18 at 06:32
  • Hex-encoding is not "raw data." It's a string encoding of data. Step one is to turn that into a `Data` object, but that's unrelated to this problem. See https://stackoverflow.com/questions/40276322/hex-binary-string-conversion-in-swift/40278391#40278391 – Rob Napier Mar 22 '18 at 13:44

1 Answers1

2

Its unclear from your question how the data is coming in, but I'm going to assume at this point that you periodically have a Data of Linear PCM data as signed integers that you want to append. If it's some other format, then you'll have to change the settings. This is all just general-purpose stuff; you will almost certainly have to modify it to your specific problem.

(Much of this code is based on Create a silent audio CMSampleBufferRef)

First you need a writer:

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

Then you need to know how how your data is formatted (this is quietly assuming that the data is a multiple of the frame size; if this isn't true, you'll need to keep track of the partial frames):

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

Then you need to know what the current frame is. This will update over time.

var currentFrame: Int64 = 0

Now you need a description of your data:

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)

And create your input adapter and add it to the writer

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

writer.add(input)

That's all the one-time setup, it's time to start the writer:

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

If all your data is the same size, you can create a reusable buffer (or you can create a new one each time):

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

When data comes in, copy it into the buffer:

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

Now create a sample buffer from the buffer and append it to the writer input:

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!)

When everything is done, finalize the writer and you're done:

input.markAsFinished()
writer.finishWriting{}
Rob Napier
  • 286,113
  • 34
  • 456
  • 610