Установка временного диапазона в AVAssetReader вызывает зависание
Итак, я пытаюсь сделать простой расчет по ранее записанному аудио (из AVAsset), чтобы создать визуальную форму волны. В настоящее время я делаю это путем усреднения набора сэмплов, размер которых определяется путем деления размера аудиофайла на разрешение, которое я хочу для формы волны.
Это все работает нормально, за исключением одной проблемы.... это слишком медленно. При работе в 3GS обработка аудиофайла занимает около 3% времени, необходимого для его воспроизведения, что является слишком медленным (например, обработка аудиофайла в течение 1 часа занимает около 2,5 минут). Я пытался максимально оптимизировать метод, но он не работает. Я опубликую код, который я использую для обработки файла. Возможно, кто-то сможет помочь с этим, но то, что я действительно ищу, - это способ обработки файла без необходимости проходить через каждый байт. Итак, скажем, учитывая разрешение 2000, я бы хотел получить доступ к файлу и взять образец в каждой из 2000 точек. Я думаю, что это будет намного быстрее, особенно если файл больше. Но единственный способ, которым я знаю, чтобы получить необработанные данные, - это доступ к аудиофайлу линейным способом. Есть идеи? Вот код, который я использую для обработки файла (обратите внимание, что все классовые переменные начинаются с '_'):
Так что я полностью изменил этот вопрос. Я запоздало понял, что 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
)}
Я использую профилировщик времени в приложении и да, он просто сидит с минимальным объемом обработки. Не могу понять, что происходит. Важно отметить, что этого не произойдет, если я не установлю свойство timeRange AVAssetReader. Я проверил, и значения для 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 ответ
Я наконец понял, почему. Очевидно, есть некоторая "минимальная" продолжительность, которую вы можете указать для timeRange AVAssetReader. Я не уверен, что именно этот минимум, где-то выше 1000, но менее 5000. Вполне возможно, что минимум меняется с продолжительностью актива... честно говоря, я не уверен. Вместо этого я сохранил длительность (то есть бесконечность) и просто изменил время начала. Вместо того, чтобы обрабатывать весь пример, я копирую только один блок буфера, обрабатываю его и затем выполняю поиск в следующий раз. У меня все еще есть проблемы с кодом, но я опубликую это как другой вопрос, если я не могу понять это.