【问题标题】:Play audio file using Audio Units?使用音频单元播放音频文件?
【发布时间】:2012-07-31 19:38:06
【问题描述】:

openframeworks 和本网站http://atastypixel.com/blog/using-remoteio-audio-unit 的帮助下,我已使用音频单元成功地将麦克风中的音频录制到音频文件中。

我希望能够将文件流式传输回音频单元并播放音频。根据Play an audio file using RemoteIO and Audio Unit,我可以使用 ExtAudioFileOpenURL 和 ExtAudioFileRead。但是,如何在缓冲区中播放音频数据?

这是我目前拥有的:

static OSStatus setupAudioFileRead() {
    //construct the file destination URL
    CFURLRef destinationURL = audioSystemFileURL();
    OSStatus status = ExtAudioFileOpenURL(destinationURL, &audioFileRef);
    CFRelease(destinationURL);

    if (checkStatus(status)) { ofLog(OF_LOG_ERROR, "ofxiPhoneSoundStream: Couldn't open file to read"); return status; }

    while( TRUE ) {
        // Try to fill the buffer to capacity.
        UInt32 framesRead = 8000;
        status = ExtAudioFileRead( audioFileRef, &framesRead, &inputBufferList );

        // error check
        if( checkStatus(status) ) { break; }

        // 0 frames read means EOF.
        if( framesRead == 0 ) { break; }

        //play audio???
    }

    return noErr;
}

【问题讨论】:

    标签: ios audio core-audio audiounit


    【解决方案1】:

    这是一种相对简单的方法,它利用了 Tasty Pixel 博客中提到的音频单元。在录音回调中,您可以使用 ExtAudioFileRead 用文件中的数据填充缓冲区,而不是使用来自麦克风的数据填充缓冲区。我将尝试在下面粘贴一个示例。请注意,这仅适用于 .caf 文件。

    在 start 方法中调用 readAudio 或 initAudioFile 函数,它只是获取有关文件的所有信息。

    - (void) start {
    readAudio();
    OSStatus status = AudioOutputUnitStart(audioUnit);
    checkStatus(status);
    }
    

    现在在 readAudio 方法中初始化音频文件引用。

    ExtAudioFileRef fileRef;
    
    void readAudio() {
    NSString * name = @"AudioFile";
    NSString * source = [[NSBundle mainBundle] pathForResource:name ofType:@"caf"];
    const char * cString = [source cStringUsingEncoding:NSASCIIStringEncoding];
    CFStringRef str = CFStringCreateWithCString(NULL, cString, kCFStringEncodingMacRoman);
    CFURLRef inputFileURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, str, kCFURLPOSIXPathStyle, false);
    
    AudioFileID fileID;
    OSStatus err = AudioFileOpenURL(inputFileURL, kAudioFileReadPermission, 0, &fileID);
    CheckError(err, "AudioFileOpenURL");
    
    
    err = ExtAudioFileOpenURL(inputFileURL, &fileRef);
    CheckError(err, "ExtAudioFileOpenURL");
    
    err = ExtAudioFileSetProperty(fileRef, kExtAudioFileProperty_ClientDataFormat, sizeof(AudioStreamBasicDescription), &audioFormat);
    CheckError(err, "ExtAudioFileSetProperty");
    }
    

    现在您已经掌握了音频数据,下一步非常简单。在recordingCallback中从文件而不是mic中读取数据。

    static OSStatus recordingCallback(void *inRefCon,
                                  AudioUnitRenderActionFlags *ioActionFlags,
                                  const AudioTimeStamp *inTimeStamp,
                                  UInt32 inBusNumber,
                                  UInt32 inNumberFrames,
                                  AudioBufferList *ioData) {
    
    // Because of the way our audio format (setup below) is chosen:
    // we only need 1 buffer, since it is mono
    // Samples are 16 bits = 2 bytes.
    // 1 frame includes only 1 sample
    
    AudioBuffer buffer;
    
    buffer.mNumberChannels = 1;
    buffer.mDataByteSize = inNumberFrames * 2;
    buffer.mData = malloc( inNumberFrames * 2 );
    
    // Put buffer in a AudioBufferList
    AudioBufferList bufferList;
    bufferList.mNumberBuffers = 1;
    bufferList.mBuffers[0] = buffer;
    
    // Then:
    // Obtain recorded samples
    OSStatus err = ExtAudioFileRead(fileRef, &inNumberFrames, &bufferList);
    
    // Now, we have the samples we just read sitting in buffers in bufferList
    // Process the new data
    [iosAudio processAudio:&bufferList];
    
    // release the malloc'ed data in the buffer we created earlier
    free(bufferList.mBuffers[0].mData);
    
    return noErr;
    }
    

    这对我有用。

    【讨论】:

    • 这里的 iosaudio 是什么?请解释
    • iosAudio 是类的全局声明。也就是说,我们只是在类中调用了一个叫做processAudio的函数,参数是bufferList
    • 要小心,因为在渲染回调中,您正在使用内存分配和 objc 消息,这可能会导致许多奇怪的错误和奇怪的行为。
    【解决方案2】:

    基本上, 1.创建一个RemoteIO单元(如何创建RemoteIO参见参考资料);

    1. 创建一个 FilePlayer 音频单元,它是一个专用的音频单元,用于读取音频文件并将文件中的音频数据提供给输出单元,例如步骤 1 中创建的 RemoteIO 单元。要实际使用 FilePlayer,很多设置(指定播放哪个文件,播放文件的哪一部分等)都需要在上面进行;

    2. 设置 RemoteIO 单元的 kAudioUnitProperty_SetRenderCallback 和 kAudioUnitProperty_StreamFormat 属性。第一个属性本质上是一个回调函数,RemoteIO 单元从中提取音频数据并播放它。第二个属性必须按照 FilePlayer 支持的 StreamFormat 设置。它可以从 FilePlayer 上调用的 get-property 函数派生。

    3. 在步骤 3 中定义回调集,其中最重要的事情是要求 FilePlayer 渲染到回调提供的缓冲区中,您需要在 FilePlayer 上调用 AudioUnitRender()。

    4. 最后启动 RemoteIO 单元播放文件。

    以上只是在 iOS 上使用音频单元播放文件的基本操作的初步概述。详情可以参考 Chris Adamson 和 Kevin Avila 的Learning Core Audio

    【讨论】:

      【解决方案3】:

      来自作者:http://atastypixel.com/blog/using-remoteio-audio-unit/,如果您向下滚动到播放部分,请尝试以下操作:

      static OSStatus playbackCallback(void *inRefCon, 
                                        AudioUnitRenderActionFlags *ioActionFlags, 
                                        const AudioTimeStamp *inTimeStamp, 
                                        UInt32 inBusNumber, 
                                        UInt32 inNumberFrames, 
                                        AudioBufferList *ioData) {    
          // Notes: ioData contains buffers (may be more than one!)
          // Fill them up as much as you can. Remember to set the size value in each buffer to match how
          // much data is in the buffer.
      
      for (int i=0; i < ioData->mNumberBuffers; i++) 
      { 
          AudioBuffer buffer = ioData->mBuffers[i];
          // copy from your whatever buffer data to output buffer
          UInt32 size = min(buffer.mDataByteSize, your buffer.size); 
          memcpy(buffer.mData, your buffer, size);
          buffer.mDataByteSize = size; // indicate how much data we wrote in the buffer
      
          // To test if your Audio Unit setup is working - comment out the three 
          // lines above and uncomment the for loop below to hear random noise  
          /*
          UInt16 *frameBuffer = buffer.mData;
          for (int j = 0; j < inNumberFrames; j++) {
              frameBuffer[j] = rand();
          }
          */
      }
      return noErr;
      }
      

      如果您只是寻找从 MIC 录制到文件并播放它,Apple 的 Speakhere 样本可能更适合使用。

      【讨论】:

      • 不确定我是否被理解。我将如何设置音频单元以流式传输音频文件并利用其“playbackCallback”。我正在使用修改版的 openframeworks ofxfenster.undef.ch/doc/ofxiPhoneSoundStream_8mm_source.html
      • 我不熟悉那个。我的回答与您的第二个链接有关。
      • 假设您有一个文件“audiofile.caf”,您如何使用 AudioUnits 播放这样的文件?
      • Apple 的使用 AudioQueue 的 Speakhere 示例是一个很好的开始。请参阅我原始答案中的链接。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多