1#include "audioProcessor.hpp"
2
3#include <iomanip>
4/* Reading and writing audio files using ffmpeg */
5void AudioProcessor::readAudioWithFFmpeg() {
6 size_t readSamples = 0;
7 const int sample = 48 * 1000;
8 const size_t totalSamples = audioData.duration * sample;
9 audioData.samples.resize(totalSamples);
10 audioData.header.sampleRate = sample;
11
12 const std::string ffmpegInputCmd =
13 "ffmpeg -i " + inputFile + " -ar " + std::to_string(sample) + " -f s16le -ac 1 -";
14 if (FILE *pipein = popen(ffmpegInputCmd.c_str(), "r")) {
15 readSamples = fread(audioData.samples.data(), sizeof(int16_t), totalSamples, pipein);
16 pclose(pipein);
17
18 if (readSamples == 0) {
19 throw std::runtime_error("Failed to read input audio");
20 }
21 } else {
22 throw std::runtime_error("Failed to open input audio");
23 }
24}
25
26void AudioProcessor::writeAudioWithFFmpeg() {
27 const int sample = 48 * 1000;
28 const size_t readSamples = audioData.samples.size();
29 const std::vector<int16_t> &outputBuffer = audioData.samples;
30 const std::string ffmpegOutputCmd =
31 "ffmpeg -y -f s16le -ar " + std::to_string(sample) + " -ac 1 -i - " + outputFile;
32
33 if (FILE *pipeout = popen(ffmpegOutputCmd.c_str(), "w")) {
34 fwrite(outputBuffer.data(), sizeof(int16_t), readSamples, pipeout);
35 pclose(pipeout);
36 } else {
37 throw std::runtime_error("Failed to open output pipe");
38 }
39}
40
41void AudioProcessor::resample(uint32_t expectedSampleRate) {
42 if (audioData.header.sampleRate == expectedSampleRate) {
43 return;
44 }
45 uint32_t oldSampleRate = audioData.header.sampleRate;
46 float ratio = static_cast<float>(expectedSampleRate) / oldSampleRate;
47 size_t newLength = static_cast<size_t>(std::ceil(audioData.samples.size() * ratio));
48 std::vector<int16_t> output(newLength);
49
50 // Linear interpolation
51 for (size_t i = 0; i < newLength; i++) {
52 float pos = i / ratio;
53 size_t idx = static_cast<size_t>(pos);
54 float frac = pos - idx;
55 output[i] = (idx + 1 < audioData.samples.size())
56 ? static_cast<int16_t>(
57 audioData.samples[idx] +
58 frac * (audioData.samples[idx + 1] - audioData.samples[idx]))
59 : audioData.samples[idx];
60 }
61 audioData.samples = output;
62 audioData.header.sampleRate = expectedSampleRate;
63 audioData.header.dataSize = output.size() * sizeof(int16_t);
64 audioData.header.chunkSize = audioData.header.dataSize + 36;
65 audioData.header.byteRate =
66 audioData.header.sampleRate * audioData.header.numChannels * sizeof(int16_t);
67 audioData.duration = calculateDuration(audioData.header);
68 /* we don't need to change this coz we are not changing the number of channels
69 newHeader.blockAlign = newHeader.numChannels * sizeof(int16_t); */
70
71 std::cout << "Resampled audio from " << oldSampleRate << " to " << expectedSampleRate << "Hz"
72 << std::endl;
73}
74
75void AudioProcessor::readRawAudioFile(bool headerOnly) {
76 std::ifstream file(inputFile, std::ios::binary);
77 if (!file) throw std::runtime_error("File not found!");
78 if (!file.is_open()) throw std::runtime_error("Failed to open file!");
79
80 file.read(reinterpret_cast<char *>(&audioData.header), sizeof(WAVHeader));
81 if (std::string(audioData.header.chunkID, 4) != "RIFF" ||
82 std::string(audioData.header.format, 4) != "WAVE")
83 throw std::runtime_error("Not a valid WAV file!");
84
85 while (std::string(audioData.header.dataID, 4) != "data") {
86 file.seekg(audioData.header.dataSize, std::ios::cur);
87 file.read(audioData.header.dataID, 4);
88 file.read(reinterpret_cast<char *>(&audioData.header.dataSize),
89 sizeof(audioData.header.dataSize));
90 if (file.eof()) {
91 std::cerr << "No 'data' chunk found in the WAV file." << std::endl;
92 throw std::runtime_error("Invalid WAV file");
93 }
94 }
95
96 audioData.duration = calculateDuration(audioData.header);
97 audioData.samples.resize(audioData.header.dataSize / sizeof(int16_t));
98 if (!headerOnly)
99 file.read(reinterpret_cast<char *>(audioData.samples.data()), audioData.header.dataSize);
100 file.close();
101}
102
103void AudioProcessor::writeRawAudioFile() {
104 std::ofstream file(outputFile, std::ios::binary);
105 file.write(reinterpret_cast<char *>(&audioData.header), sizeof(WAVHeader));
106 file.write(reinterpret_cast<char *>(audioData.samples.data()), audioData.header.dataSize);
107 file.close();
108}
109
110void AudioProcessor::processFrame(const int16_t *input, int16_t *output, int frameSize) {
111 std::vector<int16_t> frame(input, input + frameSize);
112 size_t expectedFrameSize = audioData.header.sampleRate / 1000 * MAX_PROCESSABLE_MS;
113
114 if (frameSize < (int)expectedFrameSize) {
115 frame.resize(expectedFrameSize, 0);
116 }
117 agcManager->process(frame);
118 std::copy(frame.begin(), frame.begin() + frameSize, output);
119}
120
121void AudioProcessor::performAGC() {
122 size_t frameCount = 0;
123 size_t readSamples = audioData.samples.size();
124 size_t frameSize = audioData.header.sampleRate / 1000 * MAX_PROCESSABLE_MS;
125 std::vector<int16_t> outputBuffer(readSamples);
126
127 if (readSamples == 0) {
128 throw std::runtime_error("No audio samples found");
129 }
130 bool supportedSampleRate = false;
131 for (uint32_t i : SUPPORTED_SAMPLE_RATES) {
132 if (audioData.header.sampleRate == i) {
133 supportedSampleRate = true;
134 break;
135 }
136 }
137 if (!supportedSampleRate) {
138 throw std::runtime_error("Unsupported sample rate");
139 }
140
141 agcManager = std::make_unique<AGC>(audioData.header.sampleRate);
142 auto processStart = std::chrono::steady_clock::now();
143
144 std::cout << "Processing audio..." << std::endl;
145 double percentage = 0.0;
146 for (size_t i = 0; i < readSamples; i += frameSize) {
147 size_t remainingSamples = std::min(frameSize, readSamples - i);
148 processFrame(audioData.samples.data() + i, outputBuffer.data() + i, remainingSamples);
149 percentage = (static_cast<double>(i) / readSamples) * 100;
150 std::cout << "Processed " << std::fixed << std::setprecision(2) << percentage << "%\r";
151 frameCount++;
152 }
153 audioData.samples = std::move(outputBuffer);
154 auto processEnd = std::chrono::steady_clock::now();
155
156 std::cout
157 << "Time taken to process " << frameCount << " frames: "
158 << std::chrono::duration_cast<std::chrono::milliseconds>(processEnd - processStart).count()
159 << "ms" << std::endl;
160}