OLD | NEW |
| (Empty) |
1 // Copyright 2017 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 "content/browser/renderer_host/media/render_frame_audio_output_stream_f
actory.h" | |
6 | |
7 #include <limits> | |
8 #include <utility> | |
9 | |
10 #include "base/bind.h" | |
11 #include "base/memory/shared_memory.h" | |
12 #include "base/memory/shared_memory_handle.h" | |
13 #include "base/run_loop.h" | |
14 #include "base/sync_socket.h" | |
15 #include "content/browser/renderer_host/media/media_stream_manager.h" | |
16 #include "content/browser/renderer_host/media/renderer_audio_output_stream_facto
ry_context.h" | |
17 #include "content/common/media/renderer_audio_output_stream_factory.mojom.h" | |
18 #include "content/public/browser/browser_thread.h" | |
19 #include "content/public/test/test_browser_context.h" | |
20 #include "content/public/test/test_browser_thread_bundle.h" | |
21 #include "media/audio/audio_output_controller.h" | |
22 #include "media/base/audio_parameters.h" | |
23 #include "mojo/edk/embedder/embedder.h" | |
24 #include "mojo/public/cpp/bindings/binding.h" | |
25 #include "mojo/public/cpp/system/platform_handle.h" | |
26 #include "testing/gmock/include/gmock/gmock.h" | |
27 #include "testing/gtest/include/gtest/gtest.h" | |
28 | |
29 namespace content { | |
30 | |
31 namespace { | |
32 | |
33 using testing::Test; | |
34 using AudioOutputStreamFactory = mojom::RendererAudioOutputStreamFactory; | |
35 using AudioOutputStreamFactoryPtr = | |
36 mojo::InterfacePtr<AudioOutputStreamFactory>; | |
37 using AudioOutputStreamFactoryRequest = | |
38 mojo::InterfaceRequest<AudioOutputStreamFactory>; | |
39 using AudioOutputStream = media::mojom::AudioOutputStream; | |
40 using AudioOutputStreamPtr = mojo::InterfacePtr<AudioOutputStream>; | |
41 using AudioOutputStreamRequest = mojo::InterfaceRequest<AudioOutputStream>; | |
42 using AudioOutputStreamProvider = media::mojom::AudioOutputStreamProvider; | |
43 using AudioOutputStreamProviderPtr = | |
44 mojo::InterfacePtr<AudioOutputStreamProvider>; | |
45 using AudioOutputStreamProviderRequest = | |
46 mojo::InterfaceRequest<AudioOutputStreamProvider>; | |
47 | |
48 const int kStreamId = 0; | |
49 const int kNoSessionId = 0; | |
50 const int kRenderProcessId = 42; | |
51 const int kRenderFrameId = 24; | |
52 const int kSampleFrequency = 44100; | |
53 const int kBitsPerSample = 16; | |
54 const int kSamplesPerBuffer = kSampleFrequency / 100; | |
55 const char kSalt[] = "salt"; | |
56 | |
57 media::AudioParameters GetTestAudioParameters() { | |
58 return media::AudioParameters(media::AudioParameters::AUDIO_PCM_LOW_LATENCY, | |
59 media::CHANNEL_LAYOUT_MONO, kSampleFrequency, | |
60 kBitsPerSample, kSamplesPerBuffer); | |
61 } | |
62 | |
63 class MockAudioOutputDelegate : public media::AudioOutputDelegate { | |
64 public: | |
65 // |on_destruction| can be used to observe the destruction of the delegate. | |
66 explicit MockAudioOutputDelegate( | |
67 base::OnceClosure on_destruction = base::OnceClosure()) | |
68 : on_destruction_(std::move(on_destruction)) {} | |
69 | |
70 ~MockAudioOutputDelegate() { | |
71 if (on_destruction_) | |
72 std::move(on_destruction_).Run(); | |
73 } | |
74 | |
75 MOCK_CONST_METHOD0(GetController, | |
76 scoped_refptr<media::AudioOutputController>()); | |
77 MOCK_CONST_METHOD0(GetStreamId, int()); | |
78 MOCK_METHOD0(OnPlayStream, void()); | |
79 MOCK_METHOD0(OnPauseStream, void()); | |
80 MOCK_METHOD1(OnSetVolume, void(double)); | |
81 | |
82 private: | |
83 base::OnceClosure on_destruction_; | |
84 | |
85 DISALLOW_COPY_AND_ASSIGN(MockAudioOutputDelegate); | |
86 }; | |
87 | |
88 class MockContext : public RendererAudioOutputStreamFactoryContext { | |
89 public: | |
90 explicit MockContext(bool auth_ok) : salt_(kSalt), auth_ok_(auth_ok) {} | |
91 | |
92 ~MockContext() override { EXPECT_EQ(nullptr, delegate_); } | |
93 | |
94 int GetRenderProcessId() const override { return kRenderProcessId; } | |
95 | |
96 std::string GetHMACForDeviceId( | |
97 const url::Origin& origin, | |
98 const std::string& raw_device_id) const override { | |
99 return MediaStreamManager::GetHMACForMediaDeviceID(salt_, origin, | |
100 raw_device_id); | |
101 } | |
102 | |
103 void RequestDeviceAuthorization( | |
104 int render_frame_id, | |
105 int session_id, | |
106 const std::string& device_id, | |
107 const url::Origin& security_origin, | |
108 AuthorizationCompletedCallback cb) const override { | |
109 EXPECT_EQ(render_frame_id, kRenderFrameId); | |
110 EXPECT_EQ(session_id, 0); | |
111 if (auth_ok_) { | |
112 base::ThreadTaskRunnerHandle::Get()->PostTask( | |
113 FROM_HERE, | |
114 base::Bind(cb, media::OutputDeviceStatus::OUTPUT_DEVICE_STATUS_OK, | |
115 false, GetTestAudioParameters(), "default")); | |
116 return; | |
117 } | |
118 base::ThreadTaskRunnerHandle::Get()->PostTask( | |
119 FROM_HERE, | |
120 base::Bind(cb, | |
121 media::OutputDeviceStatus:: | |
122 OUTPUT_DEVICE_STATUS_ERROR_NOT_AUTHORIZED, | |
123 false, media::AudioParameters::UnavailableDeviceParams(), | |
124 "")); | |
125 } | |
126 | |
127 // The event handler for the delegate will be stored at | |
128 // |*event_handler_location| when the delegate is created. | |
129 void PrepareDelegateForCreation( | |
130 std::unique_ptr<media::AudioOutputDelegate> delegate, | |
131 media::AudioOutputDelegate::EventHandler** event_handler_location) { | |
132 EXPECT_EQ(nullptr, delegate_); | |
133 EXPECT_EQ(nullptr, delegate_event_handler_location_); | |
134 delegate_ = std::move(delegate); | |
135 delegate_event_handler_location_ = event_handler_location; | |
136 } | |
137 | |
138 std::unique_ptr<media::AudioOutputDelegate> CreateDelegate( | |
139 const std::string& unique_device_id, | |
140 int render_frame_id, | |
141 const media::AudioParameters& params, | |
142 media::AudioOutputDelegate::EventHandler* handler) override { | |
143 EXPECT_NE(nullptr, delegate_); | |
144 EXPECT_NE(nullptr, delegate_event_handler_location_); | |
145 *delegate_event_handler_location_ = handler; | |
146 delegate_event_handler_location_ = nullptr; | |
147 return std::move(delegate_); | |
148 } | |
149 | |
150 AudioOutputStreamFactoryPtr CreateFactory() { | |
151 DCHECK(!factory_); | |
152 AudioOutputStreamFactoryPtr ret; | |
153 factory_ = base::MakeUnique<RenderFrameAudioOutputStreamFactory>( | |
154 kRenderFrameId, this); | |
155 factory_binding_ = base::MakeUnique< | |
156 mojo::Binding<mojom::RendererAudioOutputStreamFactory>>(factory_.get(), | |
157 &ret); | |
158 return ret; | |
159 } | |
160 | |
161 private: | |
162 const std::string salt_; | |
163 const bool auth_ok_; | |
164 std::unique_ptr<RenderFrameAudioOutputStreamFactory> factory_; | |
165 std::unique_ptr<mojo::Binding<mojom::RendererAudioOutputStreamFactory>> | |
166 factory_binding_; | |
167 std::unique_ptr<media::AudioOutputDelegate> delegate_; | |
168 media::AudioOutputDelegate::EventHandler** delegate_event_handler_location_ = | |
169 nullptr; | |
170 | |
171 DISALLOW_COPY_AND_ASSIGN(MockContext); | |
172 }; | |
173 | |
174 class MockClient { | |
175 public: | |
176 MockClient() {} | |
177 ~MockClient() {} | |
178 | |
179 void StreamCreated(mojo::ScopedSharedBufferHandle handle1, | |
180 mojo::ScopedHandle handle2) { | |
181 was_called_ = true; | |
182 } | |
183 | |
184 bool was_called() { return was_called_; } | |
185 | |
186 private: | |
187 bool was_called_ = false; | |
188 | |
189 DISALLOW_COPY_AND_ASSIGN(MockClient); | |
190 }; | |
191 | |
192 void AuthCallback(media::OutputDeviceStatus* status_out, | |
193 media::AudioParameters* params_out, | |
194 std::string* id_out, | |
195 media::OutputDeviceStatus status, | |
196 const media::AudioParameters& params, | |
197 const std::string& id) { | |
198 *status_out = status; | |
199 *params_out = params; | |
200 *id_out = id; | |
201 } | |
202 | |
203 } // namespace | |
204 | |
205 // This test authorizes and creates a stream, and checks that | |
206 // 1. the authorization callback is called with appropriate parameters. | |
207 // 2. the AudioOutputDelegate is created. | |
208 // 3. when the delegate calls OnStreamCreated, this is propagated to the client. | |
209 TEST(RenderFrameAudioOutputStreamFactoryTest, CreateStream) { | |
210 content::TestBrowserThreadBundle thread_bundle; | |
211 AudioOutputStreamProviderPtr provider; | |
212 AudioOutputStreamPtr output_stream; | |
213 MockClient client; | |
214 media::AudioOutputDelegate::EventHandler* event_handler = nullptr; | |
215 auto factory_context = base::MakeUnique<MockContext>(true); | |
216 factory_context->PrepareDelegateForCreation( | |
217 base::MakeUnique<MockAudioOutputDelegate>(), &event_handler); | |
218 AudioOutputStreamFactoryPtr factory_ptr = factory_context->CreateFactory(); | |
219 | |
220 media::OutputDeviceStatus status; | |
221 media::AudioParameters params; | |
222 std::string id; | |
223 factory_ptr->RequestDeviceAuthorization( | |
224 mojo::MakeRequest(&provider), kNoSessionId, "default", | |
225 base::Bind(&AuthCallback, base::Unretained(&status), | |
226 base::Unretained(¶ms), base::Unretained(&id))); | |
227 base::RunLoop().RunUntilIdle(); | |
228 EXPECT_EQ(status, media::OUTPUT_DEVICE_STATUS_OK); | |
229 EXPECT_EQ(params.AsHumanReadableString(), | |
230 GetTestAudioParameters().AsHumanReadableString()); | |
231 EXPECT_TRUE(id.empty()); | |
232 | |
233 provider->Acquire( | |
234 mojo::MakeRequest<AudioOutputStream>(&output_stream), params, | |
235 base::Bind(&MockClient::StreamCreated, base::Unretained(&client))); | |
236 base::RunLoop().RunUntilIdle(); | |
237 ASSERT_NE(event_handler, nullptr); | |
238 | |
239 base::SharedMemory shared_memory; | |
240 ASSERT_TRUE(shared_memory.CreateAndMapAnonymous(100)); | |
241 | |
242 base::CancelableSyncSocket local, remote; | |
243 ASSERT_TRUE(base::CancelableSyncSocket::CreatePair(&local, &remote)); | |
244 event_handler->OnStreamCreated(kStreamId, &shared_memory, &remote); | |
245 | |
246 base::RunLoop().RunUntilIdle(); | |
247 // Make sure we got the callback from creating stream. | |
248 EXPECT_TRUE(client.was_called()); | |
249 } | |
250 | |
251 TEST(RenderFrameAudioOutputStreamFactoryTest, NotAuthorized_Denied) { | |
252 content::TestBrowserThreadBundle thread_bundle; | |
253 AudioOutputStreamProviderPtr output_provider; | |
254 auto factory_context = base::MakeUnique<MockContext>(false); | |
255 AudioOutputStreamFactoryPtr factory_ptr = factory_context->CreateFactory(); | |
256 | |
257 media::OutputDeviceStatus status; | |
258 media::AudioParameters params; | |
259 std::string id; | |
260 factory_ptr->RequestDeviceAuthorization( | |
261 mojo::MakeRequest(&output_provider), kNoSessionId, "default", | |
262 base::Bind(&AuthCallback, base::Unretained(&status), | |
263 base::Unretained(¶ms), base::Unretained(&id))); | |
264 base::RunLoop().RunUntilIdle(); | |
265 EXPECT_EQ(status, media::OUTPUT_DEVICE_STATUS_ERROR_NOT_AUTHORIZED); | |
266 EXPECT_TRUE(id.empty()); | |
267 } | |
268 | |
269 TEST(RenderFrameAudioOutputStreamFactoryTest, ConnectionError_DeletesStream) { | |
270 content::TestBrowserThreadBundle thread_bundle; | |
271 AudioOutputStreamProviderPtr provider; | |
272 AudioOutputStreamPtr output_stream; | |
273 MockClient client; | |
274 bool delegate_is_destructed = false; | |
275 media::AudioOutputDelegate::EventHandler* event_handler = nullptr; | |
276 auto factory_context = base::MakeUnique<MockContext>(true); | |
277 factory_context->PrepareDelegateForCreation( | |
278 base::MakeUnique<MockAudioOutputDelegate>( | |
279 base::BindOnce([](bool* destructed) { *destructed = true; }, | |
280 &delegate_is_destructed)), | |
281 &event_handler); | |
282 AudioOutputStreamFactoryPtr factory_ptr = factory_context->CreateFactory(); | |
283 | |
284 factory_ptr->RequestDeviceAuthorization( | |
285 mojo::MakeRequest(&provider), kNoSessionId, "default", | |
286 base::Bind([](media::OutputDeviceStatus status, | |
287 const media::AudioParameters& params, | |
288 const std::string& id) {})); | |
289 base::RunLoop().RunUntilIdle(); | |
290 | |
291 provider->Acquire( | |
292 mojo::MakeRequest<AudioOutputStream>(&output_stream), | |
293 GetTestAudioParameters(), | |
294 base::Bind(&MockClient::StreamCreated, base::Unretained(&client))); | |
295 base::RunLoop().RunUntilIdle(); | |
296 ASSERT_NE(event_handler, nullptr); | |
297 EXPECT_FALSE(delegate_is_destructed); | |
298 output_stream.reset(); | |
299 base::RunLoop().RunUntilIdle(); | |
300 EXPECT_TRUE(delegate_is_destructed); | |
301 } | |
302 | |
303 TEST(RenderFrameAudioOutputStreamFactoryTest, DelegateError_DeletesStream) { | |
304 content::TestBrowserThreadBundle thread_bundle; | |
305 AudioOutputStreamProviderPtr provider; | |
306 AudioOutputStreamPtr output_stream; | |
307 MockClient client; | |
308 bool delegate_is_destructed = false; | |
309 media::AudioOutputDelegate::EventHandler* event_handler = nullptr; | |
310 auto factory_context = base::MakeUnique<MockContext>(true); | |
311 factory_context->PrepareDelegateForCreation( | |
312 base::MakeUnique<MockAudioOutputDelegate>( | |
313 base::BindOnce([](bool* destructed) { *destructed = true; }, | |
314 &delegate_is_destructed)), | |
315 &event_handler); | |
316 AudioOutputStreamFactoryPtr factory_ptr = factory_context->CreateFactory(); | |
317 | |
318 factory_ptr->RequestDeviceAuthorization( | |
319 mojo::MakeRequest(&provider), kNoSessionId, "default", | |
320 base::Bind([](media::OutputDeviceStatus status, | |
321 const media::AudioParameters& params, | |
322 const std::string& id) {})); | |
323 base::RunLoop().RunUntilIdle(); | |
324 | |
325 provider->Acquire( | |
326 mojo::MakeRequest<AudioOutputStream>(&output_stream), | |
327 GetTestAudioParameters(), | |
328 base::Bind(&MockClient::StreamCreated, base::Unretained(&client))); | |
329 base::RunLoop().RunUntilIdle(); | |
330 ASSERT_NE(event_handler, nullptr); | |
331 EXPECT_FALSE(delegate_is_destructed); | |
332 event_handler->OnStreamError(kStreamId); | |
333 base::RunLoop().RunUntilIdle(); | |
334 EXPECT_TRUE(delegate_is_destructed); | |
335 } | |
336 | |
337 TEST(RenderFrameAudioOutputStreamFactoryTest, OutOfRangeSessionId_BadMessage) { | |
338 // This test checks that we get a bad message if session_id is too large | |
339 // to fit in an integer. This ensures that we don't overflow when casting the | |
340 // int64_t to an int | |
341 if (sizeof(int) >= sizeof(int64_t)) { | |
342 // In this case, any int64_t would fit in an int, and the case we are | |
343 // checking for is impossible. | |
344 return; | |
345 } | |
346 | |
347 bool got_bad_message = false; | |
348 mojo::edk::SetDefaultProcessErrorCallback( | |
349 base::Bind([](bool* got_bad_message, | |
350 const std::string& s) { *got_bad_message = true; }, | |
351 &got_bad_message)); | |
352 | |
353 TestBrowserThreadBundle thread_bundle; | |
354 | |
355 AudioOutputStreamProviderPtr output_provider; | |
356 auto factory_context = base::MakeUnique<MockContext>(true); | |
357 auto factory_ptr = factory_context->CreateFactory(); | |
358 | |
359 int64_t session_id = std::numeric_limits<int>::max(); | |
360 ++session_id; | |
361 | |
362 EXPECT_FALSE(got_bad_message); | |
363 factory_ptr->RequestDeviceAuthorization( | |
364 mojo::MakeRequest(&output_provider), session_id, "default", | |
365 base::Bind([](media::OutputDeviceStatus, const media::AudioParameters&, | |
366 const std::string&) {})); | |
367 base::RunLoop().RunUntilIdle(); | |
368 EXPECT_TRUE(got_bad_message); | |
369 } | |
370 | |
371 } // namespace content | |
OLD | NEW |