用C处理音频wav文件

发布于 2024-08-25 00:12:50 字数 3127 浏览 6 评论 0原文

我正在处理 wav 文件的幅度并按某个小数因子缩放它。我正在努力思考如何以节省内存的方式读取和重写文件,同时也尝试解决该语言的细微差别(我是 C 语言的新手)。该文件可以是 8 位或 16 位格式。我想到的方法是首先将 标头数据 读入一些预定义的struct,然后在循环中处理实际数据,在循环中我将把一大块数据读入缓冲区,执行所需的操作,然后将其写入输出。

#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;
}

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

I'm working on processing the amplitude of a wav file and scaling it by some decimal factor. I'm trying to wrap my head around how to read and re-write the file in a memory-efficient way while also trying to tackle the nuances of the language (I'm new to C). The file can be in either an 8- or 16-bit format. The way I thought of doing this is by first reading the header data into some pre-defined struct, and then processing the actual data in a loop where I'll read a chunk of data into a buffer, do whatever is needed to it, and then write it to the output.

#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;
}

I'm getting differing file sizes at the end (by 1k or so, for a 40Mb file), and I suspect this is due to the fact that I'm writing an entire buffer to the output, even though the file may have terminated before filling the entire buffer size. Also, the output file is messed up - won't play or open - so I'm probably doing the whole thing wrong. Any tips on where I'm messing up will be great. Thanks!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

一身仙ぐ女味 2024-09-01 00:12:50

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并且factor = 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 最后一点,您正在重新发明轮子。只要是为了学习,就没问题。否则你应该使用现有的库。

1 You're reading bytes instead of 16-bit samples in this else branch:

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

2 You don't saturate the values when scaling, e.g. original 16-bit sample = 32000 and factor = 1.5 will wrap around the integer value instead of clamping it to the maximum of 32767.

3 You don't look at the RIFF and other headers at all. In WAV files, it is possible that the audio data is followed by some informational footers or preceded by additional headers. Or in other words: Your header struct is too static. You should also read the WAV format from the file instead of having a parameter saying it's 8 bit samples.

4 This just won't happen:

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

8-bit/16-bit values will never be greater than 255/32768 except if your computer inserts some magic bits into the memory when integers overflows :P

And audio samples are signed, so the ranges are -128;127 and -32768;32767. Overflow checking must occur in the multiplication expression. You're also making assumptions on the floating-point-to-integer rounding mode, which is configurable and should be considered. Something like if(roundf(factor * inbuff16[i]) > 32767 || roundf(factor * inbuff16[i]) < -32768), maybe.

5 You don't store the result of fread, so you will write too many samples to the output file.

6 And as a last point, you're reinventing the wheel. As long as this is for learning, it's okay. Else you should use existing libraries.

平生欢 2024-09-01 00:12:50

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

It is much better to use libraries for reading and writing sound files. E.g. libsndfile. That web page has a list of "other similar projects" you can also look at. The sndfile-tools could be good code examples to learn how to use the library.

带上头具痛哭 2024-09-01 00:12:50

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

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

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

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

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

I would recommend looking at the original file and the output file in a hex editor to see if you are re-writing the data properly. If the resulting file won't play or open, chances are the header of the output file isn't correct.

Another option is to remove your audio processing logic and simply read in the source file to your internal buffer and write it out to a file. If your code can generate a valid, working output file in this manner, then you can narrow down the problem to your processing code.

You may also want to start with a smaller file than 40Mb. If nothing else, make a copy of that input file and trim it down to a couple of seconds of audio. A smaller file will be easier to manually inspect.

Edit: The calls to fread() and fwrite() need to have their return values verified. These functions return the number of elements read or written, and if a call to either function returns a value less than expected then this could be the source of your file size difference.

Also, the second parameter to fread is in bytes. Therefore, if you want to read-fill an entire buffer, you would need to say something more like fread(inbuff16, sizeof(inbuff16[0]), BUFSIZE, infile);. The current code will only read in BUFSIZE bytes (which works for the 8-bit case by coincidence, but I would recommend changing it too for clarity).

怎樣才叫好 2024-09-01 00:12:50

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

short int extra_param_size;

This following line is also not needed for reading WAV headers (makes the header 48 bytes long, instead of the "standard" 44):

short int extra_param_size;
橘亓 2024-09-01 00:12:50

如果可能的话,您可能想考虑一种与 C 不同的语言,除非它专门用于 C 应用程序。

  • 例如,python 有一个很好的 wav 包,可以读取 &轻松写入 wav 文件。
  • 对于更专业或学术用途,第一个选择是 MATLAB,它也可以非常轻松地读取 wav 文件(直接读取向量,然后将其作为单个表达式进行操作)。

If possible you may want to look at a different language than C unless it is specifically for a C application.

  • For example python has a good wav package that reads & writes wav files easily.
  • For more professional or academic use, the first go-to is MATLAB which also reads wav files very easily (directly into vectors which are then operated on as single expressions).
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文