2014 dxdy logo

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

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




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


19/11/20
299
Москва
Я решил написать программу, которая записывает полигармонический сигнал в 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
1727
Kevsh в сообщении #1566236 писал(а):
у нас есть следующий сигнал: $10\sin{(2\pi \cdot 100)}$
Это не сигнал, а число $0$.

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


19/11/20
299
Москва
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
299
Москва
slavav
Да, действительно, спасибо. Но тут проблема не в этом, в примере одна гармоника.

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


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

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


19/11/20
299
Москва
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
1727
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
1727
Kevsh в сообщении #1566272 писал(а):
По идее звук должен просто стать в два раза выше, ведь в пике синусоида дойдёт до середины доступного диапазона шестнадцатеричных чисел. Однако в итоге я получаю звук, который отличается не только громкостью, но и какими-то другими параметрами (он становится более глухим и т. д.).
Нет, это он перелетит из максимального положительного значения в минимальное отрицательное. Это даст резкий скачок со спектром размытым по всем частотам.
Вобщем, оставайтесь в диапазоне "-32768 to 32767" без переполнения (можно специально програмное ограничение ввести).

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

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


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

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

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

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


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

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

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


18/09/21
1727
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
299
Москва
zykov
Большое спасибо. Что-то я не задумывался, как информация хранится в бинарных файлах. Думал, что дополнительный код нужен только для совсем уж низкоуровневых вещей.

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

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



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

Сейчас этот форум просматривают: dgwuqtj


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

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