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

Side by Side Diff: media/capture/video_capture_oracle_unittest.cc

Issue 1199593005: Automatic resolution throttling for screen capture pipeline. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@resolution_chooser_ITEM13
Patch Set: Created 5 years, 6 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
OLDNEW
1 // Copyright (c) 2015 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2015 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 "media/capture/video_capture_oracle.h" 5 #include "media/capture/video_capture_oracle.h"
6 6
7 #include "base/strings/stringprintf.h" 7 #include "base/strings/stringprintf.h"
8 #include "testing/gtest/include/gtest/gtest.h" 8 #include "testing/gtest/include/gtest/gtest.h"
9 9
10 namespace media { 10 namespace media {
11 11
12 namespace { 12 namespace {
13 13
14 base::TimeTicks InitialTestTimeTicks() { 14 base::TimeTicks InitialTestTimeTicks() {
15 return base::TimeTicks() + base::TimeDelta::FromSeconds(1); 15 return base::TimeTicks() + base::TimeDelta::FromSeconds(1);
16 } 16 }
17 17
18 } // namespace 18 } // namespace
19 19
20 // Tests that VideoCaptureOracle filters out events whose timestamps are 20 // Tests that VideoCaptureOracle filters out events whose timestamps are
21 // decreasing. 21 // decreasing.
22 TEST(VideoCaptureOracleTest, EnforcesEventTimeMonotonicity) { 22 TEST(VideoCaptureOracleTest, EnforcesEventTimeMonotonicity) {
23 const base::TimeDelta min_capture_period = 23 const base::TimeDelta min_capture_period =
24 base::TimeDelta::FromSeconds(1) / 30; 24 base::TimeDelta::FromSeconds(1) / 30;
25 const gfx::Rect damage_rect(0, 0, 1280, 720); 25 const gfx::Rect damage_rect(0, 0, 1280, 720);
26 const base::TimeDelta event_increment = min_capture_period * 2; 26 const base::TimeDelta event_increment = min_capture_period * 2;
27 27
28 VideoCaptureOracle oracle(min_capture_period); 28 VideoCaptureOracle oracle(min_capture_period,
29 gfx::Size(1280, 720),
30 media::RESOLUTION_POLICY_FIXED_RESOLUTION);
29 31
30 base::TimeTicks t = InitialTestTimeTicks(); 32 base::TimeTicks t = InitialTestTimeTicks();
31 for (int i = 0; i < 10; ++i) { 33 for (int i = 0; i < 10; ++i) {
32 t += event_increment; 34 t += event_increment;
33 ASSERT_TRUE(oracle.ObserveEventAndDecideCapture( 35 ASSERT_TRUE(oracle.ObserveEventAndDecideCapture(
34 VideoCaptureOracle::kCompositorUpdate, 36 VideoCaptureOracle::kCompositorUpdate,
35 damage_rect, t)); 37 damage_rect, t));
36 } 38 }
37 39
38 base::TimeTicks furthest_event_time = t; 40 base::TimeTicks furthest_event_time = t;
(...skipping 15 matching lines...) Expand all
54 56
55 // Tests that VideoCaptureOracle is enforcing the requirement that 57 // Tests that VideoCaptureOracle is enforcing the requirement that
56 // successfully captured frames are delivered in order. Otherwise, downstream 58 // successfully captured frames are delivered in order. Otherwise, downstream
57 // consumers could be tripped-up by out-of-order frames or frame timestamps. 59 // consumers could be tripped-up by out-of-order frames or frame timestamps.
58 TEST(VideoCaptureOracleTest, EnforcesFramesDeliveredInOrder) { 60 TEST(VideoCaptureOracleTest, EnforcesFramesDeliveredInOrder) {
59 const base::TimeDelta min_capture_period = 61 const base::TimeDelta min_capture_period =
60 base::TimeDelta::FromSeconds(1) / 30; 62 base::TimeDelta::FromSeconds(1) / 30;
61 const gfx::Rect damage_rect(0, 0, 1280, 720); 63 const gfx::Rect damage_rect(0, 0, 1280, 720);
62 const base::TimeDelta event_increment = min_capture_period * 2; 64 const base::TimeDelta event_increment = min_capture_period * 2;
63 65
64 VideoCaptureOracle oracle(min_capture_period); 66 VideoCaptureOracle oracle(min_capture_period,
67 gfx::Size(1280, 720),
68 media::RESOLUTION_POLICY_FIXED_RESOLUTION);
65 69
66 // Most basic scenario: Frames delivered one at a time, with no additional 70 // Most basic scenario: Frames delivered one at a time, with no additional
67 // captures in-between deliveries. 71 // captures in-between deliveries.
68 base::TimeTicks t = InitialTestTimeTicks(); 72 base::TimeTicks t = InitialTestTimeTicks();
69 int last_frame_number; 73 int last_frame_number;
70 base::TimeTicks ignored; 74 base::TimeTicks ignored;
71 for (int i = 0; i < 10; ++i) { 75 for (int i = 0; i < 10; ++i) {
72 t += event_increment; 76 t += event_increment;
73 ASSERT_TRUE(oracle.ObserveEventAndDecideCapture( 77 ASSERT_TRUE(oracle.ObserveEventAndDecideCapture(
74 VideoCaptureOracle::kCompositorUpdate, 78 VideoCaptureOracle::kCompositorUpdate,
75 damage_rect, t)); 79 damage_rect, t));
76 last_frame_number = oracle.RecordCapture(); 80 last_frame_number = oracle.RecordCapture(0.0);
77 ASSERT_TRUE(oracle.CompleteCapture(last_frame_number, true, &ignored)); 81 ASSERT_TRUE(oracle.CompleteCapture(last_frame_number, true, &ignored));
78 } 82 }
79 83
80 // Basic pipelined scenario: More than one frame in-flight at delivery points. 84 // Basic pipelined scenario: More than one frame in-flight at delivery points.
81 for (int i = 0; i < 50; ++i) { 85 for (int i = 0; i < 50; ++i) {
82 const int num_in_flight = 1 + i % 3; 86 const int num_in_flight = 1 + i % 3;
83 for (int j = 0; j < num_in_flight; ++j) { 87 for (int j = 0; j < num_in_flight; ++j) {
84 t += event_increment; 88 t += event_increment;
85 ASSERT_TRUE(oracle.ObserveEventAndDecideCapture( 89 ASSERT_TRUE(oracle.ObserveEventAndDecideCapture(
86 VideoCaptureOracle::kCompositorUpdate, 90 VideoCaptureOracle::kCompositorUpdate,
87 damage_rect, t)); 91 damage_rect, t));
88 last_frame_number = oracle.RecordCapture(); 92 last_frame_number = oracle.RecordCapture(0.0);
89 } 93 }
90 for (int j = num_in_flight - 1; j >= 0; --j) { 94 for (int j = num_in_flight - 1; j >= 0; --j) {
91 ASSERT_TRUE( 95 ASSERT_TRUE(
92 oracle.CompleteCapture(last_frame_number - j, true, &ignored)); 96 oracle.CompleteCapture(last_frame_number - j, true, &ignored));
93 } 97 }
94 } 98 }
95 99
96 // Pipelined scenario with successful out-of-order delivery attempts 100 // Pipelined scenario with successful out-of-order delivery attempts
97 // rejected. 101 // rejected.
98 for (int i = 0; i < 50; ++i) { 102 for (int i = 0; i < 50; ++i) {
99 const int num_in_flight = 1 + i % 3; 103 const int num_in_flight = 1 + i % 3;
100 for (int j = 0; j < num_in_flight; ++j) { 104 for (int j = 0; j < num_in_flight; ++j) {
101 t += event_increment; 105 t += event_increment;
102 ASSERT_TRUE(oracle.ObserveEventAndDecideCapture( 106 ASSERT_TRUE(oracle.ObserveEventAndDecideCapture(
103 VideoCaptureOracle::kCompositorUpdate, 107 VideoCaptureOracle::kCompositorUpdate,
104 damage_rect, t)); 108 damage_rect, t));
105 last_frame_number = oracle.RecordCapture(); 109 last_frame_number = oracle.RecordCapture(0.0);
106 } 110 }
107 ASSERT_TRUE(oracle.CompleteCapture(last_frame_number, true, &ignored)); 111 ASSERT_TRUE(oracle.CompleteCapture(last_frame_number, true, &ignored));
108 for (int j = 1; j < num_in_flight; ++j) { 112 for (int j = 1; j < num_in_flight; ++j) {
109 ASSERT_FALSE( 113 ASSERT_FALSE(
110 oracle.CompleteCapture(last_frame_number - j, true, &ignored)); 114 oracle.CompleteCapture(last_frame_number - j, true, &ignored));
111 } 115 }
112 } 116 }
113 117
114 // Pipelined scenario with successful delivery attempts accepted after an 118 // Pipelined scenario with successful delivery attempts accepted after an
115 // unsuccessful out of order delivery attempt. 119 // unsuccessful out of order delivery attempt.
116 for (int i = 0; i < 50; ++i) { 120 for (int i = 0; i < 50; ++i) {
117 const int num_in_flight = 1 + i % 3; 121 const int num_in_flight = 1 + i % 3;
118 for (int j = 0; j < num_in_flight; ++j) { 122 for (int j = 0; j < num_in_flight; ++j) {
119 t += event_increment; 123 t += event_increment;
120 ASSERT_TRUE(oracle.ObserveEventAndDecideCapture( 124 ASSERT_TRUE(oracle.ObserveEventAndDecideCapture(
121 VideoCaptureOracle::kCompositorUpdate, 125 VideoCaptureOracle::kCompositorUpdate,
122 damage_rect, t)); 126 damage_rect, t));
123 last_frame_number = oracle.RecordCapture(); 127 last_frame_number = oracle.RecordCapture(0.0);
124 } 128 }
125 // Report the last frame as an out of order failure. 129 // Report the last frame as an out of order failure.
126 ASSERT_FALSE(oracle.CompleteCapture(last_frame_number, false, &ignored)); 130 ASSERT_FALSE(oracle.CompleteCapture(last_frame_number, false, &ignored));
127 for (int j = 1; j < num_in_flight - 1; ++j) { 131 for (int j = 1; j < num_in_flight - 1; ++j) {
128 ASSERT_TRUE( 132 ASSERT_TRUE(
129 oracle.CompleteCapture(last_frame_number - j, true, &ignored)); 133 oracle.CompleteCapture(last_frame_number - j, true, &ignored));
130 } 134 }
131 135
132 } 136 }
133 } 137 }
134 138
135 // Tests that VideoCaptureOracle transitions between using its two samplers in a 139 // Tests that VideoCaptureOracle transitions between using its two samplers in a
136 // way that does not introduce severe jank, pauses, etc. 140 // way that does not introduce severe jank, pauses, etc.
137 TEST(VideoCaptureOracleTest, TransitionsSmoothlyBetweenSamplers) { 141 TEST(VideoCaptureOracleTest, TransitionsSmoothlyBetweenSamplers) {
138 const base::TimeDelta min_capture_period = 142 const base::TimeDelta min_capture_period =
139 base::TimeDelta::FromSeconds(1) / 30; 143 base::TimeDelta::FromSeconds(1) / 30;
140 const gfx::Rect animation_damage_rect(0, 0, 1280, 720); 144 const gfx::Rect animation_damage_rect(0, 0, 1280, 720);
141 const base::TimeDelta event_increment = min_capture_period * 2; 145 const base::TimeDelta event_increment = min_capture_period * 2;
142 146
143 VideoCaptureOracle oracle(min_capture_period); 147 VideoCaptureOracle oracle(min_capture_period,
148 gfx::Size(1280, 720),
149 media::RESOLUTION_POLICY_FIXED_RESOLUTION);
144 150
145 // Run sequences of animation events and non-animation events through the 151 // Run sequences of animation events and non-animation events through the
146 // oracle. As the oracle transitions between each sampler, make sure the 152 // oracle. As the oracle transitions between each sampler, make sure the
147 // frame timestamps won't trip-up downstream consumers. 153 // frame timestamps won't trip-up downstream consumers.
148 base::TimeTicks t = InitialTestTimeTicks(); 154 base::TimeTicks t = InitialTestTimeTicks();
149 base::TimeTicks last_frame_timestamp; 155 base::TimeTicks last_frame_timestamp;
150 for (int i = 0; i < 1000; ++i) { 156 for (int i = 0; i < 1000; ++i) {
151 t += event_increment; 157 t += event_increment;
152 158
153 // For every 100 events, provide 50 that will cause the 159 // For every 100 events, provide 50 that will cause the
(...skipping 12 matching lines...) Expand all
166 provide_animated_content_event ? animation_damage_rect : gfx::Rect(), 172 provide_animated_content_event ? animation_damage_rect : gfx::Rect(),
167 t); 173 t);
168 if (require_oracle_says_sample) 174 if (require_oracle_says_sample)
169 ASSERT_TRUE(oracle_says_sample); 175 ASSERT_TRUE(oracle_says_sample);
170 if (!oracle_says_sample) { 176 if (!oracle_says_sample) {
171 ASSERT_EQ(base::TimeDelta(), oracle.estimated_frame_duration()); 177 ASSERT_EQ(base::TimeDelta(), oracle.estimated_frame_duration());
172 continue; 178 continue;
173 } 179 }
174 ASSERT_LT(base::TimeDelta(), oracle.estimated_frame_duration()); 180 ASSERT_LT(base::TimeDelta(), oracle.estimated_frame_duration());
175 181
176 const int frame_number = oracle.RecordCapture(); 182 const int frame_number = oracle.RecordCapture(0.0);
177 183
178 base::TimeTicks frame_timestamp; 184 base::TimeTicks frame_timestamp;
179 ASSERT_TRUE(oracle.CompleteCapture(frame_number, true, &frame_timestamp)); 185 ASSERT_TRUE(oracle.CompleteCapture(frame_number, true, &frame_timestamp));
180 ASSERT_FALSE(frame_timestamp.is_null()); 186 ASSERT_FALSE(frame_timestamp.is_null());
181 if (!last_frame_timestamp.is_null()) { 187 if (!last_frame_timestamp.is_null()) {
182 const base::TimeDelta delta = frame_timestamp - last_frame_timestamp; 188 const base::TimeDelta delta = frame_timestamp - last_frame_timestamp;
183 EXPECT_LE(event_increment.InMicroseconds(), delta.InMicroseconds()); 189 EXPECT_LE(event_increment.InMicroseconds(), delta.InMicroseconds());
184 // Right after the AnimatedContentSampler lock-out transition, there were 190 // Right after the AnimatedContentSampler lock-out transition, there were
185 // a few frames dropped, so allow a gap in the timestamps. Otherwise, the 191 // a few frames dropped, so allow a gap in the timestamps. Otherwise, the
186 // delta between frame timestamps should never be more than 2X the 192 // delta between frame timestamps should never be more than 2X the
187 // |event_increment|. 193 // |event_increment|.
188 const base::TimeDelta max_acceptable_delta = (i % 100) == 78 ? 194 const base::TimeDelta max_acceptable_delta = (i % 100) == 78 ?
189 event_increment * 5 : event_increment * 2; 195 event_increment * 5 : event_increment * 2;
190 EXPECT_GE(max_acceptable_delta.InMicroseconds(), delta.InMicroseconds()); 196 EXPECT_GE(max_acceptable_delta.InMicroseconds(), delta.InMicroseconds());
191 } 197 }
192 last_frame_timestamp = frame_timestamp; 198 last_frame_timestamp = frame_timestamp;
193 } 199 }
194 } 200 }
195 201
196 // Tests that VideoCaptureOracle prevents timer polling from initiating 202 // Tests that VideoCaptureOracle prevents timer polling from initiating
197 // simultaneous captures. 203 // simultaneous captures.
198 TEST(VideoCaptureOracleTest, SamplesOnlyOneOverdueFrameAtATime) { 204 TEST(VideoCaptureOracleTest, SamplesOnlyOneOverdueFrameAtATime) {
199 const base::TimeDelta min_capture_period = 205 const base::TimeDelta min_capture_period =
200 base::TimeDelta::FromSeconds(1) / 30; 206 base::TimeDelta::FromSeconds(1) / 30;
201 const base::TimeDelta vsync_interval = 207 const base::TimeDelta vsync_interval =
202 base::TimeDelta::FromSeconds(1) / 60; 208 base::TimeDelta::FromSeconds(1) / 60;
203 const base::TimeDelta timer_interval = base::TimeDelta::FromMilliseconds( 209 const base::TimeDelta timer_interval = base::TimeDelta::FromMilliseconds(
204 VideoCaptureOracle::kMinTimerPollPeriodMillis); 210 VideoCaptureOracle::kMinTimerPollPeriodMillis);
205 211
206 VideoCaptureOracle oracle(min_capture_period); 212 VideoCaptureOracle oracle(min_capture_period,
213 gfx::Size(1280, 720),
214 media::RESOLUTION_POLICY_FIXED_RESOLUTION);
207 215
208 // Have the oracle observe some compositor events. Simulate that each capture 216 // Have the oracle observe some compositor events. Simulate that each capture
209 // completes successfully. 217 // completes successfully.
210 base::TimeTicks t = InitialTestTimeTicks(); 218 base::TimeTicks t = InitialTestTimeTicks();
211 base::TimeTicks ignored; 219 base::TimeTicks ignored;
212 bool did_complete_a_capture = false; 220 bool did_complete_a_capture = false;
213 for (int i = 0; i < 10; ++i) { 221 for (int i = 0; i < 10; ++i) {
214 t += vsync_interval; 222 t += vsync_interval;
215 if (oracle.ObserveEventAndDecideCapture( 223 if (oracle.ObserveEventAndDecideCapture(
216 VideoCaptureOracle::kCompositorUpdate, gfx::Rect(), t)) { 224 VideoCaptureOracle::kCompositorUpdate, gfx::Rect(), t)) {
217 ASSERT_TRUE( 225 ASSERT_TRUE(
218 oracle.CompleteCapture(oracle.RecordCapture(), true, &ignored)); 226 oracle.CompleteCapture(oracle.RecordCapture(0.0), true, &ignored));
219 did_complete_a_capture = true; 227 did_complete_a_capture = true;
220 } 228 }
221 } 229 }
222 ASSERT_TRUE(did_complete_a_capture); 230 ASSERT_TRUE(did_complete_a_capture);
223 231
224 // Start one more compositor-based capture, but do not notify of completion 232 // Start one more compositor-based capture, but do not notify of completion
225 // yet. 233 // yet.
226 for (int i = 0; i <= 10; ++i) { 234 for (int i = 0; i <= 10; ++i) {
227 ASSERT_GT(10, i) << "BUG: Seems like it'll never happen!"; 235 ASSERT_GT(10, i) << "BUG: Seems like it'll never happen!";
228 t += vsync_interval; 236 t += vsync_interval;
229 if (oracle.ObserveEventAndDecideCapture( 237 if (oracle.ObserveEventAndDecideCapture(
230 VideoCaptureOracle::kCompositorUpdate, gfx::Rect(), t)) { 238 VideoCaptureOracle::kCompositorUpdate, gfx::Rect(), t)) {
231 break; 239 break;
232 } 240 }
233 } 241 }
234 int frame_number = oracle.RecordCapture(); 242 int frame_number = oracle.RecordCapture(0.0);
235 243
236 // Stop providing the compositor events and start providing timer polling 244 // Stop providing the compositor events and start providing timer polling
237 // events. No overdue samplings should be recommended because of the 245 // events. No overdue samplings should be recommended because of the
238 // not-yet-complete compositor-based capture. 246 // not-yet-complete compositor-based capture.
239 for (int i = 0; i < 10; ++i) { 247 for (int i = 0; i < 10; ++i) {
240 t += timer_interval; 248 t += timer_interval;
241 ASSERT_FALSE(oracle.ObserveEventAndDecideCapture( 249 ASSERT_FALSE(oracle.ObserveEventAndDecideCapture(
242 VideoCaptureOracle::kTimerPoll, gfx::Rect(), t)); 250 VideoCaptureOracle::kTimerPoll, gfx::Rect(), t));
243 } 251 }
244 252
245 // Now, complete the oustanding compositor-based capture and continue 253 // Now, complete the oustanding compositor-based capture and continue
246 // providing timer polling events. The oracle should start recommending 254 // providing timer polling events. The oracle should start recommending
247 // sampling again. 255 // sampling again.
248 ASSERT_TRUE(oracle.CompleteCapture(frame_number, true, &ignored)); 256 ASSERT_TRUE(oracle.CompleteCapture(frame_number, true, &ignored));
249 did_complete_a_capture = false; 257 did_complete_a_capture = false;
250 for (int i = 0; i < 10; ++i) { 258 for (int i = 0; i < 10; ++i) {
251 t += timer_interval; 259 t += timer_interval;
252 if (oracle.ObserveEventAndDecideCapture( 260 if (oracle.ObserveEventAndDecideCapture(
253 VideoCaptureOracle::kTimerPoll, gfx::Rect(), t)) { 261 VideoCaptureOracle::kTimerPoll, gfx::Rect(), t)) {
254 ASSERT_TRUE( 262 ASSERT_TRUE(
255 oracle.CompleteCapture(oracle.RecordCapture(), true, &ignored)); 263 oracle.CompleteCapture(oracle.RecordCapture(0.0), true, &ignored));
256 did_complete_a_capture = true; 264 did_complete_a_capture = true;
257 } 265 }
258 } 266 }
259 ASSERT_TRUE(did_complete_a_capture); 267 ASSERT_TRUE(did_complete_a_capture);
260 268
261 // Start one more timer-based capture, but do not notify of completion yet. 269 // Start one more timer-based capture, but do not notify of completion yet.
262 for (int i = 0; i <= 10; ++i) { 270 for (int i = 0; i <= 10; ++i) {
263 ASSERT_GT(10, i) << "BUG: Seems like it'll never happen!"; 271 ASSERT_GT(10, i) << "BUG: Seems like it'll never happen!";
264 t += timer_interval; 272 t += timer_interval;
265 if (oracle.ObserveEventAndDecideCapture( 273 if (oracle.ObserveEventAndDecideCapture(
266 VideoCaptureOracle::kTimerPoll, gfx::Rect(), t)) { 274 VideoCaptureOracle::kTimerPoll, gfx::Rect(), t)) {
267 break; 275 break;
268 } 276 }
269 } 277 }
270 frame_number = oracle.RecordCapture(); 278 frame_number = oracle.RecordCapture(0.0);
271 279
272 // Confirm that the oracle does not recommend sampling until the outstanding 280 // Confirm that the oracle does not recommend sampling until the outstanding
273 // timer-based capture completes. 281 // timer-based capture completes.
274 for (int i = 0; i < 10; ++i) { 282 for (int i = 0; i < 10; ++i) {
275 t += timer_interval; 283 t += timer_interval;
276 ASSERT_FALSE(oracle.ObserveEventAndDecideCapture( 284 ASSERT_FALSE(oracle.ObserveEventAndDecideCapture(
277 VideoCaptureOracle::kTimerPoll, gfx::Rect(), t)); 285 VideoCaptureOracle::kTimerPoll, gfx::Rect(), t));
278 } 286 }
279 ASSERT_TRUE(oracle.CompleteCapture(frame_number, true, &ignored)); 287 ASSERT_TRUE(oracle.CompleteCapture(frame_number, true, &ignored));
280 for (int i = 0; i <= 10; ++i) { 288 for (int i = 0; i <= 10; ++i) {
281 ASSERT_GT(10, i) << "BUG: Seems like it'll never happen!"; 289 ASSERT_GT(10, i) << "BUG: Seems like it'll never happen!";
282 t += timer_interval; 290 t += timer_interval;
283 if (oracle.ObserveEventAndDecideCapture( 291 if (oracle.ObserveEventAndDecideCapture(
284 VideoCaptureOracle::kTimerPoll, gfx::Rect(), t)) { 292 VideoCaptureOracle::kTimerPoll, gfx::Rect(), t)) {
285 break; 293 break;
286 } 294 }
287 } 295 }
288 } 296 }
289 297
290 } // namespace media 298 } // namespace media
OLDNEW
« media/capture/video_capture_oracle.cc ('K') | « media/capture/video_capture_oracle.cc ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698