本文大部分來自官方教程的Google翻譯 但是加了一點點個人的理解和其他相關知識
轉載請注明 原文鏈接 :https://www.cnblogs.com/Multya/p/16317401.html
官方教程: https://www.sfml-dev.org/tutorials/2.5/
播放聲音和音樂
聲音還是音樂?
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 = https://www.cnblogs.com/Multya/p/...;
// 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 創建的內部記錄執行緒呼叫,
如果您的記錄器使用可能在呼叫者執行緒和記錄執行緒中同時訪問的資料,您必須保護它(例如使用互斥鎖)以避免并發訪問,這可能導致未定義的行為——損壞的資料被記錄、崩潰等
如果您對執行緒不夠熟悉,可以參考相應的教程了解更多資訊,
持續更新中,,,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/482108.html
標籤:C++
上一篇:數論
下一篇:st表
