声卡 (Sound Card)也叫音频卡,是计算机多媒体系统中最基本的组成部分,是实现声波/数字信号相互转换的一种硬件。
声卡的基本功能是把来自话筒、磁带、光盘的原始声音信号加以转换,输出到耳机、扬声器、扩音机、录音机等声响设备,或通过音乐设备数字接口(MIDI)发出合成乐器的声音。
声卡其实说白了,就是:
ADC(模数转换器)-mic;通过麦克风采集到的模拟信号,转换成对应的数字信号。
DAC(数模转换器)-speaker。将从内存中读取到的音频文件数据,数字信号转换为模拟信号从喇叭播放出去。
下图为电脑端的声卡显示
表示每秒采集多少次声音数据,以秒为单位,大部分会将其转换为ms(毫秒)计算.
采样率反应了 数字信号对模拟信号的还原度。
为了更加清晰的理解采样率的概念,我们贴出两个不同采样率的正弦波。
如下图:音频1 ,48KHz; 音频2 ,384KHz。
但是,改图并不能看出采样点不同的区别。我们往下看。
将波形放大——,便有了下面这幅图。
在红色的竖线范围内,是不是能够明显的看到384KHz的采样点非常密集,远远超过48KHz的采样点。是不是能更加真实的反应出模拟信号的正弦波了。
表示每次采集声音数据的大小,单位为位(bit)。
音频的比特深度即采样精度是与音频文件的动态范围相挂钩的,说得直白一点,就是音频的声音大小的范围。
我们一般将数字音频的电平的峰值定为0dBFS,正常的电平都是0dBFS以下的负值。
以16bit的格式为例,该格式可取的动态范围为2的16次方即65536个单位,换算成dB,即20×log(65536)≈96dB。
也就是说,这个格式的动态范围为96dB。值得一提的是,以往实体唱片的CD格式都是采用16bit的采样精度的。
那么,其他比特深度的动态范围的计算也都是同理了。通过公式20×log(2^n)我们可以得出24bit和32bit的动态范围分别为144dB和193dB(取整数)。
位深度决定动态范围。采样声波时,为每个采样指定最接近原始声波振幅的振幅值。较高的位深度可提供更多可能的振幅值,产生更大的动态范围、更低的噪声基准和更高的保真度。
一般来说 PCM 数据中的波形幅值越大,代表音量越大,对于 PCM 音频数据而言,它的幅值(即该采样点采样值的大小)代表音量的大小。如果我们需要降低某个声道的音量,可以通过减小某个声道的数据的值来实现降低某个声道的音量。
Frame是一个单位,用来描述数据量的多少。1单位的Frame = 1个采样点的字节数×声道数。
音频帧的播放时间
= 采集一帧的时间
= 一个帧对应的采样样本的个数/采样频率
= period_size / sample
将一段mic+ref的 48K 16bit 4ch的音频原始数据(其中ch1、ch2为mic音;ch3、ch4位ref音)进行音量调制。
也就是说自定义 调整音频数据的音量大小。
“程序员之间最好的沟通方式就是 给我看你的代码”。
static inline int16_t clamp16(int sample)
{
if ((sample>>15) ^ (sample>>31))
sample = 0x7FFF ^ (sample>>31);
return sample;
}
用于数据源或者重要转换节点处, 取出该节点的音频数据以文件的形式
void dump_data(const void *buffer, size_t bytes, char* str)
{
FILE *fd = NULL;
char name[1024];
snprintf(name, 1024, "/data/misc/audioserver/audio_dump_48k_16bit_%s.pcm", str);
fd = fopen(name, "ab+");
if (fd == NULL) {
ALOGE("open %s fail,(%s)%d, maybe adb shell touch %s",
name, strerror(errno), errno, name);
return;
}
fwrite(buffer, bytes, 1, fd);
ALOGD("write %s -> %zu bytes", name, bytes);
fclose(fd);
}
void yy_audio_tuning(void)
{
char* src = NULL;
int32_t src_bytes = 1024; //一包数据的大小
pcm_read(pcm->alsa_pcm, src, src_bytes); //读取1K字节数据,写进src地址中
char prop_value[100];
int16_t* src_buff = (int16_t*)src; //将char型的音频数据转化为int型
if (property_get("audiohw.in.micvolume", prop_value, NULL) > 0) {
float volume = atof(prop_value);
int frame_size = src_bytes / 8; //16bit 4ch,得到1K字节数据有多少 frames
int16_t* des = src_buff;
AALOGI("(+)%s, micvolume---- volume : %f", __func__, volume);
for (int i = 0; i < frame_size; i++) {
*des++ = clamp16(((int32_t)(*(src_buff + i * 4) * volume))); // 1ch: mic1
*des++ = clamp16(((int32_t)(*(src_buff + 1 + i * 4) * volume))); // 2ch: mic2
des += 2;
}
}
if (property_get("audiohw.in.refvolume", prop_value, NULL) > 0) {
float volume = atof(prop_value);
int frame_size = src_bytes / 8; //16bit 4ch
int16_t* des = src_buff;
AALOGI("(+)%s, refvolume---- volume : %f", __func__, volume);
for (int i = 0; i < frame_size; i++) {
des += 2;
*des++ = clamp16(((int32_t)(*(src_buff + 2 + i * 4) * volume))); // 3ch: ref1
*des++ = clamp16(((int32_t)(*(src_buff + 3 + i * 4) * volume))); // 4ch: ref2
}
}
dump_data(src, src_bytes, "4ch_capture_input");
pcm_write(src, src_bytes);
}
音频数据增益前后对比:
对ref音量增益 *10。执行命令:setprop audiohw.in.refvolume 10
采用淡进淡出的处理,也就是打开的时候,逐渐增大音量,关断的时候,逐渐减小音量。能够防止POP音的产生。
fade-in的波形:渐渐进入。
至于代码上的处理,则就比较简单了。对原始音频数据进行1/100,2/100,3/100…99/100等差比例的形式进行缓慢增加。
uint32_t Fader_In(void* buffer)
{
int* pbuf = (int*)buffer;
static int stepPosition = 0;
int mFadeInTime = 16*7; //mFadeInTime 为自己定义的,可以理解为 一段数据从1/100 -> 99/100的 fadein的时间
int frameCont = mFadeInTime / 16 * 768; // 对于48K音频来说,假设alsa的一包数据为 16ms data = 768 frame
double step = 1.00 / frameCont;
if (stepPosition >= frameCont) {
return 0;
}
for (int i = 0; i < 768; i++) {
if (stepPosition < frameCont) {
double stepTemp = stepPosition * step;
double bufTemp = pbuf[i] * stepTemp;
pbuf[i] = (int)bufTemp;
stepPosition++;
}
else {
break;
}
}
return 0;
}