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, &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 | 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, &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
サンプルコード
今回の音関係のサンプルコードをgithubにUPしておきますので参考にしてください。
プロジェクトファイル – github
コメント