How do I save AudioRecording to app folder? - swift

I am working on a app with recording function and followed a tutorial on this. I would like to edit the function where the AudioRecorder saves the file so that the file goes in the same folder it would if I dragged an audiofile directly in to xcode.
I am having trouble fetching a singe recording from the directory the Audiorecorder is saving to atm and found another tutorial that shows how to find recordings based on name from the app folder.
This is the recordings func today:
func startRecording() {
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]
print("AUDIORECORDER document path: \(documentPath)")
let audioFilename = documentPath.appendingPathComponent("\(Date().toString(dateFormat: "dd-MM-YY_'at'_HH:mm:ss")).m4a")
print("AUDIORECORDER audioFilename: \(audioFilename)")
let settings = [
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")
}
}
Any idea on how to do this? The tutorial only shows how to fetch all recordings to a list and I am trying to fetch a single recording. This is the fetch function if this helpes:
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)
}
objectWillChange.send(self)
}
It does not really matter where it saves but I need to be able to save the name to a string (So that I can fetch the right audio to the right object) and then find the audio based on that string.
Thank you.

Related

Swift/Cocoa: How do I export an AVMutableComposition as wav or aiff audio file

I have a AVMutableComposition containing only audio that I want to export to a .wav audio file.
The simplest solution for exporting audio I found was using AVAssetExportSession like in this simplified example:
let composition = AVMutableComposition()
// add tracks...
let exportSession = AVAssetExportSession(asset: composition,
presetName: AVAssetExportPresetAppleM4A)!
exportSession.outputFileType = .m4a
exportSession.outputURL = someOutUrl
exportSession.exportAsynchronously {
// done
}
But it only works for .m4a
This post mentions that in order to export to other formats, one would have to use AVAssetReader and AVAssetWriter, unfortunately though it does not go into further details.
I have tried to implement it but got stuck in the process.
This is what I have so far (again simplified):
let composition = AVMutableComposition()
let outputSettings: [String : Any] = [
AVFormatIDKey: kAudioFormatLinearPCM,
AVLinearPCMIsBigEndianKey: false,
AVLinearPCMIsFloatKey: false,
AVLinearPCMBitDepthKey: 32,
AVLinearPCMIsNonInterleaved: false,
AVSampleRateKey: 44100.0,
AVChannelLayoutKey: NSData(),
]
let assetWriter = try! AVAssetWriter(outputURL: someOutUrl, fileType: .wav)
let input = AVAssetWriterInput(mediaType: .audio, outputSettings: outputSettings)
assetWriter.add(input)
assetWriter.startWriting()
assetWriter.startSession(atSourceTime: CMTime.zero)
input.requestMediaDataWhenReady(on: .main) {
// as I understand, I need to bring in data from my
// AVMutableComposition here...
let sampleBuffer: CMSampleBuffer = ???
input.append(sampleBuffer)
}
assetWriter.finishWriting {
// done
}
It boils down to my question:
Can you provide a working example for exporting audio from a AVMutableComposition to a wav file?
After some more research I came up with the following solution.
The missing piece was the usage of AVAssetReader.
(simplified code)
// composition
let composition = AVMutableComposition()
// add stuff to composition
// reader
guard let assetReader = try? AVAssetReader(asset: composition) else { return }
assetReader.timeRange = CMTimeRange(start: .zero, duration: CMTime(value: composition.duration.value, timescale: composition.duration.timescale))
let assetReaderAudioMixOutput = AVAssetReaderAudioMixOutput(audioTracks: composition.tracks(withMediaType: .audio), audioSettings: nil)
assetReader.add(assetReaderAudioMixOutput)
guard assetReader.startReading() else { return }
// writer
let outputSettings: [String : Any] = [
AVFormatIDKey: kAudioFormatLinearPCM,
AVLinearPCMIsBigEndianKey: false,
AVLinearPCMIsFloatKey: false,
AVLinearPCMBitDepthKey: 32,
AVLinearPCMIsNonInterleaved: false,
AVSampleRateKey: 44100.0,
AVChannelLayoutKey: NSData(),
]
guard let assetWriter = try? AVAssetWriter(outputURL: someOutUrl, fileType: .wav) else { return }
let writerInput = AVAssetWriterInput(mediaType: .audio, outputSettings: outputSettings)
assetWriter.add(writerInput)
guard assetWriter.startWriting() else { return }
assetWriter.startSession(atSourceTime: CMTime.zero)
let queue = DispatchQueue(label: "my.queue.id")
writerInput.requestMediaDataWhenReady(on: queue) {
// capture assetReader in my block to prevent it being released
let readerOutput = assetReader.outputs.first!
while writerInput.isReadyForMoreMediaData {
if let nextSampleBuffer = readerOutput.copyNextSampleBuffer() {
writerInput.append(nextSampleBuffer)
} else {
writerInput.markAsFinished()
assetWriter.endSession(atSourceTime: composition.duration)
assetWriter.finishWriting() {
DispatchQueue.main.async {
// done, call my completion
}
}
break;
}
}
}
as you mention you are creating .m4a successfully why not convert .m4a file to .wav file in just put some line of code
I simply changed the extension of the file to .wav and removed the .m4a file and it worked.
func getDirectory() -> URL {
let path = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let documentDirectory = path[0]
return documentDirectory
}
let date = Date().timeIntervalSince1970
fileName = getDirectory().appendingPathComponent("\(date).m4a")
wavFileName = getDirectory().appendingPathComponent("\(date).wav")
try! FileManager.default.copyItem(at: fileName, to: wavFileName)
try! FileManager.default.removeItem(at: fileName)
I even played .wav file and it's working fine.
audioPlayer = try! AVAudioPlayer(contentsOf: wavFileName)
audioPlayer.play()
check out above example is this extension replacement causing of any load or time in your app or not

Recording audio on WatchOS over AVAudioRecorder

Is there any sample code or tutorials about that? I've found that AVAudioRecorder supported since WatchOS 4.0 https://developer.apple.com/documentation/avfoundation/avaudiorecorder. But when I am trying to use it - it records 1 second and no actual sound (just noise).
Here is my code:
let audioURL = self.getRecordedFileURL()
print(audioURL.absoluteString)
let settings = [
AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
]
do {
recorder = try AVAudioRecorder(url: audioURL, settings: settings)
recorder?.delegate = self
recorder?.record()
} catch {
finishRecording(success: false)
}
Also, should I use AudioSession here? If yes, is it required requestRecordPermission and how do deal with it? Thank you for your help!
This one works:
let recordingName = "audio.m4a"
let dirPath = getDirectory()
let pathArray = [dirPath, recordingName]
guard let filePath = URL(string: pathArray.joined(separator: "/")) else { return }
let settings = [AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
AVSampleRateKey:12000,
AVNumberOfChannelsKey:1,
AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
]
//start recording
do {
audioRecorder = try AVAudioRecorder(url: filePath, settings: settings)
audioRecorder.delegate = self
audioRecorder.record()
} catch {
print("Recording Failed")
}
func getDirectory()-> String {
let dirPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
return dirPath
}
Don't forget to add NSMicrophoneUsageDescription into your phone companion app Info.plist.

Load temp video and play

I need to play continuously a video stored inside the temp directory.
func setVideo(url vid: String!) {
let directory = NSTemporaryDirectory()
let tempURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("tempMovie\(Date().timeIntervalSince1970)").appendingPathExtension("mp4")
let tempFile = NSURL.fileURL(withPathComponents: [directory])
let file = vid.components(separatedBy: ".")
guard let path = Bundle.main.path(forResource: "\(tempURL)", ofType:file[2]) else {
debugPrint( "\(file.joined(separator: ".")) not found with path: \(file[0] + "." + file[1])")
return
}
let player = AVPlayer(url: URL(fileURLWithPath: path))
let playerLayer = AVPlayerLayer(player: player)
playerLayer.frame = self.view.bounds
self.videoView.layer.addSublayer(playerLayer)
player.play()
NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: self.player.currentItem, queue: nil, using: { (_) in
DispatchQueue.main.async {
self.player?.seek(to: kCMTimeZero)
self.player?.play()
}
})
}
The video location and file url is:
file:///private/var/mobile/Containers/Data/Application/E7D12401-F147-4905-83BE-72909F91E004/tmp/tempMovie1501672791.33525.mp4
The continuous loop part works from a previous project however I can't seem to get the temp stored video to load inside the player. I've tried to get the temp directory manually but it didn't work.
Moving the video from tmp to Documents directory worked for me.
This is how you do it:
let documentsDirectoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let destinationURL = documentsDirectoryURL.appendingPathComponent("filename.fileExtension")
do {
try FileManager.default.moveItem(at: previousTempLocation, to: destinationURL)
} catch let error as NSError { print(error.localizedDescription)}
let player = AVPlayer(url: destinationURL)
Apple says this on /tmp directory
Use this directory to write temporary files that do not need to
persist between launches of your app. Your app should remove files
from this directory when they are no longer needed; however, the
system may purge this directory when your app is not running.
Apple also recommends this for user data
Put user data in Documents/. User data generally includes any files
you might want to expose to the user—anything you might want the user
to create, import, delete or edit. Video and audio apps may even include
files that the user has downloaded to watch or listen to later.

.AAC Recorded Audio not playing in swift

I have recorded audio in .AAC format using microphone and successfully saved in document directory.When i try to play particular recorded audio it is not playing but play methods are calling,Am not getting any sound in microphone . If any one can help me to figure out this issue
Recording Audio sample code below
func record() {
self.prepareToRecord()
if let recorder = self.audioRecorder {
recorder.record()
}
}
func prepareToRecord() {
let dirPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as String
let currentDateTime = NSDate()
let formatter = NSDateFormatter()
formatter.dateFormat = "ddMMyyyy-HHmmss"
str_Recordedname = formatter.stringFromDate(currentDateTime)+".AAC"
let pathArray = [dirPath, str_Recordedname]
let filePath = NSURL.fileURLWithPathComponents(pathArray)
print(filePath)
let settings = [
AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
AVSampleRateKey: 44100.0,
AVNumberOfChannelsKey: 2 as NSNumber,
AVEncoderAudioQualityKey: AVAudioQuality.High.rawValue
]
do
{
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord)
self.audioRecorder = try AVAudioRecorder(URL:filePath!, settings:[:])
self.audioRecorder!.meteringEnabled = true
self.audioRecorder!.prepareToRecord()
self.audioRecorder!.record()
self.audioRecorder!.delegate = self
print("Recorded Audio is",filePath!)
}
catch let error as NSError
{
print(error.description)
}
}
Playing Sample code is
var filePath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as NSString
print("\nfilePath: \(filePath)")
filePath = filePath.stringByAppendingPathComponent(self.str_Recordedname)
print("\nfilePath: \(filePath)")
let filePathURL = NSURL.fileURLWithPath(filePath as String)
print("\nfilePathURL: \(filePathURL)")
do {
var player = AVPlayer()
let asset = AVURLAsset(URL: filePathURL)
let playerItem = AVPlayerItem(asset: asset)
player = AVPlayer(playerItem: playerItem)
player.play()
} catch let error as NSError {
print("Error: ", error.localizedDescription)
}
Thanks in Advance
Edit:
Removed AVPlayer Variable from Locally and pasted Globally its worked

Play m4a file from document Directory

I would to play an m4a file but I'm getting an error in the console and the file won't play.
Console prints:
The operation couldn’t be completed. (OSStatus error 2003334207.)
let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
print("File Location: ",url.path)
if(FileManager.default.fileExists(atPath: url.path)) {
do {
audioPlayer = try AVAudioPlayer(contentsOf: url)
guard let player = audioPlayer else { return }
player.prepareToPlay()
} catch let error {
print(error.localizedDescription)
}
} else {
print("file not found")
}
I tried URL like this:
let NoteUrl = Bundle.main.url(forResource: "Voice Note", withExtension: "m4a")
But I couldn't get it to play. Can someone help me to play m4a file by file's name? Any help will be appreciated.
You are passing the Documents folder to the audio player. Your code needs to append the specific filename to the Documents folder.
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let url = documentsURL.appendingPathComponent("Voice Note.m4a")