xref: /aosp_15_r20/external/webrtc/test/fuzzers/audio_processing_sample_rate_fuzzer.cc (revision d9f758449e529ab9291ac668be2861e7a55c2422)
1 /*
2  *  Copyright (c) 2022 The WebRTC project authors. All Rights Reserved.
3  *
4  *  Use of this source code is governed by a BSD-style license
5  *  that can be found in the LICENSE file in the root of the source
6  *  tree. An additional intellectual property rights grant can be found
7  *  in the file PATENTS.  All contributing project authors may
8  *  be found in the AUTHORS file in the root of the source tree.
9  */
10 
11 #include <algorithm>
12 #include <array>
13 #include <cmath>
14 #include <limits>
15 
16 #include "modules/audio_processing/include/audio_processing.h"
17 #include "modules/audio_processing/test/audio_processing_builder_for_testing.h"
18 #include "rtc_base/checks.h"
19 #include "test/fuzzers/fuzz_data_helper.h"
20 
21 namespace webrtc {
22 namespace {
23 constexpr int kMaxNumChannels = 2;
24 // APM supported max rate is 384000 Hz, using a limit slightly above lets the
25 // fuzzer exercise the handling of too high rates.
26 constexpr int kMaxSampleRateHz = 400000;
27 constexpr int kMaxSamplesPerChannel = kMaxSampleRateHz / 100;
28 
GenerateFloatFrame(test::FuzzDataHelper & fuzz_data,int input_rate,int num_channels,float * const * float_frames)29 void GenerateFloatFrame(test::FuzzDataHelper& fuzz_data,
30                         int input_rate,
31                         int num_channels,
32                         float* const* float_frames) {
33   const int samples_per_input_channel =
34       AudioProcessing::GetFrameSize(input_rate);
35   RTC_DCHECK_LE(samples_per_input_channel, kMaxSamplesPerChannel);
36   for (int i = 0; i < num_channels; ++i) {
37     float channel_value;
38     fuzz_data.CopyTo<float>(&channel_value);
39     std::fill(float_frames[i], float_frames[i] + samples_per_input_channel,
40               channel_value);
41   }
42 }
43 
GenerateFixedFrame(test::FuzzDataHelper & fuzz_data,int input_rate,int num_channels,int16_t * fixed_frames)44 void GenerateFixedFrame(test::FuzzDataHelper& fuzz_data,
45                         int input_rate,
46                         int num_channels,
47                         int16_t* fixed_frames) {
48   const int samples_per_input_channel =
49       AudioProcessing::GetFrameSize(input_rate);
50   RTC_DCHECK_LE(samples_per_input_channel, kMaxSamplesPerChannel);
51   // Write interleaved samples.
52   for (int ch = 0; ch < num_channels; ++ch) {
53     const int16_t channel_value = fuzz_data.ReadOrDefaultValue<int16_t>(0);
54     for (int i = ch; i < samples_per_input_channel * num_channels;
55          i += num_channels) {
56       fixed_frames[i] = channel_value;
57     }
58   }
59 }
60 
61 // No-op processor used to influence APM input/output pipeline decisions based
62 // on what submodules are present.
63 class NoopCustomProcessing : public CustomProcessing {
64  public:
NoopCustomProcessing()65   NoopCustomProcessing() {}
~NoopCustomProcessing()66   ~NoopCustomProcessing() override {}
Initialize(int sample_rate_hz,int num_channels)67   void Initialize(int sample_rate_hz, int num_channels) override {}
Process(AudioBuffer * audio)68   void Process(AudioBuffer* audio) override {}
ToString() const69   std::string ToString() const override { return ""; }
SetRuntimeSetting(AudioProcessing::RuntimeSetting setting)70   void SetRuntimeSetting(AudioProcessing::RuntimeSetting setting) override {}
71 };
72 }  // namespace
73 
74 // This fuzzer is directed at fuzzing unexpected input and output sample rates
75 // of APM. For example, the sample rate 22050 Hz is processed by APM in frames
76 // of floor(22050/100) = 220 samples. This is not exactly 10 ms of audio
77 // content, and may break assumptions commonly made on the APM frame size.
FuzzOneInput(const uint8_t * data,size_t size)78 void FuzzOneInput(const uint8_t* data, size_t size) {
79   if (size > 100) {
80     return;
81   }
82   test::FuzzDataHelper fuzz_data(rtc::ArrayView<const uint8_t>(data, size));
83 
84   std::unique_ptr<CustomProcessing> capture_processor =
85       fuzz_data.ReadOrDefaultValue(true)
86           ? std::make_unique<NoopCustomProcessing>()
87           : nullptr;
88   std::unique_ptr<CustomProcessing> render_processor =
89       fuzz_data.ReadOrDefaultValue(true)
90           ? std::make_unique<NoopCustomProcessing>()
91           : nullptr;
92   rtc::scoped_refptr<AudioProcessing> apm =
93       AudioProcessingBuilderForTesting()
94           .SetConfig({.pipeline = {.multi_channel_render = true,
95                                    .multi_channel_capture = true}})
96           .SetCapturePostProcessing(std::move(capture_processor))
97           .SetRenderPreProcessing(std::move(render_processor))
98           .Create();
99   RTC_DCHECK(apm);
100 
101   std::array<int16_t, kMaxSamplesPerChannel * kMaxNumChannels> fixed_frame;
102   std::array<std::array<float, kMaxSamplesPerChannel>, kMaxNumChannels>
103       float_frames;
104   std::array<float*, kMaxNumChannels> float_frame_ptrs;
105   for (int i = 0; i < kMaxNumChannels; ++i) {
106     float_frame_ptrs[i] = float_frames[i].data();
107   }
108   float* const* ptr_to_float_frames = &float_frame_ptrs[0];
109 
110   // Choose whether to fuzz the float or int16_t interfaces of APM.
111   const bool is_float = fuzz_data.ReadOrDefaultValue(true);
112 
113   // We may run out of fuzz data in the middle of a loop iteration. In
114   // that case, default values will be used for the rest of that
115   // iteration.
116   while (fuzz_data.CanReadBytes(1)) {
117     // Decide input/output rate for this iteration.
118     const int input_rate = static_cast<int>(
119         fuzz_data.ReadOrDefaultValue<size_t>(8000) % kMaxSampleRateHz);
120     const int output_rate = static_cast<int>(
121         fuzz_data.ReadOrDefaultValue<size_t>(8000) % kMaxSampleRateHz);
122     const int num_channels = fuzz_data.ReadOrDefaultValue(true) ? 2 : 1;
123 
124     // Since render and capture calls have slightly different reinitialization
125     // procedures, we let the fuzzer choose the order.
126     const bool is_capture = fuzz_data.ReadOrDefaultValue(true);
127 
128     int apm_return_code = AudioProcessing::Error::kNoError;
129     if (is_float) {
130       GenerateFloatFrame(fuzz_data, input_rate, num_channels,
131                          ptr_to_float_frames);
132 
133       if (is_capture) {
134         apm_return_code = apm->ProcessStream(
135             ptr_to_float_frames, StreamConfig(input_rate, num_channels),
136             StreamConfig(output_rate, num_channels), ptr_to_float_frames);
137       } else {
138         apm_return_code = apm->ProcessReverseStream(
139             ptr_to_float_frames, StreamConfig(input_rate, num_channels),
140             StreamConfig(output_rate, num_channels), ptr_to_float_frames);
141       }
142     } else {
143       GenerateFixedFrame(fuzz_data, input_rate, num_channels,
144                          fixed_frame.data());
145 
146       if (is_capture) {
147         apm_return_code = apm->ProcessStream(
148             fixed_frame.data(), StreamConfig(input_rate, num_channels),
149             StreamConfig(output_rate, num_channels), fixed_frame.data());
150       } else {
151         apm_return_code = apm->ProcessReverseStream(
152             fixed_frame.data(), StreamConfig(input_rate, num_channels),
153             StreamConfig(output_rate, num_channels), fixed_frame.data());
154       }
155     }
156     // APM may flag an error on unsupported audio formats, but should not crash.
157     RTC_DCHECK(apm_return_code == AudioProcessing::kNoError ||
158                apm_return_code == AudioProcessing::kBadSampleRateError);
159   }
160 }
161 
162 }  // namespace webrtc
163