使用 Swift 5 编写高质量代码的方法:
以下是从此链接中获取的代码。该链接存在的问题是,它只适用于.mov
文件输出,如果您想输出.mp4
文件,则会崩溃。下面的代码可以让您获得.mp4
输出。它经过尝试、测试并且可行。例如,一个原始大小为27mb的15秒视频可以缩小到2mb。如果您想要更好的质量,请提高bitrate
。我已将其设置为1250000。
复制并粘贴以下代码:
import AVFoundation
var assetWriter: AVAssetWriter!
var assetWriterVideoInput: AVAssetWriterInput!
var audioMicInput: AVAssetWriterInput!
var videoURL: URL!
var audioAppInput: AVAssetWriterInput!
var channelLayout = AudioChannelLayout()
var assetReader: AVAssetReader?
let bitrate: NSNumber = NSNumber(value: 1250000)
func compressFile(_ urlToCompress: URL, completion:@escaping (URL)->Void) {
var audioFinished = false
var videoFinished = false
let asset = AVAsset(url: urlToCompress)
do {
assetReader = try AVAssetReader(asset: asset)
} catch {
assetReader = nil
}
guard let reader = assetReader else {
print("Could not iniitalize asset reader probably failed its try catch")
return
}
guard let videoTrack = asset.tracks(withMediaType: AVMediaType.video).first else { return }
let videoReaderSettings: [String:Any] = [kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32ARGB]
let assetReaderVideoOutput = AVAssetReaderTrackOutput(track: videoTrack, outputSettings: videoReaderSettings)
var assetReaderAudioOutput: AVAssetReaderTrackOutput?
if let audioTrack = asset.tracks(withMediaType: AVMediaType.audio).first {
let audioReaderSettings: [String : Any] = [
AVFormatIDKey: kAudioFormatLinearPCM,
AVSampleRateKey: 44100,
AVNumberOfChannelsKey: 2
]
assetReaderAudioOutput = AVAssetReaderTrackOutput(track: audioTrack, outputSettings: audioReaderSettings)
if reader.canAdd(assetReaderAudioOutput!) {
reader.add(assetReaderAudioOutput!)
} else {
print("Couldn't add audio output reader")
return
}
}
if reader.canAdd(assetReaderVideoOutput) {
reader.add(assetReaderVideoOutput)
} else {
print("Couldn't add video output reader")
return
}
let videoSettings:[String:Any] = [
AVVideoCompressionPropertiesKey: [AVVideoAverageBitRateKey: self.bitrate],
AVVideoCodecKey: AVVideoCodecType.h264,
AVVideoHeightKey: videoTrack.naturalSize.height,
AVVideoWidthKey: videoTrack.naturalSize.width,
AVVideoScalingModeKey: AVVideoScalingModeResizeAspectFill
]
let audioSettings: [String:Any] = [AVFormatIDKey : kAudioFormatMPEG4AAC,
AVNumberOfChannelsKey : 2,
AVSampleRateKey : 44100.0,
AVEncoderBitRateKey: 128000
]
let audioInput = AVAssetWriterInput(mediaType: AVMediaType.audio, outputSettings: audioSettings)
let videoInput = AVAssetWriterInput(mediaType: AVMediaType.video, outputSettings: videoSettings)
videoInput.transform = videoTrack.preferredTransform
let videoInputQueue = DispatchQueue(label: "videoQueue")
let audioInputQueue = DispatchQueue(label: "audioQueue")
do {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"
let date = Date()
let tempDir = NSTemporaryDirectory()
let outputPath = "\(tempDir)/\(formatter.string(from: date)).mp4"
let outputURL = URL(fileURLWithPath: outputPath)
assetWriter = try AVAssetWriter(outputURL: outputURL, fileType: AVFileType.mp4)
} catch {
assetWriter = nil
}
guard let writer = assetWriter else {
print("assetWriter was nil")
return
}
writer.shouldOptimizeForNetworkUse = true
writer.add(videoInput)
writer.add(audioInput)
writer.startWriting()
reader.startReading()
writer.startSession(atSourceTime: CMTime.zero)
let closeWriter:()->Void = {
if (audioFinished && videoFinished) {
self.assetWriter?.finishWriting(completionHandler: { [weak self] in
if let assetWriter = self?.assetWriter {
do {
let data = try Data(contentsOf: assetWriter.outputURL)
print("compressFile -file size after compression: \(Double(data.count / 1048576)) mb")
} catch let err as NSError {
print("compressFile Error: \(err.localizedDescription)")
}
}
if let safeSelf = self, let assetWriter = safeSelf.assetWriter {
completion(assetWriter.outputURL)
}
})
self.assetReader?.cancelReading()
}
}
audioInput.requestMediaDataWhenReady(on: audioInputQueue) {
while(audioInput.isReadyForMoreMediaData) {
if let cmSampleBuffer = assetReaderAudioOutput?.copyNextSampleBuffer() {
audioInput.append(cmSampleBuffer)
} else {
audioInput.markAsFinished()
DispatchQueue.main.async {
audioFinished = true
closeWriter()
}
break;
}
}
}
videoInput.requestMediaDataWhenReady(on: videoInputQueue) {
while(videoInput.isReadyForMoreMediaData) {
if let cmSampleBuffer = assetReaderVideoOutput.copyNextSampleBuffer() {
videoInput.append(cmSampleBuffer)
} else {
videoInput.markAsFinished()
DispatchQueue.main.async {
videoFinished = true
closeWriter()
}
break;
}
}
}
}
如果您要压缩一个URL
,以下是如何使用它。在回调函数中返回compressedURL:
@IBAction func buttonTapped(sender: UIButton) {
let videoURL = URL(string: "...")
compressFile(videoURL) { (compressedURL) in
}
}
提醒一下,我注意到音频会拖慢速度,你可以尝试在后台任务中运行它,看看是否更快。如果你在compressFile
函数内部添加任何警报,你必须将其显示在主队列上,否则应用程序将崩溃。
DispatchQueue.global(qos: .background).async { [weak self] in
self?.compressFile(videoURL) { (compressedURL) in
DispatchQueue.main.async { [weak self] in
}
}
}
如果您要压缩混合组合,请按照以下步骤操作。您需要使用AVMutableComposition
,AVAssetExportSession
和上面的compressFile(:completion:)
函数:
@IBAction func buttonTapped(sender: UIButton) {
let mixComposition = AVMutableComposition()
let tempDir = NSTemporaryDirectory()
let dirPath = "\(tempDir)/videos_\(UUID().uuidString).mp4"
let outputFileURL = URL(fileURLWithPath: dirPath)
removeUrlFromFileManager(outputFileURL)
createAssetExportSession(mixComposition, outputFileURL)
}
func createAssetExportSession(_ mixComposition: AVMutableComposition, _ outputFileURL: URL) {
guard let exporter = AVAssetExportSession(asset: mixComposition, presetName: AVAssetExportPresetHighestQuality) else {
return
}
exporter.outputURL = outputFileURL
exporter.outputFileType = AVFileType.mp4
exporter.shouldOptimizeForNetworkUse = true
exporter.exportAsynchronously {
switch exporter.status {
case .completed:
print("completed")
do {
let data = try Data(contentsOf: outputFileURL)
print("createAssetExportSession -file size: \(Double(data.count / 1048576)) mb")
} catch let err as NSError {
print("createAssetExportSession Error: \(err.localizedDescription)")
}
case .failed:
print("failed:", exporter.error as Any)
DispatchQueue.main.async { [weak self] in
}
return
case .cancelled:
print("cancelled", exporter.error as Any)
DispatchQueue.main.async { [weak self] in
}
return
default:
print("complete")
}
guard let exporterOutputURL = exporter.outputURL else {
return
}
DispatchQueue.main.async { [weak self] in
self?.compressFile(exporterOutputURL) { (compressedURL) in
}
}
}
}
在完成使用压缩URL后,请务必从文件系统中删除它,例如在关闭视图控制器之前。
func dismissVC() {
removeUrlFromFileManager(compressedURL)
}
removeUrlFromFileManager(_ outputFileURL: URL?) {
if let outputFileURL = outputFileURL {
let path = outputFileURL.path
if FileManager.default.fileExists(atPath: path) {
do {
try FileManager.default.removeItem(atPath: path)
print("url SUCCESSFULLY removed: \(outputFileURL)")
} catch {
print("Could not remove file at url: \(outputFileURL)")
}
}
}
}