57

我有一个应用程序,允许用户录制视频,UIImagePickerController然后将其上传到 YouTube。问题是UIImagePickerController创建的视频文件很大,即使视频只有 5 秒长。例如,一个 5 秒长的视频是 16-20 兆字节。我想将视频保持在 540 或 720 质量,但我想减小文件大小。

我一直在尝试使用 AVFoundation 并AVAssetExportSession尝试获得更小的文件大小。我试过以下代码:

AVAsset *video = [AVAsset assetWithURL:videoURL];
AVAssetExportSession *exportSession = [AVAssetExportSession exportSessionWithAsset:video presetName:AVAssetExportPresetPassthrough];
exportSession.shouldOptimizeForNetworkUse = YES;
exportSession.outputFileType = AVFileTypeMPEG4;
exportSession.outputURL = [pathToSavedVideosDirectory URLByAppendingPathComponent:@"vid1.mp4"];
[exportSession exportAsynchronouslyWithCompletionHandler:^{
    NSLog(@"done processing video!");
}];

但这并没有减少文件大小。我知道我正在做的事情是可能的,因为在 Apple 的照片应用程序中,当您选择“在 YouTube 上分享”时,会自动处理视频文件,使其足够小以上传。我想在我的应用程序中做同样的事情。

我怎样才能做到这一点?

4

12 回答 12

68

使用AVCaptureSession并且AVAssetWriter您可以像这样设置压缩设置:

NSDictionary *settings = @{AVVideoCodecKey:AVVideoCodecH264,
                           AVVideoWidthKey:@(video_width),
                           AVVideoHeightKey:@(video_height),
                           AVVideoCompressionPropertiesKey:
                               @{AVVideoAverageBitRateKey:@(desired_bitrate),
                                 AVVideoProfileLevelKey:AVVideoProfileLevelH264Main31, /* Or whatever profile & level you wish to use */
                                 AVVideoMaxKeyFrameIntervalKey:@(desired_keyframe_interval)}};

AVAssetWriterInput* writer_input = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:settings];

编辑:我想如果你坚持使用UIImagePicker来创建电影,你将不得不使用AVAssetReader's copyNextSampleBufferAVAssetWriter's appendSampleBuffer方法来进行转码。

于 2012-08-05T19:23:55.890 回答
22

yourfriendzak 是对的:设置cameraUI.videoQuality = UIImagePickerControllerQualityTypeLow;不是这里的解决方案。解决方案是降低数据速率或比特率,这正是 jgh 所建议的。

我有,三种方法。第一个方法处理UIImagePicker委托方法:

// For responding to the user accepting a newly-captured picture or movie
- (void) imagePickerController: (UIImagePickerController *) picker didFinishPickingMediaWithInfo: (NSDictionary *) info {

// Handle movie capture
NSURL *movieURL = [info objectForKey:
                            UIImagePickerControllerMediaURL];

NSURL *uploadURL = [NSURL fileURLWithPath:[[NSTemporaryDirectory() stringByAppendingPathComponent:[self randomString]] stringByAppendingString:@".mp4"]];

// Compress movie first
[self convertVideoToLowQuailtyWithInputURL:movieURL outputURL:uploadURL];
}

第二种方法将视频转换为较低的比特率,而不是较低的尺寸。

- (void)convertVideoToLowQuailtyWithInputURL:(NSURL*)inputURL
                               outputURL:(NSURL*)outputURL
{
//setup video writer
AVAsset *videoAsset = [[AVURLAsset alloc] initWithURL:inputURL options:nil];

AVAssetTrack *videoTrack = [[videoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];

CGSize videoSize = videoTrack.naturalSize;

NSDictionary *videoWriterCompressionSettings =  [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:1250000], AVVideoAverageBitRateKey, nil];

NSDictionary *videoWriterSettings = [NSDictionary dictionaryWithObjectsAndKeys:AVVideoCodecH264, AVVideoCodecKey, videoWriterCompressionSettings, AVVideoCompressionPropertiesKey, [NSNumber numberWithFloat:videoSize.width], AVVideoWidthKey, [NSNumber numberWithFloat:videoSize.height], AVVideoHeightKey, nil];

AVAssetWriterInput* videoWriterInput = [AVAssetWriterInput
                                         assetWriterInputWithMediaType:AVMediaTypeVideo
                                         outputSettings:videoWriterSettings];

videoWriterInput.expectsMediaDataInRealTime = YES;

videoWriterInput.transform = videoTrack.preferredTransform;

AVAssetWriter *videoWriter = [[AVAssetWriter alloc] initWithURL:outputURL fileType:AVFileTypeQuickTimeMovie error:nil];

[videoWriter addInput:videoWriterInput];

//setup video reader
NSDictionary *videoReaderSettings = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange] forKey:(id)kCVPixelBufferPixelFormatTypeKey];

AVAssetReaderTrackOutput *videoReaderOutput = [[AVAssetReaderTrackOutput alloc] initWithTrack:videoTrack outputSettings:videoReaderSettings];

AVAssetReader *videoReader = [[AVAssetReader alloc] initWithAsset:videoAsset error:nil];

[videoReader addOutput:videoReaderOutput];

//setup audio writer
AVAssetWriterInput* audioWriterInput = [AVAssetWriterInput
                                        assetWriterInputWithMediaType:AVMediaTypeAudio
                                        outputSettings:nil];

audioWriterInput.expectsMediaDataInRealTime = NO;

[videoWriter addInput:audioWriterInput];

//setup audio reader
AVAssetTrack* audioTrack = [[videoAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0];

AVAssetReaderOutput *audioReaderOutput = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:audioTrack outputSettings:nil];

AVAssetReader *audioReader = [AVAssetReader assetReaderWithAsset:videoAsset error:nil];

[audioReader addOutput:audioReaderOutput];    

[videoWriter startWriting];

//start writing from video reader
[videoReader startReading];

[videoWriter startSessionAtSourceTime:kCMTimeZero];

dispatch_queue_t processingQueue = dispatch_queue_create("processingQueue1", NULL);

[videoWriterInput requestMediaDataWhenReadyOnQueue:processingQueue usingBlock:
 ^{

     while ([videoWriterInput isReadyForMoreMediaData]) {

         CMSampleBufferRef sampleBuffer;

         if ([videoReader status] == AVAssetReaderStatusReading &&
             (sampleBuffer = [videoReaderOutput copyNextSampleBuffer])) {

             [videoWriterInput appendSampleBuffer:sampleBuffer];
             CFRelease(sampleBuffer);
         }

         else {

             [videoWriterInput markAsFinished];

             if ([videoReader status] == AVAssetReaderStatusCompleted) {

                 //start writing from audio reader
                 [audioReader startReading];

                 [videoWriter startSessionAtSourceTime:kCMTimeZero];

                 dispatch_queue_t processingQueue = dispatch_queue_create("processingQueue2", NULL);

                 [audioWriterInput requestMediaDataWhenReadyOnQueue:processingQueue usingBlock:^{

                     while (audioWriterInput.readyForMoreMediaData) {

                         CMSampleBufferRef sampleBuffer;

                         if ([audioReader status] == AVAssetReaderStatusReading &&
                             (sampleBuffer = [audioReaderOutput copyNextSampleBuffer])) {

                            [audioWriterInput appendSampleBuffer:sampleBuffer];
                                    CFRelease(sampleBuffer);
                         }

                         else {

                             [audioWriterInput markAsFinished];

                             if ([audioReader status] == AVAssetReaderStatusCompleted) {

                                 [videoWriter finishWritingWithCompletionHandler:^(){
                                     [self sendMovieFileAtURL:outputURL];
                                 }];

                             }
                         }
                     }

                 }
                  ];
             }
         }
     }
 }
 ];
}

成功后,调用第三种方法,sendMovieFileAtURL:将压缩后的视频上传outputURL到服务器。

请注意,我在我的项目中启用了 ARCrelease ,因此如果您的项目中关闭了 ARC ,您将不得不添加一些调用。

于 2013-04-16T10:59:41.490 回答
19

UImagePickerController你有一个类型的videoQuality属性UIImagePickerControllerQualityType,并将应用于录制的电影以及你从库中挑选的那些(在转码阶段发生)。

或者,如果您必须处理不是来自库的现有资产(文件),您可能需要查看这些预设:

AVAssetExportPresetLowQuality
AVAssetExportPresetMediumQuality
AVAssetExportPresetHighestQuality

AVAssetExportPreset640x480
AVAssetExportPreset960x540
AVAssetExportPreset1280x720
AVAssetExportPreset1920x1080

并将其中一个传递给类的初始化程序AVAssetExportSession。恐怕您必须针对您的特定内容使用这些内容,因为没有关于什么是lowmedium质量或将用于640x480或用于1280x720预设的质量的精确描述。文档中唯一有用的信息如下:

导出适用于设备的 QuickTime 文件的预设名称 您可以使用这些导出选项来生成具有适合当前设备的视频大小的 QuickTime .mov 文件。

导出不会将视频从较小的尺寸放大。视频使用 H.264 压缩;音频使用 AAC 压缩

某些设备不支持某些尺寸。

除此之外,我不记得在AVFoundation

我错了,有一种方法可以调整你提到的所有参数,它确实是 AVAssetWriter:如何将 UIImage 数组导出为电影?

顺便说一句,这是一个带有代码示例的类似问题的链接:iPhone:以编程方式压缩录制的视频以共享?

于 2012-08-04T20:38:36.490 回答
14

Erik 的答案在他写它的时候可能是正确的——但现在使用 iOS8 它只是左右崩溃,我自己花了几个小时。

您需要博士学位才​​能使用 AVAssetWriter - 这很重要:https ://developer.apple.com/library/mac/documentation/AudioVideo/Conceptual/AVFoundationPG/Articles/05_Export.html#//apple_ref/doc/uid/ TP40010188-CH9-SW1

有一个很棒的库可以完全按照您的意愿进行操作,它只是 AVAssetExportSession 的替代品,具有更重要的功能,例如更改比特率:https ://github.com/rs/SDAVAssetExportSession

以下是如何使用它:

-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{

  SDAVAssetExportSession *encoder = [SDAVAssetExportSession.alloc initWithAsset:[AVAsset assetWithURL:[info objectForKey:UIImagePickerControllerMediaURL]]];
  NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
  NSString *documentsDirectory = [paths objectAtIndex:0];
  self.myPathDocs =  [documentsDirectory stringByAppendingPathComponent:
                      [NSString stringWithFormat:@"lowerBitRate-%d.mov",arc4random() % 1000]];
  NSURL *url = [NSURL fileURLWithPath:self.myPathDocs];
  encoder.outputURL=url;
  encoder.outputFileType = AVFileTypeMPEG4;
  encoder.shouldOptimizeForNetworkUse = YES;

  encoder.videoSettings = @
  {
  AVVideoCodecKey: AVVideoCodecH264,
  AVVideoCompressionPropertiesKey: @
    {
    AVVideoAverageBitRateKey: @2300000, // Lower bit rate here
    AVVideoProfileLevelKey: AVVideoProfileLevelH264High40,
    },
  };
  encoder.audioSettings = @
  {
  AVFormatIDKey: @(kAudioFormatMPEG4AAC),
  AVNumberOfChannelsKey: @2,
  AVSampleRateKey: @44100,
  AVEncoderBitRateKey: @128000,
  };

  [encoder exportAsynchronouslyWithCompletionHandler:^
  {
    int status = encoder.status;

    if (status == AVAssetExportSessionStatusCompleted)
    {
      AVAssetTrack *videoTrack = nil;
      AVURLAsset *asset = [AVAsset assetWithURL:encoder.outputURL];
      NSArray *videoTracks = [asset tracksWithMediaType:AVMediaTypeVideo];
      videoTrack = [videoTracks objectAtIndex:0];
      float frameRate = [videoTrack nominalFrameRate];
      float bps = [videoTrack estimatedDataRate];
      NSLog(@"Frame rate == %f",frameRate);
      NSLog(@"bps rate == %f",bps/(1024.0 * 1024.0));
      NSLog(@"Video export succeeded");
      // encoder.outputURL <- this is what you want!!
    }
    else if (status == AVAssetExportSessionStatusCancelled)
    {
      NSLog(@"Video export cancelled");
    }
    else
    {
      NSLog(@"Video export failed with error: %@ (%d)", encoder.error.localizedDescription, encoder.error.code);
    }
  }];
}
于 2015-04-29T08:20:47.477 回答
9

Erik Wegener 代码重写为 swift 3:

class func convertVideoToLowQuailtyWithInputURL(inputURL: NSURL, outputURL: NSURL, onDone: @escaping () -> ()) {
            //setup video writer
            let videoAsset = AVURLAsset(url: inputURL as URL, options: nil)
            let videoTrack = videoAsset.tracks(withMediaType: AVMediaTypeVideo)[0]
            let videoSize = videoTrack.naturalSize
            let videoWriterCompressionSettings = [
                AVVideoAverageBitRateKey : Int(125000)
            ]

            let videoWriterSettings:[String : AnyObject] = [
                AVVideoCodecKey : AVVideoCodecH264 as AnyObject,
                AVVideoCompressionPropertiesKey : videoWriterCompressionSettings as AnyObject,
                AVVideoWidthKey : Int(videoSize.width) as AnyObject,
                AVVideoHeightKey : Int(videoSize.height) as AnyObject
            ]

            let videoWriterInput = AVAssetWriterInput(mediaType: AVMediaTypeVideo, outputSettings: videoWriterSettings)
            videoWriterInput.expectsMediaDataInRealTime = true
            videoWriterInput.transform = videoTrack.preferredTransform
            let videoWriter = try! AVAssetWriter(outputURL: outputURL as URL, fileType: AVFileTypeQuickTimeMovie)
            videoWriter.add(videoWriterInput)
            //setup video reader
            let videoReaderSettings:[String : AnyObject] = [
                kCVPixelBufferPixelFormatTypeKey as String: Int(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange) as AnyObject
            ]

            let videoReaderOutput = AVAssetReaderTrackOutput(track: videoTrack, outputSettings: videoReaderSettings)
            let videoReader = try! AVAssetReader(asset: videoAsset)
            videoReader.add(videoReaderOutput)
            //setup audio writer
            let audioWriterInput = AVAssetWriterInput(mediaType: AVMediaTypeAudio, outputSettings: nil)
            audioWriterInput.expectsMediaDataInRealTime = false
            videoWriter.add(audioWriterInput)
            //setup audio reader
            let audioTrack = videoAsset.tracks(withMediaType: AVMediaTypeAudio)[0]
            let audioReaderOutput = AVAssetReaderTrackOutput(track: audioTrack, outputSettings: nil)
            let audioReader = try! AVAssetReader(asset: videoAsset)
            audioReader.add(audioReaderOutput)
            videoWriter.startWriting()





            //start writing from video reader
            videoReader.startReading()
            videoWriter.startSession(atSourceTime: kCMTimeZero)
            let processingQueue = DispatchQueue(label: "processingQueue1")
            videoWriterInput.requestMediaDataWhenReady(on: processingQueue, using: {() -> Void in
                while videoWriterInput.isReadyForMoreMediaData {
                    let sampleBuffer:CMSampleBuffer? = videoReaderOutput.copyNextSampleBuffer();
                    if videoReader.status == .reading && sampleBuffer != nil {
                        videoWriterInput.append(sampleBuffer!)
                    }
                    else {
                        videoWriterInput.markAsFinished()
                        if videoReader.status == .completed {
                            //start writing from audio reader
                            audioReader.startReading()
                            videoWriter.startSession(atSourceTime: kCMTimeZero)
                            let processingQueue = DispatchQueue(label: "processingQueue2")
                            audioWriterInput.requestMediaDataWhenReady(on: processingQueue, using: {() -> Void in
                                while audioWriterInput.isReadyForMoreMediaData {
                                    let sampleBuffer:CMSampleBuffer? = audioReaderOutput.copyNextSampleBuffer()
                                    if audioReader.status == .reading && sampleBuffer != nil {
                                        audioWriterInput.append(sampleBuffer!)
                                    }
                                    else {
                                        audioWriterInput.markAsFinished()
                                        if audioReader.status == .completed {
                                            videoWriter.finishWriting(completionHandler: {() -> Void in
                                                onDone();
                                            })
                                        }
                                    }
                                }
                            })
                        }
                    }
                }
            })
        }
于 2017-04-13T06:39:50.963 回答
9

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、 anAVAssetExportSessioncompressFile(: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)")
            }
        }
    }
}
于 2020-07-12T14:10:41.213 回答
7

当您想要打开UIImagePickerController以下任何一项时,您可以设置视频质量:

UIImagePickerControllerQualityType640x480
UIImagePickerControllerQualityTypeLow
UIImagePickerControllerQualityTypeMedium
UIImagePickerControllerQualityTypeHigh
UIImagePickerControllerQualityTypeIFrame960x540
UIImagePickerControllerQualityTypeIFrame1280x720

打开以下代码时尝试更改质量类型UIImagePickerController

if (([UIImagePickerController isSourceTypeAvailable:
      UIImagePickerControllerSourceTypeCamera] == NO))
    return NO;
UIImagePickerController *cameraUI = [[UIImagePickerController alloc] init];
cameraUI.sourceType = UIImagePickerControllerSourceTypeCamera;
cameraUI.mediaTypes = [[NSArray alloc] initWithObjects: (NSString *) kUTTypeMovie, nil];

cameraUI.allowsEditing = NO;
cameraUI.delegate = self;
cameraUI.videoQuality = UIImagePickerControllerQualityTypeLow;//you can change the quality here
[self presentModalViewController:cameraUI animated:YES]; 
于 2012-08-07T11:53:04.483 回答
4

斯威夫特 4:

func convertVideoToLowQuailtyWithInputURL(inputURL: NSURL, outputURL: NSURL, completion: @escaping (Bool) -> Void) {

    let videoAsset = AVURLAsset(url: inputURL as URL, options: nil)
    let videoTrack = videoAsset.tracks(withMediaType: AVMediaType.video)[0]
    let videoSize = videoTrack.naturalSize
    let videoWriterCompressionSettings = [
        AVVideoAverageBitRateKey : Int(125000)
    ]

    let videoWriterSettings:[String : AnyObject] = [
        AVVideoCodecKey : AVVideoCodecH264 as AnyObject,
        AVVideoCompressionPropertiesKey : videoWriterCompressionSettings as AnyObject,
        AVVideoWidthKey : Int(videoSize.width) as AnyObject,
        AVVideoHeightKey : Int(videoSize.height) as AnyObject
    ]

    let videoWriterInput = AVAssetWriterInput(mediaType: AVMediaType.video, outputSettings: videoWriterSettings)
    videoWriterInput.expectsMediaDataInRealTime = true
    videoWriterInput.transform = videoTrack.preferredTransform
    let videoWriter = try! AVAssetWriter(outputURL: outputURL as URL, fileType: AVFileType.mov)
    videoWriter.add(videoWriterInput)
    //setup video reader
    let videoReaderSettings:[String : AnyObject] = [
        kCVPixelBufferPixelFormatTypeKey as String: Int(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange) as AnyObject
    ]

    let videoReaderOutput = AVAssetReaderTrackOutput(track: videoTrack, outputSettings: videoReaderSettings)
    var videoReader: AVAssetReader!

    do{

        videoReader = try AVAssetReader(asset: videoAsset)
    }
    catch {

        print("video reader error: \(error)")
        completion(false)
    }
    videoReader.add(videoReaderOutput)
    //setup audio writer
    let audioWriterInput = AVAssetWriterInput(mediaType: AVMediaType.audio, outputSettings: nil)
    audioWriterInput.expectsMediaDataInRealTime = false
    videoWriter.add(audioWriterInput)
    //setup audio reader
    let audioTrack = videoAsset.tracks(withMediaType: AVMediaType.audio)[0]
    let audioReaderOutput = AVAssetReaderTrackOutput(track: audioTrack, outputSettings: nil)
    let audioReader = try! AVAssetReader(asset: videoAsset)
    audioReader.add(audioReaderOutput)
    videoWriter.startWriting()

    //start writing from video reader
    videoReader.startReading()
    videoWriter.startSession(atSourceTime: kCMTimeZero)
    let processingQueue = DispatchQueue(label: "processingQueue1")
    videoWriterInput.requestMediaDataWhenReady(on: processingQueue, using: {() -> Void in
        while videoWriterInput.isReadyForMoreMediaData {
            let sampleBuffer:CMSampleBuffer? = videoReaderOutput.copyNextSampleBuffer();
            if videoReader.status == .reading && sampleBuffer != nil {
                videoWriterInput.append(sampleBuffer!)
            }
            else {
                videoWriterInput.markAsFinished()
                if videoReader.status == .completed {
                    //start writing from audio reader
                    audioReader.startReading()
                    videoWriter.startSession(atSourceTime: kCMTimeZero)
                    let processingQueue = DispatchQueue(label: "processingQueue2")
                    audioWriterInput.requestMediaDataWhenReady(on: processingQueue, using: {() -> Void in
                        while audioWriterInput.isReadyForMoreMediaData {
                            let sampleBuffer:CMSampleBuffer? = audioReaderOutput.copyNextSampleBuffer()
                            if audioReader.status == .reading && sampleBuffer != nil {
                                audioWriterInput.append(sampleBuffer!)
                            }
                            else {
                                audioWriterInput.markAsFinished()
                                if audioReader.status == .completed {
                                    videoWriter.finishWriting(completionHandler: {() -> Void in
                                        completion(true)
                                    })
                                }
                            }
                        }
                    })
                }
            }
        }
    })
}
于 2018-09-15T16:45:59.790 回答
3

Erik Wegener 代码重写为 swift:

class func convertVideoToLowQuailtyWithInputURL(inputURL: NSURL, outputURL: NSURL, onDone: () -> ()) {
    //setup video writer
    let videoAsset = AVURLAsset(URL: inputURL, options: nil)
    let videoTrack = videoAsset.tracksWithMediaType(AVMediaTypeVideo)[0]
    let videoSize = videoTrack.naturalSize
    let videoWriterCompressionSettings = [
        AVVideoAverageBitRateKey : Int(125000)
    ]

    let videoWriterSettings:[String : AnyObject] = [
        AVVideoCodecKey : AVVideoCodecH264,
        AVVideoCompressionPropertiesKey : videoWriterCompressionSettings,
        AVVideoWidthKey : Int(videoSize.width),
        AVVideoHeightKey : Int(videoSize.height)
    ]

    let videoWriterInput = AVAssetWriterInput(mediaType: AVMediaTypeVideo, outputSettings: videoWriterSettings)
    videoWriterInput.expectsMediaDataInRealTime = true
    videoWriterInput.transform = videoTrack.preferredTransform
    let videoWriter = try! AVAssetWriter(URL: outputURL, fileType: AVFileTypeQuickTimeMovie)
    videoWriter.addInput(videoWriterInput)
    //setup video reader
    let videoReaderSettings:[String : AnyObject] = [
        kCVPixelBufferPixelFormatTypeKey as String: Int(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange)
    ]

    let videoReaderOutput = AVAssetReaderTrackOutput(track: videoTrack, outputSettings: videoReaderSettings)
    let videoReader = try! AVAssetReader(asset: videoAsset)
    videoReader.addOutput(videoReaderOutput)
    //setup audio writer
    let audioWriterInput = AVAssetWriterInput(mediaType: AVMediaTypeAudio, outputSettings: nil)
    audioWriterInput.expectsMediaDataInRealTime = false
    videoWriter.addInput(audioWriterInput)
    //setup audio reader
    let audioTrack = videoAsset.tracksWithMediaType(AVMediaTypeAudio)[0]
    let audioReaderOutput = AVAssetReaderTrackOutput(track: audioTrack, outputSettings: nil)
    let audioReader = try! AVAssetReader(asset: videoAsset)
    audioReader.addOutput(audioReaderOutput)
    videoWriter.startWriting()





    //start writing from video reader
    videoReader.startReading()
    videoWriter.startSessionAtSourceTime(kCMTimeZero)
    let processingQueue = dispatch_queue_create("processingQueue1", nil)
    videoWriterInput.requestMediaDataWhenReadyOnQueue(processingQueue, usingBlock: {() -> Void in
        while videoWriterInput.readyForMoreMediaData {
            let sampleBuffer:CMSampleBuffer? = videoReaderOutput.copyNextSampleBuffer();
            if videoReader.status == .Reading && sampleBuffer != nil {
                videoWriterInput.appendSampleBuffer(sampleBuffer!)
            }
            else {
                videoWriterInput.markAsFinished()
                if videoReader.status == .Completed {
                    //start writing from audio reader
                    audioReader.startReading()
                    videoWriter.startSessionAtSourceTime(kCMTimeZero)
                    let processingQueue = dispatch_queue_create("processingQueue2", nil)
                    audioWriterInput.requestMediaDataWhenReadyOnQueue(processingQueue, usingBlock: {() -> Void in
                        while audioWriterInput.readyForMoreMediaData {
                            let sampleBuffer:CMSampleBufferRef? = audioReaderOutput.copyNextSampleBuffer()
                            if audioReader.status == .Reading && sampleBuffer != nil {
                                audioWriterInput.appendSampleBuffer(sampleBuffer!)
                            }
                            else {
                                audioWriterInput.markAsFinished()
                                if audioReader.status == .Completed {
                                    videoWriter.finishWritingWithCompletionHandler({() -> Void in
                                        onDone();
                                    })
                                }
                            }
                        }
                    })
                }
            }
        }
    })
}
于 2016-08-31T07:49:16.933 回答
3
Use exportSession.fileLengthLimit = 1024 * 1024 * 10 //10 MB

10MB 是硬编码数字。根据您所需的比特率使用。

文件长度限制

会话不应超过。根据源资产的内容,输出可能会略微超过文件长度限制。如果您要求在使用输出之前遵守严格的限制,则应测试输出文件的长度。另请参见 maxDuration 和 timeRange。指示输出的文件长度

developer.apple.com/documentation/avfoundation/avassetexportsession/1622333-filelengthlimit

于 2019-07-25T18:38:22.587 回答
1

有一个很棒的自定义类(SDAVAssetExportSession)来进行视频压缩。您可以从此链接下载它。

下载后将 SDAVAssetExportSession.h 和 SDAVAssetExportSession.m 文件添加到您的项目中,然后使用以下代码进行压缩。在下面的代码中,您可以通过指定分辨率和比特率来压缩视频

#import "SDAVAssetExportSession.h"


- (void)compressVideoWithInputVideoUrl:(NSURL *) inputVideoUrl
{
    /* Create Output File Url */

    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSString *finalVideoURLString = [documentsDirectory stringByAppendingPathComponent:@"compressedVideo.mp4"];
    NSURL *outputVideoUrl = ([[NSURL URLWithString:finalVideoURLString] isFileURL] == 1)?([NSURL URLWithString:finalVideoURLString]):([NSURL fileURLWithPath:finalVideoURLString]); // Url Should be a file Url, so here we check and convert it into a file Url


    SDAVAssetExportSession *compressionEncoder = [SDAVAssetExportSession.alloc initWithAsset:[AVAsset assetWithURL:inputVideoUrl]]; // provide inputVideo Url Here
    compressionEncoder.outputFileType = AVFileTypeMPEG4;
    compressionEncoder.outputURL = outputVideoUrl; //Provide output video Url here
    compressionEncoder.videoSettings = @
    {
    AVVideoCodecKey: AVVideoCodecH264,
    AVVideoWidthKey: @800,   //Set your resolution width here
    AVVideoHeightKey: @600,  //set your resolution height here
    AVVideoCompressionPropertiesKey: @
        {
        AVVideoAverageBitRateKey: @45000, // Give your bitrate here for lower size give low values
        AVVideoProfileLevelKey: AVVideoProfileLevelH264High40,
        },
    };
    compressionEncoder.audioSettings = @
    {
    AVFormatIDKey: @(kAudioFormatMPEG4AAC),
    AVNumberOfChannelsKey: @2,
    AVSampleRateKey: @44100,
    AVEncoderBitRateKey: @128000,
    };

    [compressionEncoder exportAsynchronouslyWithCompletionHandler:^
     {
         if (compressionEncoder.status == AVAssetExportSessionStatusCompleted)
         {
            NSLog(@"Compression Export Completed Successfully");
         }
         else if (compressionEncoder.status == AVAssetExportSessionStatusCancelled)
         {
             NSLog(@"Compression Export Canceled");
         }
         else
         {
              NSLog(@"Compression Failed");

         }
     }];

}

取消压缩使用下面的代码行

 [compressionEncoder cancelExport]; //Video compression cancel
于 2016-11-01T13:31:44.523 回答
1

我支持etayluz的回答SDAVAssetExportSession是一个很棒的自定义类来进行视频压缩。这是我的工作代码。您可以从此链接下载SDAVAssetExportSession

下载后将 SDAVAssetExportSession.h 和 SDAVAssetExportSession.m 文件添加到您的项目中,然后使用以下代码进行压缩。在下面的代码中,您可以通过指定分辨率和比特率来压缩视频

#import "SDAVAssetExportSession.h"


- (void)compressVideoWithInputVideoUrl:(NSURL *) inputVideoUrl
{
    /* Create Output File Url */

    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSString *finalVideoURLString = [documentsDirectory stringByAppendingPathComponent:@"compressedVideo.mp4"];
    NSURL *outputVideoUrl = ([[NSURL URLWithString:finalVideoURLString] isFileURL] == 1)?([NSURL URLWithString:finalVideoURLString]):([NSURL fileURLWithPath:finalVideoURLString]); // Url Should be a file Url, so here we check and convert it into a file Url


    SDAVAssetExportSession *compressionEncoder = [SDAVAssetExportSession.alloc initWithAsset:[AVAsset assetWithURL:inputVideoUrl]]; // provide inputVideo Url Here
    compressionEncoder.outputFileType = AVFileTypeMPEG4;
    compressionEncoder.outputURL = outputVideoUrl; //Provide output video Url here
    compressionEncoder.videoSettings = @
    {
    AVVideoCodecKey: AVVideoCodecH264,
    AVVideoWidthKey: @800,   //Set your resolution width here
    AVVideoHeightKey: @600,  //set your resolution height here
    AVVideoCompressionPropertiesKey: @
        {
        AVVideoAverageBitRateKey: @45000, // Give your bitrate here for lower size give low values
        AVVideoProfileLevelKey: AVVideoProfileLevelH264High40,
        },
    };
    compressionEncoder.audioSettings = @
    {
    AVFormatIDKey: @(kAudioFormatMPEG4AAC),
    AVNumberOfChannelsKey: @2,
    AVSampleRateKey: @44100,
    AVEncoderBitRateKey: @128000,
    };

    [compressionEncoder exportAsynchronouslyWithCompletionHandler:^
     {
         if (compressionEncoder.status == AVAssetExportSessionStatusCompleted)
         {
            NSLog(@"Compression Export Completed Successfully");
         }
         else if (compressionEncoder.status == AVAssetExportSessionStatusCancelled)
         {
             NSLog(@"Compression Export Canceled");
         }
         else
         {
              NSLog(@"Compression Failed");

         }
     }];

}

取消压缩使用下面的代码行

 [compressionEncoder cancelExport]; //Video compression cancel
于 2016-11-01T13:40:45.633 回答