在 AVAssetReader 中设置时间范围会导致冻结
Posted
技术标签:
【中文标题】在 AVAssetReader 中设置时间范围会导致冻结【英文标题】:Setting Time Range in AVAssetReader causes freeze 【发布时间】:2012-05-05 01:57:28 【问题描述】:因此,我正在尝试对先前录制的音频(来自 AVAsset)进行简单计算,以创建波形视觉效果。我目前通过平均一组样本来做到这一点,其大小是通过将音频文件大小除以我想要的波形分辨率来确定的。
这一切都很好,除了一个问题......它太慢了。在 3GS 上运行,处理一个音频文件大约需要播放它所需时间的 3%,这很慢(例如,处理一个 1 小时的音频文件大约需要 2.5 分钟)。我试图尽可能优化该方法,但它不起作用。我将发布用于处理文件的代码。也许有人可以提供帮助,但我真正想要的是一种无需遍历每个字节即可处理文件的方法。因此,假设分辨率为 2,000,我想访问该文件并在 2,000 个点中的每一个点进行采样。我认为这会快很多,尤其是在文件较大的情况下。但我知道获取原始数据的唯一方法是以线性方式访问音频文件。有任何想法吗?这是我用来处理文件的代码(注意,所有类变量都以'_'开头):
所以我完全改变了这个问题。我姗姗来迟地意识到 AVAssetReader 有一个用于“寻找”的 timeRange 属性,这正是我正在寻找的(参见上面的原始问题)。此外,这个问题已经被问到并得到了回答(我只是之前没有找到),我不想重复问题。但是,我仍然遇到问题。我的应用程序冻结了一段时间,然后当我尝试copyNextSampleBuffer
时最终崩溃。我不确定发生了什么事。我似乎没有处于任何递归循环中,它只是永远不会从函数调用中返回。检查日志显示给我这个错误:
Exception Type: 00000020
Exception Codes: 0x8badf00d
Highlighted Thread: 0
Application Specific Information:
App[10570] has active assertions beyond permitted time:
(
<SBProcessAssertion: 0xddd9300> identifier: Suspending process: App[10570] permittedBackgroundDuration: 10.000000 reason: suspend owner pid:52 preventSuspend preventThrottleDownCPU preventThrottleDownUI
)
我在应用程序上使用了时间分析器,是的,它只是以最少的处理量就坐在那里。无法完全弄清楚发生了什么。需要注意的是,如果我不设置 AVAssetReader 的 timeRange 属性,则不会发生这种情况。我已经检查并且 timeRange 的值是有效的,但是由于某种原因设置它会导致问题。这是我的处理代码:
- (void) processSampleData
if (!_asset || CMTimeGetSeconds(_asset.duration) <= 0) return;
NSError *error = nil;
AVAssetTrack *songTrack = _asset.tracks.firstObject;
if (!songTrack) return;
NSDictionary *outputSettingsDict = [[NSDictionary alloc] initWithObjectsAndKeys:
[NSNumber numberWithInt:kAudioFormatLinearPCM],AVFormatIDKey,
[NSNumber numberWithInt:16], AVLinearPCMBitDepthKey,
[NSNumber numberWithBool:NO],AVLinearPCMIsBigEndianKey,
[NSNumber numberWithBool:NO],AVLinearPCMIsFloatKey,
[NSNumber numberWithBool:NO],AVLinearPCMIsNonInterleaved,
nil];
UInt32 sampleRate = 44100.0;
_channelCount = 1;
NSArray *formatDesc = songTrack.formatDescriptions;
for(unsigned int i = 0; i < [formatDesc count]; ++i)
CMAudioFormatDescriptionRef item = (__bridge_retained CMAudioFormatDescriptionRef)[formatDesc objectAtIndex:i];
const AudiostreamBasicDescription* fmtDesc = CMAudioFormatDescriptionGetStreamBasicDescription (item);
if(fmtDesc )
sampleRate = fmtDesc->mSampleRate;
_channelCount = fmtDesc->mChannelsPerFrame;
CFRelease(item);
UInt32 bytesPerSample = 2 * _channelCount; //Bytes are hard coded by AVLinearPCMBitDepthKey
_normalizedMax = 0;
_sampledData = [[NSMutableData alloc] init];
SInt16 *channels[_channelCount];
char *sampleRef;
SInt16 *samples;
NSInteger sampleTally = 0;
SInt16 cTotal;
_sampleCount = DefaultSampleSize * [UIScreen mainScreen].scale;
NSTimeInterval intervalBetweenSamples = _asset.duration.value / _sampleCount;
NSTimeInterval sampleSize = fmax(100, intervalBetweenSamples / _sampleCount);
double assetTimeScale = _asset.duration.timescale;
CMTimeRange timeRange = CMTimeRangeMake(CMTimeMake(0, assetTimeScale), CMTimeMake(sampleSize, assetTimeScale));
SInt16 totals[_channelCount];
@autoreleasepool
for (int i = 0; i < _sampleCount; i++)
AVAssetReader *reader = [AVAssetReader assetReaderWithAsset:_asset error:&error];
AVAssetReaderTrackOutput *trackOutput = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:songTrack outputSettings:outputSettingsDict];
[reader addOutput:trackOutput];
reader.timeRange = timeRange;
[reader startReading];
while (reader.status == AVAssetReaderStatusReading)
CMSampleBufferRef sampleBufferRef = [trackOutput copyNextSampleBuffer];
if (sampleBufferRef)
CMBlockBufferRef blockBufferRef = CMSampleBufferGetDataBuffer(sampleBufferRef);
size_t length = CMBlockBufferGetDataLength(blockBufferRef);
int sampleCount = length / bytesPerSample;
for (int i = 0; i < sampleCount ; i += _channelCount)
CMBlockBufferAccessDataBytes(blockBufferRef, i * bytesPerSample, _channelCount, channels, &sampleRef);
samples = (SInt16 *)sampleRef;
for (int channel = 0; channel < _channelCount; channel++)
totals[channel] += samples[channel];
sampleTally++;
CMSampleBufferInvalidate(sampleBufferRef);
CFRelease(sampleBufferRef);
for (int i = 0; i < _channelCount; i++)
cTotal = abs(totals[i] / sampleTally);
if (cTotal > _normalizedMax) _normalizedMax = cTotal;
[_sampledData appendBytes:&cTotal length:sizeof(cTotal)];
totals[i] = 0;
sampleTally = 0;
timeRange.start = CMTimeMake((intervalBetweenSamples * (i + 1)) - sampleSize, assetTimeScale); //Take the sample just before the interval
_assetNeedsProcessing = NO;
【问题讨论】:
【参考方案1】:我终于知道为什么了。显然,您可以为 AVAssetReader 的 timeRange 指定某种“最小”持续时间。我不确定这个最小值到底是多少,高于 1,000 但低于 5,000。资产的持续时间可能会发生最小变化……老实说,我不确定。相反,我保持持续时间(无限大)相同,只是更改了开始时间。我没有处理整个样本,而是只复制一个缓冲区块,处理它然后寻找下一次。我的代码仍然有问题,但如果我无法弄清楚,我会将其作为另一个问题发布。
【讨论】:
以上是关于在 AVAssetReader 中设置时间范围会导致冻结的主要内容,如果未能解决你的问题,请参考以下文章