Best Implementation of .wav Audio Recording in Swift - ios

Thanks in advance for your help,
I have been able to record in .m4a format for a while. Unfortunately, this project I'm working on needs to be recorded in .wav. I have been searching for a way of recording in .wav but I've only been able to find resources on converting to .wav after recording in .m4a. Is there a way of recording specifically in .wav?
The code I've pasted is my recording service. In settings specifically, I pass an audio format type. I haven't been able to have this type be of '.wav'.
import Foundation
import Combine
import AVFoundation
class AudioRecorder: NSObject, ObservableObject {
override init() {
super.init()
fetchRecordings()
}
let objectWillChange = PassthroughSubject<AudioRecorder, Never>()
var audioRecorder: AVAudioRecorder!
var recordings = [Recording]()
var recording = false {
didSet {
objectWillChange.send(self)
}
}
func startRecording(taskNum: Int) {
let recordingSession = AVAudioSession.sharedInstance()
do {
try recordingSession.setCategory(.playAndRecord, mode: .default)
try recordingSession.setActive(true)
} catch {
print("Failed to set up recording session")
}
let documentPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let audioFilename = documentPath.appendingPathComponent("\(Date().toString(dateFormat: "dd-MM-YY_'at'_HH:mm:ss"))-task_\(taskNum).m4a")
let settings = [
// Change to kAudioFileWAVEType from kAudioFormatMPEG4AAC for .wav files?
AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
AVSampleRateKey: 12000,
AVNumberOfChannelsKey: 1,
AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
]
do {
audioRecorder = try AVAudioRecorder(url: audioFilename, settings: settings)
audioRecorder.record()
recording = true
} catch {
print("Could not start recording")
}
}
func stopRecording() {
audioRecorder.stop()
recording = false
fetchRecordings()
}
func fetchRecordings() {
recordings.removeAll()
let fileManager = FileManager.default
let documentDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0]
let directoryContents = try! fileManager.contentsOfDirectory(at: documentDirectory, includingPropertiesForKeys: nil)
for audio in directoryContents {
let recording = Recording(fileURL: audio, createdAt: getCreationDate(for: audio))
recordings.append(recording)
}
recordings.sort(by: { $0.createdAt.compare($1.createdAt) == .orderedAscending})
objectWillChange.send(self)
}
func getCreationDate(for file: URL) -> Date {
if let attributes = try? FileManager.default.attributesOfItem(atPath: file.path) as [FileAttributeKey: Any],
let creationDate = attributes[FileAttributeKey.creationDate] as? Date {
return creationDate
} else {
return Date()
}
}
func deleteRecording(urlsToDelete: [URL]) {
for url in urlsToDelete {
print(url)
do {
try FileManager.default.removeItem(at: url)
} catch {
print("File could not be deleted!")
}
}
fetchRecordings()
}
}

Related

Retrieving the audio file from the document directory

is it possible to retrieve the recorded audio file from the document directory? I have looked through the whole internet, but using AVAsset/WKAudioFileAsset does not seem to be the right path.
Is there just the way to convert the recorded audio file to NSData (binary format), send it to the server and then the server will convert the binary data back into the audio file?
I have found this here: https://iosdevcenters.blogspot.com/2016/04/save-and-get-image-from-document.html
(This example is about retrieving images and they have used UIImage)
So, I am curious whether there is an equivalent way for retrieving an audio file? Is there a class for audio to save the file into that class just as UIImage for images?
I hope someone can help me out. Thank you very much.
My code looks as follows:
import WatchKit
import Foundation
import AVFoundation
class InterfaceController: WKInterfaceController, AVAudioRecorderDelegate{
#IBOutlet weak var btn: WKInterfaceButton!
var recordingSession : AVAudioSession!
var audioRecorder : AVAudioRecorder!
var settings = [String : Any]()
override func awake(withContext context: Any?) {
super.awake(withContext: context)
recordingSession = AVAudioSession.sharedInstance()
do{
try recordingSession.setCategory(AVAudioSession.Category.playAndRecord)
try recordingSession.setActive(true)
recordingSession.requestRecordPermission(){[unowned self] allowed in
DispatchQueue.main.async {
if allowed{
print("Allow")
} else{
print("Don't Allow")
}
}
}
}
catch{
print("failed to record!")
}
// Configure interface objects here.
// Audio Settings
settings = [
AVFormatIDKey:Int(kAudioFormatLinearPCM),
AVSampleRateKey:44100.0,
AVNumberOfChannelsKey:1,
AVLinearPCMBitDepthKey:8,
AVLinearPCMIsFloatKey:false,
AVLinearPCMIsBigEndianKey:false,
AVEncoderAudioQualityKey:AVAudioQuality.max.rawValue
]
}
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
print("Test")
}
override func didDeactivate() {
// This method is called when watch view controller is no longer visible
super.didDeactivate()
}
func directoryURL() -> URL? {
let fileManager = FileManager.default
let urls = fileManager.urls(for: .documentDirectory, in: .userDomainMask)
let documentDirectory = urls[0] as URL
let soundUrl = documentDirectory.appendingPathComponent("sound.wav")
// var soundUrlStr = soundUrl?.path
//print(fileManager.fileExists(atPath: soundUrlStr))
let filePath = (soundUrl).path
print(filePath)
print("URL")
print(soundUrl)
return soundUrl as URL?
}
func startRecording(){
let audioSession = AVAudioSession.sharedInstance()
do{
audioRecorder = try AVAudioRecorder(url: self.directoryURL()! as URL,
settings: settings)
audioRecorder.delegate = self
audioRecorder.prepareToRecord()
audioRecorder.record(forDuration: 5.0)
}
catch {
finishRecording(success: false)
}
do {
try audioSession.setActive(true)
audioRecorder.record()
} catch {
}
}
func finishRecording(success: Bool) {
audioRecorder.stop()
if success {
print(success)
} else {
audioRecorder = nil
print("Somthing Wrong.")
}
}
#IBAction func recordAudio() {
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String
let url = URL(fileURLWithPath: path)
let pathPart = url.appendingPathComponent("sound.wav")
let filePath = pathPart.path
let fileManager = FileManager.default
if fileManager.fileExists(atPath: filePath){
print("File exists!")
let audioAsset = WKAudioFileAsset(url:pathPart)
// let playerItem = WKAudioFilePlayerItem(asset:audioAsset)
print("Audio File")
print(audioAsset)
print("WAV file")
print(NSData(contentsOfFile: filePath) as Any)
}else{
print("File does not exist")
}
if audioRecorder == nil {
print("Pressed")
self.btn.setTitle("Stop")
self.btn.setBackgroundColor(UIColor(red: 119.0/255.0, green: 119.0/255.0, blue: 119.0/255.0, alpha: 1.0))
self.startRecording()
} else {
self.btn.setTitle("Record")
print("Pressed2")
self.btn.setBackgroundColor(UIColor(red: 221.0/255.0, green: 27.0/255.0, blue: 50.0/255.0, alpha: 1.0))
self.finishRecording(success: true)
}
}
func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool) {
if !flag {
finishRecording(success: false)
}
}
}

FileManager cannot find audio file

Helloo! My ultimate goal is to create a UITableViewController with recordings made in-app, using FileManager to traverse the /Documents/ directory and list all the recordings found there.
The recording and playback are functioning just fine with one recording, with the following setup:
// In VC#1
func setupRecorder(){
let audioSession:AVAudioSession = AVAudioSession.sharedInstance()
do {
try audioSession.setCategory(AVAudioSessionCategoryPlayAndRecord)
} catch {
print("Audio Setup Error: \(error)")
}
do {
try audioSession.setActive(true)
} catch {
print("Audio Setup Error: \(error)")
}
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
audioFileName = "test.caf"
audioFileUrl = documentsDirectory.appendingPathComponent(audioFileName)
print("\n\nrecording url :\(audioFileUrl.absoluteString)\n\n")
let recordSettings = [
AVFormatIDKey: Int(kAudioFormatAppleLossless),
AVEncoderAudioQualityKey : AVAudioQuality.max.rawValue,
AVEncoderBitRateKey : 320000,
AVNumberOfChannelsKey: 2,
AVSampleRateKey : 44100.0
] as [String : Any]
do {
audioRecorder = try AVAudioRecorder(url: audioFileUrl, settings: recordSettings)
} catch let error as NSError{
print("Audio Setup Error: \(error)")
audioRecorder = nil
}
audioRecorder.delegate = self
audioRecorder.isMeteringEnabled = true
audioRecorder.prepareToRecord()
}
...
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == GraphViewController.getEntrySegue() {
let navController = segue.destination as! UINavigationController
let destination = navController.topViewController as! GraphViewController
destination.audioFileName = audioFileName
destination.audioFileUrl = audioFileUrl
}
// In VC#2
func setupAudioPlayer() {
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
} catch {
print(error)
}
do {
try AVAudioSession.sharedInstance().setActive(true)
} catch {
print(error)
}
do {
audioPlayer = try AVAudioPlayer(contentsOf: audioFileUrl)
} catch {
print("Audio Player Setup Error: \(error)")
}
audioPlayer.prepareToPlay()
}
This is working all fine and dandy; it plays the recorded audio file with no problems whatsoever. However, when I try to find the file via FileManager, FileManager cannot find it. Listed below are my attempts to find this file.
print("\(FileManager.default.fileExists(atPath: audioFileUrl.absoluteString))") // -> False
print("\(FileManager.default.contents(atPath: audioFileUrl.absoluteString))") // -> nil
Clearly I must be missing something... especially given AVAudioPlayer is successfully reading the audio file with the exact same url. The path prints as:
file:///var/mobile/Containers/Data/Application/1DA6BD0F-5A23-4228-92A9-A083E55ACE21/Documents/test.caf
Any ideas? 🧐🤔
Many thanks!
EDIT: Before storing the url as a property, I was doing the following, recalculating the documentsDirectory in VC#2 however was also not succesfull:
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let results = try? FileManager.default.contentsOfDirectory(atPath: documentsDirectory.absoluteString)
You cannot save a sandboxed file URL or path string for later use, because the sandbox can move. You need to do what you were doing before, successfully: calculate the documents directory URL and then examine it. You should work entirely with URLs and never call absoluteString at all; it is the wrong call entirely and is letting you down here.
In this example, I check the documents directory for any files whatever:
let documentsDirectory =
FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let results =
try? FileManager.default.contentsOfDirectory(at: documentsDirectory,
includingPropertiesForKeys: nil)

record user voice and clear text file every 2 minutes

I am new to swift and I'm working in project that record user sound ,and convert sound file into text file every two minutes. I use timer to repeat the step every 2 minutes.
the problem is that the recorder is disable for the second call. Also, the text file does not clear the content to be prepared to the next call.
here is the full code.
import UIKit
import Speech
import AVFoundation
class ViewController: UIViewController {
var audioRecorder:AVAudioRecorder!
var inString = ""
let fileName = "Test"
var str=""
appropriateFor: nil, create: true)
let recordSettings = [AVSampleRateKey : NSNumber(value: Float(44100.0)),
AVFormatIDKey : NSNumber(value: Int32(kAudioFormatMPEG4AAC)),
AVNumberOfChannelsKey : NSNumber(value: Int32(1)),
AVEncoderAudioQualityKey : NSNumber(value: Int32(AVAudioQuality.high.rawValue))]
var timer = Timer()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
var audioSession = AVAudioSession.sharedInstance()
do {
try audioSession.setCategory(AVAudioSessionCategoryPlayAndRecord)
try audioRecorder = AVAudioRecorder(url: directoryURL()!, settings: recordSettings)
audioRecorder.prepareToRecord()
} catch {
print("error")
}
audioSession = AVAudioSession.sharedInstance()
do {
try audioSession.setCategory(AVAudioSessionCategoryPlayAndRecord)
requestSpeechAuth()
} catch {}
timer = Timer.scheduledTimer(timeInterval: 120, target: self, selector: #selector (ViewController.stopAudio), userInfo: nil, repeats: true)
}
#objc func stopAudio() {
audioRecorder.stop()
let audioSession = AVAudioSession.sharedInstance()
do {
try audioSession.setActive(false)
let recognizer = SFSpeechRecognizer(locale: Locale(identifier: "ar_SA"))
let request = SFSpeechURLRecognitionRequest(url: audioRecorder.url)
recognizer?.recognitionTask(with: request) { (result, error) in
if let error = error {
print("There was an error: \(error)")
} else {
let dir = try? FileManager.default.url(for: .documentDirectory,
in: .userDomainMask, appropriateFor: nil, create: true)
if let fileURL = dir?.appendingPathComponent(self.fileName).appendingPathExtension("txt") {
do {
self.str=""
self.str = (result?.bestTranscription.formattedString)!
try self.str.write(to: fileURL, atomically: true, encoding: .utf8)
} catch {
print("Failed writing to URL: \(fileURL), Error: " + error.localizedDescription)
}
do {
self.inString = try String(contentsOf: fileURL)
} catch {
print("Failed reading from URL: \(fileURL), Error: " + error.localizedDescription)
}
self.getIqama(fileN: self.inString,status: self.str)
}
}//end elsd
} //end result
} catch {} //end do for false
// requestSpeechAuth()
}
func directoryURL() -> URL? {
let fileManager = FileManager.default
let urls = fileManager.urls(for: .documentDirectory, in: .userDomainMask)
let documentDirectory = urls[0] as URL
let soundURL = documentDirectory.appendingPathComponent("AqimAlsalat.m4a")
return soundURL
}
func getIqama(fileN : String, status:String)
{
var st: String!
st = "السلام عليكم ورحمة الله السلام عليكم ورحمة الله"
let st1 : String!
st1 = String (fileN)
print(st1)
if st1 == st {
// audioEngine.stop()
//speechRecognitionRequest?.endAudio()
print(st1)
print("JJalal")
}
else {
print("Dalal")
print(fileN)
}
}
func requestSpeechAuth(){
SFSpeechRecognizer.requestAuthorization { authStatus in
if authStatus == SFSpeechRecognizerAuthorizationStatus.authorized {
let audioSession = AVAudioSession.sharedInstance()
do {
try audioSession.setActive(true)
self.audioRecorder.record()
} catch {}
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
any suggestion or idea?
Thanks
It seems like you need to call self.audioRecorder.record() again after you stop the recording to convert the soundfile into text. The Apple docs say that calling record() will create or erase an audio file, so that should solve your problem.
However, you may encounter another problem where you miss a period of recording while you are transcribing the text. You could consider fixing that problem by switching back and forth between two recorders, or you could try to change the audio recorder's file location (or change the location of the previous file) before starting to record again.

Swift 3 Record audio and upload to Firebase storage and play back

I am making a chat room application, so far it is able to send message, image and video.
I am using a very similar method to send video and it works, but it's not working when sending audio.
The audio file and audio url is upload to Firebase successfully, But when I tried to play back the audio, it show this error: The operation couldn’t be completed. (OSStatus error 2003334207.).
The project is getting quite overwhelmingly large, and I have very little experience using AVAudio, so if you guys had similar problems before please teach me how to fix it. Thanks!!!
Here is the code of setting up the audioRecorder, and I get the url here and pass it to other func to put the audio file to Firebase storage.
func startRecording() {
let settings = [
AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
AVSampleRateKey: 12000,
AVNumberOfChannelsKey: 1,
AVEncoderAudioQualityKey: AVAudioQuality.low.rawValue
]
do {
let audioFileUrl = getAudiFileURL()
audioRecorder = try AVAudioRecorder(url: audioFileUrl, settings: settings)
audioRecorder.delegate = self
audioRecorder.record()
blackView.isHidden = false
} catch {
finishRecording(success: false)
}
}
Here is where I try to upload the audio file to Firebase storage, and it does print out the correct downloadURL. (The URL is pointing to the file's location in the iOS devices.)
func handleAudioSendWith(url: String) {
guard let fileUrl = URL(string: url) else {
return
}
let fileName = NSUUID().uuidString + ".m4a"
FIRStorage.storage().reference().child("message_voice").child(fileName).putFile(fileUrl, metadata: nil) { (metadata, error) in
if error != nil {
print(error ?? "error")
}
if let downloadUrl = metadata?.downloadURL()?.absoluteString {
print(downloadUrl)
let values: [String : Any] = ["audioUrl": downloadUrl]
self.sendMessageWith(properties: values)
}
}
}
Here is how I set up the url for the audioRecorder above.
func getDocumentsDirectory() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let documentsDirectory = paths[0]
return documentsDirectory
}
func getAudiFileURL() -> URL {
return getDocumentsDirectory().appendingPathComponent(".m4a")
}
And this is where I play the audio:
func handleAudioPLay() {
if let audioUrl = message?.audioUrl, let url = URL(string: audioUrl) {
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord)
audioPlayer = try AVAudioPlayer(contentsOf: url)
audioPlayer?.delegate = self
audioPlayer?.prepareToPlay()
audioPlayer?.play()
print("Audio ready to play")
} catch let error {
print(error.localizedDescription)
}
}
}
I can actually download the sound file from Firebase using the url and play it on my computer, which means the url is fine.
I have solved the problem by downloading the sound file using URLSession, and play it using AVAudioPlayer(data: data!, fileTypeHint: "aac").

Xcode 8.0 beta3 AVAudioRecorder(url,setting) error catch - "Error Domain=NSOSStatusErrorDomain Code=-50 "(null)""

I'm trying to record the voice of the user and change it to a text data. I used AVAudioRecorder to record the sound and SpeechKit to change it to text which is included in iOS 10. When user touches down the button record starts and stops when button is touched up. But when I initialize the AVAudioRecorder using do catch syntax, error occurs and fails.
I added the appropriate frameworks(Speech, AVFoundation).
import UIKit
import Speech
import AVFoundation
class SearchViewController: UIViewController, CLLocationManagerDelegate, AVAudioRecorderDelegate {
var audioRecorder = AVAudioRecorder()
let recordSettings = [AVSampleRateKey : String(NSNumber(value: Float(44100.0))),
AVFormatIDKey : String(kAudioFileCAFType),
AVNumberOfChannelsKey : String(NSNumber(value: 2))]
#IBAction func recordButtonDown(_ sender: AnyObject) {
print("recordButtonDown")
self.audioPlayer.play()
sleep(1)
let fileManager = FileManager.default
let paths = fileManager.urlsForDirectory(.documentDirectory, inDomains: .userDomainMask)
var audioURL = paths[0] as NSURL
audioURL = audioURL.appendingPathComponent("soundForMapSearch.caf", isDirectory: false)!
do {
self.audioRecorder = try AVAudioRecorder(url: soundFileURL as URL, settings: self.recordSettings)
self.audioRecorder.delegate = self
self.audioRecorder.prepareToRecord()
self.audioRecorder.record()
} catch (let error) {
print("Error: \(error)")
}
}
#IBAction func recordButtonUp(_ sender: AnyObject) {
self.audioRecorder.stop()
}
override func viewDidLoad() {
super.viewDidLoad()
do {
try audioSession.setCategory(AVAudioSessionCategoryPlayAndRecord)
try audioSession.setActive(true)
audioSession.requestRecordPermission({ (recordPermission) in
})
} catch {
print("record initiallizing failed")
}
And on the line
self.audioRecorder = try AVAudioRecorder(url: soundFileURL as URL, settings: self.recordSettings)
error occurs at catch
Error: Error Domain=NSOSStatusErrorDomain Code=-50 "(null)"
is printed. I searched for this error and code=-50 means the NSURL object is invalid. How can I solve this error?
my working code
let recordingName = "recording1" + ".m4a"
let pathArray = [dirPath, recordingName]
print(pathArray)
let filePath = NSURL.fileURL(withPathComponents: pathArray)
print(filePath)
do{
let session = AVAudioSession.sharedInstance()
try! session.setCategory(AVAudioSessionCategoryPlayAndRecord)
} catch {
assertionFailure("AVAudioSession setup error: \(error)")
}
let recordSettings: [String: AnyObject] = [
AVFormatIDKey: NSNumber(value: kAudioFormatMPEG4AAC),
AVSampleRateKey: 44100.0,
AVNumberOfChannelsKey: 1,
]
try! audioRecorder = AVAudioRecorder(url: filePath!, settings: recordSettings)
audioRecorder.delegate = self
audioRecorder.isMeteringEnabled = true
audioRecorder.prepareToRecord()
audioRecorder.record()

Resources