How can I get the only the PCM data from AVAudioRecorder file?
these are the settings I use to record the file:
let settings : [String : Any] = [
AVFormatIDKey: Int(kAudioFormatLinearPCM),
AVSampleRateKey: Int(stethoscopeSampleRateDefault),
AVNumberOfChannelsKey: 1,
AVEncoderAudioQualityKey: AVAudioQuality.medium.rawValue,
]
the outcome of this is strange wav file with strange header.
How can I extract only the PCM data out of it?
The actual sound data in a wav file is in the "data" subchunk of that file - this format description might help you visualize the structure you'll have to navigate. But maybe what's tripping you up is that Apple includes an extra subchunk called "fllr" which precedes the sound data, so you have to seek past that too. Fortunately every subchunk is given an id and size, so finding the data subchunk is still relatively straightforward.
Open the file using FileHandle
Seek to byte 12, which gets you past the header and puts you at the beginning of the first subchunk (should be fmt).
Read 4 bytes and convert to a string, then read 4 more bytes and convert to an integer. The string is the subchunk name, and the integer is the size of that subchunk. If the string is not "data" then seek forward "size" number of bytes and repeat step 3.
Read the rest of the file - this is your PCM data.
With Jamie's guidance I managed to solve this. Here is my code:
func extractSubchunks(data:Data) -> RiffFile?{
var data = data
var chunks = [SubChunk]()
let position = data.subdata(in: 8..<12)
let filelength = Int(data.subdata(in: 4..<8).uint32)
let wave = String(bytes: position, encoding: .utf8) ?? "NoName"
guard wave == "WAVE" else {
print("File is \(wave) not WAVE")
return nil
}
data.removeSubrange(0..<12)
print("Found chunks")
while data.count != 0{
let position = data.subdata(in: 0..<4)
let length = Int(data.subdata(in: 4..<8).uint32)
guard let current = String(bytes: position, encoding: .utf8) else{
return nil
}
data.removeSubrange(0..<8)
let chunkData = data.subdata(in: 0..<length)
data.removeSubrange(0..<length)
let subchunk = SubChunk(name: current, size: length, data: chunkData)
chunks.append(subchunk)
print(subchunk.debugDescription)
}
let riff = RiffFile(size: filelength, subChunks: chunks)
return riff
}
Here's the definition for RiffFile and SubChunk structs:
struct RiffFile {
var size : Int
var subChunks : [SubChunk]
}
struct SubChunk {
var debugDescription: String {
return "name : \(name) size : \(size) dataAssignedsize : \(data.count)"
}
var name : String
var size : Int
var data : Data
}
Related
I am writing a point cloud file and need to keep updating the file header with the total number of points in a file: vertexCount. I don't know when the points will stop coming, so I can not just keep accumulating the values and waiting to write it to file.
The vertexCount value is kept on line 3 of ascii file, which is newline terminated.
I only see examples and functions that append data to the end of the file using write(to: URL, options: .atomic)
How can I use FileHandle to replace a specific line in a file, or overwrite the entire header?
ply
format ascii 1.0
element vertex \(vertexCount)
I see this question about replacing file contents using an array. Due to the file having at least 400 thousand lines, I do not want to separate it into individual lines. I was thinking of separating it on the end_header keyword, and then generating a new header, but am not sure how efficient this is.
Well the issue you will face is that when the numbers of digits increase it will overwrite the characters after it. You will need to use a fixed numbers of digits to be able to write them exactly over it (something like 0000000001). The number of lines doesn't really matter because it will replace any new line character after the last digit.
extension FixedWidthInteger where Self: CVarArg {
func strZero(maxLength: Int) -> String {
String(format: "%0*d", maxLength, self)
}
func write(toPLYFile atURL: URL) throws {
let fileHandle = try FileHandle(forUpdating: atURL)
try fileHandle.seek(toOffset: 36)
try fileHandle.write(contentsOf: Data(strZero(maxLength: 10).utf8))
fileHandle.closeFile()
}
}
var vertexCount = 1
let text = """
ply
format ascii 1.0
element vertex \(vertexCount.strZero(maxLength: 10))
abcdefghijklmnopqrstuvwxyz
1234567890
"""
print(text)
print("=========")
let fileURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
.appendingPathComponent("file.txt")
try Data(text.utf8).write(to: fileURL, options: .atomic)
let fileHandle = try FileHandle(forUpdating: fileURL)
try fileHandle.seek(toOffset: 36)
vertexCount = 12345
try fileHandle.write(contentsOf: Data(vertexCount.strZero(maxLength: 10).utf8))
fileHandle.closeFile()
let stringLoaded = try String(contentsOf: fileURL)
print(stringLoaded)
This will print
ply
format ascii 1.0
element vertex 0000000001
abcdefghijklmnopqrstuvwxyz
1234567890
=========
ply
format ascii 1.0
element vertex 0000012345
abcdefghijklmnopqrstuvwxyz
1234567890
Updated use:
do {
// filepath to PLY file that is being updated
let url = URL(fileURLWithPath: path)
let totalVertexCount = 12345
try totalVertexCount.write(toPLYFile: url)
} catch {
print("Error writing PLY! \(error)")
return
}
This is just something I've written really quick, definitely look into making it look and work a bit better - but should be enough to demonstrate the concept. Needs some testing to see if this is actually more efficient than separating line by line.
What it's essentially doing is it's reading the file byte by byte into an array until it finds the beginning of the third line. Then, it copies our new string into the buffer. After that, it's looking for the beginning of the fourth line and copying the rest of the file into the buffer.
I'm also calculating the total byte size so that I can trim the buffer at the end.
var finalFileByteLength = 0;
if let d = NSData(contentsOfFile: filePath) {
let newLine = "element vertex \(vertexCount)\n".data(using: .ascii)! as NSData
var buffer = [UInt8](repeating: 0, count: d.length + newLine.length)
var bytePosition = 0
var lineCount = 0
while(true) {
//Read one Byte
d.getBytes(&buffer+bytePosition, range: NSMakeRange(bytePosition, 1))
//If it's a new line character
if(buffer[bytePosition] == 10) {
lineCount += 1
//If it found the end of the second line, copy our new line
if lineCount == 2 {
newLine.getBytes(&buffer+(bytePosition+1), length: newLine.length)
bytePosition += 1
break
}
}
bytePosition += 1
finalFileByteLength+=1
}
var oldLine3Length = 0
finalFileByteLength+=newLine.length
//Find the start of the fourth line in the initial file
while(true) {
//Read one Byte
var char = UInt8()
d.getBytes(&char, range: NSMakeRange(bytePosition, 1))
//If it's a new line character
if(char == 10) {
//If it found the end of the third line, break so we have the start of the fourth line
bytePosition += 1
oldLine3Length += 1
break
}
bytePosition += 1
oldLine3Length += 1
}
//Header is now modified, copy the rest of the file
d.getBytes(&buffer+(bytePosition+newLine.length-oldLine3Length), range: NSMakeRange(bytePosition, d.length - bytePosition))
finalFileByteLength+=d.length - bytePosition + 1
let finalFileData = NSData(bytes: &buffer, length: finalFileByteLength)
//Print the result - this is probably where you'll write the entire String to a file
print(String(data: finalFileData as Data, encoding: .ascii))
}
EDIT: Managed to reduce to this:
if let d = NSData(contentsOfFile: filePath) {
let newLine = "element vertex \(vertexCount)".data(using: .ascii)! as NSData
var newLineBuffer = [UInt8](repeating: 0, count: newLine.length)
newLine.getBytes(&newLineBuffer, length: newLine.length)
var buffer = [UInt8](repeating: 0, count: d.length)
d.getBytes(&buffer, length: d.length)
var thirdIndex = buffer.firstIndex(of: 10)
thirdIndex = buffer[buffer.index(after: thirdIndex!)...].firstIndex(of: 10)
thirdIndex = buffer[buffer.index(after: thirdIndex!)...].firstIndex(of: 10)
var fourthIndex = buffer[buffer.index(after: thirdIndex!)...].firstIndex(of: 10)
buffer.removeSubrange(thirdIndex!+1..<fourthIndex!)
buffer.insert(contentsOf: newLineBuffer, at: thirdIndex!+1)
let finalFileData = NSData(bytes: buffer, length: buffer.count) as Data
print(String(data:finalFileData, encoding: .ascii))
}
I am reading data from an MFi external device into a buffer using a 3rd party SDK "sessionController". See below:
let handle: UInt64 = self.sessionController.openFile(file.path, mode: openMode)
if handle == 0 {
//Error
return
}
let c: UInt64 = file.size
var bytesArray: [UInt8] = [UInt8](fileData)
let bufferPointer: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Int(c))
bufferPointer.initialize(repeating: 0, count: Int(c))
defer {
bufferPointer.deinitialize(count: Int(c))
bufferPointer.deallocate()
}
var sum: UInt32 = 0
let singleSize: UInt32 = 8 << 20
while sum < c {
let read = self.sessionController.readFile(handle, data: bufferPointer, len: singleSize)
if read == 0 {
//There was an error
return
}
sum += read
}
let newPointer : UnsafeRawPointer = UnsafeRawPointer(bufferPointer)
fileURL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false).appendingPathComponent("test.MOV")
fileData = Data(bytes: newPointer, count: Int(c))
try! fileData.write(to: fileURL)
//Now use this fileURL to watch video in an AVPlayer...
//AVPlayer(init: fileURL)
For some reason the data stored at the fileURL becomes corrupted (I think) and I am unable to play the video file. I think I am not doing something correctly with Unsafe Swift but I am not sure what. How can I make sure that I have properly read the data from the device into memory, and then taken that data from memory and stored it on the hard drive at the fileURL? What am I doing wrong here? The video will not play in AVPlayer given the fileURL.
The main error is here:
let read = self.sessionController.readFile(handle, data: bufferPointer, len: singleSize)
If you read in multiple chunks then the second and all subsequent reads will overwrite the data read previously. So that should probably be
let read = self.sessionController.readFile(handle, data: bufferPointer + sum, len: singleSize)
Note also that the file size is defined as UInt64, but the variable sum (which holds the total number of bytes read so far) is an UInt32. This will lead to problems if there is more than 4GB data.
But generally I would avoid to read the complete data into a memory buffer. You already read in chunks, so you can write the data immediately to the destination file. Here is how that could look like:
// Output file:
let fileURL = ...
let fileHandle = try FileHandle(forWritingTo: fileURL)
defer { fileHandle.closeFile() }
// Buffer:
let bufferSize = 1024 * 1024 // Choose some buffer size
var buffer = Data(count: bufferSize)
// Read/write loop:
let fileSize: UInt64 = file.size
var remainingToRead = fileSize
while remainingToRead > 0 {
let read = buffer.withUnsafeMutableBytes { bufferPointer in
self.sessionController.readFile(handle, data: bufferPointer, len: UInt32(min(remainingToRead, UInt64(bufferSize))))
}
if read == 0 {
return // Read error
}
remainingToRead -= UInt64(read)
fileHandle.write(buffer)
}
Note also that the data is read directly into a Data value, instead of reading it into allocated memory and then copying it to another Data.
I'm looking for a fast, optimized way to trim log files on iOS. I want to specify that my log files have a maximum number of lines (e.g., 10,000). Appending new lines to the end of a text file seems relatively simple. However, I haven't yet found a fast way to trim lines at the beginning of the file. Here's the (slow) code I came up with.
guard let fileURL = self.fileURL else {
return
}
guard let path = fileURL.path else {
return
}
guard let fileHandle = NSFileHandle(forUpdatingAtPath: path) else {
return
}
fileHandle.seekToEndOfFile()
fileHandle.writeData(message.dataUsingEncoding(NSUTF8StringEncoding)!)
fileHandle.writeData("\n".dataUsingEncoding(NSUTF8StringEncoding)!)
currentLineCount += 1
// TODO: This could probably use some major optimization
if currentLineCount >= maxLineCount {
if let fileString = try? NSString(contentsOfURL: fileURL, encoding: NSUTF8StringEncoding) {
var lines = fileString.componentsSeparatedByCharactersInSet(NSCharacterSet.newlineCharacterSet())
lines.removeFirst()
let newData = lines.joinWithSeparator("\n")
fileHandle.seekToFileOffset(0)
fileHandle.writeData(newData.dataUsingEncoding(NSUTF8StringEncoding)!)
}
}
fileHandle.closeFile()
There are two aspects of your question. First, your code removes a single
line from the log file. Therefore, once the limit is reached, every new
log message causes the entire file to be read, shortened, and be re-written.
It would be more effective to use a "high-water mark" and a "low-water mark". For example, if you want the last 10.000 lines to be preserved,
let the log file grow until it has 15.000 lines, and only then truncate
it to 10.000 lines. This reduces the number of "trim actions"
considerably.
The second part is about the truncating itself. Your code loads the
file into an NSString, which requires the conversion of UTF-8 data
to Unicode characters
(and fails if there is a single invalid byte in the log file).
Then the string is split into an array, one array element removed,
the array concatenated to a string again, and then written back
to the file, which converts the Unicode characters to UTF-8.
I haven't done performance tests, but I can imagine that it could be
faster to operate on binary data only, without the conversions to
NSString, Array and back. Here is a possible implementation
which removes a given number of lines from the start of a file:
func removeLinesFromFile(fileURL: NSURL, numLines: Int) {
do {
let data = try NSData(contentsOfURL: fileURL, options: .DataReadingMappedIfSafe)
let nl = "\n".dataUsingEncoding(NSUTF8StringEncoding)!
var lineNo = 0
var pos = 0
while lineNo < numLines {
// Find next newline character:
let range = data.rangeOfData(nl, options: [], range: NSMakeRange(pos, data.length - pos))
if range.location == NSNotFound {
return // File has less than `numLines` lines.
}
lineNo++
pos = range.location + range.length
}
// Now `pos` is the position where line number `numLines` begins.
let trimmedData = data.subdataWithRange(NSMakeRange(pos, data.length - pos))
trimmedData.writeToURL(fileURL, atomically: true)
} catch let error as NSError {
print(error.localizedDescription)
}
}
I have updated Martin R answer to Swift 3, and I also changed it so that we can pass the number of lines to keep instead of the number of lines to remove:
func removeLinesFromFile(fileURL: URL, linesToKeep numLines: Int) {
do {
let data = try Data(contentsOf: fileURL, options: .dataReadingMapped)
let nl = "\n".data(using: String.Encoding.utf8)!
var lineNo = 0
var pos = data.count-1
while lineNo <= numLines {
// Find next newline character:
guard let range = data.range(of: nl, options: [ .backwards ], in: 0..<pos) else {
return // File has less than `numLines` lines.
}
lineNo += 1
pos = range.lowerBound
}
let trimmedData = data.subdata(in: pos..<data.count)
try trimmedData.write(to: fileURL)
} catch let error as NSError {
print(error.localizedDescription)
}
}
Instead of writing the new line to the log, and processing the appended content afterwards, do both write and trimming in one step:
let fileString = (try? NSString(contentsOfURL: fileURL, encoding: NSUTF8StringEncoding)) as NSString? ?? ""
var lines = fileString.characters.split("\n").map{String($0)}
lines.append(message)
// this also more generic as it will remove any number of extra lines
lines.removeFirst(max(currentLineCount - maxLineCount), 0))
let newLogContents = lines.joinWithSeparator("\n")
(newLogContents as NSString).writeToURL(fileURL, atomically: true, encoding: NSUTF8StringEncoding)
I'm trying to concatenate two NSData objects into one NSMutableData, and than get them back. For now i'm trying to do it in such way:
Get length of first object.
Write into NSMutableData in such order: first object length, first object, second object.
Code looks like:
let firstString = "first_string";
let secondString = "secondSting";
let firstData = firstString.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
let secondData = secondString.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
let mutableData = NSMutableData()
var length = firstData.length
mutableData.appendBytes(&length, length: sizeof(Int))
mutableData.appendData(firstData)
mutableData.appendData(secondData)
Then I want to get datas back. So I suppose to read first data length and then get two datas.
var length = 0
mutableData.getBytes(&length, length: sizeof(Int))
But when I'm trying to get data I'm getting crash instead:
var data = NSData()
mutableData.getBytes(&data, range: NSMakeRange(sizeof(Int), length))
Maybe somebody know where is my problem or how to get datas?
You can extract the data using subdataWithRange():
let firstData1 = mutableData.subdataWithRange(NSMakeRange(sizeof(Int), length))
if let firstString1 = NSString(data: firstData1, encoding: NSUTF8StringEncoding) as? String {
println(firstString1)
} else {
// bad encoding
}
Your solution
var data = NSData()
mutableData.getBytes(&data, range: NSMakeRange(sizeof(Int), length))
does not work and crashes because NSData is a reference type and
data a pointer to the object. You are overwriting this pointer
and the following bytes in memory.
This works perfectly without a crash in my storyboard. I just omitted the second var before length in order to avoid redefining it.
Here is the output for each line:
"first_string"
"secondSting"
<66697273 745f7374 72696e67> // let firstData = ...
<7365636f 6e645374 696e67> // let secondData = ...
<> // let mutableData = ...
12 // var length = ...
// appending data
<0c000000 00000000>
<0c000000 00000000 66697273 745f7374 72696e67>
<0c000000 00000000 66697273 745f7374 72696e67 7365636f 6e645374 696e67>
0 // length = 0
<0c000000 00000000 66697273 745f7374 72696e67 7365636f 6e645374 696e67>
12 // length
This means you probably have an error somewhere else. You did not redefine length, right?
I keep getting this error :
fatal error: unexpectedly found nil while unwrapping an Optional value
and cannot figure out how to debug it!
Here's my code :
func readCSV() -> Array<String> {
// Creates a new array of strings
var csvArray : Array<String> = Array<String>()
if let url: NSURL = NSURL(string : "URLFROMCSV" ) {
// Creates an Input Stream that will load the datas from our URL
let data :NSData! = NSData(contentsOfURL: url)!
let stream : NSInputStream! = NSInputStream(data: data)
// Opens the receiving stream
stream.open()
// Sets a buffer with a given size size
let bufferSize = 1024
var buffer = Array <UInt8>(count: bufferSize, repeatedValue: 0)
// String variable initialization
var csvFullString : String = ""
// While the stream receives datas, parses datas, convert them into strings and then concatenate them into one big string
while (stream.hasBytesAvailable) {
let readSize = stream.read(&buffer, maxLength: bufferSize)
let csvRaw = NSString (bytes: &buffer, length: readSize, encoding: NSUTF8StringEncoding)
let csvString = csvRaw as String!
csvFullString = csvFullString + csvString
}
// Fills the array with each strings. Separation between strings is made when a Θ character is parsed
csvArray = csvFullString.componentsSeparatedByString("Θ")
// Delete each null string
for(var i = 0 ; i < csvArray.count; i++) {
if(csvArray[i] == "") {
csvArray.removeAtIndex(i)
}
}
}
return csvArray
}
After searching on the web, I'm pretty sure it has something to do with unwrapping elements but the fact is when I debug it, i don't get any nil value anywhere.
PS: Would like to upload a screen but can't because i don't have 10 reputation, so bad!
Thanks in advance!
EDIT : Line let data :NSData! = NSData(contentsOfURL: url)! got the error.
Terry
You're probably creating the error in one of these two lines (though it may show up later):
let data :NSData! = NSData(contentsOfURL: url)!
let stream : NSInputStream! = NSInputStream(data: data)
You're assigning an optional value to an implicitlyUnwrappedOptional type and then using it without checking if you have a valid value.
This is why if let exists. It's a little funny that you've started to indent as if you're using if let but aren't.
Try this instead:
if let url = NSURL(string : "http://gorillaapplications.com/etablissements.csv" ) {
// Creates an Input Stream that will load the datas from our URL
if let data = NSData(contentsOfURL: url) {
let stream = NSInputStream(data: data)
stream.open()
// rest of your code here
}
else {
println("Didn't get a data object")
}
}
else {
println("Didn't get a URL object")
}
You really need to grasp Optionals for Swift. I'd recommend reading my Optionals chapter in this iBook: https://itunes.apple.com/us/book/swift-optionals-generics-for/id943445214?mt=11&uo=4&at=11lMGu
Update:
Since you added a bit more in your comments above, you're saying you get the error on this line: let data: NSData! = NSData(contentsOfURL: url)!. This is because of the ! at the end, which tells Swift you're sure that this function will return a valid value, so just use it, without checking if it's nil first. In your case, the function is returning nil and so your app crashes. Using the sample code I've provided above, you'll see that you'll no longer get a crash, but your code will execute the "Didn't get a data object" line. You'll need to correctly handle the case where you can't load data from that URL.