обработка файла audio wav с помощью C
Я работаю над обработкой амплитуды 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), и я подозреваю, что это связано с тем, что я пишу весь буфер на выход, даже если файл, возможно, завершился до заполнения всего размера буфера. Кроме того, выходной файл испорчен - не будет воспроизводиться или открываться, поэтому я, вероятно, делаю все неправильно. Любые советы о том, где я ошибаюсь будет большой. Спасибо!
5 ответов
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 и factor = 1.5 будет обертывать целое значение вместо того, чтобы зажимать его до максимума 32767.
3 вы вообще не смотрите на рифф и другие заголовки. В WAV-файлах возможно, что за звуковыми данными следуют некоторые информационные колонтитулы или предшествующие дополнительные заголовки. Или другими словами: Ваш header
struct-это слишком статично. Вы также должны прочитать формат 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
. На этой веб-странице есть список "других подобных проектов", на которые вы также можете посмотреть. The sndfile-tools
может быть хорошим примером кода, чтобы узнать, как использовать библиотеку.
Я бы рекомендовал посмотреть исходный файл и выходной файл в шестнадцатеричном редакторе, чтобы увидеть, правильно ли вы переписываете данные. Если полученный файл не будет воспроизводиться или открываться, скорее всего, заголовок выходного файла неверен.
другой вариант-удалить логику обработки звука и просто прочитать исходный файл во внутренний буфер и записать его в файл. Если ваш код может генерировать допустимый, рабочий выходной файл таким образом, вы можете сузить проблема с вашим кодом обработки.
вы также можете начать с меньшего файла, чем 40Mb. Если ничего другого, сделайте копию этого входного файла и сократите его до нескольких секунд звука. Файл меньшего размера будет легче проверить.
Edit: звонки fread()
и fwrite()
необходимо проверить их возвращаемые значения. Эти функции возвращают количество элементов, прочитанных или записанных, и если вызов любой функции возвращает значение меньше, чем ожидалось, это может быть источником разницы в размере файла.
кроме того, второй параметр fread
в байт. Поэтому, если вы хотите прочитать-заполните весь буфер, вам нужно будет сказать что-то вроде fread(inbuff16, sizeof(inbuff16[0]), BUFSIZE, infile);
. Текущий код будет считываться только в BUFSIZE
bytes (который работает для 8-битного случая по совпадению, но я бы рекомендовал изменить его тоже для ясности).
эта следующая строка также не нужна для чтения заголовков WAV (делает заголовок длиной 48 байт вместо "стандартного" 44):
short int extra_param_size;
Если возможно, вы можете посмотреть на другой язык, чем C, если он не предназначен специально для приложения C.
- например, python имеет хороший WAV-пакет, который легко читает и записывает wav-файлы.
- для более профессионального или академического использования первым шагом является MATLAB, который также очень легко читает wav-файлы (непосредственно в векторы, которые затем работают как одиночные выражения).