Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(186)

Side by Side Diff: content/renderer/media/media_stream_impl.cc

Issue 10919122: Move creation of PeerConnection from the RenderView to the RenderThreadImpl. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Adding missing files content/renderer/media/webrtc_uma_histograms.h Created 8 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "content/renderer/media/media_stream_impl.h" 5 #include "content/renderer/media/media_stream_impl.h"
6 6
7 #include <utility> 7 #include <utility>
8 8
9 #include "base/bind.h"
10 #include "base/logging.h" 9 #include "base/logging.h"
11 #include "base/metrics/histogram.h"
12 #include "base/string_number_conversions.h" 10 #include "base/string_number_conversions.h"
13 #include "base/stringprintf.h" 11 #include "base/stringprintf.h"
14 #include "base/synchronization/waitable_event.h"
15 #include "base/utf_string_conversions.h" 12 #include "base/utf_string_conversions.h"
16 #include "content/common/child_thread.h"
17 #include "content/renderer/media/capture_video_decoder.h" 13 #include "content/renderer/media/capture_video_decoder.h"
18 #include "content/renderer/media/media_stream_extra_data.h" 14 #include "content/renderer/media/media_stream_extra_data.h"
15 #include "content/renderer/media/media_stream_source_extra_data.h"
19 #include "content/renderer/media/media_stream_dependency_factory.h" 16 #include "content/renderer/media/media_stream_dependency_factory.h"
20 #include "content/renderer/media/media_stream_dispatcher.h" 17 #include "content/renderer/media/media_stream_dispatcher.h"
21 #include "content/renderer/media/peer_connection_handler_jsep.h" 18 #include "content/renderer/media/rtc_video_decoder.h"
22 #include "content/renderer/media/video_capture_impl_manager.h" 19 #include "content/renderer/media/video_capture_impl_manager.h"
23 #include "content/renderer/media/webrtc_audio_device_impl.h" 20 #include "content/renderer/media/webrtc_uma_histograms.h"
24 #include "content/renderer/p2p/ipc_network_manager.h"
25 #include "content/renderer/p2p/ipc_socket_factory.h"
26 #include "jingle/glue/thread_wrapper.h"
27 #include "media/base/message_loop_factory.h" 21 #include "media/base/message_loop_factory.h"
28 #include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h" 22 #include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h"
29 #include "third_party/WebKit/Source/WebKit/chromium/public/WebMediaStreamRegistr y.h" 23 #include "third_party/WebKit/Source/WebKit/chromium/public/WebMediaStreamRegistr y.h"
30 #include "third_party/WebKit/Source/WebKit/chromium/public/WebSecurityOrigin.h" 24 #include "third_party/WebKit/Source/WebKit/chromium/public/WebSecurityOrigin.h"
31 #include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebMediaStre amComponent.h" 25 #include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebMediaStre amComponent.h"
32 #include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebMediaStre amDescriptor.h" 26 #include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebMediaStre amDescriptor.h"
33 #include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebMediaStre amSource.h" 27 #include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebMediaStre amSource.h"
34 #include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebVector.h" 28 #include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebVector.h"
35 29
36 namespace { 30 namespace {
37 const int kVideoCaptureWidth = 640; 31 const int kVideoCaptureWidth = 640;
38 const int kVideoCaptureHeight = 480; 32 const int kVideoCaptureHeight = 480;
39 const int kVideoCaptureFramePerSecond = 30; 33 const int kVideoCaptureFramePerSecond = 30;
40
41 // Helper enum used for histogramming calls to WebRTC APIs from JavaScript.
42 enum JavaScriptAPIName {
43 kWebkitGetUserMedia,
44 kWebkitPeerConnection,
45 kInvalidName
46 };
47 } // namespace 34 } // namespace
48 35
49 // Helper method used to collect information about the number of times
50 // different WebRTC API:s are called from JavaScript.
51 // The histogram can be viewed at chrome://histograms/WebRTC.webkitApiCount.
52 static void UpdateWebRTCMethodCount(JavaScriptAPIName api_name) {
53 UMA_HISTOGRAM_ENUMERATION("WebRTC.webkitApiCount", api_name, kInvalidName);
54 }
55
56 static int g_next_request_id = 0; 36 static int g_next_request_id = 0;
57 37
58 // MediaStreamSourceExtraData contains data stored in the
59 // WebKit::WebMediaStreamSource extra data field.
60 class MediaStreamSourceExtraData
61 : public WebKit::WebMediaStreamSource::ExtraData {
62 public:
63 explicit MediaStreamSourceExtraData(
64 const media_stream::StreamDeviceInfo& device_info)
65 : device_info_(device_info) {
66 }
67 // Return device information about the camera or microphone.
68 const media_stream::StreamDeviceInfo& device_info() const {
69 return device_info_;
70 }
71
72 private:
73 media_stream::StreamDeviceInfo device_info_;
74 };
75
76 // Creates a WebKit representation of a stream sources based on 38 // Creates a WebKit representation of a stream sources based on
77 // |devices| from the MediaStreamDispatcher. 39 // |devices| from the MediaStreamDispatcher.
78 static void CreateWebKitSourceVector( 40 static void CreateWebKitSourceVector(
79 const std::string& label, 41 const std::string& label,
80 const media_stream::StreamDeviceInfoArray& devices, 42 const media_stream::StreamDeviceInfoArray& devices,
81 WebKit::WebMediaStreamSource::Type type, 43 WebKit::WebMediaStreamSource::Type type,
82 WebKit::WebVector<WebKit::WebMediaStreamSource>& webkit_sources) { 44 WebKit::WebVector<WebKit::WebMediaStreamSource>& webkit_sources) {
83 ASSERT(devices.size() == webkit_sources.size()); 45 CHECK(devices.size() == webkit_sources.size());
84 for (size_t i = 0; i < devices.size(); ++i) { 46 for (size_t i = 0; i < devices.size(); ++i) {
85 std::string source_id = StringPrintf("%s%d%u", label.c_str(), type, 47 std::string source_id = StringPrintf("%s%d%u", label.c_str(), type,
86 static_cast<unsigned int>(i)); 48 static_cast<unsigned int>(i));
87 webkit_sources[i].initialize( 49 webkit_sources[i].initialize(
88 UTF8ToUTF16(source_id), 50 UTF8ToUTF16(source_id),
89 type, 51 type,
90 UTF8ToUTF16(devices[i].name)); 52 UTF8ToUTF16(devices[i].name));
91 webkit_sources[i].setExtraData( 53 webkit_sources[i].setExtraData(
92 new MediaStreamSourceExtraData(devices[i])); 54 new MediaStreamSourceExtraData(devices[i]));
93 } 55 }
94 } 56 }
95 57
96 MediaStreamImpl::MediaStreamImpl( 58 MediaStreamImpl::MediaStreamImpl(
97 content::RenderView* render_view, 59 content::RenderView* render_view,
98 MediaStreamDispatcher* media_stream_dispatcher, 60 MediaStreamDispatcher* media_stream_dispatcher,
99 content::P2PSocketDispatcher* p2p_socket_dispatcher,
100 VideoCaptureImplManager* vc_manager, 61 VideoCaptureImplManager* vc_manager,
101 MediaStreamDependencyFactory* dependency_factory) 62 MediaStreamDependencyFactory* dependency_factory)
102 : content::RenderViewObserver(render_view), 63 : content::RenderViewObserver(render_view),
103 dependency_factory_(dependency_factory), 64 dependency_factory_(dependency_factory),
104 media_stream_dispatcher_(media_stream_dispatcher), 65 media_stream_dispatcher_(media_stream_dispatcher),
105 p2p_socket_dispatcher_(p2p_socket_dispatcher), 66 vc_manager_(vc_manager) {
106 network_manager_(NULL),
107 vc_manager_(vc_manager),
108 signaling_thread_(NULL),
109 worker_thread_(NULL),
110 chrome_worker_thread_("Chrome_libJingle_WorkerThread") {
111 } 67 }
112 68
113 MediaStreamImpl::~MediaStreamImpl() { 69 MediaStreamImpl::~MediaStreamImpl() {
114 CleanupPeerConnectionFactory();
115 }
116
117 WebKit::WebPeerConnection00Handler*
118 MediaStreamImpl::CreatePeerConnectionHandlerJsep(
119 WebKit::WebPeerConnection00HandlerClient* client) {
120 // Save histogram data so we can see how much PeerConnetion is used.
121 // The histogram counts the number of calls to the JS API
122 // webKitPeerConnection00.
123 UpdateWebRTCMethodCount(kWebkitPeerConnection);
124 DCHECK(CalledOnValidThread());
125 if (!EnsurePeerConnectionFactory())
126 return NULL;
127
128 PeerConnectionHandlerJsep* pc_handler = new PeerConnectionHandlerJsep(
129 client,
130 dependency_factory_.get());
131 return pc_handler;
132 } 70 }
133 71
134 void MediaStreamImpl::StopLocalMediaStream( 72 void MediaStreamImpl::StopLocalMediaStream(
135 const WebKit::WebMediaStreamDescriptor& stream) { 73 const WebKit::WebMediaStreamDescriptor& stream) {
136 DVLOG(1) << "MediaStreamImpl::StopLocalMediaStream"; 74 DVLOG(1) << "MediaStreamImpl::StopLocalMediaStream";
137 75
138 MediaStreamExtraData* extra_data = 76 MediaStreamExtraData* extra_data =
139 static_cast<MediaStreamExtraData*>(stream.extraData()); 77 static_cast<MediaStreamExtraData*>(stream.extraData());
140 if (extra_data && extra_data->local_stream()) { 78 if (extra_data && extra_data->local_stream()) {
141 media_stream_dispatcher_->StopStream(extra_data->local_stream()->label()); 79 media_stream_dispatcher_->StopStream(extra_data->local_stream()->label());
142 local_media_streams_.erase(extra_data->local_stream()->label()); 80 local_media_streams_.erase(extra_data->local_stream()->label());
143 } else { 81 } else {
144 NOTREACHED(); 82 NOTREACHED();
145 } 83 }
146 } 84 }
147 85
148 void MediaStreamImpl::CreateMediaStream(
149 WebKit::WebFrame* frame,
150 WebKit::WebMediaStreamDescriptor* stream) {
151 DVLOG(1) << "MediaStreamImpl::CreateMediaStream";
152
153 if (!CreateNativeLocalMediaStream(stream)) {
154 DVLOG(1) << "Failed to create native stream in CreateMediaStream.";
155 return;
156 }
157 }
158
159 void MediaStreamImpl::requestUserMedia( 86 void MediaStreamImpl::requestUserMedia(
160 const WebKit::WebUserMediaRequest& user_media_request, 87 const WebKit::WebUserMediaRequest& user_media_request,
161 const WebKit::WebVector<WebKit::WebMediaStreamSource>& audio_sources, 88 const WebKit::WebVector<WebKit::WebMediaStreamSource>& audio_sources,
162 const WebKit::WebVector<WebKit::WebMediaStreamSource>& video_sources) { 89 const WebKit::WebVector<WebKit::WebMediaStreamSource>& video_sources) {
163 // Save histogram data so we can see how much GetUserMedia is used. 90 // Save histogram data so we can see how much GetUserMedia is used.
164 // The histogram counts the number of calls to the JS API 91 // The histogram counts the number of calls to the JS API
165 // webGetUserMedia. 92 // webGetUserMedia.
166 UpdateWebRTCMethodCount(kWebkitGetUserMedia); 93 UpdateWebRTCMethodCount(kWebkitGetUserMedia);
167 DCHECK(CalledOnValidThread()); 94 DCHECK(CalledOnValidThread());
168 int request_id = g_next_request_id++; 95 int request_id = g_next_request_id++;
(...skipping 102 matching lines...) Expand 10 before | Expand all | Expand 10 after
271 DVLOG(1) << "Request ID not found"; 198 DVLOG(1) << "Request ID not found";
272 media_stream_dispatcher_->StopStream(label); 199 media_stream_dispatcher_->StopStream(label);
273 return; 200 return;
274 } 201 }
275 202
276 WebKit::WebString webkit_label = UTF8ToUTF16(label); 203 WebKit::WebString webkit_label = UTF8ToUTF16(label);
277 WebKit::WebMediaStreamDescriptor description; 204 WebKit::WebMediaStreamDescriptor description;
278 description.initialize(webkit_label, audio_source_vector, 205 description.initialize(webkit_label, audio_source_vector,
279 video_source_vector); 206 video_source_vector);
280 207
281 if (!CreateNativeLocalMediaStream(&description)) { 208 if (!dependency_factory_->CreateNativeLocalMediaStream(&description)) {
282 DVLOG(1) << "Failed to create native stream in OnStreamGenerated."; 209 DVLOG(1) << "Failed to create native stream in OnStreamGenerated.";
283 media_stream_dispatcher_->StopStream(label); 210 media_stream_dispatcher_->StopStream(label);
284 it->second.request_.requestFailed(); 211 it->second.request_.requestFailed();
285 user_media_requests_.erase(it); 212 user_media_requests_.erase(it);
286 return; 213 return;
287 } 214 }
288 local_media_streams_[label] = it->second.frame_; 215 local_media_streams_[label] = it->second.frame_;
289 CompleteGetUserMediaRequest(description, &it->second.request_); 216 CompleteGetUserMediaRequest(description, &it->second.request_);
290 user_media_requests_.erase(it); 217 user_media_requests_.erase(it);
291 } 218 }
(...skipping 85 matching lines...) Expand 10 before | Expand all | Expand 10 after
377 << "Stopping stream " << it->first; 304 << "Stopping stream " << it->first;
378 media_stream_dispatcher_->StopStream(it->first); 305 media_stream_dispatcher_->StopStream(it->first);
379 local_media_streams_.erase(it); 306 local_media_streams_.erase(it);
380 it = local_media_streams_.begin(); 307 it = local_media_streams_.begin();
381 } else { 308 } else {
382 ++it; 309 ++it;
383 } 310 }
384 } 311 }
385 } 312 }
386 313
387 void MediaStreamImpl::InitializeWorkerThread(talk_base::Thread** thread,
388 base::WaitableEvent* event) {
389 jingle_glue::JingleThreadWrapper::EnsureForCurrentMessageLoop();
390 jingle_glue::JingleThreadWrapper::current()->set_send_allowed(true);
391 *thread = jingle_glue::JingleThreadWrapper::current();
392 event->Signal();
393 }
394
395 void MediaStreamImpl::CreateIpcNetworkManagerOnWorkerThread(
396 base::WaitableEvent* event) {
397 DCHECK_EQ(MessageLoop::current(), chrome_worker_thread_.message_loop());
398 network_manager_ = new content::IpcNetworkManager(p2p_socket_dispatcher_);
399 event->Signal();
400 }
401
402 void MediaStreamImpl::DeleteIpcNetworkManager() {
403 DCHECK_EQ(MessageLoop::current(), chrome_worker_thread_.message_loop());
404 delete network_manager_;
405 network_manager_ = NULL;
406 }
407
408 bool MediaStreamImpl::EnsurePeerConnectionFactory() {
409 DCHECK(CalledOnValidThread());
410 if (!signaling_thread_) {
411 jingle_glue::JingleThreadWrapper::EnsureForCurrentMessageLoop();
412 jingle_glue::JingleThreadWrapper::current()->set_send_allowed(true);
413 signaling_thread_ = jingle_glue::JingleThreadWrapper::current();
414 }
415
416 if (!worker_thread_) {
417 if (!chrome_worker_thread_.IsRunning()) {
418 if (!chrome_worker_thread_.Start()) {
419 LOG(ERROR) << "Could not start worker thread";
420 signaling_thread_ = NULL;
421 return false;
422 }
423 }
424 base::WaitableEvent event(true, false);
425 chrome_worker_thread_.message_loop()->PostTask(FROM_HERE, base::Bind(
426 &MediaStreamImpl::InitializeWorkerThread,
427 base::Unretained(this),
428 &worker_thread_,
429 &event));
430 event.Wait();
431 DCHECK(worker_thread_);
432 }
433
434 if (!network_manager_) {
435 base::WaitableEvent event(true, false);
436 chrome_worker_thread_.message_loop()->PostTask(FROM_HERE, base::Bind(
437 &MediaStreamImpl::CreateIpcNetworkManagerOnWorkerThread,
438 base::Unretained(this),
439 &event));
440 event.Wait();
441 }
442
443 if (!socket_factory_.get()) {
444 socket_factory_.reset(
445 new content::IpcPacketSocketFactory(p2p_socket_dispatcher_));
446 }
447
448 if (!dependency_factory_->PeerConnectionFactoryCreated()) {
449 if (!dependency_factory_->CreatePeerConnectionFactory(
450 worker_thread_,
451 signaling_thread_,
452 p2p_socket_dispatcher_,
453 network_manager_,
454 socket_factory_.get())) {
455 LOG(ERROR) << "Could not create PeerConnection factory";
456 return false;
457 }
458 }
459
460 return true;
461 }
462
463 void MediaStreamImpl::CleanupPeerConnectionFactory() {
464 if (dependency_factory_.get())
465 dependency_factory_->ReleasePeerConnectionFactory();
466 if (network_manager_) {
467 // The network manager needs to free its resources on the thread they were
468 // created, which is the worked thread.
469 if (chrome_worker_thread_.IsRunning()) {
470 chrome_worker_thread_.message_loop()->PostTask(FROM_HERE, base::Bind(
471 &MediaStreamImpl::DeleteIpcNetworkManager,
472 base::Unretained(this)));
473 // Stopping the thread will wait until all tasks have been
474 // processed before returning. We wait for the above task to finish before
475 // letting the the function continue to avoid any potential race issues.
476 chrome_worker_thread_.Stop();
477 } else {
478 NOTREACHED() << "Worker thread not running.";
479 }
480 }
481 }
482
483 scoped_refptr<media::VideoDecoder> MediaStreamImpl::CreateLocalVideoDecoder( 314 scoped_refptr<media::VideoDecoder> MediaStreamImpl::CreateLocalVideoDecoder(
484 webrtc::MediaStreamInterface* stream, 315 webrtc::MediaStreamInterface* stream,
485 media::MessageLoopFactory* message_loop_factory) { 316 media::MessageLoopFactory* message_loop_factory) {
486 if (!stream->video_tracks() || stream->video_tracks()->count() == 0) 317 if (!stream->video_tracks() || stream->video_tracks()->count() == 0)
487 return NULL; 318 return NULL;
488 319
489 int video_session_id = 320 int video_session_id =
490 media_stream_dispatcher_->video_session_id(stream->label(), 0); 321 media_stream_dispatcher_->video_session_id(stream->label(), 0);
491 media::VideoCaptureCapability capability; 322 media::VideoCaptureCapability capability;
492 capability.width = kVideoCaptureWidth; 323 capability.width = kVideoCaptureWidth;
(...skipping 22 matching lines...) Expand all
515 346
516 DVLOG(1) << "MediaStreamImpl::CreateRemoteVideoDecoder label:" 347 DVLOG(1) << "MediaStreamImpl::CreateRemoteVideoDecoder label:"
517 << stream->label(); 348 << stream->label();
518 349
519 return new RTCVideoDecoder( 350 return new RTCVideoDecoder(
520 message_loop_factory->GetMessageLoop(media::MessageLoopFactory::kDecoder), 351 message_loop_factory->GetMessageLoop(media::MessageLoopFactory::kDecoder),
521 base::MessageLoopProxy::current(), 352 base::MessageLoopProxy::current(),
522 stream->video_tracks()->at(0)); 353 stream->video_tracks()->at(0));
523 } 354 }
524 355
525 bool MediaStreamImpl::CreateNativeLocalMediaStream(
526 WebKit::WebMediaStreamDescriptor* description) {
527 // Creating the peer connection factory can fail if for example the audio
528 // (input or output) or video device cannot be opened. Handling such cases
529 // better is a higher level design discussion which involves the media
530 // manager, webrtc and libjingle. We cannot create any native
531 // track objects however, so we'll just have to skip that. Furthermore,
532 // creating a peer connection later on will fail if we don't have a factory.
533 if (!EnsurePeerConnectionFactory())
534 return false;
535
536 std::string label = UTF16ToUTF8(description->label());
537 LocalNativeStreamPtr native_stream =
538 dependency_factory_->CreateLocalMediaStream(label);
539
540 // Add audio tracks.
541 WebKit::WebVector<WebKit::WebMediaStreamComponent> audio_components;
542 description->audioSources(audio_components);
543 for (size_t i = 0; i < audio_components.size(); ++i) {
544 const WebKit::WebMediaStreamSource& source = audio_components[i].source();
545 MediaStreamSourceExtraData* source_data =
546 static_cast<MediaStreamSourceExtraData*>(source.extraData());
547 if (!source_data) {
548 // TODO(perkj): Implement support for sources from remote MediaStreams.
549 NOTIMPLEMENTED();
550 continue;
551 }
552 // TODO(perkj): Refactor the creation of audio tracks to use a proper
553 // interface for receiving audio input data. Currently NULL is passed since
554 // the |audio_device| is the wrong class and is unused.
555 talk_base::scoped_refptr<webrtc::LocalAudioTrackInterface> audio_track(
556 dependency_factory_->CreateLocalAudioTrack(
557 UTF16ToUTF8(source.id()), NULL));
558 native_stream->AddTrack(audio_track);
559 audio_track->set_enabled(audio_components[i].isEnabled());
560 // TODO(xians): This set the source of all audio tracks to the same
561 // microphone. Implement support for setting the source per audio track
562 // instead.
563 dependency_factory_->SetAudioDeviceSessionId(
564 source_data->device_info().session_id);
565 }
566
567 // Add video tracks.
568 WebKit::WebVector<WebKit::WebMediaStreamComponent> video_components;
569 description->videoSources(video_components);
570 for (size_t i = 0; i < video_components.size(); ++i) {
571 const WebKit::WebMediaStreamSource& source = video_components[i].source();
572 MediaStreamSourceExtraData* source_data =
573 static_cast<MediaStreamSourceExtraData*>(source.extraData());
574 if (!source_data) {
575 // TODO(perkj): Implement support for sources from remote MediaStreams.
576 NOTIMPLEMENTED();
577 continue;
578 }
579 talk_base::scoped_refptr<webrtc::LocalVideoTrackInterface> video_track(
580 dependency_factory_->CreateLocalVideoTrack(
581 UTF16ToUTF8(source.id()), source_data->device_info().session_id));
582 native_stream->AddTrack(video_track);
583 video_track->set_enabled(video_components[i].isEnabled());
584 }
585
586 description->setExtraData(new MediaStreamExtraData(native_stream));
587
588 return true;
589 }
590
591 MediaStreamExtraData::MediaStreamExtraData( 356 MediaStreamExtraData::MediaStreamExtraData(
592 webrtc::MediaStreamInterface* remote_stream) 357 webrtc::MediaStreamInterface* remote_stream)
593 : remote_stream_(remote_stream) { 358 : remote_stream_(remote_stream) {
594 } 359 }
595 MediaStreamExtraData::MediaStreamExtraData( 360 MediaStreamExtraData::MediaStreamExtraData(
596 webrtc::LocalMediaStreamInterface* local_stream) 361 webrtc::LocalMediaStreamInterface* local_stream)
597 : local_stream_(local_stream) { 362 : local_stream_(local_stream) {
598 } 363 }
599 MediaStreamExtraData::~MediaStreamExtraData() { 364 MediaStreamExtraData::~MediaStreamExtraData() {
600 } 365 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698