如何向后播放音频?

时间:2012-08-19 13:53:45

标签: iphone ios ipad audio core-audio

有些人建议从头到尾读取音频数据并创建一个从头到尾写的副本,然后简单地播放反转的音频数据。

iOS现有示例是如何完成的吗?

我找到了一个名为MixerHost的示例项目,它在某些时候使用了一个 AudioUnitSampleType保存已从文件中读取的音频数据,并将其分配给缓冲区。

这被定义为:

typedef SInt32 AudioUnitSampleType;
#define kAudioUnitSampleFractionBits 24

根据Apple的说法:

  

音频单元和其他音频的规范音频样本类型   iPhone OS中的处理是8.24位的非交错线性PCM   定点样本。

因此换句话说,它包含非交错的线性PCM音频数据。

但我无法弄清楚这些数据的读取位置以及存储位置。这是加载音频数据并缓冲它的代码:

- (void) readAudioFilesIntoMemory {

    for (int audioFile = 0; audioFile < NUM_FILES; ++audioFile)  {

        NSLog (@"readAudioFilesIntoMemory - file %i", audioFile);

        // Instantiate an extended audio file object.
        ExtAudioFileRef audioFileObject = 0;

        // Open an audio file and associate it with the extended audio file object.
        OSStatus result = ExtAudioFileOpenURL (sourceURLArray[audioFile], &audioFileObject);

        if (noErr != result || NULL == audioFileObject) {[self printErrorMessage: @"ExtAudioFileOpenURL" withStatus: result]; return;}

        // Get the audio file's length in frames.
        UInt64 totalFramesInFile = 0;
        UInt32 frameLengthPropertySize = sizeof (totalFramesInFile);

        result =    ExtAudioFileGetProperty (
                        audioFileObject,
                        kExtAudioFileProperty_FileLengthFrames,
                        &frameLengthPropertySize,
                        &totalFramesInFile
                    );

        if (noErr != result) {[self printErrorMessage: @"ExtAudioFileGetProperty (audio file length in frames)" withStatus: result]; return;}

        // Assign the frame count to the soundStructArray instance variable
        soundStructArray[audioFile].frameCount = totalFramesInFile;

        // Get the audio file's number of channels.
        AudioStreamBasicDescription fileAudioFormat = {0};
        UInt32 formatPropertySize = sizeof (fileAudioFormat);

        result =    ExtAudioFileGetProperty (
                        audioFileObject,
                        kExtAudioFileProperty_FileDataFormat,
                        &formatPropertySize,
                        &fileAudioFormat
                    );

        if (noErr != result) {[self printErrorMessage: @"ExtAudioFileGetProperty (file audio format)" withStatus: result]; return;}

        UInt32 channelCount = fileAudioFormat.mChannelsPerFrame;

        // Allocate memory in the soundStructArray instance variable to hold the left channel, 
        //    or mono, audio data
        soundStructArray[audioFile].audioDataLeft =
            (AudioUnitSampleType *) calloc (totalFramesInFile, sizeof (AudioUnitSampleType));

        AudioStreamBasicDescription importFormat = {0};
        if (2 == channelCount) {

            soundStructArray[audioFile].isStereo = YES;
            // Sound is stereo, so allocate memory in the soundStructArray instance variable to  
            //    hold the right channel audio data
            soundStructArray[audioFile].audioDataRight =
                (AudioUnitSampleType *) calloc (totalFramesInFile, sizeof (AudioUnitSampleType));
            importFormat = stereoStreamFormat;

        } else if (1 == channelCount) {

            soundStructArray[audioFile].isStereo = NO;
            importFormat = monoStreamFormat;

        } else {

            NSLog (@"*** WARNING: File format not supported - wrong number of channels");
            ExtAudioFileDispose (audioFileObject);
            return;
        }

        // Assign the appropriate mixer input bus stream data format to the extended audio 
        //        file object. This is the format used for the audio data placed into the audio 
        //        buffer in the SoundStruct data structure, which is in turn used in the 
        //        inputRenderCallback callback function.

        result =    ExtAudioFileSetProperty (
                        audioFileObject,
                        kExtAudioFileProperty_ClientDataFormat,
                        sizeof (importFormat),
                        &importFormat
                    );

        if (noErr != result) {[self printErrorMessage: @"ExtAudioFileSetProperty (client data format)" withStatus: result]; return;}

        // Set up an AudioBufferList struct, which has two roles:
        //
        //        1. It gives the ExtAudioFileRead function the configuration it 
        //            needs to correctly provide the data to the buffer.
        //
        //        2. It points to the soundStructArray[audioFile].audioDataLeft buffer, so 
        //            that audio data obtained from disk using the ExtAudioFileRead function
        //            goes to that buffer

        // Allocate memory for the buffer list struct according to the number of 
        //    channels it represents.
        AudioBufferList *bufferList;

        bufferList = (AudioBufferList *) malloc (
            sizeof (AudioBufferList) + sizeof (AudioBuffer) * (channelCount - 1)
        );

        if (NULL == bufferList) {NSLog (@"*** malloc failure for allocating bufferList memory"); return;}

        // initialize the mNumberBuffers member
        bufferList->mNumberBuffers = channelCount;

        // initialize the mBuffers member to 0
        AudioBuffer emptyBuffer = {0};
        size_t arrayIndex;
        for (arrayIndex = 0; arrayIndex < channelCount; arrayIndex++) {
            bufferList->mBuffers[arrayIndex] = emptyBuffer;
        }

        // set up the AudioBuffer structs in the buffer list
        bufferList->mBuffers[0].mNumberChannels  = 1;
        bufferList->mBuffers[0].mDataByteSize    = totalFramesInFile * sizeof (AudioUnitSampleType);
        bufferList->mBuffers[0].mData            = soundStructArray[audioFile].audioDataLeft;

        if (2 == channelCount) {
            bufferList->mBuffers[1].mNumberChannels  = 1;
            bufferList->mBuffers[1].mDataByteSize    = totalFramesInFile * sizeof (AudioUnitSampleType);
            bufferList->mBuffers[1].mData            = soundStructArray[audioFile].audioDataRight;
        }

        // Perform a synchronous, sequential read of the audio data out of the file and
        //    into the soundStructArray[audioFile].audioDataLeft and (if stereo) .audioDataRight members.
        UInt32 numberOfPacketsToRead = (UInt32) totalFramesInFile;

        result = ExtAudioFileRead (
                     audioFileObject,
                     &numberOfPacketsToRead,
                     bufferList
                 );

        free (bufferList);

        if (noErr != result) {

            [self printErrorMessage: @"ExtAudioFileRead failure - " withStatus: result];

            // If reading from the file failed, then free the memory for the sound buffer.
            free (soundStructArray[audioFile].audioDataLeft);
            soundStructArray[audioFile].audioDataLeft = 0;

            if (2 == channelCount) {
                free (soundStructArray[audioFile].audioDataRight);
                soundStructArray[audioFile].audioDataRight = 0;
            }

            ExtAudioFileDispose (audioFileObject);            
            return;
        }

        NSLog (@"Finished reading file %i into memory", audioFile);

        // Set the sample index to zero, so that playback starts at the 
        //    beginning of the sound.
        soundStructArray[audioFile].sampleNumber = 0;

        // Dispose of the extended audio file object, which also
        //    closes the associated file.
        ExtAudioFileDispose (audioFileObject);
    }
}

哪个部分包含必须反转的音频样本数组?是AudioUnitSampleType吗?

bufferList->mBuffers[0].mData = soundStructArray[audioFile].audioDataLeft;

注意:audioDataLeft定义为AudioUnitSampleType,它是一个SInt32但不是数组。

我在Core Audio Mailing list中找到了一条线索:

  

嗯,据我所知,与iPh * n *无关(除非有些音频   API已被省略 - 我不是该程序的成员)。公平,   AudioFile.h和ExtendedAudioFile.h应该为您提供   需要读取或写入咖啡馆并访问其流/频道。   基本上,你想要向后阅读每个频道/流,所以,如果你   不需要音频文件的属性,这是非常简单的   一旦你掌握了该频道的数据,假设它不在   压缩格式。考虑到咖啡馆的格式数量   代表,这可能需要比你更多的代码行   思维。一旦掌握了未压缩的数据,它应该是   就像翻转一个字符串一样简单。然后你当然会替换   文件的数据与反转的数据,或者你可以只提供   音频输出(或您发送反向信号的任何地方)读数   无论你有什么流回来。

这是我尝试过的,但是当我将反向缓冲区分配给两个通道的mData时,我什么都没听到:

AudioUnitSampleType *leftData = soundStructArray[audioFile].audioDataLeft;
AudioUnitSampleType *reversedData = (AudioUnitSampleType *) calloc (totalFramesInFile, sizeof (AudioUnitSampleType));
UInt64 j = 0;
for (UInt64 i = (totalFramesInFile - 1); i > -1; i--) {
    reversedData[j] = leftData[i];
    j++;
}

3 个答案:

答案 0 :(得分:2)

我参与了一个示例应用,它会记录用户说的内容并向后播放。我使用CoreAudio来实现这一目标。 Link to app code

/ *      每个样本的大小为16位(2字节)(单声道)。      您可以通过从录制结束开始将其复制到不同的缓冲区中,一次加载每个样本      向后看。当您到达数据的开头时,您已经反转了数据并且播放将被反转。      * /

// set up output file
AudioFileID outputAudioFile;

AudioStreamBasicDescription myPCMFormat;
myPCMFormat.mSampleRate = 16000.00;
myPCMFormat.mFormatID = kAudioFormatLinearPCM ;
myPCMFormat.mFormatFlags =  kAudioFormatFlagsCanonical;
myPCMFormat.mChannelsPerFrame = 1;
myPCMFormat.mFramesPerPacket = 1;
myPCMFormat.mBitsPerChannel = 16;
myPCMFormat.mBytesPerPacket = 2;
myPCMFormat.mBytesPerFrame = 2;


AudioFileCreateWithURL((__bridge CFURLRef)self.flippedAudioUrl,
                       kAudioFileCAFType,
                       &myPCMFormat,
                       kAudioFileFlags_EraseFile,
                       &outputAudioFile);
// set up input file
AudioFileID inputAudioFile;
OSStatus theErr = noErr;
UInt64 fileDataSize = 0;

AudioStreamBasicDescription theFileFormat;
UInt32 thePropertySize = sizeof(theFileFormat);

theErr = AudioFileOpenURL((__bridge CFURLRef)self.recordedAudioUrl, kAudioFileReadPermission, 0, &inputAudioFile);

thePropertySize = sizeof(fileDataSize);
theErr = AudioFileGetProperty(inputAudioFile, kAudioFilePropertyAudioDataByteCount, &thePropertySize, &fileDataSize);

UInt32 dataSize = fileDataSize;
void* theData = malloc(dataSize);

//Read data into buffer
UInt32 readPoint  = dataSize;
UInt32 writePoint = 0;
while( readPoint > 0 )
{
    UInt32 bytesToRead = 2;

    AudioFileReadBytes( inputAudioFile, false, readPoint, &bytesToRead, theData );
    AudioFileWriteBytes( outputAudioFile, false, writePoint, &bytesToRead, theData );

    writePoint += 2;
    readPoint -= 2;
}

free(theData);
AudioFileClose(inputAudioFile);
AudioFileClose(outputAudioFile);

希望这会有所帮助。

答案 1 :(得分:0)

  

通常,在使用ASBD时,这些字段描述了由此描述表示的缓冲区中的样本数据的完整布局 - 通常这些缓冲区由AudioBufferList中包含的AudioBuffer表示。

     

但是,当ASBD具有kAudioFormatFlagIsNonInterleaved标志时,AudioBufferList具有不同的结构和语义。在这种情况下,ASBD字段将描述列表中包含的一个AudioBuffers的格式,并且列表中的每个AudioBuffer被确定为具有单个(单声道)音频数据通道。然后,ASBD的mChannelsPerFrame将指示AudioBufferList中包含的AudioBuffers的总数 - 其中每个缓冲区包含一个通道。这主要用于此列表的AudioUnit(和AudioConverter)表示 - 并且不会在此结构的AudioHardware用法中找到。

答案 2 :(得分:0)

您不必分配单独的缓冲区来存储反转数据,这可能需要相当多的CPU,具体取决于声音的长度。要向后播放声音,只需将sampleNumber计数器从totalFramesInFile - 1开始。

您可以像这样修改MixerHost,以达到预期的效果。

soundStructArray[audioFile].sampleNumber = 0;替换为 soundStructArray[audioFile].sampleNumber = totalFramesInFile - 1;

make sampleNumber SInt32而不是UInt32。

用这个替换你写出样本的循环。

for (UInt32 frameNumber = 0; frameNumber < inNumberFrames; ++frameNumber) {
    outSamplesChannelLeft[frameNumber]                 = dataInLeft[sampleNumber];
    if (isStereo) outSamplesChannelRight[frameNumber]  = dataInRight[sampleNumber];

    if (--sampleNumber < 0) sampleNumber = frameTotalForSound - 1;
}

这有效地使它向后播放。嗯。自从我听过MixerHost音乐以来已经有一段时间了。我必须承认我觉得这很令人愉快。