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

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

На данный момент вся функция выполняется без ошибок. По какой-то причине, когда я захожу в каталог документов симулятора через терминал, аудиофайл не воспроизводится через iTunes и выдает ошибку при попытке открыть с помощью быстрого времени "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
        }
      }
    }
  }
}

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а также зачем конвертировать один из ваших собственных активов, если вы можете распространять его уже в нужном формате?

Другие вопросы по тегам