简体   繁体   中英

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)

There are several things to consider:

  1. Must use a correct format for the file.
  2. 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)
  3. 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);
  }];
}

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)
        }
    
    

    
}

`

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM