繁体   English   中英

Swift - 将视频转换为数据,字符串使用 UTF8 编码返回 nil - 通过 HTTP 发送视频到 AWS S3 存储桶

[英]Swift - converting video to data and String returns nil with UTF8 encoding - sending video via HTTP POST to AWS S3 bucket

问题的高级解释(在 Swift 5 中)

  1. 我正在使用AVAssetWriter在 MOV 中录制视频

  2. 我正在使用exportSession.exportAsynchronously对 MP4 中的视频进行编码(但是我可以跳过这一步,我仍然有同样的问题)

  3. 我通过 HTTP POST 将视频发送到 AWS S3 存储桶:

    let fileData = try NSData(contentsOfFile:videoPathMP4.path, options:[]) as Data

    let fileContent = String(data: fileData, encoding: .utf8)

fileContent现在nil ,这意味着视频数据无法以 UTF8 进行解释。 如果我使用 UTF16,它可以工作(我得到一个字符串),但是当我在服务器端收到消息时,它不是一个可读的 MP4 文件(它已损坏?)。 我感觉这是因为它应该是 UTF8 中的字符串,但我无法将视频数据转换为 UTF8 字符串发送到服务器。

如何以 UTF8 格式发送此数据,或者如何仅以 NSData 格式发送视频数据? 我看错了吗?

以下是我的不同步骤的代码片段:

第 1 步 - 在 MOV 中录制视频:

func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
    let timestamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer).seconds
            switch _captureState {
            case .start:
                print ("starting to record")
                // Set up recorder
                _filename = UUID().uuidString
                let videoPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("\(_filename).mov")
                let writer = try! AVAssetWriter(outputURL: videoPath, fileType: .mov)
                let settings = _videoOutput!.recommendedVideoSettingsForAssetWriter(writingTo: .mov)
                let input = AVAssetWriterInput(mediaType: .video, outputSettings: settings) 
                input.mediaTimeScale = CMTimeScale(bitPattern: 600)
                input.expectsMediaDataInRealTime = true
                input.transform = CGAffineTransform(rotationAngle: .pi/2)
                let adapter = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: input, sourcePixelBufferAttributes: nil)
                if writer.canAdd(input) {
                    writer.add(input)
                }
                writer.startWriting()
                writer.startSession(atSourceTime: .zero)
                _assetWriter = writer
                _assetWriterInput = input
                _adpater = adapter
                _captureState = .capturing
                _time = timestamp
            case .capturing:
                if _assetWriterInput?.isReadyForMoreMediaData == true {
                    let time = CMTime(seconds: timestamp - _time, preferredTimescale: CMTimeScale(600))
                    _adpater?.append(CMSampleBufferGetImageBuffer(sampleBuffer)!, withPresentationTime: time)
                }
                break
            case .end:
                guard _assetWriterInput?.isReadyForMoreMediaData == true, _assetWriter!.status != .failed else { break }
                _assetWriterInput?.markAsFinished()
                _assetWriter?.finishWriting { [weak self] in
                    self?._captureState = .idle
                    self?._assetWriter = nil
                    self?._assetWriterInput = nil
                    print ("Finished writing video file: \(self!._filename)")
                }
            default:
                break
            }

}

第 2 步 - 在 MP4 中编码视频(同步以避免发送数据的竞争条件):

    func encodeVideo(at videoURL: URL, completionHandler: ((URL?, Error?) -> Void)?)  {
    let avAsset = AVURLAsset(url: videoURL, options: nil)

    let startDate = Date()

    //Create Export session
    guard let exportSession = AVAssetExportSession(asset: avAsset, presetName: AVAssetExportPresetPassthrough) else {
        completionHandler?(nil, nil)
        return
    }

    //Creating temp path to save the converted video
    let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] as URL
    let filePath = documentsDirectory.appendingPathComponent("rendered-Video.mp4")

    //Check if the file already exists then remove the previous file
    if FileManager.default.fileExists(atPath: filePath.path) {
        do {
            try FileManager.default.removeItem(at: filePath)
        } catch {
            completionHandler?(nil, error)
        }
    }

    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

    let group = DispatchGroup()
    group.enter()
    
    exportSession.exportAsynchronously(completionHandler: {() -> Void in
        switch exportSession.status {
        case .failed:
            print(exportSession.error ?? "NO ERROR")
            completionHandler?(nil, exportSession.error)
        case .cancelled:
            print("Export canceled")
            completionHandler?(nil, nil)
        case .completed:
            //Video conversion finished
            let endDate = Date()

            let time = endDate.timeIntervalSince(startDate)
            print(time)
            print("Successful!")
            print(exportSession.outputURL ?? "NO OUTPUT URL")
            completionHandler?(exportSession.outputURL, nil)

            default: break
        }
        group.leave()
    })
    group.wait(timeout: .now() + 10.0) // blocks current queue
}

第 3 步 - 发送视频(获取一个临时的 URL 到 S3 并授权发送实际的视频文件,然后发送视频文件 - 所有呼叫都进行阻塞呼叫):

    private func sendVideo(filename: String, recordID: String){
    
    //Obtain temporary URL and credentials
    let url = URL(string: "https://<OMITTED>")!

    //JSON Payload for server
    let data = [
                "recordid" : recordID,
    ] as [String : Any]
    
    let group = DispatchGroup()
    group.enter()
    Helper.sendVideoURLRequestToRestAPI(url: url, data: data)
        { response, json, error in
                // will be called at either completion or at an error.
        
                guard let statusCode = response?.statusCode else{
                    print ("[sendVideo] no status code returned - request failed")
                    return
                }
                print ("Status Code from server: \(statusCode)")
        
                if statusCode != 200 {
                    DispatchQueue.main.async {
                        let alert2 = UIAlertController(title: "Error", message: "Error submitting data - please try again! - \(json["body"] as? String)", preferredStyle: .alert)
                        alert2.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
                        self.present(alert2, animated: true)
                    }

                } else { //success
                    
                    let videoURL = json["url"] as! String

                    let parameters = [
                      [
                        "key": "key",
                        "value": (json["fields"] as! [String : Any])["key"]!,
                        "type": "text"
                      ],
                      [
                        "key": "AWSAccessKeyId",
                        "value": (json["fields"] as! [String : Any])["AWSAccessKeyId"]!,
                        "type": "text"
                      ],
                      [
                        "key": "signature",
                        "value": (json["fields"] as! [String : Any])["signature"]!,
                        "type": "text"
                      ],
                      [
                        "key": "policy",
                        "value": (json["fields"] as! [String : Any])["policy"]!,
                        "type": "text"
                      ],
                      [
                        "key": "x-amz-security-token",
                        "value": (json["fields"] as! [String : Any])["x-amz-security-token"]!,
                        "type": "text"
                      ],
                      [
                        "key": "file",
                        "src": filename, 
                        "type": "file",
                        "contentType": "video/mp4"
                      ]
                    ] as [[String : Any]]

                    let boundary = "Boundary-\(UUID().uuidString)"
                    var body = ""
                    var error: Error? = nil
                    for param in parameters {
                        if param["disabled"] == nil {
                            let paramName = param["key"]!
                            body += "--\(boundary)\r\n"
                            body += "Content-Disposition:form-data; name=\"\(paramName)\""
                            if param["contentType"] != nil {
                              body += "\r\nContent-Type: \(param["contentType"] as! String)"
                            }
                            let paramType = param["type"] as! String
                            if paramType == "text" {
                              let paramValue = param["value"] as! String
                              body += "\r\n\r\n\(paramValue)\r\n"
                            } else {
                                let paramSrc = param["src"] as! String
                                print("paramSrc: \(paramSrc)")
                                let videoPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("\(paramSrc).mov")
                                
                                self.encodeVideo(at: videoPath, completionHandler: { url, error in
                                    guard error == nil else {
                                        print ("error video mp4 conversion")
                                        print(String(describing: error))
                                        return
                                    }
                                })
                                let videoPathMP4 = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("rendered-Video.mp4")

                                do {
                                    let fileData = try NSData(contentsOfFile:videoPathMP4.path, options:[]) as Data
                                    let fileContent = String(data: fileData, encoding: .utf8)! //Exception thrown here - IF I change this to UTF16 it doesn't throw an exception but the file arrives corrupted on server side.

                                    body += "; filename=\"\(paramName)\"\r\n"
                                    + "Content-Type: \"content-type header\"\r\n\r\n\(fileContent)\r\n" //paramName = paramSrc
                                
                                  //  print ("Body: \(body)")
                                } catch {
                                    print ("Could not open video file: \(videoPathMP4.path)")
                                    return
                                }
                            }
                        }
                    }
                    body += "--\(boundary)--\r\n";
                    let postData = body.data(using: .utf8) //UTF8

                    var request = URLRequest(url: URL(string: videoURL)!,timeoutInterval: Double.infinity)
                    request.addValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
                    request.httpMethod = "POST"
                    request.httpBody = postData


                    let session = URLSession.shared
                    let task = session.dataTask(with: request as URLRequest, completionHandler: { data, response, error in

                        guard error == nil else {
                            print ("error")
                            print(String(describing: error))
                            return
                        }

                        guard let data = data else {
                            print ("Error unpacking data")
                            print(String(describing: error))
                            //semaphore.signal()
                            return
                        }

                        print ("data \(data) - \(response)")
                        print(String(data: data, encoding: .utf8)!)
                    })
                    task.resume()

                    group.leave()
                }
        }
    group.wait() // blocks current queue
}
 let boundary = "Boundary-\(UUID().uuidString)"
            var body = ""
            var error: Error? = nil
            for param in parameters {
              if param["disabled"] == nil {
                let paramName = param["key"]!
                body += "--\(boundary)\r\n"
                body += "Content-Disposition:form-data; name=\"\(paramName)\""
                if param["contentType"] != nil {
                  body += "\r\nContent-Type: \(param["contentType"] as! String)"
                }
                let paramType = param["type"] as! String
                if paramType == "text" {
                  let paramValue = param["value"] as! String
                  body += "\r\n\r\n\(paramValue)\r\n"
                } else {
                  let paramSrc = param["src"] as! String
                    print("KMKM",paramSrc)
                    
                  let fileData = try! NSData(contentsOfFile:paramSrc)
                      print("mcmjdc",fileData)
                    
                    let fileContent = String(data: fileData! as Data, encoding: .utf8)!
                  body += "; filename=\"\(paramSrc)\"\r\n"
                    + "Content-Type: \"content-type header\"\r\n\r\n\(fileContent)\r\n"
                }

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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