📄 openal教程.txt
字号:
// 保存源ID.
Sources.push_back(source);
// 返回源ID.
return source;
}
现在我们已经建立处理缓冲区的系统,我们需要得到源的伸展。在这里,我们
得到了导入文件的缓冲区ID。这个缓冲区是一个新源,我们保存他并返回。
void KillALLoadedData()
{
LoadedFiles.clear();
}
'gLoadedFilesv'存储在导入缓冲区的WAV文件的路径下,我们要处理他。
// 源ID's.
ALuint phaser1;
ALuint phaser2;
void LoadALData()
{
// 你的应用在这里,不用担心缓冲区。
phaser1 = LoadALSample("wavdata/phaser.wav", false);
phaser2 = LoadALSample("wavdata/phaser.wav", true);
KillLoadedALData();
}
他表示用于程序的所有的WAV应用的程序。我们能调用导入相同的WAV文件到不同的源,
'phaser.wav' 的缓冲区建立了一次,'gPhaser1' and 'gPhaser2' 用于背景
音乐的缓冲区。不用处理缓冲区因为系统会自动处理。
void KillALData()
{
// 释放所有的缓冲区数据.
for (vector<ALuint>::iterator iter = Buffers.begin(); iter != Buffers.end(); ++iter)
alDeleteBuffers(1, iter);
// 释放所有的源数据.
for (vector<ALuint>::iterator iter = Sources.begin(); iter != Sources.end(); ++iter)
alDeleteBuffers(1, iter);
// 清除列表.
Buffers.clear();
Sources.clear();
}
我们已完成了前述的工作。然后就是释放他们。
try
{
InitOpenAL();
LoadALData();
}
catch(string err)
{
cout << "OpenAL error: " << err.c_str() << endl;
}
如果导入源时出错,我们将改正他。这将借助程序返回的报告。
That's it. A more advanced way of reporting errors, and a more robust way of loading your wav files. We may find we need to do some modifications in the future to allow for more flexibility, but for now we will be using this source for basic file loading in future tutorials. Expect future tutorials to expand on this code.
See the Java Bindings for OpenAL page for the Java version of this tutorial (adapted by: Athomas Goldberg)
如果大家有什么问题,请告诉我e-mail:ouyunfei12@mail.china.com.
///////////////////////////////////////////////////////////////////////////////////////////////////
openal教程(七)
多普勒效应
首先,我们要来复习一下高中物理学,不要害怕。多普勒效应与我们每个人都有关,他是一个
逻辑过程,就要看你是否对他感兴趣。在理解多普勒效应是什么之前,我们必须明白“声音”
是什么。简单的讲,声音就是你的大脑被空气中的连续波打断。波从一个点发出,在空气中
旅行的样子请参考下图:(详细图请参考 http://www.devmaster.net/articles/openal-tutorials/lesson7.php)
在这张图中,用红S代表声源的位置,红L代表听众的位置。现在,声源和听众都在移动。
声源向外发出声波,在这张图中,我们用蓝圆表示。在这张图中,听众能很好的感受声音。
多普勒效应没有在这张图中表示出来;多普勒效应描述的是弯曲的声音在移动。
你可以在这张图中做一些改动。当声源发出声音时,可以看出他是从原点向外扩散,最好
的例子是池子中的水波。当你丢一块石块到水中时,将看见一圈一圈的水圈从一点扩散。我们
来看第二张图片:
现在,声源在移动,用红色的箭头表示。实际上,声源是带着速度向听众移动。注意图中
声波之间的位置。这就是多普勒效应。实际上,声源是在移动中的不同位置发散声波。
听众是怎样感受声音的呢?注意第二张图表中在声源和听众之间的声波的距离是逐渐在压缩。这就引起声波跑到了一起,听起来特别尖,急。在这里,我们要讲一下频率。声波之间的
距离实际上是由声音的频率引起的。当声源在移动中发散声音时,他也引起了频率的改变。你
也许注意到了在不同的位置的声波的距离。例如,在移动声源的对面,声波的距离实际上很宽。
因此,他的频率很底。听众感受声音频率的强弱是由听众的位置相关的。
听众的移动也能引起频率的变化。这一点很难用图形表示。如果声源固定,听众向声源移动,那么听众感受的频率的变化和声源是一样的。如果你还不能理解,请参考下面的图片:
这两张图片表示不同声波形式的声音。请看第一张,请看那两波段的顶点。那声波的顶点和上
面图式的蓝圈的距离相同。第二张图代表压缩的声波。当你对比着看这两张图时,你将发现明显的不同。第二张图的声波更多,这也代表他的频率更大。
还要附加一点,声音的速度就是声波的速度。如果声源的速度大于声波的速度,那么声源将冲破声音屏障。
OPENAL中的物理学
从上面的讲述,我们学习了多普勒效应。下面我们将讲解多普勒效应是怎样在OPENAL中应用的。在OPENAL中的文件中是这样解释的:
“多普勒效应依靠声源的速度和听众与介质的关系,声音在介质中的蔓延速度。”
我们可以这样理解,有3个要素影响着听众听的声音的最总频率。这些因素分别是:声源的速度,听众的速度和声音的速度。
当我们谈到“介质”,就是声源和听者的载体。例如,声音在水下和空气中传播是不一样的。空气和水是不同的介质。
在OPENAL 中,OPENAL是通过计算多普勒效应,因此我们需要定义一些变量来影响计算。计算公式如下:
shift = DOPPLER_FACTOR * freq * (DOPPLER_VELOCITY - l.velocity) / (DOPPLER_VELOCITY + s.velocity)
在公式中‘l’和‘s’分别代表听众和声源。‘freq’代表发散的声波的最初频率,‘shift’被声波的频率改变。这些最后的转移频率将被OPENAL的所有声音流样本化。
我们已经知道用‘AL_VELOCITY'在’alListenerfv' and 'alSourcefv'函数中定义声源和听众的速度。当文件导入缓冲器时,‘freq’将直接来自缓冲器。下面的函数将为你设置永久的值。
ALvoid alDopplerFactor(ALfloat factor);
ALvoid alDopplerVelocity(ALfloat velocity);
‘alDopplerFactor’设置否定的值将引起'AL_INVALID_value'的错误,并且整个控制将被忽略。如果设置为0将可能引起争议。做这些将使多普勒效应失效。对多普勒效应的影响将由方程式的大小的改变决定。设置为1.0将不会影响什么。在0.1 to 1.0之间设置任何值将使多普勒效应减少到最小。并且如果设置的值超过1.0将是影响最大化。
'alDopplerVelocity'设置否定的值或0将引起'AL_INVALID_value'的错误,并且整个控制将被忽略。如果设置为0将可能引起争议。Setting this will be like setting how fast sound can move through the medium. OpenAL has no sense of medium, but setting the velocity will give the effect of a medium. OpenAL also has no sense of units (kilometer, miles, parsecs), so keep that in mind when you set this value so it is consistent with all other notions of units that you have defined.
See the Java Bindings for OpenAL page for the Java version of this tutorial (adapted by: Athomas Goldberg)
//////////////////////////////////////////////////////////////////////////////////////////////////////
openal教程(8)
OggVorbis流用声源队列
大家好,由于我现在的脑袋很晕,翻译的文章难免有误,请大家批评指正,谢谢。antking@gmail.cn!!!
Hello again fellow coders. I'm back after a fairly long hiatus with another tutorial on the OpenAL api. And I think this will be a beefy one. I would first like to thank the community for their support thus far in the series. I want to put out some special thanks to DevMaster.net who is hosting the series on their website. This really got the ball rolling on the series which has now been ported to Visual C++ 6 by TheCell, and to Java by Athomas Goldberg for JOAL (Java Bindings for OpenAL). I have also heard they have been translated to Portuguese for the Brazilian GameDev. I would also like to give special thanks to Jorge Bernal for sending me some sample code which gave me enough of a kick in the pants to get me writing again. That was a big help (even though translating the code from Spanish was a chore :).
OggVorbis简介
大家是否听说过Ogg?没有比他更滑稽的声音名字了。他是音频压缩有关,诸如
MP3。有希望的是,他将替代MP3成为主流的音频压缩格式。他和MP3谁好?
那是一个很难回答的问题。还有很强的争论。有不同的关于压缩比例对声音质量
的争论,我个人还没有关于谁是最好的观点。但是事实上Ogg是免费的,而MP3
不是,因此ogg得到了很多的支持。
设计你OggVorbis流 API
请看下面的代码:
#include <string>
#include <iostream>
using namespace std;
#include <al/al.h>
#include <ogg/ogg.h>
#include <vorbis/codec.h>
#include <vorbis/vorbisenc.h>
#include <vorbis/vorbisfile.h>
#define BUFFER_SIZE (4096 * 8)
这篇教程全用C++代码书写,因此我们将一开始就包含C++标准头文件。当然还有OPENAL API,
并且我们也将包含4个新的头。这些新的头是我们设计的OggVorbis流函数库
的一部分。总数上有4个文件:‘ogg.dll'(格式和解码器),’vorbis.dll'(编码规划),‘
vorbisenc.dll'’(编码的工具),和‘vorbisfile.dll'(流和搜寻的工具)。
我们不会用’vorbisenc‘,但我们把他包含到我们用的文件中。用这些函数库将
实现99%的工作量。 在这里我们将不会用他。我怀疑我们能写出比多媒体数字信号编解码器的设计者
更好的东西。这些函数库将使我们不用额外的工作而自动更新格式进化。但是,
用这些函数库的最大原因是他的一致性。如果所有的OGG文件重新用这些库编码,那么
所有的OGG文件将能够在所有的OGG播放器中播放。
我们建立了宏'BUFFER_SIZE' 来确定我们从流中读到的块有多大。你将发现
大的缓冲区将产生高质量的声音,而不会产生突然的暂停和声音变形。当然
如果缓冲区太大也回占用你大量的内存。如果要使用很长的流,我相信4096的缓冲区就够用了。
我并不提倡用小的缓冲区,因为他会有卡嗒声。
为什么我们要截断流?为什么不直接导入整个文件,然后播放他?这是个好问题。
而答案是有太多的音频数据。实际的OGG文件十分小(通常是1-3MB),你必须记
住他们是压缩的音频数据。你不能直接播放这些压缩的音频数据。在用于缓冲区以前,
你必须解压并转换成OPENAL能识别的格式。
class ogg_stream
{
public:
void open(string path); // obtain a handle to the file void release(); // release the file handle void display(); // display some info on the Ogg bool playback(); // play the Ogg stream bool playing(); // check if the source is playing bool update(); // update the stream if necessary protected: bool stream(ALuint buffer); // reloads a buffer void empty(); // empties the queue void check(); // checks OpenAL error state string errorString(int code); // stringify an error code
这是我们的OGG流API类。公共方法是先得到我们需要的OGG,然后播放他。
保护方法为一些更为内部的处理(比如错误检测)
private: FILE* oggFile; // file handle OggVorbis_File oggStream; // stream handle vorbis_info* vorbisInfo; // some formatting data vorbis_comment* vorbisComment; // user comments ALuint buffers[2]; // front and back buffers ALuint source; // audio source ALenum format; // internal format};
首先,我必须指出我们用了2个缓冲区而不是1个,我们总是在WAV文件中这样
运用。然后来明白双缓冲区在OPENAL/DIRECTX中是怎样工作的。有一个前面
的缓冲区对应于当前屏幕,而后面的内存在后台工作。然后,他们就交换。
后台缓冲区变成前台缓冲区。一个缓冲区正在播放而另一个缓冲区在等待播放。
当一个完成后,另一个开始。
当你看见'FILE*' ,你也许会想,为什么我们在C++代码中用C文件处理。因为
在设计vorbisfile时,使用的C文件系统,而不是C++。
void ogg_stream::open(string path){ int result; if(!(oggFile = fopen(path.c_str(), "rb"))) throw string("Could not open Ogg file."); if((result = ov_open(oggFile, &oggStream, NULL, 0)) < 0) { fclose(oggFile); throw string("Could not open Ogg stream. ") + errorString(result); }
如果我们用fstream ,我们不得不建立几个新的函数,并用'ov_open_callbacks'
注册他们。如果你需要支持实际的文件系统,他将是非常有用的。函数的'ov_open'
是用于捆绑OGG流的文件句柄。现在流拥有了这个文件的句柄。
vorbisInfo = ov_info(&oggStream, -1); vorbisComment = ov_comment(&oggStream, -1); if(vorbisInfo->channels == 1) format = AL_FORMAT_MONO16; else format = AL_FORMAT_STEREO16;
这段代码用于获取文件中的一些信息。我们检测OPENAL格式计数器中有多少
通道在OGG中。
alGenBuffers(2, buffers); check(); alGenSources(1, &source); check(); alSource3f(source, AL_POSITION, 0.0, 0.0, 0.0); alSource3f(source, AL_VELOCITY, 0.0, 0.0, 0.0); alSource3f(source, AL_DIRECTION, 0.0, 0.0, 0.0); alSourcef (source, AL_ROLLOFF_FACTOR, 0.0 ); alSourcei (source, AL_SOURCE_RELATIVE, AL_TRUE );}
我们设置了一系列默认的值,位置,速度,方向......但是rolloff 因子是什么?rolloff 因子判断
变弱的力量覆盖距离。如设置为0,将关掉他。这意味着不管听众与声源多远,
都将听到声音。
void ogg_stream::release(){ alSourceStop(source); empty(); alDeleteSources(1, &source); check(); alDeleteBuffers(1, buffers); check(); ov_clear(&oggStream);}
用完后,我们将释放他们。我们停止声源,清空队列中的内存,删除我们的对象。
'ov_clear' 意味着他将释放他占有的文件流和关闭处理。
void ogg_stream::display(){ cout << "version " << vorbisInfo->version << "\n" << "channels " << vorbisInfo->channels << "\n" << "rate (hz) " << vorbisInfo->rate << "\n" << "bitrate upper " << vorbisInfo->bitrate_upper << "\n" << "bitrate nominal " << vorbisInfo->bitrate_nominal << "\n" << "bitrate lower " << vorbisInfo->bitrate_lower << "\n" << "bitrate window " << vorbisInfo->bitrate_window << "\n" << "\n" << "vendor " << vorbisComment->vendor << "\n"; for(int i = 0; i < vorbisComment->comments; i++) cout << " " << vorbisComment->user_comments[i] << "\n"; cout << endl;}
显示一些附加信息。
bool ogg_stream::playback(){ if(playing()) return true; if(!stream(buffers[0])) return false; if(!stream(buffers[1])) return false; alSourceQueueBuffers(source, 2, buffers); alSourcePlay(source); return true;}
用于播放OGG。如果OGG已经播放,如没有其他原因,他将循环。我们必须用他们设置的数据初始化
缓冲区。然后把他们排队,播放。在这里,我们用到了'alSourceQueueBuffers'。
他的作用是个声源多个缓冲区。这些缓冲区将顺序的播放。注:如果你在声源时,没有
用'alSourcei'捆绑到缓冲区,那么总是坚持用'alSourceQueueBuffers'。
bool ogg_stream::playing(){ ALenum state; alGetSourcei(source, AL_SOURCE_STATE, &state); return (state == AL_PLAYING);}
用于检测声源的状态。
bool ogg_stream::update(){ int processed; bool active = true; alGetSourcei(source, AL_BUFFERS_PROCESSED, &processed); while(processed--) { ALuint buffer; alSourceUnqueueBuffers(source, 1, &buffer); check(); active = stream(buffer); alSourceQueueBuffers(source, 1, &buffer); check(); } return active;}
队列是怎样工作的呢?有一列队列,当你推出一个缓冲区时,他将弹出前面的一个,当你进队时,他将弹出
后面的一个。
在这个类中,有两个重要的方法。我们用BIT的形式检测缓冲区是否播放完。如果
有,我们将弹出队列中的最后一个,然后从流中重填数据到缓冲区中。我们做这些
,听众将没有任何注意。声音象一条很长的音乐链子。'stream'函数也将告诉我们
流是否完成播放。
bool ogg_stream::stream(ALuint buffer){ char data[BUFFER_SIZE]; int size = 0; int section; int result; while(size < BUFFER_SIZE) { result = ov_read(&oggStream, data + size, BUFFER_SIZE - size, 0, 2, 1, & section); if(result > 0) size += result; else if(result < 0) throw oggString(result); else break; } if(size == 0) return false; alBufferData(buffer, format, data, size, vorbisInfo->rate); check(); return false;}
这是另一个重要的方法。他用于把OGG BIT流填充到缓冲区。你也许要想'ov_read'
是做什么的?他是从OGG BIT流中读去数据的。vorbisfile做所有BIT流的译码
工作,以使我们不用担心他。这个函数拿走了我们定义的'oggStream'结构,
用于写译码音频的数据缓冲区和你想译码的块的大小。
返回值'ov_read' 代表几件事。如果结果值是确定的,那么他代表读了多少
数据。这是重要的,因为'ov_read' 不能读到被要求的那么多数据。(通常,
是因为已到了文件末尾并且没数据可读)在任何情况用'ov_read' 覆盖'BUFFER_SIZE'。
如果'ov_read' 被否定,那么表示有错误在BIT流中。在这种情况下,结果值是
错误代码。如果结果值等于0,那么文件中无数据播放。
在整个循环中使代码复杂化。这种方法被设计为模块化并且更容易修改。你
可以通过改变'BUFFER_SIZE' 来使他一直播放。但这要求我们必须确定我们
填充到缓冲区中的数据必须带'ov_read' 复合调用并且确定什么都对准了。
这种方法的最后部分是调用'alBufferData',但必须确定我们从OGG文件中用
'ov_read'读到的数据的ID在缓冲区中。我们用'format'和'vorbisInfo' 数据。
void ogg_stream::empty(){ int queued; alGetSourcei(source, AL_BUFFERS_QUEUED, &queued); while(queued--) { ALuint buffer; alSourceUnqueueBuffers(source, 1, &buffer); check(); }}
这种方法将与声源无关的缓冲区退队。
void ogg_stream::check(){ int error = alGetError(); if(error != AL_NO_ERROR) throw string("OpenAL error was raised.");}
这种方法用于保存我们错误检测的类型。
string ogg_stream::errorString(int code){ switch(code) { case OV_EREAD: return string("Read from media."); case OV_ENOTVORBIS: return string("Not Vorbis data."); case OV_EVERSION: return string("Vorbis version mismatch."); case OV_EBADHEADER: return string("Invalid Vorbis header."); case OV_EFAULT: return string("Internal logic fault (bug or heap/stack corruption."); default: return string("Unknown Ogg error."); }}
'stringify'是一条错误信息,用于你控制信息框的检测。
制作OGGVORBIS 播放器
现在,我们用我们自己设计的类来播放OGG文件。他是非常简单的。
int main(int argc, char* argv[]){ ogg_stream ogg; alutInit(&argc, argv);
这里简直不用思考。
try { if(argc < 2) throw string("oggplayer *.ogg"); ogg.open(argv[1]); ogg.display();
在我们用C++时,同样要用try/catch/throw 进行异常处理。你可能已注意到
我在程序中对STRINGS进行了异常处理。
第一件事是确定文件路径是否正确。如果不正确,我们不能做任何事情,因此,我们
必须向用户展示OGG扩展的一些信息;如果正确,我们就能打开一个文件。我们也
将展示关于OGG文件的信息。
if(!ogg.playback()) throw string("Ogg refused to play."); while(ogg.update()) { if(!ogg.playing()) { if(!ogg.playback()) throw string("Ogg abruptly stopped."); else cout << "Ogg stream was interrupted.\n"; } } cout << "Program normal termination."; cin.get(); }
我们开始播放OGG文件。如果没有数据初始两个缓冲区或者不能读文件,就不能
播放OGG文件。
程序将连续不断的循环,直到'update' 方法被调用并返回真,还要能读和播放声音流。
在循环里,我们将确定OGG在播放。这可以看他是否服务相同的'update',但他也
要和系统解决其他的问题。
如果无事可做,程序将正常退出。
catch(string error) { cout << error; cin.get(); }
在程序不得不停止是显示的信息时,能抓住错误语句。
ogg.release(); alutExit(); return 0;}
主程序的结尾。
回答你可能的问题
我能为流开辟多于一个的缓冲区吗?
是的。你能同时开辟许多缓冲区,这样做将得到更好的结果。就像我前面说的
两个缓冲区不会占用太多的CPU时间,一个缓冲区完成播放,另一个完成更新
数据。当然,3,4个缓冲区将有更好的表现。
怎样经常调用OGG。UPDATA?
有下面几点。如果你想得到一个迅速的答案随便你,但那不是真正的必要。
应该在声源完成播放队列中最后一个后更新。那最大的因子将影响缓冲区大小和
代表队列的缓冲区的个数。显然,如果你有太多的数据要播放,应减少更新。
同时流超过一个OGG安全吗?
应该安全。我虽然没做过,但我想他为什么不能。通常你不会有许多流。你
可以用一条播放背景音乐,和游戏的对白,但是太多的声音将干扰流。太多的
声源将只有一个缓冲区给他们。
关于名字
‘OGG’是Xiph.org的包容器,包含了音频,视频和元数据。‘VORBIS’为OGG
设计的一个音频压缩计划。
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -