GSM音頻編碼的優化和寫入wav文件

GSM是voip中較爲常見的一種編碼,壓縮率比很高,寫到wav文件每秒只佔用1.6k字節(接近於g729),是普通g711格式的五分之一,對錄音來說可節省大量磁盤空間。生成的wav文件,可能是不牽涉到專利的原因,在各種操作系統下都能夠播放。

1、使用IPP的codec

GSM的編解碼通常使用開源C代碼,入口文件是gsm_encode.c和gsm_decode.c,能用,但不夠優化。

我使用Intel公司著名的IPP庫,這個庫經過高度優化,cpu佔用很小。

實際使用中發現用IPP進行encode後rtp發給對方,對方軟電話無聲,解不出來。經過跟蹤,IPP編碼出來的33個字節,第1個字節的前半個字節爲0,應該爲gsm“魔術字”:0xd,加上這麼幾句即可:

IppEncode(... ..., outRawBuff);

unsigned char ch = outRawBuff[0];

ch = ch | 0xd0;

outRawBuff[0] = ch;

2、寫wav文件頭

關於gsm格式的wav文件格式,網上的文章很少,大多語焉不詳。

其實掌握要這2個要點就好:

首先,voip一般都是單聲道,8000採樣率,這個是基本。

然後,GSM encode後的一幀數據是32.5個字節,wav文件當然不能存儲半個字節,所以2幀組成一個數據塊,即65字節,剩下的都是枝節了:

	... ...
	WAVEFORMATEX m_wfExt;
	int fmtLen = 20;  // fmt塊長20字節
	memcpy(FileHeader.dwRiff, "RIFF", 4);
	memcpy(FileHeader.dwWave, "WAVE", 4);
	memcpy(FileHeader.dwFormat, "fmt ", 4);
	FileHeader.dwFormatLength = fmtLen;  // 20
	FileHeader.dwFileSize = contentLen + 20 + fmtLen;
	wavFp.write((char*)&FileHeader, 20);

	m_wfExt.nChannels = 1;  // 單聲道
	m_wfExt.nSamplesPerSec = 8000;  // 採樣率
	m_wfExt.wFormatTag = WAVE_FORMAT_GSM610;
	m_wfExt.wBitsPerSample = 0;  // 每個採樣的位數,gsm編碼顯然無法湊成整數,所以填0
	m_wfExt.nBlockAlign = 65;  // 塊對齊,即一個塊的字節數:1個塊就是2數據幀65字節
	m_wfExt.nAvgBytesPerSec = 1625; // 評價每秒的字節數,1秒有50個採樣週期,2個採樣週期爲65字節,所以65*25=1625字節
	m_wfExt.cbSize = 2;  // 擴展字段長度,即佔2個字節
	WORD wSamplesPerBlock = 320;  // 擴展字段:每個塊的採樣數,對於GSM來說兩幀數據爲1塊,所以此處填320,即160x2

	char fmtBuff[300];
	memset(fmtBuff, 0, 300);
	memcpy(fmtBuff, &m_wfExt, 18);
	memcpy(fmtBuff + 18, &wSamplesPerBlock, 2);

	wavFp.write(fmtBuff, fmtLen);  // 寫入fmt塊

3、數據如何寫入wav文件?

上面說了,用gsm codec編碼或rtp收取到的一幀gsm數據是33字節,有效數據爲32.5字節,第一個字節的前4位是沒什麼用的GSM“魔術字”0xd,但如何將兩幀數據合併成65字節的塊呢?

最容易想到的辦法是將第一幀數據全部前移4bit,剩下半個字節,填充到下一幀的第一個字節,經試驗證明不行,播放出來是噪音,後來讀gsm源代碼,才發現wav文件存儲的字節高低位和普通我們認知的高低位是相反的。1個字節8位,按順序0-7位,我們平常認爲最左邊是0bit,最右邊是7bit,但wav存儲的相反,最左邊是最高位7bit,最右邊是最低位0bit。

要把顛倒了的順序重新顛倒過來,甚爲複雜,此處就不貼代碼了,詳細可參考gsm的開源代碼。

 

 

發佈了102 篇原創文章 · 獲贊 14 · 訪問量 21萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章