基于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);

}

你可能感兴趣的:(linux)