音频之声道那些事
在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