PCM音频文件的频率F、音量V、播放速度Speed的改变,C++实现

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/daska110/article/details/80322510

本文完整代码下载

实现音频改变(频率F、音量V、播放速度Sped)
通过ChangeVolumeFreq()实现

代码

头文件

class CAudioChange
    {
    public:
        void ChangeVolumeFreq(  CAudioSound*             pSound,
                                int                      soundListSize,
                                DWORD                    eachSoundLen,
                                DWORD                    RawDataCnt,
                                double                   SpeedFactor,
                                vector<AUDIO_DATA_TYPE>* __pEachMixingSound);

    private:
        // 两点式直线方程,既(y-y1)/(x-x1)=(y2-y1)/(x2-x1),整理得(y-y1)/(y2-y1)=(x-x1)/(x2-x1)
        // 供F < 1时,线性拟合用
        vector<AUDIO_DATA_TYPE> TwoPointLinearEquation( int x1, AUDIO_DATA_TYPE y1,
                                                        int x2, AUDIO_DATA_TYPE y2,
                                                        int linearFittingNum);
        // F 小于1时,归一化. 
        void FreqNormalizations(double& F);
    };

源文件

#include "StdAfx.h"
#include "AudioDevice.h"
#include <stdexcept>


/******************************************************************
本文件实现音频改变(频率F、音量V、播放速度Sped)
通过ChangeVolumeFreq()实现
*******************************************************************/

using namespace std;
//---------------------------------------------------------------------------------------------
// ChangeVolumeFreq()

// 输入:一个原始的整段wav(很长)  :CAudioSound*              pSound)
// 输出:一个改变后的wav(2205个点):vector<AUDIO_DATA_TYPE>* __pEachMixingSound

// 变F算法过程:根据F值,取某长度wav,并压缩或拟合为2205个点
// 变V算法过程:音频点倍增V倍
// 变播放速度:在原始wav中,间隔SpeedFactor个点,取点
//---------------------------------------------------------------------------------------------
void CAudioSource::CAudioChange::ChangeVolumeFreq(CAudioSound*           pSound,
    int                      soundListSize,
    DWORD                    eachSoundLen,
    DWORD                    RawDataCnt,
    double                   SpeedFactor,
    vector<AUDIO_DATA_TYPE>* __pEachMixingSound)
{
    int          SoundLen = pSound->AudioDataCnt;                           // 当前wav文件的长度
    double       F = pSound->Freq;
    FreqNormalizations(F);                                                  // F 小于1时,归一化.
    double       V = pSound->Volume;                                        // 在这里改变音量*10,*0.3
    DWORD       *pCurPositionInAudio = &(pSound->CurPositionInAudio);       // 从 已写入该wav的位置 开始继续存入缓冲区

    int beginIdx = pSound->CurPositionInAudio % SoundLen;
    int endIdx = (pSound->CurPositionInAudio + static_cast<int>(RawDataCnt * (F))) % SoundLen;      // 从起始点之后的RawDataCnt(即缓冲区长度,为2205)个点, 因为F < 0时会变为static_cast<int>(F) = 0,所以先乘再转换
    int audioIdx;

    if (F >= 1) // F若为3倍,则取3倍的数据,然后压缩到1倍数据的长度
    {
        for (audioIdx = beginIdx; audioIdx < endIdx;)
        {
            // 音量有正有负:必须是short(16位有符号数),这样才能把原始PCM数据里的负数读为负数(比如-1若放入DWORD则为65535,若放入short则为-1)
            // 数据类型由Sound.AudioData的DWORD 转为 AUDIO_DATA_TYPE的__RawDataBuffer
            // changedData是改变了数据类型(在-32767~+32767之间) + 音量、频率 的数据
            AUDIO_DATA_TYPE changedData = AUDIO_DATA_TYPE(pSound->AudioData[audioIdx]) * V;
            __pEachMixingSound->push_back(changedData);

            audioIdx += static_cast<int>(F);
            audioIdx %= SoundLen;                               // 这样若该wav播到末尾了,可以继续循环播放
        }
    }
    else        // F < 1时,用线性拟合(在原始音频中隔1个点取数,然后在上述2点之间填充linearFittingNum - 2个点,填充的办法是线性拟合)
    {
        int linearFittingNum = static_cast<int>(1.0 / F);       // 填充linearFittingNum个点的数据,linearFittingNum必须>=2,即F必须<=0.5才会填充。F在0.5~1之间不填充点
        for (audioIdx = beginIdx; audioIdx < endIdx;)
        {
            // 在原始音频中隔1个点取数时,记录出上一点和这一点的changedData值, 再算出两点斜率,根据斜率公式可预测出之后的linearFittingNum个点的值
            AUDIO_DATA_TYPE changedJumpIntervalData_pre = AUDIO_DATA_TYPE(pSound->AudioData[audioIdx]) * V;     // 在原始音频中隔1个点取数时的前一个点                                               // 不是隔linearFittingNum个点取数,而是隔1个点取数
            audioIdx = (audioIdx + 1) % SoundLen;
            AUDIO_DATA_TYPE changedJumpIntervalData_next = AUDIO_DATA_TYPE(pSound->AudioData[audioIdx]) * V;    // 在原始音频中隔1个点取数时的后一个点

            // 现在是线性拟合(在原始音频中隔1个点取数,然后在上述2点之间填充linearFittingNum - 2个点,填充的办法是线性拟合)
            vector<AUDIO_DATA_TYPE> linerFitVals
                = TwoPointLinearEquation(0, changedJumpIntervalData_pre,                    // 根据 在原始音频中隔1个点取的数,线性拟合填充的数
                0 + linearFittingNum, changedJumpIntervalData_next,
                linearFittingNum - 2);                                                  // 在上述2点之间填充linearFittingNum - 2个点,去掉的是在原始音频中采样的2个数

            // 将原始wav中的数据+拟合好的数据放入__pEachMixingSound中
            __pEachMixingSound->push_back(changedJumpIntervalData_pre);                                         // 头        在原始音频中隔1个点取的数
            for (int middleFillingIdx = 0; middleFillingIdx < linerFitVals.size(); ++middleFillingIdx)          // 中间的  填充linearFittingNum - 2个点
                __pEachMixingSound->push_back(linerFitVals[middleFillingIdx]);
            __pEachMixingSound->push_back(changedJumpIntervalData_next);                                        // 尾        在原始音频中隔1个点取的数
        }

        // 由于F<0,需要在__RawDataBuffer里少取F倍的点,由于RawDataCnt/F无法整除,可能会在__RawDataBuffer末尾少几个点,因此需要在原始wav中取数据使补齐至RawDataCnt个点
        int endFillingIdx = 0;
        if (__pEachMixingSound->size() < RawDataCnt)
        {
            for (; endFillingIdx < (RawDataCnt - __pEachMixingSound->size()); ++endFillingIdx)
            {
                AUDIO_DATA_TYPE fillingData = AUDIO_DATA_TYPE(pSound->AudioData[pSound->CurPositionInAudio + endFillingIdx * linearFittingNum]) * V;    // 因为需要填充的点很少,可能拟合不完就结束了,故每次隔linearFittingNum即1/F个点取数据,而不是隔1个点取数据然后拟合
                __pEachMixingSound->push_back(fillingData);
            }
            *pCurPositionInAudio += endFillingIdx;              // 因为   在原始wav中取数据, 故需要标记原始数据}  
        }
    }

    // SpeedFactor控制是否快进(播放快慢),即跳过一些点间隔地播放
    *pCurPositionInAudio += (static_cast<int>(RawDataCnt)) * SpeedFactor;       // 将 已写入该wav的位置 记录, RawDataCnt:无论F变大还是变小都会在__pRawDataBuffer里填满RawDataCnt个点,其实我认为应该是RawDataCnt * F * SpeedFactor         
}

//---------------------------------------------------------------------------------------------
// TwoPointLinearEquation()

// 两点式直线方程,既(y-y1)/(x-x1)=(y2-y1)/(x2-x1),整理得(y-y1)/(y2-y1)=(x-x1)/(x2-x1)
// 供F < 1时,线性拟合用
//---------------------------------------------------------------------------------------------
vector<AUDIO_DATA_TYPE> CAudioSource::CAudioChange::TwoPointLinearEquation(int x1, AUDIO_DATA_TYPE y1,
    int x2, AUDIO_DATA_TYPE y2,
    int linearFittingNum)
{
    vector<AUDIO_DATA_TYPE> yVals;
    for (int x = 0; x < linearFittingNum; ++x)
        yVals.push_back((((x - x1) / (x2 - x1)) * (y2 - y1)) + y1);
    return yVals;
}

//---------------------------------------------------------------------------------------------
// FreqNormalizations()

// F 小于1时,归一化.
// 即当(1/F) 属于[1,2)时,F属于(0.5, 1]时,   F = 1; 
//   当(1/F) 属于[2,3)时,F属于(0.33, 0.5]时,   F = 1/2; 
//   当(1/F) 属于[3,4)时,F属于(0.25, 0.33]时,F = 1/3;
//---------------------------------------------------------------------------------------------
void CAudioSource::CAudioChange::FreqNormalizations(double& F)
{
    if (F < 1)
    {
        int reciprocal = static_cast<int>(1 / F);
        for (int FNorm = 1; FNorm < 100; ++FNorm)       // F最多减小100倍
        {
            if (reciprocal >= FNorm && reciprocal < (FNorm + 1))
            {
                F = 1.0 / FNorm;
                break;
            }
        }
    }
}
阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页