C 中的程序,它读取声音数据并检查给定数据是否满足 wav 文件的先决条件

program in C which reads data of sound and checks if the given data fulfills the prerequisites of a wav file

项目需求是用C写一个程序,根据wavformat/template.
从输入中读取只有getchar的声音数据 程序必须检查给定的数据是否正确。当检测到第一个错误时,会出现相应的消息并且程序会停止。禁止使用数组、函数、指针和数学库。它还会检查我们的数据是否不足或文件大小是否错误。

波形文件格式:
http://tiny.systems/software/soundProgrammer/WavFormatDocs.pdf http://www.topherlee.com/software/pcm-tut-wavformat.html

示例我认为这段代码用于读取数字:

while ( (ch = getchar()) != '\n') 
{
    if (ch >= '0' && ch <= '9')
    {
        bytes_sec = 10 * bytes_sec  + (ch - '0');
    }
}
fprintf(stderr, "bytes/sec:%d\n", bytes_sec);

但这是不正确的,因为我们希望将数字保存为一个字节。

对于字符,我认为这个代码:

flag = 0;
fores = 0;

while ( ( (ch=getchar()) !='\n') && (flag==0)  ) {
    fores = fores + 1;
    if  (fores == 1) {
        if (ch != 'R')
        {
            flag = 1;
        }
    }
    else if (fores == 2) {
        if (ch != 'I')
            {
                flag = 1;
            }
    }
    else if (fores == 3) {
        if (ch!='F') {
                flag = 1;
            }
    }
    else {
        if ((fores != 4) || (ch != 'F')) {
            flag = 1;
        }
    }
}

if (flag == 1) {
    fprintf(stderr, "Error! \"RIFF\" not found\n");
    return 0;
}

我也不明白数据是以哪种形式(二进制、十六进制、十进制)给出的(我知道 wav 格式的数据是二进制的,但我仍然不明白数据和如何 - 形式以及它是作为单件还是单独提供)。
最后,我们无法在终端中输入与可打印字符不对应的数据,这让我很困惑。

假设我们有一个合法的 wav 文件(十六进制)的内容:

0000000 52 49 46 46 85 00 00 00 57 41 56 45 66 6d 74 20
0000020 10 00 00 00 01 00 01 00 44 ac 00 00 88 58 01 00
0000040 02 00 10 00 64 61 74 61 58 00 00 00 00 00 d4 07
0000060 a1 0f 5e 17 04 1f 8a 26 ea 2d 1c 35 18 3c d7 42
0000100 54 49 86 4f 69 55 f6 5a 27 60 f8 64 63 69 64 6d
0000120 f7 70 18 74 c5 76 fa 78 b6 7a f6 7b b9 7c ff 7c
0000140 c8 7c 12 7c e0 7a 33 79 0b 77 6c 74 58 71 d1 6d
0000160 dd 69 7e 65 b8 60 92 5b 0f 56 36 50 0c 4a 97 43
0000200 df 3c ea 35 45 78 74 72 61 44 61 74 61
0000215

我想将它从十六进制转换为十进制,然后使用记事本创建数据文件以便使用命令 fopen(fname, "rb") 但是我将不得不使用禁止的指针。 所以我还是没明白程序怎么会在输入的时候得到prices/values.

另外,在@AndreasWenzel 的建议下,我想出了这个小端代码(注意:while 中的条件不正确,选择它是为了便于在 C 编译器中检查):

#include <stdio.h>

int ch, sample_rate, help, fores, sum, i;
int main()
{
    fores = 0;
    sample_rate = 0;
    
    while ( (ch = getchar()) != '\n' )
    {
            help = (ch - '0');
            fprintf(stderr, "help:%d\n", help);
            if (help == 1)
            {
                sum = 1;
                for (i=1 ; i<=fores ; i++)
                {
                    sum = sum*2;
                }
                sample_rate = sample_rate + (sum);
                fprintf(stderr, "sample:%d\n", sample_rate);
            }
            fores = fores + 1;
    }
fprintf(stderr, "sample rate:%d\n", sample_rate);
}

如果我们输入 10100100(二进制)=164(十进制),它将打印 37(十进制)。正确吗?

请参阅 this link 以了解整数以二进制形式存储时在内存中的表示方式。请注意,对于此任务,您不必阅读有符号整数在内存中的表示方式(您只需要了解无符号整数),也不必阅读 floating-point 数字在内存中的表示方式内存。

在一个RIFF文件中(例如.wav文件),大部分数据不是以ASCII存储的,而是以二进制形式存储的。 RIFF 文件中只有极少数内容以 ASCII 格式存储。例如,(子)块 header 每个包含一个 4 字节的 ID,例如 RIFFfmt ,它以 ASCII 存储。但是数字几乎永远不会存储在字符 '0''9' 的 ASCII 编码中,正如您的代码似乎假设的那样。

读取二进制文件时,while条件

(ch=getchar()) !='\n'

没有意义。这样的表达式只对 line-delimited 文本输入有意义。在二进制文件中,值'\n'是换行符的ASCII编码值,没有特殊意义。它只是一个像任何其他字节值一样的字节值,它可能巧合地出现在二进制文件中,例如在数字的二进制表示中。

因此,为了检查 RIFF ChunkID,您的 while 循环应该始终从文件中准确读取 4 个字节。在找到 '\n' 之前,它不应继续阅读,就像现在一样。

为了读取后续的 4 个字节长的 ChunkSize 值,您应该再次准确读取 4 个字节。您还应该有一个 uint_least32_t 类型的变量,它是一个无符号整数,并且保证长度至少为 32 位。这确保变量的大小足以存储 ChunkSize。然后,您可以使用 getchar 一次读取一个字节,然后使用各个字节的值计算整数。请参阅上面 link 关于整数在计算机内存中的表示方式,以了解如何从单个字节值计算更大的数字。按照these rules on homework questions,我不会提供这个问题的解决方案,除非你特别要求。到那时,我只会提供以下提示:

  • 您必须考虑到该号码存储在 little-endian byte ordering.
  • 当使用 getchar 读取时,您应该将结果存储在 intunsigned char 中,而不是 signed charchar(这是在大多数平台上签名)。否则,如果字节的值大于 127,变量的值将为负值。这样的负值更难处理。

请注意,如果您使用的是 #include <windows.h>(并且您使用的平台具有此 header),则可以使用 typedef DWORD 而不是 uint_least32_t .如果您使用 uint_least32_t,则必须 #include <stdint.h>


编辑:既然您已经自己解决了问题,我将提供我自己的替代解决方案。但是,该任务禁止使用数组、指针和 user-defined 函数。另一方面,在我看来,所有遵守这些限制的问题解决方案都将是混乱的,并且包含大量不必要的代码重复。因此,我在我的解决方案中使用了它们,不遵守这些限制:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>
#include <limits.h>

//This will print an error message if you attempt to compile this 
//on a platform on which there are not 8 bits per byte.
#if CHAR_BIT != 8
#error "This program assumes 8 bits per byte."
#endif

void VerifyLabel( const char *label );
uint_fast32_t ReadNumber( int num_bytes, const char *fieldname );

int main( void )
{
    uint_fast32_t temp;

    VerifyLabel( "RIFF" );

    ReadNumber( 4, "ChunkSize" );

    VerifyLabel( "WAVE" );

    VerifyLabel( "fmt " );

    temp =
        ReadNumber( 4, "Subchunk1Size" );

    if ( temp != 16 )
    {
        fprintf( stderr, "Error: Expected Subchunk1Size to be 16!\n" );
        exit( EXIT_FAILURE );
    }

    temp =
        ReadNumber( 2, "AudioFormat" );

    if ( temp != 1 )
    {
        fprintf( stderr, "Error: Expected AudioFormat to be 1 (PCM)!\n" );
        exit( EXIT_FAILURE );
    }

    temp =
        ReadNumber( 2, "NumChannels" );

    if ( temp != 1 && temp != 2 )
    {
        fprintf( stderr, "Error: Expected NumChannels to be 1 (mono) or 2 (stereo)!\n" );
        exit( EXIT_FAILURE );
    }

    ReadNumber( 4, "SampleRate" );

    ReadNumber( 4, "ByteRate" );

    ReadNumber( 2, "BlockAlign" );

    temp =
        ReadNumber( 2, "BitePerSample" );

    if ( temp != 8 && temp != 16 )
    {
        fprintf( stderr, "Error: Expected BitsPerSample to be 8 or 16!\n" );
        exit( EXIT_FAILURE );
    }

    VerifyLabel( "data" );

    ReadNumber( 4, "Subchunk2Size" );

    return 0;
}

void VerifyLabel( const char *label )
{
    for ( const char *p = label; *p != '[=10=]'; p++ )
    {
        int c;

        if ( (c = getchar()) == EOF )
        {
            fprintf( stderr, "input error while verifying \"%s\" label!\n", label );
            exit( EXIT_FAILURE );
        }

        if ( (uint_fast8_t)*p != c )
        {
            fprintf( stderr, "did not find \"%s\" label in expected position!\n", label );
            exit( EXIT_FAILURE );
        }
    }

    fprintf( stderr, "\"%s\" label OK\n", label );
}

uint_fast32_t ReadNumber( int num_bytes, const char *fieldname )
{
    int c;

    uint_fast32_t sum = 0;

    if ( num_bytes < 1 || num_bytes > 4 )
    {
        fprintf( stderr, "this function only supports reading between 1 and 4 bytes\n" );
        exit( EXIT_FAILURE );
    }

    for ( int i = 0; i < num_bytes; i++ )
    {
        if ( (c = getchar()) == EOF )
        {
            fprintf( stderr, "input error while reading field \"%s\"!\n", fieldname );
            exit( EXIT_FAILURE );
        }

        sum += (uint_fast8_t)c << 8 * i;
    }

    //On most platforms, unsigned int is 32-bits. On those platforms, the following
    //line is equivalent to:
    //fprintf( stderr, "%s: %u\n", fieldname, sum );
    fprintf( stderr, "%s: %" PRIuFAST32 "\n", fieldname, sum );

    return sum;
}

这是我程序的示例输出:

"RIFF" label OK
ChunkSize: 88236
"WAVE" label OK
"fmt " label OK
Subchunk1Size: 16
AudioFormat: 1
NumChannels: 1
SampleRate: 44100
ByteRate: 44100
BlockAlign: 1
BitePerSample: 8
"data" label OK
Subchunk2Size: 88200