'AVAudioFile write not working Swift iOS 14
I have the following function:
private func getPCMBuffer(utterance: AVSpeechUtterance,
completion: @escaping (Result<AVAudioPCMBuffer, Failures>) -> Void
) {
speechSynthesizer.write(utterance) { (buffer: AVAudioBuffer) in
guard let pcmBuffer = buffer as? AVAudioPCMBuffer else {
fatalError("unknown buffer type: \(buffer)")
}
completion(.success(pcmBuffer))
}
completion(.failure(.failed))
}
Which returns to me an AVAudioPCMBuffer. I have verified the utterance I pass in speaks properly.
The issue comes when I try to write this AVAudioPCMBuffer into a URL locally, like this:
getPCMBuffer(utterance: speechUtterance) { result in
switch result {
case .success(let buffer):
var settings: [String : Any] = [:]
let savePathUrl: URL = URL(fileURLWithPath: NSHomeDirectory() + "/Documents/audioFile.caf")
settings[AVFormatIDKey] = kAudioFormatMPEG4AAC
settings[AVAudioFileTypeKey] = kAudioFileCAFType
settings[AVSampleRateKey] = buffer.format.sampleRate
settings[AVNumberOfChannelsKey] = 2
settings[AVLinearPCMIsFloatKey] = (buffer.format.commonFormat == .pcmFormatInt32)
do {
let tempFile = try AVAudioFile(forWriting: savePathUrl, settings: settings, commonFormat: buffer.format.commonFormat, interleaved: buffer.format.isInterleaved)
try tempFile.write(from: buffer)
} catch {
print(error)
}
case .failure(let failure):
print(failure.localizedDescription)
}
}
I am met with the following error: CABufferList.h:179:BytesConsumed: ASSERTION FAILURE [(nBytes <= buf->mDataByteSize) != 0 is false]:
on the line where I try to do:
try tempFile.write(from: buffer)
Solution 1:[1]
There are several things to consider:
- Must use a correct format for the file.
- The callback of
writeUtterance:toBufferCallback:
is called multiple times, each time with a the next chunk of the synthesized audio. As far as I can tell, it synthesizes up to 10ms of the audio each time. (undocumented) - You must close the file in order to flush it. Closing is done by releasing the reference to the object. (undocumented).
@property (readonly, nonatomic) AVSpeechSynthesizer *synth;
@property (strong, nonatomic) AVAudioFile *file;
...
_synth = [[AVSpeechSynthesizer alloc] init];
self.synth.delegate = self;
...
- (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer
didFinishSpeechUtterance:(AVSpeechUtterance *)utterance {
self.file = nil; // releasing the file will close it
NSLog(@"Finished");
}
- (void)generateTranscript {
AVSpeechUtterance *utterance =
[AVSpeechUtterance speechUtteranceWithString:...];
utterance.voice = ...;
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback
withOptions:AVAudioSessionCategoryOptionMixWithOthers
error:nil];
[self.synth writeUtterance:utterance toBufferCallback:^(AVAudioBuffer * _Nonnull buffer) {
NSAssert([buffer isKindOfClass:AVAudioPCMBuffer.class], @"");
NSError *error;
if (!self.file) {
NSURL *url = [NSURL fileURLWithPath:[NSTemporaryDirectory()
stringByAppendingPathComponent:@"recording.m4a"]];
NSMutableDictionary *settings = [NSMutableDictionary dictionary];
settings[AVFormatIDKey] = @(kAudioFormatMPEG4AAC);
settings[AVEncoderAudioQualityKey] = @(AVAudioQualityMax);
settings[AVSampleRateKey] = buffer.format.settings[AVSampleRateKey]; // 22050 Hz
settings[AVNumberOfChannelsKey] = buffer.format.settings[AVNumberOfChannelsKey]; // 1 channel
self.file = [[AVAudioFile alloc] initForWriting:url settings:settings
commonFormat:buffer.format.commonFormat // AVAudioPCMFormatInt16
interleaved:buffer.format.interleaved // YES
error:&error];
if (error) {
NSLog(@"%@", error);
return;
}
}
[self.file writeFromBuffer:(AVAudioPCMBuffer *)buffer error:&error];
if (error) {
NSLog(@"%@", error);
return;
}
AVAudioFrameCount frameCont = ((AVAudioPCMBuffer *)buffer).frameLength;
double sampleRate = buffer.format.sampleRate;
NSLog(@"Written %.4lfs", (double)frameCont / sampleRate);
}];
}
Regarding the format, I also advice to read AVSpeechSynthesisVoice audioFileSettings
documentation.
Solution 2:[2]
This works for me
`
private func uploadBuffer(_ buffer: AVAudioPCMBuffer) {
let fileManager = FileManager.default
do {
let documentDirectory = try! fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor:nil, create:false)
try! FileManager.default.createDirectory(atPath: documentDirectory.path, withIntermediateDirectories: true, attributes: nil)
let fileURL = documentDirectory.appendingPathComponent("out.wav")
print(fileURL.path)
let audioFile = try! AVAudioFile(forWriting: fileURL, settings: buffer.format.settings, commonFormat: buffer.format.commonFormat, interleaved: true)
try! audioFile.write(from: buffer)
} catch {
print(error)
}
}
`
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | Brett Young |