huyc

导航

基于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(&params);
    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);
}

posted on 2012-10-26 16:44  huyc  阅读(6022)  评论(0编辑  收藏  举报