Xcode|iOS バイナリーの音データーを鳴す方法を調べてみた

スポンサーリンク
Objective-C

20160828_001

AIFF,WAVEファイルを使用して音を鳴らす方法はググるとすぐに見つかるけど、バイナリーデータをそのまま使用して音を鳴らす方法はなかなか見つからなかった。

それなら、あまのじゃくHikaruappは調べてみた。

バイナリーデーターを使用して音を鳴らす

なんかわからないけどバイナリーデータの素性が分かればそのデータをそのまま使用すれば音を鳴らすことができる。

バイナリーデーターを使用して音を鳴らすAPIは2種類

1.AudioQueue
2.AudioUnit

使用できるバイナリーデータ

1.Float(32bit)
2.Integer(16bit)

バイナリーデーターの形式を知らないとダメ

1.サンプリング周波数
2.MP3等の圧縮タイプは再生不可能

AudioQueue

基本AudioQueueを使用すると思いますのでAudioQueueを先に説明していきます。

AudioQueue Float

初期設定

- (void)createAudioTools {
    
    // audio Format
    _audioDataFormat.mSampleRate = AUDIO_SAMPLING_RATE;
    _audioDataFormat.mFormatID = kAudioFormatLinearPCM;
    _audioDataFormat.mFormatFlags = kLinearPCMFormatFlagIsFloat | kAudioFormatFlagIsPacked;
    _audioDataFormat.mBitsPerChannel    = 32;
    _audioDataFormat.mChannelsPerFrame  = 2;
    _audioDataFormat.mFramesPerPacket   = 1;
    _audioDataFormat.mBytesPerFrame     = _audioDataFormat.mBitsPerChannel / 8 * _audioDataFormat.mChannelsPerFrame;    //[8]
    _audioDataFormat.mBytesPerPacket    = _audioDataFormat.mBytesPerFrame * _audioDataFormat.mFramesPerPacket;          //[8]
    _audioDataFormat.mReserved          = 0;
    
    //
    // 再生用のオーディオキューオブジェクトを作成する
    AudioQueueNewOutput(&_audioDataFormat,      // AudioStreamBasicDescription
                        AQOutputCallback,       // AudioQueueOutputCallback
                        (__bridge void *)self,  // コールバックの第一引数に渡される
                        nil,
                        nil,
                        0,
                        &_audioQueue);
    
    // bufferを3個作成。
    UInt32 bufferSize = AUDIO_BUFFER_SIZE * _audioDataFormat.mBytesPerFrame;
    
    for (NSInteger bufferBank = 0; bufferBank < 3; bufferBank++) {
        AudioQueueBufferRef   audioBuffers;
        AudioQueueAllocateBuffer(_audioQueue, bufferSize, &audioBuffers);
        [self audioQueueOutputWithQueue:self.audioQueue queueBuffer:audioBuffers];
    }
    
    
    // 再生開始
    AudioQueueStart(_audioQueue, nil);
    
}

サンプリング周波数:48000Hz

バッファーは3個作りました。音以外の処理が重たい場合はバッファーを多くすると途切れた時のプチプチ音が改善される事があります。

コールバック

再生開始と同時にコールバックが呼ばれれますのでC言語でコールバック部分を追記

// コールバック関数
static void AQOutputCallback(void *userData,
                             AudioQueueRef audioQueueRef,
                             AudioQueueBufferRef audioQueueBufferRef) {
    
    AudioQueFloatTone *addBuffer = (__bridge AudioQueFloatTone*)userData;
    [addBuffer audioQueueOutputWithQueue:audioQueueRef queueBuffer:audioQueueBufferRef];
}

ここで下記メソッドを呼ぶようにしてあります。

バイナリーデーターの挿入

- (void) audioQueueOutputWithQueue:(AudioQueueRef)audioQueueRef
                       queueBuffer:(AudioQueueBufferRef)audioQueueBufferRef {
    
    // 正弦波用のデータ作成
    float phasePerSample = 2.0 * M_PI * ( self.toneFrequancy / AUDIO_SAMPLING_RATE );
    
    audioQueueBufferRef->mAudioDataByteSize = self.audioDataFormat.mBytesPerFrame * AUDIO_BUFFER_SIZE;
    float *sampleBuffer = (float *)audioQueueBufferRef->mAudioData;

    // バファーサイズ分音のデータを書き込む
    for (int i = 0; i < AUDIO_BUFFER_SIZE; i++) {
        
        // output Left
        float toneLeft = 0;
        if (self.toneOutPut & kToneLeft) {
            toneLeft = (sin( phasePerSample * self.toneCycle ) * self.toneVolume )* INT16_MAX / 32768.f;;
        }
        
        // output Right
        float toneRight = 0;
        if (self.toneOutPut & kToneRight) {
            toneRight = (sin( phasePerSample * self.toneCycle ) * self.toneVolume )* INT16_MAX / 32768.f;;
        }
        
        
        // write tone data
        *sampleBuffer = toneLeft;
        sampleBuffer+=1;
        *sampleBuffer = toneRight;
        sampleBuffer+=1;
        
        self.toneCycle += 1;
    }
    
    AudioQueueEnqueueBuffer(audioQueueRef, audioQueueBufferRef, 0, NULL);
    
    // over flow
    if ( self.toneCycle  > AUDIO_SAMPLING_RATE ) {
        self.toneCycle -= AUDIO_SAMPLING_RATE;
    }
    
}

ここに3000HzのSine波を計算しその値を左、右の順番でアドレス直に入れていきます。

この繰り返しで無音にしたい場合は0を入れてます。

AudioQueue Integer

コードは同じで初期設定だけ違うだけです。

- (void)createAudioTools {
    
    // audio Format
    _audioDataFormat.mSampleRate        = AUDIO_SAMPLING_RATE;
    _audioDataFormat.mFormatID          = kAudioFormatLinearPCM;
    _audioDataFormat.mFormatFlags       = kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
    _audioDataFormat.mBitsPerChannel    = 16;
    _audioDataFormat.mChannelsPerFrame  = 2;
    _audioDataFormat.mFramesPerPacket   = 1;
    _audioDataFormat.mBytesPerFrame     = _audioDataFormat.mBitsPerChannel / 8 * _audioDataFormat.mChannelsPerFrame;    // [4]
    _audioDataFormat.mBytesPerPacket    = _audioDataFormat.mBytesPerFrame * _audioDataFormat.mFramesPerPacket;          // [4]
    _audioDataFormat.mReserved          = 0;
    
    //
    // 再生用のオーディオキューオブジェクトを作成する
    AudioQueueNewOutput(&_audioDataFormat,      // AudioStreamBasicDescription
                        AQOutputCallback,       // AudioQueueOutputCallback
                        (__bridge void *)self,  // コールバックの第一引数に渡される
                        nil,
                        nil,
                        0,
                        &_audioQueue);
    
    // bufferを3個作成
    UInt32 bufferSize = AUDIO_BUFFER_SIZE * _audioDataFormat.mBytesPerFrame;
    
    for (NSInteger bufferBank = 0; bufferBank < 3; bufferBank++) {
        AudioQueueBufferRef   audioBuffers;
        AudioQueueAllocateBuffer(_audioQueue, bufferSize, &audioBuffers);
        [self audioQueueOutputWithQueue:self.audioQueue queueBuffer:audioBuffers];
    }
    
    // 再生開始
    AudioQueueStart(_audioQueue, nil);
    
}

- (void) audioQueueOutputWithQueue:(AudioQueueRef)audioQueueRef
                       queueBuffer:(AudioQueueBufferRef)audioQueueBufferRef {
    
    dispatch_sync(self.audioTumuTumu, ^{
        // tone 信号のベースを作る
        float phasePerSample = 2.0 * M_PI * ( self.toneFrequancy / AUDIO_SAMPLING_RATE );
        audioQueueBufferRef->mAudioDataByteSize = self.audioDataFormat.mBytesPerFrame * AUDIO_BUFFER_SIZE;
        int16_t *sampleBuffer = (int16_t *)audioQueueBufferRef->mAudioData;
        
        // バファーサイズ分音のデータを書き込む
        for (int i = 0; i < AUDIO_BUFFER_SIZE; i++) {
            
            // output Left
            float toneLeft = 0;
            if (self.toneOutPut & kToneLeft) {
                toneLeft = sin( phasePerSample * self.toneCycle ) * self.toneVolume;
            }
            
            // output Right
            float toneRight = 0;
            if (self.toneOutPut & kToneRight) {
                toneRight = sin( phasePerSample * self.toneCycle ) * self.toneVolume;
            }
            
            
            // write tone data
            *sampleBuffer = (int16_t)(toneLeft);
            sampleBuffer+=1;
            *sampleBuffer = (int16_t)(toneRight);
            sampleBuffer+=1;
            
            self.toneCycle += 1;
        }
        
        AudioQueueEnqueueBuffer(audioQueueRef, audioQueueBufferRef, 0, NULL);
        
        // over flow
        if ( self.toneCycle  > AUDIO_SAMPLING_RATE ) {
            self.toneCycle -= AUDIO_SAMPLING_RATE;
        }
        
    });

}

もうこれで完璧ですね。

ではもう一つあまり使用しないであろうAudioUnitの説明

AudioUnit

マイナーだから使用したい、そんな人のために調べました。

AudioUnit Float

AudioUnitからAudioQueueに移行するための参考資料になるかな?

初期設定

サンプリング周波数 48000Hz

- (void)createAudioTools {
    
    AudioComponent component;
    AudioComponentDescription desc;
    desc.componentType = kAudioUnitType_Output;
    desc.componentSubType = kAudioUnitSubType_RemoteIO;
    desc.componentManufacturer = kAudioUnitManufacturer_Apple;
    desc.componentFlags = 0;
    desc.componentFlagsMask = 0;
    
    component = AudioComponentFindNext(NULL, &desc);
    AudioComponentInstanceNew(component, &_outputUnit);
    AudioUnitInitialize(_outputUnit);
    AURenderCallbackStruct callback;
    callback.inputProc = OutputCallback;
    callback.inputProcRefCon = (__bridge void * _Nullable)(self);
    
    AudioUnitSetProperty(_outputUnit,
                         kAudioUnitProperty_SetRenderCallback,
                         kAudioUnitScope_Global,
                         0,
                         &callback,
                         sizeof(AURenderCallbackStruct));
    
    AudioStreamBasicDescription outputFormat;
    UInt32 size = sizeof(AudioStreamBasicDescription);
    
    outputFormat.mSampleRate        = AUDIO_SAMPLING_RATE;
    outputFormat.mFormatID          = kAudioFormatLinearPCM;
    outputFormat.mFormatFlags       = kAudioFormatFlagIsPacked | kLinearPCMFormatFlagIsFloat;
    outputFormat.mBitsPerChannel    = 32;
    outputFormat.mChannelsPerFrame  = 2;
    outputFormat.mFramesPerPacket   = 1;
    outputFormat.mBytesPerFrame     = outputFormat.mBitsPerChannel / 8 * outputFormat.mChannelsPerFrame;
    outputFormat.mBytesPerPacket    = outputFormat.mBytesPerFrame * outputFormat.mFramesPerPacket;
    
    AudioUnitSetProperty(_outputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &outputFormat, size);
    AudioOutputUnitStart(_outputUnit);
    
}

多数のバッファーを作れないのでバイナリーデーターを頑張って入れなくてはなりません。

コールバック

static OSStatus OutputCallback(void *inRefCon,
                               AudioUnitRenderActionFlags *ioActionFlags,
                               const AudioTimeStamp *inTimeStamp,
                               UInt32 inBusNumber,
                               UInt32 inNumberFrames,
                               AudioBufferList *ioData)
{
    AudioUnitFloatTone *sm = (__bridge AudioUnitFloatTone *)inRefCon;
    return [sm renderFrames:inNumberFrames ioData:ioData];
}

ここで気をつけないといけないのが戻り値に”noErr”を返さなくてはなりません。返さないと停止してしまいます。

バイナリーデーターの挿入

- (OSStatus) renderFrames: (UInt32) numFrames ioData: (AudioBufferList *) ioData {

    // tone 信号のベースを作る
    float phasePerSample = 2.0 * M_PI * ( self.toneFrequancy / AUDIO_SAMPLING_RATE );
    
    for (NSInteger i = 0; i < ioData->mNumberBuffers; i++) {

        UInt32 channels = ioData->mBuffers[i].mNumberChannels;
        float *ptr = (float *)ioData->mBuffers[i].mData;
        for (NSInteger j = 0; j < numFrames; j+=channels) {
            
            // output Left
            float toneLeft = 0;
            if (self.toneOutPut & kToneLeft) {
                toneLeft = (sin( phasePerSample * self.toneCycle ) * self.toneVolume )* INT16_MAX / 32768.f;;
            }
            
            // output Right
            float toneRight = 0;
            if (self.toneOutPut & kToneRight) {
                toneRight = (sin( phasePerSample * self.toneCycle ) * self.toneVolume )* INT16_MAX / 32768.f;;
            }
            
            ptr[j * channels + 0] = toneLeft;
            ptr[j * channels + 1] = toneRight;
            
            self.toneCycle += channels;
            
        }
    }
    
    // over flow
    if ( self.toneCycle  > AUDIO_SAMPLING_RATE ) {
        self.toneCycle -= AUDIO_SAMPLING_RATE;
    }
    
    return noErr;
}

これで完了です。次はIntegerバージョンをどうぞ

AudioUnit Integer

コードはFloatを流用ですね

static OSStatus OutputCallback(void *inRefCon,
                               AudioUnitRenderActionFlags *ioActionFlags,
                               const AudioTimeStamp *inTimeStamp,
                               UInt32 inBusNumber,
                               UInt32 inNumberFrames,
                               AudioBufferList *ioData)
{
    AudioUnitIntegerTone *sm = (__bridge AudioUnitIntegerTone *)inRefCon;
    return [sm renderFrames:inNumberFrames ioData:ioData];
}

- (void)createAudioTools {
    
    AudioComponent component;
    AudioComponentDescription desc;
    desc.componentType = kAudioUnitType_Output;
    desc.componentSubType = kAudioUnitSubType_RemoteIO;
    desc.componentManufacturer = kAudioUnitManufacturer_Apple;
    desc.componentFlags = 0;
    desc.componentFlagsMask = 0;
    
    component = AudioComponentFindNext(NULL, &amp;desc);
    AudioComponentInstanceNew(component, &amp;_outputUnit);
    AudioUnitInitialize(_outputUnit);
    AURenderCallbackStruct callback;
    callback.inputProc = OutputCallback;
    callback.inputProcRefCon = (__bridge void * _Nullable)(self);
    
    AudioUnitSetProperty(_outputUnit,
                         kAudioUnitProperty_SetRenderCallback,
                         kAudioUnitScope_Global,
                         0,
                         &amp;callback,
                         sizeof(AURenderCallbackStruct));
    
    AudioStreamBasicDescription outputFormat;
    UInt32 size = sizeof(AudioStreamBasicDescription);
    
    outputFormat.mSampleRate        = AUDIO_SAMPLING_RATE;
    outputFormat.mFormatID          = kAudioFormatLinearPCM;
    outputFormat.mFormatFlags       = kAudioFormatFlagIsPacked | kLinearPCMFormatFlagIsSignedInteger;
    outputFormat.mBitsPerChannel    = 16;
    outputFormat.mChannelsPerFrame  = 2;
    outputFormat.mFramesPerPacket   = 1;
    outputFormat.mBytesPerFrame     = outputFormat.mBitsPerChannel / 8 * outputFormat.mChannelsPerFrame;
    outputFormat.mBytesPerPacket    = outputFormat.mBytesPerFrame * outputFormat.mFramesPerPacket;
    
    AudioUnitSetProperty(_outputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &amp;outputFormat, size);
    AudioOutputUnitStart(_outputUnit);
    
}
- (OSStatus) renderFrames: (UInt32) numFrames ioData: (AudioBufferList *) ioData {
    
    // tone 信号のベースを作る
    float phasePerSample = 2.0 * M_PI * ( self.toneFrequancy / AUDIO_SAMPLING_RATE );
    
    for (NSInteger i = 0; i < ioData->mNumberBuffers; i++) {
        
        UInt32 channels = ioData->mBuffers[i].mNumberChannels;
        int16_t *ptr = (int16_t *)ioData->mBuffers[i].mData;
        for (NSInteger j = 0; j < numFrames; j+=channels) {

            // output Left
            float toneLeft = 0;
            if (self.toneOutPut & kToneLeft) {
                toneLeft = sin( phasePerSample * self.toneCycle ) * self.toneVolume;
            }
            
            // output Right
            float toneRight = 0;
            if (self.toneOutPut & kToneRight) {
                toneRight = sin( phasePerSample * self.toneCycle ) * self.toneVolume;
            }

            ptr[j * channels + 0] = (int16_t)toneLeft;
            ptr[j * channels + 1] = (int16_t)toneRight;
            
            self.toneCycle += channels;
        }
    }
    
    // over flow
    if ( self.toneCycle  > AUDIO_SAMPLING_RATE ) {
        self.toneCycle -= AUDIO_SAMPLING_RATE;
    }
    
    return noErr;
}

まとめ・駆け足すぎてすみません

これでマスターですね(笑)

バイナリーデーターを使用するときてどんな時?例えばP2Pで音声データーをもらった時とかエンコーダーのライブラリを使用した時に画像と音声が別に吐き出された時に使用します。

この方法で音を鳴らしたHikaruappオリジナルアプリはこれです。

sine wave

sinewavebanner

サンプルコード

今回の音関係のサンプルコードをgithubにUPしておきますので参考にしてください。

プロジェクトファイル – github

 

コメント

タイトルとURLをコピーしました