8

我正在处理 wav 文件的幅度并将其缩放一些小数因子。我正试图围绕如何以内存有效的方式读取和重写文件,同时还试图解决语言的细微差别(我是 C 新手)。该文件可以是 8 位或 16 位格式。我想这样做的方法是首先将标头数据读入一些预定义的结构,然后在循环中处理实际数据,我会将一大块数据读入缓冲区,做任何需要的事情,然后将其写入输出。

#include <stdio.h>
#include <stdlib.h>


typedef struct header 
{
    char chunk_id[4];
    int chunk_size;
    char format[4];
    char subchunk1_id[4];
    int subchunk1_size;
    short int audio_format;
    short int num_channels;
    int sample_rate;
    int byte_rate;
    short int block_align;
    short int bits_per_sample;
    short int extra_param_size;
    char subchunk2_id[4];
    int subchunk2_size;
} header;

typedef struct header* header_p;

void scale_wav_file(char * input, float factor, int is_8bit)
{
    FILE * infile = fopen(input, "rb");
    FILE * outfile = fopen("outfile.wav", "wb");

    int BUFSIZE = 4000, i, MAX_8BIT_AMP = 255, MAX_16BIT_AMP = 32678;

    // used for processing 8-bit file
    unsigned char inbuff8[BUFSIZE], outbuff8[BUFSIZE];

    // used for processing 16-bit file
    short int inbuff16[BUFSIZE], outbuff16[BUFSIZE];

    // header_p points to a header struct that contains the file's metadata fields
    header_p meta = (header_p)malloc(sizeof(header));

    if (infile)
    {

        // read and write header data
        fread(meta, 1, sizeof(header), infile);
        fwrite(meta, 1, sizeof(meta), outfile);

        while (!feof(infile))
        {
            if (is_8bit)
            {
                fread(inbuff8, 1, BUFSIZE, infile);   
            } else {
                fread(inbuff16, 1, BUFSIZE, infile);      
            }

            // scale amplitude for 8/16 bits
            for (i=0; i < BUFSIZE; ++i)
            {
                if (is_8bit)
                {
                    outbuff8[i] = factor * inbuff8[i];
                    if ((int)outbuff8[i] > MAX_8BIT_AMP)
                    {
                        outbuff8[i] = MAX_8BIT_AMP;
                    }
                } else {
                    outbuff16[i] = factor * inbuff16[i];
                    if ((int)outbuff16[i] > MAX_16BIT_AMP)
                    {
                        outbuff16[i] = MAX_16BIT_AMP;
                    } else if ((int)outbuff16[i] < -MAX_16BIT_AMP) {
                        outbuff16[i] = -MAX_16BIT_AMP;
                    }
                }
            }

            // write to output file for 8/16 bit
            if (is_8bit)
            {
                fwrite(outbuff8, 1, BUFSIZE, outfile);
            } else {
                fwrite(outbuff16, 1, BUFSIZE, outfile);
            }
        }
    }

    // cleanup
    if (infile) { fclose(infile); }
    if (outfile) { fclose(outfile); }
    if (meta) { free(meta); }
}

int main (int argc, char const *argv[])
{
    char infile[] = "file.wav";
    float factor = 0.5;
    scale_wav_file(infile, factor, 0);
    return 0;
}

最后我得到不同的文件大小(1k 左右,对于 40Mb 文件),我怀疑这是因为我正在将整个缓冲区写入输出,即使文件可能已经终止在填充整个缓冲区大小之前。此外,输出文件搞砸了 - 无法播放或打开 - 所以我可能做错了整个事情。关于我在哪里搞砸的任何提示都会很棒。谢谢!

4

5 回答 5

9

1您在此 else 分支中读取字节而不是 16 位样本:

while (!feof(infile))
    {
        if (is_8bit)
        {
            fread(inbuff8, 1, BUFSIZE, infile);   
        } else {
            fread(inbuff16, 1, BUFSIZE, infile); // <-- should be BUFSIZE*2     
        }

2缩放时不要使值饱和,例如原始 16 位样本 = 32000 和因子 = 1.5 将环绕整数值,而不是将其钳制到最大值 32767。

3你根本不看 RIFF 和其他标题。在 WAV 文件中,音频数据可能后跟一些信息性页脚或附加标题。或者换句话说:你的header结构太静态了。您还应该从文件中读取 WAV 格式,而不是有一个参数说它是 8 位样本。

4这不会发生:

                outbuff16[i] = factor * inbuff16[i];
                if ((int)outbuff16[i] > MAX_16BIT_AMP)

8 位/16 位值永远不会大于 255/32768,除非您的计算机在整数溢出时将一些魔术位插入内存:P

并且音频样本已签名,因此范围为 -128;127 和 -32768;32767。溢出检查必须发生在乘法表达式中。您还对浮点到整数舍入模式进行了假设,该模式是可配置的,应予以考虑。像if(roundf(factor * inbuff16[i]) > 32767 || roundf(factor * inbuff16[i]) < -32768),也许。

5你不存储 的结果fread,所以你会在输出文件中写入太多的样本。

6最后一点,你正在重新发明轮子。只要是用来学习的,没问题。否则,您应该使用现有的库。

于 2010-03-16T19:57:11.340 回答
5

使用库来读取和写入声音文件要好得多。例如libsndfile。该网页有一个“其他类似项目”列表,您也可以查看。这sndfile-tools可能是学习如何使用该库的好代码示例。

于 2010-03-16T23:45:00.760 回答
1

我建议在十六进制编辑器中查看原始文件和输出文件,看看您是否正确地重写了数据。如果生成的文件无法播放或打开,则可能是输出文件的标题不正确。

另一种选择是删除您的音频处理逻辑,只需将源文件读入您​​的内部缓冲区并将其写入文件。如果您的代码可以以这种方式生成有效的工作输出文件,那么您可以将问题缩小到您的处理代码。

您可能还想从小于 40Mb 的文件开始。如果没有别的,请复制该输入文件并将其修剪为几秒钟的音频。较小的文件将更容易手动检查。

编辑:调用fread()fwrite()需要验证其返回值。这些函数返回读取或写入的元素数量,如果对任一函数的调用返回的值小于预期值,则这可能是文件大小差异的根源。

此外,第二个参数以fread字节为单位。因此,如果您想读取填充整个缓冲区,则需要说类似fread(inbuff16, sizeof(inbuff16[0]), BUFSIZE, infile);. 当前代码只能以BUFSIZE字节为单位读取(巧合地适用于 8 位情况,但为了清楚起见,我建议也更改它)。

于 2010-03-16T19:45:06.950 回答
1

读取 WAV 标头也不需要以下行(使标头长 48 个字节,而不是“标准”44):

short int extra_param_size;
于 2011-11-16T09:33:08.610 回答
0

如果可能,您可能希望查看与 C 不同的语言,除非它专门用于 C 应用程序。

  • 例如,python 有一个很好的 wav 包,可以轻松读取和写入 wav 文件。
  • 对于更专业或学术用途,第一个选择是 MATLAB,它也很容易读取 wav 文件(直接进入向量,然后作为单个表达式进行操作)。
于 2011-08-18T05:19:47.167 回答