减少 swift/iOS 中的视频大小以上传到服务器
reduce video size in swift/iOS to upload to server
我正在使用 UIImagePickerController 从我的 swift iOS 应用中挑选视频。我正在保存此 URL,现在想将其转换为数据以发送到我的服务器进行存储,使用:
let messageVideoData = NSData(contentsOfURL: chosenVideoURL)
问题是文件太大了。对于在我的 iPhone 6s 上拍摄的 7 秒视频,分辨率为 1280、720,帧速率为 30,文件大小超过 4 MB。我注意到用 whatsapp 和其他聊天应用程序发送的相同图像减少到几百 KB。
减小外部存储文件大小的最佳方法是什么?该视频主要针对手机,因此可以将分辨率降低到 800 或更低。
我尝试将 UIImagePickerController 质量设置为:
picker.videoQuality = UIImagePickerControllerQualityType.Type640x480
但这仅将文件大小减小到 3.5 MB。
使用:
picker.videoQuality = UIImagePickerControllerQualityType.TypeLow
将分辨率降低到远低于理想值的值。
我应该采取另一种方法来减小我的视频文件大小以便存储在我的服务器上吗?
试试这个压缩视频的答案。根据jojaba's answer:
If you are wanting to compress the video for remote sharing and to
keep the original quality for local storage on the iPhone, you should
look into AVAssetExportSession or AVAssetWriter.
Compress Video Without Low Quality
虽然这种方法是按照 Objective-C。
您还应该考虑阅读 iOS 如何管理 Assets。
//use SDAVAssetExportSession library with sprcifica bitrate as per requirement
// video file size ~10-15 MB apporox
func aVodzLatestVideoCompressor(inputURL: URL, aOutputURL: URL, aStartTime:Float, aEndTime:Float) {
let startTime = CMTime(seconds: Double(aStartTime), preferredTimescale: 1000)
let endTime = CMTime(seconds: Double(aEndTime), preferredTimescale: 1000)
let timeRange = CMTimeRange(start: startTime, end: endTime)
let anAsset = AVURLAsset(url: inputURL, options: nil)
guard let videoTrack = anAsset.tracks(withMediaType: AVMediaType.video).first else { return }
var aQuality:Float = 0.0
var duration = anAsset.duration
let totalSeconds = Int(CMTimeGetSeconds(duration))
print("duration -\(duration) - totalSeconds -\(totalSeconds)")
var bitrate = min(aQuality, videoTrack.estimatedDataRate)
let landscap = self.isLandScapVideo(afileURL: inputURL )
var originalWidth = videoTrack.naturalSize.width
var originalHeight = videoTrack.naturalSize.height
print("originalWidth -\(originalWidth) originalHeight- \(originalHeight) ")
while (originalWidth >= 1920 || originalHeight >= 1920) {
originalWidth = originalWidth / 2
originalHeight = originalHeight / 2
}
var setWidth = Int(originalWidth)
var setlHeight = Int(originalHeight)
if sizeVideo < 10.0 {
// COMPRESS_QUALITY_HIGH:
setWidth = Int(originalWidth)
setlHeight = Int(originalHeight)
aQuality = Float(setWidth * setlHeight * 20)
bitrate = min(aQuality, videoTrack.estimatedDataRate)
}else if sizeVideo < 20.0 {
//COMPRESS_QUALITY_MEDIUM:
if totalSeconds > 35{
setWidth = Int(originalWidth / 2.7)
setlHeight = Int(originalHeight / 2.7)
}else if totalSeconds > 25 {
setWidth = Int(originalWidth / 2.3)
setlHeight = Int(originalHeight / 2.3)
}else{
setWidth = Int(originalWidth / 2.0)
setlHeight = Int(originalHeight / 2.0)
}
aQuality = Float(setWidth * setlHeight * 10)
bitrate = min(aQuality, videoTrack.estimatedDataRate)
}else if sizeVideo < 30.0 {
//COMPRESS_QUALITY_MEDIUM:
if totalSeconds > 35{
setWidth = Int(originalWidth / 3)
setlHeight = Int(originalHeight / 3)
}else if totalSeconds > 20 {
setWidth = Int(originalWidth / 2.5)
setlHeight = Int(originalHeight / 2.5)
}else{
setWidth = Int(originalWidth / 2.0)
setlHeight = Int(originalHeight / 2.0)
}
aQuality = Float(setWidth * setlHeight * 10)
bitrate = min(aQuality, videoTrack.estimatedDataRate)
}else{
if totalSeconds > 35{
setWidth = Int(originalWidth / 3.0)
setlHeight = Int(originalHeight / 3.0)
}else if totalSeconds > 25 {
setWidth = Int(originalWidth / 2.5)
setlHeight = Int(originalHeight / 2.5)
}else{
setWidth = Int(originalWidth / 2.0)
setlHeight = Int(originalHeight / 2.0)
}
aQuality = Float(setWidth * setlHeight * 10)
bitrate = min(aQuality, videoTrack.estimatedDataRate)
}
print("aQuality")
print(Float(aQuality))
print("bitrate")
print(Float(bitrate))
let encoder = SDAVAssetExportSession(asset: anAsset)
encoder?.shouldOptimizeForNetworkUse = true
encoder?.timeRange = timeRange
encoder?.outputFileType = AVFileType.mp4.rawValue
encoder?.outputURL = aOutputURL
//960 X 540 , 1280 * 720 , 1920*1080 // size reduce parameter
encoder?.videoSettings = [
AVVideoCodecKey: AVVideoCodecType.h264,
AVVideoWidthKey: landscap ? NSNumber(value:1280) : NSNumber(value:720) ,
AVVideoHeightKey: landscap ? NSNumber(value:720) : NSNumber(value:1280),
AVVideoCompressionPropertiesKey: [
AVVideoAverageBitRateKey: NSNumber(value: bitrate),
AVVideoProfileLevelKey: AVVideoProfileLevelH264High40
]
]
encoder?.audioSettings = [
AVFormatIDKey: NSNumber(value: kAudioFormatMPEG4AAC),
AVNumberOfChannelsKey: NSNumber(value: 2),
AVSampleRateKey: NSNumber(value: 44100),
AVEncoderBitRateKey: NSNumber(value: 128000)
]
encoder?.exportAsynchronously(completionHandler: {
if encoder?.status == .completed {
print("Video export succeeded")
DispatchQueue.main.async {
appDelegate.hideLoader()
//NotificationCenter.default.post(name: Notification.Name("getMediaEffect"), object: "3")
//self.sendCompletion?(UIImage(), aOutputURL)
let text = "Original video- \(inputURL.verboseFileSizeInMB()) \n and Compressed video \(aOutputURL.verboseFileSizeInMB()) "
let alertController = UIAlertController.init(title: "Compressed!!", message: text , preferredStyle: .alert)
alertController.addAction(UIAlertAction.init(title: "share to server!", style: .default, handler: { (action) in
// Completion block
NotificationCenter.default.post(name: Notification.Name("getMediaEffect"), object: "3")
self.sendCompletion?(UIImage(), aOutputURL)
}))
alertController.addAction(UIAlertAction.init(title: "Save", style: .default, handler: { (action) in
// Completion block
DispatchQueue.main.async {
appDelegate.hideLoader()
if let videoURL = aOutputURL as? URL{
self.shareVideo(aUrl:videoURL )
}
}
}))
alertController.addAction(UIAlertAction.init(title: "cancel!", style: .default, handler: { (action) in
}))
self.present(alertController, animated: true, completion: nil)
}
} else if encoder?.status == .cancelled {
print("Video export cancelled")
DispatchQueue.main.async {
appDelegate.hideLoader()
self.view.makeToast("error_something_went_wrong".localized)
}
} else {
print("Video export failed with error: \(encoder!.error.localizedDescription) ")
DispatchQueue.main.async {
appDelegate.hideLoader()
self.view.makeToast("error_something_went_wrong".localized)
}
}
})
}
func isLandScapVideo(afileURL: URL) -> Bool{
let resolution = self.resolutionForLocalVideo(url: afileURL)
guard let width = resolution?.width, let height = resolution?.height else {
return false
}
if abs(width) > abs(height){
//landscap
return true
}else{
//potrait
return false
}
}
extension URL {
func verboseFileSizeInMB() -> Float{
let p = self.path
let attr = try? FileManager.default.attributesOfItem(atPath: p)
if let attr = attr {
let fileSize = Float(attr[FileAttributeKey.size] as! UInt64) / (1024.0 * 1024.0)
print(String(format: "FILE SIZE: %.2f MB", fileSize))
return fileSize
} else {
return Float.zero
}
}
}
//Below update if any issue in library at SDAVAssetExportSession library changes below at m file:(changes as per your requirement)
—— CGAffineTransform matrix = CGAffineTransformMakeTranslation(transx / xratio, transy / yratio - transform.ty);
——//fix Orientation - 1
UIImageOrientation videoAssetOrientation = UIImageOrientationUp;
BOOL isVideoAssetPortrait = NO;
CGAffineTransform videoTransform = videoTrack.preferredTransform;
if (videoTransform.a == 0 && videoTransform.b == 1.0 && videoTransform.c == -1.0 && videoTransform.d == 0) {
videoAssetOrientation = UIImageOrientationRight;
isVideoAssetPortrait = YES;
}
if (videoTransform.a == 0 && videoTransform.b == -1.0 && videoTransform.c == 1.0 && videoTransform.d == 0) {
videoAssetOrientation = UIImageOrientationLeft;
isVideoAssetPortrait = YES;
}
if (videoTransform.a == 1.0 && videoTransform.b == 0 && videoTransform.c == 0 && videoTransform.d == 1.0) {
videoAssetOrientation = UIImageOrientationUp;
}
if (videoTransform.a == -1.0 && videoTransform.b == 0 && videoTransform.c == 0 && videoTransform.d == -1.0) {
videoAssetOrientation = UIImageOrientationDown;
}
// [passThroughLayer setTransform:transform atTime:kCMTimeZero];
if ((videoAssetOrientation = UIImageOrientationDown) || (videoAssetOrientation = UIImageOrientationLeft)){
[passThroughLayer setTransform:videoTrack.preferredTransform atTime:kCMTimeZero];
}else{
[passThroughLayer setTransform:transform atTime:kCMTimeZero];
}
我正在使用 UIImagePickerController 从我的 swift iOS 应用中挑选视频。我正在保存此 URL,现在想将其转换为数据以发送到我的服务器进行存储,使用:
let messageVideoData = NSData(contentsOfURL: chosenVideoURL)
问题是文件太大了。对于在我的 iPhone 6s 上拍摄的 7 秒视频,分辨率为 1280、720,帧速率为 30,文件大小超过 4 MB。我注意到用 whatsapp 和其他聊天应用程序发送的相同图像减少到几百 KB。
减小外部存储文件大小的最佳方法是什么?该视频主要针对手机,因此可以将分辨率降低到 800 或更低。
我尝试将 UIImagePickerController 质量设置为:
picker.videoQuality = UIImagePickerControllerQualityType.Type640x480
但这仅将文件大小减小到 3.5 MB。
使用:
picker.videoQuality = UIImagePickerControllerQualityType.TypeLow
将分辨率降低到远低于理想值的值。
我应该采取另一种方法来减小我的视频文件大小以便存储在我的服务器上吗?
试试这个压缩视频的答案。根据jojaba's answer:
If you are wanting to compress the video for remote sharing and to keep the original quality for local storage on the iPhone, you should look into AVAssetExportSession or AVAssetWriter.
Compress Video Without Low Quality
虽然这种方法是按照 Objective-C。
您还应该考虑阅读 iOS 如何管理 Assets。
//use SDAVAssetExportSession library with sprcifica bitrate as per requirement
// video file size ~10-15 MB apporox
func aVodzLatestVideoCompressor(inputURL: URL, aOutputURL: URL, aStartTime:Float, aEndTime:Float) {
let startTime = CMTime(seconds: Double(aStartTime), preferredTimescale: 1000)
let endTime = CMTime(seconds: Double(aEndTime), preferredTimescale: 1000)
let timeRange = CMTimeRange(start: startTime, end: endTime)
let anAsset = AVURLAsset(url: inputURL, options: nil)
guard let videoTrack = anAsset.tracks(withMediaType: AVMediaType.video).first else { return }
var aQuality:Float = 0.0
var duration = anAsset.duration
let totalSeconds = Int(CMTimeGetSeconds(duration))
print("duration -\(duration) - totalSeconds -\(totalSeconds)")
var bitrate = min(aQuality, videoTrack.estimatedDataRate)
let landscap = self.isLandScapVideo(afileURL: inputURL )
var originalWidth = videoTrack.naturalSize.width
var originalHeight = videoTrack.naturalSize.height
print("originalWidth -\(originalWidth) originalHeight- \(originalHeight) ")
while (originalWidth >= 1920 || originalHeight >= 1920) {
originalWidth = originalWidth / 2
originalHeight = originalHeight / 2
}
var setWidth = Int(originalWidth)
var setlHeight = Int(originalHeight)
if sizeVideo < 10.0 {
// COMPRESS_QUALITY_HIGH:
setWidth = Int(originalWidth)
setlHeight = Int(originalHeight)
aQuality = Float(setWidth * setlHeight * 20)
bitrate = min(aQuality, videoTrack.estimatedDataRate)
}else if sizeVideo < 20.0 {
//COMPRESS_QUALITY_MEDIUM:
if totalSeconds > 35{
setWidth = Int(originalWidth / 2.7)
setlHeight = Int(originalHeight / 2.7)
}else if totalSeconds > 25 {
setWidth = Int(originalWidth / 2.3)
setlHeight = Int(originalHeight / 2.3)
}else{
setWidth = Int(originalWidth / 2.0)
setlHeight = Int(originalHeight / 2.0)
}
aQuality = Float(setWidth * setlHeight * 10)
bitrate = min(aQuality, videoTrack.estimatedDataRate)
}else if sizeVideo < 30.0 {
//COMPRESS_QUALITY_MEDIUM:
if totalSeconds > 35{
setWidth = Int(originalWidth / 3)
setlHeight = Int(originalHeight / 3)
}else if totalSeconds > 20 {
setWidth = Int(originalWidth / 2.5)
setlHeight = Int(originalHeight / 2.5)
}else{
setWidth = Int(originalWidth / 2.0)
setlHeight = Int(originalHeight / 2.0)
}
aQuality = Float(setWidth * setlHeight * 10)
bitrate = min(aQuality, videoTrack.estimatedDataRate)
}else{
if totalSeconds > 35{
setWidth = Int(originalWidth / 3.0)
setlHeight = Int(originalHeight / 3.0)
}else if totalSeconds > 25 {
setWidth = Int(originalWidth / 2.5)
setlHeight = Int(originalHeight / 2.5)
}else{
setWidth = Int(originalWidth / 2.0)
setlHeight = Int(originalHeight / 2.0)
}
aQuality = Float(setWidth * setlHeight * 10)
bitrate = min(aQuality, videoTrack.estimatedDataRate)
}
print("aQuality")
print(Float(aQuality))
print("bitrate")
print(Float(bitrate))
let encoder = SDAVAssetExportSession(asset: anAsset)
encoder?.shouldOptimizeForNetworkUse = true
encoder?.timeRange = timeRange
encoder?.outputFileType = AVFileType.mp4.rawValue
encoder?.outputURL = aOutputURL
//960 X 540 , 1280 * 720 , 1920*1080 // size reduce parameter
encoder?.videoSettings = [
AVVideoCodecKey: AVVideoCodecType.h264,
AVVideoWidthKey: landscap ? NSNumber(value:1280) : NSNumber(value:720) ,
AVVideoHeightKey: landscap ? NSNumber(value:720) : NSNumber(value:1280),
AVVideoCompressionPropertiesKey: [
AVVideoAverageBitRateKey: NSNumber(value: bitrate),
AVVideoProfileLevelKey: AVVideoProfileLevelH264High40
]
]
encoder?.audioSettings = [
AVFormatIDKey: NSNumber(value: kAudioFormatMPEG4AAC),
AVNumberOfChannelsKey: NSNumber(value: 2),
AVSampleRateKey: NSNumber(value: 44100),
AVEncoderBitRateKey: NSNumber(value: 128000)
]
encoder?.exportAsynchronously(completionHandler: {
if encoder?.status == .completed {
print("Video export succeeded")
DispatchQueue.main.async {
appDelegate.hideLoader()
//NotificationCenter.default.post(name: Notification.Name("getMediaEffect"), object: "3")
//self.sendCompletion?(UIImage(), aOutputURL)
let text = "Original video- \(inputURL.verboseFileSizeInMB()) \n and Compressed video \(aOutputURL.verboseFileSizeInMB()) "
let alertController = UIAlertController.init(title: "Compressed!!", message: text , preferredStyle: .alert)
alertController.addAction(UIAlertAction.init(title: "share to server!", style: .default, handler: { (action) in
// Completion block
NotificationCenter.default.post(name: Notification.Name("getMediaEffect"), object: "3")
self.sendCompletion?(UIImage(), aOutputURL)
}))
alertController.addAction(UIAlertAction.init(title: "Save", style: .default, handler: { (action) in
// Completion block
DispatchQueue.main.async {
appDelegate.hideLoader()
if let videoURL = aOutputURL as? URL{
self.shareVideo(aUrl:videoURL )
}
}
}))
alertController.addAction(UIAlertAction.init(title: "cancel!", style: .default, handler: { (action) in
}))
self.present(alertController, animated: true, completion: nil)
}
} else if encoder?.status == .cancelled {
print("Video export cancelled")
DispatchQueue.main.async {
appDelegate.hideLoader()
self.view.makeToast("error_something_went_wrong".localized)
}
} else {
print("Video export failed with error: \(encoder!.error.localizedDescription) ")
DispatchQueue.main.async {
appDelegate.hideLoader()
self.view.makeToast("error_something_went_wrong".localized)
}
}
})
}
func isLandScapVideo(afileURL: URL) -> Bool{
let resolution = self.resolutionForLocalVideo(url: afileURL)
guard let width = resolution?.width, let height = resolution?.height else {
return false
}
if abs(width) > abs(height){
//landscap
return true
}else{
//potrait
return false
}
}
extension URL {
func verboseFileSizeInMB() -> Float{
let p = self.path
let attr = try? FileManager.default.attributesOfItem(atPath: p)
if let attr = attr {
let fileSize = Float(attr[FileAttributeKey.size] as! UInt64) / (1024.0 * 1024.0)
print(String(format: "FILE SIZE: %.2f MB", fileSize))
return fileSize
} else {
return Float.zero
}
}
}
//Below update if any issue in library at SDAVAssetExportSession library changes below at m file:(changes as per your requirement)
—— CGAffineTransform matrix = CGAffineTransformMakeTranslation(transx / xratio, transy / yratio - transform.ty);
——//fix Orientation - 1
UIImageOrientation videoAssetOrientation = UIImageOrientationUp;
BOOL isVideoAssetPortrait = NO;
CGAffineTransform videoTransform = videoTrack.preferredTransform;
if (videoTransform.a == 0 && videoTransform.b == 1.0 && videoTransform.c == -1.0 && videoTransform.d == 0) {
videoAssetOrientation = UIImageOrientationRight;
isVideoAssetPortrait = YES;
}
if (videoTransform.a == 0 && videoTransform.b == -1.0 && videoTransform.c == 1.0 && videoTransform.d == 0) {
videoAssetOrientation = UIImageOrientationLeft;
isVideoAssetPortrait = YES;
}
if (videoTransform.a == 1.0 && videoTransform.b == 0 && videoTransform.c == 0 && videoTransform.d == 1.0) {
videoAssetOrientation = UIImageOrientationUp;
}
if (videoTransform.a == -1.0 && videoTransform.b == 0 && videoTransform.c == 0 && videoTransform.d == -1.0) {
videoAssetOrientation = UIImageOrientationDown;
}
// [passThroughLayer setTransform:transform atTime:kCMTimeZero];
if ((videoAssetOrientation = UIImageOrientationDown) || (videoAssetOrientation = UIImageOrientationLeft)){
[passThroughLayer setTransform:videoTrack.preferredTransform atTime:kCMTimeZero];
}else{
[passThroughLayer setTransform:transform atTime:kCMTimeZero];
}