blob: 12b4ab3a4fb7d8a247131240580b22aab38650a0 [file] [log] [blame]
Richard Burton00553462021-11-10 16:27:14 +00001/*
2 * Copyright (c) 2021 Arm Limited. All rights reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17#include <cmath>
18#include <algorithm>
19
20#include "UseCaseHandler.hpp"
21#include "hal.h"
22#include "UseCaseCommonUtils.hpp"
23#include "AudioUtils.hpp"
24#include "InputFiles.hpp"
25#include "RNNoiseModel.hpp"
26#include "RNNoiseProcess.hpp"
27
28namespace arm {
29namespace app {
30
31 /**
32 * @brief Helper function to increment current audio clip features index.
33 * @param[in,out] ctx Pointer to the application context object.
34 **/
35 static void IncrementAppCtxClipIdx(ApplicationContext& ctx);
36
37 /**
38 * @brief Quantize the given features and populate the input Tensor.
39 * @param[in] inputFeatures Vector of floating point features to quantize.
40 * @param[in] quantScale Quantization scale for the inputTensor.
41 * @param[in] quantOffset Quantization offset for the inputTensor.
42 * @param[in,out] inputTensor TFLite micro tensor to populate.
43 **/
44 static void QuantizeAndPopulateInput(rnn::vec1D32F& inputFeatures,
45 float quantScale, int quantOffset,
46 TfLiteTensor* inputTensor);
47
48 /* Noise reduction inference handler. */
49 bool NoiseReductionHandler(ApplicationContext& ctx, bool runAll)
50 {
51 constexpr uint32_t dataPsnTxtInfStartX = 20;
52 constexpr uint32_t dataPsnTxtInfStartY = 40;
53
54 /* Variables used for memory dumping. */
55 size_t memDumpMaxLen = 0;
56 uint8_t* memDumpBaseAddr = nullptr;
57 size_t undefMemDumpBytesWritten = 0;
58 size_t *pMemDumpBytesWritten = &undefMemDumpBytesWritten;
59 if (ctx.Has("MEM_DUMP_LEN") && ctx.Has("MEM_DUMP_BASE_ADDR") && ctx.Has("MEM_DUMP_BYTE_WRITTEN")) {
60 memDumpMaxLen = ctx.Get<size_t>("MEM_DUMP_LEN");
61 memDumpBaseAddr = ctx.Get<uint8_t*>("MEM_DUMP_BASE_ADDR");
62 pMemDumpBytesWritten = ctx.Get<size_t*>("MEM_DUMP_BYTE_WRITTEN");
63 }
64 std::reference_wrapper<size_t> memDumpBytesWritten = std::ref(*pMemDumpBytesWritten);
65
66 auto& platform = ctx.Get<hal_platform&>("platform");
Richard Burton00553462021-11-10 16:27:14 +000067 auto& profiler = ctx.Get<Profiler&>("profiler");
68
69 /* Get model reference. */
70 auto& model = ctx.Get<RNNoiseModel&>("model");
71 if (!model.IsInited()) {
72 printf_err("Model is not initialised! Terminating processing.\n");
73 return false;
74 }
75
76 /* Populate Pre-Processing related parameters. */
77 auto audioParamsWinLen = ctx.Get<uint32_t>("frameLength");
78 auto audioParamsWinStride = ctx.Get<uint32_t>("frameStride");
79 auto nrNumInputFeatures = ctx.Get<uint32_t>("numInputFeatures");
80
81 TfLiteTensor* inputTensor = model.GetInputTensor(0);
82 if (nrNumInputFeatures != inputTensor->bytes) {
83 printf_err("Input features size must be equal to input tensor size."
84 " Feature size = %" PRIu32 ", Tensor size = %zu.\n",
85 nrNumInputFeatures, inputTensor->bytes);
86 return false;
87 }
88
89 TfLiteTensor* outputTensor = model.GetOutputTensor(model.m_indexForModelOutput);
90
91 /* Initial choice of index for WAV file. */
92 auto startClipIdx = ctx.Get<uint32_t>("clipIndex");
93
94 std::function<const int16_t* (const uint32_t)> audioAccessorFunc = get_audio_array;
95 if (ctx.Has("features")) {
96 audioAccessorFunc = ctx.Get<std::function<const int16_t* (const uint32_t)>>("features");
97 }
98 std::function<uint32_t (const uint32_t)> audioSizeAccessorFunc = get_audio_array_size;
99 if (ctx.Has("featureSizes")) {
100 audioSizeAccessorFunc = ctx.Get<std::function<uint32_t (const uint32_t)>>("featureSizes");
101 }
102 std::function<const char*(const uint32_t)> audioFileAccessorFunc = get_filename;
103 if (ctx.Has("featureFileNames")) {
104 audioFileAccessorFunc = ctx.Get<std::function<const char*(const uint32_t)>>("featureFileNames");
105 }
106 do{
Richard Burton9b8d67a2021-12-10 12:32:51 +0000107 platform.data_psn->clear(COLOR_BLACK);
108
Richard Burton00553462021-11-10 16:27:14 +0000109 auto startDumpAddress = memDumpBaseAddr + memDumpBytesWritten;
110 auto currentIndex = ctx.Get<uint32_t>("clipIndex");
111
112 /* Creating a sliding window through the audio. */
113 auto audioDataSlider = audio::SlidingWindow<const int16_t>(
114 audioAccessorFunc(currentIndex),
115 audioSizeAccessorFunc(currentIndex), audioParamsWinLen,
116 audioParamsWinStride);
117
118 info("Running inference on input feature map %" PRIu32 " => %s\n", currentIndex,
119 audioFileAccessorFunc(currentIndex));
120
121 memDumpBytesWritten += DumpDenoisedAudioHeader(audioFileAccessorFunc(currentIndex),
122 (audioDataSlider.TotalStrides() + 1) * audioParamsWinLen,
123 memDumpBaseAddr + memDumpBytesWritten,
124 memDumpMaxLen - memDumpBytesWritten);
125
126 rnn::RNNoiseProcess featureProcessor = rnn::RNNoiseProcess();
127 rnn::vec1D32F audioFrame(audioParamsWinLen);
128 rnn::vec1D32F inputFeatures(nrNumInputFeatures);
129 rnn::vec1D32F denoisedAudioFrameFloat(audioParamsWinLen);
130 std::vector<int16_t> denoisedAudioFrame(audioParamsWinLen);
131
132 std::vector<float> modelOutputFloat(outputTensor->bytes);
133 rnn::FrameFeatures frameFeatures;
134 bool resetGRU = true;
135
136 while (audioDataSlider.HasNext()) {
137 const int16_t* inferenceWindow = audioDataSlider.Next();
138 audioFrame = rnn::vec1D32F(inferenceWindow, inferenceWindow+audioParamsWinLen);
139
140 featureProcessor.PreprocessFrame(audioFrame.data(), audioParamsWinLen, frameFeatures);
141
142 /* Reset or copy over GRU states first to avoid TFLu memory overlap issues. */
143 if (resetGRU){
144 model.ResetGruState();
145 } else {
146 /* Copying gru state outputs to gru state inputs.
147 * Call ResetGruState in between the sequence of inferences on unrelated input data. */
148 model.CopyGruStates();
149 }
150
151 QuantizeAndPopulateInput(frameFeatures.m_featuresVec,
152 inputTensor->params.scale, inputTensor->params.zero_point,
153 inputTensor);
154
155 /* Strings for presentation/logging. */
156 std::string str_inf{"Running inference... "};
157
158 /* Display message on the LCD - inference running. */
159 platform.data_psn->present_data_text(
160 str_inf.c_str(), str_inf.size(),
161 dataPsnTxtInfStartX, dataPsnTxtInfStartY, false);
162
163 info("Inference %zu/%zu\n", audioDataSlider.Index() + 1, audioDataSlider.TotalStrides() + 1);
164
165 /* Run inference over this feature sliding window. */
166 profiler.StartProfiling("Inference");
167 bool success = model.RunInference();
168 profiler.StopProfiling();
169 resetGRU = false;
170
171 if (!success) {
172 return false;
173 }
174
175 /* De-quantize main model output ready for post-processing. */
176 const auto* outputData = tflite::GetTensorData<int8_t>(outputTensor);
177 auto outputQuantParams = arm::app::GetTensorQuantParams(outputTensor);
178
179 for (size_t i = 0; i < outputTensor->bytes; ++i) {
180 modelOutputFloat[i] = (static_cast<float>(outputData[i]) - outputQuantParams.offset)
181 * outputQuantParams.scale;
182 }
183
184 /* Round and cast the post-processed results for dumping to wav. */
185 featureProcessor.PostProcessFrame(modelOutputFloat, frameFeatures, denoisedAudioFrameFloat);
186 for (size_t i = 0; i < audioParamsWinLen; ++i) {
187 denoisedAudioFrame[i] = static_cast<int16_t>(std::roundf(denoisedAudioFrameFloat[i]));
188 }
189
190 /* Erase. */
191 str_inf = std::string(str_inf.size(), ' ');
192 platform.data_psn->present_data_text(
193 str_inf.c_str(), str_inf.size(),
194 dataPsnTxtInfStartX, dataPsnTxtInfStartY, false);
195
196 if (memDumpMaxLen > 0) {
197 /* Dump output tensors to memory. */
198 memDumpBytesWritten += DumpOutputDenoisedAudioFrame(
199 denoisedAudioFrame,
200 memDumpBaseAddr + memDumpBytesWritten,
201 memDumpMaxLen - memDumpBytesWritten);
202 }
203 }
204
205 if (memDumpMaxLen > 0) {
206 /* Needed to not let the compiler complain about type mismatch. */
207 size_t valMemDumpBytesWritten = memDumpBytesWritten;
208 info("Output memory dump of %zu bytes written at address 0x%p\n",
209 valMemDumpBytesWritten, startDumpAddress);
210 }
211
212 DumpDenoisedAudioFooter(memDumpBaseAddr + memDumpBytesWritten, memDumpMaxLen - memDumpBytesWritten);
213
Richard Burton9b8d67a2021-12-10 12:32:51 +0000214 info("All inferences for audio clip complete.\n");
Richard Burton00553462021-11-10 16:27:14 +0000215 profiler.PrintProfilingResult();
216 IncrementAppCtxClipIdx(ctx);
217
Ayaan Masood233cec02021-12-09 17:22:22 +0000218 std::string clearString{' '};
219 platform.data_psn->present_data_text(
220 clearString.c_str(), clearString.size(),
221 dataPsnTxtInfStartX, dataPsnTxtInfStartY, false);
222
223 std::string completeMsg{"Inference complete!"};
224
225 /* Display message on the LCD - inference complete. */
226 platform.data_psn->present_data_text(
227 completeMsg.c_str(), completeMsg.size(),
228 dataPsnTxtInfStartX, dataPsnTxtInfStartY, false);
229
Richard Burton00553462021-11-10 16:27:14 +0000230 } while (runAll && ctx.Get<uint32_t>("clipIndex") != startClipIdx);
231
232 return true;
233 }
234
235 size_t DumpDenoisedAudioHeader(const char* filename, size_t dumpSize,
236 uint8_t *memAddress, size_t memSize){
237
238 if (memAddress == nullptr){
239 return 0;
240 }
241
242 int32_t filenameLength = strlen(filename);
243 size_t numBytesWritten = 0;
244 size_t numBytesToWrite = 0;
245 int32_t dumpSizeByte = dumpSize * sizeof(int16_t);
246 bool overflow = false;
247
248 /* Write the filename length */
249 numBytesToWrite = sizeof(filenameLength);
250 if (memSize - numBytesToWrite > 0) {
251 std::memcpy(memAddress, &filenameLength, numBytesToWrite);
252 numBytesWritten += numBytesToWrite;
253 memSize -= numBytesWritten;
254 } else {
255 overflow = true;
256 }
257
258 /* Write file name */
259 numBytesToWrite = filenameLength;
260 if(memSize - numBytesToWrite > 0) {
261 std::memcpy(memAddress + numBytesWritten, filename, numBytesToWrite);
262 numBytesWritten += numBytesToWrite;
263 memSize -= numBytesWritten;
264 } else {
265 overflow = true;
266 }
267
268 /* Write dumpSize in byte */
269 numBytesToWrite = sizeof(dumpSizeByte);
270 if(memSize - numBytesToWrite > 0) {
271 std::memcpy(memAddress + numBytesWritten, &(dumpSizeByte), numBytesToWrite);
272 numBytesWritten += numBytesToWrite;
273 memSize -= numBytesWritten;
274 } else {
275 overflow = true;
276 }
277
278 if(false == overflow) {
279 info("Audio Clip dump header info (%zu bytes) written to %p\n", numBytesWritten, memAddress);
280 } else {
281 printf_err("Not enough memory to dump Audio Clip header.\n");
282 }
283
284 return numBytesWritten;
285 }
286
287 size_t DumpDenoisedAudioFooter(uint8_t *memAddress, size_t memSize){
288 if ((memAddress == nullptr) || (memSize < 4)) {
289 return 0;
290 }
291 const int32_t eofMarker = -1;
292 std::memcpy(memAddress, &eofMarker, sizeof(int32_t));
293
294 return sizeof(int32_t);
295 }
296
297 size_t DumpOutputDenoisedAudioFrame(const std::vector<int16_t> &audioFrame,
298 uint8_t *memAddress, size_t memSize)
299 {
300 if (memAddress == nullptr) {
301 return 0;
302 }
303
304 size_t numByteToBeWritten = audioFrame.size() * sizeof(int16_t);
305 if( numByteToBeWritten > memSize) {
George Gekova2b0fc22021-11-08 16:30:43 +0000306 printf_err("Overflow error: Writing %zu of %zu bytes to memory @ 0x%p.\n", memSize, numByteToBeWritten, memAddress);
Richard Burton00553462021-11-10 16:27:14 +0000307 numByteToBeWritten = memSize;
308 }
309
310 std::memcpy(memAddress, audioFrame.data(), numByteToBeWritten);
311 info("Copied %zu bytes to %p\n", numByteToBeWritten, memAddress);
312
313 return numByteToBeWritten;
314 }
315
316 size_t DumpOutputTensorsToMemory(Model& model, uint8_t* memAddress, const size_t memSize)
317 {
318 const size_t numOutputs = model.GetNumOutputs();
319 size_t numBytesWritten = 0;
320 uint8_t* ptr = memAddress;
321
322 /* Iterate over all output tensors. */
323 for (size_t i = 0; i < numOutputs; ++i) {
324 const TfLiteTensor* tensor = model.GetOutputTensor(i);
325 const auto* tData = tflite::GetTensorData<uint8_t>(tensor);
326#if VERIFY_TEST_OUTPUT
327 arm::app::DumpTensor(tensor);
328#endif /* VERIFY_TEST_OUTPUT */
329 /* Ensure that we don't overflow the allowed limit. */
330 if (numBytesWritten + tensor->bytes <= memSize) {
331 if (tensor->bytes > 0) {
332 std::memcpy(ptr, tData, tensor->bytes);
333
334 info("Copied %zu bytes for tensor %zu to 0x%p\n",
335 tensor->bytes, i, ptr);
336
337 numBytesWritten += tensor->bytes;
338 ptr += tensor->bytes;
339 }
340 } else {
341 printf_err("Error writing tensor %zu to memory @ 0x%p\n",
342 i, memAddress);
343 break;
344 }
345 }
346
347 info("%zu bytes written to memory @ 0x%p\n", numBytesWritten, memAddress);
348
349 return numBytesWritten;
350 }
351
352 static void IncrementAppCtxClipIdx(ApplicationContext& ctx)
353 {
354 auto curClipIdx = ctx.Get<uint32_t>("clipIndex");
355 if (curClipIdx + 1 >= NUMBER_OF_FILES) {
356 ctx.Set<uint32_t>("clipIndex", 0);
357 return;
358 }
359 ++curClipIdx;
360 ctx.Set<uint32_t>("clipIndex", curClipIdx);
361 }
362
363 void QuantizeAndPopulateInput(rnn::vec1D32F& inputFeatures,
364 const float quantScale, const int quantOffset, TfLiteTensor* inputTensor)
365 {
366 const float minVal = std::numeric_limits<int8_t>::min();
367 const float maxVal = std::numeric_limits<int8_t>::max();
368
369 auto* inputTensorData = tflite::GetTensorData<int8_t>(inputTensor);
370
371 for (size_t i=0; i < inputFeatures.size(); ++i) {
372 float quantValue = ((inputFeatures[i] / quantScale) + quantOffset);
373 inputTensorData[i] = static_cast<int8_t>(std::min<float>(std::max<float>(quantValue, minVal), maxVal));
374 }
375 }
376
377
378} /* namespace app */
379} /* namespace arm */