如何解决Swift - 将视频转换为数据,字符串使用 UTF8 编码返回 nil - 通过 HTTP POST 将视频发送到 AWS S3 存储桶
问题的高级解释(在 Swift 5 中)
-
我正在使用
在 MOV 中录制视频AVAssetWriter
-
我正在使用
exportSession.exportAsynchronously
对 MP4 中的视频进行编码(但是我可以跳过这一步,但我仍然遇到同样的问题) -
我通过 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 步 - 发送视频(获取 S3 的临时 URL 并授权发送实际视频文件,然后发送视频文件 - 所有调用都阻止调用):
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"]!,[
"key": "signature","value": (json["fields"] as! [String : Any])["signature"]!,[
"key": "policy","value": (json["fields"] as! [String : Any])["policy"]!,[
"key": "x-amz-security-token","value": (json["fields"] as! [String : Any])["x-amz-security-token"]!,[
"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
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。