AVAssetReader 和 AVAssetWriter类提供的低级功能,能处理更复杂的媒体样本。
AVAssetReader 和 AVAssetWriter
AVAssetReader
AVAssetReader 用于从 AVAsset示例中读取媒体样本。
通常会配置一个或多个AVAssetReaderOutput实例,并通过 copyNextSampleBuffer 方法访问音频和视频帧。
一个资源读取器的内部通道都是以多线程的方式不断提取下一个可用样本的。这样可以在系统请求资源时最小化时延。
AVAssetWriter
AVAssetWriter 用于对媒体资源进行编码并将其写入到容器文件中(如MPEG-4)。
通常由一个或多个AVAssetWriterInput对象配置,用于附加将包含要写入容器的媒体样本的CMSampleBuffer对象。
AVAssetWriter可用于实时操作和离线操作两种:
- 实时。当处理实时资源时,比如从AVCaptureVideoDataOutput写入捕捉样本时,AVAssetWriterInput应该令expectsMediaDataInRealTime属性为YES类确保readyForMoreMediaData(指示保持数据样本交错的情况下是否可以附加更多信息)值被正确计算。
- 离线。当从离线资源中读取媒体资源时,比如从AVAssetReader读取样本buffer,仍然需要readyForMoreMediaData,再可以使用requestMediaDataWhenReadyOnQueue:usingBlock方法来控制数据的提供。
创建波形图
创建波形需要
读取:读取音频样本进行渲染,需要读取或者解压音频数据。
缩减:读取到的样本远比我们需要的多,可以将样本分成小的样本块,并在每个样本块上找到最大值最小值和平均值。
渲染:将缩减后的样本呈现在屏幕上。
读取
使用AVAssetReader实例从AVAsset中读取音频样本并返回一个NSData对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| + (void)loadAudioSamplesFromAsset:(AVAsset *)asset completionBlock:(THSampleDataCompletionBlock)completionBlock { NSString *tracks = @"tracks"; [asset loadValuesAsynchronouslyForKeys:@[tracks] completionHandler:^{ AVKeyValueStatus status = [asset statusOfValueForKey:tracks error:nil]; NSData *sampleData = nil; if (status == AVKeyValueStatusLoaded) { sampleData = [self readAudioSamplesFromAsset:asset]; } dispatch_async(dispatch_get_main_queue(), ^{ completionBlock(sampleData); }); }]; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| + (NSData *)readAudioSamplesFromAsset:(AVAsset *)asset { NSError *error = nil; AVAssetReader *assetReader = [[AVAssetReader alloc] initWithAsset:asset error:&error]; if (!assetReader) { NSLog(@"Error creating asset reader: %@", [error localizedDescription]); return nil; } AVAssetTrack *track = [[asset tracksWithMediaType:AVMediaTypeAudio] firstObject]; NSDictionary *outputSettings = @{ AVFormatIDKey : @(kAudioFormatLinearPCM), AVLinearPCMIsBigEndianKey : @NO, AVLinearPCMIsFloatKey : @NO, AVLinearPCMBitDepthKey : @(16) }; AVAssetReaderTrackOutput *trackOutput = [[AVAssetReaderTrackOutput alloc] initWithTrack:track outputSettings:outputSettings]; [assetReader addOutput:trackOutput]; [assetReader startReading]; NSMutableData *sampleData = [NSMutableData data]; while (assetReader.status == AVAssetReaderStatusReading) { CMSampleBufferRef sampleBuffer = [trackOutput copyNextSampleBuffer]; if (sampleBuffer) { CMBlockBufferRef blockBufferRef = CMSampleBufferGetDataBuffer(sampleBuffer); size_t length = CMBlockBufferGetDataLength(blockBufferRef); SInt16 sampleBytes[length]; CMBlockBufferCopyDataBytes(blockBufferRef, 0, length, sampleBytes); [sampleData appendBytes:sampleBytes length:length]; CMSampleBufferInvalidate(sampleBuffer); CFRelease(sampleBuffer); } } if (assetReader.status == AVAssetReaderStatusCompleted) { return sampleData; } else { NSLog(@"Failed to read audio samples from asset"); return nil; } }
|
缩减音频样本
处理带有音频信息的NSData对象,根据指定的大小,将样本分成一个个样本块,找到样本块中的最大样本,得到筛选结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| - (NSArray *)filteredSamplesForSize:(CGSize)size { NSMutableArray *filteredSamples = [[NSMutableArray alloc] init]; NSUInteger sampleCount = self.sampleData.length / sizeof(SInt16); NSUInteger binSize = sampleCount / size.width;
SInt16 *bytes = (SInt16 *) self.sampleData.bytes; SInt16 maxSample = 0; for (NSUInteger i = 0; i < sampleCount; i += binSize) { SInt16 sampleBin[binSize]; for (NSUInteger j = 0; j < binSize; j++) { sampleBin[j] = CFSwapInt16LittleToHost(bytes[i + j]); } SInt16 value = [self maxValueInArray:sampleBin ofSize:binSize]; [filteredSamples addObject:@(value)]; if (value > maxSample) { maxSample = value; } } CGFloat scaleFactor = (size.height / 2) / maxSample;
for (NSUInteger i = 0; i < filteredSamples.count; i++) { filteredSamples[i] = @([filteredSamples[i] integerValue] * scaleFactor); }
return filteredSamples; }
|
1 2 3 4 5 6 7 8 9 10
| - (SInt16)maxValueInArray:(SInt16[])values ofSize:(NSUInteger)size { SInt16 maxValue = 0; for (int i = 0; i < size; i++) { if (abs(values[i]) > maxValue) { maxValue = abs(values[i]); } } return maxValue; }
|
渲染音频样本
创建UIView子类, 使用QuartzCore渲染筛选后的结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| - (void)drawRect:(CGRect)rect { CGContextRef context = UIGraphicsGetCurrentContext(); CGContextScaleCTM(context, THWidthScaling, THHeightScaling); CGFloat xOffset = self.bounds.size.width - (self.bounds.size.width * THWidthScaling); CGFloat yOffset = self.bounds.size.height - (self.bounds.size.height * THHeightScaling); CGContextTranslateCTM(context, xOffset / 2, yOffset / 2); NSArray *filteredSamples = [self.filter filteredSamplesForSize:self.bounds.size];
CGFloat midY = CGRectGetMidY(rect); CGMutablePathRef halfPath = CGPathCreateMutable(); CGPathMoveToPoint(halfPath, NULL, 0.0f, midY); for (NSUInteger i = 0; i < filteredSamples.count; i++) { float sample = [filteredSamples[i] floatValue]; CGPathAddLineToPoint(halfPath, NULL, i, midY - sample); }
CGPathAddLineToPoint(halfPath, NULL, filteredSamples.count, midY); CGMutablePathRef fullPath = CGPathCreateMutable(); CGPathAddPath(fullPath, NULL, halfPath); w CGAffineTransform transform = CGAffineTransformIdentity; transform = CGAffineTransformTranslate(transform, 0, CGRectGetHeight(rect)); transform = CGAffineTransformScale(transform, 1.0, -1.0); CGPathAddPath(fullPath, &transform, halfPath);
CGContextAddPath(context, fullPath); CGContextSetFillColorWithColor(context, self.waveColor.CGColor); CGContextDrawPath(context, kCGPathFill); CGPathRelease(halfPath); CGPathRelease(fullPath); }
|