I am using an AVAudioRecorder to record human voice. My code is as follows:
// Property of Class
var recorder:AVAudioRecorder?
func recordButtonTapped() {
let audioSession = AVAudioSession.sharedInstance()
do {
try audioSession.setCategory(AVAudioSessionCategoryRecord)
try audioSession.setActive(true)
if audioSession.recordPermission() != .granted {
audioSession.requestRecordPermission({ (success) in
self.startRecording()
})
}
else {
self.startRecording()
}
} catch {
print("Unable To Set Category")
}
}
func startRecording() {
// libraryPathWith(media) just gets the path to the documents directory
// Like so: Documents/MediaLibrary/Audio/<mediaID>.<mediaExtension>
if let path = MMFileManager.libraryPathWith(media: self.media) {
isRecording = true
do {
let settings = [
AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
AVSampleRateKey: 44100,
AVNumberOfChannelsKey: 1,
AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
]
recorder = try AVAudioRecorder(url: path, settings: settings)
recorder?.delegate = self
if recorder!.prepareToRecord() {
recorder?.record()
}
}
catch {
isRecording = false
}
}
}
func stopRecording() {
self.recordingLabel.text = "Recording Complete"
self.recordingLabel.textColor = UIColor.white
if let rec = recorder {
rec.stop()
recorder = nil
}
isRecording = false
}
AVAudioRecorderDelegate
func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool) {
print("RECORDED AUDIO SUCCESSFULLY \(flag)")
}
func audioRecorderEncodeErrorDidOccur(_ recorder: AVAudioRecorder, error: Error?) {
print("AUDIO RECORDER ERROR \(error?.localizedDescription)")
}
After I call stop on the AVAudioRecorder the audioRecorderEncodeErrorDidOccur function never gets called, but the audioRecorderDidFinishRecording function does but the flag is always false. It prints out "RECORDED AUDIO SUCCESSFULLY false"
QUESTION
When I record using the code above I does save a file to my documents directory at the location specified. But this file is not something that I can play. It writes a text file, not an audio file as I specify the extension to be .aac.
Why does the AVAudioRecorder not record audio? And how do I get it to do so?
This is how I did it, first import
AVFoundation
and add the AVAudioRecorderDelegate to your ViewController:
class RecordViewController: UIViewController, AVAudioRecorderDelegate
then create a global instance of the AVAudioRecorder:
var audioRecorder : AVAudioRecorder!
Then I created a record button that starts the recording:
#IBAction func playButton(_ sender: Any) {
let dirPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String
let recordingName = "voiceRecording.wav"
let pathArray = [dirPath, recordingName]
let filePath = URL(string: pathArray.joined(separator: "/"))
let session = AVAudioSession.sharedInstance()
try! session.setCategory(AVAudioSessionCategoryPlayAndRecord, with: .defaultToSpeaker)
try! audioRecorder = AVAudioRecorder(url: filePath!, settings: [:])
audioRecorder.delegate = self
audioRecorder.isMeteringEnabled = true
audioRecorder.prepareToRecord()
audioRecorder.record()
}
dirPath finds the directory where the image will be stored.
recordingName will set the name of the actual recorded file
filepath combines the directory and recordingName for the final location
The rest are pretty much self explanatory.
Then create a pause button which is simpler:
#IBAction func pauseButton(_ sender: Any) {
audioRecorder.stop()
let audioSession = AVAudioSession.sharedInstance()
try! audioSession.setActive(false)
}
This should solve how you record the audio.
If you've verified your url has the "aac" extension, then I suspect the you simply forgot to call stop() on your recorder. This results in an un-finalized file.
Also print your errors in the catch block.
do{
try throwingFunc()
} catch {
print(error)
}
The issue was with the line in the stopRecording() function. Below the call to stop() the recorder I am immediately assigning the AVAudioRecorder instance to nil. This deallocates and ends the AVAudioRecorder before the post processing to create the authentic .aac file can be completed.
Related
Below is the code I've put together to attempt to take a phrase, save it to a file, then play that saved file. Not sure what area isn't working (not correct file name, not saving the file, not finding the file). Any help would be appreciated. (The speakPhrase is just a helper function to let me know that the speech synthesizer actually works, which it does).
import AVFoundation
import Foundation
class Coordinator {
let synthesizer: AVSpeechSynthesizer
var player: AVAudioPlayer?
init() {
let synthesizer = AVSpeechSynthesizer()
self.synthesizer = synthesizer
}
var recordingPath: URL {
let soundName = "Finally.caf"
// I've tried numerous file extensions. .caf was in an answer somewhere else. I would think it would be
// .pcm, but that doesn't work either.
// Local Directory
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
return paths[0].appendingPathComponent(soundName)
}
func speakPhrase(phrase: String) {
let utterance = AVSpeechUtterance(string: phrase)
utterance.voice = AVSpeechSynthesisVoice(language: "en")
synthesizer.speak(utterance)
}
func playFile() {
print("Trying to play the file")
do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
try AVAudioSession.sharedInstance().setActive(true)
player = try AVAudioPlayer(contentsOf: recordingPath, fileTypeHint: AVFileType.caf.rawValue)
guard let player = player else {return}
player.play()
} catch {
print("Error playing file.")
}
}
func saveAVSpeechUtteranceToFile() {
let utterance = AVSpeechUtterance(string: "This is speech to record")
utterance.voice = AVSpeechSynthesisVoice(language: "en-US")
utterance.rate = 0.50
synthesizer.write(utterance) { [self] (buffer: AVAudioBuffer) in
guard let pcmBuffer = buffer as? AVAudioPCMBuffer else {
fatalError("unknown buffer type: \(buffer)")
}
if pcmBuffer.frameLength == 0 {
// Done
} else {
// append buffer to file
do {
let audioFile = try AVAudioFile(forWriting: recordingPath, settings: pcmBuffer.format.settings, commonFormat: .pcmFormatInt16, interleaved: false)
try audioFile.write(from: pcmBuffer)
} catch {
print(error.localizedDescription)
}
}
}
}
}
Did you noticed the bufferCallback in the below function is called multiple times?
func write(_ utterance: AVSpeechUtterance,toBufferCallback bufferCallback: #escaping AVSpeechSynthesizer.BufferCallback)
So the root cause is pretty simple: the AVSpeechUtterance's audio is divided into multiple parts. On my iPhone, the callback calls about 20 times.
So if you create a new audio file in the closure every time, you will get a very tiny audio file(on my iPhone it was a 6kb audio file). That audio is not noticeable if you play it.
So replace the function to
func saveAVSpeechUtteranceToFile() {
let utterance = AVSpeechUtterance(string: "This is speech to record")
utterance.voice = AVSpeechSynthesisVoice(language: "en-US")
utterance.rate = 0.50
// Only create new file handle if `output` is nil.
var output: AVAudioFile?
synthesizer.write(utterance) { [self] (buffer: AVAudioBuffer) in
guard let pcmBuffer = buffer as? AVAudioPCMBuffer else {
fatalError("unknown buffer type: \(buffer)")
}
if pcmBuffer.frameLength == 0 {
// Done
} else {
do{
// this closure is called multiple times. so to save a complete audio, try create a file only for once.
if output == nil {
try output = AVAudioFile(
forWriting: recordingPath,
settings: pcmBuffer.format.settings,
commonFormat: .pcmFormatInt16,
interleaved: false)
}
try output?.write(from: pcmBuffer)
}catch {
print(error.localizedDescription)
}
}
}
}
BTW, I uploaded Github Demo here.
Finally, tell you how to inspect the file contents on an iOS device.
Xcode Window Menu -> Device and Simulators, do like below to copy out your app's content.
I want to download and play the sound from Google Translate (Text to Speech).
The mp3 file's downloaded successfull. I tried to play it with AVAudioPlayer, but there's no sound on both simulator and my real iPhone
I use XCode 10.2.1, Swift 5. Test on simulator (XSMax) and iPhone XSMax
import UIKit
import AVFoundation
class TextToSpeechGoogleTranslate: AVAudioPlayer, AVAudioPlayerDelegate {
var player: AVAudioPlayer?
func speak() {
let fileURL = URL(string: "https://translate.google.com/translate_tts?ie=UTF-8&total=1&idx=0&client=tw-ob&tl=vi&q=Hello" )!
let documentsUrl:URL = (FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first as URL?)!
let destinationFileUrl = documentsUrl.appendingPathComponent("voice.mp3")
let sessionConfig = URLSessionConfiguration.default
let session = URLSession(configuration: sessionConfig)
let request = URLRequest(url:fileURL)
let task = session.downloadTask(with: request) { (tempLocalUrl, response, error) in
if let tempLocalUrl = tempLocalUrl, error == nil {
if let statusCode = (response as? HTTPURLResponse)?.statusCode {
print("Successfully downloaded. Status code: \(statusCode)")
}
do {
try? FileManager.default.removeItem(at: destinationFileUrl)
try FileManager.default.copyItem(at: tempLocalUrl, to: destinationFileUrl)
print(destinationFileUrl)
do {
self.player = try AVAudioPlayer(contentsOf: destinationFileUrl)
self.player!.delegate = self
self.player!.prepareToPlay()
self.player!.volume = 1.0
self.player!.play()
}
catch let error as NSError {
print("Error: \(error.localizedDescription)")
}
catch {
print("AVAudioPlayer init failed")
}
} catch (let writeError) {
print("Error creating a file \(destinationFileUrl) : \(writeError)")
}
} else {
print("Error took place while downloading a file. Error description: %#", error?.localizedDescription as Any);
}
}
task.resume()
}
}
Output:
Successfully downloaded. Status code: 200
file:///Users/macbook/Library/Developer/CoreSimulator/Devices/F43F9B03-674C-4EE1-8CAD-01B5145868DE/data/Containers/Data/Application/6D1A3310-386D-4706-9F1E-DFF536B2A43F/Documents/voice.mp3
I played that file in Finder. It's OK.
I just tried your code and it seems to work fine. Maybe the problem is in how you create the TextToSpeechGoogleTranslate object and call speak() on it. For example, here is what I tried:
class ViewController: UIViewController {
var test: TextToSpeechGoogleTranslate?
override func viewDidLoad() {
super.viewDidLoad()
test = TextToSpeechGoogleTranslate()
test!.speak()
}
}
If that doesn't help, can you post some code showing how you call this method?
import UIKit
import AVFoundation
class ViewController: UIViewController, AVAudioPlayerDelegate {
var audioPlayer = TextToSpeechGoogleTranslate()
override func viewDidLoad() {
super.viewDidLoad()
audioPlayer.delegate = self
audioPlayer!.speak()
}
}
1) create instance of your class
2) Confirm it's delegate
3) call your function after you received your data
4) while testing from device, don't forget to turn off silence mode (volume up)
I used many codes that was for record an play the voice, but most of them are not in swift3 and they don't work in my app.
This code works, but I want to create a separate class from the viewcontroller that do recording an playing voices. Also the mentioned github code is complex an I'm searching for a simplified code.
Update:
After recording, when I check existence of the recorded file, the file doesn't exist, and it raises EXC_BAD_ACCESS error on appDelegate.
What's wrong?
Any suggestions would be appreciated.
Try to record audio by wirting line
let isRec = AudioManager.shared.record(fileName: "rec")
if isRec returned true then recording is happening else not.
To finish recording use : let recordedURL = AudioManager.shared.finishRecording()
To play recorded file send above url to setupUpPlayer() function in manager class
Not to forget to use extension code snippets give below the code snippet which are delegate functions of AVAudioRecorder and AVAudioPlayer
import Foundation
import AVFoundation
class AudioManager: NSObject {
static let shared = AudioManager()
var recordingSession: AVAudioSession?
var recorder: AVAudioRecorder?
var meterTimer: Timer?
var recorderApc0: Float = 0
var recorderPeak0: Float = 0
//PLayer
var player: AVAudioPlayer?
var savedFileURL: URL?
func setup() {
recordingSession = AVAudioSession.sharedInstance()
do {
try recordingSession?.setCategory(AVAudioSessionCategoryPlayAndRecord, with: .defaultToSpeaker)
try recordingSession?.setActive(true)
recordingSession?.requestRecordPermission({ (allowed) in
if allowed {
print("Mic Authorised")
} else {
print("Mic not Authorised")
}
})
} catch {
print("Failed to set Category", error.localizedDescription)
}
}
func record(fileName: String) -> Bool {
setup()
let url = getUserPath().appendingPathComponent(fileName + ".m4a")
let audioURL = URL.init(fileURLWithPath: url.path)
let recordSettings: [String: Any] = [AVFormatIDKey: kAudioFormatMPEG4AAC,
AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue,
AVNumberOfChannelsKey: 2,
AVSampleRateKey: 44100.0]
do {
recorder = try AVAudioRecorder.init(url: audioURL, settings: recordSettings)
recorder?.delegate = self
recorder?.isMeteringEnabled = true
recorder?.prepareToRecord()
recorder?.record()
self.meterTimer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true, block: { (timer: Timer) in
//Update Recording Meter Values so we can track voice loudness
if let recorder = self.recorder {
recorder.updateMeters()
self.recorderApc0 = recorder.averagePower(forChannel: 0)
self.recorderPeak0 = recorder.peakPower(forChannel: 0)
}
})
savedFileURL = url
print("Recording")
return true
} catch {
print("Error Handling", error.localizedDescription)
return false
}
}
func getUserPath() -> URL {
return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
}
func finishRecording() -> String {
recorder?.stop()
self.meterTimer?.invalidate()
var fileURL: String?
if let url: URL = recorder?.url {
fileURL = String(describing: url)
}
return /fileURL
}
//Player
func setupPlayer(_ url: URL) {
do {
try player = AVAudioPlayer.init(contentsOf: url)
} catch {
print("Error1", error.localizedDescription)
}
player?.prepareToPlay()
player?.play()
player?.volume = 1.0
player?.delegate = self
}
}
//MARK:- Audio Recorder Delegate
extension AudioManager: AVAudioRecorderDelegate {
func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool) {
print("AudioManager Finish Recording")
}
func audioRecorderEncodeErrorDidOccur(_ recorder: AVAudioRecorder, error: Error?) {
print("Encoding Error", /error?.localizedDescription)
}
}
//MARK:- Audio Player Delegates
extension AudioManager: AVAudioPlayerDelegate {
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer,
successfully flag: Bool) {
player.stop()
print("Finish Playing")
}
func audioPlayerDecodeErrorDidOccur(_ player: AVAudioPlayer,
error: Error?) {
print(/error?.localizedDescription)
}
}
I am developing an audio Player in swift and I need 2 buttons ( Play and Stop ) to control it. I have used the following code:
override func viewDidLoad() {
super.viewDidLoad()
let urlstring = "http://radio.spainmedia.es/wp-content/uploads/2015/12/tailtoddle_lo4.mp3"
let url = NSURL(string: urlstring)
print("the url = \(url!)")
downloadFileFromURL(url: url!)
}
func downloadFileFromURL(url:NSURL) {
var downloadTask:URLSessionDownloadTask
downloadTask = URLSession.shared.downloadTask(with: url as URL, completionHandler: { (URL, response, error) -> Void in
self.play(url: URL! as NSURL)
})
downloadTask.resume()
}
func play(url:NSURL) {
print("playing \(url)")
do {
self.SoundPlayer = try AVAudioPlayer(contentsOf: url as URL)
SoundPlayer.prepareToPlay()
SoundPlayer.volume = 1.0
SoundPlayer.play()
} catch let error as NSError {
//self.player = nil
print(error.localizedDescription)
} catch {
print("AVAudioPlayer init failed")
}
}
#IBAction func Play(_ sender: Any) {
SoundPlayer.play()
}
#IBAction func Stop(_ sender: Any) {
SoundPlayer.stop()
}
Basically, when the user opens the app, the mp3 audio file starts automatically. Therefore I have created 2 additional buttons, Start and Stop and I want to control this Audio Player with them. Would you please advice me how to do it, because now they are not connected with the Audio Player and no action is performed when I click on them.
If your objective is to just play the file while streaming it, get the Data from the URL from and then pass it to the AVAudioPlayer.
if let theURL = URL(string: "http://radio.spainmedia.es/wp-content/uploads/2015/12/tailtoddle_lo4.mp3") {
if let data = try? Data(contentsOf: theURL){
if let audioPlayer = try? AVAudioPlayer(data: data) {
print("Done")
audioPlayer.play()
} else {
print("Noo")
}
}
}
AVAudioPlayer(contentsOf: url as URL) often fails to fetch data from remote location.
You can also check other options such as AVPlayer to play your file
how can i play remote .mp3 file in my ios application?
I want a user to press a button, it changes background color (to yellow), a WAV is played and on completion of the WAV the button reverts to its original color (to red). So have a completion handler around the sound. Have tried various combinations of the code below but the WAV plays and the button doesn't appear to change color.
Is this the wrong approach or am I doing something wrong? Don't want to have to put completion handlers around the color changes as that, I presume, is overkill.
Many thanks.
typealias CompletionHandler = (success:Bool) -> Void
#IBAction func fireButton(sender: AnyObject) {
playLaser( { (success)-> Void in
if success {
self.shots -= 1
self.labelShotsLeft.text = String(self.shots)
} else {
}
})
}
func playLaser(completionHandler: CompletionHandler) {
fireButton.layer.backgroundColor = UIColor.yellowColor().CGColor
let url = NSBundle.mainBundle().URLForResource("laser", withExtension: "wav")!
do {
player = try AVAudioPlayer(contentsOfURL: url)
guard let player = player else { return }
player.prepareToPlay()
player.play()
} catch let error as NSError {
print(error.description)
}
self.fireButton.layer.backgroundColor = UIColor.redColor().CGColor
completionHandler(success: true)
}
To detect AVAudioPlayer finish playing, you need to use AVAudioPlayerDelegate.
You may need to write something like this:
func playLaser(completionHandler: CompletionHandler) {
fireButton.layer.backgroundColor = UIColor.yellowColor().CGColor
let url = NSBundle.mainBundle().URLForResource("laser", withExtension: "wav")!
do {
player = try AVAudioPlayer(contentsOfURL: url)
guard let player = player else { return }
player.delegate = self //<- Sorry, this was missing in my first post
player.play()
} catch let error as NSError {
print(error.description)
}
audioPlayerCompletionHandler = completionHandler
}
var audioPlayerCompletionHandler: CompletionHandler?
func audioPlayerDidFinishPlaying(player: AVAudioPlayer, successfully flag: Bool) {
self.fireButton.layer.backgroundColor = UIColor.redColor().CGColor
audioPlayerCompletionHandler?(success: true)
}
(You need to add conformance to AVAudioPlayerDelegate to your ViewController's declaration header.)
Code does not magically pause and wait just because you say play.play() — that would be horrible! Thus, your so-called completion handler is not a completion handler at all. It runs immediately — that is, as soon you start playing. Your code does nothing about obtaining information as to when the audio player has finished playing.
For that, you need to configure a delegate and receive the delegate message that audio player emits when it finishes playing.
This is one of those questions which is a little more subtle than meets the eye. I tried putting three completion handlers around each task: change colour to yellow, play sound, change colour back to red. The code was being executed in the correct sequence as I NSLogged it but the button never changed colour due to screen updating controls. Here is the code that works that I hope other readers might find useful:
Swift 2.0
#IBAction func fireButton(sender: AnyObject) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
dispatch_sync(dispatch_get_main_queue()) {
self.fireButton.layer.backgroundColor = UIColor.yellowColor().CGColor
}
self.playLaser( { (success)-> Void in
if success {
self.shots -= 1
} else {
}
})
dispatch_sync(dispatch_get_main_queue()) {
self.labelShotsLeft.text = String(self.shots)
self.fireButton.layer.backgroundColor = UIColor.redColor().CGColor
}
}
}
func playLaser(completion: (success: Bool) -> ()) {
let url = NSBundle.mainBundle().URLForResource("laser", withExtension: "wav")!
do {
player = try AVAudioPlayer(contentsOfURL: url)
guard let player = player else { return }
player.play()
completion(success: true)
} catch let error as NSError {
completion(success: false)
}
}
Swift 3.0
#IBAction func fireButton(_ sender: AnyObject) {
let fireQueue = DispatchQueue(label: "queueFirebutton")
fireQueue.async {
DispatchQueue.main.sync {
self.fireButtonDisabled()
}
DispatchQueue.main.sync {
self.playLaser()
self.shots -= 1
if self.shots <= 0 {
self.shots = 0
}
}
DispatchQueue.main.sync {
if self.shots < 0 { self.shots = 0}
self.labelShotsLeft.text = String(self.shots)
sleep(1)
self.fireButtonEnabled()
}
}
}
func playLaser() {
let url = Bundle.main.url(forResource: "laser", withExtension: "wav")!
do {
player = try AVAudioPlayer(contentsOf: url)
guard let player = player else { return }
player.play()
} catch {
}
}