AVFoundation.framework學(xué)習(xí)(3)

導(dǎo)出音視頻文件

要讀取和寫入視聽asset氮凝,必須使用AVFoundation框架中的導(dǎo)出api谜洽。AVAssetExportSession類僅是一個簡單的導(dǎo)出的接口萝映,例如修改文件格式或者修剪資源的長度。想要更多的導(dǎo)出需求阐虚,我們需要是用AVAssetReader類和AVAssetWriter類

如果想要對asset中的內(nèi)容操作序臂,那么使用AVAssetReader類。例如敌呈,讀取asset的track以產(chǎn)生波形的只管表示贸宏。要從樣本緩沖區(qū)或者靜止圖像等媒體生成asset,使用AVAssetWriter類

注意:asset的讀取和編寫不能用于實(shí)時處理磕洪。實(shí)際上,AVAssetReader不能用于http實(shí)時流等實(shí)時源讀取诫龙。但是析显,我們可以使用AVAssetWriter對實(shí)時數(shù)據(jù)(例如AVCaptureOutput對象)進(jìn)行處理。我們需要設(shè)置expectsMediaDataInRealTime=yes签赃。對于非實(shí)時性源數(shù)據(jù)谷异,將此屬性設(shè)置為yes,將導(dǎo)致文件無法正確存取锦聊。

讀取asset

每個AVAssetReader對象一次只能與一個asset相關(guān)聯(lián)歹嘹,但是asset可以包含多個track。因此孔庭,在開始讀取asset之前尺上,我們必須要將AVAssetReaderOutput類的具體子類分配給AVAssetReader對象材蛛,以便配置媒體數(shù)據(jù)的讀取方式。AVAssetReaderOutput基類有三個具體的子類怎抛,可以滿足讀取asset的要求卑吭。他們是AVAssetReaderTrackOutput,AVAssetReaderAudioMixOutput和AVAssetReaderVideoCompositionOutput马绝。

具體關(guān)系如圖


創(chuàng)建asset的閱讀器 -AVAssetReader
NSError *outError;
AVAsset *someAsset = <#AVAsset that you want to read#>;
AVAssetReader *assetReader = [AVAssetReader assetReaderWithAsset:someAsset error:&outError];
BOOL success = (assetReader != nil)

注意:這里必須檢測AVAssetReader實(shí)例對象是否真正的生成豆赏。error包含沒有生成的錯誤信息

設(shè)置 AVAssetReaderOutput

創(chuàng)建AVAssetReader后,我們應(yīng)該設(shè)置一個輸出富稻。當(dāng)我們設(shè)置輸出后掷邦,確保alwaysCopiesSampleData屬性是NO。通過這種方式椭赋,我們可以獲得性能改進(jìn)的好處抚岗。

如果只想從一個或者多個track讀取媒體數(shù)據(jù),并轉(zhuǎn)換數(shù)據(jù)為其他格式纹份,那么我們應(yīng)該使用AVAssetReaderTrackOutput類苟跪,該類可以從asset中讀取每個AVAssetTrack對象。如果想壓縮一個音頻track成 linear pcm蔓涧,我們應(yīng)該如下設(shè)置

AVAsset *localAsset = assetReader.asset;
// Get the audio track to read.
AVAssetTrack *audioTrack = [[localAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0];
// Decompression settings for Linear PCM
NSDictionary *decompressionAudioSettings = @{ AVFormatIDKey : [NSNumber numberWithUnsignedInt:kAudioFormatLinearPCM] };
// Create the output with the audio track and decompression settings.
AVAssetReaderOutput *trackOutput = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:audioTrack outputSettings:decompressionAudioSettings];
// Add the output to the reader if possible.
if ([assetReader canAddOutput:trackOutput])
    [assetReader addOutput:trackOutput];

注意件已,這里要是把a(bǔ)udioSettings傳遞為nil,那么就是告訴assetReader以最方便的未壓縮格式返回樣本元暴。

視頻合成輸出的行為方式大致相同:我們可以使用AVVideoComposition對象從asset中讀取多個track篷扩。從多個AVVideoComposition 的track中讀取媒體數(shù)據(jù)并將其解壓縮為ARGB。方式如下

AVVideoComposition *videoComposition = <#An AVVideoComposition that specifies how the video tracks from the AVAsset are composited#>;
// Assumes assetReader was initialized with an AVComposition.
AVComposition *composition = (AVComposition *)assetReader.asset;
// Get the video tracks to read.
NSArray *videoTracks = [composition tracksWithMediaType:AVMediaTypeVideo];
// Decompression settings for ARGB.
NSDictionary *decompressionVideoSettings = @{ (id)kCVPixelBufferPixelFormatTypeKey : [NSNumber numberWithUnsignedInt:kCVPixelFormatType_32ARGB], (id)kCVPixelBufferIOSurfacePropertiesKey : [NSDictionary dictionary] };
// Create the video composition output with the video tracks and decompression setttings.
AVAssetReaderOutput *videoCompositionOutput = [AVAssetReaderVideoCompositionOutput assetReaderVideoCompositionOutputWithVideoTracks:videoTracks videoSettings:decompressionVideoSettings];
// Associate the video composition used to composite the video tracks being read with the output.
videoCompositionOutput.videoComposition = videoComposition;
// Add the output to the reader if possible.
if ([assetReader canAddOutput:videoCompositionOutput])
    [assetReader addOutput:videoCompositionOutput];
讀取asset的媒體數(shù)據(jù)

在設(shè)置所需的所有輸出后開始讀取asset茉盏,我們應(yīng)該調(diào)用startReading方法鉴未。接下來,使用copyNextSampleBuffer方法從每個輸出中單獨(dú)檢索媒體數(shù)據(jù)鸠姨。要是使用單個輸出啟動assert reader并讀取所有的媒體铜秆,請看下列例子:

// Start the asset reader up.
[self.assetReader startReading];
BOOL done = NO;
while (!done)
{
  // Copy the next sample buffer from the reader output.
  CMSampleBufferRef sampleBuffer = [self.assetReaderOutput copyNextSampleBuffer];
  if (sampleBuffer)
  {
    // Do something with sampleBuffer here.
    CFRelease(sampleBuffer);
    sampleBuffer = NULL;
  }
  else
  {
    // Find out why the asset reader output couldn't copy another sample buffer.
    if (self.assetReader.status == AVAssetReaderStatusFailed)
    {
      NSError *failureError = self.assetReader.error;
      // Handle the error here.
    }
    else
    {
      // The asset reader output has read all of its samples.
      done = YES;
    }
  }
}

寫asset

AVAssetWriter 類可以將多個源的媒體數(shù)據(jù)寫入指定文件格式的單個文件。我們不需要將AVAssetWriter和asset聯(lián)系在一起讶迁,但是需要有輸出文件與之對應(yīng)连茧。這是因?yàn)锳VAssetWriter可以使用多個源寫入媒體數(shù)據(jù),因此我們必須要為每個單獨(dú)的track創(chuàng)建AVAssetWriterInput對象巍糯。每一個AVAssetWriterInput對象都已CMSampleBufferRef對象的形式接受數(shù)據(jù)啸驯,如果想要追加數(shù)據(jù)到CVPixelBufferRef對象上,應(yīng)該使用AVAssetWriterInputPixelBufferAdaptor類祟峦。

創(chuàng)建AVAssetWriter對象

創(chuàng)建AVAssetWriter對象罚斗,我們需要指定一個輸出路徑的url。

NSError *outError;
NSURL *outputURL = <#NSURL object representing the URL where you want to save the video#>;
AVAssetWriter *assetWriter = [AVAssetWriter assetWriterWithURL:outputURL
                                                      fileType:AVFileTypeQuickTimeMovie
                                                         error:&outError];
BOOL success = (assetWriter != nil);
設(shè)置asset的 輸入源

使用AVAssetWriter對象可以寫入媒體數(shù)據(jù)宅楞,我們必須至少設(shè)置一個輸入源针姿。例如袱吆,我們將媒體數(shù)據(jù)轉(zhuǎn)換成了CMSampleBufferRef對象,那么我們只需要使用AVAssetWriterInput類即可搓幌。要設(shè)置將音頻媒體數(shù)據(jù)壓縮為128kbps aac 并將其連接到AVAssetWriter 杆故,步驟如下

// Configure the channel layout as stereo.
AudioChannelLayout stereoChannelLayout = {
    .mChannelLayoutTag = kAudioChannelLayoutTag_Stereo,
    .mChannelBitmap = 0,
    .mNumberChannelDescriptions = 0
};
 
// Convert the channel layout object to an NSData object.
NSData *channelLayoutAsData = [NSData dataWithBytes:&stereoChannelLayout length:offsetof(AudioChannelLayout, mChannelDescriptions)];
 
// Get the compression settings for 128 kbps AAC.
NSDictionary *compressionAudioSettings = @{
    AVFormatIDKey         : [NSNumber numberWithUnsignedInt:kAudioFormatMPEG4AAC],
    AVEncoderBitRateKey   : [NSNumber numberWithInteger:128000],
    AVSampleRateKey       : [NSNumber numberWithInteger:44100],
    AVChannelLayoutKey    : channelLayoutAsData,
    AVNumberOfChannelsKey : [NSNumber numberWithUnsignedInteger:2]
};
 
// Create the asset writer input with the compression settings and specify the media type as audio.
AVAssetWriterInput *assetWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio outputSettings:compressionAudioSettings];
// Add the input to the writer if possible.
if ([assetWriter canAddInput:assetWriterInput])
    [assetWriter addInput:assetWriterInput];

注意:如果要以存儲的格式寫入媒體數(shù)據(jù),請?jiān)趏utputSettings傳遞nil溉愁。僅當(dāng)使用fileType為AVFileTypeQuickTimeMovie初始化才傳遞為nil处铛。

如果asset writer的input 可以選擇性的包含一些元數(shù)據(jù),或者分別使用元數(shù)據(jù)和transform屬性為特定的track指定不同的transfrom拐揭。對于將數(shù)據(jù)源是一個視頻track的asset writer的input撤蟆,我們可以通過執(zhí)行下列操作維護(hù)視頻的原始transform。

AVAsset *videoAsset = <#AVAsset with at least one video track#>;
AVAssetTrack *videoAssetTrack = [[videoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
assetWriterInput.transform = videoAssetTrack.preferredTransform;

注意:metadata 和transform 屬性要在啟動AVAssetWriter之前開啟堂污。

將媒體數(shù)據(jù)寫入到輸出文件時家肯,有時候可能需要分配像素緩沖區(qū)。因此盟猖,使用AVAssetWriterInputPixelBufferAdaptor類讨衣。為了獲取最高效率,我們應(yīng)該提供一個緩沖池式镐》凑颍看下列代碼

NSDictionary *pixelBufferAttributes = @{
     kCVPixelBufferCGImageCompatibilityKey : [NSNumber numberWithBool:YES],
     kCVPixelBufferCGBitmapContextCompatibilityKey : [NSNumber numberWithBool:YES],
     kCVPixelBufferPixelFormatTypeKey : [NSNumber numberWithInt:kCVPixelFormatType_32ARGB]
};
AVAssetWriterInputPixelBufferAdaptor *inputPixelBufferAdaptor = [AVAssetWriterInputPixelBufferAdaptor assetWriterInputPixelBufferAdaptorWithAssetWriterInput:self.assetWriterInput sourcePixelBufferAttributes:pixelBufferAttributes];

所有的AVAssetWriterInputPixelBufferAdaptor和一個asset 的input關(guān)聯(lián)。并且intput必須是AVMediaTypeVideo類型的媒體數(shù)據(jù)

編寫媒體數(shù)據(jù)

當(dāng)我們?yōu)锳VAssetWriter配置所有的inputs 時娘汞,我們就可以編寫媒體數(shù)據(jù)了歹茶。通過調(diào)用startWriting方法啟動寫入數(shù)據(jù)。然后需要調(diào)用startSessionAtSourceTime:方法來啟動編寫會話你弦。每個媒體數(shù)據(jù)都有個時間范圍惊豺,AVAssetWriter通過這個來一次編寫input∏葑鳎看下列例子:

CMTime halfAssetDuration = CMTimeMultiplyByFloat64(self.asset.duration, 0.5);
[self.assetWriter startSessionAtSourceTime:halfAssetDuration];
//Implementation continues.

通過尸昧,要結(jié)束寫的session,我們必須調(diào)用endSessionAtSourceTime方法旷偿。但是彻磁,如果我們寫的session已經(jīng)寫到文件末尾了,只需要調(diào)用finishWriting即可狸捅。

// Prepare the asset writer for writing.
[self.assetWriter startWriting];
// Start a sample-writing session.
[self.assetWriter startSessionAtSourceTime:kCMTimeZero];
// Specify the block to execute when the asset writer is ready for media data and the queue to call it on.
[self.assetWriterInput requestMediaDataWhenReadyOnQueue:myInputSerialQueue usingBlock:^{
     while ([self.assetWriterInput isReadyForMoreMediaData])
     {
          // Get the next sample buffer.
          CMSampleBufferRef nextSampleBuffer = [self copyNextSampleBufferToWrite];
          if (nextSampleBuffer)
          {
               // If it exists, append the next sample buffer to the output file.
               [self.assetWriterInput appendSampleBuffer:nextSampleBuffer];
               CFRelease(nextSampleBuffer);
               nextSampleBuffer = nil;
          }
          else
          {
               // Assume that lack of a next sample buffer means the sample buffer source is out of samples and mark the input as finished.
               [self.assetWriterInput markAsFinished];
               break;
          }
     }
}];
重新編碼assets

我們可以串聯(lián) AVAssetReaderAVAssetWriter,將asset從一種表示轉(zhuǎn)換成另一種表示累提。使用這些對象尘喝,我們可以比使用AVAssetExportSession對象更多的控制轉(zhuǎn)換。例如斋陪,我們可以選擇要在輸出文件中表示那些軌道朽褪,指定自己的輸出格式置吓,或者在轉(zhuǎn)換過程中修改asset。此過程的第一步是根據(jù)需要設(shè)置
AVAssetReader和AVAssetWriter 的input缔赠。當(dāng)AVAssetReader和AVAssetWriter完全配置完畢后衍锚,,分別調(diào)用startReading和startWriting方法啟動它們嗤堰〈髦剩看下列代碼

NSString *serializationQueueDescription = [NSString stringWithFormat:@"%@ serialization queue", self];
 
// Create a serialization queue for reading and writing.
dispatch_queue_t serializationQueue = dispatch_queue_create([serializationQueueDescription UTF8String], NULL);
 
// Specify the block to execute when the asset writer is ready for media data and the queue to call it on.
[self.assetWriterInput requestMediaDataWhenReadyOnQueue:serializationQueue usingBlock:^{
     while ([self.assetWriterInput isReadyForMoreMediaData])
     {
          // Get the asset reader output's next sample buffer.
          CMSampleBufferRef sampleBuffer = [self.assetReaderOutput copyNextSampleBuffer];
          if (sampleBuffer != NULL)
          {
               // If it exists, append this sample buffer to the output file.
               BOOL success = [self.assetWriterInput appendSampleBuffer:sampleBuffer];
               CFRelease(sampleBuffer);
               sampleBuffer = NULL;
               // Check for errors that may have occurred when appending the new sample buffer.
               if (!success && self.assetWriter.status == AVAssetWriterStatusFailed)
               {
                    NSError *failureError = self.assetWriter.error;
                    //Handle the error.
               }
          }
          else
          {
               // If the next sample buffer doesn't exist, find out why the asset reader output couldn't vend another one.
               if (self.assetReader.status == AVAssetReaderStatusFailed)
               {
                    NSError *failureError = self.assetReader.error;
                    //Handle the error here.
               }
               else
               {
                    // The asset reader output must have vended all of its samples. Mark the input as finished.
                    [self.assetWriterInput markAsFinished];
                    break;
               }
          }
     }
}];

export 真是匯總demo

上面都是知識片段,看知識片段還不能完全了解具體的使用踢匣,我們下列就具體的編寫一個demo來看看

demo的具體步驟如下

  • 1.使用串行隊(duì)列異步處理讀取和寫入的視聽數(shù)據(jù)
  • 2.初始化AVAssetReader對象告匠,配置兩個output,一個視頻另一個音頻
  • 3.初始化AVASsetWriter對象离唬,配置兩個inputs后专,一個音頻一個視頻
  • 4.使用AVAssetReader對象異步將獲取的數(shù)據(jù)傳輸給AVASsetWriter對象
  • 5.用dispatch goupe通知重新編碼過程的完成。
  • 6允許用戶在開始后取消重新編碼過程
強(qiáng)制引用屬性
@property (nonatomic,strong) AVCaptureSession * captureSession;
@property (nonatomic,strong) dispatch_queue_t mainSerializationQueue;
@property (nonatomic,strong) dispatch_queue_t rwAudioSerializationQueue;
@property (nonatomic,strong) dispatch_queue_t rwVideoSerializationQueue;
@property (nonatomic,strong) AVURLAsset *asset;
@property (nonatomic,strong) NSURL *outputURL;
@property (nonatomic,assign) BOOL cancelled;
@property (nonatomic,strong) AVAssetReader *assetReader;
@property (nonatomic,strong) AVAssetWriter *assetWriter;
@property (nonatomic,strong) AVAssetReaderTrackOutput *assetReaderAudioOutput;
@property (nonatomic,strong) AVAssetWriterInput *assetWriterAudioInput;
@property (nonatomic,strong) AVAssetReaderTrackOutput *assetReaderVideoOutput;
@property (nonatomic,strong) AVAssetWriterInput *assetWriterVideoInput;
@property (nonatomic,strong) dispatch_group_t dispatchGroup;
@property (nonatomic,assign) BOOL audioFinished;
@property (nonatomic,assign) BOOL videoFinished;
-(AVURLAsset * )getAVAssetABC{
    NSURL *fileURL = [[NSBundle mainBundle] URLForResource:@"abc" withExtension:@"mp4"];
    AVURLAsset * asset = [AVURLAsset URLAssetWithURL:fileURL options:nil];
    return  asset;
}
- (void)viewDidLoad {
    [super viewDidLoad];
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
    NSString *path = [[[paths objectAtIndex:0]stringByAppendingPathComponent:[NSUUID UUID].UUIDString] stringByAppendingString:@".mov"];
    self.path = path;
    NSLog(@"%@",self.path);
//    [self edit];
//    [self capture];
    [self readerWriter];
}

-(void)readerWriter{
    NSString *serializationQueueDescription = [NSString stringWithFormat:@"%@ serialization queue", self];
    // Create the main serialization queue.
    self.mainSerializationQueue = dispatch_queue_create([serializationQueueDescription UTF8String], NULL);
    
    NSString *rwAudioSerializationQueueDescription = [NSString stringWithFormat:@"%@ rw audio serialization queue", self];
    // Create the serialization queue to use for reading and writing the audio data.
    self.rwAudioSerializationQueue = dispatch_queue_create([rwAudioSerializationQueueDescription UTF8String], NULL);
    NSString *rwVideoSerializationQueueDescription = [NSString stringWithFormat:@"%@ rw video serialization queue", self];
    // Create the serialization queue to use for reading and writing the video data.
    self.rwVideoSerializationQueue = dispatch_queue_create([rwVideoSerializationQueueDescription UTF8String], NULL);
    
    
    self.asset = [self getAVAssetABC];
    self.cancelled = NO;
    self.outputURL = [NSURL fileURLWithPath:self.path];;
    [self.asset loadValuesAsynchronouslyForKeys:@[@"tracks"] completionHandler:^{
        // Once the tracks have finished loading, dispatch the work to the main serialization queue.
        dispatch_async(self.mainSerializationQueue, ^{
            // Due to asynchronous nature, check to see if user has already cancelled.
            if (self.cancelled)
                return;
            BOOL success = YES;
            NSError *localError = nil;
            // Check for success of loading the assets tracks.
            success = ([self.asset statusOfValueForKey:@"tracks" error:&localError] == AVKeyValueStatusLoaded);
            if (success)
            {
                // If the tracks loaded successfully, make sure that no file exists at the output path for the asset writer.
                NSFileManager *fm = [NSFileManager defaultManager];
                NSString *localOutputPath = [self.outputURL path];
                if ([fm fileExistsAtPath:localOutputPath])
                    success = [fm removeItemAtPath:localOutputPath error:&localError];
            }
            if (success)
                success = [self setupAssetReaderAndAssetWriter:&localError];
            if (success)
                success = [self startAssetReaderAndWriter:&localError];
            if (!success)
                [self readingAndWritingDidFinishSuccessfully:success withError:localError];
        });
    }];
}

- (BOOL)setupAssetReaderAndAssetWriter:(NSError **)outError
{
    self.assetReader = [[AVAssetReader alloc] initWithAsset:self.asset error:outError];
    BOOL success = (self.assetReader != nil);
    if (success)
    {
        // If the asset reader was successfully initialized, do the same for the asset writer.
        self.assetWriter = [[AVAssetWriter alloc] initWithURL:self.outputURL fileType:AVFileTypeQuickTimeMovie error:outError];
        success = (self.assetWriter != nil);
    }
    
    if (success)
    {
        // If the reader and writer were successfully initialized, grab the audio and video asset tracks that will be used.
        AVAssetTrack *assetAudioTrack = nil, *assetVideoTrack = nil;
        NSArray *audioTracks = [self.asset tracksWithMediaType:AVMediaTypeAudio];
        if ([audioTracks count] > 0)
            assetAudioTrack = [audioTracks objectAtIndex:0];
        NSArray *videoTracks = [self.asset tracksWithMediaType:AVMediaTypeVideo];
        if ([videoTracks count] > 0)
            assetVideoTrack = [videoTracks objectAtIndex:0];
        
        ///音頻輸入和輸出
        if (assetAudioTrack)
        {
            // If there is an audio track to read, set the decompression settings to Linear PCM and create the asset reader output.
            NSDictionary *decompressionAudioSettings = @{ AVFormatIDKey : [NSNumber numberWithUnsignedInt:kAudioFormatLinearPCM] };
            self.assetReaderAudioOutput = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:assetAudioTrack outputSettings:decompressionAudioSettings];
            [self.assetReader addOutput:self.assetReaderAudioOutput];
            // Then, set the compression settings to 128kbps AAC and create the asset writer input.
            AudioChannelLayout stereoChannelLayout = {
                .mChannelLayoutTag = kAudioChannelLayoutTag_Stereo,
                .mChannelBitmap = 0,
                .mNumberChannelDescriptions = 0
            };
            NSData *channelLayoutAsData = [NSData dataWithBytes:&stereoChannelLayout length:offsetof(AudioChannelLayout, mChannelDescriptions)];
            NSDictionary *compressionAudioSettings = @{
                                                       AVFormatIDKey         : [NSNumber numberWithUnsignedInt:kAudioFormatMPEG4AAC],
                                                       AVEncoderBitRateKey   : [NSNumber numberWithInteger:128000],
                                                       AVSampleRateKey       : [NSNumber numberWithInteger:44100],
                                                       AVChannelLayoutKey    : channelLayoutAsData,
                                                       AVNumberOfChannelsKey : [NSNumber numberWithUnsignedInteger:2]
                                                       };
            self.assetWriterAudioInput = [AVAssetWriterInput assetWriterInputWithMediaType:[assetAudioTrack mediaType] outputSettings:compressionAudioSettings];
            [self.assetWriter addInput:self.assetWriterAudioInput];
        }
        
        if (assetVideoTrack)
        {
            // If there is a video track to read, set the decompression settings for YUV and create the asset reader output.
            NSDictionary *decompressionVideoSettings = @{
                                                         (id)kCVPixelBufferPixelFormatTypeKey     : [NSNumber numberWithUnsignedInt:kCVPixelFormatType_422YpCbCr8],
                                                         (id)kCVPixelBufferIOSurfacePropertiesKey : [NSDictionary dictionary]
                                                         };
            self.assetReaderVideoOutput = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:assetVideoTrack outputSettings:decompressionVideoSettings];
            [self.assetReader addOutput:self.assetReaderVideoOutput];
            CMFormatDescriptionRef formatDescription = NULL;
            // Grab the video format descriptions from the video track and grab the first one if it exists.
            NSArray *videoFormatDescriptions = [assetVideoTrack formatDescriptions];
            if ([videoFormatDescriptions count] > 0)
                formatDescription = (__bridge CMFormatDescriptionRef)[videoFormatDescriptions objectAtIndex:0];
            CGSize trackDimensions = {
                .width = 0.0,
                .height = 0.0,
            };
            // If the video track had a format description, grab the track dimensions from there. Otherwise, grab them direcly from the track itself.
            if (formatDescription)
                trackDimensions = CMVideoFormatDescriptionGetPresentationDimensions(formatDescription, false, false);
            else
                trackDimensions = [assetVideoTrack naturalSize];
            NSDictionary *compressionSettings = nil;
            // If the video track had a format description, attempt to grab the clean aperture settings and pixel aspect ratio used by the video.
            if (formatDescription)
            {
                NSDictionary *cleanAperture = nil;
                NSDictionary *pixelAspectRatio = nil;
                CFDictionaryRef cleanApertureFromCMFormatDescription = CMFormatDescriptionGetExtension(formatDescription, kCMFormatDescriptionExtension_CleanAperture);
                if (cleanApertureFromCMFormatDescription)
                {
                    cleanAperture = @{
                                      AVVideoCleanApertureWidthKey            : (id)CFDictionaryGetValue(cleanApertureFromCMFormatDescription, kCMFormatDescriptionKey_CleanApertureWidth),
                                      AVVideoCleanApertureHeightKey           : (id)CFDictionaryGetValue(cleanApertureFromCMFormatDescription, kCMFormatDescriptionKey_CleanApertureHeight),
                                      AVVideoCleanApertureHorizontalOffsetKey : (id)CFDictionaryGetValue(cleanApertureFromCMFormatDescription, kCMFormatDescriptionKey_CleanApertureHorizontalOffset),
                                      AVVideoCleanApertureVerticalOffsetKey   : (id)CFDictionaryGetValue(cleanApertureFromCMFormatDescription, kCMFormatDescriptionKey_CleanApertureVerticalOffset)
                                      };
                }
                CFDictionaryRef pixelAspectRatioFromCMFormatDescription = CMFormatDescriptionGetExtension(formatDescription, kCMFormatDescriptionExtension_PixelAspectRatio);
                if (pixelAspectRatioFromCMFormatDescription)
                {
                    pixelAspectRatio = @{
                                         AVVideoPixelAspectRatioHorizontalSpacingKey : (id)CFDictionaryGetValue(pixelAspectRatioFromCMFormatDescription, kCMFormatDescriptionKey_PixelAspectRatioHorizontalSpacing),
                                         AVVideoPixelAspectRatioVerticalSpacingKey   : (id)CFDictionaryGetValue(pixelAspectRatioFromCMFormatDescription, kCMFormatDescriptionKey_PixelAspectRatioVerticalSpacing)
                                         };
                }
                // Add whichever settings we could grab from the format description to the compression settings dictionary.
                if (cleanAperture || pixelAspectRatio)
                {
                    NSMutableDictionary *mutableCompressionSettings = [NSMutableDictionary dictionary];
                    if (cleanAperture)
                        [mutableCompressionSettings setObject:cleanAperture forKey:AVVideoCleanApertureKey];
                    if (pixelAspectRatio)
                        [mutableCompressionSettings setObject:pixelAspectRatio forKey:AVVideoPixelAspectRatioKey];
                    compressionSettings = mutableCompressionSettings;
                }
            }
            // Create the video settings dictionary for H.264.
            NSMutableDictionary *videos = (NSMutableDictionary *) @{
                                                                           AVVideoCodecKey  : AVVideoCodecH264,
                                                                           AVVideoWidthKey  : [NSNumber numberWithDouble:trackDimensions.width],
                                                                           AVVideoHeightKey : [NSNumber numberWithDouble:trackDimensions.height]
                                                                           };
            NSMutableDictionary * videoSettings = [NSMutableDictionary dictionary];
            [videoSettings setDictionary:videos];
            // Put the compression settings into the video settings dictionary if we were able to grab them.
            if (compressionSettings)
                [videoSettings setObject:compressionSettings forKey:AVVideoCompressionPropertiesKey];
            // Create the asset writer input and add it to the asset writer.
            self.assetWriterVideoInput = [AVAssetWriterInput assetWriterInputWithMediaType:[assetVideoTrack mediaType] outputSettings:videoSettings];
            [self.assetWriter addInput:self.assetWriterVideoInput];
        }
    }
    return success;
}

- (BOOL)startAssetReaderAndWriter:(NSError **)outError
{
    BOOL success = YES;
    // Attempt to start the asset reader.
    success = [self.assetReader startReading];
    if (!success)
        *outError = [self.assetReader error];
    if (success)
    {
        // If the reader started successfully, attempt to start the asset writer.
        success = [self.assetWriter startWriting];
        if (!success)
            *outError = [self.assetWriter error];
    }
    
    if (success)
    {
        // If the asset reader and writer both started successfully, create the dispatch group where the reencoding will take place and start a sample-writing session.
        self.dispatchGroup = dispatch_group_create();
        [self.assetWriter startSessionAtSourceTime:kCMTimeZero];
        self.audioFinished = NO;
        self.videoFinished = NO;
        
        if (self.assetWriterAudioInput)
        {
            // If there is audio to reencode, enter the dispatch group before beginning the work.
            dispatch_group_enter(self.dispatchGroup);
            // Specify the block to execute when the asset writer is ready for audio media data, and specify the queue to call it on.
            [self.assetWriterAudioInput requestMediaDataWhenReadyOnQueue:self.rwAudioSerializationQueue usingBlock:^{
                // Because the block is called asynchronously, check to see whether its task is complete.
                if (self.audioFinished)
                    return;
                BOOL completedOrFailed = NO;
                // If the task isn't complete yet, make sure that the input is actually ready for more media data.
                while ([self.assetWriterAudioInput isReadyForMoreMediaData] && !completedOrFailed)
                {
                    // Get the next audio sample buffer, and append it to the output file.
                    CMSampleBufferRef sampleBuffer = [self.assetReaderAudioOutput copyNextSampleBuffer];
                    if (sampleBuffer != NULL)
                    {
                        BOOL success = [self.assetWriterAudioInput appendSampleBuffer:sampleBuffer];
                        CFRelease(sampleBuffer);
                        sampleBuffer = NULL;
                        completedOrFailed = !success;
                    }
                    else
                    {
                        completedOrFailed = YES;
                    }
                }
                if (completedOrFailed)
                {
                    // Mark the input as finished, but only if we haven't already done so, and then leave the dispatch group (since the audio work has finished).
                    BOOL oldFinished = self.audioFinished;
                    self.audioFinished = YES;
                    if (oldFinished == NO)
                    {
                        [self.assetWriterAudioInput markAsFinished];
                    }
                    dispatch_group_leave(self.dispatchGroup);
                }
            }];
        }
        
        if (self.assetWriterVideoInput)
        {
            // If we had video to reencode, enter the dispatch group before beginning the work.
            dispatch_group_enter(self.dispatchGroup);
            // Specify the block to execute when the asset writer is ready for video media data, and specify the queue to call it on.
            [self.assetWriterVideoInput requestMediaDataWhenReadyOnQueue:self.rwVideoSerializationQueue usingBlock:^{
                // Because the block is called asynchronously, check to see whether its task is complete.
                if (self.videoFinished)
                    return;
                BOOL completedOrFailed = NO;
                // If the task isn't complete yet, make sure that the input is actually ready for more media data.
                while ([self.assetWriterVideoInput isReadyForMoreMediaData] && !completedOrFailed)
                {
                    // Get the next video sample buffer, and append it to the output file.
                    CMSampleBufferRef sampleBuffer = [self.assetReaderVideoOutput copyNextSampleBuffer];
                    if (sampleBuffer != NULL)
                    {
                        BOOL success = [self.assetWriterVideoInput appendSampleBuffer:sampleBuffer];
                        CFRelease(sampleBuffer);
                        sampleBuffer = NULL;
                        completedOrFailed = !success;
                    }
                    else
                    {
                        completedOrFailed = YES;
                    }
                }
                if (completedOrFailed)
                {
                    // Mark the input as finished, but only if we haven't already done so, and then leave the dispatch group (since the video work has finished).
                    BOOL oldFinished = self.videoFinished;
                    self.videoFinished = YES;
                    if (oldFinished == NO)
                    {
                        [self.assetWriterVideoInput markAsFinished];
                    }
                    dispatch_group_leave(self.dispatchGroup);
                }
            }];
        }
        // Set up the notification that the dispatch group will send when the audio and video work have both finished.
        dispatch_group_notify(self.dispatchGroup, self.mainSerializationQueue, ^{
            BOOL finalSuccess = YES;
            NSError *finalError = nil;
            // Check to see if the work has finished due to cancellation.
            if (self.cancelled)
            {
                // If so, cancel the reader and writer.
                [self.assetReader cancelReading];
                [self.assetWriter cancelWriting];
            }
            else
            {
                // If cancellation didn't occur, first make sure that the asset reader didn't fail.
                if ([self.assetReader status] == AVAssetReaderStatusFailed)
                {
                    finalSuccess = NO;
                    finalError = [self.assetReader error];
                }
                // If the asset reader didn't fail, attempt to stop the asset writer and check for any errors.
                if (finalSuccess)
                {
                    finalSuccess = [self.assetWriter finishWriting];
                    if (!finalSuccess)
                        finalError = [self.assetWriter error];
                }
            }
            // Call the method to handle completion, and pass in the appropriate parameters to indicate whether reencoding was successful.
            [self readingAndWritingDidFinishSuccessfully:finalSuccess withError:finalError];
        });
    }
    // Return success here to indicate whether the asset reader and writer were started successfully.
    return success;
}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末输莺,一起剝皮案震驚了整個濱河市戚哎,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌嫂用,老刑警劉巖型凳,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異尸折,居然都是意外死亡啰脚,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進(jìn)店門实夹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來橄浓,“玉大人,你說我怎么就攤上這事亮航≥┦担” “怎么了?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵缴淋,是天一觀的道長准给。 經(jīng)常有香客問我,道長重抖,這世上最難降的妖魔是什么露氮? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮钟沛,結(jié)果婚禮上畔规,老公的妹妹穿的比我還像新娘。我一直安慰自己恨统,他們只是感情好叁扫,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布三妈。 她就那樣靜靜地躺著,像睡著了一般莫绣。 火紅的嫁衣襯著肌膚如雪畴蒲。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天对室,我揣著相機(jī)與錄音模燥,去河邊找鬼。 笑死软驰,一個胖子當(dāng)著我的面吹牛涧窒,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播锭亏,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼纠吴,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了慧瘤?” 一聲冷哼從身側(cè)響起戴已,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎锅减,沒想到半個月后糖儡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡怔匣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年握联,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片每瞒。...
    茶點(diǎn)故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡金闽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出剿骨,到底是詐尸還是另有隱情代芜,我是刑警寧澤,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布浓利,位于F島的核電站挤庇,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏贷掖。R本人自食惡果不足惜嫡秕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望苹威。 院中可真熱鬧淘菩,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽腹暖。三九已至汇在,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間脏答,已是汗流浹背糕殉。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留殖告,地道東北人阿蝶。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像黄绩,于是被迫代替她去往敵國和親羡洁。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評論 2 348

推薦閱讀更多精彩內(nèi)容