幾種播放音頻文件的方式(十二) —— OpenAL框架之基本概覽(一)

版本記錄

版本號 時間
V1.0 2017.12.29

前言

ios系統中有很多方式可以播放音頻文件,這里我們就詳細的說明下播放音樂文件的原理和實例。感興趣的可以看我寫的上面幾篇。
1. 幾種播放音頻文件的方式(一) —— 播放本地音樂
2. 幾種播放音頻文件的方式(二) —— 音效播放
3. 幾種播放音頻文件的方式(三) —— 網絡音樂播放
4. 幾種播放音頻文件的方式(四) —— 音頻隊列服務(Audio Queue Services)(一)
5. 幾種播放音頻文件的方式(五) —— 音頻隊列服務(Audio Queue Services)簡介(二)
6. 幾種播放音頻文件的方式(六) —— 音頻隊列服務(Audio Queue Services)之關于音頻隊列(三)
7. 幾種播放音頻文件的方式(七) —— 音頻隊列服務(Audio Queue Services)之錄制音頻(四)
8. 幾種播放音頻文件的方式(八) —— 音頻隊列服務(Audio Queue Services)之播放音頻(五)
9. 幾種播放音頻文件的方式(九) —— Media Player框架之基本概覽(一)
10. 幾種播放音頻文件的方式(十) —— Media Player框架之簡單播放音頻示例(二)
11. 幾種播放音頻文件的方式(十一) —— AudioUnit框架之基本概覽(一)

框架基本

1. Overview

這個框架在開發文檔中是找不到的,但是在xcode中可以看到。下面我們就看一下OpenAL框架的頭文件和組成。和其他框架一樣,在使用前都需要引入頭文件。

#import <OpenAL/OpenAL.h>
openAL框架
/*!
    @file       OpenAL.h
    @framework  OpenAL.framework
    @copyright  (c) 2002-2015 by Apple, Inc., all rights reserved.
    @abstract   Umbrella header for OpenAL framework.
*/

#ifndef OpenAL_OpenAL_h
#define OpenAL_OpenAL_h

#include <OpenAL/al.h>
#include <OpenAL/alc.h>
#include <OpenAL/oalStaticBufferExtension.h>
#include <OpenAL/oalMacOSX_OALExtensions.h>

#endif /* defined(OpenAL_OpenAL_h) */

2. OpenAL構成

下面我們就看一下OpenAL的構成。

它由3個實體構成:Listener(聽者)、Source(音源)和Buffer(緩存)。

  • Listener :任何可以被Listener“聽到”的聲音都是來自揚聲器。openAL允許你指定Listener相對于Source的位置, 但是本例中我們忽略不計。我們只是針對最基本的靜態聲音。但是請記住“Listener”的概念,在你處理更復雜的情況時,你可以任意移動此對象。
  • Source:本質上類似與揚聲器,它將產生Listener可以“聽”到的聲音。像Listener一樣,你可以通過移動Source來獲得groovy位置效應。
  • Buffer: 就是我們播放的聲音。它保存原始的音頻數據。

3. 兩個重要的對象

  • device(設備):Device實際上是播放聲音的硬件。
  • context(環境):是當前所有聲音在其中播放的會話(session)。

工作過程

  • 獲取device
  • context關聯到device
  • 將數據放入buffer
  • buffer鏈接到一個source
  • 播放source

下面我們就詳細的看一下大致過程。

獲取設備device,并建立一個context

// define these somewhere, like in your .h file  

    ALCcontext* mContext;  ALCdevice* mDevice;  
    // start up openAL  
    -(void)initOpenAL  
    {  
        // Initialization   
        mDevice = alcOpenDevice(NULL);

        // select the "preferred device"    
        if (mDevice)
        {      
            // use the device to make a context         
            mContext = alcCreateContext(mDevice,NULL);       
            // set my context to the currently active one       
            alcMakeContextCurrent(mContext);   
        }  
    }

將數據存入buffer

//打開音頻文件
// get the full path of the file  
  NSString* fileName = [[NSBundle mainBundle] pathForResource:@"neatoEffect" ofType:@"caf"];  

  // first, open the file  
  AudioFileID fileID = [self openAudioFile:fileName];
// open the audio file  
// returns a big audio ID struct  

- (AudioFileID)openAudioFile:(NSString*)filePath  
{  
    AudioFileID outAFID;   
    // use the NSURl instead of a cfurlref cuz it is easier     
    NSURL * afUrl = [NSURL fileURLWithPath:filePath];
    // do some platform specific stuff..  
#if TARGET_OS_IPHONE    
    OSStatus result = AudioFileOpenURL((CFURLRef)afUrl, kAudioFileReadPermission, 0, &outAFID);  
#else   
    OSStatus result = AudioFileOpenURL((CFURLRef)afUrl, fsRdPerm, 0, &outAFID);  
#endif      
    if (result != 0)
        NSLog(@"cannot openf file: %@",filePath);  
   
    return outAFID;  

從文件中獲取實際音頻數據。要先計算出有多少數據在文件中

// find out how big the actual audio data is  
    UInt32 fileSize = [self audioFileSize:fileID];

/ find the audio portion of the file  
// return the size in bytes  
- (UInt32)audioFileSize:(AudioFileID)fileDescriptor  
{  
    UInt64 outDataSize = 0;    
    UInt32 thePropSize = sizeof(UInt64);   
    OSStatus result = AudioFileGetProperty(fileDescriptor, kAudioFilePropertyAudioDataByteCount, &thePropSize, &outDataSize);  
    if(result != 0)
        NSLog(@"cannot find file size");   
       
    return (UInt32)outDataSize;  
}

這里使用了個函數AudioFileGetProperty(),它用來計算文件中有多少數據并將其放入outDataSize變量。

將數據從文件復制到openAL緩存

// this is where the audio data will live for the moment  
unsigned char * outData = malloc(fileSize);

// this where we actually get the bytes from the file and put them  
// into the data buffer  
OSStatus result = noErr;  
result = AudioFileReadBytes(fileID, false, 0, &fileSize, outData);  
AudioFileClose(fileID);   //close the file 

if (result != 0)
    NSLog(@"cannot load effect: %@",fileName);  
   
NSUInteger bufferID;  

// grab a buffer ID from openAL  
alGenBuffers(1, &bufferID);  
// jam the audio data into the new buffer  
alBufferData(bufferID,AL_FORMAT_STEREO16,outData,fileSize,44100);    // save the buffer so I can release it later  
[bufferStorageArray addObject:[NSNumber numberWithUnsignedInteger:bufferID]];

上面分配一下空間給數據,使用audio toolkit中的AudioFileReadBytes()函數從文件中讀取字節到分配好的內存塊中。然后調用alGenBuffers()產生一個有 效的bufferID,再調用alBufferData()加載數據塊到openAL buffer的緩存中。

如果你是像文章開始介紹的那樣使用afconvert命令生成的音頻文件,那么你已經知道它們的格式和采樣率了。 然而,如果你想要支持各種音頻格式和頻率, 你最好要構建一個類似于audioFileSize:的方法,但使用kAudioFilePropertyDataFormat獲取格式然后轉換為適當的 AL_FORMAT,而獲得頻率可能更復雜些。

將準備好的緩存區連接到source

像緩存一樣,我們也需要從openAL獲取一個有效的sourceID。一旦我們獲得了sourceID我們就可以將source和緩存聯系起來 了。最后我們還要進行一些緩存基本設定。如果我們想循環播放,還要設定AL_LOOPING為true。 缺省時,播放是不循環的,所以忽略它就好。然后我將此ID存入到字典數據結構中,以便可以根據名稱查找ID。

NSUInteger sourceID;  
// grab a source ID from openAL  
alGenSources(1, &sourceID);  
// attach the buffer to the source  
alSourcei(sourceID, AL_BUFFER, bufferID);  
// set some basic source prefs  
alSourcef(sourceID, AL_PITCH, 1.0f);  
alSourcef(sourceID, AL_GAIN, 1.0f);  
if (loops)
    alSourcei(sourceID, AL_LOOPING, AL_TRUE);  
 
// store this for future use  
[soundDictionary setObject:[NSNumber numberWithUnsignedInt:sourceID] forKey:@"neatoSound"];  
// clean up the buffer  if (outData)  
{  
    free(outData);     
    outData = NULL;  
}

開始播放

// the main method: grab the sound ID from the library  
// and start the source playing  
- (void)playSound:(NSString*)soundKey  
{  
    NSNumber* numVal = [soundDictionary objectForKey:soundKey];    
    if (numVal == nil)
        return;    
   
    NSUInteger sourceID = [numVal unsignedIntValue];   
    alSourcePlay(sourceID);  
}

如果聲音不循環,那么它將會自然停止。如果是循環的,你可能需要停止它。

停止播放

- (void)stopSound:(NSString*)soundKey  
{  
    NSNumber* numVal = [soundDictionary objectForKey:soundKey];    
    if (numVal == nil)
        return;    
   
    NSUInteger sourceID = [numVal unsignedIntValue];
    alSourceStop(sourceID);  
}

清除臨時內存

-(void)cleanUpOpenAL:(id)sender  
{  
    // delete the sources   
    for (NSNumber* sourceNumber in [soundDictionary allValues])
    {      
        NSUInteger sourceID = [sourceNumber unsignedIntegerValue];         
        alDeleteSources(1, &sourceID);     
    }  
   
    [soundDictionary removeAllObjects];  
    // delete the buffers   
    for (NSNumber* bufferNumber in bufferStorageArray)
    {      
        NSUInteger bufferID = [bufferNumber unsignedIntegerValue];         
        alDeleteBuffers(1, &bufferID);     
    }  
    [bufferStorageArray removeAllObjects];  
   
    // destroy the context      
    alcDestroyContext(mContext);   
    // close the device     
    alcCloseDevice(mDevice);  
}

注意:在實際應用中你可能有不只一個source(我的每個buffer都有一個source,但我只有8組聲音所以不會有什么問題),而可以使用 的source數目是有上限的。

參考文章

1. OpenAL系列之一 – iPhone上的OpenAL音頻

后記

未完,待續~~~

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容