आपके खेल को ध्वनि की आवश्यकता है! आपने शायद स्क्रीन पर ड्राइंग के लिए पहले से ही OpenGL का उपयोग किया है। आपको इसका API पता चल गया है, और इसलिए आप OpenAL में बदल गए क्योंकि नाम परिचित लगता है।खैर, अच्छी खबर यह है कि ओपेन भी बहुत परिचित एपीआई है। यह मूल रूप से OpenGL विनिर्देश एपीआई की नकल करने के लिए डिज़ाइन किया गया था। यही कारण है कि मैंने इसे गेम के लिए कई साउंड सिस्टम में से चुना; इसके अलावा, यह क्रॉस-प्लेटफॉर्म है।इस लेख में, मैं इस बारे में विस्तार से बात करूंगा कि C ++ में लिखे गए गेम में OpenAL का उपयोग करने के लिए किस कोड की आवश्यकता है। हम कोड उदाहरणों के साथ 3 डी अंतरिक्ष में ध्वनियों, संगीत और ध्वनि की स्थिति पर चर्चा करेंगे।ओपेन का इतिहास
मैं संक्षिप्त होने की कोशिश करूंगा। जैसा कि ऊपर उल्लेख किया गया है, यह जानबूझकर ओपनजीएल एपीआई की नकल के रूप में तैयार किया गया था, और इसके लिए एक कारण है। यह एक सुविधाजनक एपीआई है जो कई के लिए जाना जाता है, और यदि ग्राफिक्स गेम इंजन के एक तरफ हैं, तो ध्वनि अलग होनी चाहिए। प्रारंभ में, OpenAL को ओपन-सोर्स माना जाता था, लेकिन फिर कुछ हुआ ...लोगों को ग्राफिक्स के रूप में ध्वनि में इतनी दिलचस्पी नहीं है, इसलिए क्रिएटिव ने अंततः OpenAL को अपनी संपत्ति बनाया, और संदर्भ कार्यान्वयन अब मालिकाना है और स्वतंत्र नहीं है। परंतु! OpenAL विनिर्देश अभी भी एक "खुला" मानक है, अर्थात यह प्रकाशित है ।समय-समय पर, विनिर्देशों में संशोधन किया जाता है, लेकिन कई नहीं। ग्राफिक्स के रूप में ध्वनि में तेजी से बदलाव नहीं होता है, क्योंकि इसके लिए कोई विशेष आवश्यकता नहीं है।खुले विनिर्देश ने अन्य लोगों को विनिर्देश के खुले स्रोत के कार्यान्वयन की अनुमति दी। ऐसा ही एक कार्यान्वयन OpenAL सॉफ्ट है , और स्पष्ट रूप से, यह किसी भी अन्य के लिए देखने के लिए कोई मतलब नहीं है। यह वह कार्यान्वयन है जिसका मैं उपयोग करूंगा, और मेरा सुझाव है कि आप इसका भी उपयोग करें।वह क्रॉस-प्लेटफॉर्म है। यह काफी उत्सुकता से लागू किया जाता है - वास्तव में, पुस्तकालय के अंदर आपके सिस्टम में मौजूद अन्य ध्वनि एपीआई का उपयोग करता है। विंडोज पर, यह यूनिक्स, ओएसएस पर डायरेक्टसाउंड का उपयोग करता है । इसके लिए धन्यवाद, वह क्रॉस-प्लेटफॉर्म बनने में सक्षम थी; संक्षेप में, यह रैपर एपीआई के लिए एक बड़ा नाम है। आप इस एपीआई की गति के बारे में चिंतित हो सकते हैं। लेकिन चिंता मत करो। यह एक ही ध्वनि है, और यह एक बड़ा भार नहीं बनाता है, इसलिए इसे ग्राफिक्स एपीआई द्वारा आवश्यक बड़े अनुकूलन की आवश्यकता नहीं है। लेकिन पर्याप्त कहानी, चलो प्रौद्योगिकी पर चलते हैं।आपको OpenAL में कोड लिखने की क्या आवश्यकता है?
आपको अपनी पसंद के टूलचेन में ओपनल सॉफ्ट बनाने की आवश्यकता है। यह एक बहुत ही सरल प्रक्रिया है जिसे आप सोर्स इनस्टॉल सेक्शन के निर्देशों के अनुसार फॉलो कर सकते हैं । मुझे इससे कोई समस्या नहीं है, लेकिन यदि आपको कोई कठिनाई है, तो मूल लेख के तहत एक टिप्पणी लिखें या ओपनअल सॉफ्ट मेलिंग सूची में लिखें ।अगला, आपको कुछ ध्वनि फ़ाइलों और उन्हें डाउनलोड करने के तरीके की आवश्यकता होगी। ऑडियो डेटा को बफ़र्स में लोड करना और विभिन्न ऑडियो प्रारूपों के सूक्ष्म विवरण इस लेख के दायरे से बाहर हैं, लेकिन आप Ogg / Vorbis फ़ाइलों को डाउनलोड और स्ट्रीमिंग के बारे में पढ़ सकते हैं । WAV फ़ाइलों को डाउनलोड करना बहुत सरल है, इस बारे में इंटरनेट पर पहले से ही सैकड़ों लेख हैं।ऑडियो फ़ाइलों को खोजने का कार्य आपको अपने लिए तय करना होगा। इंटरनेट पर कई शोर और विस्फोट हैं जिन्हें आप डाउनलोड कर सकते हैं। यदि आपके पास एक अफवाह है, तो आप अपने खुद के चिपट्यून संगीत [ हैबे पर अनुवाद ] लिखने की कोशिश कर सकते हैं ।इसके अलावा, प्रोग्रामर्स गाइड को OpenALSoft से संभाल कर रखें । यह प्रलेखन "आधिकारिक" विशेषज्ञता के साथ बहुत बेहतर पीडीएफ है।वह, वास्तव में, सब है। हम मान लेंगे कि आप पहले से ही जानते हैं कि कोड कैसे लिखना है, आईडीई और टूलचैन का उपयोग करें।ओपन एपीआई अवलोकन
जैसा कि मैंने कई बार कहा, यह ओपनजीएल एपीआई के समान है। समानता इस तथ्य में निहित है कि यह राज्यों पर आधारित है और आप विवरणकर्ताओं / पहचानकर्ताओं के साथ बातचीत करते हैं, न कि सीधे वस्तुओं के साथ।OpenGL और OpenAL में एपीआई सम्मेलनों के बीच विसंगतियां हैं, लेकिन वे महत्वपूर्ण नहीं हैं। ओपनजीएल में, आपको एक प्रतिपादन संदर्भ उत्पन्न करने के लिए विशेष ओएस कॉल करने की आवश्यकता होती है। ये चुनौतियाँ विभिन्न OS के लिए भिन्न हैं और वास्तव में OpenGL विनिर्देश का हिस्सा नहीं हैं। ओपन में, सब कुछ अलग है - संदर्भ निर्माण कार्य विनिर्देश का हिस्सा हैं और ऑपरेटिंग सिस्टम की परवाह किए बिना समान हैं।एपीआई के साथ बातचीत करते समय, तीन मुख्य प्रकार की वस्तुएं होती हैं जिनके साथ आप बातचीत करते हैं। श्रोताओं("श्रोता") 3 डी अंतरिक्ष में स्थित "कान" का स्थान है (हमेशा एक श्रोता होता है)। स्रोत ("स्रोत") "स्पीकर" हैं जो ध्वनि बनाते हैं, फिर से 3 डी अंतरिक्ष में। श्रोता और स्रोतों को अंतरिक्ष में स्थानांतरित किया जा सकता है और इस पर निर्भर करता है कि आप खेल में वक्ताओं के माध्यम से क्या सुनते हैं।अंतिम वस्तु बफ़र हैं । वे ध्वनियों के नमूने संग्रहीत करते हैं जो श्रोताओं के लिए स्रोत खेलेंगे।ऐसे मोड भी हैं जो गेम का उपयोग ओपनअल के माध्यम से ऑडियो को संसाधित करने के तरीके को बदलने के लिए करते हैं।सूत्रों का कहना है
जैसा कि ऊपर उल्लेख किया गया है, ये वस्तुएं ध्वनियों के स्रोत हैं। उन्हें स्थिति और दिशा निर्धारित की जा सकती है, और वे प्लेबैक ऑडियो डेटा के बफर से जुड़े हैं।श्रोता
खेल में "कान" का एकमात्र सेट। श्रोता जो सुनता है उसे कंप्यूटर के वक्ताओं के माध्यम से पुन: प्रस्तुत किया जाता है। उसका भी एक पद है।बफ़र
ओपनजीएल में, उनके समकक्ष Texture2D है। संक्षेप में, यह ऑडियो डेटा है जो स्रोत पुन: पेश करता है।जानकारी का प्रकार
क्रॉस-प्लेटफ़ॉर्म कोड का समर्थन करने में सक्षम होने के लिए, ओपल एक निश्चित अनुक्रम करता है और कुछ डेटा प्रकारों को परिभाषित करता है। वास्तव में, यह OpenGL का इतनी सटीक रूप से अनुसरण करता है कि हम OpenAL प्रकारों को भी सीधे OpenGL प्रकारों में परिवर्तित कर सकते हैं। नीचे दी गई तालिका उन्हें और उनके समकक्षों को सूचीबद्ध करती है।OpenAL
OpenAL त्रुटि पहचान को सरल बनाने के
तरीके पर एक लेख है , लेकिन पूर्णता के लिए, मैं इसे यहां दोहराऊंगा। ओपन एपीआई कॉल दो प्रकार की होती हैं : नियमित और प्रासंगिक।प्रसंग के साथ शुरू होने वाले कॉन्टेक्ट कॉल alc
OpenGL win32 कॉल्स के समान हैं जो कि लिनक्स पर रेंडरिंग संदर्भ या उनके समकक्षों को प्राप्त करने के लिए हैं। सभी ऑपरेटिंग सिस्टम के लिए समान कॉल करने के लिए ध्वनि एक सरल पर्याप्त चीज़ है। साधारण कॉल के साथ शुरू al
। प्रासंगिक कॉल में त्रुटियां प्राप्त करने के लिए, हम कॉल करते हैं alcGetError
; नियमित कॉल के मामले में, हम कॉल करते हैं alGetError
। वे या तो एक मान ALCenum
या एक मान लौटाते हैं ALenum
जो बस संभावित त्रुटियों को सूचीबद्ध करता है।अब हम केवल एक मामले पर विचार करेंगे, लेकिन बाकी सभी चीजों में वे लगभग समान हैं। आइए सामान्य चुनौतियों को लेते हैं al
। पहले, विवरण देने के उबाऊ काम करने के लिए एक प्रीप्रोसेसर मैक्रो बनाएं:#define alCall(function, ...) alCallImpl(__FILE__, __LINE__, function, __VA_ARGS__)
सैद्धांतिक रूप से, अपने संकलक समर्थन कर सकते हैं नहीं __FILE__
या तो __LINE__
है, लेकिन ईमानदारी से, मैं आश्चर्यचकित हो कि अगर निकला तो किया जाना है। __VA_ARGS__
इस मैक्रो को पारित किए जा सकने वाले तर्कों की एक परिवर्तनीय संख्या को दर्शाता है।अगला, हम एक फ़ंक्शन को कार्यान्वित करते हैं जो मैन्युअल रूप से रिपोर्ट की गई अंतिम त्रुटि प्राप्त करता है और मानक त्रुटि स्ट्रीम के लिए एक स्पष्ट मूल्य प्रदर्शित करता है।bool check_al_errors(const std::string& filename, const std::uint_fast32_t line)
{
ALenum error = alGetError();
if(error != AL_NO_ERROR)
{
std::cerr << "***ERROR*** (" << filename << ": " << line << ")\n" ;
switch(error)
{
case AL_INVALID_NAME:
std::cerr << "AL_INVALID_NAME: a bad name (ID) was passed to an OpenAL function";
break;
case AL_INVALID_ENUM:
std::cerr << "AL_INVALID_ENUM: an invalid enum value was passed to an OpenAL function";
break;
case AL_INVALID_VALUE:
std::cerr << "AL_INVALID_VALUE: an invalid value was passed to an OpenAL function";
break;
case AL_INVALID_OPERATION:
std::cerr << "AL_INVALID_OPERATION: the requested operation is not valid";
break;
case AL_OUT_OF_MEMORY:
std::cerr << "AL_OUT_OF_MEMORY: the requested operation resulted in OpenAL running out of memory";
break;
default:
std::cerr << "UNKNOWN AL ERROR: " << error;
}
std::cerr << std::endl;
return false;
}
return true;
}
कई संभावित गलतियाँ नहीं हैं। कोड में मैंने जो व्याख्याएँ लिखी हैं, वे केवल वही जानकारी हैं जो आपको इन त्रुटियों के बारे में प्राप्त होंगी, लेकिन विनिर्देश बताते हैं कि कोई विशेष फ़ंक्शन किसी विशिष्ट त्रुटि को क्यों लौटा सकता है।फिर हम दो अलग-अलग टेम्प्लेट फ़ंक्शंस लागू करते हैं, जो हमारे सभी ओपन-कॉल को लपेटेंगे।template<typename alFunction, typename... Params>
auto alCallImpl(const char* filename,
const std::uint_fast32_t line,
alFunction function,
Params... params)
->typename std::enable_if_t<!std::is_same_v<void,decltype(function(params...))>,decltype(function(params...))>
{
auto ret = function(std::forward<Params>(params)...);
check_al_errors(filename,line);
return ret;
}
template<typename alcFunction, typename... Params>
auto alcCallImpl(const char* filename,
const std::uint_fast32_t line,
alcFunction function,
ALCdevice* device,
Params... params)
->typename std::enable_if_t<std::is_same_v<void,decltype(function(params...))>,bool>
{
function(std::forward<Params>(params)...);
return check_alc_errors(filename,line,device);
}
उनमें से दो हैं, क्योंकि पहले का उपयोग ओपनल कार्यों के लिए किया जाता है जो वापस आते हैं void
, और दूसरे का उपयोग तब किया जाता है जब फ़ंक्शन गैर-रिक्त मान लौटाता है। यदि आप C ++ में मेटाप्रोग्रामिंग टेम्प्लेट से बहुत परिचित नहीं हैं, तो सी कोड के हिस्सों पर एक नज़र डालें std::enable_if
। वे निर्धारित करते हैं कि प्रत्येक फ़ंक्शन कॉल के लिए कंपाइलर द्वारा इनमें से कौन से टेम्पलेट फ़ंक्शन कार्यान्वित किए जाते हैं।और अब कॉल के लिए भी यही है alc
:#define alcCall(function, device, ...) alcCallImpl(__FILE__, __LINE__, function, device, __VA_ARGS__)
bool check_alc_errors(const std::string& filename, const std::uint_fast32_t line, ALCdevice* device)
{
ALCenum error = alcGetError(device);
if(error != ALC_NO_ERROR)
{
std::cerr << "***ERROR*** (" << filename << ": " << line << ")\n" ;
switch(error)
{
case ALC_INVALID_VALUE:
std::cerr << "ALC_INVALID_VALUE: an invalid value was passed to an OpenAL function";
break;
case ALC_INVALID_DEVICE:
std::cerr << "ALC_INVALID_DEVICE: a bad device was passed to an OpenAL function";
break;
case ALC_INVALID_CONTEXT:
std::cerr << "ALC_INVALID_CONTEXT: a bad context was passed to an OpenAL function";
break;
case ALC_INVALID_ENUM:
std::cerr << "ALC_INVALID_ENUM: an unknown enum value was passed to an OpenAL function";
break;
case ALC_OUT_OF_MEMORY:
std::cerr << "ALC_OUT_OF_MEMORY: an unknown enum value was passed to an OpenAL function";
break;
default:
std::cerr << "UNKNOWN ALC ERROR: " << error;
}
std::cerr << std::endl;
return false;
}
return true;
}
template<typename alcFunction, typename... Params>
auto alcCallImpl(const char* filename,
const std::uint_fast32_t line,
alcFunction function,
ALCdevice* device,
Params... params)
->typename std::enable_if_t<std::is_same_v<void,decltype(function(params...))>,bool>
{
function(std::forward<Params>(params)...);
return check_alc_errors(filename,line,device);
}
template<typename alcFunction, typename ReturnType, typename... Params>
auto alcCallImpl(const char* filename,
const std::uint_fast32_t line,
alcFunction function,
ReturnType& returnValue,
ALCdevice* device,
Params... params)
->typename std::enable_if_t<!std::is_same_v<void,decltype(function(params...))>,bool>
{
returnValue = function(std::forward<Params>(params)...);
return check_alc_errors(filename,line,device);
}
सबसे बड़ा बदलाव समावेशन है device
, जिसे सभी कॉल का उपयोग करते हैं alc
, साथ ही साथ शैली की त्रुटियों के संगत उपयोग ALCenum
और ALC_
। वे बहुत समान दिखते हैं, और बहुत लंबे समय से मेरे कोड और समझ al
को alc
खराब करने के लिए छोटे परिवर्तन , इसलिए मैंने इसके ठीक ऊपर पढ़ना जारी रखा c
।बस इतना ही। आमतौर पर, C ++ में एक OpenAL कॉल निम्न विकल्पों में से एक जैसा दिखता है:
alGenSources(1, &source);
ALenum error = alGetError();
if(error != AL_NO_ERROR)
{
}
alcCaptureStart(&device);
ALCenum error = alcGetError();
if(error != ALC_NO_ERROR)
{
}
const ALchar* sz = alGetString(param);
ALenum error = alGetError();
if(error != AL_NO_ERROR)
{
}
const ALCchar* sz = alcGetString(&device, param);
ALCenum error = alcGetError();
if(error != ALC_NO_ERROR)
{
}
लेकिन अब हम इसे इस तरह से कर सकते हैं:
if(!alCall(alGenSources, 1, &source))
{
}
if(!alcCall(alcCaptureStart, &device))
{
}
const ALchar* sz;
if(!alCall(alGetString, sz, param))
{
}
const ALCchar* sz;
if(!alcCall(alcGetString, sz, &device, param))
{
}
यह आपको अजीब लग सकता है, लेकिन यह मेरे लिए अधिक सुविधाजनक है। बेशक, आप एक अलग संरचना चुन सकते हैं।.Wav फाइलें डाउनलोड करें
आप या तो उन्हें स्वयं डाउनलोड कर सकते हैं, या लाइब्रेरी का उपयोग कर सकते हैं। यहाँ लोडिंग .wav फ़ाइलों का एक ओपन-सोर्स कार्यान्वयन है । मैं पागल हूं, इसलिए मैं खुद ऐसा करता हूं:std::int32_t convert_to_int(char* buffer, std::size_t len)
{
std::int32_t a = 0;
if(std::endian::native == std::endian::little)
std::memcpy(&a, buffer, len);
else
for(std::size_t i = 0; i < len; ++i)
reinterpret_cast<char*>(&a)[3 - i] = buffer[i];
return a;
}
bool load_wav_file_header(std::ifstream& file,
std::uint8_t& channels,
std::int32_t& sampleRate,
std::uint8_t& bitsPerSample,
ALsizei& size)
{
char buffer[4];
if(!file.is_open())
return false;
if(!file.read(buffer, 4))
{
std::cerr << "ERROR: could not read RIFF" << std::endl;
return false;
}
if(std::strncmp(buffer, "RIFF", 4) != 0)
{
std::cerr << "ERROR: file is not a valid WAVE file (header doesn't begin with RIFF)" << std::endl;
return false;
}
if(!file.read(buffer, 4))
{
std::cerr << "ERROR: could not read size of file" << std::endl;
return false;
}
if(!file.read(buffer, 4))
{
std::cerr << "ERROR: could not read WAVE" << std::endl;
return false;
}
if(std::strncmp(buffer, "WAVE", 4) != 0)
{
std::cerr << "ERROR: file is not a valid WAVE file (header doesn't contain WAVE)" << std::endl;
return false;
}
if(!file.read(buffer, 4))
{
std::cerr << "ERROR: could not read fmt/0" << std::endl;
return false;
}
if(!file.read(buffer, 4))
{
std::cerr << "ERROR: could not read the 16" << std::endl;
return false;
}
if(!file.read(buffer, 2))
{
std::cerr << "ERROR: could not read PCM" << std::endl;
return false;
}
if(!file.read(buffer, 2))
{
std::cerr << "ERROR: could not read number of channels" << std::endl;
return false;
}
channels = convert_to_int(buffer, 2);
if(!file.read(buffer, 4))
{
std::cerr << "ERROR: could not read sample rate" << std::endl;
return false;
}
sampleRate = convert_to_int(buffer, 4);
if(!file.read(buffer, 4))
{
std::cerr << "ERROR: could not read (sampleRate * bitsPerSample * channels) / 8" << std::endl;
return false;
}
if(!file.read(buffer, 2))
{
std::cerr << "ERROR: could not read dafaq" << std::endl;
return false;
}
if(!file.read(buffer, 2))
{
std::cerr << "ERROR: could not read bits per sample" << std::endl;
return false;
}
bitsPerSample = convert_to_int(buffer, 2);
if(!file.read(buffer, 4))
{
std::cerr << "ERROR: could not read data chunk header" << std::endl;
return false;
}
if(std::strncmp(buffer, "data", 4) != 0)
{
std::cerr << "ERROR: file is not a valid WAVE file (doesn't have 'data' tag)" << std::endl;
return false;
}
if(!file.read(buffer, 4))
{
std::cerr << "ERROR: could not read data size" << std::endl;
return false;
}
size = convert_to_int(buffer, 4);
if(file.eof())
{
std::cerr << "ERROR: reached EOF on the file" << std::endl;
return false;
}
if(file.fail())
{
std::cerr << "ERROR: fail state set on the file" << std::endl;
return false;
}
return true;
}
char* load_wav(const std::string& filename,
std::uint8_t& channels,
std::int32_t& sampleRate,
std::uint8_t& bitsPerSample,
ALsizei& size)
{
std::ifstream in(filename, std::ios::binary);
if(!in.is_open())
{
std::cerr << "ERROR: Could not open \"" << filename << "\"" << std::endl;
return nullptr;
}
if(!load_wav_file_header(in, channels, sampleRate, bitsPerSample, size))
{
std::cerr << "ERROR: Could not load wav header of \"" << filename << "\"" << std::endl;
return nullptr;
}
char* data = new char[size];
in.read(data, size);
return data;
}
मैं कोड की व्याख्या नहीं करूंगा, क्योंकि यह पूरी तरह से हमारे लेख के विषय में नहीं है; लेकिन यह बहुत स्पष्ट है अगर आप इसे WAV फ़ाइल के विनिर्देशन के साथ समानांतर में पढ़ते हैं ।आरंभ और विनाश
पहले हमें OpenAL को इनिशियलाइज़ करने की आवश्यकता है, और फिर, किसी भी अच्छे प्रोग्रामर की तरह, इसे तब खत्म करें जब हम इसके साथ काम करना खत्म कर दें। यह आरंभीकरण के दौरान प्रयोग किया जाता है ALCdevice
(ध्यान दें कि यह ALC
है नहीं AL
) है, जो अनिवार्य पृष्ठभूमि संगीत और इसे का उपयोग करता है खेलने के लिए अपने कंप्यूटर पर कुछ का प्रतिनिधित्व करता है ALCcontext
।ALCdevice
ग्राफिक्स कार्ड चुनने के समान। जिस पर आपका OpenGL गेम रेंडर करेगा। ALCcontext
ओपनिंग के लिए रेंडरिंग प्रसंग के समान (आपरेटिंग सिस्टम के लिए अद्वितीय) बनाना चाहते हैं।Alcdevice
OpenAL डिवाइस वह है जो ध्वनि आउटपुट के माध्यम से होता है, चाहे वह साउंड कार्ड या चिप हो, लेकिन सैद्धांतिक रूप से यह बहुत सारी अलग-अलग चीजें हो सकती हैं। iostream
स्क्रीन के बजाय मानक आउटपुट प्रिंटर के समान हो सकता है, एक डिवाइस एक फाइल या एक डेटा स्ट्रीम भी हो सकता है।हालांकि, प्रोग्रामिंग गेम्स के लिए, यह एक ध्वनि उपकरण होगा, और आमतौर पर हम चाहते हैं कि यह सिस्टम में एक मानक ध्वनि आउटपुट डिवाइस हो।सिस्टम में उपलब्ध उपकरणों की सूची प्राप्त करने के लिए, आप उनसे इस फ़ंक्शन के लिए अनुरोध कर सकते हैं:bool get_available_devices(std::vector<std::string>& devicesVec, ALCdevice* device)
{
const ALCchar* devices;
if(!alcCall(alcGetString, devices, device, nullptr, ALC_DEVICE_SPECIFIER))
return false;
const char* ptr = devices;
devicesVec.clear();
do
{
devicesVec.push_back(std::string(ptr));
ptr += devicesVec.back().size() + 1;
}
while(*(ptr + 1) != '\0');
return true;
}
यह वास्तव में एक कॉल के चारों ओर एक आवरण के आसपास सिर्फ एक आवरण है alcGetString
। रिटर्न वैल्यू एक वैल्यू द्वारा अलग किए गए स्ट्रिंग्स की सूची के लिए एक संकेतक है null
और दो मूल्यों के साथ समाप्त होता है null
। यहाँ, रैपर इसे हमारे लिए सुविधाजनक वेक्टर में बदल देता है।सौभाग्य से, हमें ऐसा करने की आवश्यकता नहीं है! सामान्य स्थिति में, जैसा कि मुझे संदेह है, ज्यादातर गेम डिवाइस को डिफ़ॉल्ट रूप से ध्वनि को आउटपुट कर सकते हैं, जो भी हो। मैं शायद ही कभी ऑडियो डिवाइस को बदलने के लिए विकल्प देखता हूं जिसके माध्यम से आप ध्वनि उत्पन्न करना चाहते हैं। इसलिए, OpenAL डिवाइस को प्रारंभ करने के लिए, हम कॉल का उपयोग करते हैं alcOpenDevice
। यह कॉल बाकी सब से थोड़ा अलग है, क्योंकि यह उस त्रुटि स्थिति को निर्दिष्ट नहीं करता है जिसे प्राप्त किया जा सकता है alcGetError
, इसलिए हम इसे सामान्य समारोह की तरह कहते हैं:ALCdevice* openALDevice = alcOpenDevice(nullptr);
if(!openALDevice)
{
}
यदि आपने ऊपर दिखाए गए उपकरणों को सूचीबद्ध किया है, और आप चाहते हैं कि उपयोगकर्ता उनमें से किसी एक का चयन करें, तो आपको इसके नाम को alcOpenDevice
इसके स्थान पर स्थानांतरित करना होगा nullptr
। डिवाइस को डिफ़ॉल्ट रूप से खोलने केnullptr
आदेश भेजना । रिटर्न वैल्यू या तो संबंधित डिवाइस है, या यदि कोई त्रुटि होती है। इस पर निर्भर करते हुए कि आपने गणना पूरी की है या नहीं, त्रुटि पटरियों पर प्रोग्राम को रोक सकती है। कोई उपकरण नहीं = कोई ओपन नहीं; नो ओपनल = नो साउंड; कोई आवाज नहीं = कोई खेल नहीं। किसी प्रोग्राम को बंद करते समय आखिरी चीज हम उसे सही तरीके से खत्म करते हैं।nullptr
ALCboolean closed;
if(!alcCall(alcCloseDevice, closed, openALDevice, openALDevice))
{
}
इस स्तर पर, यदि समापन संभव नहीं था, तो यह हमारे लिए महत्वपूर्ण नहीं है। डिवाइस को बंद करने से पहले, हमें सभी निर्मित संदर्भों को बंद करना होगा, हालांकि, मेरे अनुभव में, यह कॉल भी संदर्भ को पूरा करता है। लेकिन हम इसे सही करेंगे। यदि आप कॉल करने से पहले सब कुछ पूरा करते हैं alcCloseDevice
, तो कोई त्रुटि नहीं होनी चाहिए, और यदि किसी कारण से वे उत्पन्न हुए हैं, तो आप इसके बारे में कुछ नहीं कर सकते।आपने देखा होगा कि alcCall
डिवाइस की दो प्रतियाँ भेजने से कॉल आता है। यह इस वजह से हुआ कि टेम्प्लेट फ़ंक्शन कैसे काम करता है - एक त्रुटि जाँच के लिए आवश्यक है, और दूसरा फ़ंक्शन पैरामीटर के रूप में उपयोग किया जाता है।सैद्धांतिक रूप से, मैं टेम्पलेट फ़ंक्शन में सुधार कर सकता हूं ताकि यह त्रुटि जाँच के लिए पहला पैरामीटर पास करे और फिर भी इसे फ़ंक्शन में भेजे; लेकिन मैं इसे करने के लिए आलसी हूं। मैं इसे आपके होमवर्क के रूप में छोड़ दूंगा।हमारे ALCcontext
आरंभीकरण का दूसरा भाग संदर्भ है। पहले की तरह, यह ओपनजीएल से प्रतिपादन संदर्भ के समान है। एक कार्यक्रम में कई संदर्भ हो सकते हैं और हम उनके बीच स्विच कर सकते हैं, लेकिन हमें इसकी आवश्यकता नहीं होगी। प्रत्येक संदर्भ के अपने श्रोता और स्रोत होते हैं , और उन्हें संदर्भों के बीच पारित नहीं किया जा सकता है।शायद यह साउंड प्रोसेसिंग सॉफ्टवेयर में उपयोगी है। हालांकि, 99.9% मामलों में खेल के लिए, केवल एक संदर्भ पर्याप्त है।एक नया संदर्भ बनाना बहुत सरल है:ALCcontext* openALContext;
if(!alcCall(alcCreateContext, openALContext, openALDevice, openALDevice, nullptr) || !openALContext)
{
std::cerr << "ERROR: Could not create audio context" << std::endl;
}
हमें ALCdevice
एक संदर्भ बनाने के लिए क्या चाहिए, इसके लिए हमें संवाद करने की आवश्यकता है ; हम कुंजियों और मूल्यों की एक वैकल्पिक शून्य-समाप्ति सूची भी पारित कर सकते हैं ALCint
, जो कि ऐसी विशेषताएँ हैं जिनके साथ संदर्भ बनाया जाना चाहिए।ईमानदारी से, मुझे यह भी पता नहीं है कि पास करने की स्थिति किस स्थिति में आती है। आपका गेम नियमित कंप्यूटर पर सामान्य ध्वनि विशेषताओं के साथ चलेगा। गुण कंप्यूटर के आधार पर डिफ़ॉल्ट मान रखते हैं, इसलिए यह विशेष रूप से महत्वपूर्ण नहीं है। लेकिन मामले में आपको अभी भी इसकी आवश्यकता है:यदि आपको त्रुटियां मिलती हैं, तो सबसे अधिक संभावना यह है कि आपके द्वारा इच्छित विशेषताएँ असंभव हैं या आप समर्थित डिवाइस के लिए कोई अन्य संदर्भ नहीं बना सकते हैं; इसके परिणामस्वरूप त्रुटि होगी ALC_INVALID_VALUE
। यदि आप एक अमान्य डिवाइस पास करते हैं, तो आपको एक त्रुटि मिलेगी ALC_INVALID_DEVICE
, लेकिन, निश्चित रूप से, हम पहले से ही इस त्रुटि की जांच कर रहे हैं।संदर्भ बनाना पर्याप्त नहीं है। हमें अभी भी इसे चालू करने की आवश्यकता है - यह विंडोज ओपनजीएल रेंडरिंग कॉन्सेप्ट जैसा दिखता है, है ना? यह समान हे।ALCboolean contextMadeCurrent = false;
if(!alcCall(alcMakeContextCurrent, contextMadeCurrent, openALDevice, openALContext)
|| contextMadeCurrent != ALC_TRUE)
{
std::cerr << "ERROR: Could not make audio context current" << std::endl;
}
संदर्भ के साथ आगे के संचालन के लिए संदर्भ को चालू करना आवश्यक है (या इसमें स्रोत और श्रोताओं के साथ)। ऑपरेशन वापस आ जाएगा true
या false
, केवल संभावित त्रुटि मान प्रसारित alcGetError
होता है ALC_INVALID_CONTEXT
जो नाम से स्पष्ट है।संदर्भ के साथ समाप्त करना, अर्थात। कार्यक्रम से बाहर निकलते समय, यह आवश्यक है कि संदर्भ अब चालू नहीं है, और फिर इसे नष्ट कर दें।if(!alcCall(alcMakeContextCurrent, contextMadeCurrent, openALDevice, nullptr))
{
}
if(!alcCall(alcDestroyContext, openALDevice, openALContext))
{
}
से एकमात्र संभावित त्रुटि alcDestroyContext
उसी के रूप में है alcMakeContextCurrent
- ALC_INVALID_CONTEXT
; यदि आप सब कुछ सही करते हैं, तो आप इसे प्राप्त नहीं करेंगे, लेकिन यदि आप करते हैं, तो इसके बारे में कुछ भी नहीं किया जा सकता है।उन त्रुटियों की जांच क्यों की जाती है जिनके साथ कुछ भी नहीं किया जा सकता है?
क्योंकि मैं चाहता हूं कि उनके बारे में संदेश कम से कम त्रुटियों की धारा में दिखाई दें, जो हमारे लिए है। alcCall
माना कि यह हमें कभी त्रुटि नहीं देता है, लेकिन यह जानना उपयोगी होगा कि ऐसी त्रुटि किसी और के कंप्यूटर पर होती है। इसके लिए धन्यवाद, हम समस्या का अध्ययन कर सकते हैं, और संभवतः ओपल सॉफ्ट डेवलपर्स को बग की रिपोर्ट कर सकते हैं ।
हमारी पहली ध्वनि खेलें
खैर, यह सब पर्याप्त है, चलो ध्वनि खेलते हैं। शुरू करने के लिए, हमें स्पष्ट रूप से एक ध्वनि फ़ाइल की आवश्यकता है। उदाहरण के लिए, यह एक, एक खेल से मैं कभी खत्म कर दूंगा ।मैं इस व्यवस्था का रक्षक हूँ!तो, आईडीई खोलें और निम्नलिखित कोड का उपयोग करें। OpenAL Soft में प्लग इन करना याद रखें और ऊपर दिखाए गए फाइल अपलोड कोड और एरर चेकिंग कोड को जोड़ें।int main()
{
ALCdevice* openALDevice = alcOpenDevice(nullptr);
if(!openALDevice)
return 0;
ALCcontext* openALContext;
if(!alcCall(alcCreateContext, openALContext, openALDevice, openALDevice, nullptr) || !openALContext)
{
std::cerr << "ERROR: Could not create audio context" << std::endl;
return 0;
}
ALCboolean contextMadeCurrent = false;
if(!alcCall(alcMakeContextCurrent, contextMadeCurrent, openALDevice, openALContext)
|| contextMadeCurrent != ALC_TRUE)
{
std::cerr << "ERROR: Could not make audio context current" << std::endl;
return 0;
}
std::uint8_t channels;
std::int32_t sampleRate;
std::uint8_t bitsPerSample;
std::vector<char> soundData;
if(!load_wav("iamtheprotectorofthissystem.wav", channels, sampleRate, bitsPerSample, soundData))
{
std::cerr << "ERROR: Could not load wav" << std::endl;
return 0;
}
ALuint buffer;
alCall(alGenBuffers, 1, &buffer);
ALenum format;
if(channels == 1 && bitsPerSample == 8)
format = AL_FORMAT_MONO8;
else if(channels == 1 && bitsPerSample == 16)
format = AL_FORMAT_MONO16;
else if(channels == 2 && bitsPerSample == 8)
format = AL_FORMAT_STEREO8;
else if(channels == 2 && bitsPerSample == 16)
format = AL_FORMAT_STEREO16;
else
{
std::cerr
<< "ERROR: unrecognised wave format: "
<< channels << " channels, "
<< bitsPerSample << " bps" << std::endl;
return 0;
}
alCall(alBufferData, buffer, format, soundData.data(), soundData.size(), sampleRate);
soundData.clear();
ALuint source;
alCall(alGenSources, 1, &source);
alCall(alSourcef, source, AL_PITCH, 1);
alCall(alSourcef, source, AL_GAIN, 1.0f);
alCall(alSource3f, source, AL_POSITION, 0, 0, 0);
alCall(alSource3f, source, AL_VELOCITY, 0, 0, 0);
alCall(alSourcei, source, AL_LOOPING, AL_FALSE);
alCall(alSourcei, source, AL_BUFFER, buffer);
alCall(alSourcePlay, source);
ALint state = AL_PLAYING;
while(state == AL_PLAYING)
{
alCall(alGetSourcei, source, AL_SOURCE_STATE, &state);
}
alCall(alDeleteSources, 1, &source);
alCall(alDeleteBuffers, 1, &buffer);
alcCall(alcMakeContextCurrent, contextMadeCurrent, openALDevice, nullptr);
alcCall(alcDestroyContext, openALDevice, openALContext);
ALCboolean closed;
alcCall(alcCloseDevice, closed, openALDevice, openALDevice);
return 0;
}
संकलन! हम रचना करते हैं! प्रक्षेपण! मैं इस प्रणाली का prrorootector हूँ । अगर आपको आवाज नहीं सुनाई देती है, तो सब कुछ फिर से जांचें। यदि कंसोल विंडो में कुछ लिखा गया है, तो यह त्रुटि स्ट्रीम का मानक आउटपुट होना चाहिए, और यह महत्वपूर्ण है। हमारी त्रुटि रिपोर्टिंग फ़ंक्शन हमें स्रोत कोड की पंक्ति बताएगी जो त्रुटि उत्पन्न करती है। यदि आपको कोईत्रुटि मिलती है , तो प्रोग्रामर गाइड और उन शर्तों को समझने के लिए विनिर्देशन पढ़ें, जिनके तहत यह त्रुटि किसी फ़ंक्शन द्वारा उत्पन्न की जा सकती है। इससे आपको यह पता लगाने में मदद मिलेगी। यदि यह सफल नहीं होता है, तो मूल लेख के तहत एक टिप्पणी छोड़ दें, और मैं मदद करने की कोशिश करूंगा।RIFF WAVE डेटा डाउनलोड करें
std::uint8_t channels;
std::int32_t sampleRate;
std::uint8_t bitsPerSample;
std::vector<char> soundData;
if(!load_wav("iamtheprotectorofthissystem.wav", channels, sampleRate, bitsPerSample, soundData))
{
std::cerr << "ERROR: Could not load wav" << std::endl;
return 0;
}
यह वेव बूट कोड को संदर्भित करता है। महत्वपूर्ण बात यह है कि हम डेटा प्राप्त करते हैं, या तो एक पॉइंटर के रूप में, या एक वेक्टर में एकत्र किया जाता है: चैनलों की संख्या, नमूना दर और प्रति नमूने बिट्स की संख्या।बफर पीढ़ी
ALuint buffer;
alCall(alGenBuffers, 1, &buffer);
यदि आप कभी भी OpenGL में बनावट डेटा बफ़र्स उत्पन्न करते हैं, तो यह संभवतः आपके लिए परिचित है। संक्षेप में, हम एक बफर उत्पन्न करते हैं और दिखावा करते हैं कि यह केवल साउंड कार्ड में मौजूद होगा। वास्तव में, यह सबसे अधिक संभावना है कि साधारण रैम में संग्रहीत किया जाएगा, लेकिन OpenAL विनिर्देश इन सभी कार्यों का सार करता है।तो, मान हमारे बफर के लिएALuint
एक संभाल है । याद रखें कि बफर अनिवार्य रूप से साउंड कार्ड की मेमोरी में ध्वनि डेटा है। अब हमारे पास इस डेटा की सीधी पहुंच नहीं है , क्योंकि हमने इसे प्रोग्राम से लिया (नियमित रैम से) और इसे साउंड कार्ड / चिप इत्यादि में स्थानांतरित कर दिया। OpenGL इसी तरह काम करता है, RAM से VRAM तक बनावट डेटा ले जाता है।यह वर्णन करने वालाउत्पन्न करता है alGenBuffers
। इसमें कुछ संभावित त्रुटि मान हैं, जिनमें से सबसे महत्वपूर्ण है AL_OUT_OF_MEMORY
, जिसका अर्थ है कि हम अब साउंड कार्ड में ध्वनि डेटा नहीं जोड़ सकते हैं। आपको यह त्रुटि नहीं मिलेगी यदि, उदाहरण के लिए, आप एक एकल बफ़र का उपयोग करते हैं, लेकिन आपको यह विचार करने की आवश्यकता है कि क्या आप एक इंजन बना रहे हैं ।ऑडियो डेटा का प्रारूप निर्धारित करें
ALenum format;
if(channels == 1 && bitsPerSample == 8)
format = AL_FORMAT_MONO8;
else if(channels == 1 && bitsPerSample == 16)
format = AL_FORMAT_MONO16;
else if(channels == 2 && bitsPerSample == 8)
format = AL_FORMAT_STEREO8;
else if(channels == 2 && bitsPerSample == 16)
format = AL_FORMAT_STEREO16;
else
{
std::cerr
<< "ERROR: unrecognised wave format: "
<< channels << " channels, "
<< bitsPerSample << " bps" << std::endl;
return 0;
}
ध्वनि डेटा इस तरह से काम करता है: कई चैनल हैं और प्रति नमूना थोड़ा आकार है । डेटा में कई नमूने होते हैं । ऑडियो डेटा में नमूनोंकी संख्या निर्धारित करने के लिए , हम निम्नलिखित कार्य करते हैं:std::int_fast32_t numberOfSamples = dataSize / (numberOfChannels * (bitsPerSample / 8));
ऑडियो डेटा की अवधि की गणना करने के लिए क्या आसानी से परिवर्तित किया जा सकता है :std::size_t duration = numberOfSamples / sampleRate;
लेकिन जब हमें न तो जानने की आवश्यकता है numberOfSamples
, न ही duration
, हालांकि, यह जानना महत्वपूर्ण है कि इन सभी सूचनाओं का उपयोग कैसे किया जाता है।बैक टू format
- हमें ओपल को ऑडियो डेटा प्रारूप बताने की आवश्यकता है। यह स्पष्ट लगता है, है ना? हम OpenGL बनावट बफ़र को कैसे भरें, इसके समान है, यह कहते हुए कि डेटा एक BGRA अनुक्रम में है और 8-बिट मानों से बना है, हमें OpenAL में भी ऐसा करने की आवश्यकता है।ओपनल को यह बताने के लिए कि सूचक द्वारा बताए गए डेटा की व्याख्या कैसे करें जिसे हम बाद में पास करेंगे, हमें डेटा प्रारूप को परिभाषित करने की आवश्यकता है। प्रारूप के तहत , यह OpenAL द्वारा समझा जाता है। केवल चार संभावित अर्थ हैं। चैनलों की संख्या के लिए दो संभावित मूल्य हैं: एक मोनो के लिए, दो स्टीरियो के लिए।चैनलों की संख्या के अलावा, हमारे पास प्रति नमूने बिट्स की संख्या है। यह या 8
, या के बराबर है 16
, और अनिवार्य रूप से ध्वनि की गुणवत्ता है।इसलिए प्रति नमूने चैनलों और बिट्स के मूल्यों का उपयोग करते हुए, जिसे लहर लोड फ़ंक्शन ने हमें सूचित किया है, हम यह निर्धारित कर सकते हैं कि ALenum
भविष्य के पैरामीटर के लिए कौन सा उपयोग करना है format
।बफ़र भरना
alCall(alBufferData, buffer, format, soundData.data(), soundData.size(), sampleRate);
soundData.clear();
इसके साथ, सब कुछ सरल होना चाहिए। हम OpenAL बफर में लोड करते हैं, जो विवरणकर्ता को इंगित करता है buffer
; निर्दिष्ट soundData.data()
आकार में ptr द्वारा इंगित किया गया डेटा । हम इस डेटा के पैरामीटर के माध्यम से OpenAL को भी सूचित करेंगे । अंत में, हम बस उस डेटा को हटा देते हैं जो लहर लोडर को प्राप्त हुआ था। क्यों? क्योंकि हमने उन्हें पहले ही साउंड कार्ड में कॉपी कर लिया है। हमें उन्हें दो स्थानों पर संग्रहीत करने और कीमती संसाधनों को खर्च करने की आवश्यकता नहीं है। यदि साउंड कार्ड डेटा खो देता है, तो हम बस इसे फिर से डिस्क से डाउनलोड करेंगे और हमें इसे सीपीयू या किसी अन्य पर कॉपी करने की आवश्यकता नहीं होगी।size
sampleRate
format
स्रोत सेटिंग
स्मरण करो कि OpenAL अनिवार्य रूप से एक श्रोता है जो एक या अधिक स्रोतों द्वारा बनाई गई ध्वनियों को सुनता है । खैर, अब एक ध्वनि स्रोत बनाने का समय है।ALuint source;
alCall(alGenSources, 1, &source);
alCall(alSourcef, source, AL_PITCH, 1);
alCall(alSourcef, source, AL_GAIN, 1.0f);
alCall(alSource3f, source, AL_POSITION, 0, 0, 0);
alCall(alSource3f, source, AL_VELOCITY, 0, 0, 0);
alCall(alSourcei, source, AL_LOOPING, AL_FALSE);
alCall(alSourcei, source, AL_BUFFER, buffer);
ईमानदारी से, इनमें से कुछ मापदंडों को निर्धारित करना आवश्यक नहीं है, क्योंकि डिफ़ॉल्ट मान हमारे लिए काफी उपयुक्त हैं। लेकिन यह हमें कुछ पहलुओं को दिखाता है जिनके साथ आप प्रयोग कर सकते हैं और देख सकते हैं कि वे क्या करते हैं (आप समय के साथ चालाकी से काम कर सकते हैं और उन्हें बदल सकते हैं)।पहले हम स्रोत उत्पन्न करते हैं - याद रखें, यह फिर से OpenAL API के अंदर किसी चीज़ के लिए एक हैंडल है। हम पिच (टोन) सेट करते हैं ताकि यह बदल न जाए, लाभ (वॉल्यूम) ऑडियो डेटा के मूल मूल्य के बराबर हो जाता है, स्थिति और गति रीसेट हो जाती है; हम ध्वनि को लूप नहीं करते हैं , क्योंकि अन्यथा हमारा कार्यक्रम कभी समाप्त नहीं होगा, और बफर को इंगित करेगा।याद रखें कि विभिन्न स्रोत एक ही बफर का उपयोग कर सकते हैं। उदाहरण के लिए, अलग-अलग स्थानों से एक खिलाड़ी को गोली मारने वाले दुश्मन एक ही शॉट ध्वनि खेल सकते हैं, इसलिए हमें ध्वनि डेटा की कई प्रतियों की आवश्यकता नहीं है, लेकिन केवल 3 डी अंतरिक्ष में कुछ स्थान हैं जहां से ध्वनि बनाई गई है।ध्वनि खेलने
alCall(alSourcePlay, source);
ALint state = AL_PLAYING;
while(state == AL_PLAYING)
{
alCall(alGetSourcei, source, AL_SOURCE_STATE, &state);
}
पहले हमें स्रोत खेलना शुरू करना होगा। बस बुलाओ alSourcePlay
।तब हम AL_SOURCE_STATE
स्रोत की वर्तमान स्थिति को संग्रहीत करने के लिए एक मूल्य बनाते हैं और इसे अंतहीन रूप से अपडेट करते हैं। जब यह समान नहीं रह जाता है, AL_PLAYING
तो हम जारी रख सकते हैं। आप उस स्थिति को बदल सकते हैं AL_STOPPED
जब वह बफर से ध्वनि करना समाप्त कर देती है (या जब कोई त्रुटि होती है)। यदि आप लूपिंग के लिए मान सेट करते हैं true
, तो ध्वनि हमेशा के लिए चलेगी।तब हम स्रोत बफर को बदल सकते हैं और एक और ध्वनि बजा सकते हैं। या फिर एक ही ध्वनि को दोहराएं, आदि। बस बफर सेट करें, इसका उपयोग करें alSourcePlay
, और alSourceStop
यदि आवश्यक हो , तो हो सकता है। निम्नलिखित लेखों में हम इस पर अधिक विस्तार से विचार करेंगे।सफाई
alCall(alDeleteSources, 1, &source);
alCall(alDeleteBuffers, 1, &buffer);
चूंकि हम केवल एक बार ऑडियो डेटा खेलते हैं और बाहर निकलते हैं, इसलिए हम पहले से बनाए गए स्रोत और बफर को हटा देंगे।स्पष्टीकरण के बिना बाकी कोड समझ में आता है।आगे कहाँ जाना है?
इस लेख में वर्णित सब कुछ जानने के बाद, आप पहले से ही एक छोटा खेल बना सकते हैं! पोंग या कुछ अन्य क्लासिक गेम बनाने की कोशिश करें , उनके लिए अधिक आवश्यक नहीं है।लेकिन याद रखें! ये बफ़र केवल छोटी ध्वनियों के लिए उपयुक्त हैं, कुछ सेकंड के लिए सबसे अधिक संभावना है। यदि आपको संगीत या ध्वनि अभिनय की आवश्यकता है, तो आपको ओडियोल को ऑडियो स्ट्रीम करना होगा। हम ट्यूटोरियल की एक श्रृंखला के निम्नलिखित भागों में से एक में इस बारे में बात करेंगे।