обработка аудио WAV-файла с помощью C - PullRequest
7 голосов
/ 16 марта 2010

Я работаю над обработкой амплитуды 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;
}

В конце я получаю файлы разных размеров (примерно на 1 КБ или около того, для файла размером 40 МБ), и я подозреваю, что это связано с тем, что я записываю в буфер весь буфер, даже если файл может завершиться до заполнения всего размера буфера. Кроме того, выходной файл испорчен - не воспроизводится и не открывается - поэтому я, вероятно, все делаю неправильно. Любые советы о том, где я облажаюсь, будут отличными. Спасибо!

Ответы [ 5 ]

9 голосов
/ 16 марта 2010

1 Вы читаете байты вместо 16-битных выборок в этой ветке else:

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 И, наконец, вы заново изобретаете колесо. Пока это для обучения, все в порядке. В противном случае вы должны использовать существующие библиотеки.

5 голосов
/ 17 марта 2010

Гораздо лучше использовать библиотеки для чтения и записи звуковых файлов. Например. libsndfile. На этой веб-странице есть список «других похожих проектов», на которые вы также можете посмотреть. sndfile-tools может быть хорошим примером кода, чтобы узнать, как использовать библиотеку.

1 голос
/ 16 ноября 2011

Эта следующая строка также не нужна для чтения заголовков WAV (длина заголовка составляет 48 байт вместо «стандартных» 44):

short int extra_param_size;
1 голос
/ 16 марта 2010

Я бы рекомендовал просмотреть исходный файл и выходной файл в шестнадцатеричном редакторе, чтобы увидеть, правильно ли вы переписываете данные. Если полученный файл не воспроизводится или не открывается, скорее всего, заголовок выходного файла неверен.

Другой вариант - удалить логику обработки звука и просто прочитать исходный файл во внутренний буфер и записать его в файл. Если ваш код может сгенерировать действительный рабочий файл вывода таким образом, вы можете сузить проблему до кода обработки.

Вы также можете начать с файла меньшего размера, чем 40 МБ. Если ничего другого, сделайте копию этого входного файла и обрежьте его до пары секунд звука. Файл меньшего размера будет легче проверить вручную.

Редактировать: Для вызовов fread() и fwrite() необходимо проверить свои возвращаемые значения. Эти функции возвращают количество прочитанных или записанных элементов, и если вызов одной из функций возвращает значение меньше ожидаемого, это может быть причиной разницы в размере вашего файла.

Кроме того, второй параметр fread указан в байтах. Поэтому, если вы хотите прочитать и заполнить весь буфер, вам нужно будет сказать что-то более похожее на fread(inbuff16, sizeof(inbuff16[0]), BUFSIZE, infile);. Текущий код будет читаться только в BUFSIZE байтах (это работает для 8-битного случая по совпадению, но я бы порекомендовал изменить его также для ясности).

0 голосов
/ 18 августа 2011

Если возможно, вы можете захотеть взглянуть на язык, отличный от C, если только он не предназначен специально для приложения на языке C.

  • Например, в python есть хороший wav-пакет, который легко читает и записывает wav-файлы.
  • Для более профессионального или академического использования первым шагом является MATLAB, который также очень легко читает файлы wav (непосредственно в векторы, которые затем обрабатываются как одиночные выражения).
...