2012-02-20 2 views
4

В настоящее время я работаю над приложением как частью моего бакалавра по информатике. Приложение будет сопоставлять данные с аппаратного обеспечения iPhone (акселерометр, gps) и воспроизводимой музыки.Как правильно читать декодированные образцы PCM на iOS, используя AVAssetReader - в настоящее время некорректное декодирование

Проект все еще находится в зачаточном состоянии, работая над ним всего на 2 месяца.

В тот момент, когда я сейчас, и где мне нужна помощь, читает образцы PCM из песен из библиотеки itunes и воспроизводит их с помощью и аудиоустройства. В настоящее время реализация, которую я хотел бы выполнять, делает следующее: выбирает случайную песню из iTunes и читает сэмплы из нее, когда это необходимо, и сохраняет в буфере, позволяет называть его sampleBuffer. Позже в потребительской модели звуковой блок (с микшером и выходом remoteIO) имеет обратный вызов, где я просто копирую необходимое количество выборок из SampleBuffer в буфер, указанный в обратном вызове. То, что я потом слышу через динамики, - это нечто совсем не то, что я ожидаю; Я могу понять, что он играет песню, но кажется, что она неправильно декодирована, и у нее много шума! Я прикрепил изображение, которое показывает первые полсекунды (24576 выборок @ 44.1 кГц), и это не похоже на нормальный вывод. Прежде чем попасть в список, я проверил, что файл не поврежден, аналогично, я написал тестовые примеры для буфера (поэтому я знаю, что буфер не изменяет образцы), и хотя это может быть не лучший способ сделать это (некоторые утверждают, что идут по пути аудио очереди), я хочу выполнять различные манипуляции с образцами, а также изменять песню до ее завершения, перестраивать воспроизведение какой-либо песни и т. д. Кроме того, возможно, некоторые неправильные настройки в однако, график, который отображает сэмплы (который показывает, что образцы декодируются некорректно), берется прямо из буфера, поэтому я сейчас только ищу, чтобы решить, почему чтение с диска и декодирование работает неправильно. Прямо сейчас я просто хочу получить игру через работу. Cant разместить изображения, так как новые для StackOverflow так Heres ссылку на изображение: http://i.stack.imgur.com/RHjlv.jpg

Листинг:

Это где я установка в audioReadSettigns, которые будут использоваться для AVAssetReaderAudioMixOutput

// Set the read settings 
    audioReadSettings = [[NSMutableDictionary alloc] init]; 
    [audioReadSettings setValue:[NSNumber numberWithInt:kAudioFormatLinearPCM] 
         forKey:AVFormatIDKey]; 
    [audioReadSettings setValue:[NSNumber numberWithInt:16] forKey:AVLinearPCMBitDepthKey]; 
    [audioReadSettings setValue:[NSNumber numberWithBool:NO] forKey:AVLinearPCMIsBigEndianKey]; 
    [audioReadSettings setValue:[NSNumber numberWithBool:NO] forKey:AVLinearPCMIsFloatKey]; 
    [audioReadSettings setValue:[NSNumber numberWithBool:NO] forKey:AVLinearPCMIsNonInterleaved]; 
    [audioReadSettings setValue:[NSNumber numberWithFloat:44100.0] forKey:AVSampleRateKey]; 

теперь следующий код - метод, который получает NSString с persistant_id песни:

-(BOOL)setNextSongID:(NSString*)persistand_id { 

assert(persistand_id != nil); 

MPMediaItem *song = [self getMediaItemForPersistantID:persistand_id]; 
NSURL *assetUrl = [song valueForProperty:MPMediaItemPropertyAssetURL]; 
AVURLAsset *songAsset = [AVURLAsset URLAssetWithURL:assetUrl 
              options:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] 
                       forKey:AVURLAssetPreferPreciseDurationAndTimingKey]]; 


NSError *assetError = nil; 

assetReader = [[AVAssetReader assetReaderWithAsset:songAsset error:&assetError] retain]; 

if (assetError) { 
    NSLog(@"error: %@", assetError); 
    return NO; 
} 

CMTimeRange timeRange = CMTimeRangeMake(kCMTimeZero, songAsset.duration); 
[assetReader setTimeRange:timeRange]; 

track = [[songAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0]; 

assetReaderOutput = [AVAssetReaderAudioMixOutput assetReaderAudioMixOutputWithAudioTracks:[NSArray arrayWithObject:track] 
                      audioSettings:audioReadSettings]; 

if (![assetReader canAddOutput:assetReaderOutput]) { 
    NSLog(@"cant add reader output... die!"); 
    return NO; 
} 

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

// just getting some basic information about the track to print 
NSArray *formatDesc = ((AVAssetTrack*)[[assetReaderOutput audioTracks] objectAtIndex:0]).formatDescriptions; 
for (unsigned int i = 0; i < [formatDesc count]; ++i) { 
    CMAudioFormatDescriptionRef item = (CMAudioFormatDescriptionRef)[formatDesc objectAtIndex:i]; 
    const CAStreamBasicDescription *asDesc = (CAStreamBasicDescription*)CMAudioFormatDescriptionGetStreamBasicDescription(item); 
    if (asDesc) { 
     // get data 
     numChannels = asDesc->mChannelsPerFrame; 
     sampleRate = asDesc->mSampleRate; 
     asDesc->Print(); 
    } 
} 
[self copyEnoughSamplesToBufferForLength:24000]; 
return YES; 
} 

Ниже представлена ​​функция - (Недействительными) copyEnoughSamplesToBufferForLength:

-(void)copyEnoughSamplesToBufferForLength:(UInt32)samples_count { 

[w_lock lock]; 
int stillToCopy = 0; 
if (sampleBuffer->numSamples() < samples_count) { 
    stillToCopy = samples_count; 
} 

NSAutoreleasePool *apool = [[NSAutoreleasePool alloc] init]; 


CMSampleBufferRef sampleBufferRef; 
SInt16 *dataBuffer = (SInt16*)malloc(8192 * sizeof(SInt16)); 

int a = 0; 

while (stillToCopy > 0) { 

    sampleBufferRef = [assetReaderOutput copyNextSampleBuffer]; 
    if (!sampleBufferRef) { 
     // end of song or no more samples 
     return; 
    } 

    CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBufferRef); 
    CMItemCount numSamplesInBuffer = CMSampleBufferGetNumSamples(sampleBufferRef); 
    AudioBufferList audioBufferList; 

    CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(sampleBufferRef, 
                  NULL, 
                  &audioBufferList, 
                  sizeof(audioBufferList), 
                  NULL, 
                  NULL, 
                  0, 
                  &blockBuffer); 

    int data_length = floorf(numSamplesInBuffer * 1.0f); 

    int j = 0; 

    for (int bufferCount=0; bufferCount < audioBufferList.mNumberBuffers; bufferCount++) { 
     SInt16* samples = (SInt16 *)audioBufferList.mBuffers[bufferCount].mData; 
     for (int i=0; i < numSamplesInBuffer; i++) { 
      dataBuffer[j] = samples[i]; 
      j++; 
     } 
    } 

    CFRelease(sampleBufferRef); 
    sampleBuffer->putSamples(dataBuffer, j); 
    stillToCopy = stillToCopy - data_length; 
} 

free(dataBuffer); 
[w_lock unlock]; 
[apool release]; 
} 

Теперь sampleBuffer будет неправильно декодированные образцы. Может ли кто-нибудь помочь мне, почему это так? Это происходит для разных файлов в моей медиатеке iTunes (mp3, aac, wav и т. Д.). Любая помощь будет принята с благодарностью, кроме того, если вам нужен какой-либо другой список моего кода, или, возможно, то, что выводит звук, я буду прикреплять его к запросу. Я сидел на этом в течение прошлой недели, пытаясь отладить его и не нашел никакой помощи онлайн - все, кажется, делают это на моем пути, но кажется, что только у меня есть эта проблема.

Спасибо за любую помощь вообще!

Питер

ответ

10

В настоящее время я также работаю над проектом, который включает в себя извлечение аудиоматериалов из библиотеки iTunes в AudioUnit.

Обратный звонок визуального вывода включен для вашей справки. Формат ввода задается как SInt16StereoStreamFormat.

Я использовал круговую буферную реализацию Майкла Тайсона - TPCircularBuffer в качестве буферного хранилища. Очень проста в использовании и понимает !!! Спасибо, Майкл!

- (void) loadBuffer:(NSURL *)assetURL_ 
{ 
    if (nil != self.iPodAssetReader) { 
     [iTunesOperationQueue cancelAllOperations]; 

     [self cleanUpBuffer]; 
    } 

    NSDictionary *outputSettings = [NSDictionary dictionaryWithObjectsAndKeys: 
            [NSNumber numberWithInt:kAudioFormatLinearPCM], AVFormatIDKey, 
            [NSNumber numberWithFloat:44100.0], AVSampleRateKey, 
            [NSNumber numberWithInt:16], AVLinearPCMBitDepthKey, 
            [NSNumber numberWithBool:NO], AVLinearPCMIsNonInterleaved, 
            [NSNumber numberWithBool:NO], AVLinearPCMIsFloatKey, 
            [NSNumber numberWithBool:NO], AVLinearPCMIsBigEndianKey, 
            nil]; 

    AVURLAsset *asset = [AVURLAsset URLAssetWithURL:assetURL_ options:nil]; 
    if (asset == nil) { 
     NSLog(@"asset is not defined!"); 
     return; 
    } 

    NSLog(@"Total Asset Duration: %f", CMTimeGetSeconds(asset.duration)); 

    NSError *assetError = nil; 
    self.iPodAssetReader = [AVAssetReader assetReaderWithAsset:asset error:&assetError]; 
    if (assetError) { 
     NSLog (@"error: %@", assetError); 
     return; 
    } 

    AVAssetReaderOutput *readerOutput = [AVAssetReaderAudioMixOutput assetReaderAudioMixOutputWithAudioTracks:asset.tracks audioSettings:outputSettings]; 

    if (! [iPodAssetReader canAddOutput: readerOutput]) { 
     NSLog (@"can't add reader output... die!"); 
     return; 
    } 

    // add output reader to reader 
    [iPodAssetReader addOutput: readerOutput]; 

    if (! [iPodAssetReader startReading]) { 
     NSLog(@"Unable to start reading!"); 
     return; 
    } 

    // Init circular buffer 
    TPCircularBufferInit(&playbackState.circularBuffer, kTotalBufferSize); 

    __block NSBlockOperation * feediPodBufferOperation = [NSBlockOperation blockOperationWithBlock:^{ 
     while (![feediPodBufferOperation isCancelled] && iPodAssetReader.status != AVAssetReaderStatusCompleted) { 
      if (iPodAssetReader.status == AVAssetReaderStatusReading) { 
       // Check if the available buffer space is enough to hold at least one cycle of the sample data 
       if (kTotalBufferSize - playbackState.circularBuffer.fillCount >= 32768) { 
        CMSampleBufferRef nextBuffer = [readerOutput copyNextSampleBuffer]; 

        if (nextBuffer) { 
         AudioBufferList abl; 
         CMBlockBufferRef blockBuffer; 
         CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(nextBuffer, NULL, &abl, sizeof(abl), NULL, NULL, kCMSampleBufferFlag_AudioBufferList_Assure16ByteAlignment, &blockBuffer); 
         UInt64 size = CMSampleBufferGetTotalSampleSize(nextBuffer); 

         int bytesCopied = TPCircularBufferProduceBytes(&playbackState.circularBuffer, abl.mBuffers[0].mData, size); 

         if (!playbackState.bufferIsReady && bytesCopied > 0) { 
          playbackState.bufferIsReady = YES; 
         } 

         CFRelease(nextBuffer); 
         CFRelease(blockBuffer); 
        } 
        else { 
         break; 
        } 
       } 
      } 
     } 
     NSLog(@"iPod Buffer Reading Finished"); 
    }]; 

    [iTunesOperationQueue addOperation:feediPodBufferOperation]; 
} 

static OSStatus ipodRenderCallback (

            void      *inRefCon,  // A pointer to a struct containing the complete audio data 
            // to play, as well as state information such as the 
            // first sample to play on this invocation of the callback. 
            AudioUnitRenderActionFlags *ioActionFlags, // Unused here. When generating audio, use ioActionFlags to indicate silence 
            // between sounds; for silence, also memset the ioData buffers to 0. 
            const AudioTimeStamp  *inTimeStamp, // Unused here. 
            UInt32      inBusNumber, // The mixer unit input bus that is requesting some new 
            //  frames of audio data to play. 
            UInt32      inNumberFrames, // The number of frames of audio to provide to the buffer(s) 
            //  pointed to by the ioData parameter. 
            AudioBufferList    *ioData   // On output, the audio data to play. The callback's primary 
            //  responsibility is to fill the buffer(s) in the 
            //  AudioBufferList. 
            ) 
{ 
    Audio* audioObject = (Audio*)inRefCon; 

    AudioSampleType *outSample   = (AudioSampleType *)ioData->mBuffers[0].mData; 

    // Zero-out all the output samples first 
    memset(outSample, 0, inNumberFrames * kUnitSize * 2); 

    if (audioObject.playingiPod && audioObject.bufferIsReady) { 
     // Pull audio from circular buffer 
     int32_t availableBytes; 

     AudioSampleType *bufferTail  = TPCircularBufferTail(&audioObject.circularBuffer, &availableBytes); 

     memcpy(outSample, bufferTail, MIN(availableBytes, inNumberFrames * kUnitSize * 2)); 
     TPCircularBufferConsume(&audioObject.circularBuffer, MIN(availableBytes, inNumberFrames * kUnitSize * 2)); 
     audioObject.currentSampleNum += MIN(availableBytes/(kUnitSize * 2), inNumberFrames); 

     if (availableBytes <= inNumberFrames * kUnitSize * 2) { 
      // Buffer is running out or playback is finished 
      audioObject.bufferIsReady = NO; 
      audioObject.playingiPod = NO; 
      audioObject.currentSampleNum = 0; 

      if ([[audioObject delegate] respondsToSelector:@selector(playbackDidFinish)]) { 
       [[audioObject delegate] performSelector:@selector(playbackDidFinish)]; 
      } 
     } 
    } 

    return noErr; 
} 

- (void) setupSInt16StereoStreamFormat { 

    // The AudioUnitSampleType data type is the recommended type for sample data in audio 
    // units. This obtains the byte size of the type for use in filling in the ASBD. 
    size_t bytesPerSample = sizeof (AudioSampleType); 

    // Fill the application audio format struct's fields to define a linear PCM, 
    //  stereo, noninterleaved stream at the hardware sample rate. 
    SInt16StereoStreamFormat.mFormatID   = kAudioFormatLinearPCM; 
    SInt16StereoStreamFormat.mFormatFlags  = kAudioFormatFlagsCanonical; 
    SInt16StereoStreamFormat.mBytesPerPacket = 2 * bytesPerSample; // *** kAudioFormatFlagsCanonical <- implicit interleaved data => (left sample + right sample) per Packet 
    SInt16StereoStreamFormat.mFramesPerPacket = 1; 
    SInt16StereoStreamFormat.mBytesPerFrame  = SInt16StereoStreamFormat.mBytesPerPacket * SInt16StereoStreamFormat.mFramesPerPacket; 
    SInt16StereoStreamFormat.mChannelsPerFrame = 2;     // 2 indicates stereo 
    SInt16StereoStreamFormat.mBitsPerChannel = 8 * bytesPerSample; 
    SInt16StereoStreamFormat.mSampleRate  = graphSampleRate; 


    NSLog (@"The stereo stream format for the \"iPod\" mixer input bus:"); 
    [self printASBD: SInt16StereoStreamFormat]; 
} 
+0

Большое спасибо ! Действительно полезно! – Peter

+0

Что такое kUnitSize? и что такое kTotalBufferSize? –

+1

@smartfaceweb: В моем случае, я использовал следующие настройки '#define kUnitSize SizeOf (AudioSampleType) #define kBufferUnit 655360 #define kTotalBufferSize kBufferUnit * kUnitSize' – infiniteloop

0

Если бы я тебя, я бы либо использовать kAudioUnitSubType_AudioFilePlayer, чтобы воспроизвести файл и получить доступ к его образцы с блоками визуализации обратного вызова.

Или

Используйте ExtAudioFileRef, чтобы извлечь образцы прямо в буфер.

+0

AudioFilePlayer позволяет мне указывать только один файл для воспроизведения, а дальше он не может быть из iTunes. ExtAudioFileRef также использует аудио-сессии, которые не позволяют получать доступ из iTunes (или, по крайней мере, я не могу заставить его работать). Кто-нибудь реализовал что-то подобное, которое может мне помочь? Пожалуйста, – Peter

+0

У меня нет большого опыта работы с библиотекой itune, я боюсь. Помогает ли это? http://www.subfurther.com/blog/2010/12/13/from-ipod-library-to-pcm-samples-in-far-fewer-steps-than-were-previously-necessary/ – dubbeat

2

Я предполагаю, что это своего рода поздно, но вы можете попробовать эту библиотеку:

https://bitbucket.org/artgillespie/tslibraryimport

После использования этого, чтобы сохранить звук в файл, вы могли бы обрабатывать данные с визуализации обратных вызовов от MixerHost.