質問

For my iOS application, I need to convert an MP3 File Stored in a NSData object to AAC format which will also be stored in a NSData object to be later streamed. I'm also doing some DSP in between the conversion, so I'm taking advantage of AudioFileOpenWithCallbacks to open the existing, in-Memory, MP3 File and this works fine.

However, I'm having trouble creating a read-able AAC file using AudioFileInitializeWithCallbacks (AFIWCB) to fill up an NSMutableData object. To test the audio, I'm writing the NSMutableData object to disk when the conversion is done, but when I examine the meta-data on this file, there is no bit rate or channel information and the file is un-playable. The file size is approximately correct though.

And if I skip over AFIWCB and write directly to disk using ExtAudioFileCreateWithURL, it works perfectly, though writing to disk is undesirable for my application.

Does anyone have any familiarity using AFIWCB to write audio to an in-memory buffer? The documentation isn't very clear and I have a feeling I'm leaving something out or not using the callbacks correctly.

Thanks for any help you can provide.

EDIT: Figured out my problem, it was in the outputWriteProc Callback. Fixed it below:

Code:

-(void) convertData: (NSData *) audioData {

    AudioFileID         refInputAudioFileID;            //these will be wrapped in Ext Audio File
    AudioFileID         refOutputAudioFileID;
    ExtAudioFileRef     inputFileID;                    //these deal with the actual reading and writing
    ExtAudioFileRef     outputFileID;

    // Client Audio Format Description
    AudioStreamBasicDescription clientFormat;
    memset(&clientFormat, 0, sizeof(clientFormat));
    clientFormat.mFormatID          = kAudioFormatLinearPCM;
    clientFormat.mFramesPerPacket   = 1;
    clientFormat.mChannelsPerFrame  = 2;
    clientFormat.mBitsPerChannel    = 32;
    clientFormat.mBytesPerPacket    = clientFormat.mBytesPerFrame = 4 * clientFormat.mChannelsPerFrame;
    clientFormat.mFormatFlags       = kAudioFormatFlagsNativeFloatPacked;// | kAudioFormatFlagIsNonInterleaved;
    clientFormat.mSampleRate        = 44100;

    //Output Audio Format Description
    AudioStreamBasicDescription outputFormat;
    memset(&outputFormat, 0, sizeof(outputFormat));
    outputFormat.mChannelsPerFrame  = 2;
    outputFormat.mSampleRate        = 44100;
    outputFormat.mFormatID          = kAudioFormatMPEG4AAC;
    outputFormat.mFormatFlags       = kMPEG4Object_AAC_Main;
    outputFormat.mBitsPerChannel    = 0;
    outputFormat.mBytesPerFrame     = 0;
    outputFormat.mBytesPerPacket    = 0;
    outputFormat.mFramesPerPacket   = 1024;

    //Open the Source Audio File (in Memory) and wrap it with an ExtAudioFile (this works fine)
    OSStatus result = AudioFileOpenWithCallbacks(audioData, readProc, 0, getSizeProc, 0, kAudioFileMP3Type, &refInputAudioFileID);
    if(result != noErr)
        [self CheckResult:result withMessage:@"AudioFileOpenWithCallbacks failed "];

    //2) wrap with ExtAudioFile (this works fine)
    result = ExtAudioFileWrapAudioFileID(refInputAudioFileID, false, &inputFileID);
    [self CheckResult:result withMessage:@"ExtAudioFileWrap failed for input audio "];

    UInt64 fileSizeInFrames;
    UInt32 sizeProp = sizeof(fileSizeInFrames);
    result = 0;
    result = ExtAudioFileGetProperty(inputFileID, kExtAudioFileProperty_FileLengthFrames, &sizeProp, &fileSizeInFrames);
    if(result!=noErr)
        [self CheckResult:result withMessage:@"ExtAudioFileGet Prop FileLengthFrames failed "];
    else
        sourceAudioFileSizeinFrames = fileSizeInFrames;

    //Initialize the destination audio file using NSMutableData and wrap it with ExtAudioFile (this is where I'm having problems)
    destAudioData = [[NSMutableData alloc] initWithCapacity:1000000];
    result = 0;
    result = AudioFileInitializeWithCallbacks(destAudioData, outputReadProc, outputWriteProc,
                                              getOutputSizeProc, setOutputSizeProc, kAudioFileM4AType,
                                              &outputFormat, 0, &refOutputAudioFileID);

    [self CheckResult:result withMessage:@"AudioFIleIWithCallbacks failed "];

    result = 0;
    result = ExtAudioFileWrapAudioFileID(refOutputAudioFileID, true, &outputFileID);
    [self CheckResult:result withMessage:@"ExtAudioFilWrap for dest audio failed "];

    UInt32 outputFormatSize = sizeof(outputFormat);
    result = 0;
    result = AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, NULL, &outputFormatSize, &outputFormat);
    [self CheckResult:result withMessage:@"AudioFormatGetProp failed on output audio "];

    // Set Up Client Formats for Input
    int size = sizeof(clientFormat);
    result = 0;
    result = ExtAudioFileSetProperty(inputFileID, kExtAudioFileProperty_ClientDataFormat, size, &clientFormat);
    [self CheckResult:result withMessage:@"Error on ExtAudioFileSetProperty ClientFormat on Input "];

    // Specify the software codec
    UInt32 codec = kAppleSoftwareAudioCodecManufacturer;
    result = 0;
    result = ExtAudioFileSetProperty(outputFileID, kExtAudioFileProperty_CodecManufacturer, sizeof(UInt32), &codec);
    [self CheckResult:result withMessage:@"Error Setting Audio Codec for Output File "];

    //specify client format on output
    size = sizeof(clientFormat);
    result = 0;
    result = ExtAudioFileSetProperty(outputFileID, kExtAudioFileProperty_ClientDataFormat, sizeof(clientFormat), &clientFormat);
    [self CheckResult:result withMessage:@"Error on ExtAudioFileSetProperty ClientDataFormat for Output File "];


    UInt64 totalFrames      = 0;
    int ioBufferSizeSamples = 1024;

    while (1) {

        UInt32 bufferByteSize       = ioBufferSizeSamples * 4 * 2;
        char srcBuffer[bufferByteSize];
        UInt32 numFrames            = (bufferByteSize/clientFormat.mBytesPerFrame);

        AudioBufferList fillBufList;
        fillBufList.mNumberBuffers  = 1;
        fillBufList.mBuffers[0].mNumberChannels     = clientFormat.mChannelsPerFrame;
        fillBufList.mBuffers[0].mDataByteSize       = bufferByteSize;
        fillBufList.mBuffers[0].mData               = srcBuffer;
        result = 0;

        //read samples
        result = ExtAudioFileRead(inputFileID, &numFrames, &fillBufList);

        if (result != noErr) {
            [self CheckResult:result withMessage:@"Error on ExtAudioFileRead for input "];
        totalFrames = 0;
            break;
          }
        if (!numFrames)
             break;

        /**********Do DSP HERE*****************/

        totalFrames = totalFrames + numFrames;

        //write output audio here
        result = 0;
        result = ExtAudioFileWrite(outputFileID,
                                   numFrames,
                                   &fillBufList);

        [self CheckResult:result withMessage:@"Error on ExtAudioFileWrite for output "];
    }

    // clean up
    result = 0;
    result = ExtAudioFileDispose(inputFileID);
    [self CheckResult:result withMessage:@"Error on ExtAudioFileDispose InputFileId "];

    result = 0;
    AudioFileClose(refInputAudioFileID);
    [self CheckResult:result withMessage:@"Error on AudioFile Clsoe InputFileId "];

    result = 0;
    ExtAudioFileDispose(outputFileID);
    [self CheckResult:result withMessage:@"Error on ExtAudioFileDispose OutputFileID "];

    result = 0;
    AudioFileClose(refOutputAudioFileID);
    [self CheckResult:result withMessage:@"Error on AudioFileClose OutputFileID "];

    //save the destination audio file here...
    NSString *destAudioPath     = [[Utils audioFilePathPrefix] stringByAppendingPathComponent:
                                   [NSString stringWithFormat:@"tone.m4a"]];

    NSURL *destURL = [NSURL fileURLWithPath:destAudioPath];

    BOOL writeOK = [destAudioData writeToURL:destURL atomically:YES];
    if(!writeOK)
        NSLog(@"problem writing the destination audio to its path \n");
}

/* *********These are the callbacks required for AudioFileOpen With Callbacks and they work fine **********/
static OSStatus readProc(void   *inClientData,
                         SInt64 position,
                         UInt32 requestCount,
                         void   *buffer,
                         UInt32 *actualCount)
{
    NSData *inAudioData     = (NSData *) inClientData;
    size_t dataSize         = inAudioData.length;
    size_t bytesToRead      = 0;

    if(position < dataSize) {
        size_t bytesAvailable = dataSize - position;
        bytesToRead = requestCount <= bytesAvailable ? requestCount : bytesAvailable;
        [inAudioData getBytes: buffer range:NSMakeRange(position, bytesToRead)];

        *actualCount = bytesToRead;
    } else {
        NSLog(@"data was not read \n");
        bytesToRead = 0;
        *actualCount = 0;
    }
    return noErr;
}

static SInt64 getSizeProc(void* inClientData) {
    NSData *inAudioData = (NSData *) inClientData;
    size_t dataSize = inAudioData.length;
    return dataSize;
}

/**************These are the callbacks for AudioFileInitializeWithCallbacks ********/

static OSStatus outputReadProc (void   *outClientData,
                                SInt64 outputReadPosition,
                                UInt32 outputReadRequestCount,
                                void   *outputReadBuffer,
                                UInt32 *outputReadActualCount)
{        
    NSData *inAudioData     = (NSData *) outClientData;
    size_t dataSize         = inAudioData.length;
    size_t bytesToRead      = 0;

    if(outputReadPosition < dataSize) {
        size_t bytesAvailable = dataSize - outputReadPosition;
        bytesToRead = outputReadRequestCount <= bytesAvailable ? outputReadRequestCount : bytesAvailable;
        [inAudioData getBytes: outputReadBuffer range:NSMakeRange(outputReadPosition, bytesToRead)];

        *outputReadActualCount = bytesToRead;
    } else {
        bytesToRead = 0;
        *outputReadActualCount = 0;
    }
    return noErr;
}

static OSStatus outputWriteProc(void    *outClientData,
                                SInt64  writePosition,
                                UInt32  writeRequestCount,
                                const void    *writeBuffer,
                                UInt32  *writeActualCount){

NSMutableData *outAudioData = (NSMutableData *) outClientData;
UInt32 dataLen = [outAudioData length];
if(writePosition + writeRequestCount - 1 > dataLen){
    [outAudioData increaseLengthBy:(writePosition + writeRequestCount - dataLen)];
}

[outAudioData replaceBytesInRange: NSMakeRange(writePosition, writeRequestCount) withBytes: writeBuffer];

*writeActualCount  = writeRequestCount;

return noErr;
}

static SInt64 getOutputSizeProc(void *outClientData) {
    NSMutableData *inAudioData = (NSMutableData *) outClientData;
    size_t dataSize = inAudioData.length;
    return dataSize;
}

static OSStatus setOutputSizeProc(void  *outClientData, SInt64 inSize){
    NSMutableData *inAudioData = (NSMutableData *)outClientData;
    [inAudioData setLength: inSize];
    return noErr;
}
役に立ちましたか?

解決

Figured out my problem - I was simply appending data to my outputWriteProc callback.

ExtAudioFileClose and AudioFileClose triggers the outputWriteProc callback to close the file and those functions want to overwrite data in different parts of the file. Appending the data was leaving me with a garbage header file and other corrupt parts.

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top