Почему я не могу воспроизвести файл после сжатия аудиофайла?

Аудиофайл не будет воспроизводиться после его уменьшения с помощью AVAssetReader/ AVAssetWriter

На данный момент вся функция выполняется нормально, ошибок не выдает. По какой-то причине, когда я захожу в каталог документов симулятора через терминал, аудиофайл не воспроизводится через iTunes и выдает ошибку при попытке открыть с помощью QuickTime «QuickTime Player не может открыть «test1.m4a»

Кто-нибудь специализируется в этой области и понимает, почему это не работает?

protocol FileConverterDelegate {
  func fileConversionCompleted()
}

class WKAudioTools: NSObject {

  var delegate: FileConverterDelegate?

  var url: URL?
  var assetReader: AVAssetReader?
  var assetWriter: AVAssetWriter?

  func convertAudio() {

    let documentDirectory = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
    let exportURL = documentDirectory.appendingPathComponent(Assets.soundName1).appendingPathExtension("m4a")

    url = Bundle.main.url(forResource: Assets.soundName1, withExtension: Assets.mp3)

    guard let assetURL = url else { return }
    let asset = AVAsset(url: assetURL)

    //reader
    do {
      assetReader = try AVAssetReader(asset: asset)
    } catch let error {
      print("Error with reading >> \(error.localizedDescription)")
    }

    let assetReaderOutput = AVAssetReaderAudioMixOutput(audioTracks: asset.tracks, audioSettings: nil)
    //let assetReaderOutput = AVAssetReaderTrackOutput(track: track!, outputSettings: nil)

    guard let assetReader = assetReader else {
      print("reader is nil")
      return
    }

    if assetReader.canAdd(assetReaderOutput) == false {
      print("Can't add output to the reader ☹️")
      return
    }

    assetReader.add(assetReaderOutput)

    // writer
    do {
      assetWriter = try AVAssetWriter(outputURL: exportURL, fileType: .m4a)
    } catch let error {
      print("Error with writing >> \(error.localizedDescription)")
    }

    var channelLayout = AudioChannelLayout()

    memset(&channelLayout, 0, MemoryLayout.size(ofValue: channelLayout))
    channelLayout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo

    // use different values to affect the downsampling/compression
    let outputSettings: [String: Any] = [AVFormatIDKey: kAudioFormatMPEG4AAC,
                                         AVSampleRateKey: 44100.0,
                                         AVNumberOfChannelsKey: 2,
                                         AVEncoderBitRateKey: 128000,
                                         AVChannelLayoutKey: NSData(bytes: &channelLayout, length:  MemoryLayout.size(ofValue: channelLayout))]

    let assetWriterInput = AVAssetWriterInput(mediaType: .audio, outputSettings: outputSettings)

    guard let assetWriter = assetWriter else { return }

    if assetWriter.canAdd(assetWriterInput) == false {
      print("Can't add asset writer input ☹️")
      return
    }

    assetWriter.add(assetWriterInput)
    assetWriterInput.expectsMediaDataInRealTime = false

    // MARK: - File conversion
    assetWriter.startWriting()
    assetReader.startReading()

    let audioTrack = asset.tracks[0]

    let startTime = CMTime(seconds: 0, preferredTimescale: audioTrack.naturalTimeScale)

    assetWriter.startSession(atSourceTime: startTime)

    // We need to do this on another thread, so let's set up a dispatch group...
    var convertedByteCount = 0
    let dispatchGroup = DispatchGroup()

    let mediaInputQueue = DispatchQueue(label: "mediaInputQueue")
    //... and go
    dispatchGroup.enter()
    assetWriterInput.requestMediaDataWhenReady(on: mediaInputQueue) {
      while assetWriterInput.isReadyForMoreMediaData {
        let nextBuffer = assetReaderOutput.copyNextSampleBuffer()

        if nextBuffer != nil {
          assetWriterInput.append(nextBuffer!)  // FIXME: Handle this safely
          convertedByteCount += CMSampleBufferGetTotalSampleSize(nextBuffer!)
        } else {
          // done!
          assetWriterInput.markAsFinished()
          assetReader.cancelReading()
          dispatchGroup.leave()

          DispatchQueue.main.async {
            // Notify delegate that conversion is complete
            self.delegate?.fileConversionCompleted()
            print("Process complete ????")

            if assetWriter.status == .failed {
              print("Writing asset failed ☹️ Error: ", assetWriter.error)
            }
          }
          break
        }
      }
    }
  }
}

person Joey Slomowitz    schedule 15.08.2018    source источник
comment
Можете ли вы объяснить цель кода? Я вижу, что вы сохраняете mp3 как m4a, но происходит и кое-что еще, потому что вам не нужен буфер семплов только для этого.   -  person matt    schedule 16.08.2018
comment
Я использовал эту ссылку для своего решения ›› gist.github.com/abeldomingues/fe8fa797fd55603f2f4a   -  person Joey Slomowitz    schedule 16.08.2018
comment
Насколько я понимаю, буфер сэмплов полезен для просмотра хода сжатия, но я не считаю, что это необходимо.   -  person Joey Slomowitz    schedule 16.08.2018
comment
Что ж, если все вам нужно перекодировать mp3, то это перебор...   -  person matt    schedule 16.08.2018


Ответы (1)


Вам нужно вызвать finishWriting на вашем AVAssetWriter, чтобы полностью записать вывод:

assetWriter.finishWriting {
    DispatchQueue.main.async {
        // Notify delegate that conversion is complete
        self.delegate?.fileConversionCompleted()
        print("Process complete ????")

        if assetWriter.status == .failed {
            print("Writing asset failed ☹️ Error: ", assetWriter.error)
        }
    }
}

Если exportURL существует до того, как вы начнете преобразование, вы должны удалить его, иначе преобразование завершится ошибкой:

try! FileManager.default.removeItem(at: exportURL)

Как указывает @matt, зачем буфер, когда вы можете сделать преобразование более простым с помощью AVAssetExportSession, а также зачем преобразовывать один из ваших собственных ресурсов, когда вы можете распространять его уже в нужном формате?

person Rhythmic Fistman    schedule 17.08.2018
comment
Это сработало!! Большое спасибо! ????. К сожалению, я не очень хорошо понимаю эту технологию, и ответы, которые я нашел, были весьма ограниченными. Выполняет ли буфер выборки сжатие пакетов? Или это совершенно не нужно? Все, что я хотел сделать, это уменьшить звуковой файл размером 27 МБ, прежде чем передать его на Apple Watch. Прямо сейчас этот код уменьшает его до 10 МБ. Как вы думаете, AVAssetExportSession был бы лучшим кандидатом для этого, и есть ли у вас рабочий пример, который я мог бы проверить? Спасибо еще раз. - person Joey Slomowitz; 20.08.2018