c++

构建wav音频文件

c++

Posted by YiMiTuMi on December 13, 2024

构建wav文件

这套用来构建一个wav文件,主要用接受传过来的音频数据并将其保存成音频文件,为了保证保证即使接受任意大小的音频数据也能构建出一个wav文件,所以在收到一个新的音频文件时,会构建一个音频数据并保存为wav文件,后面收到的会进行追加,这样即使发送方突然停止发送或突然停止保存或者当前程序崩溃,也会记录先前的数据音频数据不会丢失。

用于保存wav文件的类,他接受一个 WAV_INFO 结构体的数据,会一直保存。

WAV_INFO:

#pragma pack(1)

#define PLAYBACK_PCM 1              //1为PCM编码
#define PLAYBACK_BITSPER_SAMPLE 16 //数据位数
#define BUFF_SIZE 4096
#define FILE_PATH_SIZE 1024

typedef struct _WAV_INFO
{
   wchar_t szFileName[1024];
   long long iStartDeduceTime; //开始推演的推演时间戳
   long long iEndDeduceTime;   //推演结束的时间戳

   unsigned short nChannleNumber; //通道数
   unsigned long nSampleRate; //采样率
   unsigned short nAudioFormat; //编码格式 1为PCM编码
   unsigned short nBitsPerSample; //数据位数

   int iBuffSize;
   char szBuff[BUFF_SIZE];

   _WAV_INFO ()
   {
      nChannleNumber = 0;
      nSampleRate = 0;
      nAudioFormat = PLAYBACK_PCM;
      nBitsPerSample = PLAYBACK_BITSPER_SAMPLE;
      iStartDeduceTime = 0;
      iEndDeduceTime = 0;
      bState = true;
      memset(szBuff, 0, sizeof(BUFF_SIZE));
      memset(szFileName, 0, sizeof(FILE_PATH_SIZE));
   }

}WAV_INFO, *PWAV_INFO;

#pragma pack()

PlayBackWAVInfo.h文件:

#ifndef SOURCESPPLAYBACKINFO_H
#define SOURCESPPLAYBACKINFO_H

#include <map>
#include <QString>
#include <QFileInfo>
#include <QDataStream>
#include <QAudioFormat>
#include <qdebug.h>
#include <mutex>
#include <tuple>
#include <QDateTime>
#include "wavdeal.h"
#include <QReadWriteLock>

#define PLAYBACK_PCM 1              //1为PCM编码

#define PLAYBACK_BITSPER_SAMPLE 16 //数据位数


struct WavFileHeader
{
    //RIFF头
    char RiffName[4];
    unsigned long nRiffLength; //存的是后面所有文件的大小; //存的是fmt保存的大小,包含这之后,data前面几个,共16个;

    //数据类型标识符
    char wavname[4];

    //格式块中的块头
    char fmtName[4];
    unsigned long nFmtLength;

    //格式块中的块数据
    unsigned short nAudioFormat; //编码格式 1为PCM编码
    unsigned short nChannleNumber; //通道数
    unsigned long nSampleRate; //采样率
    unsigned long nBytesPerSecond; //传输速率 = 采样率*通道数*样本数据位数/8
    unsigned short nBytesPerSample; //数据块对齐单位 传输速率/采样率
    unsigned short nBitsPerSample; //数据位数

    //数据块
    char chDATA[4];
    unsigned long nDATALen; //数据的长度;
};

//读音频文件使用
struct WavFileHeader_OlD
{
    //RIFF头
    char RiffName[4];
    unsigned long nRiffLength; //存的是后面所有文件的大小;

    //数据类型标识符
    char wavname[4];

    //格式块中的块头
    char fmtName[4];
    unsigned long nFmtLength;

    //格式块中的块数据
    unsigned short nAudioFormat; //编码格式 1为PCM编码
    unsigned short nChannleNumber; //通道数
    unsigned long nSampleRate; //采样率
    unsigned long nBytesPerSecond; //传输速率 = 采样率*通道数*样本数据位数/8
    unsigned short nBytesPerSample; //数据块对齐单位 传输速率/采样率
    unsigned short nBitsPerSample; //数据位数
};

struct extInfo{
    //扩展信息
    unsigned short nAppendMessage;    //扩展域大小

    char* AppendMessageData;    //扩展信息域数据
};

struct FactInfo{
    //Fact块,可选字段,一般当wav文件由某些软件转化而成,则包含块状和扩展信息
    char factName[4];
    unsigned long nFactLength;

    char FactData[4];
    //数据块中的块头
    char DataName[4];
    unsigned long nDataLength;
};


typedef struct _PLAYBACK_INFO
{
    long long iStartTime;       //记录开始时间戳(天文时间)
    long long iCurTime;         //当前写入的时间戳
    long long iStartDeduceTime; //开始推演时间

    unsigned short nChannleNumber; //通道数
    unsigned long nSampleRate; //采样率
    unsigned short nAudioFormat; //编码格式 1为PCM编码
    unsigned short nBitsPerSample; //数据位数

    QString strFileName; //文件名
    QString strFilePath; //文件路径

    _PLAYBACK_INFO()
    {
        iStartTime = 0;
        iStartDeduceTime = 0;
    }

} PLAYBACK_INFO, *PPLAYBACK_INFO;


class PlayBackWAVInfo
{

public:

    PlayBackWAVInfo();
    virtual ~PlayBackWAVInfo();

public:

    //设置保存文件路径
    void SetSaveFilePath(const QString &strFilePath);

    //获取保存文件路径
    QString GetSaveFilePath();

    //保存
    bool SavePlayBack(const WAV_INFO &wavInfo);

    //关闭
    bool ClosePlayBack(const Qstring &strName);

    //读取音频数据,用于测试`
    QByteArray ReadWavFile(const QString &strFilePath);

    //关闭保存所有文件
    void CloseAll();

private:

    //创建音频文件
    bool CleartWavFile(PPLAYBACK_INFO pPlayBackInfo);

    //增加写入音频数据
    bool WriteWavFile(PPLAYBACK_INFO pPlayBackInfo, QByteArray value);

    //判断文件是否存在
    bool isFileExist(const QString &strFilePath);

    //初始化数据
    void InitData(PPLAYBACK_INFO pPlayBackInfo, const WAV_INFO &wavInfo);

private:

    //文件名称
    std::map<QString, PPLAYBACK_INFO> m_mapPlayBackInfo;

    //保存文件位置
    QString m_strSaveMavFilePath;

    //用于测试读取的音频
    QByteArray m_musicDataArray;

    //读写锁
    QReadWriteLock m_readWriteLock;
};


#endif // SOURCESPPLAYBACKINFO_H

PlayBackWAVInfo.cpp文件:

#include "SourcesPlayBackInfo.h"
#include <QCoreApplication>


PlayBackWAVInfo::PlayBackWAVInfo()
{
    m_mapPlayBackInfo.clear();
}

PlayBackWAVInfo::~PlayBackWAVInfo()
{
    CloseAll();
}

//设置保存文件路径
void PlayBackWAVInfo::SetSaveFilePath(const QString &strFilePath)
{
    m_strSaveMavFilePath = strFilePath;
}

QString PlayBackWAVInfo::GetSaveFilePath()
{
    return m_strSaveMavFilePath;
}

bool PlayBackWAVInfo::SavePlayBack(const WAV_INFO &wavInfo)
{
    PPLAYBACK_INFO pPlayBackInfo = nullptr;
    QString strWavName =  wavInfo.szFileName;

    if (m_mapPlayBackInfo.count(strWavName) == 1)
    {
        //已经存在了直接写入
        pPlayBackInfo = m_mapPlayBackInfo[strWavName];
    }
    else
    {
        //不存在创建一个
        pPlayBackInfo = new PLAYBACK_INFO;
        m_mapPlayBackInfo.insert(std::pair<QString, PPLAYBACK_INFO>(tuplePlayBack, pPlayBackInfo));

        InitData(pPlayBackInfo, wavInfo);

        CleartWavFile(pPlayBackInfo);
    }

    //写入文件
    if (pPlayBackInfo != nullptr)
    {

        QByteArray value;
        value.resize(wavInfo.iBuffSize);
        memcpy(value.data(), wavInfo.szBuff, wavInfo.iBuffSize);

        //写入数据
        WriteWavFile(pPlayBackInfo, value);

        //更新天文时间戳
        QDateTime currentTime = QDateTime::currentDateTime();
        pPlayBackInfo->iCurTime = currentTime.toMSecsSinceEpoch();
    }

    return true;
}

void PlayBackWAVInfo::InitData(PPLAYBACK_INFO pPlayBackInfo, const WAV_INFO &wavInfo)
{
    pPlayBackInfo->iStartDeduceTime = wavInfo.iStartDeduceTime; //推演时间戳
    pPlayBackInfo->nChannleNumber = wavInfo.nChannleNumber;     //通道数
    pPlayBackInfo->nSampleRate = wavInfo.nSampleRate;           //采样率
    pPlayBackInfo->nAudioFormat = wavInfo.nAudioFormat;         //编码格式
    pPlayBackInfo->nBitsPerSample = wavInfo.nBitsPerSample;     //数据位数
    pPlayBackInfo->strFileName = wavInfo.szFileName;


    //更新天文时间戳
    QDateTime currentTime = QDateTime::currentDateTime();
    pPlayBackInfo->iStartTime = currentTime.toSecsSinceEpoch();
}


bool PlayBackWAVInfo::isFileExist(const QString &strFilePath)
{
    QFileInfo fileInfo(strFilePath);

    return fileInfo.isFile();
}

bool PlayBackWAVInfo::CleartWavFile(PPLAYBACK_INFO pPlayBackInfo)
{
    //构建文件名
    QString strFileName = QString(".wav").arg(pPlayBackInfo->strFileName);
    QString strFilePath = m_strSaveMavFilePath + "\\" + strFileName;


    pPlayBackInfo->strFilePath = strFilePath;

    //打开文件
    QFile pFile(strFilePath);
    if (!pFile.open(QFile::ReadWrite))
    {
        return false;
    }

    //构建WAV文件
    WavFileHeader wavFileInfo;

    //RIFF标识头
    wavFileInfo.RiffName[0] = 'R';
    wavFileInfo.RiffName[1] = 'I';
    wavFileInfo.RiffName[2] = 'F';
    wavFileInfo.RiffName[3] = 'F';

    //整个wav文件大小 - sizeof(RiffName + nRiffLength) 大小
    wavFileInfo.nRiffLength = sizeof(WavFileHeader) - sizeof(unsigned long);

    //wav 标识
    wavFileInfo.wavname[0] = 'W';
    wavFileInfo.wavname[1] = 'A';
    wavFileInfo.wavname[2] = 'V';
    wavFileInfo.wavname[3] = 'E';

    //fmt
    wavFileInfo.fmtName[0] = 'f';
    wavFileInfo.fmtName[1] = 'm';
    wavFileInfo.fmtName[2] = 't';
    wavFileInfo.fmtName[3] = ' ';

    wavFileInfo.nFmtLength = 0x0010; //一般情况下Size为16,如果为18则最后多了2个字节的附加信息

    //格式块数据
    wavFileInfo.nAudioFormat = pPlayBackInfo->nAudioFormat; //编码格式 1为PCM编码
    wavFileInfo.nChannleNumber = pPlayBackInfo->nChannleNumber; //通道数
    wavFileInfo.nSampleRate = pPlayBackInfo->nSampleRate; //采样率
    wavFileInfo.nBitsPerSample = pPlayBackInfo->nBitsPerSample; //数据位数

    //传输速率
    wavFileInfo.nBytesPerSecond = (wavFileInfo.nSampleRate * wavFileInfo.nChannleNumber * PLAYBACK_BITSPER_SAMPLE) / 8;

    //数据块对齐单位
    wavFileInfo.nBytesPerSample = wavFileInfo.nBytesPerSecond / wavFileInfo.nSampleRate;

    //数据块
    wavFileInfo.chDATA[0] = 'd';
    wavFileInfo.chDATA[1] = 'a';
    wavFileInfo.chDATA[2] = 't';
    wavFileInfo.chDATA[3] = 'a';
    wavFileInfo.nDATALen = 0;   //文件总长度

    //保存到文件中
    pFile.seek(0);

    //将文件头保存到文件中
    QByteArray datagram;
    datagram.resize(sizeof(WavFileHeader));
    memcpy(datagram.data(), &wavFileInfo, sizeof(WavFileHeader));
    pFile.write(datagram);

    //关闭文件
    pFile.close();

    return true;
}

bool PlayBackWAVInfo::WriteWavFile(PPLAYBACK_INFO pPlayBackInfo, QByteArray value)
{
    //打开文件
    QFile pFile(pPlayBackInfo->strFilePath);
    if (!pFile.open(QFile::ReadWrite))
    {
       return false;
    }

    //获取文件总大小
    unsigned long iFileSize = pFile.size();

    WavFileHeader wavFileInfo;

    //先读取结构体数据
    pFile.seek(0); //设置文件位置
    pFile.read((char*)(&wavFileInfo), sizeof(WavFileHeader));

    //修改数据长度
    wavFileInfo.nRiffLength += value.length();
    wavFileInfo.nDATALen += value.length();

    //覆盖数据
    pFile.seek(0); //设置文件位置
    QByteArray datagram;
    datagram.resize(sizeof(WavFileHeader));
    memcpy(datagram.data(), &wavFileInfo, sizeof(WavFileHeader));
    pFile.write(datagram);

    //写入数据
    pFile.seek(pFile.size()); //设置到文件末尾
    pFile.write(value);

    //关闭文件
    pFile.close();
}


//关闭
bool PlayBackWAVInfo::ClosePlayBack(const QString& strName)
{
    if (m_mapPlayBackInfo.count(strName) == 1)
    {
        PPLAYBACK_INFO pPlayBack = m_mapPlayBackInfo[strName];
        if (pPlayBack != nullptr)
        {
            //清空
             m_mapPlayBackInfo.erase(m_mapPlayBackInfo.find(strName));
             delete pPlayBack;
        }
    }
}

void PlayBackWAVInfo::CloseAll()
{
    if (m_mapPlayBackInfo.empty())
    {
        return;
    }

    std::map<QString, PPLAYBACK_INFO>::iterator iter = m_mapPlayBackInfo.begin();

    while (iter != m_mapPlayBackInfo.end())
    {

        PPLAYBACK_INFO pPlayBack = iter->second;
        iter = m_mapPlayBackInfo.erase(iter);
        delete pPlayBack;
    }
}

//读取音频数据,用于测试
QByteArray PlayBackWAVInfo::ReadWavFile(const QString &strFilePath)
{
    //    QString fileName = QFileDialog::getOpenFileName(nullptr,"选择文件");
    QString fileName = strFilePath; //"E://【阵元域数据】//核潜艇-柱晶级-SU05-110转-7叶.wav";
    QAudioFormat format;
    unsigned long totalLength = 0;
    QByteArray soundDataArray;

    QFile file(fileName);
    if(!file.open(QIODevice::ReadOnly))
    {
        qDebug()<<"文件打开失败";
    }

    //音频的所有数据
    m_musicDataArray = file.readAll();

    //获取wav数据内容
    WavFileHeader_OlD wavFile;
    memcpy(&wavFile, m_musicDataArray, sizeof (WavFileHeader));

    if(wavFile.nAudioFormat == 1)
    {
        format.setCodec("audio/pcm");
        qDebug()<<"PCM编码";
    }

    format.setSampleRate(wavFile.nSampleRate);
    format.setChannelCount(wavFile.nChannleNumber);
    format.setSampleSize(wavFile.nBitsPerSample);
    format.setByteOrder(QAudioFormat::LittleEndian);
    format.setSampleType(QAudioFormat::SignedInt);
//    format.setSampleType(QAudioFormat::UnSignedInt);

    int soundFs = wavFile.nSampleRate;

    int extLength = 0;
    if(wavFile.nFmtLength == 16)
    {
        qDebug()<<"没有扩展信息";
    }

    FactInfo factInfo;
    memcpy(&factInfo,m_musicDataArray.mid(sizeof (WavFileHeader)+extLength),sizeof (FactInfo));
    QString tempName = factInfo.factName;
    tempName.resize(4);
    qDebug()<<tempName;

    qDebug()<<"数据长度:"<<factInfo.nFactLength;
    totalLength = factInfo.nFactLength;

    //截取音频文件
    soundDataArray = m_musicDataArray.mid(sizeof (WavFileHeader) + extLength + sizeof (FactInfo));
    qDebug()<<"音频文件数据长度:"<<soundDataArray.length();

    return soundDataArray;
}

用法:

int main(int argc, char *argv[])
{
	PlayBackWAVInfo PlayBackInfo;
	QString strSavePath;	

	//设置保存文件路径
     PlayBackInfo.SetSaveFilePath(strSavePath);

	//保存音频数据,伪代码
	while (true)
	{
		WAV_INFO wavInfo; //音频数据
		PlayBackInfo.SavePlayBack(wavInfo);
	}	



    return 0;
}