OLD | NEW |
| (Empty) |
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "media/audio/audio_silence_detector.h" | |
6 | |
7 #include "base/bind.h" | |
8 #include "base/bind_helpers.h" | |
9 #include "base/message_loop/message_loop.h" | |
10 #include "base/synchronization/waitable_event.h" | |
11 #include "base/threading/thread.h" | |
12 #include "base/time/time.h" | |
13 #include "media/base/audio_bus.h" | |
14 #include "testing/gmock/include/gmock/gmock.h" | |
15 #include "testing/gtest/include/gtest/gtest.h" | |
16 | |
17 using ::testing::_; | |
18 using ::testing::InvokeWithoutArgs; | |
19 using ::testing::TestWithParam; | |
20 using ::testing::Values; | |
21 | |
22 namespace media { | |
23 | |
24 static const int kSampleRate = 48000; | |
25 static const int kFramesPerBuffer = 128; | |
26 | |
27 namespace { | |
28 | |
29 class MockObserver { | |
30 public: | |
31 MOCK_METHOD1(OnAudible, void(bool)); | |
32 }; | |
33 | |
34 struct TestScenario { | |
35 const float* data; | |
36 int num_channels; | |
37 int num_frames; | |
38 float value_range; | |
39 | |
40 TestScenario(const float* d, int c, int f, float v) | |
41 : data(d), num_channels(c), num_frames(f), value_range(v) {} | |
42 }; | |
43 | |
44 } // namespace | |
45 | |
46 class AudioSilenceDetectorTest : public TestWithParam<TestScenario> { | |
47 public: | |
48 AudioSilenceDetectorTest() | |
49 : audio_manager_thread_(new base::Thread("AudioThread")), | |
50 notification_received_(false, false) { | |
51 audio_manager_thread_->Start(); | |
52 audio_message_loop_ = audio_manager_thread_->message_loop_proxy(); | |
53 } | |
54 | |
55 virtual ~AudioSilenceDetectorTest() { | |
56 SyncWithAudioThread(); | |
57 } | |
58 | |
59 AudioSilenceDetector* silence_detector() const { | |
60 return silence_detector_.get(); | |
61 } | |
62 | |
63 void StartSilenceDetector(float threshold, MockObserver* observer) { | |
64 audio_message_loop_->PostTask( | |
65 FROM_HERE, | |
66 base::Bind(&AudioSilenceDetectorTest::StartDetectorOnAudioThread, | |
67 base::Unretained(this), threshold, observer)); | |
68 SyncWithAudioThread(); | |
69 } | |
70 | |
71 void StopSilenceDetector() { | |
72 audio_message_loop_->PostTask( | |
73 FROM_HERE, | |
74 base::Bind(&AudioSilenceDetectorTest::StopDetectorOnAudioThread, | |
75 base::Unretained(this))); | |
76 SyncWithAudioThread(); | |
77 } | |
78 | |
79 // Creates an AudioBus, sized and populated with kFramesPerBuffer frames of | |
80 // data. The given test |data| is repeated to fill the buffer. | |
81 scoped_ptr<AudioBus> CreatePopulatedBuffer( | |
82 const float* data, int num_channels, int num_frames) { | |
83 scoped_ptr<AudioBus> bus = AudioBus::Create(num_channels, kFramesPerBuffer); | |
84 for (int ch = 0; ch < num_channels; ++ch) { | |
85 for (int frames = 0; frames < kFramesPerBuffer; frames += num_frames) { | |
86 const int num_to_copy = std::min(num_frames, kFramesPerBuffer - frames); | |
87 memcpy(bus->channel(ch) + frames, data + num_frames * ch, | |
88 sizeof(float) * num_to_copy); | |
89 } | |
90 } | |
91 return bus.Pass(); | |
92 } | |
93 | |
94 void SignalNotificationReceived() { | |
95 notification_received_.Signal(); | |
96 } | |
97 | |
98 void WaitForNotificationReceived() { | |
99 notification_received_.Wait(); | |
100 } | |
101 | |
102 // Post a task on the audio thread and block until it is executed. This | |
103 // provides a barrier: All previous tasks pending on the audio thread have | |
104 // completed before this method returns. | |
105 void SyncWithAudioThread() { | |
106 base::WaitableEvent done(false, false); | |
107 audio_message_loop_->PostTask( | |
108 FROM_HERE, | |
109 base::Bind(&base::WaitableEvent::Signal, base::Unretained(&done))); | |
110 done.Wait(); | |
111 } | |
112 | |
113 private: | |
114 void StartDetectorOnAudioThread(float threshold, MockObserver* observer) { | |
115 const AudioSilenceDetector::AudibleCallback notify_is_audible = | |
116 base::Bind(&MockObserver::OnAudible, base::Unretained(observer)); | |
117 silence_detector_.reset(new AudioSilenceDetector( | |
118 kSampleRate, base::TimeDelta::FromMilliseconds(1), threshold)); | |
119 silence_detector_->Start(notify_is_audible); | |
120 } | |
121 | |
122 void StopDetectorOnAudioThread() { | |
123 silence_detector_->Stop(true); | |
124 silence_detector_.reset(); | |
125 } | |
126 | |
127 scoped_ptr<base::Thread> audio_manager_thread_; | |
128 scoped_refptr<base::MessageLoopProxy> audio_message_loop_; | |
129 | |
130 base::WaitableEvent notification_received_; | |
131 | |
132 scoped_ptr<AudioSilenceDetector> silence_detector_; | |
133 | |
134 DISALLOW_COPY_AND_ASSIGN(AudioSilenceDetectorTest); | |
135 }; | |
136 | |
137 TEST_P(AudioSilenceDetectorTest, TriggersAtVariousThresholds) { | |
138 static const float kThresholdsToTry[] = | |
139 { 0.0f, 0.125f, 0.25f, 0.5f, 0.75f, 1.0f }; | |
140 | |
141 const TestScenario& scenario = GetParam(); | |
142 | |
143 for (size_t i = 0; i < arraysize(kThresholdsToTry); ++i) { | |
144 MockObserver observer; | |
145 | |
146 // Start the detector. This should cause a single callback to indicate | |
147 // we're starting out in silence. | |
148 EXPECT_CALL(observer, OnAudible(false)) | |
149 .WillOnce(InvokeWithoutArgs( | |
150 this, &AudioSilenceDetectorTest::SignalNotificationReceived)) | |
151 .RetiresOnSaturation(); | |
152 StartSilenceDetector(kThresholdsToTry[i], &observer); | |
153 WaitForNotificationReceived(); | |
154 | |
155 // Send more data to the silence detector. For some test scenarios, the | |
156 // sound data will trigger a callback. | |
157 const bool expect_a_callback = (kThresholdsToTry[i] < scenario.value_range); | |
158 if (expect_a_callback) { | |
159 EXPECT_CALL(observer, OnAudible(true)) | |
160 .WillOnce(InvokeWithoutArgs( | |
161 this, &AudioSilenceDetectorTest::SignalNotificationReceived)) | |
162 .RetiresOnSaturation(); | |
163 } | |
164 scoped_ptr<AudioBus> bus = CreatePopulatedBuffer( | |
165 scenario.data, scenario.num_channels, scenario.num_frames); | |
166 silence_detector()->Scan(bus.get(), bus->frames()); | |
167 if (expect_a_callback) | |
168 WaitForNotificationReceived(); | |
169 | |
170 // Stop the detector. This should cause another callback to indicate we're | |
171 // ending in silence. | |
172 EXPECT_CALL(observer, OnAudible(false)) | |
173 .WillOnce(InvokeWithoutArgs( | |
174 this, &AudioSilenceDetectorTest::SignalNotificationReceived)) | |
175 .RetiresOnSaturation(); | |
176 StopSilenceDetector(); | |
177 WaitForNotificationReceived(); | |
178 | |
179 SyncWithAudioThread(); | |
180 } | |
181 } | |
182 | |
183 static const float kAllZeros[] = { | |
184 // left channel | |
185 0.0f, | |
186 // right channel | |
187 0.0f | |
188 }; | |
189 | |
190 static const float kAllOnes[] = { | |
191 // left channel | |
192 1.0f, | |
193 // right channel | |
194 1.0f | |
195 }; | |
196 | |
197 static const float kSilentLeftChannel[] = { | |
198 // left channel | |
199 0.5f, 0.5f, 0.5f, 0.5f, | |
200 // right channel | |
201 0.0f, 0.25f, 0.0f, 0.0f | |
202 }; | |
203 | |
204 static const float kSilentRightChannel[] = { | |
205 // left channel | |
206 1.0f, 1.0f, 0.75f, 0.5f, 1.0f, | |
207 // right channel | |
208 0.0f, 0.0f, 0.0f, 0.0f, 0.0f | |
209 }; | |
210 | |
211 static const float kAtDifferentVolumesAndBias[] = { | |
212 // left channel | |
213 1.0f, 0.9f, 0.8f, 0.7f, 0.6f, 0.5f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f, | |
214 // right channel | |
215 0.0f, 0.2f, 0.4f, 0.6f, 0.4f, 0.2f, 0.0f, 0.2f, 0.4f, 0.6f, 0.4f, 0.2f | |
216 }; | |
217 | |
218 INSTANTIATE_TEST_CASE_P( | |
219 Scenarios, AudioSilenceDetectorTest, | |
220 Values( | |
221 TestScenario(kAllZeros, 2, 1, 0.0f), | |
222 TestScenario(kAllOnes, 2, 1, 0.0f), | |
223 TestScenario(kSilentLeftChannel, 2, 4, 0.25f), | |
224 TestScenario(kSilentRightChannel, 2, 5, 0.5f), | |
225 TestScenario(kAtDifferentVolumesAndBias, 2, 12, 0.6f))); | |
226 | |
227 } // namespace media | |
OLD | NEW |