Как мне кодировать iOS видео в формате .mp4 из UIImagePickerController, чтобы устройства Android могли их воспроизводить? - PullRequest
1 голос
/ 23 января 2020

Я использую UIImagePickerController для записи коротких (<30 с) видео, которые затем сохраняются и загружаются через наш API. Приложение кроссплатформенное, поэтому мне нужно, чтобы записанные видео были закодированы в формате mp4, чтобы их могли воспроизводить устройства Android. </p>

Я использовал инструкции из следующих вопросов для создания своего решения:

Swift - Как записать видео в формате MP4 с помощью UIImagePickerController?

AVFoundation записывает видео в формате MP4

https://forums.developer.apple.com/thread/94762

Я записываю свое видео через UIImagePickerController следующим образом:

    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
    // Local variable inserted by Swift 4.2 migrator.
    let info = convertFromUIImagePickerControllerInfoKeyDictionary(info)


    let videoNSURL = info[convertFromUIImagePickerControllerInfoKey(UIImagePickerController.InfoKey.mediaURL)] as? NSURL

    videoURL = videoNSURL!.absoluteURL
    if let videoURL = videoURL {
        let avAsset = AVURLAsset(url: videoURL, options: nil)

        avAsset.exportVideo { (exportedURL) in
            if let uploadVC = self.uploadVC {
                uploadVC.incomingFileURL = exportedURL
                uploadVC.myJewelleryID = self.myJewelleryID
                uploadVC.topicID = self.topicID
            }
            DispatchQueue.main.async { [weak self] in
              //Update UI with results from previous closure
                self?.dismiss(animated: true, completion: nil)
                self?.showUploadContainer()
                self?.updateVideoContainerWithURL(url: exportedURL)
            }
        }
    }
}

Затем он передает экспортированный URL MP4 в представление контейнера загрузки, где сохраняет файл на устройство:

private func saveVideoFileToDevice() {

    //Filename Struct = [AssetID]_[TopicID]_[CustomerID]_[Datestamp]
    let date = Date()
    let formater = DateFormatter()
    formater.locale = Locale(identifier: "en_US_POSIX")
    formater.dateFormat = "YYYY-MM-dd-HH-mm-ss"

    uploadFileName = ""
    if let mjID = myJewelleryID {
        uploadFileName = "ASID_\(mjID)_\(User.instance.customerID)_\(formater.string(from: date)).mp4"
    } else if let tID = topicID {
        uploadFileName = "ASID_\(tID)_\(User.instance.customerID)_\(formater.string(from: date)).mp4"
    }

    let fileManager = FileManager.default

    if let destURL = URL(string: "file://\(NSHomeDirectory())/Documents/\(uploadFileName!)") {

        var fileData: Data!
        print("destURL = \(destURL)")
        do {
            try fileManager.copyItem(at: incomingFileURL! as URL, to: destURL)
            fileData = try Data(contentsOf: incomingFileURL! as URL)

            try fileData.write(to: destURL)


        }
        catch {
            print("DEBUG: Failed to save video data")
        }
    }
}

, а затем загружает файл в наш API. Хотя файл MP4, он не воспроизводится на Android. При проверке файл выглядит очень похоже на файл, который фактически воспроизводится на устройстве Android, когда мы сравниваем код c data:

Screenshot of codec comparison

У кого-нибудь есть идеи, как мне это исправить?

Спасибо!

Ответы [ 2 ]

1 голос
/ 23 января 2020
var exportSession: AVAssetExportSession!


func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
            picker.dismiss(animated: true, completion: nil)

        guard let videoURL = (info[UIImagePickerController.InfoKey.mediaURL] as? URL) else { return }
        encodeVideo(videoURL)
    }

func encodeVideo(_ videoURL: URL)  {
        let avAsset = AVURLAsset(url: videoURL, options: nil)

        //Create Export session
        exportSession = AVAssetExportSession(asset: avAsset, presetName: AVAssetExportPresetPassthrough)

        let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] as URL
        let filePath = documentsDirectory.appendingPathComponent("rendered-Video.mp4")
        deleteFile(filePath)

        exportSession!.outputURL = filePath
        exportSession!.outputFileType = AVFileType.mp4
        exportSession!.shouldOptimizeForNetworkUse = true
        let start = CMTimeMakeWithSeconds(0.0, preferredTimescale: 0)
        let range = CMTimeRangeMake(start: start, duration: avAsset.duration)
        exportSession.timeRange = range

        exportSession!.exportAsynchronously(completionHandler: {() -> Void in
            DispatchQueue.main.async {
                Utility.stopActivityIndicator()

                switch self.exportSession!.status {
                case .failed:
                    self.view.makeToast(self.exportSession?.error?.localizedDescription ?? "")
                case .cancelled:
                    self.view.makeToast("Export canceled")
                case .completed:
                    if let url = self.exportSession.outputURL {
                        //Rendered Video URL
                    }
                default:
                    break
                }
            }
        })
    }

Не забудьте импортировать AVFoundation

Надеюсь, что это поможет!

1 голос
/ 23 января 2020
//MARK:- Convert iPhoneVideo(.mov) to mp4
extension AVURLAsset
{
    func exportVideo(presetName: String = AVAssetExportPresetHighestQuality, outputFileType: AVFileType = .mp4, fileExtension: String = "mp4", then completion: @escaping (URL?) -> Void)
    {
        let filename = url.deletingPathExtension().appendingPathExtension(fileExtension).lastPathComponent
        let outputURL = FileManager.default.temporaryDirectory.appendingPathComponent(filename)

    if let session = AVAssetExportSession(asset: self, presetName: presetName) {
        session.outputURL = outputURL
        session.outputFileType = outputFileType
        let start = CMTimeMakeWithSeconds(0.0, preferredTimescale: 0)
        let range = CMTimeRangeMake(start: start, duration: duration)
        session.timeRange = range
        session.shouldOptimizeForNetworkUse = true
        session.exportAsynchronously {
            switch session.status {
            case .completed:
                completion(outputURL)
            case .cancelled:
                debugPrint("Video export cancelled.")
                completion(nil)
            case .failed:
                let errorMessage = session.error?.localizedDescription ?? "n/a"
                debugPrint("Video export failed with error: \(errorMessage)")
                completion(nil)
            default:
                break
            }
        }
    } else {
        completion(nil)
    }
}
}


//MARK:- ImagePicker delegate methods
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any])
    {

if let url = info[UIImagePickerController.InfoKey.mediaURL] as? URL {

                let avAsset = AVURLAsset(url: url, options: nil)
            avAsset.exportVideo(presetName: AVAssetExportPresetHighestQuality, outputFileType: AVFileType.mp4, fileExtension: "mp4") { (mp4Url) in
                print("Mp4 converted url : \(String(describing: mp4Url))")
                self.videoPath = mp4Url//videoURL//

            }

}

}
...