AVFoundation編程指南05-編輯

寫(xiě)在前面

喜歡AVFoundation資料的同學(xué)可以關(guān)注我的專題:《AVFoundation》專輯
也可以關(guān)注我的簡(jiǎn)書(shū)賬號(hào)

正文

AVFoundation框架提供了一組功能豐富的類,以便于編輯audio visual assetsAVFoundation的編輯API的核心是組合。合成只是來(lái)自一個(gè)或多個(gè)不同媒體assetstracks的集合。 AVMutableComposition類提供用于插入和刪除tracks以及管理其時(shí)間順序的接口。圖3-1顯示了如何將新組合從現(xiàn)有assets組合拼湊在一起以形成新的assets。如果你只想將多個(gè)asset按順序合并到一個(gè)文件中,那么就可以根據(jù)需要進(jìn)行詳細(xì)說(shuō)明。如果要在合成中的曲目上執(zhí)行任何自定義音頻或視頻處理,則需要分別合并音頻混合或視頻合成。

圖3-1 AVMutableComposition將assets組合在一起

使用AVMutableAudioMix類,你可以在合成中的音軌上執(zhí)行自定義音頻處理,如圖3-2所示。目前,你可以為音軌指定maximum volume或設(shè)置volume ramp
圖3-2 AVMutableAudioMix執(zhí)行音頻混合

你可以使用AVMutableVideoComposition類直接處理合成中的視頻tracks以進(jìn)行編輯,如圖3-3所示。使用單個(gè)視頻合成,你可以為輸出視頻指定所需的渲染大小和比例以及幀持續(xù)時(shí)間。通過(guò)視頻合成的說(shuō)明(由AVMutableVideoCompositionInstruction類表示),你可以修改視頻的背景顏色并應(yīng)用圖層指令。這些圖層指令(由AVMutableVideoCompositionLayerInstruction類表示)可用于將變換,變換rampsopacityopacity ramps應(yīng)用于合成中的視頻tracks。視頻合成類還使你能夠使用·animationTool屬性將Core Animation框架中的效果引入到視頻中。

圖3-3 AVMutableVideoComposition

要將合成與音頻混合和視頻合成相結(jié)合,可以使用AVAssetExportSession對(duì)象,如圖3-4所示。使用合成初始化導(dǎo)出會(huì)話,然后分別將音頻混合和視頻合成分配給audioMixvideoComposition屬性。

圖3-4使用AVAssetExportSession將媒體元素組合到輸出文件中

創(chuàng)建Composition

要?jiǎng)?chuàng)建自己的合成,請(qǐng)使用AVMutableComposition類。要將媒體數(shù)據(jù)添加到composition(合成)中,必須添加一個(gè)或多個(gè)composition tracks(合成軌道),由AVMutableCompositionTrack類表示。最簡(jiǎn)單的情況是創(chuàng)建一個(gè)包含一個(gè)視頻track和一個(gè)音頻track的可變組合:

AVMutableComposition *mutableComposition = [AVMutableComposition composition];
// Create the video composition track.
AVMutableCompositionTrack *mutableCompositionVideoTrack = [mutableComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
// Create the audio composition track.
AVMutableCompositionTrack *mutableCompositionAudioTrack = [mutableComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid]

初始化Composition Track選項(xiàng)

將新tracks添加到合成時(shí),必須同時(shí)提供媒體類型和tracks ID。雖然音頻和視頻是最常用的媒體類型,但你也可以指定其他媒體類型,例如AVMediaTypeSubtitleAVMediaTypeText

與某些視聽(tīng)數(shù)據(jù)相關(guān)聯(lián)的每個(gè)track都具有稱為track ID的唯一標(biāo)識(shí)符。如果指定kCMPersistentTrackID_Invalid作為首選track ID,則會(huì)自動(dòng)為你生成唯一標(biāo)識(shí)符并與track關(guān)聯(lián)。

將Audiovisual數(shù)據(jù)添加到Composition中

一旦你有一個(gè)或多個(gè)tracks的合成,你就可以開(kāi)始將媒體數(shù)據(jù)添加到適當(dāng)?shù)?code>track。要將媒體數(shù)據(jù)添加到合成軌道,你需要訪問(wèn)媒體數(shù)據(jù)所在的AVAsset對(duì)象。你可以使用可變組合track接口將同一基礎(chǔ)媒體類型的多個(gè)track放在同一track上。以下示例說(shuō)明如何將兩個(gè)不同的視頻資源track按順序添加到同一合成track

// You can retrieve AVAssets from a number of places, like the camera roll for example.
AVAsset *videoAsset = <#AVAsset with at least one video track#>;
AVAsset *anotherVideoAsset = <#another AVAsset with at least one video track#>;
// Get the first video track from each asset.
AVAssetTrack *videoAssetTrack = [[videoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
AVAssetTrack *anotherVideoAssetTrack = [[anotherVideoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
// Add them both to the composition.
[mutableCompositionVideoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero,videoAssetTrack.timeRange.duration) ofTrack:videoAssetTrack atTime:kCMTimeZero error:nil];
[mutableCompositionVideoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero,anotherVideoAssetTrack.timeRange.duration) ofTrack:anotherVideoAssetTrack atTime:videoAssetTrack.timeRange.duration error:nil];

檢索兼容的Composition Tracks

在可能的情況下,每種媒體類型只應(yīng)有一個(gè)composition track。兼容asset跟蹤的這種統(tǒng)一導(dǎo)致最小量的資源使用。在連續(xù)呈現(xiàn)媒體數(shù)據(jù)時(shí),你應(yīng)將相同類型的任何媒體數(shù)據(jù)放在同一composition track上。你可以查詢可變組合以查明是否存在與所需asset跟蹤兼容的任何合成track

AVMutableCompositionTrack *compatibleCompositionTrack = [mutableComposition mutableTrackCompatibleWithTrack:<#the AVAssetTrack you want to insert#>];
if (compatibleCompositionTrack) {
    // Implementation continues.
}

注意:在同一合成track上放置多個(gè)視頻片段可能會(huì)導(dǎo)致視頻片段之間轉(zhuǎn)換時(shí)播放中丟幀,尤其是在嵌入式設(shè)備上。為視頻片段選擇合成track的數(shù)量完全取決于你的應(yīng)用程序及其預(yù)期平臺(tái)的設(shè)計(jì)。

生成一個(gè)Volume Ramp

單個(gè)AVMutableAudioMix對(duì)象可以單獨(dú)對(duì)合成中的所有音頻track執(zhí)行自定義音頻處理。你可以使用audioMix類方法創(chuàng)建音頻混合,并使用AVMutableAudioMixInputParameters類的實(shí)例將音頻混合與合成中的特定track相關(guān)聯(lián)。音頻混合可用于改變音頻trackvolume。以下示例顯示如何在特定音頻軌道上設(shè)置volume ramp,以便在合成期間緩慢淡出音頻:

AVMutableAudioMix *mutableAudioMix = [AVMutableAudioMix audioMix];
// Create the audio mix input parameters object.
AVMutableAudioMixInputParameters *mixParameters = [AVMutableAudioMixInputParameters audioMixInputParametersWithTrack:mutableCompositionAudioTrack];
// Set the volume ramp to slowly fade the audio out over the duration of the composition.
[mixParameters setVolumeRampFromStartVolume:1.f toEndVolume:0.f timeRange:CMTimeRangeMake(kCMTimeZero, mutableComposition.duration)];
// Attach the input parameters to the audio mix.
mutableAudioMix.inputParameters = @[mixParameters];

自定義視頻處理過(guò)程

和音頻混合一樣,你只需要一個(gè)AVMutableVideoComposition對(duì)象即可在合成的視頻track上執(zhí)行所有自定義視頻處理。使用視頻合成,你可以直接為合成的視頻track設(shè)置適當(dāng)?shù)匿秩镜?code>size(尺寸),scale(比例)和frame(幀速率)。有關(guān)為這些屬性設(shè)置適當(dāng)值的詳細(xì)示例,請(qǐng)參閱Setting the Render Size and Frame Duration

更改構(gòu)圖的背景顏色

所有視頻合成還必須具有包含至少一個(gè)視頻合成指令的AVVideoCompositionInstruction對(duì)象的數(shù)組。你可以使用AVMutableVideoCompositionInstruction類來(lái)創(chuàng)建自己的視頻合成指令。使用視頻合成指令,你可以修改合成的背景顏色,指定是否需要后期處理或應(yīng)用圖層指令。

以下示例說(shuō)明如何創(chuàng)建視頻合成指令,將整個(gè)合成的背景顏色更改為紅色。

AVMutableVideoCompositionInstruction *mutableVideoCompositionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
mutableVideoCompositionInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, mutableComposition.duration);
mutableVideoCompositionInstruction.backgroundColor = [[UIColor redColor] CGColor]

Opacity Ramps的使用

視頻合成指令也可用于應(yīng)用視頻合成層指令。 AVMutableVideoCompositionLayerInstruction對(duì)象可以將變換,變換rampsopacityopacity ramps應(yīng)用于合成中的某個(gè)視頻track。視頻合成指令的layerInstructions數(shù)組中的層指令的順序決定了如何在該合成指令的持續(xù)時(shí)間內(nèi)對(duì)來(lái)自源track的視頻幀進(jìn)行分層和組合。以下代碼片段顯示如何設(shè)置opacity ramp漸變以在轉(zhuǎn)換到第二個(gè)視頻之前慢慢淡出合成中的第一個(gè)視頻:

AVAsset *firstVideoAssetTrack = <#AVAssetTrack representing the first video segment played in the composition#>;
AVAsset *secondVideoAssetTrack = <#AVAssetTrack representing the second video segment played in the composition#>;
// Create the first video composition instruction.
AVMutableVideoCompositionInstruction 
*firstVideoCompositionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
// Set its time range to span the duration of the first video track.
firstVideoCompositionInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, firstVideoAssetTrack.timeRange.duration);
// Create the layer instruction and associate it with the composition video track.
AVMutableVideoCompositionLayerInstruction *firstVideoLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:mutableCompositionVideoTrack];
// Create the opacity ramp to fade out the first video track over its entire duration.
[firstVideoLayerInstruction setOpacityRampFromStartOpacity:1.f toEndOpacity:0.f timeRange:CMTimeRangeMake(kCMTimeZero, firstVideoAssetTrack.timeRange.duration)];
// Create the second video composition instruction so that the second video track isn't transparent.
AVMutableVideoCompositionInstruction *secondVideoCompositionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
// Set its time range to span the duration of the second video track.
secondVideoCompositionInstruction.timeRange = CMTimeRangeMake(firstVideoAssetTrack.timeRange.duration, CMTimeAdd(firstVideoAssetTrack.timeRange.duration, secondVideoAssetTrack.timeRange.duration));
// Create the second layer instruction and associate it with the composition video track.
AVMutableVideoCompositionLayerInstruction *secondVideoLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:mutableCompositionVideoTrack];
// Attach the first layer instruction to the first video composition instruction.
firstVideoCompositionInstruction.layerInstructions = @[firstVideoLayerInstruction];
// Attach the second layer instruction to the second video composition instruction.
secondVideoCompositionInstruction.layerInstructions = @[secondVideoLayerInstruction];
// Attach both of the video composition instructions to the video composition.
AVMutableVideoComposition *mutableVideoComposition = [AVMutableVideoComposition videoComposition];
mutableVideoComposition.instructions = @[firstVideoCompositionInstruction, secondVideoCompositionInstruction]

結(jié)合核心動(dòng)畫(huà)效果

視頻合成可以通過(guò)animationTool屬性將Core Animation的強(qiáng)大功能添加到合成中。通過(guò)此動(dòng)畫(huà)工具,你可以完成諸如水印視頻和添加標(biāo)題或動(dòng)畫(huà)疊加層等任務(wù)。核心動(dòng)畫(huà)可以通過(guò)兩種不同的方式用于視頻合成:你可以將核心動(dòng)畫(huà)圖層添加為其自己的composition track,或者你可以直接將合成動(dòng)畫(huà)效果(使用核心動(dòng)畫(huà)圖層)渲染到合成中的視頻幀中。以下代碼通過(guò)在視頻中心添加水印來(lái)顯示后一個(gè)選項(xiàng):

CALayer *watermarkLayer = <#CALayer representing your desired watermark image#>;
CALayer *parentLayer = [CALayer layer];
CALayer *videoLayer = [CALayer layer];
parentLayer.frame = CGRectMake(0, 0, mutableVideoComposition.renderSize.width, mutableVideoComposition.renderSize.height);
videoLayer.frame = CGRectMake(0, 0, mutableVideoComposition.renderSize.width, mutableVideoComposition.renderSize.height);
[parentLayer addSublayer:videoLayer];
watermarkLayer.position = CGPointMake(mutableVideoComposition.renderSize.width/2, mutableVideoComposition.renderSize.height/4);
[parentLayer addSublayer:watermarkLayer];
mutableVideoComposition.animationTool = [AVVideoCompositionCoreAnimationTool videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:videoLayer inLayer:parentLayer];

綜述:組合多個(gè)Asset并將結(jié)果保存到相冊(cè)中

此簡(jiǎn)短的代碼示例說(shuō)明了如何組合兩個(gè)視頻asset track和音頻asset track來(lái)創(chuàng)建單個(gè)視頻文件。它顯示了如何:

注意:為了專注于最相關(guān)的代碼,此示例省略了完整應(yīng)用程序的幾個(gè)方面,例如內(nèi)存管理和錯(cuò)誤處理。要使用AVFoundation,你應(yīng)該有足夠的經(jīng)驗(yàn)使用Cocoa來(lái)推斷缺失的部分。

創(chuàng)建Composition

要將來(lái)自不同assettrack拼湊在一起,可以使用AVMutableComposition對(duì)象。創(chuàng)建合成并添加一個(gè)音頻和一個(gè)視頻track

AVMutableComposition *mutableComposition = [AVMutableComposition composition];
AVMutableCompositionTrack *videoCompositionTrack = [mutableComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
AVMutableCompositionTrack *audioCompositionTrack = [mutableComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];

添加Assets

一個(gè)空的composition對(duì)你是沒(méi)有意義的。將兩個(gè)視頻asset track和音頻asset track添加到composition中。

AVAssetTrack *firstVideoAssetTrack = [[firstVideoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
AVAssetTrack *secondVideoAssetTrack = [[secondVideoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
[videoCompositionTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, firstVideoAssetTrack.timeRange.duration) ofTrack:firstVideoAssetTrack atTime:kCMTimeZero error:nil];
[videoCompositionTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, secondVideoAssetTrack.timeRange.duration) ofTrack:secondVideoAssetTrack atTime:firstVideoAssetTrack.timeRange.duration error:nil];
[audioCompositionTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, CMTimeAdd(firstVideoAssetTrack.timeRange.duration, secondVideoAssetTrack.timeRange.duration)) ofTrack:[[audioAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0] atTime:kCMTimeZero error:nil]

注意:這假設(shè)你有兩個(gè)asset,每個(gè)asset至少包含一個(gè)視頻track,第三個(gè)asset包含至少一個(gè)音頻track。可以從相機(jī)膠卷中檢索視頻,并且可以從音樂(lè)庫(kù)或視頻本身檢索音頻track

檢查視頻方向

將視頻和音頻曲目添加到合成后,你需要確保兩個(gè)視頻track的方向都正確。默認(rèn)情況下,假設(shè)所有視頻track都處于橫向模式。如果你的視頻track是以縱向模式拍攝的,則導(dǎo)出時(shí)視頻將無(wú)法正確定位。同樣,如果你嘗試將縱向模式下的視頻鏡頭與橫向模式下的視頻鏡頭組合在一起,則導(dǎo)出會(huì)話將無(wú)法完成。

BOOL isFirstVideoPortrait = NO;
CGAffineTransform firstTransform = firstVideoAssetTrack.preferredTransform;
// Check the first video track's preferred transform to determine 
if it was recorded in portrait mode.
if (firstTransform.a == 0 && firstTransform.d == 0 && (firstTransform.b == 1.0 || firstTransform.b == -1.0) && (firstTransform.c == 1.0 || firstTransform.c == -1.0)) {
    isFirstVideoPortrait = YES;
}
BOOL isSecondVideoPortrait = NO;
CGAffineTransform secondTransform = secondVideoAssetTrack.preferredTransform;
// Check the second video track's preferred transform to determine if it was recorded in portrait mode.
if (secondTransform.a == 0 && secondTransform.d == 0 && (secondTransform.b == 1.0 || secondTransform.b == -1.0) && (secondTransform.c == 1.0 || secondTransform.c == -1.0)) {
    isSecondVideoPortrait = YES;
}
if ((isFirstVideoAssetPortrait && !isSecondVideoAssetPortrait) || (!isFirstVideoAssetPortrait && isSecondVideoAssetPortrait)) {
    UIAlertView *incompatibleVideoOrientationAlert = [[UIAlertView alloc] initWithTitle:@"Error!" message:@"Cannot combine a video shot in portrait mode with a video shot in landscape mode." delegate:self cancelButtonTitle:@"Dismiss" otherButtonTitles:nil];
    [incompatibleVideoOrientationAlert show];
    return;
}

應(yīng)用視頻合成層指令

一旦你知道視頻片段具有兼容的方向,你就可以對(duì)每個(gè)視頻片段應(yīng)用必要的圖層說(shuō)明,并將這些圖層說(shuō)明添加到視頻合成中。

AVMutableVideoCompositionInstruction *firstVideoCompositionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
// Set the time range of the first instruction to span the duration of the first video track.
firstVideoCompositionInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, firstVideoAssetTrack.timeRange.duration);
AVMutableVideoCompositionInstruction * secondVideoCompositionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
// Set the time range of the second instruction to span the duration of the second video track.
secondVideoCompositionInstruction.timeRange = CMTimeRangeMake(firstVideoAssetTrack.timeRange.duration, CMTimeAdd(firstVideoAssetTrack.timeRange.duration, secondVideoAssetTrack.timeRange.duration));
AVMutableVideoCompositionLayerInstruction *firstVideoLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoCompositionTrack];
// Set the transform of the first layer instruction to the preferred transform of the first video track.
[firstVideoLayerInstruction setTransform:firstTransform atTime:kCMTimeZero];
AVMutableVideoCompositionLayerInstruction *secondVideoLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoCompositionTrack];
// Set the transform of the second layer instruction to the preferred transform of the second video track.
[secondVideoLayerInstruction setTransform:secondTransform atTime:firstVideoAssetTrack.timeRange.duration];
firstVideoCompositionInstruction.layerInstructions = @[firstVideoLayerInstruction];
secondVideoCompositionInstruction.layerInstructions = @[secondVideoLayerInstruction];
AVMutableVideoComposition *mutableVideoComposition = [AVMutableVideoComposition videoComposition];
mutableVideoComposition.instructions = @[firstVideoCompositionInstruction, secondVideoCompositionInstruction];

所有AVAssetTrack對(duì)象都具有preferredTransform屬性,該屬性包含該asset track的方向信息。只要asset track顯示在屏幕上,就會(huì)應(yīng)用此轉(zhuǎn)換。在前面的代碼中,圖層指令的變換設(shè)置為asset track的變換,以便在調(diào)整渲染大小后,新合成中的視頻可以正確顯示。

設(shè)置渲染Size和幀持續(xù)時(shí)間

要完成視頻方向修復(fù),你必須相應(yīng)地調(diào)整renderSize屬性。你還應(yīng)該為frameDuration屬性選擇合適的值,例如1/30秒(或每秒30幀)。默認(rèn)情況下,renderScale屬性設(shè)置為1.0,這適用于此合成。

CGSize naturalSizeFirst, naturalSizeSecond;
// If the first video asset was shot in portrait mode, then so was the second one if we made it here.
if (isFirstVideoAssetPortrait) {
// Invert the width and height for the video tracks to ensure that they display properly.
    naturalSizeFirst = CGSizeMake(firstVideoAssetTrack.naturalSize.height, firstVideoAssetTrack.naturalSize.width);
    naturalSizeSecond = CGSizeMake(secondVideoAssetTrack.naturalSize.height, secondVideoAssetTrack.naturalSize.width);
}
else {
// If the videos weren't shot in portrait mode, we can just use their natural sizes.
    naturalSizeFirst = firstVideoAssetTrack.naturalSize;
    naturalSizeSecond = secondVideoAssetTrack.naturalSize;
}
float renderWidth, renderHeight;
// Set the renderWidth and renderHeight to the max of the two videos widths and heights.
if (naturalSizeFirst.width > naturalSizeSecond.width) {
    renderWidth = naturalSizeFirst.width;
}
else {
    renderWidth = naturalSizeSecond.width;
}
if (naturalSizeFirst.height > naturalSizeSecond.height) {
    renderHeight = naturalSizeFirst.height;
}
else {
    renderHeight = naturalSizeSecond.height;
}
mutableVideoComposition.renderSize = CGSizeMake(renderWidth, renderHeight);
// Set the frame duration to an appropriate value (i.e. 30 frames per second for video).
mutableVideoComposition.frameDuration = CMTimeMake(1,30);

導(dǎo)出合成并將其保存到相冊(cè)

此過(guò)程的最后一步是將整個(gè)構(gòu)圖導(dǎo)出到單個(gè)視頻文件中,并將該視頻保存到相機(jī)膠卷。你使用AVAssetExportSession對(duì)象來(lái)創(chuàng)建新的視頻文件,并將輸出文件的所需URL傳遞給它。然后,你可以使用ALAssetsLibrary類將生成的視頻文件保存到相冊(cè)。

// Create a static date formatter so we only have to initialize it once.
static NSDateFormatter *kDateFormatter;
if (!kDateFormatter) {
    kDateFormatter = [[NSDateFormatter alloc] init];
    kDateFormatter.dateStyle = NSDateFormatterMediumStyle;
    kDateFormatter.timeStyle = NSDateFormatterShortStyle;
}
// Create the export session with the composition and set the preset to the highest quality.
AVAssetExportSession *exporter = [[AVAssetExportSession alloc] initWithAsset:mutableComposition presetName:AVAssetExportPresetHighestQuality];
// Set the desired output URL for the file created by the export process.
exporter.outputURL = [[[[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:@YES error:nil] URLByAppendingPathComponent:[kDateFormatter stringFromDate:[NSDate date]]] URLByAppendingPathExtension:CFBridgingRelease(UTTypeCopyPreferredTagWithClass((CFStringRef)AVFileTypeQuickTimeMovie, kUTTagClassFilenameExtension))];
// Set the output file type to be a QuickTime movie.
exporter.outputFileType = AVFileTypeQuickTimeMovie;
exporter.shouldOptimizeForNetworkUse = YES;
exporter.videoComposition = mutableVideoComposition;
// Asynchronously export the composition to a video file and save this file to the camera roll once export completes.
[exporter exportAsynchronouslyWithCompletionHandler:^{
dispatch_async(dispatch_get_main_queue(), ^{
    if (exporter.status == AVAssetExportSessionStatusCompleted) {
        ALAssetsLibrary *assetsLibrary = [[ALAssetsLibrary alloc] init];
        if ([assetsLibrary videoAtPathIsCompatibleWithSavedPhotosAlbum:exporter.outputURL]) {
            [assetsLibrary writeVideoAtPathToSavedPhotosAlbum:exporter.outputURL completionBlock:NULL];
            }
        }
    });
}];
上一章 目錄 下一章
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,578評(píng)論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,701評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 178,691評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,974評(píng)論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,694評(píng)論 6 413
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 56,026評(píng)論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,015評(píng)論 3 450
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 43,193評(píng)論 0 290
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,719評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,668評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,151評(píng)論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,846評(píng)論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 35,255評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,592評(píng)論 1 295
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,394評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,635評(píng)論 2 380