设为首页收藏本站|繁體中文

Project1

 找回密码
 注册会员
搜索
查看: 4686|回复: 16

[通用发布] Seal 0.1.1 「焦尾」3D音頻渲染庫

[复制链接]

Lv1.梦旅人

梦石
0
星屑
95
在线时间
953 小时
注册时间
2007-4-25
帖子
805
发表于 2012-12-30 10:33:10 | 显示全部楼层 |阅读模式

加入我们,或者,欢迎回来。

您需要 登录 才可以下载或查看,没有帐号?注册会员

x
本帖最后由 苏小脉 于 2012-12-31 05:40 编辑

Scorched End Audio Library

原本是給 RGE2 做的音頻渲染庫,但由於 RGE2 目前基本沒有活躍開發,故而先獨立出來給各位 RM 用戶試用。這個庫可以給 3D 遊戲用,但也兼容 2D。

Seal 是 Scorched End Audio Library 的簡稱,是從「焦尾音頻庫」翻譯過來的。相傳焦尾是東漢蔡邕所製名琴,與號鐘、繞梁、綠綺並稱為四大名琴。Seal 的底層是 OpenAL(相當於音頻界的 OpenGL),在其基礎上添加了對 Ogg Vorbis、MPEG1、2、3 和簡單 WAVE 格式的支持,同時也抽象了流式音頻的播放,在未來還會根據用戶的需求提供各種便捷的輔助方法、抽象層等。Seal 在原本的 OpenAL 的 C API 基礎上建立了 Ruby 綁定。

Seal 與默認的音頻模塊

默認的音頻模塊提供的是純粹的音頻重放,外加調節音量和音調的能力,除此之外並無其他特效。Seal 目前通過 OpenAL 支持對以下自然界的聲音現象的模擬:

聲音的增長和衰減

通俗的說就是聲源和觀察者之間的距離與觀察者聽到的聲音強度成正比。一個發聲物體離得越遠,觀察者附近的壓強震動就越小。比如牆上一把火炬,靠近它時燃燒的聲音變大,遠離它時聲音變小。RPG的場景中隨處都可以有這方面的應用——玩家走近市井,漸聞喧嘩;NPC 漸行漸遠,腳步聲漸悄。

多普勒效應

聲源和觀察者保持相對運動,當觀察者接收到聲波時,聲波的頻率與最初聲源發出的頻率和波長產生了差別的現象。比如當一輛救護車朝朝觀察者行駛而來,觀察者聽到的鳴笛聲會變得很急促,這是由於頻率變高、波長變短;當救護車駛過並遠離觀察者時,觀察者聽到的鳴笛聲則會變得很低沉,這是由於頻率變低、波長變長。遊戲中,法師的一個火球飛來,擦身而過的馬車都應該產生多普勒效應。

聲音的空間定位

人類通常可以識別出聲源在空間中的位置。前面提到的聲音的增長和衰減實際上是空間定位的一部分,因為通過聲音強度通常就能反過來推出發聲物體的距離,但僅限於距離,不含方向。玩家理應識別出兩個正在左手邊決鬥的NPC所發出的刀劍交擊聲是從左方傳來,而不是從右方。這個效果的具體表現手法是將音頻輸出到在不同位置的音頻輸出設備終端上。一對音箱或是當代筆記本電腦內置的揚聲器都有一左一右兩個輸出口,如此就把聲音的渲染從一個點提升到了一條線——聲音可以來自左邊,中間或者右邊。更多的輸出聲道和終端的搭配可以產生更真實的環繞音,把線提升到平面,平面提升到空間。

迴響
在特殊的環境中,一部分聲波被物體反射並逆向運動,與原有聲波交融所形成的效果(在特定場合下,反射回來的波甚至會再次被反射)。同樣的音效在不同的環境中會產生不同屬性的迴響——小屋內,管道中,森林中,空間站中,冰霜宮殿中……總之,有了對迴響效果的渲染,聲音的真實度便能大幅提升,尤其是在從一個環境切換到另一個環境中的時候那種聲音的驟變。

其它
除特效外,Seal 目前還具備 RM 所沒有的諸如播放時調節音量、音調,暫停播放、獲取音頻屬性等能力,當然仍然還有很多功能待實現,見「待完成」一欄。

編程模型

一個音頻數據文件的通過 Buffer 類或者 Stream 類對象加載——小巧的音頻數據用 Buffer 加載,大體積的音頻數據用 Stream 流式傳播。Buffer 或 Stream 的實例會被關聯上一個抽象地代表聲源的 Source 類對象,並通過 Source 的各種接口對音頻就行各種實時的操控、加工和過濾。全局可以有 n 個 Source 實例(遊戲中各種發聲的物體通常都是不同的 Source 實例),但只有一個 Listener 類的單一實例,表示觀察者(通常是場景中移動的玩家)。Source 的實例和觀察者都有位置和速度屬性,而實時更新這些屬性就能自動改變聲音在空間定位和減淡上的渲染。此間涉及到的大量數學計算都是底層引擎負責的,它最終會渲染出在某一時刻觀察者所在的地點聽到的把所有 Source 的聲音混合後的聲音。

Seal 的模型和 RGSS 的圖形編程模型有很大的可比性,彼此的對象之間有大致的對應關係。

Source 類似於 RGSS 的 Sprite,一個表示音頻,一個表示圖形,前者的不同實例最終集成於混音器的輸出,後者的不同實例最終集成到整個遊戲的圖形場景,兩者的存在都是為了方便對原有數據的實時加工渲染。

Buffer 類似 Bitmap,前者是音頻數據的容器,後者是圖像數據的容器。通常用於加載小橋的音頻文件,比如 RM 的各種 BGS、ME 和 SE。

Stream 從抽象層來看也基本等同於 Buffer,但從程序效率角度來看的話則大有不同——Buffer是將音頻一次性讀入內存,而 Stream 是流式數據傳輸。音頻數據通常比圖像數據大,一首5分鐘的MP3解壓之後會佔據50MB的空間,如果一次性讀入主內存那將會是很大的開銷。由於音頻播放的特性是只需要當前正在播放的這一部分數據,所以流式每次只讀取一小塊數據並傳輸給音頻輸出設備,而之前的數據佔據的內存則可以直接被釋放或重用。一個流隨時保持在內存中的數據只是原有音頻數據的很小一部分,就不會導致各種內存效率的問題(Cache miss,碎片,大量的頁缺失、內存顛簸)。通常需要用到流式的是背景音樂,比如 RM 的各種 BGM。

迴響是 OpenAL 軟件實現的的特效擴展系列之一。在 Seal 中,迴響是通過控制另外兩種對象實現的:EffectSlotReverb(Reverb 是迴響的英文 Reverberation 的簡寫)。Reverb 的實例有各種關於迴響的參數設置,表示一種特定環境的迴響。Source 的實例可以選擇應用某種迴響,這時就需要使用 EffectSlot。EffectSlot 可以看作是流水線上作業的一個過濾器,負責過濾數字信號,將輸入的音頻應用上迴響的效果。EffectSlot 可以插入不同類型的效果,但由於目前 Seal 只實現了迴響的效果,所以目前僅限 Reverb。從設計模式角度來看,EffectSlot 就是適配器,統一了 Source 和特效對象之間的關聯接口。

簡單流程

初始化 Seal:
RUBY 代码复制打印
  1. Seal.startup
  2.  
  3. include Seal # Seal 模塊是 Seal 的頂層命名空間,必要的時候可以 include 一下避免重複修飾 Seal::*


利用 Source 對象表示聲源,並關聯上一個 Buffer 對象:
RUBY 代码复制打印
  1. source = Source.new
  2. source.buffer = Buffer.new("audio.ogg")


改變 source 的位置:
RUBY 代码复制打印
  1. source.position = 3, 2, -4  # 歐幾里德 3D 空間座標體系,2D 遊戲只要永遠保持第三個座標為 0 即可


改變 listener (觀察者,Seal::Listener 的單例)的位置:

RUBY 代码复制打印
  1. Seal.listener.position = -1, -1, 0


播放 source:
RUBY 代码复制打印
  1. source.play


需要播放大體積的音頻資源時則採用 Stream:
RUBY 代码复制打印
  1. source.stream = Stream.open("background_music.ogg")


從一個 buffer 切換到 stream 及其相反:

RUBY 代码复制打印
  1. source.buffer = ...
  2.  
  3. # ...
  4.  
  5. source.buffer = nil
  6. source.stream = ...
  7.  
  8. # ...
  9.  
  10. source.stream = nil
  11. source.buffer = ...


應用迴響效果:
RUBY 代码复制打印
  1. # 分配一個 EffectSlot 對象,並關聯上一個 Reverb 對象
  2. slot = EffectSlot.new(Reverb.new(Reverb::Preset::FOREST))
  3. # 開始進行迴響效果的過濾
  4. source.feed(slot, 0)


其中 Reverb::Preset 包含一系列預定義的迴響效果,如城堡、工廠、戶外、城市等。詳見:
http://zhang.su/seal/Seal/Reverb/Preset.html

最終化 Seal:
RUBY 代码复制打印
  1. seal.cleanup


詳細文檔
在以下兩個地方都可以查閱:

http://zhang.su/seal/
http://rubydoc.info/gems/seal

rubydoc.info 上那個可以看以前版本的。暫時還沒有精力寫中文文檔 TvT,先湊合看著英文吧……

這裡有一些命令行的 demo 程序:
https://github.com/zhangsu/seal/tree/master/demo

注意事項

  • 只有單聲道的音頻數據文件在被 Seal 加載後才有空間定位、聲音減淡的效果。OpenAL 默認多聲道的音頻在設計的時候就考慮了立體音,所以不會強制改變它切換聲道的效果。多聲道音頻需要被事先轉換為單聲道音頻文件才能利用 Seal 的空間定位和減淡。
  • Seal 並不支持解碼所有 WAVE 的子格式。Seal 只是出於測試的目的提供了對無壓縮的小於等於 16bps 的 WAVE 的支持,但實際上微軟官方的規格中有很多 WAVE 的子格式(包括壓縮失真格式)。建議大家統一使用 Ogg Vorbis 格式,開放自由。
  • 當 Source 被 GC 回收時,底層的對象被釋放,聲音的播放自然也會終止。管理好 Source 的作用域和生命週期,慎用局部變量。使用局部變量極容易出現聲音還沒有播放完就突然中斷的情況,原因是被 GC 回收了對象。
  • 用 VA 測試時發現有很小的幾率在調用 Seal.startup 時出現打開設備失敗的異常,但重試又好使了,原因不明,而且只是在 RM 環境中出現。目前的救急方案是:

    RUBY 代码复制打印
    1. begin
    2.   Seal.startup
    3. rescue Seal::SealError
    4.   retry
    5. end


    或者限制重試次數:

    RUBY 代码复制打印
    1. 3.times do
    2.   begin
    3.     Seal.startup
    4.     break
    5.   rescue Seal::SealError
    6.     next
    7.   end
    8. end



下載

首先必需的是 OpenAL 庫,這裡提供 MSVC 10.0 預編譯的 32 位二進制,你也可以自己編譯,只要是這裡的 OpenAL 軟件實現。

OpenAL32.zip (220.15 KB, 下载次数: 127)

评分

参与人数 1星屑 +800 收起 理由
feizhaodan + 800 奖赏条例(虽然可能不需要

查看全部评分

[email protected]:~> repeat 1 fortune
Matz is nice, so we are nice.

Lv1.梦旅人

Mr.Gandum

梦石
0
星屑
48
在线时间
2070 小时
注册时间
2007-1-31
帖子
3046

贵宾

发表于 2012-12-30 10:44:18 | 显示全部楼层
播放位置就是传说中的“在远处听声音会比较小的”效果囧?
好像很酷的效果的说。

点评

是的 :)  发表于 2012-12-30 10:57
回复 支持 反对

使用道具 举报

Lv2.观梦者

梦石
0
星屑
340
在线时间
336 小时
注册时间
2012-8-9
帖子
118
发表于 2012-12-31 07:35:26 | 显示全部楼层
啊,非常强大的库= =
我在C++里一直苦于找不到比较容易使用的跨平台的开源的音频库 = =现在用的是SDL的音频部分= =
别人给我推荐过libo不过拿货我一直没弄明白
收藏了.
hello, world
数字君开始重新做人了原ID:[@]2719358[/@]

欢迎大家关注我的博客
有事情邮件联系 shuenhoy#gmail.com(#换成@)
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
61
在线时间
1640 小时
注册时间
2011-9-26
帖子
313
发表于 2013-3-30 11:00:41 | 显示全部楼层
有一个问题困扰在下好久了,希望能够赐教。
https://github.com/Shy07/SINRGE2
这个是在下的一个项目,未对 seal 或 ruby 做修改,但是流播放一直不太稳定,其他程序对硬盘读写一频繁,seal 就会自动停止播放。source 的状态一直被监视着,应该没理由会被 GC 回收才对。恳请指教。

PS:在下签出了 RGE2 的 SVN,发现 Demo 中 seal 不支持流播放,这个意味着什么吗?
愿善用者善用之
https://github.com/Shy07/SINRGE2
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
95
在线时间
953 小时
注册时间
2007-4-25
帖子
805
 楼主| 发表于 2013-3-31 08:53:32 | 显示全部楼层
Shy07 发表于 2013-3-30 11:00
有一个问题困扰在下好久了,希望能够赐教。
https://github.com/Shy07/SINRGE2
这个是在下的一个项目,未对 ...

感謝反饋!應該是個BUG,我這兩天先測試一下。

RGE2 的開發現在既然沒有繼續,我也就暫時沒有把新的代碼合併進去。
[email protected]:~> repeat 1 fortune
Matz is nice, so we are nice.
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
61
在线时间
1640 小时
注册时间
2011-9-26
帖子
313
发表于 2013-3-31 21:33:31 | 显示全部楼层
本帖最后由 Shy07 于 2013-3-31 21:35 编辑
苏小脉 发表于 2013-3-31 08:53
感謝反饋!應該是個BUG,我這兩天先測試一下。

RGE2 的開發現在既然沒有繼續,我也就暫時沒有把新的代碼 ...


另外希望加一个从内存读取的函数,方便ruby层的处理
例如:
RUBY 代码复制打印
  1. [url=home.php?mod=space&uid=171370]@source[/url] = Seal::Source.new
  2.  
  3. buf = ""
  4. open("audiofile", "rb") {|f| buf = f.read}
  5.  
  6. # do something like decrypting
  7.  
  8. @source.buffer = Seal::Buffer.new(buf, buf.size)
  9.  
  10. @source.play


这样或许还可以用 IO#read_nonblock 来干些别的事情?  

点评

这诡异的@功能就不能聪明些么= =  发表于 2013-3-31 21:36
愿善用者善用之
https://github.com/Shy07/SINRGE2
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
95
在线时间
953 小时
注册时间
2007-4-25
帖子
805
 楼主| 发表于 2013-4-10 07:18:23 | 显示全部楼层
Shy07 发表于 2013-3-31 21:33
另外希望加一个从内存读取的函数,方便ruby层的处理
例如:
@source = Seal::Source.new

嗯,这个好办,已经计划在下一次发布里了……

关于流播放中断的问题我现在暂时还没能重现,你试过改变(变大) source 的 chunk_size 和 queue_size 这两个属性吗?
[email protected]:~> repeat 1 fortune
Matz is nice, so we are nice.
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
61
在线时间
1640 小时
注册时间
2011-9-26
帖子
313
发表于 2013-4-12 22:29:31 | 显示全部楼层
苏小脉 发表于 2013-4-10 07:18
嗯,这个好办,已经计划在下一次发布里了……

关于流播放中断的问题我现在暂时还没能重现,你试过改变( ...

我觉得不是chunk_size、queue_size的大小问题,而是读取的容错处理。

因为我猜测问题出在seal读取时,而非播放时。比如seal在播放时而且正好在读取,这时候我用PotPlayer打开大比特率的视频文件,中断问题可以100%重现。
而其他软件如foobar,也会偶尔出现卡顿现象,但是还能继续播放。

而修改chunk_size、queue_size正好证明了我的猜测:
我把chunk_size设为1048576,缓存的数据多了,读取次数减少,这时候打开PotPlayer遇上seal读取的几率减少,相应地seal中断几率也减少。

不知道我这样理解是否正确?


愿善用者善用之
https://github.com/Shy07/SINRGE2
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
95
在线时间
953 小时
注册时间
2007-4-25
帖子
805
 楼主| 发表于 2013-5-20 08:37:17 | 显示全部楼层
Shy07 发表于 2013-4-12 22:29
我觉得不是chunk_size、queue_size的大小问题,而是读取的容错处理。

因为我猜测问题出在seal读取时,而 ...

嗯,我说试试改 chunk_size 和 queue_size 也是为了验证是否是这个问题……其实这是 OpenAL 的一个限制,OpenAL 播放流式声源是通过一个消费者任务不断的把 Buffer 从队列中移除并传递给 OpenAL 流水线实现的(生产者是不断往队列中添加 Buffer 的客户代码,比如 Seal),而当这个队列变空之后,这个消费者任务就会自动终止,声源的播放自然也会很快中断。最理想的实现应该是用某种特殊的 Buffer 表示“流终止”并在所有流的 Buffer 都处理完毕后推入队列末尾,向 OpenAL 传递“流到这里就终止了”的信号。OpenAL 端的消费者则会是一个阻塞式的线程,当队列长度为零但还没有收到“流终止”的信号时阻塞并等待“队列长度 >= 1”的信号;如果当前队列头是“流终止”,则终止任务。但 OpenAL 不支持这种模型,所以只能用另外的 workaround……

https://github.com/zhangsu/seal 的 master 更新了一个补丁,再试试吧 :)
[email protected]:~> repeat 1 fortune
Matz is nice, so we are nice.
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
61
在线时间
1640 小时
注册时间
2011-9-26
帖子
313
发表于 2013-5-20 20:54:57 | 显示全部楼层
本帖最后由 Shy07 于 2013-5-20 21:19 编辑
苏小脉 发表于 2013-5-20 08:37
嗯,我说试试改 chunk_size 和 queue_size 也是为了验证是否是这个问题……其实这是 OpenAL 的一个限制, ...


嗯,一会儿试试 :-)

顺便再反馈个问题:

C 代码复制打印
  1. seal_move_listener
  2. seal_move_src

这两个函数声明的时候用了 SEAL_API,但是 msvc 下的 exports.def 并没有导出这两个函数,似乎是忘了?


原来已经修正了,那暂时应该没问题了 :-)
愿善用者善用之
https://github.com/Shy07/SINRGE2
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册会员

本版积分规则

拿上你的纸笔,建造一个属于你的梦想世界,加入吧。
 注册会员
找回密码

站长信箱:[email protected]|手机版|小黑屋|无图版|Project1游戏制作

GMT+8, 2020-12-5 13:17

Powered by Discuz! X3.1

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表