Windows环境下的麦克风录音系统
简介
这是我很早以前的大学毕业设计,忽然间找到贴出来以纪念自己的纯真年代...但是因为CSDN不给面子所以导致短短的一篇文章贴了足足7次..他老提时说文章超过了64K,老大,拜托,那是算上了里面的图片大小吧...:-(
本文简单介绍了声卡的工作原理 , 录音的原理以及数字音频的基本知识并且利用 Windows 提供的 Waveform Aduio APIs 以及 Multimedia File I/O APIs 实现一个Windows 环境下的麦克风录音以及将录音文件保存成 .wav 文件的简单系统 .
关键字
Waveform Aduio APIs, Multimedia File I/O APIs,waveInXXX,
mmioXXX, 麦克风 , 录音 , 波形文件 ,VC6++
要深入的了解麦克风录音的实现我们必须了解声卡的工作原理 ,麦克风录音的原理以及了解相关的编程接口,下面我们将慢慢道来…
1. 声卡的工作原理
声卡的工作原理其实很简单 ,我们知道,麦克风和喇叭所用的都是模拟信号,而电脑所能处理的都是数字信号,两者不能混用,声卡的作用就是实现两者的转换。从结构上分,声卡可分为模数转换电路和数模转换电路两部分,模数转换电路负责将麦克风等声音输入设备采到的模拟声音信号转换为电脑能处理的数字信号,而数模转换电路负责将电脑使用的数字声音信号转换为喇叭等设备能使用的模拟信号,就这么简单 。
上图就是一块典型的声卡 , Mic 插口 用于连接麦克风 , 通过它可以录制外界的声音
2. 数字音频基础知识
麦克风录音的过程其实就是将模拟信号转化成数字信号的过程 , 其中涉及的一些概念如下 :
1. 采样率 (Sampling Rate)
采样率指声卡在一秒之中对声音 ( 波形 ) 作记录的次数 , 根据研究声音播出时的质量常常只能达到采样率的一半 , 因此必须采取双倍的采样率才能将声音标准重现 . 也就是只要采样率大于原始信号频率的两倍以上即可减低错误 , 达到和原始声音差不多的质量 . 人的听力大概是 20KHZ, 所以高品质的采样率应为其两倍以上 .
当声音来源为音乐时 , 因为它所横跨的频率变化极为宽广 , 通常以 44.1KHZ 的频率为 CD 音乐采样率的标准 . 但是若以语言为主由于人说话的语音大概是 10KHZ, 因此加倍采样 , 只取 22KHZ 即可 , 采样率越高所记录下来的音质就越清晰 , 当然 , 越高的采样所记录下的文件就越大 .
2. 采样位
解析度决定了采样的音波是否能保持原来的形状 , 越接近原型则需解析度越高 , 若以 8 位来采样的话其能表达的组合种类是 2 的 8 次方 , 即 256, 表示用 8 位的采样大小能分辨出 256 个层次的声音 , 若用 16 位来采样 , 则能分辨的差异将高达 2 的 16 次方 , 为 65536, 其精度自然大为提高 .16 位 ,8 位采样的差别在于动态范围的宽窄 , 动态范围宽广 ,音量起伏的大小变化就能够更精细的被记录下来 , 如此一来不论是细微的声音或是强烈的动感震撼 , 都可以表现的淋漓尽致 , 而 CD 音质的采样规格正式 16 位采样的规格 .
3. 量化误差 (Quantization error)
在采样的过程中 , 不断连续变化的模拟信号要用数字化的数值来表示 , 这样的过程就会发生所谓的量化误差 (Quantization error). 所谓的量化误差指的是实际的信号的振幅 (smplitude) 和数字化之后所的数字之间的差异 . 如果用将数字信号还原成模拟信号的角度看 , 量化误差就是失真 (Distortion). 我们可以用增加采样大小的方式来降低量化误差 ,也就是更多的位 (bits) 来表示一个采样信号 , 这样可以提高精度 .
4. 量化 (Quantization), 线性量化法 (Linear quantization) 和非线性量化法 (Nonlinear quantization)
所谓的量化 (Quantization) 就是将模拟信号所代表的连续范围分成一段一段的区间 (Interval), 每一段区间我们定义一个数字化的值 . 区间的数目是跟采样大小有关 , 举例来说 , 有一种最简单的量化法称为 ” 线性量化法 ”(Linear quantization), 这种量化法采用等距离的间隔空间 , 架设一个讯号它的最大值是 5.0, 采样大小为 3 位 , 则每个量化区间就时 5.0/2^3, 也就是 0.625 单位 . 另外一种相反的量化方法就是 ” 非线性量化法 ”(Nonlinear quantization), 这种量化法采用不同的间隔空间 . 以 ” 对数量化法 ”(Logarithm quantization) 为例 . 低振幅范围的量化区间就比高振幅的范围的区间较为接近 , 用这种量化的法产生的结果就是在低振幅时我们会得到佳好的效果 . 通常如果使用同样的采样大小 ,非线性量化法会比线性量化法得到更好的声音品质 . 但是如果是要对声音做滤波 (filtered) 或一些运算的时候 , 使用线性量化法会比较容易处理 .
5. 声音强度
波形振幅的平方.两个声音强度上的差常以分贝 (db) 为单位来度量 ,计算公式如下:
20*log(A1/A2) 分贝 , A1,A2 为两个声音的振幅 .
a. 如果采样大小为 8 位 ,则采样的动态范围为 20*log(256) 分贝 =48db;
b. 如果样本大小为 16 位 ,则采样动态范围为 20*log(65536) 大约是 96 分贝 ,接近了人听觉极限和痛苦极限,是再线音乐的理想范围, windows 同时支持 8 位和 16 位的采样大小 .
6. 音频编码方法
目前已经发展了许多音频编码的方法用以减少存储量或是传输的时间 , 以下所列为两种较普遍的编码方法 :
a.PCM(Pulse code modulation);
脉冲编码调制 ,即对波形按照固定周期频率采样。为了保证采样后数据质量,采样频率必须是样本声音最高频率的两倍,这就是 Nyquist 频率 .
b.ADPCM(Adaptive delta pulse modulation).
3. RIFF 文件结构和 WAVE 文件格式
Windows 支持两种 RIFF(Resource Interchange File Format," 资源交互文件格式 ") 格式的音频文件: MIDI 的 RMID 文件和波形音频文件格式 WAVE 文件,其中在计算机领域最常用的数字化声音文件格式是后者,它是微软专门为 Windows 系统定义的波形文件格式( Waveform Audio ),由于其扩展名为 "*.wav" ,因而该类文件也被称为WAVE 文件。 为了突出重点,有的放矢,本文涉及到的声音文件所指的就是 WAVE 文件。常见的 WAVE 语音文件主要有两种,分别对应于单声道( 11.025KHz 采样率、 8Bit 的采样值)和双声道( 44.1KHz 采样率、 16Bit 的采样值)。这里的采样率是指声音信号在进行 " 模→数 " 转换过程中单位时间内采样的次数。采样值是指每一次采样周期内声音模拟信号的积分值。对于单声道声音文件,采样数据为八位的短整数( short int 00H-FFH );而对于双声道立体声声音文件,每次采样数据为一个 16 位的整数( int ),高八位和低八位分别代表左右两个声道。 WAVE 文件数据块包含以脉冲编码调制( PCM )格式表示的样本。在进行声音编程处理以前,首先让我们来了解一下 RIFF 文件和 WAVE 文件格式。
RIFF 文件结构可以看作是树状结构,其基本构成是称为 " 块 " ( Chunk )的单元,每个块有 " 标志符 " 、 " 数据大小 " 及 " 数据 " 所组成,块的结构如图 2 所示:
块的标志符( 4BYTES ) |
数据大小 ( 4BYTES ) |
数据 |
图 2
从上图可以看出,其中 " 标志符 " 为 4 个字符所组成的代码,如 "RIFF" , "LIST" 等,指定块的标志 ID ;数据大小用来指定块的数据域大小,它的尺寸也为 4 个字符;数据用来描述具体的声音信号,它可以由若干个子块构成,一般情况下块与块是平行的,不能相互嵌套,但是有两种类型的块可以嵌套子块,他们是 "RIFF" 或 "LIST" 标志的块,其中RIFF 块的级别最高,它可以包括 LIST 块。另外, RIFF 块和 LIST 块与其他块不同, RIFF 块的数据总是以一个指定文件中数据存储格式的四个字符码(称为格式类型)开始,如WAVE 文件有一个 "WAVE" 的格式类型。 LIST 块的数据总是以一个指定列表内容的 4 个字符码(称为列表类型)开始,例如扩展名为 ".AVI" 的视频文件就有一个 "strl" 的列表类型。 RIFF 和 LIST 的块结构如下:
RIFF/LIST 标志符 |
|
数据 1 大小 |
|
数据 1 |
格式 / 列表类型 |
数据 |
图 3
WAVE 文件是非常简单的一种 RIFF 文件,它的格式类型为 "WAVE" 。 RIFF 块包含两个子块,这两个子块的 ID 分别是 "fmt" 和 "data", 其中 "fmt" 子块由结构PCMWAVEFORMAT 所组成,其子块的大小就是 sizeofof(PCMWAVEFORMAT), 数据组成就是 PCMWAVEFORMAT 结构中的数据。 WAVE 文件的结构如下图 4 所示:
标志符( RIFF ) |
数据大小 |
格式类型( "WAVE" ) |
"fmt" |
Sizeof(PCMWAVEFORMAT) |
PCMWAVEFORMAT |
"data" |
声音数据大小 |
声音数据 |
图 4
PCMWAVEFORMAT 结构定义如下:
typedef struct 16位单声道:
16 位双声道:
图 5 4. 硬件抽象层 ( HAL, Hardware Abstraction Layer ) HAL 是一个可加载的核心模块 (HAL.dll) ,它为运行在 Windows NT 架构 ( 包括 WindowsNT4.0,Windows2000,WindowsXP) 上 的硬件平台提供低级接口 , HAL隐藏各种与硬件有关的细节 ,例如: I/O 接口 ,中断控制器,声卡…这样的话如果用户需要访问声卡硬件的话只能通过该声卡的驱动程序来实现,声卡驱动程序再调用 HAL中的相应例程来实现 ,下图显示了 HAL ,声卡驱动程序, Waveform Audio APIs ,我们的麦克录音程序之间的关系: |
5. Waveform Audio
Waveform Audio APIs 是 Microsoft 提供给广大 Win32 程序员用来给自己的应用程序添加声音支持的一套强大的 API, 它提供的功能如下 :
1. 打开 / 关闭 / 查询声音设备 ;
2. 播放波形文件 ;
3. 设置播放速度 ;
4. 播放进度控制 ;
5. 录音 ;
6. 得到当前的播放位置 ;
7. 调节音量 .
下面简单介绍一下这套 API 提供的主要函数 :
- 打开录音设备函数
MMRESULT waveInOpen(
LPHWAVEIN phwi, // 输入设备句柄
UINT uDeviceID, // 输入设备 ID
LPWAVEFORMATEX pwfx, // 录音格式指针
DWORD dwCallback, // 处理 MM_WIM_*** 消息的回调函数或窗
// 口句柄,线程 ID
DWORD dwCallbackInstance,
DWORD fdwOpen // 处理消息方式的符号位
);
- 为录音设备准备缓存函数
MMRESULT waveInPrepareHeader( HWAVEIN hwi,
LPWAVEHDR pwh,
UINT bwh );
- 给输入设备增加一个缓存
MMRESULT waveInAddBuffer( HWAVEIN hwi,
LPWAVEHDR pwh,
UINT cbwh );
- 开始录音
MMRESULT waveInStart( HWAVEIN hwi );
- 清除缓存
MMRESULT waveInUnprepareHeader( HWAVEIN hwi,
LPWAVEHDR pwh,
UINT cbwh);
- 停止录音
MMRESULT waveInReset( HWAVEIN hwi );
- 关闭录音设备
MMRESULT waveInClose( HWAVEIN hwi );
- Wave_audio 数据格式
typedef struct {
WORD wFormatTag; // 数据格式,一般为 WAVE_FORMAT_PCM 即
// 脉冲编码
WORD nChannels; // 声道
DWORD nSamplesPerSec; // 采样频率
DWORD nAvgBytesPerSec; // 每秒数据量
WORD nBlockAlign;
WORD wBitsPerSample; // 样本大小
WORD cbSize;
} WAVEFORMATEX;
- waveform-audio 缓存格式
typedef struct {
LPSTR lpData; // 内存指针
DWORD dwBufferLength; // 长度
DWORD dwBytesRecorded; // 已录音的字节长度
DWORD dwUser;
DWORD dwFlags;
DWORD dwLoops; // 循环次数
struct wavehdr_tag * lpNext;
DWORD reserved;
} WAVEHDR;
- 相关消息
MM_WIM_OPEN: 打开设备时消息,在此期间我们可以进行一些初始化工作
MM_WIM_DATA: 当缓存已满或者停止录音时的消息,处理这个消息可以对
缓存进行重新分配,实现不限长度录音
MM_WIM_CLOSE :关闭录音设备时的消息。
5. Multimedia File I/O
Multimedia File I/O APIs 是 Microsoft 提供的另外一套强大的针对媒体文件 I/O 的 API, 我们知道许多像 MediaPlay,RealOne 这样的多媒体程序对媒体文件的读写性能要求很高 , 它们几乎要求实时的将磁盘上的媒体文件以流的形式读入 , 但是对于一般的文件 I/O 形式如图 1:
1.文件从磁盘上被读入操作系统的File I/O的buffer;
2. 然后拷贝到应用程序自己的 buffer 中 ;
3. 应用程序这时候才能读取文件内容 .
上述的过程对于多媒体应用程序来说是低效的而且浪费宝贵的内存资源,如果文件和大的话势必还要采取分段读取等机制, Multimedia File I/O 采取了一种直接存取机制 ( 如图 2), 使得应用程序可以直接读取操作系统的 File I/O buffer, 大大提高了效率 . 后面我们会利用此套 API 实现录音文件的存储 .
6. 麦克录音系统简介
本文实现的麦克录音系统将具备以下功能 :
1. 录制用户通过麦克风发出的声音 ;
这将利用到 Waveform APIs, 流程如下 :
a. 打开录音设备 waveInOpen;
b. 准备 wave 数据头 waveInPrepareHeader;
c. 准备数据块 waveInAddBuffer;
d. 开始录音 waveInStart;
e. 停止录音 (waveInReset);
f. 关闭录音设备 (waveInClose);
g. 当开始录音后当 buffer 已满时 , 将收到 MM_WIM_DATA 消息 , 处理该
消息可以保存已录好数据 .
2. 根据用户的声音强弱动态显示声音波形 ;
这主要通过 GDI 函数来实现 .
3. 将用户通过麦克风发出的声音录制成 wav 文件保存 .
这将利用到 Multimedia file I/O APIs.
a .调用 mminoOpen 函数来打开 WAVE 文件 , 获取 HMMIO 类型的文件句柄 ;
b .根据 WAVE 文件的结构 , 调用 mmioRead 、 mmioWrite 和 mmioSeek 函数实现文件的读、写和定位操作 ;
c .调用 mmioClose 函数来关闭 WAVE 文件 .
7. 麦克录音系统的实现 (MicDemo)
下面是该系统的界面 :
namespace perdubug { // prevent the name-space pollution
class CSoundIn
{
public :
BOOL __initMic(); // get the best wave format supported by your sound card
// and then i will use the format to capture sound.
void __closeMic();
BOOL __openMic(); // open device and begin to capture with the best format(when
// invoke __initMic function then you will get the best format
// supported by host's sound card
//
// if your want to capture sound and export into a wav file please invoke this function
// to tell me the full path then i will create the wav file.
//
void __createOutputWaveFile( const TCHAR * lpszFileName);
// if you invoke any member function return error/false please
// use this function to get the result
DWORD __getLastError();
//
// when the capture buffer is filled please invoke this function to 'add buffer'(Actually
// you should create two-circular buffers,when 1st buffer is filled then switch to 2st,1st
// buffer will be wrote into wav file.
//
void AddBuffer();
virtual ~ CSoundIn();
friend CSoundIn & theSoundCapture();
private :
BOOL GetBestWaveFormat(WAVEFORMATEX & waveFormatEx);
// because sound card is one and only so i must limit the number of CSoundIn object,
// but how to limit the class object nums?maybe put constructor into private scope is
// a good idea,:-)
CSoundIn();
private :
WAVEINCAPS m_WaveInDevCaps;
HWAVEIN m_WaveIn;
WAVEHDR m_WaveHeader;
WAVEFORMATEX m_WaveFormat;
UINT m_WaveInSampleRate;
int m_NbMaxSamples;
UINT m_SizeRecord;
DWORD m_dwLastError;
enum { MAX_SIZE_INPUT_BUFFER = 1 * 2 * 1024 } ; // samples * voie * size_samples
public :
SHORT InputBuffer[MAX_SIZE_INPUT_BUFFER]; // used for int FFT,many GUI application
// want to display sound peak so..
BOOL m_bTerminateThread; // to 'kill' waveCallback function
BOOL m_bImportToWaveFile;
CWaveFile m_waveFile;
} ;
} // end namespace perdubug
对于将录音保存在WAV文件的工作主要是由CwaveFile类来完成.下面是该类的定义:
//
// Encapsulates reading or writing sound data to or from a wave file
// -----------------------------------------------------------------------------
class CWaveFile
{
public :
WAVEFORMATEX * m_pwfx; // Pointer to WAVEFORMATEX structure
HMMIO m_hmmio; // MM I/O handle for the WAVE
MMCKINFO m_ck; // Multimedia RIFF chunk
MMCKINFO m_ckRiff; // Use in opening a WAVE file
DWORD m_dwSize; // The size of the wave file
MMIOINFO m_mmioinfoOut;
DWORD m_dwFlags;
BOOL m_bIsReadingFromMemory;
BYTE * m_pbData;
BYTE * m_pbDataCur;
ULONG m_ulDataSize;
CHAR * m_pResourceBuffer;
protected :
HRESULT ReadMMIO();
HRESULT WriteMMIO( WAVEFORMATEX * pwfxDest );
public :
CWaveFile();
~ CWaveFile();
HRESULT Open( LPCTSTR strFileName, WAVEFORMATEX * pwfx, DWORD dwFlags );
HRESULT OpenFromMemory( BYTE * pbData, ULONG ulDataSize,
WAVEFORMATEX * pwfx, DWORD dwFlags );
HRESULT Close();
HRESULT Read( BYTE * pBuffer, DWORD dwSizeToRead, DWORD * pdwSizeRead );
HRESULT Write( UINT nSizeToWrite, BYTE * pbData, UINT * pnSizeWrote );
DWORD GetSize();
HRESULT ResetFile();
WAVEFORMATEX * GetFormat() { return m_pwfx; } ;
} ;
1 .用VC6 ++ 建立一个MFC基于对话框的工程:MicDemo;
2 .添加我们的两个类CSoundIn,CwaveFile;
3 .当我们点击开始(Start)按钮的时候我们就要开始录音了…
void CMicDemoDlg::OnStart()
{
m_btnStart.EnableWindow(FALSE);
if (theSoundCapture().__initMic())
{
m_filePath.SetWindowText(_T( " yangchen.wav. " ));
theSoundCapture().__createOutputWaveFile(_T( " yangchen.wav " ));
if ( ! theSoundCapture().__openMic())
{
::MessageBox( this -> m_hWnd,_T( " Can not open microphone! " ), _T( " Error " ),MB_OK | MB_ICONERROR);
return ;
}
}
m_btnStop.EnableWindow(TRUE);
// 设置定时器是为了画波形用的
SetTimer( 1 , 200 /* start slow */ , NULL);
}
4 .在定时器的回调函数中画波形.
void CMicDemoDlg::OnTimer(UINT nIDEvent)
{
if (nIDEvent == 1 )
{
static const int xCon = 13 ;
static const int yCon = 13 ;
static const int wCon = 623 ;
static const int hCon = 80 ;
CClientDC dc( this );
CBitmap Bitmap;
CBitmap * pbmOld = NULL;
CDC dcMem;
dcMem.CreateCompatibleDC( & dc);
Bitmap.CreateCompatibleBitmap( & dc,wCon,hCon);
pbmOld = dcMem.SelectObject( & Bitmap);
dcMem.PatBlt( 0 , 0 ,wCon,hCon, WHITENESS);
dcMem.MoveTo( 0 ,hCon / 2 );
//
// display incomming signal--key idea!
//
for ( int x = 0 ; x < wCon; x ++ ) // display Input
{
dcMem.LineTo(x,(hCon >> 1 ) - (theSoundCapture().InputBuffer[x] >> 7 ));
}
dc.BitBlt(xCon,yCon,wCon,hCon, & dcMem, 0 , 0 , SRCCOPY);
dcMem.SelectObject(pbmOld);
dcMem.DeleteDC();
}
else
CDialog::OnTimer(nIDEvent);
}
5 .点击停止(Stop)按钮的时候停止录音和写WAV文件
void CMicDemoDlg::OnStop()
{
m_btnStop.EnableWindow(FALSE);
theSoundCapture().__closeMic();
m_btnStart.EnableWindow(TRUE);
}
看完整段代码你可能会很奇怪怎么在CmicDemoDlg中居然都没有定义一个CSoundIn对象 ?? 呵呵,原因很简单,因为设备的独占性所以在一个时刻只能有一个CSoundIn对象存在(因为CSoundIn对象需要占据录音设备),所以我们必须限制程序员生成CSoundIn对象的数量,怎么限制呢 ? 那就是把CSoundIn的构造函数放在private区域里面:
private :
BOOL GetBestWaveFormat(WAVEFORMATEX & waveFormatEx);
// because sound card is one and only so i must limit the number of CSoundIn object,
// but how to limit the class object nums?maybe put constructor into private scope is
// a good idea,:-)
CSoundIn();
这样的话就根本无法声明一个CSoundIn对象,不信你试一下在你的代码中写上:
CSoundIn soundInObj;
能编译通过吗 ? 肯定是不能,那如何调用CSoundIn的成员函数呢 ? 答案是通过一个全局函数:
// global function,:-(
// client can only through this function to use CSoundIn object
CSoundIn & theSoundCapture()
{
static CSoundIn p;
return p;
}
这时候你应该明白了为什么上面的代码中调用CSoundIn的成员函数的时候都是用theSoundCapture来做的原因了吧.
Windows环境下的麦克风录音系统相关推荐
- 硬盘克隆带linux系统,一种Windows环境下基于DiskGenius克隆Linux系统的方法及系统与流程...
本发明涉及服务器操作系统技术领域,特别是一种windows环境下基于diskgenius克隆linux系统的方法及系统. 背景技术: 服务器在测试过程中需要反复安装linux操作系统用于服务器的各种测 ...
- Windows环境下Unicode编程总结和将ANSI转换到Unicode 将Unicode转换到ANSI
Windows环境下Unicode编程总结 UNICODE环境设置 在安装Visual Studio时,在选择VC++时需要加入unicode选项,保证相关的库文件可以拷贝到system32下. UN ...
- Windows环境下在IDEA编辑器中spark开发安装步骤
以下是windows环境下安装spark的过程: 1.安装JDK(version:1.8.0.152) 2.安装scala(version:2.11/2.12) 3.安装spark(version:s ...
- 在Windows环境下搭建Android开发环境
标题:在Windows环境下搭建 Android 开发环境 作者:CrazyPebble 时间:2011年2月28日 声明:此文在参考其他网上资料以及笔者实践总结写下,一来自己可以做一些总结,二来给跟 ...
- 腾讯云CMQ消息队列在Windows环境下的使用
版权声明:本文由李少华原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/100 来源:腾云阁 https://www.qcl ...
- windows环境下如何安装memcached教程
Memcached 是一个开源免费高性能的分布式内存对象缓存系统,能够加快网站访问速度和减轻数据库压力,本文向大家介绍下windows环境下如何安装memcached. 工具/原料 memcached ...
- java 内存映射文件进程间通讯_[转]Windows环境下利用“共享内存”实现进程间通信的C/C++代码---利用CreateFileMapping和MapViewOfFile...
进程间的通信方式有很多种, 上次我们说了最傻瓜的"共享外存/文件"的方法. 那么, 在本文中, 我们即将学习"共享内存"的方式实现进程间的通信, 这是IPC最快 ...
- Windows环境下配置环境变量
安装好MySQL后,在Windows环境下配置环境变量 1)新建MYSQL_HOME系统变量 配置MySQL的安装路径:C:\Program Files\MySQL\MySQL Server 8.0 ...
- 缺少nst linux.mbr文件,用EasyBCD2.0在Windows环境下引导Linux启动
用EasyBCD2.0在Windows环境下引导Linux启动 以在Win7环境下用EasyBCD2.0.2引导Ubuntu启动为例(Ubuntu安装在Win7之前,倘若反了进不去Win7,可以用Wi ...
最新文章
- 数字图像处理中所用数学工具3---算术操作处理图像
- Python PIP Install throws TypeError: unsupported operand type(s) for -=: 'Retry' and 'int'
- C++:cin、cin.getline()、getline()的用法
- 熊猫直播P2P分享率优化(下):ASN组网
- 《 廊桥遗梦 》:用我的整个余生和全部的心来爱你 ...
- 椭圆极点极线性质_又见阿氏圆——适合作椭圆大题的小题
- php中abs,php中的abs函数怎么用
- oracle中with子句的用法(转)
- Windows下查看进程及结束进程命令
- 群晖NAS教程(二十三)、利用Docker安装mysql8,并使用ipv6和域名访问
- 自定义拍照时 拍照界面_女研究生劝父亲盖房时把围墙退后三尺,新房成网红,一天20人拍照...
- Ubuntu——.z01 .z02 .z03此类拆分后的压缩文件解压办法
- 如何配置java环境_vscode配置java环境
- 【小5聊】前端基础之上传图片等文件IE浏览器是会显示两个请求
- Struts常见错误及原因分析
- c++连通区域处理 种子生成法
- 解决SkyP2M工程常见问题所参考的博客汇总
- threejs知识点:1.模型分析
- jsx中文是什么牌子口红_jsx中文是什么牌子口红_cl口红是什么牌子 cl口红中文名字...
- 马云等10位大佬心酸往事:9天9夜未睡、装姑娘陪聊、遭人追杀、一夜白头…
热门文章
- 双十一销量预测_天猫双十一数据竟然可以预测?
- windows解决error: Microsoft Visual C++ 9.0 is required. Get it from http://aka.ms/vcpython27
- 基于天池淘宝母婴用品数据的可视化分析
- WinForm实现管理员权限运行的三种方式
- TableEdit UI
- 百度地图API (1):往地图中添加标注点
- 儿童100以内的加减乘除法游戏训练c语言,一百以内的加减乘除法游戏....
- 代码实现pe文件图标替换
- painter打不开问题的解决
- 摄像头工作原理学习总结