在PCM音頻初見 - blackstar666 - 博客園 (cnblogs.com)中我們概況性地討論PCM中幾個重要的參數,接下來就好好聊聊聲道的一些事情。
一、聲道是什么
聲道是指在不同空間位置錄制或播放聲音時采集或播放的獨立音頻信號。通俗的講就是:聲道數就是聲源總數,比如:錄制棚內在不同方位上安放的錄音器,最后錄制的音頻就是多聲道的;播放多聲道的音頻文件,不同聲道的音頻信號通過對應聲道的揚聲器播放出來。
二、聲道類型
單聲道
單聲道的音頻只有一個音源,它是一種比較原始的再現聲音的方式;它的主要目的是讓人感覺聲音只在一個地方發出來。單聲道音頻在雙聲道或多聲道設備上播放,每個聲道的音頻信號完全一致,沒有一點點差異,起不到立體效果。
立體聲
立體聲是使用兩個或多個獨立音頻通道的聲音再現,其方式可產生從各個方向聽到的聲音的感覺,就像在自然聽覺中一樣。將不同的揚聲器插入到不同的聲道上,揚聲器中就會播放對應的聲道音頻信號。現實中使用最多的是雙聲道的耳機,有時候我們會感覺耳機左右的聲音信號強弱不同,聲音也有差別,這說明當前播放的音頻是立體聲的;如果聽起來完全沒什么區別,就是單聲道的音頻。
4.0環繞聲
4.0環繞聲也就是四聲道,4.0環繞聲在將四個揚聲器放置到四個角落的場景非常有用。聽眾可以聽到四個方位傳入耳朵的聲音。該環繞聲系統設備放置方位如下:

5.1環繞聲
5.1環繞聲系統使用6個通道,其中5個標准通道和1個超低音通道。該環繞聲系統設備放置方位圖如下:

7.1環繞聲
7.1環繞聲系統使用8個聲道,其中7個標准聲道和1個超低音聲道。該環繞聲系統設備放置方位圖如下:

三、編程中的聲道
在實際編程中(如下操作的音頻文件參數:44100HZ + 16bits + 2channels),當聲卡通道和被渲染的音頻聲道不同的時候,就需要我們進行聲道轉換。比如:2聲道轉換為單聲道。
1 void SplitChannel() 2 { 3 const auto originalFilename = R"(C:\Users\cvter\Desktop\audios\origin_audio_1_2.pcm)"; 4 const auto splitChannelFilename1 = R"(C:\Users\cvter\Desktop\audios\split_channel_2.pcm)"; 5 ifstream inStream(originalFilename, ios_base::binary); 6 if (!inStream.is_open()) { 7 return; 8 } 9 ofstream outStream(splitChannelFilename1, ios_base::binary); 10 if (!outStream.is_open()) { 11 return; 12 } 13 14 auto fileBuf1 = inStream.rdbuf(); 15 auto fileBuf2 = outStream.rdbuf(); 16 auto buf = make_unique<char[]>(kSampleBytes); 17 while (!inStream.eof()){ 18 memset(buf.get(), 0xff, kSampleBytes); 19 auto len = fileBuf1->sgetn(buf.get(), kSampleBytes); 20 if (len != kSampleBytes) { 21 break; 22 } 23 fileBuf2->sputn(buf.get() + kSampleBytes / kChannels, kDstSampleBytes); 24 fileBuf2->pubsync(); 25 } 26 27 inStream.close(); 28 outStream.close(); 29 }
分離效果圖如下:

將單聲道音頻合並成雙聲道,其中將分離出來的單聲道作為雙聲道左聲道,右聲道完全靜音:
1 void InsertBlankPCMData(const std::string& filename) 2 { 3 ifstream inStream(filename, ios_base::binary); 4 auto fileBuf1 = inStream.rdbuf(); 5 if (!fileBuf1->is_open()) { 6 return; 7 } 8 constexpr auto kInsertBlankFilename = R"(C:\Users\cvter\Desktop\audios\split_channel_1_blank.pcm)"; 9 ofstream outStream(kInsertBlankFilename, ios_base::binary); 10 auto fileBuf2 = outStream.rdbuf(); 11 if (!fileBuf2->is_open()) { 12 return; 13 } 14 int cnt = 0; 15 auto buf = make_unique<char[]>(kSampleBytes); 16 while (true){ 17 memset(buf.get(), 0xff, kSampleBytes); 18 auto len = fileBuf1->sgetn(buf.get(), kDstSampleBytes); 19 if (len == 0) { 20 break; 21 } 22 fileBuf2->sputn(buf.get(), kDstSampleBytes); 23 fileBuf2->sputn(buf.get() + kDstSampleBytes, kDstSampleBytes); 24 fileBuf2->pubsync(); 25 } 26 27 inStream.close(); 28 outStream.close(); 29 }

如果你用耳機聽的話,左邊聲道是有聲音的,右聲道沒有,不要以為自己的耳機壞了。
那么我們還可以將兩首單聲道音頻合並成一首雙聲道音頻,實現代碼如下:
1 void MakeStereoDifferentSources(const std::string& filenameL, const std::string& filenameR) 2 { 3 ifstream inStream1(filenameL, ios_base::binary); 4 ifstream inStream2(filenameR, ios_base::binary); 5 if (!inStream1.is_open() || !inStream2.is_open()) { 6 return; 7 } 8 9 const auto kStereoFilename = R"(C:\Users\cvter\Desktop\audios\stereo.pcm)"; 10 ofstream outStream(kStereoFilename, ios_base::binary); 11 if (!outStream.is_open()) { 12 return; 13 } 14 15 auto fileBuf1 = inStream1.rdbuf(); 16 auto fileBuf2 = inStream2.rdbuf(); 17 auto fileBuf3 = outStream.rdbuf(); 18 auto buf = make_unique<char[]>(kSampleBytes); 19 while (!inStream1.eof() || !inStream2.eof()){ 20 memset(buf.get(), 0xff, kSampleBytes); 21 auto len1 = fileBuf1->sgetn(buf.get(), kDstSampleBytes); 22 auto len2 = fileBuf2->sgetn(buf.get() + kDstSampleBytes, kDstSampleBytes); 23 if (len1 == 0 && len2 == 0) { 24 break; 25 } 26 fileBuf3->sputn(buf.get(), kSampleBytes); 27 28 } 29 }

當然,大家也可以將多個單聲道音頻組合成多聲道的,但是沒有對應的設備進行播放,就不驗證了。我在實際開發中出來過8通道的,就是將8首音頻重采樣為單聲道后,合並成8聲道的音頻,然后塞入到該設備聲卡上。由於博客不支持上傳壓縮文件,這里我推送到github上:git@github.com:blackStar1314/resources.git
