为科尔多瓦视频编辑器修改 AVAsset 中的比特率

Posted

技术标签:

【中文标题】为科尔多瓦视频编辑器修改 AVAsset 中的比特率【英文标题】:Modify bitrate in AVAsset for cordova Video Editor 【发布时间】:2015-10-14 08:46:08 【问题描述】:

我需要在不降低分辨率的情况下减少使用混合应用拍摄的视频的尺寸,因此我正在尝试修改 cordova video editor plugin 以通过更改比特率来减小视频的尺寸。 我尝试使用 SDAVAssetExportSession 没有任何成功并得到大量 CVPixel 错误。 插件代码为:

- (void) transcodeVideo:(CDVInvokedUrlCommand*)command

    NSDictionary* options = [command.arguments objectAtIndex:0];

    if ([options isKindOfClass:[NSNull class]]) 
        options = [NSDictionary dictionary];
    

    NSString *assetPath = [options objectForKey:@"fileUri"];
    NSString *videoFileName = [options objectForKey:@"outputFileName"];

    CDVQualityType qualityType = ([options objectForKey:@"quality"]) ? [[options objectForKey:@"quality"] intValue] : LowQuality;

    NSString *presetName = Nil;

    switch(qualityType) 
        case HighQuality:
            presetName = AVAssetExportPresetHighestQuality;
            break;
        case MediumQuality:
        default:
            presetName = AVAssetExportPresetMediumQuality;
            break;
        case LowQuality:
            presetName = AVAssetExportPresetLowQuality;
    

    CDVOutputFileType outputFileType = ([options objectForKey:@"outputFileType"]) ? [[options objectForKey:@"outputFileType"] intValue] : MPEG4;

    BOOL optimizeForNetworkUse = ([options objectForKey:@"optimizeForNetworkUse"]) ? [[options objectForKey:@"optimizeForNetworkUse"] intValue] : NO;

    float videoDuration = [[options objectForKey:@"duration"] floatValue];

    BOOL saveToPhotoAlbum = [options objectForKey:@"saveToLibrary"] ? [[options objectForKey:@"saveToLibrary"] boolValue] : YES;

    NSString *stringOutputFileType = Nil;
    NSString *outputExtension = Nil;

    switch (outputFileType) 
        case QUICK_TIME:
            stringOutputFileType = AVFileTypeQuickTimeMovie;
            outputExtension = @".mov";
            break;
        case M4A:
            stringOutputFileType = AVFileTypeAppleM4A;
            outputExtension = @".m4a";
            break;
        case M4V:
            stringOutputFileType = AVFileTypeAppleM4V;
            outputExtension = @".m4v";
            break;
        case MPEG4:
        default:
            stringOutputFileType = AVFileTypeMPEG4;
            outputExtension = @".mp4";
            break;
    

    // remove file:// from the assetPath if it is there
    assetPath = [[assetPath stringByReplacingOccurrencesOfString:@"file://" withString:@""] mutableCopy];

    // check if the video can be saved to photo album before going further
    if (saveToPhotoAlbum && !UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(assetPath))
    
        NSString *error = @"Video cannot be saved to photo album";
        [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:error ] callbackId:command.callbackId];
        return;
    

    NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
    NSString *tempVideoPath =[NSString stringWithFormat:@"%@/%@%@", docDir, videoFileName, @".mov"];
    NSData *videoData = [NSData dataWithContentsOfFile:assetPath];
    [videoData writeToFile:tempVideoPath atomically:NO];

    AVURLAsset *avAsset = [AVURLAsset URLAssetWithURL:[NSURL fileURLWithPath:tempVideoPath] options:nil];
    NSArray *compatiblePresets = [AVAssetExportSession exportPresetsCompatibleWithAsset:avAsset];

    if ([compatiblePresets containsObject:AVAssetExportPresetLowQuality])
    
        AVAssetExportSession *exportSession = [[AVAssetExportSession alloc]initWithAsset:avAsset presetName: presetName];
        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
        NSString *videoPath = [NSString stringWithFormat:@"%@/%@%@", [paths objectAtIndex:0], videoFileName, outputExtension];

        exportSession.outputURL = [NSURL fileURLWithPath:videoPath];
        exportSession.outputFileType = stringOutputFileType;
        exportSession.shouldOptimizeForNetworkUse = optimizeForNetworkUse;

        NSLog(@"videopath of your file: %@", videoPath);

        if (videoDuration)
        
            int32_t preferredTimeScale = 600;
            CMTime startTime = CMTimeMakeWithSeconds(0, preferredTimeScale);
            CMTime stopTime = CMTimeMakeWithSeconds(videoDuration, preferredTimeScale);
            CMTimeRange exportTimeRange = CMTimeRangeFromTimeToTime(startTime, stopTime);
            exportSession.timeRange = exportTimeRange;
        

        [exportSession exportAsynchronouslyWithCompletionHandler:^
            switch ([exportSession status]) 
                case AVAssetExportSessionStatusCompleted:
                    if (saveToPhotoAlbum) 
                        UISaveVideoAtPathToSavedPhotosAlbum(videoPath, self, nil, nil);
                    
                    NSLog(@"Export Complete %d %@", exportSession.status, exportSession.error);
                    [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:videoPath] callbackId:command.callbackId];
                    break;
                case AVAssetExportSessionStatusFailed:
                    NSLog(@"Export failed: %@", [[exportSession error] localizedDescription]);
                    [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:[[exportSession error] localizedDescription]] callbackId:command.callbackId];
                    break;
                case AVAssetExportSessionStatusCancelled:
                    NSLog(@"Export canceled");
                    break;
                default:
                    NSLog(@"Export default in switch");
                    break;
            
        ];
    


如何在 cordova 插件中实现 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];

我真的不需要灵活的解决方案,硬编码就可以了。 我并不是真正的 Object-C 专家(我发现它是一种非常晦涩的语言)

【问题讨论】:

为什么不要求作者进行更改?很可能其他人也需要这种灵活性。 【参考方案1】:

我设法解决了 SDAVAssetExportSession 的问题。接下来,我正在联系作者以提交更改。以下是代码(必须添加到 CoreVideo.framework):

- (void) transcodeVideo:(CDVInvokedUrlCommand*)command

    NSDictionary* options = [command.arguments objectAtIndex:0];

    if ([options isKindOfClass:[NSNull class]]) 
        options = [NSDictionary dictionary];
    

    NSString *assetPath = [options objectForKey:@"fileUri"];
    NSString *videoFileName = [options objectForKey:@"outputFileName"];

    CDVQualityType qualityType = ([options objectForKey:@"quality"]) ? [[options objectForKey:@"quality"] intValue] : LowQuality;

    NSString *presetName = Nil;

    switch(qualityType) 
        case HighQuality:
            presetName = AVAssetExportPresetHighestQuality;
            break;
        case MediumQuality:
        default:
            presetName = AVAssetExportPresetMediumQuality;
            break;
        case LowQuality:
            presetName = AVAssetExportPresetLowQuality;
    

    CDVOutputFileType outputFileType = ([options objectForKey:@"outputFileType"]) ? [[options objectForKey:@"outputFileType"] intValue] : MPEG4;

    BOOL optimizeForNetworkUse = ([options objectForKey:@"optimizeForNetworkUse"]) ? [[options objectForKey:@"optimizeForNetworkUse"] intValue] : NO;

    float videoDuration = [[options objectForKey:@"duration"] floatValue];

    BOOL saveToPhotoAlbum = [options objectForKey:@"saveToLibrary"] ? [[options objectForKey:@"saveToLibrary"] boolValue] : YES;

    NSString *stringOutputFileType = Nil;
    NSString *outputExtension = Nil;

    switch (outputFileType) 
        case QUICK_TIME:
            stringOutputFileType = AVFileTypeQuickTimeMovie;
            outputExtension = @".mov";
            break;
        case M4A:
            stringOutputFileType = AVFileTypeAppleM4A;
            outputExtension = @".m4a";
            break;
        case M4V:
            stringOutputFileType = AVFileTypeAppleM4V;
            outputExtension = @".m4v";
            break;
        case MPEG4:
        default:
            stringOutputFileType = AVFileTypeMPEG4;
            outputExtension = @".mp4";
            break;
    

    // remove file:// from the assetPath if it is there
    assetPath = [[assetPath stringByReplacingOccurrencesOfString:@"file://" withString:@""] mutableCopy];

    // check if the video can be saved to photo album before going further
    if (saveToPhotoAlbum && !UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(assetPath))
    
        NSString *error = @"Video cannot be saved to photo album";
        [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:error ] callbackId:command.callbackId];
        return;
    

    NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
    NSString *tempVideoPath =[NSString stringWithFormat:@"%@/%@%@", docDir, videoFileName, @".mov"];
    NSData *videoData = [NSData dataWithContentsOfFile:assetPath];
    [videoData writeToFile:tempVideoPath atomically:NO];

    AVURLAsset *avAsset = [AVURLAsset URLAssetWithURL:[NSURL fileURLWithPath:tempVideoPath] options:nil];
    NSArray *compatiblePresets = [AVAssetExportSession exportPresetsCompatibleWithAsset:avAsset];

    SDAVAssetExportSession *encoder = [SDAVAssetExportSession.alloc initWithAsset:avAsset];

    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *videoPath = [NSString stringWithFormat:@"%@/%@%@", [paths objectAtIndex:0], videoFileName, outputExtension];

    encoder.outputFileType = stringOutputFileType;
    encoder.outputURL = [NSURL fileURLWithPath:videoPath];
    encoder.shouldOptimizeForNetworkUse = optimizeForNetworkUse;
    encoder.videoSettings = @
    
    AVVideoCodecKey: AVVideoCodecH264,
    AVVideoWidthKey: @1280,
    AVVideoHeightKey: @720,
    AVVideoCompressionPropertiesKey: @
        
        AVVideoAverageBitRateKey: @1200000,
        AVVideoProfileLevelKey: AVVideoProfileLevelH264High40,
        ,
    ;
    encoder.audiosettings = @
    
    AVFormatIDKey: @(kAudioFormatMPEG4AAC),
    AVNumberOfChannelsKey: @2,
    AVSampleRateKey: @44100,
    AVEncoderBitRateKey: @128000,
    ;

    [encoder exportAsynchronouslyWithCompletionHandler:^
     
         if (encoder.status == AVAssetExportSessionStatusCompleted)
         
             if (saveToPhotoAlbum) 
                 UISaveVideoAtPathToSavedPhotosAlbum(videoPath, self, nil, nil);
             
             NSLog(@"Export Complete %d %@", encoder.status, encoder.error);
             [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:videoPath] callbackId:command.callbackId];
         
         else if (encoder.status == AVAssetExportSessionStatusCancelled)
         
             NSLog(@"Video export cancelled");
         
         else
         
             NSLog(@"Export failed: %@", [[encoder error] localizedDescription]);
             [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:[[encoder error] localizedDescription]] callbackId:command.callbackId];
         
         /*switch ([encoder status]) 
          case AVAssetExportSessionStatusCompleted:

          break;
          case AVAssetExportSessionStatusFailed:

          break;
          case AVAssetExportSessionStatusCancelled:
          NSLog(@"Export canceled");
          break;
          default:
          NSLog(@"Export default in switch");
          break;
          */

     ];
    /*if ([compatiblePresets containsObject:AVAssetExportPresetLowQuality])
     
     AVAssetExportSession *exportSession = [[AVAssetExportSession alloc]initWithAsset:avAsset presetName: presetName];
     NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
     NSString *videoPath = [NSString stringWithFormat:@"%@/%@%@", [paths objectAtIndex:0], videoFileName, outputExtension];

     exportSession.outputURL = [NSURL fileURLWithPath:videoPath];
     exportSession.outputFileType = stringOutputFileType;
     exportSession.shouldOptimizeForNetworkUse = optimizeForNetworkUse;

     NSLog(@"videopath of your file: %@", videoPath);

     if (videoDuration)
     
     int32_t preferredTimeScale = 600;
     CMTime startTime = CMTimeMakeWithSeconds(0, preferredTimeScale);
     CMTime stopTime = CMTimeMakeWithSeconds(videoDuration, preferredTimeScale);
     CMTimeRange exportTimeRange = CMTimeRangeFromTimeToTime(startTime, stopTime);
     exportSession.timeRange = exportTimeRange;
     

     [exportSession exportAsynchronouslyWithCompletionHandler:^
     switch ([exportSession status]) 
     case AVAssetExportSessionStatusCompleted:
     if (saveToPhotoAlbum) 
     UISaveVideoAtPathToSavedPhotosAlbum(videoPath, self, nil, nil);
     
     NSLog(@"Export Complete %d %@", exportSession.status, exportSession.error);
     [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:videoPath] callbackId:command.callbackId];
     break;
     case AVAssetExportSessionStatusFailed:
     NSLog(@"Export failed: %@", [[exportSession error] localizedDescription]);
     [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:[[exportSession error] localizedDescription]] callbackId:command.callbackId];
     break;
     case AVAssetExportSessionStatusCancelled:
     NSLog(@"Export canceled");
     break;
     default:
     NSLog(@"Export default in switch");
     break;
     
     ];
     */


【讨论】:

以上是关于为科尔多瓦视频编辑器修改 AVAsset 中的比特率的主要内容,如果未能解决你的问题,请参考以下文章

Swift 3 将数据转换为 AVAsset 或 PHAsset

将AVAsset视频文件拆分为块

使用 AVAsset/AVCaptureSession 裁剪视频

在 Nativescript 应用程序中将 PHAsset/AVAsset 转换为 mp4 视频

请求 AVAsset 时维护顺序

AVAsset 的 NaturalSize 与视频文件不同