接着上篇。
首先,保存原视频的track group,AVAssetTrackGroup是一组track,同时只能播放其中一条track,但是不同的AVAssetTrackGroup中的track可以同时播放。通过asset.trackGroups可以获取某个asset的所有trackGroup。通过检查某个trackGroup的trackIDs,你可以知道哪些track是一个group的。前面我们已经把每条track和对应的trackID放到字典assetWriterInputsCorrespondingToOriginalTrackIDs中,这里就可以派上用场了。每个group要设置一个默认的track,通过trackID获取asset的某条track,然后检查其enabled属性,yes就是默认track,no则不是。
接着检查有没有legible group,可以通过TrackID来获取对应的track,然后使用hasMediaCharacteristic:AVMediaCharacteristicLegible检查track是否是legible类型。如果有,就把subtitlesInput加入iputs数组。接着创建AVAssetWriterInputGroup,把inputs数组和defaultInput指定给AVAssetWriterInputGroup便利构造器。我们回过头来捋一下思路,iputs数组里有某个track group的track对应的AVAssetWriterInput,如果该group是音视频的,很简单,我们单纯从assetWriterInputsCorrespondingToOriginalTrackIDs取出来AVAssetWriterInput加入iputs数组。如果是legible的,那么我们把我们新建的字幕(组,可能有多个字幕)AVAssetWriterInput(s,表明可能是个数组)加入该数组。然后将group加到assetWriter。
// Preserve track groups from the original asset
BOOL groupedSubtitles = NO;
for (AVAssetTrackGroup *trackGroup in asset.trackGroups)
{
// Collect the inputs that correspond to the group's track IDs in an array
NSMutableArray *inputs = [NSMutableArray array];
AVAssetWriterInput *defaultInput;
for (NSNumber *trackID in trackGroup.trackIDs)
{
AVAssetWriterInput *input = assetWriterInputsCorrespondingToOriginalTrackIDs[trackID];
if (input)
[inputs addObject:input];
// Determine which of the inputs is the default according to the enabled state of the corresponding tracks
if (!defaultInput && [asset trackWithTrackID:(CMPersistentTrackID)[trackID intValue]].enabled)
defaultInput = input;
}
// See if this is a legible (all of the tracks have characteristic AVMediaCharacteristicLegible), and group the new subtitle tracks with it if so
BOOL isLegibleGroup = NO;
for (NSNumber *trackID in trackGroup.trackIDs)
{
if ([[asset trackWithTrackID:(CMPersistentTrackID)[trackID intValue]] hasMediaCharacteristic:AVMediaCharacteristicLegible])
{
isLegibleGroup = YES;
}
else if (isLegibleGroup)
{
isLegibleGroup = NO;
break;
}
}
// If it is a legible group, add the new subtitles to this group
if (!groupedSubtitles && isLegibleGroup)
{
[inputs addObjectsFromArray:newSubtitlesInputs];
groupedSubtitles = YES;
}
AVAssetWriterInputGroup *inputGroup = [AVAssetWriterInputGroup assetWriterInputGroupWithInputs:inputs defaultInput:defaultInput];
if ([assetWriter canAddInputGroup:inputGroup])
{
[assetWriter addInputGroup:inputGroup];
}
else
{
NSLog(@"cannot add asset writer group");
}
}
// If no legible group was found to add the new subtitles to, create a group for them (if there are any)
if (!groupedSubtitles && (newSubtitlesInputs.count > 0))
{
AVAssetWriterInputGroup *inputGroup = [AVAssetWriterInputGroup assetWriterInputGroupWithInputs:newSubtitlesInputs defaultInput:nil];
if ([assetWriter canAddInputGroup:inputGroup])
{
[assetWriter addInputGroup:inputGroup];
}
else
{
NSLog(@"cannot add asset writer group");
}
}
保存原视频的track引用,这里是为track添加相关联的track。譬如说我切换音轨,对应的字幕也发生切换,那么这里就使用到了关联track。
先使用tracks属性遍历asset的track,然后使用availableTrackAssociationTypes属性遍历某条track的关联track的类型。这边需要注意的是,在av中一般都是以类型来区分的,虽然也有ID类型,因为很多都是数组的,所以使用类型会比较有意义。因此我们接着遍历关联track类型数组,根据类型,使用associatedTracksOfType方法从该track中取到关联的track(s)。然后我们建立一个associatedTrackIDs数组来存放这些tracks对应的id,然后将这个数组和对应的类型存入trackReferencesForTrack字典中。这样下来,我们就获得了某条track对应的关联tracks的IDs。一个asset有很多条track,因此最后我们把某条track的ID和其关联track的ID数组存入trackReferencesCorrespondingToOriginalTrackIDs字典。当然这样我们只完成了ID的对应关系。接着我们要把ID转化为AVAssetWriterInput。我们最终的目的是要把referencingInput加上关联的referencedInput,使用addTrackAssociationWithTrackOfInput:type:方法。感觉不会再爱了~~~~~
// Preserve track references from original asset
NSMutableDictionary *trackReferencesCorrespondingToOriginalTrackIDs = [NSMutableDictionary dictionary];
for (AVAssetTrack *track in asset.tracks)
{
NSMutableDictionary *trackReferencesForTrack = [NSMutableDictionary dictionary];
NSMutableSet *availableTrackAssociatonTypes = [NSMutableSet setWithArray:track.availableTrackAssociationTypes];
for (NSString *trackAssociationType in availableTrackAssociatonTypes)
{
NSArray *associatedTracks = [track associatedTracksOfType:trackAssociationType];
if (associatedTracks.count > 0)
{
NSMutableArray *associatedTrackIDs = [NSMutableArray arrayWithCapacity:associatedTracks.count];
for (AVAssetTrack *associatedTrack in associatedTracks)
{
[associatedTrackIDs addObject:@(associatedTrack.trackID)];
}
trackReferencesForTrack[trackAssociationType] = associatedTrackIDs;
}
}
trackReferencesCorrespondingToOriginalTrackIDs[@(track.trackID)] = trackReferencesForTrack;
}
for (NSNumber *referencingTrackIDKey in trackReferencesCorrespondingToOriginalTrackIDs)
{
AVAssetWriterInput *referencingInput = assetWriterInputsCorrespondingToOriginalTrackIDs[referencingTrackIDKey];
NSDictionary *trackReferences = trackReferencesCorrespondingToOriginalTrackIDs[referencingTrackIDKey];
for (NSString *trackReferenceTypeKey in trackReferences)
{
NSArray *referencedTrackIDs = trackReferences[trackReferenceTypeKey];
for (NSNumber *thisReferencedTrackID in referencedTrackIDs)
{
AVAssetWriterInput *referencedInput = assetWriterInputsCorrespondingToOriginalTrackIDs[thisReferencedTrackID];
if (referencingInput && referencedInput && [referencingInput canAddTrackAssociationWithTrackOfInput:referencedInput type:trackReferenceTypeKey])
[referencingInput addTrackAssociationWithTrackOfInput:referencedInput type:trackReferenceTypeKey];
}
}
}
写入原视频的内容。这是启动assetWriter,告诉它可以写入output了。startSessionAtSourceTime有待了解。接着叫assetReader可以开始读取。现在我们要处理的资源都在字典数组inputsOutputs中。我们分别取出:
AVAssetWriterInput *input = inputOutput[@"input"];
AVAssetReaderTrackOutput *assetReaderTrackOutput = inputOutput[@"output"];
现在让AVAssetWriterInput去请求media Data,当请求成功就处理。这时候我们检查AVAssetWriterInput的状态是不是isReadyForMoreMediaData。如果是,我们就从assetReaderTrackOutput 调用copyNextSampleBuffer获取一个sampleBuffer。获取成功,就让AVAssetWriterInput完成拼接操作。如果获取不到,我们就标记AVAssetWriterInput为完成。检查assetReader的状态看看是完成还是失败还是等待什么。
// Write the movie
if ([assetWriter startWriting])
{
[assetWriter startSessionAtSourceTime:kCMTimeZero];
dispatch_group_t dispatchGroup = dispatch_group_create();
[assetReader startReading];
// Write samples from AVAssetReaderTrackOutputs
for (NSDictionary *inputOutput in inputsOutputs)
{
dispatch_group_enter(dispatchGroup);
dispatch_queue_t requestMediaDataQueue = dispatch_queue_create("request media data", DISPATCH_QUEUE_SERIAL);
AVAssetWriterInput *input = inputOutput[@"input"];
AVAssetReaderTrackOutput *assetReaderTrackOutput = inputOutput[@"output"];
[input requestMediaDataWhenReadyOnQueue:requestMediaDataQueue usingBlock:^{
while ([input isReadyForMoreMediaData])
{
CMSampleBufferRef nextSampleBuffer = [assetReaderTrackOutput copyNextSampleBuffer];
if (nextSampleBuffer)
{
[input appendSampleBuffer:nextSampleBuffer];
CFRelease(nextSampleBuffer);
}
else
{
[input markAsFinished];
dispatch_group_leave(dispatchGroup);
if (assetReader.status == AVAssetReaderStatusFailed)
NSLog(@"the reader failed: %@", assetReader.error);
break;
}
}
}];
}
// Write samples from SubtitlesTextReaders
for (NSDictionary *subtitlesInputOutput in subtitlesInputsOutputs)
{
dispatch_group_enter(dispatchGroup);
dispatch_queue_t requestMediaDataQueue = dispatch_queue_create("request media data", DISPATCH_QUEUE_SERIAL);
AVAssetWriterInput *input = subtitlesInputOutput[@"input"];
SubtitlesTextReader *subtitlesTextReader = subtitlesInputOutput[@"output"];
[input requestMediaDataWhenReadyOnQueue:requestMediaDataQueue usingBlock:^{
while ([input isReadyForMoreMediaData])
{
CMSampleBufferRef nextSampleBuffer = [subtitlesTextReader copyNextSampleBuffer];
if (nextSampleBuffer)
{
[input appendSampleBuffer:nextSampleBuffer];
CFRelease(nextSampleBuffer);
}
else
{
[input markAsFinished];
dispatch_group_leave(dispatchGroup);
break;
}
}
}];
}
dispatch_group_wait(dispatchGroup, DISPATCH_TIME_FOREVER);
[assetReader cancelReading];
dispatch_group_enter(dispatchGroup);
[assetWriter finishWritingWithCompletionHandler:^(void) {
if (AVAssetWriterStatusCompleted == assetWriter.status)
NSLog(@"writing success to %@", assetWriter.outputURL);
else if (AVAssetWriterStatusFailed == assetWriter.status)
NSLog (@"writer failed with error: %@", assetWriter.error);
dispatch_group_leave(dispatchGroup);
}];
dispatch_group_wait(dispatchGroup, DISPATCH_TIME_FOREVER);