【问题标题】:How do I synthesize sounds with CoreAudio on iPhone/Mac如何在 iPhone/Mac 上使用 CoreAudio 合成声音
【发布时间】:2010-11-24 13:44:23
【问题描述】:

我想在 iPhone 中播放合成声音。我不想使用预先录制的声音并使用 SystemSoundID 播放现有的二进制文件,而是想合成它。部分原因是我希望能够连续播放声音(例如,当用户的手指在屏幕上时)而不是一次性的声音样本。

如果我想合成一个中间 A+1 (A4) (440Hz),我可以使用 sin() 计算一个正弦波;我不知道的是如何将这些位排列成一个数据包,然后 CoreAudio 可以播放。网络上的大多数教程都只是简单地播放现有的二进制文件。

谁能帮我制作一个简单的 440Hz 合成正弦声波?

【问题讨论】:

    标签: iphone objective-c cocoa core-audio


    【解决方案1】:

    许多音频技术允许传入数据而不是声音文件。例如,AVAudioPlayer 有:

    -initWithData:error:
    Initializes and returns an audio player for playing a designated memory buffer.
    
    - (id)initWithData:(NSData *)data error:(NSError **)outError
    

    但是,我不确定您将如何传入数据 ptr、启动声音,然后通过传入其他数据 ptr 或重复相同的操作等来保持声音循环。

    【讨论】:

      【解决方案2】:

      你想要做的可能是设置一个 AudioQueue。它允许您在回调中用合成的音频数据填充缓冲区。您可以将 AudeioQueue 设置为在新线程中运行:

      #define BUFFER_SIZE 16384
      #define BUFFER_COUNT 3
      static AudioQueueRef audioQueue;
      void SetupAudioQueue() {
          OSStatus err = noErr;
          // Setup the audio device.
          AudioStreamBasicDescription deviceFormat;
          deviceFormat.mSampleRate = 44100;
          deviceFormat.mFormatID = kAudioFormatLinearPCM;
          deviceFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
          deviceFormat.mBytesPerPacket = 4;
          deviceFormat.mFramesPerPacket = 1;
          deviceFormat.mBytesPerFrame = 4;
          deviceFormat.mChannelsPerFrame = 2;
          deviceFormat.mBitsPerChannel = 16;
          deviceFormat.mReserved = 0;
          // Create a new output AudioQueue for the device.
          err = AudioQueueNewOutput(&deviceFormat, AudioQueueCallback, NULL,
                                    CFRunLoopGetCurrent(), kCFRunLoopCommonModes,
                                    0, &audioQueue);
          // Allocate buffers for the AudioQueue, and pre-fill them.
          for (int i = 0; i < BUFFER_COUNT; ++i) {
              AudioQueueBufferRef mBuffer;
              err = AudioQueueAllocateBuffer(audioQueue, BUFFER_SIZE, mBuffer);
              if (err != noErr) break;
              AudioQueueCallback(NULL, audioQueue, mBuffer);
          }
          if (err == noErr) err = AudioQueueStart(audioQueue, NULL);
          if (err == noErr) CFRunLoopRun();
        }
      

      然后,只要 AudioQueue 需要更多数据,就会调用您的回调方法 AudioQueueCallback。用类似的东西实现:

      void AudioQueueCallback(void* inUserData, AudioQueueRef inAQ,
                              AudioQueueBufferRef inBuffer) {
          void* pBuffer = inBuffer->mAudioData;
          UInt32 bytes = inBuffer->mAudioDataBytesCapacity;
          // Write max <bytes> bytes of audio to <pBuffer>
          outBuffer->mAudioDataByteSize = actualNumberOfBytesWritten
          err = AudioQueueEnqueueBuffer(audioQueue, inBuffer, 0, NULL);
      }
      

      【讨论】:

      • 这是不正确的。您不应该在分配循环中调用 AudioQueueCallback。我也不相信描述设置正确。另外,您应该调用 AudioQueueStart(audioQueue, nil) 而不是这种奇怪的方式。请查看 AudioUnit 框架。
      • @thefaj:我相信你是不正确的人。这个例子取自我的应用程序 SC68 Player (itunes.apple.com/se/app/sc68-player/id295290413?mt=8),我最初是从 Apple 的示例 iPhone 应用程序 SpeakHere (developer.apple.com/iphone/library/samplecode/SpeakHere) 中获取音频回放的代码,查看 AQPlayer.mm 文件。 SC68 Player 的完整源代码可用 (peylow.se/sc68player.html)。
      • 您的示例缺少 AudioQueueStart(),这是应该调用 AudioQueueCallback 的方式。
      • 上帝...我很遗憾我采用了实际代码并试图缩小它的大小以获得答案...我可能应该只编写一些伪代码并添加指向 Apple 文档的链接。
      • 我认为看到代码得到更正很有用。我并不是说马虎,但完美的代码有时可以隐藏实际使用某些框架的复杂性。通过这种方式,我们可以了解事情会如何出错以及如何解决它们。
      【解决方案3】:

      Davide Vosti 指向 http://lists.apple.com/archives/coreaudio-api/2008/Dec/msg00173.html 的链接不再有效,因为 Apple 列表似乎没有响应。这是 Google 的完整缓存。

      //
      //  AudioUnitTestAppDelegate.m
      //  AudioUnitTest
      //
      //  Created by Marc Vaillant on 11/25/08.
      //  Copyright __MyCompanyName__ 2008. All rights reserved.
      //
      
      #import "AudioUnitTestAppDelegate.h"
      #include <AudioUnit/AudioUnit.h>
      //#include "MachTimer.hpp"
      #include <vector>
      #include <iostream>
      
      using namespace std;
      
      #define kOutputBus 0
      #define kInputBus 1
      #define SAMPLE_RATE 44100
      
      vector<int> _pcm;
      int _index;
      
      @implementation AudioUnitTestAppDelegate
      
      @synthesize window;
      
      void generateTone(
                      vector<int>& pcm, 
                      int freq, 
                      double lengthMS, 
                      int sampleRate, 
                      double riseTimeMS, 
                      double gain)
      {
        int numSamples = ((double) sampleRate) * lengthMS / 1000.;
        int riseTimeSamples = ((double) sampleRate) * riseTimeMS / 1000.;
      
        if(gain > 1.)
          gain = 1.;
        if(gain < 0.)
          gain = 0.;
      
        pcm.resize(numSamples);
      
        for(int i = 0; i < numSamples; ++i)
        {
          double value = sin(2. * M_PI * freq * i / sampleRate);
          if(i < riseTimeSamples)
            value *= sin(i * M_PI / (2.0 * riseTimeSamples));
          if(i > numSamples - riseTimeSamples - 1)
            value *= sin(2. * M_PI * (i - (numSamples - riseTimeSamples) + riseTimeSamples)/ (4. * riseTimeSamples));
      
          pcm[i] = (int) (value * 32500.0 * gain);
          pcm[i] += (pcm[i]<<16);
        }
      
      }
      
      static OSStatus playbackCallback(void *inRefCon, 
                                        AudioUnitRenderActionFlags *ioActionFlags, 
                                        const AudioTimeStamp *inTimeStamp, 
                                        UInt32 inBusNumber, 
                                        UInt32 inNumberFrames, 
                                        AudioBufferList *ioData) 
      {    
          cout<<"index = "<<_index<<endl;
          cout<<"numBuffers = "<<ioData->mNumberBuffers<<endl;
      
          int totalNumberOfSamples = _pcm.size();
          for(UInt32 i = 0; i < ioData->mNumberBuffers; ++i)
          {
            int samplesLeft = totalNumberOfSamples - _index;
            int numSamples = ioData->mBuffers[i].mDataByteSize / 4;
            if(samplesLeft > 0)
            {
              if(samplesLeft < numSamples)
              {
                memcpy(ioData->mBuffers[i].mData, &_pcm[_index], samplesLeft * 4);
                _index += samplesLeft;
                memset((char*) ioData->mBuffers[i].mData + samplesLeft * 4, 0, (numSamples - samplesLeft) * 4) ;
              }
              else
              {
                memcpy(ioData->mBuffers[i].mData, &_pcm[_index], numSamples * 4) ;
                _index += numSamples;
              }
            }
            else
              memset(ioData->mBuffers[i].mData, 0, ioData->mBuffers[i].mDataByteSize);
          }
      
          return noErr;
      }
      
      - (void)applicationDidFinishLaunching:(UIApplication *)application 
      {    
        //generate pcm tone  freq = 800, duration = 1s, rise/fall time = 5ms
      
        generateTone(_pcm, 800, 1000, SAMPLE_RATE, 5, 0.8);
        _index = 0;
      
        OSStatus status;
        AudioComponentInstance audioUnit;
      
        // Describe audio component
        AudioComponentDescription desc;
        desc.componentType = kAudioUnitType_Output;
        desc.componentSubType = kAudioUnitSubType_RemoteIO;
        desc.componentFlags = 0;
        desc.componentFlagsMask = 0;
        desc.componentManufacturer = kAudioUnitManufacturer_Apple;
      
        // Get component
        AudioComponent inputComponent = AudioComponentFindNext(NULL, &desc);
      
        // Get audio units
        status = AudioComponentInstanceNew(inputComponent, &audioUnit);
        //checkStatus(status);
      
        UInt32 flag = 1;
        // Enable IO for playback
        status = AudioUnitSetProperty(audioUnit, 
                        kAudioOutputUnitProperty_EnableIO, 
                        kAudioUnitScope_Output, 
                        kOutputBus,
                        &flag, 
                        sizeof(flag));
        //checkStatus(status);
      
        // Describe format
      
        AudioStreamBasicDescription audioFormat;
        audioFormat.mSampleRate = SAMPLE_RATE;
        audioFormat.mFormatID = kAudioFormatLinearPCM;
        audioFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
        audioFormat.mFramesPerPacket = 1;
        audioFormat.mChannelsPerFrame = 2;
        audioFormat.mBitsPerChannel = 16;
        audioFormat.mBytesPerPacket = 4;
        audioFormat.mBytesPerFrame = 4;
      
        // Apply format
      
        status = AudioUnitSetProperty(audioUnit, 
                        kAudioUnitProperty_StreamFormat, 
                        kAudioUnitScope_Input, 
                        kOutputBus, 
                        &audioFormat, 
                        sizeof(audioFormat));
      //  checkStatus(status);
      
        // Set output callback
        AURenderCallbackStruct callbackStruct;
        callbackStruct.inputProc = playbackCallback;
        callbackStruct.inputProcRefCon = self;
        status = AudioUnitSetProperty(audioUnit, 
                        kAudioUnitProperty_SetRenderCallback, 
                        kAudioUnitScope_Global, 
                        kOutputBus,
                        &callbackStruct, 
                        sizeof(callbackStruct));
      
        // Initialize
        status = AudioUnitInitialize(audioUnit);
      
        // Start playing
      
        status = AudioOutputUnitStart(audioUnit);
      
        [window makeKeyAndVisible];
      }
      
      
      - (void)dealloc {
          [window release];
          [super dealloc];
      }
      
      
      @end
      

      【讨论】:

      • 我会添加它作为对 Davide 问题的评论,但 cmets 有 600 个字符的限制。
      猜你喜欢
      • 1970-01-01
      • 2012-07-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-10-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多