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

Project1

 找回密码
 注册会员
搜索
查看: 7358|回复: 16
打印 上一主题 下一主题

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

[复制链接]

Lv1.梦旅人

梦石
0
星屑
110
在线时间
953 小时
注册时间
2007-4-25
帖子
805
跳转到指定楼层
1
发表于 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, 下载次数: 163)

RM 程序

可以使用提供的 Win32API 綁定以及 seal 的 C 庫。庫文件(OpenAL32.dll,seal.dll)默認需要放在工程根目錄,如果需要更改,可以在 class SealAPI 下面添加一行:
  1. LIB_DIR = '庫文件所載目錄相對路徑'
复制代码
Win32API 綁定:
RUBY 代码复制
  1. module Seal
  2.   class SealAPI < Win32API
  3.     STRCPY_S = Win32API.new('msvcrt', 'strcpy_s', 'pll', 'i')
  4.  
  5.     def initialize(func, arg_types, return_type = 'i', *args)
  6.       @return_string = return_type == 'p'
  7.       library = File.join(defined?(LIB_DIR) ? LIB_DIR : '.', 'seal')
  8.       super(library, "seal_#{func}", arg_types, return_type, *args)
  9.     end
  10.  
  11.     def [](*args)
  12.       result = call(*args)
  13.       if @return_string and result.is_a? Integer
  14.         # String pointer is returned to Ruby as an integer even though we
  15.         # specified 'p' as the return value - possibly a bug in Ruby 1.9.3's
  16.         # Win32API implementation. Work around it.
  17.         message_buffer = ' ' * 128
  18.         STRCPY_S.call(message_buffer, message_buffer.size, result)
  19.         return message_buffer.strip
  20.       end
  21.       result
  22.     end unless method_defined? :[]
  23.   end
  24.  
  25.   module Helper
  26.     GET_ERR_MSG = SealAPI.new('get_err_msg', 'i', 'p')
  27.  
  28.     class << self
  29.       def define_enum(mod, constants, start_value = 0)
  30.         constants.each_with_index do |constant, index|
  31.           mod.const_set(constant, start_value + index)
  32.         end
  33.       end
  34.  
  35.       # Returns a destructor for a native Seal object. This is most likely
  36.       # called in the initializer method, but we cannot define the proc handler
  37.       # there because that will capture the binding of the implicit `self` (due
  38.       # to the nature of closure), which makes the object that `self` refers to
  39.       # unrecyclable.
  40.       def free(obj, destroyer)
  41.         lambda { |object_id| destroyer[obj] }
  42.       end
  43.     end
  44.  
  45.   private
  46.     def check_error(error_code)
  47.       raise SealError, GET_ERR_MSG[error_code], caller.shift if error_code != 0
  48.       nil
  49.     end
  50.  
  51.     def input_audio(media, filename, format, inputter)
  52.       check_error(inputter[media, filename, format])
  53.     end
  54.  
  55.     def set_obj_int(obj, int, setter)
  56.       check_error(setter[obj, int])
  57.       int
  58.     end
  59.  
  60.     def get_obj_int(obj, getter)
  61.       buffer = '    '
  62.       check_error(getter[obj, buffer])
  63.       buffer.unpack('i')[0]
  64.     end
  65.  
  66.     def set_obj_char(obj, bool, setter)
  67.       set_obj_int(obj, bool ? 1 : 0, setter)
  68.     end
  69.  
  70.     def get_obj_char(obj, getter)
  71.       buffer = ' '
  72.       check_error(getter[obj, buffer])
  73.       buffer.unpack('c')[0] != 0
  74.     end
  75.  
  76.     # Win32API does not support float argument type, need to pass as integer.
  77.     def set_obj_float(obj, float, setter)
  78.       check_error(setter[obj, [float].pack('f').unpack('i')[0]])
  79.       float
  80.     end
  81.  
  82.     def get_obj_float(obj, getter)
  83.       float_buffer = '    '
  84.       check_error(getter[obj, float_buffer])
  85.       float_buffer.unpack('f')[0]
  86.     end
  87.   end
  88.  
  89.   VERSION = SealAPI.new('get_version', 'v', 'p')[]
  90.  
  91.   class << self
  92.     include Helper
  93.  
  94.     STARTUP = SealAPI.new('startup', 'p')
  95.     CLEANUP = SealAPI.new('cleanup', 'v', 'v')
  96.     GET_PER_SRC_EFFECT_LIMIT = SealAPI.new('get_per_src_effect_limit', 'v')
  97.  
  98.     def startup(device = nil)
  99.       check_error(STARTUP[device ? device : 0])
  100.     end
  101.  
  102.     def cleanup
  103.       CLEANUP[]
  104.     end
  105.  
  106.     def per_source_effect_limit
  107.       GET_PER_SRC_EFFECT_LIMIT[]
  108.     end
  109.   end
  110.  
  111.   module Format
  112.     Helper.define_enum(self, [
  113.       :UNKNOWN,
  114.       :WAV,
  115.       :OV,
  116.       :MPG
  117.     ])
  118.   end
  119.  
  120.   class SealError < Exception
  121.   end
  122.  
  123.   class Listener
  124.     include Helper
  125.  
  126.     SET_GAIN = SealAPI.new('set_listener_gain', 'i')
  127.     GET_GAIN = SealAPI.new('get_listener_gain', 'p')
  128.     SET_POS = SealAPI.new('set_listener_pos', 'iii')
  129.     GET_POS = SealAPI.new('get_listener_pos', 'ppp')
  130.     SET_VEL = SealAPI.new('set_listener_vel', 'iii')
  131.     GET_VEL = SealAPI.new('get_listener_vel', 'ppp')
  132.     SET_ORIEN = SealAPI.new('set_listener_orien', 'p')
  133.     GET_ORIEN = SealAPI.new('get_listener_orien', 'p')
  134.  
  135.     def gain=(gain)
  136.       check_error(SET_GAIN[[gain].pack('f').unpack('i')[0]])
  137.       gain
  138.     end
  139.  
  140.     def gain
  141.       gain_buffer = '    '
  142.       check_error(GET_GAIN[gain_buffer])
  143.       return gain_buffer.unpack('f')[0]
  144.     end
  145.  
  146.     def position=(position)
  147.       set_3float(position, SET_POS)
  148.     end
  149.  
  150.     def position
  151.       get_3float(GET_POS)
  152.     end
  153.  
  154.     def velocity=(velocity)
  155.       set_3float(velocity, SET_VEL)
  156.     end
  157.  
  158.     def velocity
  159.       get_3float(GET_VEL)
  160.     end
  161.  
  162.     def orientation=(orientation)
  163.       check_error(SET_ORIEN[orientation.flatten.pack('f*')])
  164.     end
  165.  
  166.     def orientation
  167.       orientation_buffer = '    ' * 6
  168.       check_error(GET_ORIEN[orientation_buffer])
  169.       orientation = orientation_buffer.unpack('f*')
  170.       [orientation[0..2], orientation[3..5]]
  171.     end
  172.  
  173.   private
  174.     def set_3float(float_tuple, setter)
  175.       integer_tuple = float_tuple.pack('f*').unpack('i*')
  176.       check_error(setter[*integer_tuple])
  177.       return float_tuple
  178.     end
  179.  
  180.     def get_3float(getter)
  181.       float_tuple_buffers = Array.new(3) { '    ' }
  182.       check_error(getter[*float_tuple_buffers])
  183.       return float_tuple_buffers.join.unpack('f*')
  184.     end
  185.   end
  186.  
  187.   LISTENER = Listener.new
  188.  
  189.   def self.listener
  190.     LISTENER
  191.   end
  192.  
  193.   class << Listener
  194.     undef allocate
  195.     undef new
  196.   end
  197.  
  198.   class Buffer
  199.     include Helper
  200.  
  201.     INIT = SealAPI.new('init_buf', 'p')
  202.     DESTROY = SealAPI.new('destroy_buf', 'p')
  203.     LOAD = SealAPI.new('load2buf', 'ppi')
  204.     GET_SIZE = SealAPI.new('get_buf_size', 'pp')
  205.     GET_FREQ = SealAPI.new('get_buf_freq', 'pp')
  206.     GET_BPS = SealAPI.new('get_buf_bps', 'pp')
  207.     GET_NCHANNELS = SealAPI.new('get_buf_nchannels', 'pp')
  208.  
  209.     def initialize(filename, format = Format::UNKNOWN)
  210.       @buffer = '    '
  211.       check_error(INIT[@buffer])
  212.       input_audio(@buffer, filename, format, LOAD)
  213.       ObjectSpace.define_finalizer(self, Helper.free(@buffer, DESTROY))
  214.       self
  215.     end
  216.  
  217.     def load(filename, format = Format::UNKNOWN)
  218.       input_audio(@buffer, filename, format, LOAD)
  219.       self
  220.     end
  221.  
  222.     def size
  223.       get_obj_int(@buffer, GET_SIZE)
  224.     end
  225.  
  226.     def frequency
  227.       get_obj_int(@buffer, GET_FREQ)
  228.     end
  229.  
  230.     def bit_depth
  231.       get_obj_int(@buffer, GET_BPS)
  232.     end
  233.  
  234.     def channel_count
  235.       get_obj_int(@buffer, GET_NCHANNELS)
  236.     end
  237.   end
  238.  
  239.   class Stream
  240.     include Helper
  241.  
  242.     OPEN = SealAPI.new('open_stream', 'ppi')
  243.     CLOSE = SealAPI.new('close_stream', 'p')
  244.     REWIND = SealAPI.new('rewind_stream', 'p')
  245.  
  246.     class << self
  247.       alias open new
  248.     end
  249.  
  250.     def initialize(filename, format = Format::UNKNOWN)
  251.       [url=home.php?mod=space&uid=33438]@stream[/url] = '    ' * 5
  252.       input_audio(@stream, filename, format, OPEN)
  253.       ObjectSpace.define_finalizer(self, Helper.free(@stream, CLOSE))
  254.       self
  255.     end
  256.  
  257.     def frequency
  258.       field(4)
  259.     end
  260.  
  261.     def bit_depth
  262.       field(2)
  263.     end
  264.  
  265.     def channel_count
  266.       field(3)
  267.     end
  268.  
  269.     def rewind
  270.       check_error(REWIND[@stream])
  271.     end
  272.  
  273.     def close
  274.       check_error(CLOSE[@stream])
  275.     end
  276.  
  277.   private
  278.     def field(index)
  279.       @stream[index * 4, 4].unpack('i')[0]
  280.     end
  281.   end
  282.  
  283.   class Reverb
  284.     include Helper
  285.  
  286.     INIT = SealAPI.new('init_rvb', 'p')
  287.     DESTROY = SealAPI.new('destroy_rvb', 'p')
  288.     LOAD = SealAPI.new('load_rvb', 'pi')
  289.     SET_DENSITY = SealAPI.new('set_rvb_density', 'pi')
  290.     SET_DIFFUSION = SealAPI.new('set_rvb_diffusion', 'pi')
  291.     SET_GAIN = SealAPI.new('set_rvb_gain', 'pi')
  292.     SET_HFGAIN = SealAPI.new('set_rvb_hfgain', 'pi')
  293.     SET_DECAY_TIME = SealAPI.new('set_rvb_decay_time', 'pi')
  294.     SET_HFDECAY_RATIO = SealAPI.new('set_rvb_hfdecay_ratio', 'pi')
  295.     SET_REFLECTIONS_GAIN = SealAPI.new('set_rvb_reflections_gain', 'pi')
  296.     SET_REFLECTIONS_DELAY = SealAPI.new('set_rvb_reflections_delay', 'pi')
  297.     SET_LATE_GAIN = SealAPI.new('set_rvb_late_gain', 'pi')
  298.     SET_LATE_DELAY = SealAPI.new('set_rvb_late_delay', 'pi')
  299.     SET_AIR_ABSORBTION_HFGAIN =
  300.       SealAPI.new('set_rvb_air_absorbtion_hfgain', 'pi')
  301.     SET_ROOM_ROLLOFF_FACTOR = SealAPI.new('set_rvb_room_rolloff_factor', 'pi')
  302.     SET_HFDECAY_LIMITED = SealAPI.new('set_rvb_hfdecay_limited', 'pi')
  303.     GET_DENSITY = SealAPI.new('get_rvb_density', 'pp')
  304.     GET_DIFFUSION = SealAPI.new('get_rvb_diffusion', 'pp')
  305.     GET_GAIN = SealAPI.new('get_rvb_gain', 'pp')
  306.     GET_HFGAIN = SealAPI.new('get_rvb_hfgain', 'pp')
  307.     GET_DECAY_TIME = SealAPI.new('get_rvb_decay_time', 'pp')
  308.     GET_HFDECAY_RATIO = SealAPI.new('get_rvb_hfdecay_ratio', 'pp')
  309.     GET_REFLECTIONS_GAIN = SealAPI.new('get_rvb_reflections_gain', 'pp')
  310.     GET_REFLECTIONS_DELAY = SealAPI.new('get_rvb_reflections_delay', 'pp')
  311.     GET_LATE_GAIN = SealAPI.new('get_rvb_late_gain', 'pp')
  312.     GET_LATE_DELAY = SealAPI.new('get_rvb_late_delay', 'pp')
  313.     GET_AIR_ABSORBTION_HFGAIN =
  314.       SealAPI.new('get_rvb_air_absorbtion_hfgain', 'pp')
  315.     GET_ROOM_ROLLOFF_FACTOR = SealAPI.new('get_rvb_room_rolloff_factor', 'pp')
  316.     IS_HFDECAY_LIMITED = SealAPI.new('is_rvb_hfdecay_limited', 'pp')
  317.  
  318.     def initialize(preset = nil)
  319.       @effect = '    '
  320.       check_error(INIT[@effect])
  321.       load(preset) if preset
  322.       ObjectSpace.define_finalizer(self, Helper.free(@effect, DESTROY))
  323.       self
  324.     end
  325.  
  326.     def load(preset)
  327.       check_error(LOAD[@effect, preset])
  328.     end
  329.  
  330.     def density=(density)
  331.       set_obj_float(@effect, density, SET_DENSITY)
  332.     end
  333.  
  334.     def diffusion=(diffusion)
  335.       set_obj_float(@effect, diffusion, SET_DIFFUSION)
  336.     end
  337.  
  338.     def gain=(gain)
  339.       set_obj_float(@effect, gain, SET_GAIN)
  340.     end
  341.  
  342.     def hfgain=(hfgain)
  343.       set_obj_float(@effect, hfgain, SET_HFGAIN)
  344.     end
  345.  
  346.     def decay_time=(decay_time)
  347.       set_obj_float(@effect, decay_time, SET_DECAY_TIME)
  348.     end
  349.  
  350.     def hfdecay_ratio=(hfdecay_ratio)
  351.       set_obj_float(@effect, hfdecay_ratio, SET_HFDECAY_RATIO)
  352.     end
  353.  
  354.     def reflections_gain=(reflections_gain)
  355.       set_obj_float(@effect, reflections_gain, SET_REFLECTIONS_GAIN)
  356.     end
  357.  
  358.     def reflections_delay=(reflections_delay)
  359.       set_obj_float(@effect, reflections_delay, SET_REFLECTIONS_DELAY)
  360.     end
  361.  
  362.     def late_gain=(late_gain)
  363.       set_obj_float(@effect, late_gain, SET_LATE_GAIN)
  364.     end
  365.  
  366.     def late_delay=(late_delay)
  367.       set_obj_float(@effect, late_delay, SET_LATE_DELAY)
  368.     end
  369.  
  370.     def air_absorbtion_hfgain=(air_absorbtion_hfgain)
  371.       set_obj_float(@effect, air_absorbtion_hfgain, SET_AIR_ABSORBTION_HFGAIN)
  372.     end
  373.  
  374.     def room_rolloff_factor=(room_rolloff_factor)
  375.       set_obj_float(@effect, room_rolloff_factor, SET_ROOM_ROLLOFF_FACTOR)
  376.     end
  377.  
  378.     def hfdecay_limited=(hfdecay_limited)
  379.       set_obj_char(@effect, hfdecay_limited, SET_HFDECAY_LIMITED)
  380.     end
  381.  
  382.     def density
  383.       get_obj_float(@effect, GET_DENSITY)
  384.     end
  385.  
  386.     def diffusion
  387.       get_obj_float(@effect, GET_DIFFUSION)
  388.     end
  389.  
  390.     def gain
  391.       get_obj_float(@effect, GET_GAIN)
  392.     end
  393.  
  394.     def hfgain
  395.       get_obj_float(@effect, GET_HFGAIN)
  396.     end
  397.  
  398.     def decay_time
  399.       get_obj_float(@effect, GET_DECAY_TIME)
  400.     end
  401.  
  402.     def hfdecay_ratio
  403.       get_obj_float(@effect, GET_HFDECAY_RATIO)
  404.     end
  405.  
  406.     def reflections_gain
  407.       get_obj_float(@effect, GET_REFLECTIONS_GAIN)
  408.     end
  409.  
  410.     def reflections_delay
  411.       get_obj_float(@effect, GET_REFLECTIONS_DELAY)
  412.     end
  413.  
  414.     def late_gain
  415.       get_obj_float(@effect, GET_LATE_GAIN)
  416.     end
  417.  
  418.     def late_delay
  419.       get_obj_float(@effect, GET_LATE_DELAY)
  420.     end
  421.  
  422.     def air_absorbtion_hfgain
  423.       get_obj_float(@effect, GET_AIR_ABSORBTION_HFGAIN)
  424.     end
  425.  
  426.     def room_rolloff_factor
  427.       get_obj_float(@effect, GET_ROOM_ROLLOFF_FACTOR)
  428.     end
  429.  
  430.     def hfdecay_limited
  431.       get_obj_char(@effect, IS_HFDECAY_LIMITED)
  432.     end
  433.  
  434.     alias hfdecay_limited? hfdecay_limited
  435.  
  436.     module Preset
  437.       Helper.define_enum(self, [
  438.         :GENERIC,
  439.         :PADDEDCELL,
  440.         :ROOM,
  441.         :BATHROOM,
  442.         :LIVINGROOM,
  443.         :STONEROOM,
  444.         :AUDITORIUM,
  445.         :CONCERTHALL,
  446.         :CAVE,
  447.         :ARENA,
  448.         :HANGAR,
  449.         :CARPETEDHALLWAY,
  450.         :HALLWAY,
  451.         :STONECORRIDOR,
  452.         :ALLEY,
  453.         :FOREST,
  454.         :CITY,
  455.         :MOUNTAINS,
  456.         :QUARRY,
  457.         :PLAIN,
  458.         :PARKINGLOT,
  459.         :SEWERPIPE,
  460.         :UNDERWATER,
  461.         :DRUGGED,
  462.         :DIZZY,
  463.         :PSYCHOTIC
  464.       ])
  465.  
  466.       module Castle
  467.         Helper.define_enum(self, [
  468.           :SMALLROOM,
  469.           :SHORTPASSAGE,
  470.           :MEDIUMROOM,
  471.           :LARGEROOM,
  472.           :LONGPASSAGE,
  473.           :HALL,
  474.           :CUPBOARD,
  475.           :COURTYARD,
  476.           :ALCOVE
  477.         ], Preset::PSYCHOTIC + 1)
  478.       end
  479.  
  480.       module Factory
  481.         Helper.define_enum(self, [
  482.           :SMALLROOM,
  483.           :SHORTPASSAGE,
  484.           :MEDIUMROOM,
  485.           :LARGEROOM,
  486.           :LONGPASSAGE,
  487.           :HALL,
  488.           :CUPBOARD,
  489.           :COURTYARD,
  490.           :ALCOVE
  491.         ], Castle::ALCOVE + 1)
  492.       end
  493.  
  494.       module IcePalace
  495.         Helper.define_enum(self, [
  496.           :SMALLROOM,
  497.           :SHORTPASSAGE,
  498.           :MEDIUMROOM,
  499.           :LARGEROOM,
  500.           :LONGPASSAGE,
  501.           :HALL,
  502.           :CUPBOARD,
  503.           :COURTYARD,
  504.           :ALCOVE
  505.         ], Factory::ALCOVE + 1)
  506.       end
  507.  
  508.       module SpaceStation
  509.         Helper.define_enum(self, [
  510.           :SMALLROOM,
  511.           :SHORTPASSAGE,
  512.           :MEDIUMROOM,
  513.           :LARGEROOM,
  514.           :LONGPASSAGE,
  515.           :HALL,
  516.           :CUPBOARD,
  517.           :ALCOVE
  518.         ], IcePalace::ALCOVE + 1)
  519.       end
  520.  
  521.       module WoodenGalleon
  522.         Helper.define_enum(self, [
  523.           :SMALLROOM,
  524.           :SHORTPASSAGE,
  525.           :MEDIUMROOM,
  526.           :LARGEROOM,
  527.           :LONGPASSAGE,
  528.           :HALL,
  529.           :CUPBOARD,
  530.           :COURTYARD,
  531.           :ALCOVE
  532.         ], SpaceStation::ALCOVE + 1)
  533.       end
  534.  
  535.       module Sports
  536.         Helper.define_enum(self, [
  537.           :EMPTYSTADIUM,
  538.           :SQUASHCOURT,
  539.           :SMALLSWIMMINGPOOL,
  540.           :LARGESWIMMINGPOOL,
  541.           :GYMNASIUM,
  542.           :FULLSTADIUM,
  543.           :STADIUMTANNOY
  544.         ], WoodenGalleon::ALCOVE + 1)
  545.       end
  546.  
  547.       module Prefab
  548.         Helper.define_enum(self, [
  549.           :WORKSHOP,
  550.           :SCHOOLROOM,
  551.           :PRACTISEROOM,
  552.           :OUTHOUSE,
  553.           :CARAVAN
  554.         ], Sports::STADIUMTANNOY + 1)
  555.       end
  556.  
  557.       module Dome
  558.         Helper.define_enum(self, [
  559.           :TOMB,
  560.           :SAINTPAULS
  561.         ], Prefab::CARAVAN + 1)
  562.       end
  563.  
  564.       module Pipe
  565.         Helper.define_enum(self, [
  566.           :SMALL,
  567.           :LONGTHIN,
  568.           :LARGE,
  569.           :RESONANT
  570.         ], Dome::SAINTPAULS + 1)
  571.       end
  572.  
  573.       module Outdoors
  574.         Helper.define_enum(self, [
  575.           :BACKYARD,
  576.           :ROLLINGPLAINS,
  577.           :DEEPCANYON,
  578.           :CREEK,
  579.           :VALLEY
  580.         ], Pipe::RESONANT + 1)
  581.       end
  582.  
  583.       module Mood
  584.         Helper.define_enum(self, [
  585.           :HEAVEN,
  586.           :HELL,
  587.           :MEMORY
  588.         ], Outdoors::VALLEY + 1)
  589.       end
  590.  
  591.       module Driving
  592.         Helper.define_enum(self, [
  593.           :COMMENTATOR,
  594.           :PITGARAGE,
  595.           :INCAR_RACER,
  596.           :INCAR_SPORTS,
  597.           :INCAR_LUXURY,
  598.           :FULLGRANDSTAND,
  599.           :EMPTYGRANDSTAND,
  600.           :TUNNEL
  601.         ], Mood::MEMORY + 1)
  602.       end
  603.  
  604.       module City
  605.         Helper.define_enum(self, [
  606.           :STREETS,
  607.           :SUBWAY,
  608.           :MUSEUM,
  609.           :LIBRARY,
  610.           :UNDERPASS,
  611.           :ABANDONED
  612.         ], Driving::TUNNEL + 1)
  613.       end
  614.  
  615.       module Misc
  616.         Helper.define_enum(self, [
  617.           :DUSTYROOM,
  618.           :CHAPEL,
  619.           :SMALLWATERROOM
  620.         ], City::ABANDONED + 1)
  621.       end
  622.     end
  623.   end
  624.  
  625.   class Source
  626.     include Helper
  627.  
  628.     INIT = SealAPI.new('init_src', 'p')
  629.     DESTROY = SealAPI.new('destroy_src', 'p')
  630.     PLAY = SealAPI.new('play_src', 'p')
  631.     STOP = SealAPI.new('stop_src', 'p')
  632.     REWIND = SealAPI.new('rewind_src', 'p')
  633.     PAUSE = SealAPI.new('pause_src', 'p')
  634.     DETACH = SealAPI.new('detach_src_audio', 'p')
  635.     SET_BUF = SealAPI.new('set_src_buf', 'pp')
  636.     SET_STREAM = SealAPI.new('set_src_stream', 'pp')
  637.     FEED_EFS = SealAPI.new('feed_efs', 'ppi')
  638.     UPDATE = SealAPI.new('update_src', 'p')
  639.     SET_POS = SealAPI.new('set_src_pos', 'piii')
  640.     SET_VEL = SealAPI.new('set_src_vel', 'piii')
  641.     SET_GAIN = SealAPI.new('set_src_gain', 'pi')
  642.     SET_PITCH = SealAPI.new('set_src_pitch', 'pi')
  643.     SET_AUTO = SealAPI.new('set_src_auto', 'pi')
  644.     SET_RELATIVE = SealAPI.new('set_src_relative', 'pi')
  645.     SET_LOOPING = SealAPI.new('set_src_looping', 'pi')
  646.     SET_QUEUE_SIZE = SealAPI.new('set_src_queue_size', 'pi')
  647.     SET_CHUNK_SIZE = SealAPI.new('set_src_chunk_size', 'pi')
  648.     GET_POS = SealAPI.new('get_src_pos', 'pppp')
  649.     GET_VEL = SealAPI.new('get_src_vel', 'pppp')
  650.     GET_GAIN = SealAPI.new('get_src_gain', 'pp')
  651.     GET_PITCH = SealAPI.new('get_src_pitch', 'pp')
  652.     GET_AUTO = SealAPI.new('is_src_auto', 'pp')
  653.     GET_RELATIVE = SealAPI.new('is_src_relative', 'pp')
  654.     GET_LOOPING = SealAPI.new('is_src_looping', 'pp')
  655.     GET_QUEUE_SIZE = SealAPI.new('get_src_queue_size', 'pp')
  656.     GET_CHUNK_SIZE = SealAPI.new('get_src_chunk_size', 'pp')
  657.     GET_TYPE = SealAPI.new('get_src_type', 'pp')
  658.     GET_STATE = SealAPI.new('get_src_state', 'pp')
  659.  
  660.     def initialize
  661.       [url=home.php?mod=space&uid=171370]@source[/url] = '    ' * 5
  662.       check_error(INIT[@source])
  663.       ObjectSpace.define_finalizer(self, Helper.free(@source, DESTROY))
  664.       self
  665.     end
  666.  
  667.     def play
  668.       operate(PLAY)
  669.     end
  670.  
  671.     def stop
  672.       operate(STOP)
  673.     end
  674.  
  675.     def rewind
  676.       operate(REWIND)
  677.     end
  678.  
  679.     def pause
  680.       operate(PAUSE)
  681.     end
  682.  
  683.     def buffer=(buffer)
  684.       set_audio(:@buffer, buffer, SET_BUF)
  685.     end
  686.  
  687.     def stream=(stream)
  688.       set_audio(:@stream, stream, SET_STREAM)
  689.     end
  690.  
  691.     attr_reader :buffer, :stream
  692.  
  693.     def feed(effect_slot, index)
  694.       native_efs_obj = effect_slot.instance_variable_get(:@effect_slot)
  695.       check_error(FEED_EFS[@source, native_efs_obj, index])
  696.       self
  697.     end
  698.  
  699.     def update
  700.       operate(UPDATE)
  701.     end
  702.  
  703.     def position=(position)
  704.       set_3float(position, SET_POS)
  705.     end
  706.  
  707.     def velocity=(velocity)
  708.       set_3float(velocity, SET_VEL)
  709.     end
  710.  
  711.     def gain=(gain)
  712.       set_obj_float(@source, gain, SET_GAIN)
  713.     end
  714.  
  715.     def pitch=(pitch)
  716.       set_obj_float(@source, pitch, SET_PITCH)
  717.     end
  718.  
  719.     def auto=(auto)
  720.       set_obj_char(@source, auto, SET_AUTO)
  721.     end
  722.  
  723.     def queue_size=(queue_size)
  724.       set_obj_int(@source, queue_size, SET_QUEUE_SIZE)
  725.     end
  726.  
  727.     def chunk_size=(chunk_size)
  728.       set_obj_int(@source, chunk_size, SET_CHUNK_SIZE)
  729.     end
  730.  
  731.     def relative=(relative)
  732.       set_obj_char(@source, relative, SET_RELATIVE)
  733.     end
  734.  
  735.     def looping=(looping)
  736.       set_obj_char(@source, looping, SET_LOOPING)
  737.     end
  738.  
  739.     def position
  740.       get_3float(GET_POS)
  741.     end
  742.  
  743.     def velocity
  744.       get_3float(GET_VEL)
  745.     end
  746.  
  747.     def gain
  748.       get_obj_float(@source, GET_GAIN)
  749.     end
  750.  
  751.     def pitch
  752.       get_obj_float(@source, GET_PITCH)
  753.     end
  754.  
  755.     def auto
  756.       get_obj_char(@source, GET_AUTO)
  757.     end
  758.  
  759.     def relative
  760.       get_obj_char(@source, GET_RELATIVE)
  761.     end
  762.  
  763.     def looping
  764.       get_obj_char(@source, GET_LOOPING)
  765.     end
  766.  
  767.     alias auto? auto
  768.     alias relative? relative
  769.     alias looping? looping
  770.  
  771.     def queue_size
  772.       get_obj_int(@source, GET_QUEUE_SIZE)
  773.     end
  774.  
  775.     def chunk_size
  776.       get_obj_int(@source, GET_CHUNK_SIZE)
  777.     end
  778.  
  779.     def type
  780.       case get_obj_int(@source, GET_TYPE)
  781.       when Type::STATIC
  782.         Type::STATIC
  783.       when Type::STREAMING
  784.         Type::STREAMING
  785.       else
  786.         Type::UNDETERMINED
  787.       end
  788.     end
  789.  
  790.     def state
  791.       case get_obj_int(@source, GET_STATE)
  792.       when State::PLAYING
  793.         State::PLAYING
  794.       when State::PAUSED
  795.         State::PAUSED
  796.       when State::STOPPED
  797.         State::STOPPED
  798.       else
  799.         State::INITIAL
  800.       end
  801.     end
  802.  
  803.   private
  804.     def operate(operation)
  805.       check_error(operation[@source])
  806.       self
  807.     end
  808.  
  809.     def set_audio(var, audio, setter)
  810.       if audio.nil?
  811.         operate(DETACH)
  812.       else
  813.         check_error(setter[@source, audio.instance_variable_get(var)])
  814.       end
  815.       instance_variable_set(var, audio)
  816.       audio
  817.     end
  818.  
  819.     def set_3float(float_tuple, setter)
  820.       integer_tuple = float_tuple.pack('f*').unpack('i*')
  821.       check_error(setter[@source, *integer_tuple])
  822.       float_tuple
  823.     end
  824.  
  825.     def get_3float(getter)
  826.       float_tuple_buffers = Array.new(3) { '    ' }
  827.       check_error(getter[@source, *float_tuple_buffers])
  828.       float_tuple_buffers.join.unpack('f*')
  829.     end
  830.  
  831.     module State
  832.       Helper.define_enum(self, [
  833.         :INITIAL,
  834.         :PLAYING,
  835.         :PAUSED,
  836.         :STOPPED
  837.       ])
  838.     end
  839.  
  840.     module Type
  841.       Helper.define_enum(self, [
  842.         :UNDETERMINED,
  843.         :STATIC,
  844.         :STREAMING
  845.       ])
  846.     end
  847.   end
  848.  
  849.   class EffectSlot
  850.     include Helper
  851.  
  852.     INIT = SealAPI.new('init_efs', 'p')
  853.     DESTROY = SealAPI.new('destroy_efs', 'p')
  854.     SET_EFFECT = SealAPI.new('set_efs_effect', 'pp')
  855.     SET_GAIN = SealAPI.new('set_efs_gain', 'pi')
  856.     SET_AUTO = SealAPI.new('set_efs_auto', 'pi')
  857.     GET_GAIN = SealAPI.new('get_efs_gain', 'pp')
  858.     GET_AUTO = SealAPI.new('is_efs_auto', 'pp')
  859.  
  860.     def initialize(effect = nil)
  861.       @effect_slot = '    '
  862.       check_error(INIT[@effect_slot])
  863.       self.effect = effect if effect
  864.       ObjectSpace.define_finalizer(self, Helper.free(@effect_slot, DESTROY))
  865.       self
  866.     end
  867.  
  868.     def effect=(effect)
  869.       native_effect_obj = effect ? effect.instance_variable_get(:@effect) : 0
  870.       check_error(SET_EFFECT[@effect_slot, native_effect_obj])
  871.       @effect = effect
  872.       effect
  873.     end
  874.  
  875.     attr_reader :effect
  876.  
  877.     def gain=(gain)
  878.       set_obj_float(@effect_slot, gain, SET_GAIN)
  879.     end
  880.  
  881.     def gain
  882.       get_obj_float(@effect_slot, GET_GAIN)
  883.     end
  884.  
  885.     def auto=(auto)
  886.       set_obj_char(@effect_slot, auto, SET_AUTO)
  887.     end
  888.  
  889.     def auto
  890.       get_obj_char(@effect_slot, GET_AUTO)
  891.     end
  892.  
  893.     alias auto? auto
  894.   end
  895. end


Seal 的 C 庫:
seal.zip (243.88 KB, 下载次数: 151)

(以防萬一以上壓縮包也包含了 Win32API 綁定的 Ruby 源文件,可以直接粘貼入腳本編輯器)

非 Ruby 程序

可以直接將 Seal 編譯為 C 庫並調用(其實就是上面提供的 seal.dll):
https://github.com/zhangsu/seal#msvc--microsoft-visual-studio-2010

Ruby 程序

可以直接使用 gem:
https://rubygems.org/gems/seal

目前正和@叶子 趕製一款小遊戲演示 Seal 的音效功能,預計這幾天發佈。

待完成功能

  • Source 屬性
    • 最遠減淡距離
    • 滾降因子
    • 參考距離
    • 最大、最小音量
    • 方向矢量與錐形音量範圍
    • 當前播放位置
  • 改變 OpenAL 距離模型
  • 音速
  • 多普勒因子
  • 輸出設備枚舉
  • 音頻輸入(錄製)
  • 特效
    • 過濾器
    • 环形调制器
    • 回音
  • 前奏
  • 對象克隆(Object#clone/Object#dup)


貢獻代碼

目前的開發是在 Github 上:https://github.com/zhangsu/seal
強烈歡迎提交 pull request,大小不論,哪怕只是修復一個錯字。

评分

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

查看全部评分

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

Lv1.梦旅人

Mr.Gandum

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

贵宾

2
发表于 2012-12-30 10:44:18 | 只看该作者
播放位置就是传说中的“在远处听声音会比较小的”效果囧?
好像很酷的效果的说。

点评

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

使用道具 举报

Lv2.观梦者

梦石
0
星屑
448
在线时间
343 小时
注册时间
2012-8-9
帖子
118
3
发表于 2012-12-31 07:35:26 | 只看该作者
啊,非常强大的库= =
我在C++里一直苦于找不到比较容易使用的跨平台的开源的音频库 = =现在用的是SDL的音频部分= =
别人给我推荐过libo不过拿货我一直没弄明白
收藏了.
hello, world

有事情邮件联系 shuenhoy#gmail.com(#换成@)
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
66
在线时间
1641 小时
注册时间
2011-9-26
帖子
313
4
发表于 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
星屑
110
在线时间
953 小时
注册时间
2007-4-25
帖子
805
5
 楼主| 发表于 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
星屑
66
在线时间
1641 小时
注册时间
2011-9-26
帖子
313
6
发表于 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
星屑
110
在线时间
953 小时
注册时间
2007-4-25
帖子
805
7
 楼主| 发表于 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
星屑
66
在线时间
1641 小时
注册时间
2011-9-26
帖子
313
8
发表于 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
星屑
110
在线时间
953 小时
注册时间
2007-4-25
帖子
805
9
 楼主| 发表于 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
星屑
66
在线时间
1641 小时
注册时间
2011-9-26
帖子
313
10
发表于 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, 2024-4-27 12:06

Powered by Discuz! X3.1

© 2001-2013 Comsenz Inc.

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