avsubtitleWriter demo解析(四):writeSubtitles下篇

接着上篇。

首先,保存原视频的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");
		}
	}

如果原视频没有legible group,就使用我们的AVAssetWriterInput(s)创建一个。

// 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;
					}
				}
			}];
		}

写入字幕的buffer

// 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;
					}
				}
			}];
		}

完成一些后续操作,这边我们完成了读取操作,因此我们取消assetReader的读取。接着assetWriter进行真正写入的操作,并调用完成处理,我们检查assetWriter的状态,看是成功了还是失败了。这边读写是繁重的任务,采用了dispatch_group_enter、dispatch_group_wait对来使用GCD,提高性能。

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);

整个系列完成。第四篇还只是个毛坯,需要添加润色。

你可能感兴趣的:(Mac开发)