公司最近有一个需求,需要压缩视频,体积和清晰度都有要求。在网上查了很多资料,最终使用了assetReader和assetWriter,但是网上找到的资料,直接拷贝到项目中,都或多或少有些问题,或少了一些参数,所以我把自己这些天查到的,并更改好,能顺利压缩的代码写出来,记录下,也方便大家查看。但这些代码只是适用于我自己的项目,如果大家使用有问题的话,可以留言或者自己网上去寻找解决方法。

    之前压缩视频,我使用的是苹果提供的一个基于AVFoundation框架的类,AVAssetExportSession,用它来进行视频压缩和转码,使用也是比较方便的,只需设置输出之类和格式等参数。如下图所示,设置比较简单:

func saveVideo(from videoURL: URL,

to videoFinalPath: URL,

handler: ((URL?) -> Void)? = nil) {

let asset = AVAsset(url: videoURL)

if (deleteIfExists(path: videoFinalPath)) {

guard let exporter = AVAssetExportSession(asset: asset, presetName: AVAssetExportPreset960x540) else {

handler?(nil)

return

}

exporter.outputURL = videoFinalPath

exporter.outputFileType = .mp4

exporter.exportAsynchronously { () -> Void in

switch exporter.status {

case .failed:

let err = exporter.error

print("failed import video: \(exporter.error)")

handler?(nil)

case .cancelled:

print("cancelled import video: \(exporter.error)")

handler?(nil)

case .completed:

print("completed import video save")

handler?(videoFinalPath)

default:

handler?(nil)

break

}

}

}

}

    其中AVAssetExportPreset960x540就是压缩的质量参数。之前我选择的是AVAssetExportPresetMediumQuality,压缩的体积还是不错的。压缩的视频是我自己拍摄的41秒的视频,70多MB,能压缩到10多MB,但是视频的清晰度就大打折扣。后来我选择了AVAssetExportPreset640x480的压缩参数,压缩出来为18.3MB,但是视频还是不太清晰,但是比之前AVAssetExportPresetMediumQuality的要清晰一点。选 AVAssetExportPreset960x540,压缩出来为27MB,视频这时是比较清晰的,但是体积太大了。参考微信的压缩视频功能,微信中发送视频,默认的压缩出来是13MB多,而且很清晰。所以,上面的参数都达不到这个效果,需要重新找方法去解决。

    之后,我使用了assetReader和assetWriter,这个是可以自定义压缩参数并实现更精细的控制,但使用起来相对复杂一些。在使用的时候需要设置好音视频的参数,如果设置不对,可能压缩失败,直接报错闪退。我反复设置了参数后,终于可以压缩,视频的体积和清晰度都能达到想要的效果,可是这时我发现,压缩出来的视频永远是相当于home键在右侧的方向。比如,我拿手机拍一个视频,手机是竖着的,home键朝下,那么压缩出来的视频,便是会逆时针转90度。查了参数,才知道可以通过AVAssetTrack中的preferredTransform查看视频的方向。默认0度就是手机横着,Home键在右边拍摄的视频。如果手机是竖着,Home键在下方,拍摄的视频,那么他的角度是90度。代码如下:

//获取视频的角度

private func degressFromVideoFileWithURL(videoTrack: AVAssetTrack)->Int {

var degress = 0

let t: CGAffineTransform = videoTrack.preferredTransform

if(t.a == 0 && t.b == 1.0 && t.c == -1.0 && t.d == 0){

// Portrait

degress = 90

}else if(t.a == 0 && t.b == -1.0 && t.c == 1.0 && t.d == 0){

// PortraitUpsideDown

degress = 270

}else if(t.a == 1.0 && t.b == 0 && t.c == 0 && t.d == 1.0){

// LandscapeRight

degress = 0

}else if(t.a == -1.0 && t.b == 0 && t.c == 0 && t.d == -1.0){

// LandscapeLeft

degress = 180

}

return degress

}

    然后,我也找到了解决方法。如果视频的角度不为0,我们在压缩时,直接把视频手动旋转到之前的角度就行了。大家切记,不要再用AVAssetExportSession去旋转方向,网上很多这样的文章,我也试了,转向是成功了,但是之前压缩好的体积,经过AVAssetExportSession再一压缩又变大了……

        现在把已经测试过能顺利运行的代码贴出来,供大家参考:

func exportVideo(from videoURL: URL,

to videoFinalPath: URL,

completeHandler: @escaping (URL) -> ()) {

// 创建音视频输入asset

let asset = AVAsset(url: videoURL)

// 原视频大小,用于测试压缩效果

let oldfileItem = try? FileManager.default.attributesOfItem(atPath: videoURL.path)

print(oldfileItem?[FileAttributeKey.size]) //打印之前视频体积

// 创建音视频Reader和Writer

guard let reader = try? AVAssetReader(asset: asset),

let writer = try? AVAssetWriter.init(outputURL: videoFinalPath, fileType: AVFileType.mp4) else { return}

//视频输出配置

let configVideoOutput: [String : Any] = [kCVPixelBufferPixelFormatTypeKey: NSNumber(value: kCVPixelFormatType_422YpCbCr8)] as! [String: Any]

//音频输出配置

let configAudioOutput: [String : Any] = [AVFormatIDKey: NSNumber(value: kAudioFormatLinearPCM)] as! [String: Any]

let compressionProperties: [String: Any] = [AVVideoAverageBitRateKey: 1600 * 1024, //码率

AVVideoExpectedSourceFrameRateKey: 25, //帧率

AVVideoProfileLevelKey: AVVideoProfileLevelH264HighAutoLevel]

let videoCodeec: String = AVVideoCodecType.h264.rawValue //视频编码

var videoSettings: [String: Any] = [AVVideoCodecKey: videoCodeec, //视频编码

AVVideoWidthKey: 960, //视频宽(必须填写正确,否则压缩后有问题)

AVVideoHeightKey: 540, //视频高(必须填写正确,否则压缩后有问题)

AVVideoCompressionPropertiesKey: compressionProperties,

AVVideoScalingModeKey: AVVideoScalingModeResizeAspectFill] //设置视频缩放方式

let stereoChannelLayout: AudioChannelLayout = AudioChannelLayout(mChannelLayoutTag: kAudioChannelLayoutTag_Stereo,

mChannelBitmap: AudioChannelBitmap.init(rawValue: 0),

mNumberChannelDescriptions: 0,

mChannelDescriptions: AudioChannelDescription())

let channelLayoutAsData: NSData = NSData(bytes: (stereoChannelLayout as? UnsafeRawPointer), length: MemoryLayout.size(ofValue: AudioChannelLayout.self))

let audioSettings: [String: Any] = [AVFormatIDKey : kAudioFormatMPEG4AAC,

AVEncoderBitRateKey : 96000, // 码率

AVSampleRateKey : 44100, // 采样率

AVChannelLayoutKey : channelLayoutAsData,

AVNumberOfChannelsKey : 2]

// video part

guard let videoTrack: AVAssetTrack = (asset.tracks(withMediaType: .video)).first else {return}

//获取原视频的角度

let degree = self.degressFromVideoFileWithURL(videoTrack: videoTrack)

//获取原视频的宽高,如果是手机拍摄,一般是宽大,高小,如果是手机自带录屏,那么是高大,宽小

let naturalSize = videoTrack.naturalSize

if naturalSize.width < naturalSize.height {

videoSettings[AVVideoWidthKey] = 540

videoSettings[AVVideoHeightKey] = 960

}

let videoOutput: AVAssetReaderTrackOutput = AVAssetReaderTrackOutput(track: videoTrack, outputSettings: configVideoOutput)

let videoInput: AVAssetWriterInput = AVAssetWriterInput(mediaType: .video, outputSettings: videoSettings)

//视频写入的旋转(这句很重要)

if let transform = self.getAffineTransform(degree: degree, videoTrack: videoTrack) {

videoInput.transform = transform

}

if reader.canAdd(videoOutput) {

reader.add(videoOutput)

}

if writer.canAdd(videoInput) {

writer.add(videoInput)

}

// audio part

guard let audioTrack: AVAssetTrack = (asset.tracks(withMediaType: .audio)).first else {return}

let audioOutput: AVAssetReaderTrackOutput = AVAssetReaderTrackOutput(track: audioTrack, outputSettings: configAudioOutput)

let audioInput: AVAssetWriterInput = AVAssetWriterInput(mediaType: .audio, outputSettings: audioSettings)

if reader.canAdd(audioOutput) {

reader.add(audioOutput)

}

if writer.canAdd(audioInput) {

writer.add(audioInput)

}

// 开始读写

reader.startReading()

writer.startWriting()

writer.startSession(atSourceTime: .zero)

let group = DispatchGroup()

group.enter()

videoInput.requestMediaDataWhenReady(on: DispatchQueue(label: "videoOutQueue"), using: {

var completedOrFailed = false

while (videoInput.isReadyForMoreMediaData) && !completedOrFailed {

let sampleBuffer: CMSampleBuffer? = videoOutput.copyNextSampleBuffer()

if sampleBuffer != nil {//}&& reader.status == .reading {

let result = videoInput.append(sampleBuffer!)

// if (!result) {

// reader.cancelReading()

// break

// }

} else {

completedOrFailed = true

videoInput.markAsFinished()

group.leave()

break

}

}

})

group.enter()

audioInput.requestMediaDataWhenReady(on: DispatchQueue(label: "audioOutQueue"), using: {

var completedOrFailed = false

while (audioInput.isReadyForMoreMediaData) && !completedOrFailed {

let sampleBuffer: CMSampleBuffer? = audioOutput.copyNextSampleBuffer()

if sampleBuffer != nil {//}&& reader.status == .reading {

let result = audioInput.append(sampleBuffer!)

// if (!result) {

// reader.cancelReading()

// break

// }

} else {

completedOrFailed = true

audioInput.markAsFinished()

group.leave()

break

}

}

})

group.notify(queue: DispatchQueue.main) {

if reader.status == .reading {

reader.cancelReading()

}

switch writer.status {

case .writing:

writer.finishWriting(completionHandler: {

DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.3, execute: {

let newfileSize = try? FileManager.default.attributesOfItem(atPath: videoFinalPath.path)

print(newfileSize?[FileAttributeKey.size]) //打印压缩后视频的体积

completeHandler(videoFinalPath)

})

})

case .cancelled:

print("$$$ compress cancelled")

case .failed:

print("$$$ compress failed",writer.error)

case .completed:

print("$$$ compress completed")

case .unknown:

print("$$$ compress unknown")

}

}

}

private func getAffineTransform(degree: Int, videoTrack: AVAssetTrack)-> CGAffineTransform? {

var translateToCenter: CGAffineTransform?

var mixedTransform: CGAffineTransform?

if degree == 90 { //视频旋转90度,home按键在左"

translateToCenter = CGAffineTransform(translationX: videoTrack.naturalSize.height, y: 0.0)

mixedTransform = translateToCenter!.rotated(by: Double.pi / 2)

} else if degree == 180 { //视频旋转180度,home按键在上"

translateToCenter = CGAffineTransform(translationX: videoTrack.naturalSize.width, y: videoTrack.naturalSize.height)

mixedTransform = translateToCenter!.rotated(by: Double.pi)

} else if degree == 270 { //视频旋转270度,home按键在右"

translateToCenter = CGAffineTransform(translationX: 0.0, y: videoTrack.naturalSize.width)

mixedTransform = translateToCenter!.rotated(by: Double.pi / 2 * 3)

}

return mixedTransform

}

    以上便是所有代码,希望对大家有帮助!另外关于压缩视频的参数,有篇文章写得非常详细,链接为:手把手教你iOS自定义视频压缩 - 知乎

    大家可以在视频压缩出来后,参照文章的参数,进行调整,以便达到最佳效果。

参考链接

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。