How to correctly read decoded PCM samples on iOS using AVAssetReader — currently incorrect decoding

后端 未结 3 1675
失恋的感觉
失恋的感觉 2020-12-24 11:34

I am currently working on an application as part of my Bachelor in Computer Science. The application will correlate data from the iPhone hardware (accelerometer, gps) and mu

相关标签:
3条回答
  • 2020-12-24 11:47

    I guess it is kind of late, but you could try this library:

    https://bitbucket.org/artgillespie/tslibraryimport

    After using this to save the audio into a file, you could process the data with render callbacks from MixerHost.

    0 讨论(0)
  • 2020-12-24 11:49

    If I were you I would either use kAudioUnitSubType_AudioFilePlayer to play the file and access its samples with the units render callback.

    Or

    Use ExtAudioFileRef to extract the samples straight to a buffer.

    0 讨论(0)
  • 2020-12-24 11:56

    Currently, I am also working on a project which involves extracting audio samples from iTunes Library into AudioUnit.

    The audiounit render call back is included for your reference. The input format is set as SInt16StereoStreamFormat.

    I have made use of Michael Tyson's circular buffer implementation - TPCircularBuffer as the buffer storage. Very easy to use and understand!!! Thanks Michael!

    - (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 讨论(0)
提交回复
热议问题