文章目录

  • 概述
    • 输入
    • 输出
    • 混音算法
    • 注意
  • 代码
    • 数据类型
    • 头文件
    • 源文件

概述

参考:
归一化、叠加+均值
相加相乘的混音算法

实现了PCM混音算法。以5个wav混音,其中每个wav长度均为2205个点,为例。
数据格式:以16位有符号数short存储wav音频文件。(#define AUDIO_DATA_TYPE short)

输入

allMixingSounds为5个vector,其中每个vector为2205个音频点。

输出

__pRawDataBuffer为1个vector,其中为2205个音频点。

混音算法

  1. 时间片切割:单点、分段
  2. 简单叠加
  3. 叠加+归一化

注意

音频信号有正负。
叠加时要用更长的数据长度来存储,防止溢出。(有符号数超过上限,如short的音频值超过32767或小于-32768)

代码

数据类型

#define AUDIO_DATA_TYPE short        // 为方便修改,在AudioDevice.h的顶部设定数据类型宏定义// DWORD(unsigned long)? long? short?
#define AUDIO_DATA_TYPE_MAX 32767   // 2^15(short)
#define AUDIO_DATA_TYPE_MIN -32768#define WIDEN_TEMP_TYPE int           // 4字节有符号的中间变量,用于混音时防止溢出

头文件

class CAudioMix{public:// 切割时间片,单点混音void TimeSliceByPoint(    vector<vector<AUDIO_DATA_TYPE>> allMixingSounds,DWORD                           RawDataCnt,vector<AUDIO_DATA_TYPE>*       __pRawDataBuffer);// 切割时间片,分段混音void TimeSliceBySection(vector<vector<AUDIO_DATA_TYPE>>   allMixingSounds,DWORD                           RawDataCnt,vector<AUDIO_DATA_TYPE>*       __pRawDataBuffer);// CombinePointsToOneWay1void CombinePointsToOneWay1(vector<vector<AUDIO_DATA_TYPE>>  allMixingSounds,DWORD                           RawDataCnt,vector<AUDIO_DATA_TYPE>*       __pRawDataBuffer);// MixNewLCvoid CombinePointsToOneNewLC(vector<vector<AUDIO_DATA_TYPE>>   allMixingSounds,DWORD                           RawDataCnt,vector<AUDIO_DATA_TYPE>*       __pRawDataBuffer);// 直接简单地叠加void CAudioSource::CAudioMix::MixSoundsBySimplyAdd(vector<vector<AUDIO_DATA_TYPE>>  allMixingSounds,DWORD                           RawDataCnt,vector<AUDIO_DATA_TYPE>*       __pRawDataBuffer);// 叠加,然后取均值void CAudioSource::CAudioMix::MixSoundsByMean(vector<vector<AUDIO_DATA_TYPE>>   allMixingSounds,DWORD                           RawDataCnt,vector<AUDIO_DATA_TYPE>*       __pRawDataBuffer);// 叠加,然后归一化void CAudioSource::CAudioMix::AddAndNormalization(vector<vector<AUDIO_DATA_TYPE>>   allMixingSounds,DWORD                           RawDataCnt,vector<AUDIO_DATA_TYPE>*       __pRawDataBuffer);};

源文件

#include "StdAfx.h"
#include "AudioDevice.h"
#include <stdexcept>using namespace std;//---------------------------------------------------------------------------------------------
// TimeSliceByPoint()
// 切割时间片,单点混音,wav = 3、4个时,效果好一点
//---------------------------------------------------------------------------------------------
void CAudioSource::CAudioMix::TimeSliceByPoint(vector<vector<AUDIO_DATA_TYPE>>  allMixingSounds,DWORD                           RawDataCnt,vector<AUDIO_DATA_TYPE>*       __pRawDataBuffer)
{for (int idxInEachSound = 0; idxInEachSound < RawDataCnt; idxInEachSound += allMixingSounds.size()){for (int soundIdx = 0; soundIdx < allMixingSounds.size(); ++soundIdx){if (idxInEachSound + soundIdx < RawDataCnt)                  // 即if(__pRawDataBuffer.size() < RawDataCnt),但这样时间复杂度小__pRawDataBuffer->push_back(allMixingSounds[soundIdx][idxInEachSound + soundIdx]);}}
}//---------------------------------------------------------------------------------------------
// TimeSliceBySection()
// 切割时间片,分段混音,wav=2时,效果好一些
//---------------------------------------------------------------------------------------------
void CAudioSource::CAudioMix::TimeSliceBySection(vector<vector<AUDIO_DATA_TYPE>>    allMixingSounds,DWORD                           RawDataCnt,vector<AUDIO_DATA_TYPE>*       __pRawDataBuffer)
{const int SECTION_LEN = allMixingSounds.size();       // 2个wav时,SECTION_LEN=2是最好的,故推测SECTION_LEN = allMixingSounds.size()是最好的,晚上测试//   const int SECTION_LEN = 50;int idxInEachSound = 0;while (idxInEachSound < RawDataCnt){for (int soundIdx = 0; soundIdx < allMixingSounds.size(); ++soundIdx){for (int idxInEachSection = 0; idxInEachSection < SECTION_LEN; ++idxInEachSection) // wav1先一次性填充满SECTION_LEN个点,wav2再重复上述过程{if (__pRawDataBuffer->size() < RawDataCnt)                 // 即if(__pRawDataBuffer.size() < RawDataCnt),但这样时间复杂度小 // idxInEachSound + soundIdx < RawDataCnt__pRawDataBuffer->push_back(allMixingSounds[soundIdx][idxInEachSound + idxInEachSection]);}idxInEachSound += SECTION_LEN;}}
}//---------------------------------------------------------------------------------------------
// CombinePointsToOneWay1
// http://blog.sina.com.cn/s/blog_4d61a7570101arsr.html
// 通过“将多点组合为一个点”的方式混音
//---------------------------------------------------------------------------------------------
void CAudioSource::CAudioMix::CombinePointsToOneWay1(vector<vector<AUDIO_DATA_TYPE>>    allMixingSounds,DWORD                           RawDataCnt,vector<AUDIO_DATA_TYPE>*       __pRawDataBuffer)
{// 初始化中间变量WIDEN_TEMP_TYPE tempMul = 1;WIDEN_TEMP_TYPE tempSum = 0;for (int i = 0; i < RawDataCnt; ++i){// 复位中间变量tempMul = 1;tempSum = 0;// 求中间变量for (int wavNum = 0; wavNum < allMixingSounds.size(); ++wavNum){tempMul *= allMixingSounds[wavNum][i];tempSum += allMixingSounds[wavNum][i];}// 通过“将多点组合为一个点”的方式混音WIDEN_TEMP_TYPE mixedTempData = tempSum - (tempMul >> 0x10);AUDIO_DATA_TYPE mixedData = static_cast<AUDIO_DATA_TYPE>(mixedTempData);// 防止上下溢出if (mixedData > AUDIO_DATA_TYPE_MAX)mixedData = AUDIO_DATA_TYPE_MAX;else if (mixedData < AUDIO_DATA_TYPE_MIN)mixedData = AUDIO_DATA_TYPE_MIN;__pRawDataBuffer->push_back(mixedData);}
}//---------------------------------------------------------------------------------------------
// MixNewLC
// http://blog.csdn.net/xuheazx/article/details/39523721 的方法3
//---------------------------------------------------------------------------------------------
void CAudioSource::CAudioMix::CombinePointsToOneNewLC(vector<vector<AUDIO_DATA_TYPE>>   allMixingSounds,DWORD                           RawDataCnt,vector<AUDIO_DATA_TYPE>*       __pRawDataBuffer)
{// 初始化中间变量WIDEN_TEMP_TYPE tempMul = 1;WIDEN_TEMP_TYPE tempSum = 0;WIDEN_TEMP_TYPE mixedTempData;int howManyPointsArePos = 0;int sz = allMixingSounds.size();for (int i = 0; i < RawDataCnt; ++i){// 复位中间变量tempMul = 1;tempSum = 0;// 求中间变量howManyPointsArePos = 0;                                // 统计每个点是不是都是正数for (int wavNum = 0; wavNum < sz; ++wavNum){tempMul *= allMixingSounds[wavNum][i];tempSum += allMixingSounds[wavNum][i];if (allMixingSounds[wavNum][i] < 0)++howManyPointsArePos;}// 混音if (howManyPointsArePos == sz)mixedTempData = tempSum - (tempMul / -(pow(2, 16 - 1) - 1));elsemixedTempData = tempSum - (tempMul / (pow(2, 16 - 1) - 1));// 防止上下溢出if (mixedTempData > AUDIO_DATA_TYPE_MAX)mixedTempData = AUDIO_DATA_TYPE_MAX;else if (mixedTempData < AUDIO_DATA_TYPE_MIN)mixedTempData = AUDIO_DATA_TYPE_MIN;AUDIO_DATA_TYPE mixedData = static_cast<AUDIO_DATA_TYPE>(mixedTempData);__pRawDataBuffer->push_back(mixedData);}
}//---------------------------------------------------------------------------------------------
// MixSoundsBySimplyAdd()// 直接简单地叠加
// 由于叠加可能会超出上限32767导致溢出,因此需要将"short"(有符号2字节)用更大的范围"int"(有符号4字节)来表示。
// 效果很好
//---------------------------------------------------------------------------------------------
void CAudioSource::CAudioMix::MixSoundsBySimplyAdd(vector<vector<AUDIO_DATA_TYPE>>  allMixingSounds,DWORD                           RawDataCnt,vector<AUDIO_DATA_TYPE>*       __pRawDataBuffer)
{WIDEN_TEMP_TYPE Sum = 0;                                  // 用更大的范围来表示(用有符号的int,而不要用无符号的DWORD)for (int i = 0; i < RawDataCnt; ++i){Sum = 0;                                               // 复位叠加的值for (int wavNum = 0; wavNum < allMixingSounds.size(); ++wavNum){Sum += allMixingSounds[wavNum][i];}// 叠加之后,会溢出if (Sum > AUDIO_DATA_TYPE_MAX)Sum = AUDIO_DATA_TYPE_MAX;else if (Sum < AUDIO_DATA_TYPE_MIN)Sum = AUDIO_DATA_TYPE_MIN;__pRawDataBuffer->push_back(AUDIO_DATA_TYPE(Sum));        // 把int再强制转换回为short}
}//---------------------------------------------------------------------------------------------
// MixSoundsByMean()// 叠加,然后取均值
// 由于叠加可能会超出上限32767导致溢出,因此需要将"short"(有符号2字节)用更大的范围"int"(有符号4字节)来表示。
// 缺点:wav文件越多,混音后的值越小
//---------------------------------------------------------------------------------------------
void CAudioSource::CAudioMix::MixSoundsByMean(vector<vector<AUDIO_DATA_TYPE>>   allMixingSounds,DWORD                           RawDataCnt,vector<AUDIO_DATA_TYPE>*       __pRawDataBuffer)
{WIDEN_TEMP_TYPE Sum = 0;                                  // 用更大的范围来表示(用有符号的int,而不要用无符号的DWORD)WIDEN_TEMP_TYPE Mean = 0;int sz = allMixingSounds.size();for (int i = 0; i < RawDataCnt; ++i){// 复位叠加的值Sum = 0;Mean = 0;for (int wavNum = 0; wavNum < allMixingSounds.size(); ++wavNum){Sum += allMixingSounds[wavNum][i];}Mean = Sum / sz;// 叠加之后,会溢出if (Mean > AUDIO_DATA_TYPE_MAX)Mean = AUDIO_DATA_TYPE_MAX;else if (Mean < AUDIO_DATA_TYPE_MIN)Mean = AUDIO_DATA_TYPE_MIN;__pRawDataBuffer->push_back(AUDIO_DATA_TYPE(Mean));        // 把int再强制转换回为short}
}//---------------------------------------------------------------------------------------------
// AddAndNormalization()// 叠加,然后归一化混音
// 由于叠加可能会超出上限32767导致溢出,因此需要将"short"(有符号2字节)用更大的范围"int"(有符号4字节)来表示。
// 效果很好
//---------------------------------------------------------------------------------------------
void CAudioSource::CAudioMix::AddAndNormalization(vector<vector<AUDIO_DATA_TYPE>>   allMixingSounds,DWORD                           RawDataCnt,vector<AUDIO_DATA_TYPE>*       __pRawDataBuffer)
{WIDEN_TEMP_TYPE Sum = 0;                                  // 用更大的范围来表示(用有符号的int,而不要用无符号的DWORD)double decayFactor = 1;                                       // 衰减因子(防止溢出)for (int i = 0; i < RawDataCnt; ++i){Sum = 0;                                             // 复位叠加的值for (int wavNum = 0; wavNum < allMixingSounds.size(); ++wavNum){Sum += allMixingSounds[wavNum][i];}Sum *= decayFactor;                                        // 将衰减因子作用在叠加的音频上// 计算衰减因子// 1. 叠加之后,会溢出,计算溢出的倍数(即衰减因子)if (Sum > AUDIO_DATA_TYPE_MAX){decayFactor = static_cast<double>(AUDIO_DATA_TYPE_MAX) / static_cast<double>(Sum);    // 算大了,就用小数0.8衰减Sum = AUDIO_DATA_TYPE_MAX;}else if (Sum < AUDIO_DATA_TYPE_MIN){decayFactor = static_cast<double>(AUDIO_DATA_TYPE_MIN) / static_cast<double>(Sum);   // 算小了,就用大数1.2增加Sum = AUDIO_DATA_TYPE_MIN;}// 2. 衰减因子的平滑(为了防止个别点偶然的溢出)if (decayFactor < 1){decayFactor += static_cast<double>(1 - decayFactor) / static_cast<double>(32);}__pRawDataBuffer->push_back(AUDIO_DATA_TYPE(Sum));      // 把int再强制转换回为short}
}

##调用

 // 不同的混音方法:由allMixingSounds混音得到__RawDataBuffer
//  Mix.TimeSliceByPoint(allMixingSounds, RawDataCnt, &__RawDataBuffer);                        // 切割时间片,单点混音
//  Mix.TimeSliceBySection(allMixingSounds, RawDataCnt, &__RawDataBuffer);                      // 切割时间片,分段混音
//  Mix.CombinePointsToOneWay1(allMixingSounds, RawDataCnt, &__RawDataBuffer);                  // “n点合1”混音1
//  Mix.CombinePointsToOneNewLC(allMixingSounds, RawDataCnt, &__RawDataBuffer);                 // “n点合1”混音2
//  Mix.MixSoundsBySimplyAdd(allMixingSounds, RawDataCnt, &__RawDataBuffer);                    // 简单地叠加,效果最好
//  Mix.MixSoundsByMean(allMixingSounds, RawDataCnt, &__RawDataBuffer);                         // 简单地叠加,然后取均值Mix.AddAndNormalization(allMixingSounds, RawDataCnt, &__RawDataBuffer);                        // 叠加,然后归一化

PCM混音算法 C++实现 (包括归一化加权算法,时间片切割算法,幅值简单叠加算法)相关推荐

  1. ffmpeg pcm混音

    视频会议中经常需要处理的场景有多路音频混音,那么混音有很多种算法有比较主流的有归一权重.叠加均值.平均权重等方法:如果公司要开发生产级别的音频混合要的算法可能会更加多,可以找算法公司购买. ffmpe ...

  2. Android视频编辑器(五)音频编解码、从视频中分离音频、音频混音、音频音量调节等

    前言 这篇博客,主要讲解的是android端的音频处理,在开发Android视频编辑器的时候,有一个非常重要的点就是音频的相关处理.比如如何从视频中分离音频(保存为mp3文件),然后分离出来的音频如何 ...

  3. ffmpeg混音(将多个声音合成一个)命令

    ffmpeg命令中可以使用filter amix实现这个功能. 官方文档 http://ffmpeg.org/ffmpeg-filters.html  6.8 amix  Mixes multiple ...

  4. Android音视频【十一】视频混音

    人间观察 其实人这一辈子 真的遇不到几个真心对你好爱你的人 如果有幸能牵手 那就别并肩 好好的 别老是冷冰冰 说反话 简介 短视频的编辑功能有很多,比如:添加背景音乐,剪切,拼接视频/音频,特效,贴纸 ...

  5. java 混音_Android中一种效果奇好的混音方法详解

    初识音频 从初中物理上我们就学到,声音是一种波.计算机只能处理离散的信号,通过收集足够多的离散的信号,来不断逼近波形,这个过程我们叫做采样.怎么样才能更好的还原声音信息呢?这里很自然引出两个概念了. ...

  6. Android上一种效果奇好的混音方法介绍

    本文将对几种音频混音的方法进行详细的介绍和比较,读完之后你应该可以对混音有个基本的认识,针对不同情形知道应该采用哪种具体的处理方法了. 如果对音频的一些基础知识还不是很了解的建议先去阅读一下上一篇文章 ...

  7. 算法:三种简单排序算法

    排序算法比較常见的有:冒泡排序.简单选择排序.直接插入排序:希尔排序.堆排序.归并排序和高速排序算法等. 今天先学习一下前面三种比較简单的算法.排序的相关概念: ①排序的稳定性:两个或多个元素相等.排 ...

  8. 几个常见的简单的算法(暴力法,递推法,枚举法,递归法,分治法,贪心法,回溯法)

    最近在学习算法相关知识. 通过买的视频教程了解到了一些简单的算法,为了加深感悟,同时也为了理解,将这几个常见的算法的定义进行记录. 算法是程序的灵魂,也可以认为是程序最重要的部分. 在通过算法解决问题 ...

  9. java归一化混音_改进型归一化混音算法

    改进型归一化混音算法 linear PCM格式的音频混音 音频混音的原理:量化的语音信号的叠加等价于空气中声波的叠加. 反应到PCM音频数据上,也就是把同一个声道的数值进行简单的相加,但是这样同时会产 ...

最新文章

  1. invalidate
  2. BeautifulSoup 一行代码获取今日日期,与smtplib结合
  3. Windows环境下smarty安装简明教程
  4. 零拷贝实现高效的数据传输 -Efficient data transfer through zero copy
  5. java线程安全(一)
  6. hashtable允许null键和值吗_【29期】Java集合框架 10 连问,你有被问过吗?
  7. 关于Apache2.4版本的phpMyAdmin的配置
  8. python高段编程_25个有用的 Python 代码段
  9. Amlogic Linux系列(三) 视频解码分析
  10. java递归生成无限层级的树--分类管理
  11. 二项式展开推广与微积分的关系
  12. 组网方案设计,运用Mesh组网实现无缝漫游!
  13. IP代理池检测代理可用性
  14. luoguP3353 在你窗外闪耀的星星
  15. 将hexo博客部署到阿里云服务器
  16. 紫晶存储2017年上半年营收6012万元 净赚639万元
  17. PERL常见问题解答--FAQ(4)--Data: Strings
  18. 基于OpenCV的鱼眼相机畸变矫正(含代码)
  19. 如何彻底删除adsafe
  20. JS 倒计时展示小工具

热门文章

  1. CityScapes数据集简介与数据处理和精度指标
  2. 系统启动时显示“NTLDR is missing”而无法进入系统的解决方法
  3. java mp3 信息_android,java获取MP3文件信息(作者,专辑等)
  4. 手持话筒测试软件,无线麦克风有哪些测试方法
  5. Github Copilot 和 Github Copilot Nightly 的区别
  6. IBM Lenovo V7000存储服务器维修记录
  7. 系统安全加固4——输入密码错误5次锁定账户900秒
  8. thebrain8破解
  9. 【每日一题】(D0807)悉尼歌剧院 网格
  10. CAD怎么转PDF?转换途径说明