Установка временного диапазона в 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. Вполне возможно, что минимум меняется с продолжительностью актива... честно говоря, я не уверен. Вместо этого я сохранил длительность (то есть бесконечность) и просто изменил время начала. Вместо того, чтобы обрабатывать весь пример, я копирую только один блок буфера, обрабатываю его и затем выполняю поиск в следующий раз. У меня все еще есть проблемы с кодом, но я опубликую это как другой вопрос, если я не могу понять это.

Другие вопросы по тегам