обработка аудиофайла wav с помощью C
-
20-09-2019 - |
Вопрос
Я работаю над обработкой амплитуды 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 Мб), и я подозреваю, что это связано с тем фактом, что я записываю в выходной файл весь буфер, даже несмотря на то, что файл, возможно, завершился до заполнения всего размера буфера.Кроме того, выходной файл испорчен - не воспроизводится и не открывается, - так что я, вероятно, все делаю неправильно.Любые советы о том, где я напортачил, будут замечательными.Спасибо!
Решение
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 Вы вообще не смотрите на РИФФ и другие заголовки.В 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 И, наконец, вы изобретаете велосипед заново.Пока это для обучения, все в порядке.В противном случае вам следует использовать существующие библиотеки.
Другие советы
Гораздо лучше использовать библиотеки для чтения и записи звуковых файлов.Например. libsndfile
.На этой веб-странице есть список "других подобных проектов", с которыми вы также можете ознакомиться.В sndfile-tools
могли бы быть хорошие примеры кода, чтобы узнать, как использовать библиотеку.
Я бы рекомендовал просмотреть исходный файл и выходной файл в шестнадцатеричном редакторе, чтобы убедиться, правильно ли вы переписываете данные.Если результирующий файл не будет воспроизводиться или открываться, скорее всего, заголовок выходного файла неверен.
Другой вариант - удалить вашу логику обработки звука и просто прочитать исходный файл из вашего внутреннего буфера и записать его в файл.Если ваш код может сгенерировать таким образом действительный рабочий выходной файл, то вы можете сузить проблему до вашего обрабатывающего кода.
Возможно, вы также захотите начать с файла размером менее 40 МБ.Если ничего другого нет, сделайте копию этого входного файла и сократите его до пары секунд звука.Файл меньшего размера будет легче проверить вручную.
Редактировать: Призывы к fread()
и fwrite()
необходимо проверить их возвращаемые значения.Эти функции возвращают количество прочитанных или записанных элементов, и если вызов любой из функций возвращает значение меньше ожидаемого, то это может быть причиной разницы в размере вашего файла.
Кроме того, второй параметр для fread
выражается в байтах.Поэтому, если вы хотите заполнить чтением весь буфер целиком, вам нужно было бы сказать что-то более похожее fread(inbuff16, sizeof(inbuff16[0]), BUFSIZE, infile);
.Текущий код будет считываться только в BUFSIZE
байт (что по совпадению работает для 8-битного регистра, но я бы рекомендовал изменить и его для наглядности).
Эта следующая строка также не нужна для чтения заголовков WAV (длина заголовка составляет 48 байт вместо "стандартных" 44).:
short int extra_param_size;
Если возможно, вы можете захотеть посмотреть на другой язык, отличный от C, если только он не предназначен специально для приложения на C.
- Например, у python есть хороший wav-пакет, который легко считывает и записывает wav-файлы.
- Для более профессионального или академического использования первым шагом является MATLAB, который также очень легко считывает wav-файлы (непосредственно в векторы, с которыми затем работают как с отдельными выражениями).