4

I've managed to get the raw data from a MPMediaItem using an AVAssetReader after combining the answers of a couple of SO questions like this one and this one and a nice blog post. I'm also able to play this raw data using FMOD, but then a problem arises.

It appears the resulting audio is of lower quality than the original track. Though AVAssetTrack formatDescription tells me there are 2 channels in the data, the result sounds mono. It also sounds a bit dampened (less crispy) like the bitrate is lowered.

Am I doing something wrong or is the quality of the MPMediaItem data lowered on purpose by the AVAssetReader (because of piracy)?


#define OUTPUTRATE   44100

Initializing the AVAssetReader and AVAssetReaderTrackOutput

// prepare AVAsset and AVAssetReaderOutput etc
MPMediaItem* mediaItem = ...;
NSURL* ipodAudioUrl = [mediaItem valueForProperty:MPMediaItemPropertyAssetURL];
AVURLAsset * asset = [[AVURLAsset alloc] initWithURL:ipodAudioUrl options:nil];

NSError * error = nil;
assetReader = [[AVAssetReader alloc] initWithAsset:asset error:&error];

if(error)
    NSLog(@"error creating reader: %@", [error debugDescription]);

AVAssetTrack* songTrack = [asset.tracks objectAtIndex:0];
NSArray* trackDescriptions = songTrack.formatDescriptions;

numChannels = 2;
for(unsigned int i = 0; i < [trackDescriptions count]; ++i) 
{
    CMAudioFormatDescriptionRef item = (CMAudioFormatDescriptionRef)[trackDescriptions objectAtIndex:i];
    const AudioStreamBasicDescription* bobTheDesc = CMAudioFormatDescriptionGetStreamBasicDescription (item);
    if(bobTheDesc && bobTheDesc->mChannelsPerFrame == 1) {
        numChannels = 1;
    }
}   

NSDictionary* outputSettingsDict = [[[NSDictionary alloc] initWithObjectsAndKeys:

                                    [NSNumber numberWithInt:kAudioFormatLinearPCM],AVFormatIDKey,
                                    [NSNumber numberWithInt:OUTPUTRATE],AVSampleRateKey,
                                    [NSNumber numberWithInt:16],AVLinearPCMBitDepthKey,
                                    [NSNumber numberWithBool:NO],AVLinearPCMIsBigEndianKey,
                                    [NSNumber numberWithBool:NO],AVLinearPCMIsFloatKey,
                                    [NSNumber numberWithBool:NO],AVLinearPCMIsNonInterleaved,
                                    nil] autorelease];

AVAssetReaderTrackOutput * output = [[[AVAssetReaderTrackOutput alloc] initWithTrack:songTrack outputSettings:outputSettingsDict] autorelease];
[assetReader addOutput:output];
[assetReader startReading];

Initializing FMOD and the FMOD sound

// Init FMOD
FMOD_RESULT result = FMOD_OK;
unsigned int version = 0;

/*
 Create a System object and initialize
 */    
result = FMOD::System_Create(&system); 
ERRCHECK(result);

result = system->getVersion(&version);
ERRCHECK(result);

if (version < FMOD_VERSION)
{
    fprintf(stderr, "You are using an old version of FMOD %08x.  This program requires %08x\n", version, FMOD_VERSION);
    exit(-1);
}

result = system->setSoftwareFormat(OUTPUTRATE, FMOD_SOUND_FORMAT_PCM16, 1, 0, FMOD_DSP_RESAMPLER_LINEAR);
ERRCHECK(result);    

result = system->init(32, FMOD_INIT_NORMAL | FMOD_INIT_ENABLE_PROFILE, NULL);
ERRCHECK(result);


// Init FMOD sound stream

CMTimeRange timeRange = [songTrack timeRange];
float durationInSeconds = timeRange.duration.value / timeRange.duration.timescale;

FMOD_CREATESOUNDEXINFO exinfo = {0};
memset(&exinfo, 0, sizeof(FMOD_CREATESOUNDEXINFO));

exinfo.cbsize            = sizeof(FMOD_CREATESOUNDEXINFO);              /* required. */
exinfo.decodebuffersize  = OUTPUTRATE;                                  /* Chunk size of stream update in samples.  This will be the amount of data passed to the user callback. */
exinfo.length            = OUTPUTRATE * numChannels * sizeof(signed short) * durationInSeconds; /* Length of PCM data in bytes of whole song (for Sound::getLength) */
exinfo.numchannels       = numChannels;                                 /* Number of channels in the sound. */
exinfo.defaultfrequency  = OUTPUTRATE;                                  /* Default playback rate of sound. */
exinfo.format            = FMOD_SOUND_FORMAT_PCM16;                     /* Data format of sound. */
exinfo.pcmreadcallback   = pcmreadcallback;                             /* User callback for reading. */
exinfo.pcmsetposcallback = pcmsetposcallback;                           /* User callback for seeking. */

result = system->createStream(NULL, FMOD_OPENUSER, &exinfo, &sound);
ERRCHECK(result);

result = system->playSound(FMOD_CHANNEL_FREE, sound, false, &channel);
ERRCHECK(result);

Reading from the AVAssetReaderTrackOutput into a ring buffer

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

if (sampleBufferRef)
{
    AudioBufferList audioBufferList;
    CMBlockBufferRef blockBuffer;
    CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(sampleBufferRef, NULL, &audioBufferList, sizeof(audioBufferList), NULL, NULL, 0, &blockBuffer);

    if(blockBuffer == NULL)
    {
        stopLoading = YES;
        continue;
    }

    if(&audioBufferList == NULL)
    {
        stopLoading = YES;
        continue;
    }

    if(audioBufferList.mNumberBuffers != 1)
        NSLog(@"numBuffers = %lu", audioBufferList.mNumberBuffers);

    for( int y=0; y<audioBufferList.mNumberBuffers; y++ )
    {
        AudioBuffer audioBuffer = audioBufferList.mBuffers[y];
        SInt8 *frame = (SInt8*)audioBuffer.mData;

        for(int i=0; i<audioBufferList.mBuffers[y].mDataByteSize; i++)
        {
            ringBuffer->push_back(frame[i]);
        }
    }

    CMSampleBufferInvalidate(sampleBufferRef);
    CFRelease(sampleBufferRef);
}
Community
  • 1
  • 1
Jeroen Bouma
  • 593
  • 6
  • 16

2 Answers2

0

I'm not familiar with FMOD, so I can't comment there. AVAssetReader doesn't do any "copy protection" stuff, so that's not a worry. (If you can get the AVAssetURL, the track is DRM free)

Since you are using non-interleaved buffers, there will only be one buffer, so I guess your last bit of code might be wrong

Here's an example of some code that's working well for me. Btw, your for loop is probably not going to be very performant. You may consider using memcpy or something... If you are not restricted to your existing ring buffer, try TPCircularBuffer (https://github.com/michaeltyson/TPCircularBuffer) it is amazing.

CMSampleBufferRef nextBuffer = NULL;

if(_reader.status == AVAssetReaderStatusReading)
{
    nextBuffer = [_readerOutput copyNextSampleBuffer];
}                   

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

    // the correct way to get the number of bytes in the buffer
    size_t size = CMSampleBufferGetTotalSampleSize(nextBuffer);

    memcpy(ringBufferTail, abl.mBuffers[0].mData, size);

    CFRelease(nextBuffer);
    CFRelease(blockBuffer);
}

Hope this helps

yano
  • 4,095
  • 3
  • 35
  • 68
0

You're initialiazing FMOD to output mono audio. Try

result = system->setSoftwareFormat(OUTPUTRATE, FMOD_SOUND_FORMAT_PCM16, 2, 0, FMOD_DSP_RESAMPLER_LINEAR);
Rhythmic Fistman
  • 34,352
  • 5
  • 87
  • 159