2

My app needs to generate a audio file and I'm writing the file generator following my last Android version. On Android it uses OKIO to deal with IO and iOS it uses the native NSData.

Every WAV file needs a header to inform some parameters for the data reader (media player).

It uses this file generator, writing bytes following some specifications provided on the internet.

//Audio file content, this variable will be used
//to storage the audio data (PCM).
var content = [UInt8]() //This is not empty.
var fileSize: Int = 0 //this is not zero.

//Total size of the file, with the header.
let totalFileSize = fileSize + HEADER_SIZE

//Header data
let header = NSMutableData()

//RIFF
header.append([UInt8]("RIFF".utf8), length: 4)

//Size of the entity file
header.append(Data(bytes: readInt(Int32(totalFileSize).littleEndian)))

//WAVE
header.append([UInt8]("WAVE".utf8), length: 4)

//FMT
header.append([UInt8]("fmt ".utf8), length: 4)

//BITRATE
header.append(Data(bytes: readInt(BITRATE.littleEndian)))

//Audio format
var audioFormat = AUDIO_FORMAT_PCM.littleEndian
header.append(&audioFormat, length: 2)

//Number of channels
var audioChannels = CHANNELS.littleEndian
header.append(&audioChannels, length: 2)

//Sample rate
var sampleRate = SAMPLE_RATE.littleEndian
header.append(&sampleRate, length: 4)

//Byte rate
var byteRate = ((SAMPLE_RATE*UInt32(CHANNELS)*UInt32(BYTES_PER_SAMPLE))/UInt32(8)).littleEndian
header.append(&byteRate, length: 4)

//Block align
var blockAlign = (UInt16(CHANNELS) * UInt16(BYTES_PER_SAMPLE) / UInt16(8)).littleEndian
header.append(&blockAlign, length: 2)

//Bytes per sample
var bytesPerSample = BYTES_PER_SAMPLE.littleEndian
header.append(&bytesPerSample, length: 2)

//Data
header.append([UInt8]("data".utf8), length: 4)

//Size of the audio data
var sizeLittleEndian = UInt32(fileSize).littleEndian
header.append(&sizeLittleEndian, length: 4)

print(header.length) //44

It uses this method to write Int on the buffer:

func readInt(_ i: Int32) -> [UInt8] {
  return [UInt8(truncatingBitPattern: (i >> 24) & 0xff),
          UInt8(truncatingBitPattern: (i >> 16) & 0xff),
          UInt8(truncatingBitPattern: (i >>  8) & 0xff),
          UInt8(truncatingBitPattern: (i      ) & 0xff)]
}

On Android, the file is being generated without any problem. But on iOS these 2 parameters are wrong. Look (Most top file generated on Android code and the bottom was generated by iOS code):

enter image description here

Swift 3

I really don't know what is happening, can you help me?

Pedro Paulo Amorim
  • 1,838
  • 2
  • 27
  • 50

2 Answers2

4

One of the main issues is that the readInt function is returning big-endian, it needs to be little-endian.

Anyway, this worked for me. This is how I initialize a WAV file. Hope it helps someone.

func createHeader() {

    let sampleRate:Int32 = 44100
    let chunkSize:Int32 = 36
    let subChunkSize:Int32 = 16
    let format:Int16 = 1
    let channels:Int16 = 1
    let bitsPerSample:Int16 = 16
    let byteRate:Int32 = sampleRate * Int32(channels * bitsPerSample / 8)
    let blockAlign: Int16 = channels * 2
    let dataSize:Int32 = 0

    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)

    header.write(to: fileURL!, atomically: true)

}

func intToByteArray(_ i: Int32) -> [UInt8] {
    return [
            //little endian
            UInt8(truncatingBitPattern: (i      ) & 0xff),
            UInt8(truncatingBitPattern: (i >>  8) & 0xff),
            UInt8(truncatingBitPattern: (i >> 16) & 0xff),
            UInt8(truncatingBitPattern: (i >> 24) & 0xff)
    ]
}

func shortToByteArray(_ i: Int16) -> [UInt8] {
    return [
        //little endian
        UInt8(truncatingBitPattern: (i      ) & 0xff),
        UInt8(truncatingBitPattern: (i >>  8) & 0xff)
    ]
}
0

The written totalFileSize looks big endian on iOS while on Android it's small. Maybe you're .littleEndianing it twice?

The iOS data block doesn't look right either, on Android there are plausible sample values after data, but on iOS it looks like you've taken the address of some CoreAudio structure (maybe an AudioUnit? an ExtAudioFile? an AudioConverter?).

Rhythmic Fistman
  • 34,352
  • 5
  • 87
  • 159
  • I removed the code that provides the data. But it simply read a PCM file, get bytes and and these bytes on a temporary buffer. – Pedro Paulo Amorim Sep 30 '16 at 00:30
  • About the first question, I really don't know, is this related with the value or the position? – Pedro Paulo Amorim Sep 30 '16 at 00:31
  • ah, you've written the header of a `.caf` file into your wav file. that's not going to end well. yes, the `.caf` file is lpcm, but what format? you will likely need to decode it first. the value of `totalFileSize` is wrong in the wav file – Rhythmic Fistman Sep 30 '16 at 00:42
  • No, this is writing a PCM file, the recorder uses `kAudioFormatLinearPCM` as format, bitrate: 16000 and sample rate: 44100. The problem is not only related with the value of `totalFileSize`, but with `header.append(Data(bytes: readInt(BITRATE.littleEndian)))` too. – Pedro Paulo Amorim Sep 30 '16 at 08:23
  • http://stackoverflow.com/questions/26593831/how-to-write-int32-to-nsoutputstream-in-swift – Pedro Paulo Amorim Sep 30 '16 at 08:24