Как правильно читать декодированные образцы PCM на iOS с помощью AVAssetReader, в настоящее время некорректное декодирование

В настоящее время я работаю над приложением в рамках бакалавриата компьютерных наук. Приложение сопоставляет данные с оборудования iPhone (акселерометр, GPS) и воспроизводимой музыки.

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

В тот момент, когда я нахожусь прямо сейчас и где мне нужна помощь, я читаю сэмплы PCM из песен из библиотеки itunes и воспроизводю их с помощью аудиоустройства. В настоящее время реализация, с которой я хотел бы работать, делает следующее: выбирает случайную песню из iTunes и, при необходимости, читает из нее сэмплы и сохраняет в буфере, позволяет называть ее sampleBuffer. Позже в потребительской модели аудиоустройство (которое имеет микшер и выход remoteIO) имеет обратный вызов, где я просто копирую необходимое количество выборок из sampleBuffer в буфер, указанный в обратном вызове. То, что я затем слышу через динамики, не совсем то, что я ожидал; Я могу распознать, что песня проигрывается, но кажется, что она неправильно декодирована и в ней много шума! Я приложил изображение, которое показывает первые полсекунды (24576 отсчетов при 44,1 кГц), и это не похоже на нормальный результат. Прежде чем перейти к листингу, я проверил, не поврежден ли файл, точно так же я написал тестовые примеры для буфера (так что я знаю, что буфер не изменяет образцы), и хотя это может быть не лучший способ сделать это. (некоторые утверждают, что нужно идти по маршруту аудио-очереди), я хочу выполнить различные манипуляции с образцами, а также изменить песню до ее завершения, изменить композицию, которая воспроизводится, и т. д. Кроме того, возможно, есть некоторые неправильные настройки в аудио unit, однако график, отображающий образцы (который показывает, что образцы декодируются неправильно), берется прямо из буфера, поэтому я сейчас ищу только решение, почему чтение с диска и декодирование не работают правильно. Прямо сейчас я просто хочу поиграться в работе. Не могу публиковать изображения, потому что вы новичок в stackoverflow, поэтому вот ссылка на изображение: 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;
}

Ниже представлена ​​функция - (void) 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 и т. Д.). Любая помощь будет принята с благодарностью, кроме того, если вам понадобится какой-либо другой листинг моего кода или, возможно, то, как звучит вывод, я приложу его по запросу. Я сидел на этом в течение прошлой недели, пытаясь отладить его, и не нашел никакой помощи в Интернете - кажется, что все делают это на моем пути, но кажется, что только у меня есть эта проблема.

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

Питер


person Peter    schedule 20.02.2012    source источник


Ответы (3)


В настоящее время я также работаю над проектом, который включает извлечение аудиосэмплов из библиотеки 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];
}
person infiniteloop    schedule 05.03.2012
comment
Большое спасибо! Действительно полезно! - person Peter; 05.03.2012
comment
Что такое kUnitSize? а что такое kTotalBufferSize? - person the-a-train; 25.07.2012
comment
@smartfaceweb: В моем случае я использовал следующую настройку #define kUnitSize sizeof(AudioSampleType) #define kBufferUnit 655360 #define kTotalBufferSize kBufferUnit * kUnitSize - person infiniteloop; 26.07.2012
comment
@infiniteloop, не могли бы вы сообщить нам, работает ли этот код и с iOS? основываясь на моем ограниченном исследовании аудиоустройств, кажется, что iOS имеет гораздо меньше функций аудиоустройства, чем его аналог OSX - person abbood; 05.09.2012
comment
не берите в голову: developer.apple.com/library/ios/#documentation/MusicAudio/. - person abbood; 05.09.2012
comment
@ www.fossfactory.org работает на iOS 4.3-5.1.1, но на iOS 6 я еще не пробовал. - person infiniteloop; 06.09.2012
comment
@infiniteloop Мне было интересно об одном ... Предположим, у меня есть буфер TPCircularBuffer, объявленный как общедоступный iVar, как мне объявить свойство? это атомарно или неаотмично? автор упомянул что-то об атомарности в репозитории git hub: github.com/michaeltyson/TPCircularBuffer - person abbood; 19.09.2012
comment
@ www.fossfactory.org на самом деле я оборачиваю структуру TPCircularBuffer внутри typedef struct { AudioLibrary * audio; AudioUnit ioUnit; AudioUnit mixerUnit; BOOL playingiPod; BOOL bufferIsReady; TPCircularBuffer circularBuffer; Float64 samplingRate; } PlaybackState, *PlaybackStatePtr; и объявляю @property (assign) PlaybackState playbackState; в классе. - person infiniteloop; 21.09.2012
comment
Вы проверяете, что в кольцевом буфере доступно не менее 32768 доступных перед заполнением, и это то же самое число, которое возвращает CMSampleBufferGetTotalSampleSize, но мне интересно, может ли этот размер когда-либо отличаться по какой-либо причине. Есть ли какое-то конкретное значение для того, что есть? - person dizy; 26.07.2014

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

https://bitbucket.org/artgillespie/tslibraryimport

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

person Totoro    schedule 15.11.2012

На вашем месте я бы использовал kAudioUnitSubType_AudioFilePlayer для воспроизведения файла и доступа к его образцам с помощью обратного вызова рендеринга модулей.

Or

Используйте ExtAudioFileRef для извлечения семплов прямо в буфер.

person dubbeat    schedule 22.02.2012
comment
AudioFilePlayer позволяет мне указать только один файл для воспроизведения, и, кроме того, он не может быть из iTunes. ExtAudioFileRef также использует аудиосеансы, которые не разрешают доступ из iTunes (или, по крайней мере, я не могу заставить его работать). Кто-нибудь реализовал что-то подобное, что могло бы мне помочь? Пожалуйста - person Peter; 01.03.2012
comment
Боюсь, что у меня нет большого опыта работы с библиотекой itune. Но помогает ли это? subf Further.com/blog/2010/12/13/ - person dubbeat; 02.03.2012