8

オーディオ信号を開始および停止するボタンを使用して、iOS 用の非常にシンプルなアプリケーションを構築しようとしています。信号は正弦波になり、再生中にモデル (ボリュームのインスタンス変数) をチェックし、それに応じてボリュームを変更します。

私の困難は、タスクの不明確な性質に関係しています。テーブルの作成方法、テーブルにデータを入力する方法、ボタンの押下に応答する方法などを理解しています。ただし、何かを無期限に継続させる場合 (この場合は音) には、少し行き詰まります。どんなポインタでも素晴らしいでしょう!

読んでくれてありがとう。

4

1 に答える 1

17

これは、生成された周波数をオンデマンドで再生する必要最小限のアプリケーションです。iOS と OSX のどちらを行うかを指定していないので、OSX の方が少し簡単なので (オーディオ セッション カテゴリをいじることはありません)、OSX を選択しました。iOS が必要な場合は、オーディオ セッション カテゴリの基本を調べて、デフォルトの出力オーディオ ユニットを RemoteIO オーディオ ユニットに交換することで、足りない部分を見つけることができます。

これの意図は、純粋に Core Audio / Audio Unit の基本を示すことであることに注意してください。AUGraphこれよりも複雑になりたい場合は、おそらく API を調べることをお勧めします (また、明確な例を提供するために、エラー チェックは行っていません。Core Audio を扱うときは常にエラー チェックを行います)。 .

AudioToolboxこのコードを使用するには、とAudioUnitフレームワークをプロジェクトに追加する必要があります。

#import <AudioToolbox/AudioToolbox.h>

@interface SWAppDelegate : NSObject <NSApplicationDelegate>
{
    AudioUnit outputUnit;
    double renderPhase;
}
@end

@implementation SWAppDelegate

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
//  First, we need to establish which Audio Unit we want.

//  We start with its description, which is:
    AudioComponentDescription outputUnitDescription = {
        .componentType         = kAudioUnitType_Output,
        .componentSubType      = kAudioUnitSubType_DefaultOutput,
        .componentManufacturer = kAudioUnitManufacturer_Apple
    };

//  Next, we get the first (and only) component corresponding to that description
    AudioComponent outputComponent = AudioComponentFindNext(NULL, &outputUnitDescription);

//  Now we can create an instance of that component, which will create an
//  instance of the Audio Unit we're looking for (the default output)
    AudioComponentInstanceNew(outputComponent, &outputUnit);
    AudioUnitInitialize(outputUnit);

//  Next we'll tell the output unit what format our generated audio will
//  be in. Generally speaking, you'll want to stick to sane formats, since
//  the output unit won't accept every single possible stream format.
//  Here, we're specifying floating point samples with a sample rate of
//  44100 Hz in mono (i.e. 1 channel)
    AudioStreamBasicDescription ASBD = {
        .mSampleRate       = 44100,
        .mFormatID         = kAudioFormatLinearPCM,
        .mFormatFlags      = kAudioFormatFlagsNativeFloatPacked,
        .mChannelsPerFrame = 1,
        .mFramesPerPacket  = 1,
        .mBitsPerChannel   = sizeof(Float32) * 8,
        .mBytesPerPacket   = sizeof(Float32),
        .mBytesPerFrame    = sizeof(Float32)
    };

    AudioUnitSetProperty(outputUnit,
                         kAudioUnitProperty_StreamFormat,
                         kAudioUnitScope_Input,
                         0,
                         &ASBD,
                         sizeof(ASBD));

//  Next step is to tell our output unit which function we'd like it
//  to call to get audio samples. We'll also pass in a context pointer,
//  which can be a pointer to anything you need to maintain state between
//  render callbacks. We only need to point to a double which represents
//  the current phase of the sine wave we're creating.
    AURenderCallbackStruct callbackInfo = {
        .inputProc       = SineWaveRenderCallback,
        .inputProcRefCon = &renderPhase
    };

    AudioUnitSetProperty(outputUnit,
                         kAudioUnitProperty_SetRenderCallback,
                         kAudioUnitScope_Global,
                         0,
                         &callbackInfo,
                         sizeof(callbackInfo));

//  Here we're telling the output unit to start requesting audio samples
//  from our render callback. This is the line of code that starts actually
//  sending audio to your speakers.
    AudioOutputUnitStart(outputUnit);
}

// This is our render callback. It will be called very frequently for short
// buffers of audio (512 samples per call on my machine).
OSStatus SineWaveRenderCallback(void * inRefCon,
                                AudioUnitRenderActionFlags * ioActionFlags,
                                const AudioTimeStamp * inTimeStamp,
                                UInt32 inBusNumber,
                                UInt32 inNumberFrames,
                                AudioBufferList * ioData)
{
    // inRefCon is the context pointer we passed in earlier when setting the render callback
    double currentPhase = *((double *)inRefCon);
    // ioData is where we're supposed to put the audio samples we've created
    Float32 * outputBuffer = (Float32 *)ioData->mBuffers[0].mData;
    const double frequency = 440.;
    const double phaseStep = (frequency / 44100.) * (M_PI * 2.);

    for(int i = 0; i < inNumberFrames; i++) {
        outputBuffer[i] = sin(currentPhase);
        currentPhase += phaseStep;
    }

    // If we were doing stereo (or more), this would copy our sine wave samples
    // to all of the remaining channels
    for(int i = 1; i < ioData->mNumberBuffers; i++) {
        memcpy(ioData->mBuffers[i].mData, outputBuffer, ioData->mBuffers[i].mDataByteSize);
    }

    // writing the current phase back to inRefCon so we can use it on the next call
    *((double *)inRefCon) = currentPhase;
    return noErr;
}

- (void)applicationWillTerminate:(NSNotification *)notification
{
    AudioOutputUnitStop(outputUnit);
    AudioUnitUninitialize(outputUnit);
    AudioComponentInstanceDispose(outputUnit);
}

@end

を呼び出しAudioOutputUnitStart()て、AudioOutputUnitStop()オーディオの生成を開始/停止することができます。周波数を動的に変更したい場合はstruct、renderPhase double と必要な周波数を表す別の double の両方を含む へのポインタを渡すことができます。

レンダー コールバックには注意してください。リアルタイム スレッドから呼び出されます (メインの実行ループと同じスレッドからではありません)。Render コールバックには、かなり厳しい時間要件が適用されます。つまり、コールバックで行うべきではないことが次のように多数あります。

  • メモリを割り当てる
  • ミューテックスを待つ
  • ディスク上のファイルから読み取る
  • Objective-C メッセージング (はい、真剣です。)

これが唯一の方法ではないことに注意してください。あなたがこのコアオーディオにタグを付けたので、私はこの方法でそれを示しただけです。周波数を変更する必要がない場合はAVAudioPlayer、正弦波を含む事前に作成されたサウンド ファイルを使用できます。

Novocaineもあります。これは、この冗長性の多くを隠します。また、Audio Queue API を調べることもできます。これは、私が書いた Core Audio サンプルとほぼ同じように機能しますが、ハードウェアからもう少し分離します (つまり、レンダー コールバックでの動作に関する厳密性が低くなります)。

于 2013-01-23T11:18:10.997 に答える