Извлекайте сэмплы из iPod Library в формате PCM и играйте со звуковыми эффектами - PullRequest
8 голосов
/ 25 января 2011

Я пытаюсь извлечь необработанные сэмплы PCM из MP3 в библиотеке iPod, чтобы я мог воспроизводить песню и управлять высотой звука, темпом и применять звуковые эффекты (например, фильтры).Я уже пошел по пути AVPlayer и AVAudioPlayer, которые оба вообще не позволяют очень сильно контролировать воспроизведение.

Код, приведенный ниже, насколько я получил с этим.Сейчас я нахожусь в точке, где я не знаю, что делать с CMSampleBufferRef в моем цикле while, потому что я не знаю, какую инфраструктуру использовать для воспроизведения аудио и применения таких эффектов.

Любая идея, чтобудет лучшим подходом для достижения этого?Я рассмотрел случаи, когда файл конвертировался с помощью AVAssetWriter, но это не поможет мне, потому что этот процесс занимает слишком много времени.Конечно, я могу просто прочитать сэмплы PCM в память для воспроизведения без необходимости сначала записывать их на диск?

Примечание: я знаю, что приведенный ниже код ссылается на mp3 в проекте, но я знаю, что этот подход будет работатьтак же, как если бы я вытягивал NSURL из MPMediaPropertyAssetURL


-(IBAction)loadTrack:(id)sender {

 NSString *songPath = [[NSBundle mainBundle] pathForResource:@"Smooth_Sub Focus_192" ofType:@"mp3"];
 NSURL *assetURL = [[NSURL alloc] initFileURLWithPath:songPath];

 AVURLAsset *songAsset = [AVURLAsset URLAssetWithURL:assetURL options:nil];

 NSError *assetError = nil;
 AVAssetReader *assetReader = [[AVAssetReader assetReaderWithAsset:songAsset
                error:&assetError] retain];
 if (assetError) {
  NSLog (@"Error: %@", assetError);
  return;
 }

 AVAssetReaderOutput *assetReaderOutput = [[AVAssetReaderAudioMixOutput assetReaderAudioMixOutputWithAudioTracks:songAsset.tracks
                           audioSettings: nil] retain];
 if (![assetReader canAddOutput:assetReaderOutput]) {
  NSLog (@"Incompatible Asser Reader Output");
  return;
 }

 [assetReader addOutput: assetReaderOutput];
 [assetReader startReading];

 CMSampleBufferRef nextBuffer;
 while (nextBuffer = [assetReaderOutput copyNextSampleBuffer]) {
  /* What Do I Do Here? */
 }

 [assetReader release];
 [assetReaderOutput release];

}

Ответы [ 4 ]

9 голосов
/ 25 января 2011

Я делаю нечто подобное в своем собственном коде.Следующий метод возвращает некоторые NSData для AVURLAsset:

- (NSData *)extractDataForAsset:(AVURLAsset *)songAsset {

    NSError * error = nil;
    AVAssetReader * reader = [[AVAssetReader alloc] initWithAsset:songAsset error:&error];

    AVAssetTrack * songTrack = [songAsset.tracks objectAtIndex:0];
    AVAssetReaderTrackOutput * output = [[AVAssetReaderTrackOutput alloc] initWithTrack:songTrack outputSettings:nil];
    [reader addOutput:output];
    [output release];

    NSMutableData * fullSongData = [[NSMutableData alloc] init];
    [reader startReading];

    while (reader.status == AVAssetReaderStatusReading){

        AVAssetReaderTrackOutput * trackOutput = (AVAssetReaderTrackOutput *)[reader.outputs objectAtIndex:0];
        CMSampleBufferRef sampleBufferRef = [trackOutput copyNextSampleBuffer];

        if (sampleBufferRef){
            CMBlockBufferRef blockBufferRef = CMSampleBufferGetDataBuffer(sampleBufferRef);

            size_t length = CMBlockBufferGetDataLength(blockBufferRef);
            UInt8 buffer[length];
            CMBlockBufferCopyDataBytes(blockBufferRef, 0, length, buffer);

            NSData * data = [[NSData alloc] initWithBytes:buffer length:length];
            [fullSongData appendData:data];
            [data release];

            CMSampleBufferInvalidate(sampleBufferRef);
            CFRelease(sampleBufferRef);
        }
    }

    if (reader.status == AVAssetReaderStatusFailed || reader.status == AVAssetReaderStatusUnknown){
        // Something went wrong. Handle it.
    }

    if (reader.status == AVAssetReaderStatusCompleted){
        // You're done. It worked.
    }

    [reader release];

    return [fullSongData autorelease];
}

Я бы порекомендовал сделать это в фоновом потоке, потому что это отнимает много времени.

Недостатком этого метода является то, что вся песнязагружен в память, которая, конечно, ограничена.

5 голосов
/ 21 сентября 2011

В дополнение к ответу Тома Ирвинга я предлагаю заменить

       UInt8 buffer[length];
       CMBlockBufferCopyDataBytes(blockBufferRef, 0, length, buffer);

        NSData * data = [[NSData alloc] initWithBytes:buffer length:length];

с

        NSMutableData * data = [[NSMutableData alloc] initWithLength:length];
        CMBlockBufferCopyDataBytes(blockBufferRef, 0, length, data.mutableBytes);

, что позволяет избежать двойной обработки сэмплов и уменьшает накладные расходы на использование памяти.

альтернативно, вы можете заключить [NSMutableData dataWithLength: length] в пул автоматического выпуска, как показано в этом ответе , к несвязанному, но похожему вопросу.

2 голосов
/ 09 апреля 2011

Я думаю, вы хотите, чтобы это было там, чтобы обеспечить его PCM ...

NSDictionary* outputSettingsDict = [[NSDictionary alloc] initWithObjectsAndKeys:

                        [NSNumber numberWithInt:kAudioFormatLinearPCM],AVFormatIDKey,
                   //     [NSNumber numberWithInt:44100.0],AVSampleRateKey, /*Not Supported*/
                   //     [NSNumber numberWithInt: 2],AVNumberOfChannelsKey,    /*Not Supported*/

                        [NSNumber numberWithInt:16],AVLinearPCMBitDepthKey,
                        [NSNumber numberWithBool:NO],AVLinearPCMIsBigEndianKey,
                        [NSNumber numberWithBool:NO],AVLinearPCMIsFloatKey,
                        [NSNumber numberWithBool:NO],AVLinearPCMIsNonInterleaved,

                        nil];

AVAssetReaderTrackOutput* output = [[AVAssetReaderTrackOutput alloc] initWithTrack:songTrack outputSettings:outputSettingsDict];
0 голосов
/ 22 августа 2012

Для продолжительности песни, я полагаю, вы можете просто запросить Asset песни таким образом:

float songTimeInSeconds = CMTimeGetSeconds(songAsset.duration);
    int songMinutes = (int)(songTimeInSeconds/60.);
    int songSeconds = (int)(songTimeInSeconds - 60.0*songMinutes);
...