2014 dxdy logo

Научный форум dxdy

Математика, Физика, Computer Science, Machine Learning, LaTeX, Механика и Техника, Химия,
Биология и Медицина, Экономика и Финансовая Математика, Гуманитарные науки




Начать новую тему Ответить на тему
 
 Как кодируется звук?
Сообщение08.10.2022, 00:13 


19/11/20
297
Москва
Я решил написать программу, которая записывает полигармонический сигнал в WAV файл (то есть можно будет его прослушать). Вроде получилось, но результаты немного отличаются от того, чего я ожидал. Для начала опишу, как всё работает.
Допустим, у нас есть следующий сигнал: $10\sin{(2\pi \cdot 100)}$
1)Определим нужные для кодирования параметры:
частота дискретизации($f_s$) - $44100$ Гц
количество каналов($chan$) - $1$
количество бит для одного семпла($bps$) - $16$
длина записи($t$) - $5$ с
амплитуда сигнала($A_m$) - $10$
частота сигнала($f$) - $100$ Гц
2)Запишем формулу для вычисления каждого дискретного отсчёта:
$x_i=A_m\sin{(\frac{2\pi ft_i}{f_s})}$
3)Для отображения сигнала в каком-нибудь матлабе формула из п.2 сгодится, но закодировать такое не получится. Первая проблема - отрицательная часть. Я решил сдвинуть синусоиду вверх, чтобы она изменялась не от $-10$ до $10$, а от $0$ до $20$. Сделаем это:
$x_i=A_m\sin{(\frac{2\pi ft_i}{f_s})} + A_m$
Вторая проблема - нужно как-то это свести к шестнадцатеричному числу, которое изменяется от $0$ до $2^{bps}$. Для этого введём дополнительную переменную $q=\frac{2A_m}{2^{bps}}$, после чего преобразуем нашу формулу в следующий вид:
$x_i=\frac{A_m\sin{(\frac{2\pi ft_i}{f_s})} + A_m}{q}=\sin(\frac{2\pi f t_i}{f_s} + 1)\cdot 2^{bps-1}$
Мы можем округлять $x_i$ по усечению, я думаю, в этом случае о погрешности можно особо не беспокоиться.

Вот, собственно, неожиданности:
1)$q=\frac{2A_m}{2^{bps}}$, тут в числителе $A_m$ только из-за того, что это максимальное значение, которое может встретиться в сигнале. То есть в общем случае в числителе стоит максимальная амплитуда всех гармоник сигнала. Как мне казалось - чем амплитуда гармоники меньше, тем тише получается результат. Я попробовал сделать следующее: $q=\frac{A_m \cdot 10}{2^{bps}}$, всё остальное остаётся прежним. По идее сигнал должен стать в пять раз тише. На деле звук, конечно, становится тише, но помимо этого он ещё и сам становится менее чётким, больше похоже на гул. Я подумал, что это из-за низкого разрешения и попробовал умножать всего лишь на два. Результат стал чуть-чуть громче, но сам звук всё ещё очень сильно отличается от оригинала. На что влияет амплитуда? Как можно изменить громкость звука, не изменяя высоту сигнала? Есть ставить всё так, как я написал, то она, как мне кажется, максимальная (я даже не знал, что мои наушники могут так шуметь).
2)Сейчас я знаю максимум, до которого дойдёт сигнал, просто потому я сам его задаю. А как поступать со случайным сигналом? (тут скорее я прошу ссылку на какую-нибудь статью, я просто не очень даже понимаю, как это загуглить).
3)Этот вопрос вытекает из второго. Есть ли какие-то книги на эту тему? Я так понимаю, что это всё ЦОС, но обычно там не рассматривают всё это дело именно в контексте звука.

 Профиль  
                  
 
 Re: Как кодируется звук?
Сообщение08.10.2022, 00:22 
Заслуженный участник


26/05/14
981
Как у вас $+ 1$ внутрь синуса попала?

-- 08.10.2022, 00:39 --

Покажите код.

 Профиль  
                  
 
 Re: Как кодируется звук?
Сообщение08.10.2022, 00:40 


18/09/21
1704
Kevsh в сообщении #1566236 писал(а):
у нас есть следующий сигнал: $10\sin{(2\pi \cdot 100)}$
Это не сигнал, а число $0$.

 Профиль  
                  
 
 Re: Как кодируется звук?
Сообщение08.10.2022, 15:16 


19/11/20
297
Москва
slavav
Да, действительно, ошибка в формуле (должно быть $x_i=(\sin{(\frac{2\pi f t_i}{f_s})} + 1)2^{bps-1}$). Я её в своём коде не использовал, так что проблема не в ней.
Вот так выглядит основной код (на C++):
Код:
    int depth = pow(2, bitsPerSample); //количество уровней квантования
    float level = GetMaxAm(ams, harmsCount), //находим максимальную гармонику
        quant = level * 2 / depth;
    const float pi = 3.14159265;

    for (int t = 0; t < sampleRate * time; ++t)
    {
        int sample = 0;
        for (int j = 0; j < harmsCount; ++j)
            sample += static_cast<int>(((ams[j] * sin(2 * pi * f * t * harms[j] / sampleRate) + ams[j]) / quant)); //static_cast нужен для того, чтобы отбросить дробную часть
        fout.write(reinterpret_cast<char*>(&sample), 2); //запись в файл
    }

В массиве ams хранятся амплитуды, в массиве harms хранятся номера гармоник.

-- 08.10.2022, 15:19 --

zykov
Да, конечно, правильная формула $10\sin{(2\pi \cdot 100 t)}$

 Профиль  
                  
 
 Re: Как кодируется звук?
Сообщение08.10.2022, 17:00 
Заслуженный участник


26/05/14
981
У вас переполнение. Вы складываете несколько гармоник. В сумме они могут быть больше level - переполнение. Замените GetMaxAm на GetSumAm.

 Профиль  
                  
 
 Re: Как кодируется звук?
Сообщение08.10.2022, 17:17 


19/11/20
297
Москва
slavav
Да, действительно, спасибо. Но тут проблема не в этом, в примере одна гармоника.

 Профиль  
                  
 
 Re: Как кодируется звук?
Сообщение08.10.2022, 19:04 
Заслуженный участник


26/05/14
981
Тогда приведите полный работающий пример. Мы гадаем, а вы говорите "а ошибка то не тут!". Так дело не пойдет.

 Профиль  
                  
 
 Re: Как кодируется звук?
Сообщение08.10.2022, 19:22 


19/11/20
297
Москва
slavav
Я выше привёл сигнал, который хочу прослушать: $10\sin{(2\pi\cdot 100t)}$
Выше я привёл код, он для данного сигнала сойдёт (переполнения не будет, потому что гармоника одна). Я его запускаю и получаю один звук (правильный он или нет сложно сказать, конечно, просто обычный звук длиной 5 сек). Потом я изменяю эту строчку:
Код:
quant = level * 2 / depth;

на
Код:
quant = level * 4 / depth;

то есть по сути увеличиваю количество уровней квантования в два раза. По идее звук должен просто стать в два раза выше, ведь в пике синусоида дойдёт до середины доступного диапазона шестнадцатеричных чисел. Однако в итоге я получаю звук, который отличается не только громкостью, но и какими-то другими параметрами (он становится более глухим и т. д.). То есть это не похоже на то, что мы потянули вниз ползунок громкости.
Вот полный код, он рабочий и создаёт в папке проекте файл out.wav:
Код:
include <iostream>
#include <fstream>
#include <cmath>

float GetSumAm(float* ams, int size)
{
    int sum = 0;
    for (int i = 0; i < size; ++i)
        sum += ams[i];
    return sum;
}

void WriteWAVHeader(std::ofstream& file, int sampleRate, int bitsPerSample, int chan, int time)
{
    int fileSize = sampleRate * bitsPerSample * chan * time / 8;

    int chunkId = 0x46464952,
        chunkSize = fileSize + 44 - 8,
        format = 0x45564157,
        subchunk1Id = 0x20746D66,
        subchunk1Size = 0x00000010,
        audioFormat = 0x0001,
        byteRate = sampleRate * chan * bitsPerSample / 8,
        blockAlign = chan * bitsPerSample / 8,
        subchunk2Id = 0x61746164,
        subchunk2Size = fileSize;

    file.write(reinterpret_cast<char*>(&chunkId), 4); //RIFF
    file.write(reinterpret_cast<char*>(&chunkSize), 4); //Размер файла минус 8
    file.write(reinterpret_cast<char*>(&format), 4); //Формат WAV
    file.write(reinterpret_cast<char*>(&subchunk1Id), 4); //Содержит символы fmt
    file.write(reinterpret_cast<char*>(&subchunk1Size), 4); //Оставшийся заголовок
    file.write(reinterpret_cast<char*>(&audioFormat), 2);     //Формат аудио
    file.write(reinterpret_cast<char*>(&chan), 2);     //Количество каналов
    file.write(reinterpret_cast<char*>(&sampleRate), 4); //Частота дискретизации
    file.write(reinterpret_cast<char*>(&byteRate), 4); //Количество байт в секунду
    file.write(reinterpret_cast<char*>(&blockAlign), 2);     //Количество байт для семпла
    file.write(reinterpret_cast<char*>(&bitsPerSample), 2);     //Количество бит за семпл
    file.write(reinterpret_cast<char*>(&subchunk2Id), 4); //Метка start
    file.write(reinterpret_cast<char*>(&subchunk2Size), 4); //Размер файла
}

int GetSignal(int sampleRate, int bitsPerSample, int chan, int time,
    int f, float* ams, float* harms, int harmsCount, char* filePath)
{
    //File
    std::ofstream fout;
    fout.open(filePath, std::ios::binary | std::ios::out);
    //Additional signal parameters and constants
    int depth = pow(2, bitsPerSample);
    float level = GetSumAm(ams, harmsCount), quant = level * 2 / depth;
    const float pi = 3.14159265;
    //WAV's header
    WriteWAVHeader(fout, sampleRate, bitsPerSample, chan, time);
    //Getting signal
    for (int t = 0; t < sampleRate * time; ++t)
    {
        int sample = 0;
        for (int j = 0; j < harmsCount; ++j)
            sample += static_cast<int>(((ams[j] * sin(2 * pi * f * t * harms[j] / sampleRate) + ams[j]) / quant));
        fout.write(reinterpret_cast<char*>(&sample), 2);
    }
    return 0;
}

int main()
{
    int harmsCount = 1;
    float* ams = new float[harmsCount];
    ams[0] = 10.0;
    float* harms = new float[harmsCount];
    harms[0] = 1;
    char path[9] = "out.wav\0";
    GetSignal(44100, 16, 1, 5, 100, ams, harms, harmsCount, path);
}

 Профиль  
                  
 
 Re: Как кодируется звук?
Сообщение08.10.2022, 19:45 


18/09/21
1704
Kevsh в сообщении #1566236 писал(а):
Сделаем это:
$x_i=A_m\sin{(\frac{2\pi ft_i}{f_s})} + A_m$
Здесь ерундой какой-то занимаететсь.
Там пишется целое число со знаком. Не надо его делать положительным. Так и делайте синусоиду около нуля (как центра).

 Профиль  
                  
 
 Re: Как кодируется звук?
Сообщение08.10.2022, 21:04 
Заслуженный участник


26/05/14
981
WAVE PCM soundfile format:
Цитата:
16-bit samples are stored as 2's-complement signed integers, ranging from -32768 to 32767.

 Профиль  
                  
 
 Re: Как кодируется звук?
Сообщение08.10.2022, 21:14 


18/09/21
1704
Kevsh в сообщении #1566272 писал(а):
По идее звук должен просто стать в два раза выше, ведь в пике синусоида дойдёт до середины доступного диапазона шестнадцатеричных чисел. Однако в итоге я получаю звук, который отличается не только громкостью, но и какими-то другими параметрами (он становится более глухим и т. д.).
Нет, это он перелетит из максимального положительного значения в минимальное отрицательное. Это даст резкий скачок со спектром размытым по всем частотам.
Вобщем, оставайтесь в диапазоне "-32768 to 32767" без переполнения (можно специально програмное ограничение ввести).

PS: "звук должен просто стать в два раза выше" - не выше, а громче. "Выше" говорят, когда частота будет выше.

 Профиль  
                  
 
 Re: Как кодируется звук?
Сообщение08.10.2022, 22:07 


19/11/20
297
Москва
zykov
Да, действительно, сдвигать ничто никуда не нужно – теперь всё работает. Тогда я понимаю не это. Проблема теперь с хранением данных в бинарном файле. Допустим, я хочу сделать такую запись:
Код:
int a= -1;
file.write(reinterpret_cast<char*>(&a), 2);

В файл записывается вот это:
Код:
FF FF

Допустим, это обратный код. А знак где хранится тогда? Я из-за этого подумал, что отрицательные числа вообще записывать нельзя и сдвинул синусоиду.

 Профиль  
                  
 
 Re: Как кодируется звук?
Сообщение08.10.2022, 22:29 


14/01/11
2945
Kevsh в сообщении #1566281 писал(а):
Допустим, это обратный код.

В данном случае не обратный, а дополнительный.

 Профиль  
                  
 
 Re: Как кодируется звук?
Сообщение08.10.2022, 22:39 


18/09/21
1704
Kevsh в сообщении #1566281 писал(а):
А знак где хранится тогда?
Знак хранится в старшем бите.
0xFFFF - значит "-1" для 16-битного целого со знаком. (Если из 0x0000 вычесть 1, то будет 0xFFFF.)

Но вот это:
Kevsh в сообщении #1566281 писал(а):
Используется синтаксис C++
int a= -1;
file.write(reinterpret_cast<char*>(&a), 2);
не правильно.
Скорее всего 'int' это 32-битный целый со знаком (так почти везде на современных персоналках).
Для положительных ещё сработает обрезание до 16 бит, а для отрицательных вообще говоря не верно. Хотя и сработает для отрицательных не выходящих за пределы.
Лучше сделать
Используется синтаксис C++
#include <cstdint>
int16_t a = -1;
file.write(reinterpret_cast<char*>(&a), sizeof(a));

 Профиль  
                  
 
 Re: Как кодируется звук?
Сообщение09.10.2022, 11:20 


19/11/20
297
Москва
zykov
Большое спасибо. Что-то я не задумывался, как информация хранится в бинарных файлах. Думал, что дополнительный код нужен только для совсем уж низкоуровневых вещей.

 Профиль  
                  
Показать сообщения за:  Поле сортировки  
Начать новую тему Ответить на тему  [ Сообщений: 15 ] 

Модераторы: Karan, Toucan, PAV, maxal, Супермодераторы



Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей


Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете добавлять вложения

Найти:
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group