PCM音频数据封装为WAV文件

------------------------------------全系列文章目录------------------------------------

  • PCM(Pulse Code Modulation,脉冲编码调制),PCM音频数据是指经过采样、量化、编码转换成的未压缩数字音频数据。

  • WAV最常见的声音文件格式之一,是微软公司专门为Windows开发的一种标准数字音频文件,该文件能记录各种单声道或立体声的声音信息,并能保证声音不失真

  • WAV格式组成
    在这里插入图片描述

    • RIFF(Resource Interchange File Format,资源交换档案标准),是一种把资料储存在被标记的区块(tagged chunks)中的档案格式(meta-format),不同编码的音频/视频流按照RIFF所定义的存储规则保存,在读取播放时,就可以按照RIFF规则分析文件,正常解析出音频/视频流。
  • 示例代码

    • 按照上述定义,声明如下数据结构
    struct RIFF_t {
        char        ID[4];
        uint32_t    size;
        char        format[4];
    };
    
    struct FMT_t {
        char        ID[4];
        uint32_t    size;
        uint16_t    audio_format;
        uint16_t    channels;
        uint32_t    sample_rate;
        uint32_t    byte_rate;
        uint16_t    block_align;
        uint16_t    bits_per_sample;
    };
    
    struct DATA_t {
        char        ID[4];
        uint32_t    size;
        //void		*data;		//此处因为数据需要从文件中边读取边存储,因此为了方便操作,不声明data
    };
    
    struct PCM_to_WAV_t {
        char        *in_file_name;
        char        *out_file_name;
        FILE        *in_file;
        FILE        *out_file;
        uint16_t    pcm_data;	//保存从PCM文件中读取的数据,因为PCM数据大小必然是16的倍数,所以声明为uint16_t
    
        struct RIFF_t   riff;
        struct FMT_t    fmt;
        struct DATA_t   data;
    };
    
    • 初始化操作
    int PCM_to_WAV_Init(PCM_to_WAV_t *p2w, int argc, char** argv)
    {
        if (argc == 3) {
            p2w->in_file_name        = argv[1];
            p2w->fmt.sample_rate     = 44100;
            p2w->fmt.channels        = 2;
            p2w->fmt.bits_per_sample = 16;
            p2w->out_file_name       = argv[2];
        } else if (argc == 6) {
            p2w->in_file_name        = argv[1];
            p2w->fmt.sample_rate     = atoi(argv[2]);
            p2w->fmt.channels        = atoi(argv[3]);
            p2w->fmt.bits_per_sample = atoi(argv[4]);
            p2w->out_file_name       = argv[5];
        } else {
            printf("$.exe $.pcm $sample_rate $channels $bits_per_sample $.wav\n");
            printf("Example: $.exe in.pcm 44100 2 16 out.wav\n");
            printf("$.exe $.pcm $.wav\n");
            printf("Example: $.exe in.pcm out.wav\n");
            return -1;
        }
    
        p2w->pcm_data = 0;
        p2w->in_file  = NULL;
        p2w->out_file = NULL;
    
        memcpy(p2w->riff.ID, "RIFF", 4);
        memcpy(p2w->riff.format, "WAVE", 4);
        p2w->riff.size = 36;
    
        memcpy(p2w->fmt.ID, "fmt ", 4);
        p2w->fmt.size           = 16;
        p2w->fmt.audio_format   = 1;
        p2w->fmt.byte_rate      = p2w->fmt.sample_rate * p2w->fmt.channels * p2w->fmt.bits_per_sample / 8;
        p2w->fmt.block_align    = p2w->fmt.channels * p2w->fmt.bits_per_sample / 8;
    
        memcpy(p2w->data.ID, "data", 4);
        p2w->data.size = 0;
    
        return 0;
    }
    
    • 转换过程
    int PCM_to_WAV(PCM_to_WAV_t *p2w)
    {
        p2w->in_file  = fopen(p2w->in_file_name, "rb+");
        p2w->out_file = fopen(p2w->out_file_name, "wb+");
        if (p2w->in_file == NULL || p2w->out_file == NULL) {
            printf("open file error\n");
            return -1;
        }
    
        fseek(p2w->out_file, sizeof(struct RIFF_t), 1);
        fwrite(&p2w->fmt, sizeof(struct FMT_t), 1, p2w->out_file);
        fseek(p2w->out_file, sizeof(struct DATA_t), 1);
    
        fread(&p2w->pcm_data, sizeof(uint16_t), 1, p2w->in_file);
        while (! feof(p2w->in_file) ) {
            p2w->data.size += 2;
            fwrite(&p2w->pcm_data, sizeof(uint16_t), 1, p2w->out_file);
            fread(&p2w->pcm_data, sizeof(uint16_t), 1, p2w->in_file);
        }
    
        p2w->riff.size += p2w->data.size;
        rewind(p2w->out_file);
        fwrite(&p2w->riff, sizeof(struct RIFF_t), 1, p2w->out_file);
        fseek(p2w->out_file, sizeof(struct FMT_t), 1);
        fwrite(&p2w->data, sizeof(struct DATA_t), 1, p2w->out_file);
    
        fclose(p2w->in_file);
        fclose(p2w->out_file);
    
        return 0;
    }
    
    • 打印块信息
    void PCM_to_WAV_Info(PCM_to_WAV_t *p2w)
    {
        printf("in file: %s\nout file: %s\n", p2w->in_file_name, p2w->out_file_name);
        printf("RIFF: \n\tID: %4.4s\n\tsize: %d\n\tformat: %4.4s\n",
                    p2w->riff.ID, p2w->riff.size, p2w->riff.format);
        printf("FMT: \n\tID: %4.4s\n\tSize: %d\n\tAudio fmt: %d\n\tChannels: %d\n",
                p2w->fmt.ID, p2w->fmt.size, p2w->fmt.audio_format, p2w->fmt.channels);
        printf("\tsample rate: %d\n\tByte Rate: %d\n\tBlock Align: %d\n\tBits Per Sample: %d\n",
                p2w->fmt.sample_rate, p2w->fmt.byte_rate, p2w->fmt.block_align, p2w->fmt.bits_per_sample);
        printf("DATA: \n\tID: %4.4s\n\tSize: %d\n", p2w->data.ID, p2w->data.size);
    }
    
    • 调用
    int main(int argc, char **argv)
    {
        struct PCM_to_WAV_t pcm2wav;
    
        if (PCM_to_WAV_Init(&pcm2wav, argc, argv) != 0)
            return -1;
        if (PCM_to_WAV(&pcm2wav) != 0)
            return -1;
        PCM_to_WAV_Info(&pcm2wav);
    
        return 0;
    }
    
  • 参考资料

不当之处请在评论区指出
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要将PCM数据封装WAV文件格式,需要将PCM数据流保存到WAV文件中,并为WAV文件添加相应的文件头和元数据WAV文件格式是一种常见的音频文件格式,通常用于存储未经压缩的音频数据。 下面是一个简单的示例代码,演示了如何将PCM数据封装WAV文件格式: ```java public static void pcmToWav(String pcmPath, String wavPath, int sampleRate, int channels, int bitPerSample) { FileInputStream pcmInputStream = null; FileOutputStream wavOutputStream = null; long totalAudioLen, totalDataLen; long longSampleRate = sampleRate; int byteRate = bitPerSample * channels * (int) (longSampleRate / 8); try { pcmInputStream = new FileInputStream(new File(pcmPath)); wavOutputStream = new FileOutputStream(new File(wavPath)); totalAudioLen = pcmInputStream.getChannel().size(); totalDataLen = totalAudioLen + 36; byte[] data = new byte[1024]; writeWaveFileHeader(wavOutputStream, totalAudioLen, totalDataLen, longSampleRate, channels, byteRate); while (pcmInputStream.read(data) != -1) { wavOutputStream.write(data); } pcmInputStream.close(); wavOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } } private static void writeWaveFileHeader( FileOutputStream outputStream, long totalAudioLen, long totalDataLen, long longSampleRate, int channels, int byteRate) throws IOException { byte[] header = new byte[44]; header[0] = 'R'; // RIFF/WAVE header header[1] = 'I'; header[2] = 'F'; header[3] = 'F'; header[4] = (byte) (totalDataLen & 0xff); header[5] = (byte) ((totalDataLen >> 8) & 0xff); header[6] = (byte) ((totalDataLen >> 16) & 0xff); header[7] = (byte) ((totalDataLen >> 24) & 0xff); header[8] = 'W'; header[9] = 'A'; header[10] = 'V'; header[11] = 'E'; header[12] = 'f'; // 'fmt ' chunk header[13] = 'm'; header[14] = 't'; header[15] = ' '; header[16] = 16; // 4 bytes: size of 'fmt ' chunk header[17] = 0; header[18] = 0; header[19] = 0; header[20] = 1; // format = 1 header[21] = 0; header[22] = (byte) channels; header[23] = 0; header[24] = (byte) (longSampleRate & 0xff); header[25] = (byte) ((longSampleRate >> 8) & 0xff); header[26] = (byte) ((longSampleRate >> 16) & 0xff); header[27] = (byte) ((longSampleRate >> 24) & 0xff); header[28] = (byte) (byteRate & 0xff); header[29] = (byte) ((byteRate >> 8) & 0xff); header[30] = (byte) ((byteRate >> 16) & 0xff); header[31] = (byte) ((byteRate >> 24) & 0xff); header[32] = (byte) (2 * 16 / 8); // block align header[33] = 0; header[34] = (byte) bitPerSample; header[35] = 0; header[36] = 'd'; // data chunk header[37] = 'a'; header[38] = 't'; header[39] = 'a'; header[40] = (byte) (totalAudioLen & 0xff); header[41] = (byte) ((totalAudioLen >> 8) & 0xff); header[42] = (byte) ((totalAudioLen >> 16) & 0xff); header[43] = (byte) ((totalAudioLen >> 24) & 0xff); outputStream.write(header, 0, 44); } ``` 在这个示例代码中,我们定义了一个`pcmToWav()`方法,用于将PCM数据流保存为WAV文件。该方法接收三个参数:PCM文件路径、WAV文件路径、采样率、声道数和位宽。在方法中,我们使用FileInputStream读取PCM文件中的数据,并使用FileOutputStream将WAV文件写入磁盘。同时,我们调用了`writeWaveFileHeader()`方法,向WAV文件中添加WAV文件头和元数据。在添加WAV文件头和元数据时,我们需要根据采样率、声道数和位宽计算出相应的文件头和元数据,并将其写入WAV文件中。 通过调用`pcmToWav()`方法,我们可以将PCM数据流保存为WAV文件,并在保存的过程中添加相应的文件头和元数据
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值