PCM脉码调制数字音频格式是70年代末发展起来的,记录媒体之一的CD,80年代初由飞利浦和索尼公司共同推出。PCM的音频格式也被DVD-A所采用,它支持立体声和5.1环绕声,1999年由DVD讨论会发布和推出的。
PCM的比特率,从14-bit发展到16-bit、18-bit、20-bit直到24-bit;采样频率从44.1kHz发展到192kHz。到目前 为止PCM这项技术可以改善和提高的方面则越来越来小。只是简单的增加PCM比特率和采样率,不能根本的改善它的根本问题。
PCM的主要问题在于:
1)任何PCM数字音频系统需要在其输入端设置急剧升降的滤波器,仅让20 Hz - 22.05 kHz的频率通过(高端22.05 kHz是由于CD 44.1 kHz的一半频率而确定),这是一项非常困难的任务。
2)在录音时采用多级或者串联抽选的数字滤波器(减低采样率),在重放时采用多级的内插的数字滤波器 (提高采样率),为了控制小信号在编码时的失真,两者又都需要加入重复定量噪声。这样就限制了PCM技术在音频还原时的保真度。
为了全面改善PCM 数字音频技术,获得更好的声音质量,就需要有新的技术来替换。近年来飞利浦和索尼公司再次联手,共同推出一种称为直接流数字编码技术DSD的格式, 其记录媒体为超级音频CD即SACD,支持立体声和5.1环绕声。
WAVE文件作为多媒体中使用的声波文件格式之一,它是以RIFF格式为标准的。 RIFF是英文Resource Interchange File Format的缩写,每个WAVE文件的头四个字节便是“RIFF”。WAVE文件由文件头和数据体两大部分组成。其中文件头又分为RIFF/WAV文件标识段和声音数据格式说明段两部分。WAVE文件各部分内容及格式见附表。
常见的声音文件主要有两种,分别对应于单声道(11.025KHz采样率、8Bit 的采样值)和双声道(44.1KHz采样率、16Bit的采样值)。采样率是指:声音信号在“模→数”转换过程中单位时间内采样的次数。采样值是指每一次采样周期内声音模拟信号的积分值。对于单声道声音文件,采样数据为八位的短整数(short int 00H-FFH); 而对于双声道立体声声音文件,每次采样数据为一个16位的整数(int),高八位和低八位分别代表左右两个声道。
WAVE文件数据块包含以脉冲编码调制(PCM)格式表示的样本。WAVE文件是由样本组织而成的。在单声道WAVE文件中,声道0代表左声道,声道1代表右声道。在多声道WAVE文件中,样本是交替出现的。
wave文件 = wave文件头部 + 音频数据(PCM格式)
//wave文件头部格式(共计44字节)
偏移地址 | 字节数 | 数据类型 | 内容 | 说明 |
0x00 | 4 | unsigned long | “RIFF” | 文件头标识,一般就是“RIFF”四个字母。 |
0x04 | 4 | unsigned long | int | 整个文件的大小。不包括0x00~0x07的空间大小。 |
0x08 | 4 | unsigned long | “WAVE” | 一般就是“WAVE”四个字母。 |
0x0c | 4 | unsigned long | “fmt” | 格式说明块,一般就是“fmt”。 |
0x10 | 4 | unsigned long | int | 本数据块的大小。 |
0x14 | 2 | unsigned short | 格式类别 | 音频格式说明(1为PCM形式的声音数据)。 |
0x16 | 2 | unsigned short | 声道数 | 单声道为1,双声道为2。 |
0x18 | 4 | unsigned long | 采样率 | 每秒样本数,表示每个通道的播放速度。 |
0x1c | 4 | unsigned long | 数据速率,以字节为单位计算 | 波形音频数据传送速率,其值为通道数×采样率×每样本的数据位数/8。 |
0x20 | 2 | unsigned short | 数据块的调整块 | 其值为通道数×每样本的数据位值/8。 |
0x22 | 2 | unsigned short | 每个样本的数据位数 | 表示每个声道中各个样本的数据位数。 |
0x24 | 4 | char | “DATA” | 一般就是“DATA”。 |
0x28 | 4 | long | int | 音频数据的长度。 |
PCM数据包格式
Sample1 | Sample2 | Sample3 | Smaple4 |
Channel0 | Channel0 | Channel0 | Channel0 |
8 bit单声道PCM数据包格式
Sample1 | Sample2 | ||
Chanell0(left) | Channel1(right) | Channel0(left) | Channel0(right) |
8bit立体音PCM数据包格式
Sample1 | Sample2 | ||
Channel0 Low-order byte | Channel0 Hight-order byte | Channel0 Low-order byte | Channel0 Hight-order byte |
16bit单声道PCM数据包格式
Smaple1 | |||
Channel0(left) Low-order byte | Channel0(left) High-order byte | Channel1(right) Low-order byte | Channel1(right) High-order byte |
16bit立体音PCM数据包格式
#ifndef __By6000WaveFile_H__
#define __By6000WaveFile_H__
#include <windows.h>
#include <fstream>
#include <string>
#include <memory>
using namespace std;
struct WaveFile_Header
{
unsigned long ulChunkID; //equal to "RIFF"
unsigned long ulChunkSize; //size of chunk,not contain ulChunkID and ulChunkSize
unsigned long ulFormat; //equal to "WAVE"
unsigned long ulSubChunk01ID; //equal to "fmt"
unsigned long ulSubChunk01Size; //size of SubChunk01,equal to 16 when audio format is pcm
unsigned short usAudioFormat; //1,PCM; ...
unsigned short usChannelCount; //1,only one audio channel; 2,have two audio channels
unsigned long ulSampleRate; //sample count per second
unsigned long ulByteRate; //byte count per sample
unsigned short usBlockAlign; //
unsigned short usBitsPerSample; //bit count a sampe have
unsigned long ulSubChunk02ID; //equal to "DATA"
unsigned long ulSubChunk02Size; //length of valid data
};
class CBy6000WaveFile;
struct THREAD_PARAMS_By6000WaveFile
{
CBy6000WaveFile* lpRunClass;
bool (CBy6000WaveFile::*lpRunFun)(void* lpParam);
};
#define SINGLE_CONVERT_LENGTH_MAX 32*1024
class CBy6000WaveFile
{
public:
CBy6000WaveFile(void);
virtual ~CBy6000WaveFile(void);
bool parse_wave_file(WaveFile_Header& varWaveFileHeader, const wchar_t* lpszFilePath);
bool data_convert_wave16_to_by6000pcm128(const wchar_t* lpszBy6000pcm128_FilePath, const wchar_t* lpszWave16_FilePath);
private:
bool single_data_convert_wave16_to_by6000pcm128(BYTE* lpszBy6000pcm128_DataBuffer, const BYTE* lpszWave16_DataBuffer, long lConverLen);
bool start_thread_convert();
void end_thread_convert();
bool thread_fun_convert(void* lpParam);
static DWORD __stdcall thread_run(LPVOID lpParam);
private:
fstream m_fsReader;
fstream m_fsWriter;
wstring m_wstrBy6000pcm128_FilePath;
wstring m_wstrWave16_FilePath;
HANDLE m_hThread_Convert;
bool m_bThreadRunning_Convert;
THREAD_PARAMS_By6000WaveFile m_varThreadParams_By6000WaveFile;
bool m_bConverSucceed;
auto_ptr<BYTE> m_ptrWave16_DataBuffer;
auto_ptr<BYTE> m_ptrBy6000pcm128_DataBuffer;
};
#endif
//By6000WaveFile.cpp
#include "StdAfx.h"
#include "By6000WaveFile.h"
CBy6000WaveFile::CBy6000WaveFile(void)
{
m_bThreadRunning_Convert = false;
m_hThread_Convert = NULL;
m_bConverSucceed = false;
m_ptrWave16_DataBuffer = auto_ptr<BYTE>(new BYTE[SINGLE_CONVERT_LENGTH_MAX + 0x100]);
m_ptrBy6000pcm128_DataBuffer = auto_ptr<BYTE>(new BYTE[4*SINGLE_CONVERT_LENGTH_MAX + 0x100]);
}
CBy6000WaveFile::~CBy6000WaveFile(void)
{
}
bool CBy6000WaveFile::parse_wave_file(WaveFile_Header& varWaveFileHeader, const wchar_t* lpszFilePath)
{
m_fsReader.open(lpszFilePath, ios::in | ios::binary);
if(!m_fsReader.is_open() )
{
return false;
}
m_fsReader.read((char*)&varWaveFileHeader, sizeof(varWaveFileHeader) );
m_fsReader.close();
return true;
}
bool CBy6000WaveFile::data_convert_wave16_to_by6000pcm128(const wchar_t* lpszBy6000pcm128_FilePath, const wchar_t* lpszWave16_FilePath)
{
m_wstrBy6000pcm128_FilePath = lpszBy6000pcm128_FilePath;
m_wstrWave16_FilePath = lpszWave16_FilePath;
start_thread_convert();
if(WAIT_OBJECT_0 !=::WaitForSingleObject(m_hThread_Convert, 24*3600*1000) )
{
end_thread_convert();
return false;
}
end_thread_convert();
if(!m_bConverSucceed)
{
return false;
}
return true;
}
bool CBy6000WaveFile::single_data_convert_wave16_to_by6000pcm128(BYTE* lpszBy6000pcm128_DataBuffer, const BYTE* lpszWave16_DataBuffer, long lConverLen)
{
if(NULL == lpszBy6000pcm128_DataBuffer || NULL == lpszWave16_DataBuffer)
{
return false;
}
if(0 != lConverLen % 4)
{
return false;
}
for(int i = 0; i < lConverLen; i += 4)
{
/*
lpszBy6000pcm128_DataBuffer[4*i + 0] = lpszWave16_DataBuffer[i];
lpszBy6000pcm128_DataBuffer[4*i + 1] = lpszWave16_DataBuffer[i + 1];
lpszBy6000pcm128_DataBuffer[4*i + 4] = lpszWave16_DataBuffer[i + 2];
lpszBy6000pcm128_DataBuffer[4*i + 5] = lpszWave16_DataBuffer[i + 3];
*/
lpszBy6000pcm128_DataBuffer[4*i + 0] = lpszWave16_DataBuffer[i + 1];
lpszBy6000pcm128_DataBuffer[4*i + 1] = lpszWave16_DataBuffer[i + 0];
lpszBy6000pcm128_DataBuffer[4*i + 4] = lpszWave16_DataBuffer[i + 3];
lpszBy6000pcm128_DataBuffer[4*i + 5] = lpszWave16_DataBuffer[i + 2];
}
return true;
}
bool CBy6000WaveFile::start_thread_convert()
{
//if the thread is running, end it first
end_thread_convert();
memset(&m_varThreadParams_By6000WaveFile, 0, sizeof(m_varThreadParams_By6000WaveFile) );
m_varThreadParams_By6000WaveFile.lpRunClass = this;
m_varThreadParams_By6000WaveFile.lpRunFun = &CBy6000WaveFile::thread_fun_convert;
m_bThreadRunning_Convert = true;
m_bConverSucceed = false;
m_hThread_Convert = ::CreateThread(NULL, 0, &CBy6000WaveFile::thread_run, &m_varThreadParams_By6000WaveFile, FALSE, NULL);
if(NULL == m_hThread_Convert)
{
return false;
}
return true;
}
void CBy6000WaveFile::end_thread_convert()
{
if(m_bThreadRunning_Convert)
{
m_bThreadRunning_Convert = false;
::WaitForSingleObject(m_hThread_Convert, 200);
::CloseHandle(m_hThread_Convert);
m_hThread_Convert = NULL;
}
}
bool CBy6000WaveFile::thread_fun_convert(void* lpParam)
{
WaveFile_Header varWaveFile_Header = {0};
if(!parse_wave_file(varWaveFile_Header, m_wstrWave16_FilePath.c_str() ) )
{
return false;
}
m_fsReader.open(m_wstrWave16_FilePath.c_str(), ios::in|ios::binary);
if(!m_fsReader.is_open() )
{
return false;
}
m_fsWriter.open(m_wstrBy6000pcm128_FilePath.c_str(), ios::out|ios::binary);
if(!m_fsWriter.is_open() )
{
m_fsReader.close();
return false;
}
m_fsWriter.clear();
long lConvertTotalLength = varWaveFile_Header.ulSubChunk02Size - (varWaveFile_Header.ulSubChunk02Size % 4);
long lReadPos = sizeof(WaveFile_Header);
long lReadLen = (SINGLE_CONVERT_LENGTH_MAX < lConvertTotalLength) ? SINGLE_CONVERT_LENGTH_MAX : lConvertTotalLength;
while(true)
{
memset(m_ptrWave16_DataBuffer.get(), 0, SINGLE_CONVERT_LENGTH_MAX + 0x100);
memset(m_ptrBy6000pcm128_DataBuffer.get(), 0, 4*SINGLE_CONVERT_LENGTH_MAX + 0x100);
m_fsReader.seekg(lReadPos);
m_fsReader.read((char*)m_ptrWave16_DataBuffer.get(), lReadLen);
single_data_convert_wave16_to_by6000pcm128(m_ptrBy6000pcm128_DataBuffer.get(), m_ptrWave16_DataBuffer.get(), lReadLen);
m_fsWriter.write((char*)m_ptrBy6000pcm128_DataBuffer.get(), 4*lReadLen);
m_fsWriter.flush();
lReadPos += lReadLen;
//finish the convert
if(long(lReadPos - sizeof(WaveFile_Header) ) >= lConvertTotalLength)
{
break;
}
else
{
long lLengthRemain = lConvertTotalLength + sizeof(WaveFile_Header) - lReadPos;
lReadLen = (SINGLE_CONVERT_LENGTH_MAX < lLengthRemain) ? SINGLE_CONVERT_LENGTH_MAX : lLengthRemain;
}
}
m_fsReader.close();
m_fsWriter.close();
m_bConverSucceed = true;
return true;
}
DWORD CBy6000WaveFile::thread_run(LPVOID lpParam)
{
THREAD_PARAMS_By6000WaveFile* lpThreadParams = reinterpret_cast<THREAD_PARAMS_By6000WaveFile*>(lpParam);
CBy6000WaveFile* lpRunClass = lpThreadParams->lpRunClass;
bool (CBy6000WaveFile::*lpRunFun)(void*) = lpThreadParams->lpRunFun;
(lpRunClass->*lpRunFun)(NULL);
return 0;
}
//main.cpp
// Console.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <windows.h>
#include <iostream>
using namespace std;
// #define BOOST_ALL_NO_LIB
// #include <boost/thread.hpp>
// #pragma comment(lib, "boost_thread-vc90-mt-1_48.lib")
#include "By6000WaveFile.h"
int main(int argc, char* argv[])
{
CBy6000WaveFile varBy6000WaveFile;
WaveFile_Header varWaveFileHeader = {0};
varBy6000WaveFile.parse_wave_file(varWaveFileHeader, L"D:\\Wave.wav");
varBy6000WaveFile.data_convert_wave16_to_by6000pcm128(L"D:\\Wave.pcm", L"D:\\Wave.wav");
return 0;
}