Arama Motoru

OpenAL Ders 6: Gelişmiş Yükleme ve Hata Yönetimi

14/06/2003
02/05/2010

Bazı şeyleri işlemede çok titiz olmanın bize gerekmediği düşünülüp birkaç tane çok basit çam devirdik. Bunun sebebi,  sağlamlıktan ziyade daha kolay öğrenmek için kodları basit yazmaktı. Çabucak ileri kısma geçmek isteğinden doğru yolu öğrenmesini erteledik. Ki en önemlisi, hataları yönetmenin daha gelişmiş bir yolunu öğrenmekti. Yine , ses verisini yükleme yöntemini tekrar düzenleyeceğiz. Metotlarımızda yanlış olan herhangi bir şey yok, ama süreç daha organize ve esnek bir yaklaşıma ihtiyaç duyar.
İlkin, bitirdiğimiz zaman bize çokça yardımı olacak birkaç fonksiyonu değerlendireceğiz.

string GetALErrorString(ALenum err);
/*
 * 1) hata kodunu çıkarsar.
 * 2) hata, string olarak döndürülür.
 */
 
string GetALCErrorString(ALenum err);
/*
 * 1) hata kodunu çıkarsar.
 * 2) hata, string olarak döndürülür.
 */
 
ALuint LoadALBuffer(string path);
/*
 * 1) bir bufer yaratır.
 * 2) buffer’a bir wav dosyası yükler.
 * 3) buffer id döndürülür.
 */
 
ALuint GetLoadedALBuffer(string path);
/*
 * 1) dosyanın hali hazırda yüklülüğü kontrol edilir.
 * 2) yüklüyse, buffer id döndürülür.
 * 3) yüklü değilse, yükleyip buffer id döndürülür.
 */
 
ALuint LoadALSample(string path, bool loop);
/*
 * 1) bir source yaratır.
 * 2) dizinle 'GetLoadedALBuffer' çağrılıp 
 *    source’ünün buffer’ı olarak , döndürülen buffer id’i kullanır.
 * 3) source id döndürülür.
 */
 
void KillALLoadedData();
/*
 * 1) geçici yüklü faz veriyi serbest bırakır.
 */
 
bool LoadALData();
/*
 * 1) uygulama için tüm buffer’lar ve source’leri yükler.
 */
 
void KillALData();
/*
 * 1) tüm bufferları serbest bırakır.
 * 2) tüm sourcelerı serbest bırakır.
 */
 
vector LoadedFiles; // geçici yüklü dosya dizinlerini tutar.
vector Buffers; // tüm yüklü buffer’ları tutar.
vector Sources; // tüm geçerli source’leri tutar.

Fonksiyonlara yakından bakıp ne yaptıklarını anlamayı çalışalım. Basitçe, artık bufferlar ve sourceler arasındaki ilişkiden endişe duymak zorunda olmayacağımız sistemi yaratmaya giriştik. Bir dosyadan bir source'nin yaratılması için çağırabildiğimiz ve kendi bufferını yaratmayı yönetecek bu sistem de buffer kopyalaması oluşmaz (Aynı verili iki buffer'ın olmaz). Bu sistem, sınırlı bir resource olarak bufferları yönetir, Ve verimli bir şekilde bu resource'i yönetir.

string GetALErrorString(ALenum err)
{
    switch(err)
    {
        case AL_NO_ERROR:
            return string("AL_NO_ERROR");
        break;
 
        case AL_INVALID_NAME:
            return string("AL_INVALID_NAME");
        break;
 
        case AL_INVALID_ENUM:
            return string("AL_INVALID_ENUM");
        break;
 
        case AL_INVALID_VALUE:
            return string("AL_INVALID_VALUE");
        break;
 
        case AL_INVALID_OPERATION:
            return string("AL_INVALID_OPERATION");
        break;
 
        case AL_OUT_OF_MEMORY:
            return string("AL_OUT_OF_MEMORY");
        break;
    };
}

Bu fonksiyon, OpenAL hata kodunu string’e çevirir böylece konsolda okunabilir (Veya bir diğer çıktı aygıtında). OpenAL sdk mevcut versiyonunda 'AL_OUT_OF_MEMORY' hatası bahsedilen tek istisnadır. Tabi, biz tüm hataları dikkate alacağız ki kodumuz sonraki sürümlerle uyumlu olsun.

string GetALCErrorString(ALenum err)
{
    switch(err)
    {
        case ALC_NO_ERROR:
            return string("AL_NO_ERROR");
        break;
 
        case ALC_INVALID_DEVICE:
            return string("ALC_INVALID_DEVICE");
        break;
 
        case ALC_INVALID_CONTEXT:
            return string("ALC_INVALID_CONTEXT");
        break;
 
        case ALC_INVALID_ENUM:
            return string("ALC_INVALID_ENUM");
        break;
 
        case ALC_INVALID_VALUE:
            return string("ALC_INVALID_VALUE");
        break;
 
        case ALC_OUT_OF_MEMORY:
            return string("ALC_OUT_OF_MEMORY");
        break;
    };
}

Bu fonksiyon, Alc hatalarını yorumlanması için öncekine benzer bir iş yapar. OpenAL ve Alc, ortak id'ler paylaşır, Tabi tamamen ortak değil ve Her ikisi için aynı fonksiyonu kullanacak kadar değil.
'alGetError' fonksiyonu hakkında bir not daha: OpenAL sdk, aynı anda sadece tek bir hatanın tutulduğunu belirtmektedir (yığma yok). Fonksiyon çağrıldığı zaman almış olduğu ilk hatayı döndürür ve sonra hata bitini 'AL_NO_ERROR' e getirir. Diğer bir deyişle hali hazırda orada depolu önceden bir hata yoksa hata bitinde sadece bir hata depolu olacaktır.

ALuint LoadALBuffer(string path)
{
    // Buffer’ı tanımlayan bilgiyi depolayacak değişkenler.
    ALenum format;
    ALsizei size;
    ALvoid* data;
    ALsizei freq;
    ALboolean loop;
 
    // Buffer id ve hata kontrol değişkenleri.
    ALuint buffer;
    ALenum result;
 
    // bir buffer yarat. Başarıyla yaratıldığını kontrol et.
    alGenBuffers(1, &buffer);
 
    if ((result = alGetError()) != AL_NO_ERROR)
        throw GetALErrorString(result);
 
    // dosyadan wav datayı oku. Doğru yüklülüğünü kontrol et.
    alutLoadWAVFile(szFilePath, &format, &data, &size, &freq, &loop);
 
    if ((result = alGetError()) != AL_NO_ERROR)
        throw GetALErrorString(result);
 
    // buffer’a wav datasını yolla. Doğru şekilde alındığını kontrol et.
    alBufferData(buffer, format, data, size, freq);
 
    if ((result = alGetError()) != AL_NO_ERROR)
        throw GetALErrorString(result);
 
    // geçici datadan kurtul.
    alutUnloadWAV(format, data, size, freq);
 
    if ((result = alGetError()) != AL_NO_ERROR)
        throw GetALErrorString(result);
 
    // buffer id’i döndür.
    return buffer;
}

Anlayacağınız üzere yükleme aşamasında mümkün tüm hataların kontrolünü yaparız. Bir hatanın fırlatılmasına(throw) sebebiyet verecek birçok şey bu aşamada olabilir. Mesela bufferı yaratmak veya veriyi yüklemek için yeterli sistem belleği yoktur, Wav dosyasının kendi bile var olmayabilir veya bir hata üretecek OpenAL fonksiyonlarının herhangi birisine geçersiz bir parametre değeri geçilmiş olabilir.

ALuint GetLoadedALBuffer(string path)
{
    int count = 0; // 'count', buffer listesinin indeksi olacak.
 
    ALuint buffer; // yüklü bufferın Buffer id’si.
 
 
    // listede her bir dosya dizini için tekrarla.
    for(vector::iterator iter = LoadedFiles.begin(); iter != LoadedFiles.end(); ++iter, count++)
    {
        // Eğer bu dosya yolu hali hazırda yüklü olanla aynıysa, sadece buffer ID’si döndürülür.
        if(*iter == path)
            return Buffers[count];
    }
 
    // Eğer bir şey çıkartılamazsa bu dosya yenidir ve onun için bir buffer yaratırız.
    buffer = LoadALBuffer(path);
 
    // Listeye bu yeni buffer’ı ekleyip bu dosyanın hali hazırda yüklendiği kaydedilir.
    Buffers.push_back(buffer);
 
    LoadedFiles.push_back(path);
 
    return buffer;
}

Bu muhtemelen,  çoğunuzu sıkıntıya sokacak kod parçası olacak, Ama o gerçekte karmaşık değildir. Şimdiye kadar yüklediğimiz bütün wav dosya yollarını içeren bir listede bir arama yapıyoruz. Eğer dizinlerin biri yüklemeyi istediğimizle aynıysa,  sadece ilk yüklediğimizdeki onun buffer id'sini döndüreceğiz. Bu fonksiyon ile dosyalarımızı yüklediğimiz sürece , asla kopyalama nedenli  buffer israf etmiş olmayacağız. Bu şekilde yüklenen her dosya yine kendi listesiyle teması kesmemeli. 'Bufferların’ listesi, 'yüklüdosyalar' listesine paraleldir. Demek istediğim,Bufferlar’ indeksindeki her buffer, bufferların yaratıldığı ‘yüklüdosyalar’’daki indeksin dizini aynıdır.

ALuint LoadALSample(string path, bool loop)
{
    ALuint source;
    ALuint buffer;
    ALenum result;
 
    // dosyanın buffer id’sini elde et (gerekiyorsa onu yükle).
    buffer = GetLoadedALBuffer(path);
 
    // bir source üret.
    alGenSources(1 &source);
 
    if ((result = alGetError()) != AL_NO_ERROR)
        throw GetALErrorString(result);
 
    // source özelliklerini yapılandır
    alSourcei (source, AL_BUFFER,   buffer   );
    alSourcef (source, AL_PITCH,    1.0      );
    alSourcef (source, AL_GAIN,     1.0      );
    alSourcefv(source, AL_POSITION, SourcePos);
    alSourcefv(source, AL_VELOCITY, SourceVel);
    alSourcei (source, AL_LOOPING,  loop     );
 
    // source id’i kaydet.
    Sources.push_back(source);
 
    // source id’i döndür.
    return source;
}

Bizin için bufferları yönetecek bir sistemi yarattığımız düşünülürse, sadece source'leri pişpişleyecek bir ilaveye ihtiyaç duyarız. Bu kodda, yüklü dosyanın buffer id’sini dosya arama sonucu olarak elde ederiz. Bu buffer yeni source'e bağlanır. Source id'i kaydedip onu döndürürüz.

void KillALLoadedData()
{
    LoadedFiles.clear();
}

'gLoadedFilesv' global vektörü bir buffera yüklü tüm wav dosyalarının dosya yolunu depolar. Verimizin hepsini yükledikten sonra ayakaltında bu veriyi tutmanın anlamı yoktur, bundan dolayı ondan kurtulacağız.

// Source id'leri.
 
ALuint phaser1;
ALuint phaser2;
 
void LoadALData()
{
    // Bu şeyler uygulamanızda burada. Bufferlar hakkında endişe yok.
    phaser1 = LoadALSample("wavdata/phaser.wav", false);
    phaser2 = LoadALSample("wavdata/phaser.wav", true);
 
    KillLoadedALData();
}

Önceki derslerde bu fonksiyonu gördük. Program tarafından kullanılan tüm wav'ları yükleyen programın bir bölümünü temsil ediyor. Onda, sistemimizin, neden faydalı olduğunu görebiliriz. İki farklı source’e aynı wav dosyasını yüklemek üzere çağrıyı yapmış olsak da, 'phaser.wav' dosyası bufferı sadece bir kez yaratılacak, ve 'gPhaser1' ve 'gPhaser2' sourcelerinin ikisi de çalma için bu bufferı kullanacak. Bufferların yönetiminde artık kaygı yok çünkü Sistem otomatik olarak onları yönetecek.

void KillALData()
{
    // tüm buffer datasının serbest bırak.
    for (vector::iterator iter = Buffers.begin(); iter != Buffers.end(); ++iter)
        alDeleteBuffers(1, iter);
 
    // tüm source datasının serbest bırak.
    for (vector::iterator iter = Sources.begin(); iter != Sources.end(); ++iter)
        alDeleteBuffers(1, iter);
 
    // listeleri yok et.
    Buffers.clear();
    Sources.clear();
}

En başından, buffer ve source id'lerini stl vektörlerinde depoladık.
. Buffer ve sourcelerin tümünü boşaltıp ayrı ayrı onları serbest bırakırız. Sonra listelerini yok ederiz. Şimdi yapmaya ihtiyaç duyduğumuz tek şey, yakalayacağımız(catch)  OpenAL hatalarını fırlattırmaktır(throw).

    try
    {
        InitOpenAL();
 
        LoadALData();
    }
    catch(string err)
    {
        cout << "OpenAL error: " << err.c_str() << endl;
    }

Eğer bir şey, yükleme esnasında yanlış gitse derhal bundan haberdar edileceğiz. Hatayı yakaladığımız (catch) zaman bu konsolda bildirilecek. Debuglama veya genel hata raporlamada bunu kullanabiliriz.
Hepsi bu kadar. Hataları raporlamanın daha ileri bir yolu, Ve wav dosyalarınız yüklemenin daha sağlam bir yolu. Daha fazla esnekliği imkan vermesi için gelecekte bazı değişiklikler yapmaya ihtiyaç duyabiliriz, Ama şimdilik gelecek derslerde temel dosya yüklemesi için bu kaynağı kullanacağız. Bu kodu genişletmek için gelecek dersleri bekle.


Bu dersin java versiyonu için Java Bindings for OpenAL sayfasını ziyaret edin (Athomas Goldberg tarafından adapte edilmiştir)

Dersin Orjinali : http://www.devmaster.net/articles/openal-tutorials/lesson6.php