簡述
在 Qt 之 WAV檔案解析 中給出了WAV檔案屬性的計算,具體包括檔案大小、音頻時長、比特率等屬性,這裡我們再次驗證一下這些屬性值的計算 。
在計算之前,我們要知道一下wav檔案中的三個參數 采樣頻率、音頻通道數、每次采樣得到的樣本位數 ,這三個參數用來表示聲音,同時決定了wav檔案的音質,大小。下面簡單介紹一下這三個參數。
采樣頻率
指每秒鐘取得聲音樣本的次數。采樣的過程就是抽取某點的頻率值,很顯然,在一秒中内抽取的點越多,擷取得頻率資訊更豐富,為了複原波形,采樣頻率越高,聲音的品質也就越好,聲音的還原也就越真實,但同時它占的資源比較多。由于人耳的分辨率很有限,太高的頻率并不能分辨出來。22050 的采樣頻率是常用的,44100已是CD音質,超過48000或96000的采樣對人耳已經沒有意義。
音頻通道數
聲音的通道的數目。常見的單聲道和立體聲(雙聲道),現在發展到了四聲環繞(四聲道)和5.1聲道。如果是雙聲道,采樣就是雙份的,檔案也差不多要大一倍。
每次采樣得到的樣本位數
采樣位數可以了解為聲霸卡處理聲音的解析度。這個數值越大,解析度就越高,錄制和回放的聲音就越真實。 采樣位數也叫采樣大小或量化位數。它是用來衡量聲音波動變化的一個參數,也就是聲霸卡的分辨率或可以了解為聲霸卡處理聲音的解析度。它的數值越大,分辨率也就越高,錄制和回放的聲音就越真實。
計算公式
波形資料傳輸速率(每秒平均位元組數) = 采樣頻率 × 音頻通道數 × 每次采樣得到的樣本位數 / 8
比特率(kbs) = 波形資料傳輸速率 × 8 / 1000
WAV檔案所占大小(位元組) = 波形資料傳輸速率 × 音頻檔案時長
音頻檔案時長(秒) = WAV檔案所占容量 / 波形資料傳輸速率
關于以上幾個屬性我們可以右擊wav檔案檢視檔案屬性看到這幾個值。見下圖。
從上述兩幅圖中我們可以知道這個wav檔案的總大小為6947位元組,比特率為88kbs,時間為0s,是不是很詫異,為什麼這裡時間為0呢?實際上windows這裡隻是按整數顯示了音頻時長,那麼真正的時間怎麼計算呢?
這裡我們已經知道了wav檔案的大小,看上述公式,我們還要知道波形資料傳輸速率,波形資料傳輸速率而又是由采樣頻率 、 音頻通道數 、 每次采樣得到的樣本位數 來決定,那麼這些參數怎麼擷取到呢?
看過Qt 之 解析wav檔案的頭資訊(詳細分析、對比不同wav檔案的資料)這篇文章就應該知道如何去解析一個wav檔案,并擷取所有的檔案頭資訊,如果不知道檔案頭資訊是什麼,請參考Qt 之 WAV檔案解析。
好了,既然對于一個wav檔案,我們能夠擷取到所有的頭資訊,那麼接下來就來驗證以上公式計算的結果。
上圖為wav檔案的頭資訊資料,我們可以看到波形資料傳輸速率(nBytesPerSecond)的值為11025,檔案總大小為6947位元組,音頻資料大小為6903位元組,檔案頭資訊為44位元組。
音頻檔案時長(秒) = WAV檔案所占容量 / 波形資料傳輸速率 = 6903 / 11025 = 0.626122 s
比特率(kbs) = 波形資料傳輸速率 × 8 / 1000 = 11025 × 8 / 1000 = 88 kbs
這裡為什麼精确到小數點後六位,其實也是為了與程式記錄的時間做對比,這裡也要特别注意:實際上 WAV檔案所占容量 為 WAV檔案中 音頻資料大小 ,而并非WAV檔案總大小 , 但是 檔案頭資訊所占位元組非常小,是以就算是将這塊大小加上進行計算,對最後的計算結果影響也非常小。下面我們就用QAudioOutput 來播放這個wav檔案,同時記錄播放時間 。
代碼之路
// 播放wav檔案
void MyAudioInput::onPlay()
{
sourceFile.setFileName(WAV_RECORD_FILENAME);
sourceFile.open(QIODevice::ReadOnly);
// 設定播放音頻格式;
QAudioFormat format;
format.setSampleRate();
format.setChannelCount();
format.setSampleSize();
format.setCodec("audio/pcm");
// wav檔案即按照這個位元組存儲順序儲存資料;
format.setByteOrder(QAudioFormat::LittleEndian);
format.setSampleType(QAudioFormat::UnSignedInt);
QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice());
//qDebug() << info.supportedCodecs();
if (!info.isFormatSupported(format))
{
qWarning() << "Raw audio format not supported by backend, cannot play audio.";
return;
}
m_audioOutput = new QAudioOutput(format, this);
connect(m_audioOutput, SIGNAL(stateChanged(QAudio::State)), this, SLOT(handleStateChanged(QAudio::State)));
m_audioOutput->start(&sourceFile);
m_time.start();
}
// 播放狀态更新;
void MyAudioInput::handleStateChanged(QAudio::State state)
{
switch (state) {
case QAudio::IdleState:
// Finished playing (no more data)
qDebug() << "elapsedUSecs:" << m_audioOutput->elapsedUSecs();
qDebug() << "time : " << m_time.elapsed();
onStopPlay();
break;
case QAudio::StoppedState:
// Stopped for other reasons
if (m_audioOutput->error() != QAudio::NoError) {
// Error handling
}
break;
default:
break;
}
}
// 關閉播放;
void MyAudioInput::onStopPlay()
{
if (m_audioOutput != NULL)
{
m_audioOutput->stop();
sourceFile.close();
delete m_audioOutput;
m_audioOutput = NULL;
}
}
代碼中我分别用了QAudioOutput類的elapsedUSecs方法和QTime類的elapsed方法來記錄wav檔案音頻時長。以下是兩個方法的介紹。
elapsedUSecs() 輸出為微妙
elapsed() 輸出為毫秒
通過記錄得到以下資料:
m_audioOutput->elapsedUSecs() : 636000
m_time.elapsed() : 635
m_audioOutput->elapsedUSecs() : 639000
m_time.elapsed() : 638
m_audioOutput->elapsedUSecs() : 642000
m_time.elapsed() : 641
m_audioOutput->elapsedUSecs() : 639000
m_time.elapsed() : 639
而我們的計算結果為: 0.626122 s = 626.122 ms = 626122 us , 顯然程式中擷取的時間大于計算的時間,這也很好了解,因為程式的運作需要消耗一定的時間,是以記錄的時間存在很小的誤差(誤差範圍大緻在0.009s ~ 0.016s),如果電腦性能更好這個誤差就越小。
特别注意
這裡我們用QAudioOutput類來計算wav檔案時長,這裡我們要給QAudioOutput類對象設定播放格式QAudioFormat ,設定的格式必須與解析出來的檔案頭資訊中的 采樣頻率、音頻通道數、每次采樣得到的樣本位數、編碼格式等嚴格保持一緻,否則不僅播放出來的聲音不清楚,記錄的音頻時長也有問題。
尾
通過以上發現,我們的計算公式是成立的。基本上我們可以在wav檔案頭資訊中擷取wav檔案的全部資訊,唯一就是wav**檔案時長**需要通過檔案頭中的資訊進行計算得到。是以如果我們想要做一個播放器,在播放器上顯示一個wav檔案的時長,我們就需要先解析wav檔案的頭資訊,通過計算得到檔案時長。
更多參考
Qt 之 WAV檔案解析
Qt之實作錄音播放及raw(pcm)轉wav格式
Qt 之 解析wav檔案的頭資訊(詳細分析、對比不同wav檔案的資料)