本文大部分來自官方教程的Google翻譯 但是加了一點點個人的理解和其他相關知識 轉載請註明 原文鏈接 :https://www.cnblogs.com/Multya/p/16317401.html 官方教程: https://www.sfml-dev.org/tutorials/2.5/ 播放聲音 ...
本文大部分來自官方教程的Google翻譯 但是加了一點點個人的理解和其他相關知識
轉載請註明 原文鏈接 :https://www.cnblogs.com/Multya/p/16317401.html
播放聲音和音樂
聲音還是音樂?
SFML 提供了兩個用於播放音頻的類:sf::Sound
和sf::Music
. 它們都提供或多或少相同的功能,主要區別在於它們的工作方式。(記得#include <SFML/Audio.hpp> 喂)
sf::Sound
是一個輕量級對象,用於播放從sf::SoundBuffer
. 它應該用於可以存儲在記憶體中的小聲音,並且在播放時應該沒有延遲。例如槍聲、腳步聲等。
sf::Music
不會將所有音頻數據載入到記憶體中,而是從源文件動態流式傳輸。它通常用於播放持續幾分鐘的壓縮音樂,否則將需要幾秒鐘才能載入並占用數百 MB 的記憶體。
載入和播放聲音
如上所述,聲音數據不是直接存儲sf::Sound
在一個名為sf::SoundBuffer
. 這個類封裝了音頻數據,它基本上是一個 16 位有符號整數數組(稱為“音頻樣本”)。樣本是聲音信號在給定時間點的幅度,因此樣本數組表示完整的聲音。
事實上,sf::Sound
/sf::SoundBuffer
類的工作方式 與圖形模塊中的sf::Sprite
/sf::Texture
相同。因此,如果您瞭解精靈和紋理如何協同工作,您可以將相同的概念應用於聲音和聲音緩衝區。
您可以使用其loadFromFile
功能從磁碟上的文件載入聲音緩衝區:
#include <SFML/Audio.hpp>
int main()
{
sf::SoundBuffer buffer;
if (!buffer.loadFromFile("sound.wav"))
return -1;
...
return 0;
}
loadFromMemory
與其他所有內容一樣,您也可以從記憶體(loadFromMemory
) 或自定義輸入流(loadFromStream
)載入音頻文件。
SFML 支持音頻文件格式 WAV, OGG/Vorbis 和 FLAC。由於許可問題,不支持 MP3。
您還可以直接從樣本數組載入聲音緩衝區,以防它們來自其他來源:
std::vector<sf::Int16> samples = ...;
buffer.loadFromSamples(&samples[0], samples.size(), 2, 44100);
由於loadFromSamples
載入的是樣本的原始數組而不是音頻文件,因此它需要額外的參數才能獲得對聲音的完整描述。第一個(第三個參數)是通道數;1 聲道定義單聲道,2 聲道定義立體聲,等等。第二個附加屬性(第四個參數)是採樣率;它定義了每秒必須播放多少個樣本才能重建原始聲音。
現在已經載入了音頻數據,我們可以用一個sf::Sound
實例來播放它。
sf::SoundBuffer buffer;
// load something into the sound buffer...
sf::Sound sound;
sound.setBuffer(buffer);
sound.play();
很酷的是,如果您願意,您可以將相同的聲音緩衝區分配給多個聲音。您甚至可以毫無問題地一起玩它們。
聲音(和音樂)在單獨的線程中播放。這意味著你可以在調用後自由地做任何你想做的事情play()
(當然除了破壞聲音或其數據),聲音將繼續播放直到它完成或明確停止。
播放音樂
與sf::Sound
不同的是,sf::Music
它不預載入音頻數據,而是直接從源流式傳輸數據。音樂的初始化因此更直接:
sf::Music music;
if (!music.openFromFile("music.ogg"))
return -1; // error
music.play();
需要註意的是,與所有其他 SFML 資源不同,載入函數loadFromFile
被命名openFromFile
. 這是因為音樂並沒有真正載入,這個功能只是打開它。數據僅在稍後播放音樂時載入。它還有助於記住,只要播放音頻文件,它就必須保持可用。
的其他載入函數sf::Music
遵循相同的約定:如 openFromMemory
, openFromStream
.
下一步是什麼?
現在您可以載入和播放聲音或音樂,讓我們看看您可以用它做什麼。
要控制播放,可以使用以下功能:
play
開始或恢復播放pause
暫停播放stop
停止播放和倒帶setPlayingOffset
改變當前播放位置
例子:
// start playback
sound.play();
// advance to 2 seconds
sound.setPlayingOffset(sf::seconds(2.f));
// pause playback
sound.pause();
// resume playback
sound.play();
// stop playback and rewind
sound.stop();
該getStatus
函數返回聲音或音樂的當前狀態,您可以使用它來知道它是停止、播放還是暫停。
聲音和音樂播放也由一些屬性控制,這些屬性可以隨時更改。
音高(pitch) 是改變聲音感知頻率的一個因素:大於 1 以較高的音高播放聲音,小於 1 以較低的音高播放聲音,1 保持不變。改變音高有一個副作用:它會影響播放速度。
sound.setPitch(1.2f);
音量(volume) 是……音量 。值範圍從 0(靜音)到 100(全音量)。預設值為 100,這意味著您不能發出比其初始音量更大的聲音。
sound.setVolume(50.f);
迴圈(loop) 屬性控制聲音/音樂是否自動迴圈播放 。如果它迴圈播放,它將在完成後從頭開始播放,一次又一次,直到您明確調用stop
. 如果不設置迴圈,它會在完成後自動停止。
sound.setLoop(true);
更多屬性可用,但它們與3D化相關,併在相應教程中進行了說明。
常見錯誤
損壞的聲音緩衝區
最常見的錯誤是讓聲音緩衝區超出作用域(因此被破壞),而聲音仍在使用它。
sf::Sound loadSound(std::string filename)
{
sf::SoundBuffer buffer; // this buffer is local to the function, it will be destroyed...
buffer.loadFromFile(filename);
return sf::Sound(buffer);
} // ... here
sf::Sound sound = loadSound("s.wav");
sound.play(); // ERROR: the sound's buffer no longer exists, the behavior is undefined
請記住,聲音只保留指向 您提供給它的聲音緩衝區的指針,它不包含自己的副本。您必須正確管理聲音緩衝區的生命周期,以便它們在被聲音使用時保持活動狀態。
太多的聲音
另一個錯誤來源是當您嘗試創建大量聲音時。SFML 內部有限制;它可能因操作系統而異,但不應超過 256。此限制是可以同時存在的sf::Sound
實例數和sf::Music
實例數之和。保持低於限制的一個好方法是在不再需要時銷毀(或回收)未使用的聲音。當然,這隻適用於您必須管理大量聲音和音樂的情況。
在播放時破壞音樂源
請記住,只要播放音樂,它就需要它的來源。當您的應用程式播放它時,磁碟上的音樂文件可能不會被刪除或移動,但是當您從記憶體中的文件或自定義輸入流中播放音樂時,事情會變得更加複雜:
// we start with a music file in memory (imagine that we extracted it from a zip archive)
std::vector<char> fileData = ...;
// we play it
sf::Music music;
music.openFromMemory(&fileData[0], fileData.size());
music.play();
// "ok, it seems that we don't need the source file any longer"
fileData.clear();
// ERROR: the music was still streaming the contents of fileData! The behavior is now undefined
sf::Music 不可複製
最後的“錯誤”是一個提醒:sf::Music
類是不可複製的,所以你不會被允許這樣做:
sf::Music music;
sf::Music anotherMusic = music; // ERROR
void doSomething(sf::Music music)
{
...
}
sf::Music music;
doSomething(music); // ERROR (the function should take its argument by reference, not by value)
錄製音頻
錄製到聲音緩衝區
捕獲的音頻數據最常見的用途是將其保存到聲音緩衝區 ( sf::SoundBuffer
) 中,以便可以播放或保存到文件中。
這可以通過sf::SoundBufferRecorder
類的非常簡單的介面來實現:
// first check if an input audio device is available on the system
if (!sf::SoundBufferRecorder::isAvailable())
{
// error: audio capture is not available on this system
...
}
// create the recorder
sf::SoundBufferRecorder recorder;
// start the capture
recorder.start();
// wait...
// stop the capture
recorder.stop();
// retrieve the buffer that contains the captured audio data
const sf::SoundBuffer& buffer = recorder.getBuffer();
靜態函數檢查系統SoundBufferRecorder::isAvailable
是否支持錄音。如果返回false
,您將根本無法使用sf::SoundBufferRecorder
該類。
和函數是不言自明的start
。stop
捕獲在其自己的線程中運行,這意味著您可以在開始和停止之間做任何您想做的事情。捕獲結束後,錄製的音頻數據可在聲音緩衝區中使用,您可以使用該 getBuffer
函數獲取該緩衝區。
使用記錄的數據,您可以:
-
將其保存到文件
buffer.saveToFile("my_record.ogg");
-
直接播放
sf::Sound sound(buffer); sound.play();
-
訪問原始音頻數據並對其進行分析、轉換等。
const sf::Int16* samples = buffer.getSamples(); std::size_t count = buffer.getSampleCount(); doSomething(samples, count);
如果您想在錄音機銷毀或重新啟動後使用捕獲的音頻數據,請不要忘記製作緩衝區的副本。
選擇輸入設備
如果您有多個聲音輸入設備連接到您的電腦(例如麥克風、聲音介面(外部音效卡)或網路攝像頭麥克風),您可以指定用於錄製的設備。聲音輸入設備由其名稱標識。可通過靜態函數SoundBufferRecorder::getAvailableDevices()
獲得包含所有連接設備名稱的std::vector<std::string>
。然後,您可以通過將所選設備名稱傳遞給setDevice()
方法,從列表中選擇一個設備進行錄製。甚至可以即時更改設備(即在錄製時)。
當前使用的設備名稱可以通過調用獲取getDevice()
。如果您不自己選擇設備,則將使用預設設備。它的名字可以通過靜態SoundBufferRecorder::getDefaultDevice()
函數獲得。
下麵是一個如何設置輸入設備的小例子:
// get the available sound input device names
std::vector<std::string> availableDevices = sf::SoundRecorder::getAvailableDevices();
// choose a device
std::string inputDevice = availableDevices[0];
// create the recorder
sf::SoundBufferRecorder recorder;
// set the device
if (!recorder.setDevice(inputDevice))
{
// error: device selection failed
...
}
// use recorder as usual
自定義錄製
如果您不希望將捕獲的數據存儲在聲音緩衝區中,您可以編寫自己的錄音機。這樣做將允許您在捕獲音頻數據時(幾乎)直接從錄音設備進行處理。例如,通過這種方式,您可以通過網路傳輸捕獲的音頻,對其執行實時分析等。
要編寫自己的記錄器,您必須從sf::SoundRecorder
抽象基類繼承。實際上, sf::SoundBufferRecorder
只是這個類的一個內置特化。
您只有一個虛函數可以在派生類中覆蓋:onProcessSamples
. 每次捕獲新的音頻樣本塊時都會調用它,因此這是您實現特定內容的地方。
預設情況下,音頻樣本每 100 毫秒提供給onProcessSamples
方法一次。您可以使用該 setProcessingInterval
方法更改間隔。例如,如果您想要實時處理記錄的數據,您可能想要使用更小的間隔。請註意,這隻是一個提示,實際周期可能會有所不同,因此不要依賴它來實現精確的計時。
還有兩個額外的虛函數可以選擇覆蓋:onStart
和onStop
. 它們分別在捕獲開始/停止時調用。它們對於初始化/清理任務很有用。
這是一個完整的派生類的骨架:
class MyRecorder : public sf::SoundRecorder
{
virtual bool onStart() // optional
{
// initialize whatever has to be done before the capture starts
...
// return true to start the capture, or false to cancel it
return true;
}
virtual bool onProcessSamples(const sf::Int16* samples, std::size_t sampleCount)
{
// do something useful with the new chunk of samples
...
// return true to continue the capture, or false to stop it
return true;
}
virtual void onStop() // optional
{
// clean up whatever has to be done after the capture is finished
...
}
}
isAvailable
/start
/stop
函數在基類sf::SoundRecorder
中定義 ,start
因此在每個派生類中都被繼承。這意味著您可以使用與sf::SoundBufferRecorder
類完全相同的任何記錄器類方法 。
if (!MyRecorder::isAvailable())
{
// error...
}
MyRecorder recorder;
recorder.start();
...
recorder.stop();
線程問題
由於記錄是在一個單獨的線程中完成的,因此瞭解究竟發生了什麼以及在哪裡發生是很重要的。
onStart
將由start
函數直接調用,因此它在調用它的同一線程中執行。但是, onProcessSample
和onStop
始終將從 SFML 創建的內部記錄線程調用。
如果您的記錄器使用可能在調用者線程和記錄線程中同時訪問的數據,您必須保護它(例如使用互斥鎖)以避免併發訪問,這可能導致未定義的行為——損壞的數據被記錄、崩潰等
如果您對線程不夠熟悉,可以參考相應的教程瞭解更多信息。
持續更新中。。。