基于Linux 2.6内核ALSA架构的PCM混音demo
一个混音例程,多声道混音成单声道,录制下了原始声音和混音之后的声音。
混音之后的声音是8kHz,16bit,带符号单声道的声音。
#define ALSA_PCM_NEW_HW_PARAMS_API #include <alsa/asoundlib.h> #include <alloca.h> #include <stdio.h> void mixchannel(FILE *fp, const snd_pcm_channel_area_t *areas, unsigned int chs, snd_pcm_uframes_t offset, snd_pcm_uframes_t frames, snd_pcm_format_t fmt, unsigned int step); int main(int argc, char *argv[]) { const char *dev = "hw:0,0"; snd_pcm_t *handle; snd_pcm_hw_params_t *params; snd_pcm_format_t fmt = SND_PCM_FORMAT_S16_LE; unsigned int channel = 2; unsigned int rate = 44100;//源采样率 unsigned int drate = 8000;//目标采样率 unsigned int step; unsigned int phbits; unsigned int fmtbits; snd_pcm_uframes_t periods; FILE *fp1, *fp2; int rval; if (argc != 3) { printf("usage: %s raw.file, mono.file.\n", argv[0]); return 0; } fp1 = fopen(argv[1], "w");//原始流 fp2 = fopen(argv[2], "w");//混音之后的流 if (!fp1 || !fp2) { printf("file open error!\n"); return -1; } rval = snd_pcm_open(&handle, dev, SND_PCM_STREAM_CAPTURE, 0); if (rval < 0) { printf("open failed!(%s).\n", snd_strerror(rval)); return -1; } snd_pcm_hw_params_alloca(¶ms); snd_pcm_hw_params_any(handle, params); rval = snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_MMAP_INTERLEAVED); if (rval < 0) { printf("set access failed!(%s).\n", snd_strerror(rval)); return -1; } rval = snd_pcm_hw_params_set_format(handle, params, fmt); if (rval < 0) { printf("set format failed!(%s).\n", snd_strerror(rval)); return -1; } rval = snd_pcm_hw_params_set_channels(handle, params, channel); if (rval < 0) { printf("set channel(%u) failed!(%s).\n", channel, snd_strerror(rval)); return -1; } rval = snd_pcm_hw_params_set_rate_near(handle, params, &rate, 0); if (rval < 0) { printf("set rate(%u) failed!(%s).\n", rate, snd_strerror(rval)); return -1; } rval = snd_pcm_hw_params(handle, params); if (rval < 0) { printf("set params failed!(%s).\n", snd_strerror(rval)); return -1; } snd_pcm_hw_params_get_format(params, &fmt); phbits = snd_pcm_format_physical_width(fmt); fmtbits = snd_pcm_format_width(fmt); snd_pcm_hw_params_get_period_size(params, &periods, 0); printf("capture.\n"); printf("rate %u -> %u.\n", rate, drate); printf("channel %d -> 1.\n", channel); printf("fmt:%s, bytes: %u, bits:%u.\n", snd_pcm_format_name(fmt), phbits/8, fmtbits); rval = snd_pcm_start(handle); if (rval < 0) { printf("start failed!(%s).\n", snd_strerror(rval)); return -1; } step = rate/drate; while (1) { snd_pcm_uframes_t offset, frames; snd_pcm_sframes_t avail, commits; snd_pcm_state_t state = snd_pcm_state(handle); if (state == SND_PCM_STATE_XRUN) { rval = snd_pcm_prepare(handle); if (rval < 0) { printf("strong error!(%s).\n", snd_strerror(rval)); break; } rval = snd_pcm_start(handle); if (rval < 0) { printf("start error!(%s)\n", snd_strerror(rval)); break; } } avail = snd_pcm_avail_update(handle); if (avail < 0) { continue; } if (avail < periods) { rval = snd_pcm_wait(handle, -1); if (rval < 0) { continue; } } //printf("avail:%lu.\n", avail); while (avail >= periods) { frames = periods; const snd_pcm_channel_area_t *areas; rval = snd_pcm_mmap_begin(handle, &areas, &offset, &frames); //交错模式下的PCM数据保存在同一个缓冲区内,可以直接写入文件 fwrite(areas->addr + areas->step/8*offset, frames, areas->step/8, fp1); mixchannel(fp2, areas, channel, offset, frames, fmt, step); commits = snd_pcm_mmap_commit(handle, offset, frames); //printf("commit, commits:%ld.\n", commits); if (rval < 0) { break; } if (commits < 0 || commits != frames) { break; } avail -= periods; } } fclose(fp1); fclose(fp2); snd_pcm_drop(handle); snd_pcm_close(handle); return 0; } void mixchannel(FILE *fp, const snd_pcm_channel_area_t *areas, unsigned int chs, snd_pcm_uframes_t offset, snd_pcm_uframes_t frames, snd_pcm_format_t fmt, unsigned int step) { static char buf[1024*1024]; char *bp = buf; const char *smp; int fbytes = snd_pcm_format_physical_width(fmt)/8; int fbits = snd_pcm_format_width(fmt); int bigendian = snd_pcm_format_big_endian(fmt); int sfmt = snd_pcm_format_signed(fmt); unsigned long long umask, val; long long smask; for (int i = rand()%step; i < frames; i += step) { umask = 0; for (int ch = 0; ch < chs; ++ch) { smp = areas[ch].addr + areas[ch].first/8 + areas[ch].step/8*(offset+i); val = 0; for (int j = 0; j < fbytes; ++j) { if (bigendian) { val += ((unsigned char)smp[j] >> ((fbytes-j-1)*8)); } else { val += ((unsigned char)smp[j] << (j*8)); } } if (sfmt) { val ^= (1u<<(fbits-1)); //转换值域,带符号转无符号 } umask += val; //混声道 } umask /= chs; //如果不取均值,则是噪音,但是取均值之后,声音会变弱,原理上应该是不需要取均值的,也许是哪里出了其他问题 umask &= 0xffff; //截断16bit smask = umask-0x8000u;//转换值域,无符号转成带符号 *bp = smask & 0xff; *(bp+1) = (smask >> 8) & 0xff; bp += 2; } fwrite(buf, 2, frames/step, fp); }