Swift 5和高质量的代码
以下是遵循此链接中的代码的方法。链接的问题是它只适用于.mov
文件输出,如果你想输出一个.mp4
文件,它会崩溃。下面的代码可让您获得.mp4
输出。它经过了尝试、测试和工作。例如,原本 27mb 的 15 秒视频减少到 2mb。如果您想要更好的质量,请提高bitrate
. 我将其设置为1250000。
c+p 这段代码:
import AVFoundation
// add these properties
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) // *** you can change this number to increase/decrease the quality. The more you increase, the better the video quality but the the compressed file size will also increase
// compression function, it returns a .mp4 but you can change it to .mov inside the do try block towards the middle. Change assetWriter = try AVAssetWriter ... AVFileType.mp4 to AVFileType.mov
func compressFile(_ urlToCompress: URL, completion:@escaping (URL)->Void) {
var audioFinished = false
var videoFinished = false
let asset = AVAsset(url: urlToCompress)
//create asset reader
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")
// show user error message/alert
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")
// show user error message/alert
return
}
}
if reader.canAdd(assetReaderVideoOutput) {
reader.add(assetReaderVideoOutput)
} else {
print("Couldn't add video output reader")
// show user error message/alert
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")
// show user error message/alert
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) {
// request data here
while(videoInput.isReadyForMoreMediaData) {
if let cmSampleBuffer = assetReaderVideoOutput.copyNextSampleBuffer() {
videoInput.append(cmSampleBuffer)
} else {
videoInput.markAsFinished()
DispatchQueue.main.async {
videoFinished = true
closeWriter()
}
break;
}
}
}
}
如果您正在压缩URL
. 在回调中返回压缩URL:
@IBAction func buttonTapped(sender: UIButton) {
// show activity indicator
let videoURL = URL(string: "...")
compressFile(videoURL) { (compressedURL) in
// remove activity indicator
// do something with the compressedURL such as sending to Firebase or playing it in a player on the *main queue*
}
}
仅供参考,我注意到音频会减慢速度,您还可以在后台任务上尝试此操作,看看它是否运行得更快。如果您在函数本身中添加了警报之类的东西compressFile
,则必须在 mainQueue 上显示它,否则应用程序将崩溃。
DispatchQueue.global(qos: .background).async { [weak self] in
self?.compressFile(videoURL) { (compressedURL) in
DispatchQueue.main.async { [weak self] in
// also remove activity indicator on mainQueue in addition to whatever is inside the function itself that needs to be updated on the mainQueue
}
}
}
如果您正在压缩混合组合,以下是如何做到这一点。您将需要使用 an AVMutableComposition
、 anAVAssetExportSession
和compressFile(:completion:)
上面的函数:
@IBAction func buttonTapped(sender: UIButton) {
// show activity indicator
let mixComposition = AVMutableComposition()
// code to create mix ...
// create a local file
let tempDir = NSTemporaryDirectory()
let dirPath = "\(tempDir)/videos_\(UUID().uuidString).mp4"
let outputFileURL = URL(fileURLWithPath: dirPath)
removeUrlFromFileManager(outputFileURL) // check to see if the file already exists, if it does remove it, code is at the bottom of the answer
createAssetExportSession(mixComposition, outputFileURL)
}
// here is the AssetExportSession function with the compressFile(:completion:) inside the callback
func createAssetExportSession(_ mixComposition: AVMutableComposition, _ outputFileURL: URL) {
// *** If your video/url doesn't have sound (not mute but literally no sound, my iPhone's mic was broken when I recorded the video), change this to use AVAssetExportPresetPassthrough instead of HighestQulity. When my video didn't have sound the exporter.status kept returning .failed *** You can check for sound using https://stackoverflow.com/a/64733623/4833705
guard let exporter = AVAssetExportSession(asset: mixComposition, presetName: AVAssetExportPresetHighestQuality) else {
// alert user there is a problem
return
}
exporter.outputURL = outputFileURL
exporter.outputFileType = AVFileType.mp4
exporter.shouldOptimizeForNetworkUse = true
exporter.exportAsynchronously {
switch exporter.status {
case .completed:
print("completed")
// view the AssetExportSession file size using HighestQuality which will be very high
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
// remove activity indicator
// alert user there is a problem
}
return
case .cancelled:
print("cancelled", exporter.error as Any)
DispatchQueue.main.async { [weak self] in
// remove activity indicator
// alert user there is a problem
}
return
default:
print("complete")
}
guard let exporterOutputURL = exporter.outputURL else {
// alert user there is a problem
return
}
DispatchQueue.main.async { [weak self] in
self?.compressFile(exporterOutputURL) { (compressedURL) in
// remove activity indicator
// do something with the compressedURL such as sending to Firebase or playing it in a player on the *main queue*
}
}
}
}
确保在完成后从文件系统中删除压缩 URL,例如在关闭 vc 之前
func dismissVC() {
removeUrlFromFileManager(compressedURL)
// dismiss vc ...
}
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)")
}
}
}
}