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

Side by Side Diff: webrtc/modules/rtp_rtcp/source/tmmbr_help.cc

Issue 1989363006: TMMBRHelp::FindBoundingSet function cleaned (Closed) Base URL: https://chromium.googlesource.com/external/webrtc.git@master
Patch Set: change function signature and remove now unused functions Created 4 years, 7 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
« no previous file with comments | « webrtc/modules/rtp_rtcp/source/tmmbr_help.h ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 /* 1 /*
2 * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. 2 * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
3 * 3 *
4 * Use of this source code is governed by a BSD-style license 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 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 6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may 7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree. 8 * be found in the AUTHORS file in the root of the source tree.
9 */ 9 */
10 10
11 #include "webrtc/modules/rtp_rtcp/source/tmmbr_help.h" 11 #include "webrtc/modules/rtp_rtcp/source/tmmbr_help.h"
12 12
13 #include <assert.h> 13 #include <algorithm>
14 #include <string.h>
15
16 #include <limits> 14 #include <limits>
15 #include <memory>
17 16
18 #include "webrtc/base/checks.h" 17 #include "webrtc/base/checks.h"
19 #include "webrtc/modules/rtp_rtcp/source/rtp_rtcp_config.h" 18 #include "webrtc/modules/rtp_rtcp/source/rtp_rtcp_config.h"
20 19
21 namespace webrtc { 20 namespace webrtc {
22 void 21 void
23 TMMBRSet::VerifyAndAllocateSet(uint32_t minimumSize) 22 TMMBRSet::VerifyAndAllocateSet(uint32_t minimumSize)
24 { 23 {
25 clear(); 24 clear();
26 reserve(minimumSize); 25 reserve(minimumSize);
(...skipping 23 matching lines...) Expand all
50 uint32_t ssrcSet) { 49 uint32_t ssrcSet) {
51 RTC_DCHECK_LT(size(), capacity()); 50 RTC_DCHECK_LT(size(), capacity());
52 SetEntry(size(), tmmbrSet, packetOHSet, ssrcSet); 51 SetEntry(size(), tmmbrSet, packetOHSet, ssrcSet);
53 } 52 }
54 53
55 void TMMBRSet::RemoveEntry(uint32_t sourceIdx) { 54 void TMMBRSet::RemoveEntry(uint32_t sourceIdx) {
56 RTC_DCHECK_LT(sourceIdx, size()); 55 RTC_DCHECK_LT(sourceIdx, size());
57 erase(begin() + sourceIdx); 56 erase(begin() + sourceIdx);
58 } 57 }
59 58
60 void TMMBRSet::SwapEntries(uint32_t i, uint32_t j) {
61 using std::swap;
62 swap((*this)[i], (*this)[j]);
63 }
64
65 void TMMBRSet::ClearEntry(uint32_t idx) {
66 SetEntry(idx, 0, 0, 0);
67 }
68
69 TMMBRHelp::TMMBRHelp()
70 : _candidateSet(),
71 _boundingSet(),
72 _ptrIntersectionBoundingSet(NULL),
73 _ptrMaxPRBoundingSet(NULL) {
74 }
75
76 TMMBRHelp::~TMMBRHelp() {
77 delete [] _ptrIntersectionBoundingSet;
78 delete [] _ptrMaxPRBoundingSet;
79 _ptrIntersectionBoundingSet = 0;
80 _ptrMaxPRBoundingSet = 0;
81 }
82
83 TMMBRSet*
84 TMMBRHelp::VerifyAndAllocateBoundingSet(uint32_t minimumSize)
85 {
86 rtc::CritScope lock(&_criticalSection);
87
88 if(minimumSize > _boundingSet.capacity())
89 {
90 // make sure that our buffers are big enough
91 if(_ptrIntersectionBoundingSet)
92 {
93 delete [] _ptrIntersectionBoundingSet;
94 delete [] _ptrMaxPRBoundingSet;
95 }
96 _ptrIntersectionBoundingSet = new float[minimumSize];
97 _ptrMaxPRBoundingSet = new float[minimumSize];
98 }
99 _boundingSet.VerifyAndAllocateSet(minimumSize);
100 return &_boundingSet;
101 }
102
103 TMMBRSet* TMMBRHelp::BoundingSet() {
104 return &_boundingSet;
105 }
106
107 TMMBRSet* 59 TMMBRSet*
108 TMMBRHelp::VerifyAndAllocateCandidateSet(uint32_t minimumSize) 60 TMMBRHelp::VerifyAndAllocateCandidateSet(uint32_t minimumSize)
109 { 61 {
110 rtc::CritScope lock(&_criticalSection); 62 rtc::CritScope lock(&_criticalSection);
111 63
112 _candidateSet.VerifyAndAllocateSet(minimumSize); 64 _candidateSet.VerifyAndAllocateSet(minimumSize);
113 return &_candidateSet; 65 return &_candidateSet;
114 } 66 }
115 67
116 TMMBRSet* 68 TMMBRSet*
(...skipping 15 matching lines...) Expand all
132 { 84 {
133 if(_candidateSet.Tmmbr(i)) 85 if(_candidateSet.Tmmbr(i))
134 { 86 {
135 candidateSet.AddEntry(_candidateSet.Tmmbr(i), 87 candidateSet.AddEntry(_candidateSet.Tmmbr(i),
136 _candidateSet.PacketOH(i), 88 _candidateSet.PacketOH(i),
137 _candidateSet.Ssrc(i)); 89 _candidateSet.Ssrc(i));
138 } 90 }
139 else 91 else
140 { 92 {
141 // make sure this is zero if tmmbr = 0 93 // make sure this is zero if tmmbr = 0
142 assert(_candidateSet.PacketOH(i) == 0); 94 RTC_DCHECK_EQ(_candidateSet.PacketOH(i), 0u);
143 // Old code: 95 // Old code:
144 // _candidateSet.ptrPacketOHSet[i] = 0; 96 // _candidateSet.ptrPacketOHSet[i] = 0;
145 } 97 }
146 } 98 }
147 99
148 // Number of set candidates 100 // Number of set candidates
149 int32_t numSetCandidates = candidateSet.lengthOfSet(); 101 int32_t numSetCandidates = candidateSet.lengthOfSet();
150 // Find bounding set 102 // Find bounding set
151 uint32_t numBoundingSet = 0; 103 uint32_t numBoundingSet = 0;
152 if (numSetCandidates > 0) 104 if (numSetCandidates > 0)
153 { 105 {
154 numBoundingSet = FindTMMBRBoundingSet(numSetCandidates, candidateSet); 106 FindBoundingSet(std::move(candidateSet), &_boundingSet);
107 numBoundingSet = _boundingSet.size();
155 if(numBoundingSet < 1 || (numBoundingSet > _candidateSet.size())) 108 if(numBoundingSet < 1 || (numBoundingSet > _candidateSet.size()))
156 { 109 {
157 return -1; 110 return -1;
158 } 111 }
159 boundingSet = &_boundingSet; 112 boundingSet = &_boundingSet;
160 } 113 }
161 return numBoundingSet; 114 return numBoundingSet;
162 } 115 }
163 116
164 117
165 int32_t 118 void
166 TMMBRHelp::FindTMMBRBoundingSet(int32_t numCandidates, TMMBRSet& candidateSet) 119 TMMBRHelp::FindBoundingSet(std::vector<rtcp::TmmbItem> candidateSet,
120 std::vector<rtcp::TmmbItem>* boundingSet)
167 { 121 {
168 rtc::CritScope lock(&_criticalSection); 122 RTC_DCHECK(boundingSet);
123 RTC_DCHECK(!candidateSet.empty());
124 size_t numCandidates = candidateSet.size();
169 125
170 uint32_t numBoundingSet = 0; 126 uint32_t numBoundingSet = 0;
171 VerifyAndAllocateBoundingSet(candidateSet.capacity()); 127 boundingSet->clear();
128 boundingSet->reserve(candidateSet.size());
172 129
173 if (numCandidates == 1) 130 if (numCandidates == 1)
174 { 131 {
175 for (uint32_t i = 0; i < candidateSet.size(); i++) 132 RTC_DCHECK(candidateSet[0].bitrate_bps());
176 { 133 *boundingSet = std::move(candidateSet);
177 if (candidateSet.Tmmbr(i) > 0) 134 return;
178 {
179 _boundingSet.AddEntry(candidateSet.Tmmbr(i),
180 candidateSet.PacketOH(i),
181 candidateSet.Ssrc(i));
182 numBoundingSet++;
183 }
184 }
185 return (numBoundingSet == 1) ? 1 : -1;
186 } 135 }
187 136
188 // 1. Sort by increasing packetOH 137 // 1. Sort by increasing packetOH
189 for (int i = candidateSet.size() - 1; i >= 0; i--) 138 std::sort(candidateSet.begin(), candidateSet.end(),
190 { 139 [](const rtcp::TmmbItem& lhs, const rtcp::TmmbItem& rhs) {
191 for (int j = 1; j <= i; j++) 140 return lhs.packet_overhead() < rhs.packet_overhead();
192 { 141 });
193 if (candidateSet.PacketOH(j-1) > candidateSet.PacketOH(j))
194 {
195 candidateSet.SwapEntries(j-1, j);
196 }
197 }
198 }
199 // 2. For tuples with same OH, keep the one w/ the lowest bitrate 142 // 2. For tuples with same OH, keep the one w/ the lowest bitrate
200 for (uint32_t i = 0; i < candidateSet.size(); i++) 143 for (uint32_t i = 0; i < candidateSet.size(); i++)
201 { 144 {
202 if (candidateSet.Tmmbr(i) > 0) 145 if (candidateSet[i].bitrate_bps())
203 { 146 {
204 // get min bitrate for packets w/ same OH 147 // get min bitrate for packets w/ same OH
205 uint32_t currentPacketOH = candidateSet.PacketOH(i); 148 uint32_t currentPacketOH = candidateSet[i].packet_overhead();
206 uint32_t currentMinTMMBR = candidateSet.Tmmbr(i); 149 uint32_t currentMinTMMBR = candidateSet[i].bitrate_bps();
207 uint32_t currentMinIndexTMMBR = i; 150 uint32_t currentMinIndexTMMBR = i;
208 for (uint32_t j = i+1; j < candidateSet.size(); j++) 151 for (uint32_t j = i+1; j < candidateSet.size(); j++)
209 { 152 {
210 if(candidateSet.PacketOH(j) == currentPacketOH) 153 if(candidateSet[j].packet_overhead() == currentPacketOH)
211 { 154 {
212 if(candidateSet.Tmmbr(j) < currentMinTMMBR) 155 if(candidateSet[j].bitrate_bps() < currentMinTMMBR)
213 { 156 {
214 currentMinTMMBR = candidateSet.Tmmbr(j); 157 currentMinTMMBR = candidateSet[j].bitrate_bps();
215 currentMinIndexTMMBR = j; 158 currentMinIndexTMMBR = j;
216 } 159 }
217 } 160 }
218 } 161 }
219 // keep lowest bitrate 162 // keep lowest bitrate
220 for (uint32_t j = 0; j < candidateSet.size(); j++) 163 for (uint32_t j = 0; j < candidateSet.size(); j++)
221 { 164 {
222 if(candidateSet.PacketOH(j) == currentPacketOH 165 if(candidateSet[j].packet_overhead() == currentPacketOH
223 && j != currentMinIndexTMMBR) 166 && j != currentMinIndexTMMBR)
224 { 167 {
225 candidateSet.ClearEntry(j); 168 candidateSet[j].set_bitrate_bps(0);
226 numCandidates--; 169 numCandidates--;
227 } 170 }
228 } 171 }
229 } 172 }
230 } 173 }
231 // 3. Select and remove tuple w/ lowest tmmbr. 174 // 3. Select and remove tuple w/ lowest tmmbr.
232 // (If more than 1, choose the one w/ highest OH). 175 // (If more than 1, choose the one w/ highest OH).
233 uint32_t minTMMBR = 0; 176 uint32_t minTMMBR = 0;
234 uint32_t minIndexTMMBR = 0; 177 uint32_t minIndexTMMBR = 0;
235 for (uint32_t i = 0; i < candidateSet.size(); i++) 178 for (uint32_t i = 0; i < candidateSet.size(); i++)
236 { 179 {
237 if (candidateSet.Tmmbr(i) > 0) 180 if (candidateSet[i].bitrate_bps())
238 { 181 {
239 minTMMBR = candidateSet.Tmmbr(i); 182 minTMMBR = candidateSet[i].bitrate_bps();
240 minIndexTMMBR = i; 183 minIndexTMMBR = i;
241 break; 184 break;
242 } 185 }
243 } 186 }
244 187
245 for (uint32_t i = 0; i < candidateSet.size(); i++) 188 for (uint32_t i = 0; i < candidateSet.size(); i++)
246 { 189 {
247 if (candidateSet.Tmmbr(i) > 0 && candidateSet.Tmmbr(i) <= minTMMBR) 190 if (candidateSet[i].bitrate_bps() &&
191 candidateSet[i].bitrate_bps() <= minTMMBR)
248 { 192 {
249 // get min bitrate 193 // get min bitrate
250 minTMMBR = candidateSet.Tmmbr(i); 194 minTMMBR = candidateSet[i].bitrate_bps();
251 minIndexTMMBR = i; 195 minIndexTMMBR = i;
252 } 196 }
253 } 197 }
254 // first member of selected list 198 // first member of selected list
255 _boundingSet.SetEntry(numBoundingSet, 199 boundingSet->push_back(candidateSet[minIndexTMMBR]);
256 candidateSet.Tmmbr(minIndexTMMBR),
257 candidateSet.PacketOH(minIndexTMMBR),
258 candidateSet.Ssrc(minIndexTMMBR));
259 200
201 std::unique_ptr<float[]> _ptrIntersectionBoundingSet(new float[numCandidates ]);
202 std::unique_ptr<float[]> _ptrMaxPRBoundingSet(new float[numCandidates]);
260 // set intersection value 203 // set intersection value
261 _ptrIntersectionBoundingSet[numBoundingSet] = 0; 204 _ptrIntersectionBoundingSet[numBoundingSet] = 0;
262 // calculate its maximum packet rate (where its line crosses x-axis) 205 // calculate its maximum packet rate (where its line crosses x-axis)
263 uint32_t packet_overhead_bits = 8 * _boundingSet.PacketOH(numBoundingSet); 206 uint32_t packet_overhead_bits = 8 * boundingSet->back().packet_overhead();
264 if (packet_overhead_bits == 0) { 207 if (packet_overhead_bits == 0) {
265 // Avoid division by zero. 208 // Avoid division by zero.
266 _ptrMaxPRBoundingSet[numBoundingSet] = std::numeric_limits<float>::max(); 209 _ptrMaxPRBoundingSet[numBoundingSet] = std::numeric_limits<float>::max();
267 } else { 210 } else {
268 _ptrMaxPRBoundingSet[numBoundingSet] = 211 _ptrMaxPRBoundingSet[numBoundingSet] =
269 _boundingSet.Tmmbr(numBoundingSet) * 1000 / 212 boundingSet->back().bitrate_bps() /
270 static_cast<float>(packet_overhead_bits); 213 static_cast<float>(packet_overhead_bits);
271 } 214 }
272 numBoundingSet++; 215 numBoundingSet++;
273 // remove from candidate list 216 // remove from candidate list
274 candidateSet.ClearEntry(minIndexTMMBR); 217 candidateSet[minIndexTMMBR].set_bitrate_bps(0);
275 numCandidates--; 218 numCandidates--;
276 219
277 // 4. Discard from candidate list all tuple w/ lower OH 220 // 4. Discard from candidate list all tuple w/ lower OH
278 // (next tuple must be steeper) 221 // (next tuple must be steeper)
279 for (uint32_t i = 0; i < candidateSet.size(); i++) 222 for (uint32_t i = 0; i < candidateSet.size(); i++)
280 { 223 {
281 if(candidateSet.Tmmbr(i) > 0 224 if(candidateSet[i].bitrate_bps()
282 && candidateSet.PacketOH(i) < _boundingSet.PacketOH(0)) 225 && candidateSet[i].packet_overhead() < boundingSet->front().packet_o verhead())
283 { 226 {
284 candidateSet.ClearEntry(i); 227 candidateSet[i].set_bitrate_bps(0);
285 numCandidates--; 228 numCandidates--;
286 } 229 }
287 } 230 }
288 231
289 if (numCandidates == 0) 232 if (numCandidates == 0)
290 { 233 {
291 // Should be true already:_boundingSet.lengthOfSet = numBoundingSet; 234 // Should be true already:_boundingSet.lengthOfSet = numBoundingSet;
292 assert(_boundingSet.lengthOfSet() == numBoundingSet); 235 RTC_DCHECK_EQ(boundingSet->size(), numBoundingSet);
293 return numBoundingSet; 236 return;
294 } 237 }
295 238
296 bool getNewCandidate = true; 239 bool getNewCandidate = true;
297 uint32_t curCandidateTMMBR = 0; 240 rtcp::TmmbItem curCandidate;
298 size_t curCandidateIndex = 0;
299 uint32_t curCandidatePacketOH = 0;
300 uint32_t curCandidateSSRC = 0;
301 do 241 do
302 { 242 {
303 if (getNewCandidate) 243 if (getNewCandidate)
304 { 244 {
305 // 5. Remove first remaining tuple from candidate list 245 // 5. Remove first remaining tuple from candidate list
306 for (uint32_t i = 0; i < candidateSet.size(); i++) 246 for (uint32_t i = 0; i < candidateSet.size(); i++)
307 { 247 {
308 if (candidateSet.Tmmbr(i) > 0) 248 if (candidateSet[i].bitrate_bps())
309 { 249 {
310 curCandidateTMMBR = candidateSet.Tmmbr(i); 250 curCandidate = candidateSet[i];
311 curCandidatePacketOH = candidateSet.PacketOH(i); 251 candidateSet[i].set_bitrate_bps(0);
312 curCandidateSSRC = candidateSet.Ssrc(i);
313 curCandidateIndex = i;
314 candidateSet.ClearEntry(curCandidateIndex);
315 break; 252 break;
316 } 253 }
317 } 254 }
318 } 255 }
319 256
320 // 6. Calculate packet rate and intersection of the current 257 // 6. Calculate packet rate and intersection of the current
321 // line with line of last tuple in selected list 258 // line with line of last tuple in selected list
322 RTC_DCHECK_NE(curCandidatePacketOH, 259 RTC_DCHECK_NE(curCandidate.packet_overhead(),
323 _boundingSet.PacketOH(numBoundingSet - 1)); 260 boundingSet->back().packet_overhead());
324 float packetRate 261 float packetRate
325 = float(curCandidateTMMBR 262 = float(curCandidate.bitrate_bps()
326 - _boundingSet.Tmmbr(numBoundingSet-1))*1000 263 - boundingSet->back().bitrate_bps())
327 / (8*(curCandidatePacketOH 264 / (8*(curCandidate.packet_overhead()
328 - _boundingSet.PacketOH(numBoundingSet-1))); 265 - boundingSet->back().packet_overhead()));
329 266
330 // 7. If the packet rate is equal or lower than intersection of 267 // 7. If the packet rate is equal or lower than intersection of
331 // last tuple in selected list, 268 // last tuple in selected list,
332 // remove last tuple in selected list & go back to step 6 269 // remove last tuple in selected list & go back to step 6
333 if(packetRate <= _ptrIntersectionBoundingSet[numBoundingSet-1]) 270 if(packetRate <= _ptrIntersectionBoundingSet[numBoundingSet-1])
334 { 271 {
335 // remove last tuple and goto step 6 272 // remove last tuple and goto step 6
336 numBoundingSet--; 273 numBoundingSet--;
337 _boundingSet.ClearEntry(numBoundingSet); 274 boundingSet->pop_back();
338 _ptrIntersectionBoundingSet[numBoundingSet] = 0;
339 _ptrMaxPRBoundingSet[numBoundingSet] = 0;
340 getNewCandidate = false; 275 getNewCandidate = false;
341 } else 276 } else
342 { 277 {
343 // 8. If packet rate is lower than maximum packet rate of 278 // 8. If packet rate is lower than maximum packet rate of
344 // last tuple in selected list, add current tuple to selected 279 // last tuple in selected list, add current tuple to selected
345 // list 280 // list
346 if (packetRate < _ptrMaxPRBoundingSet[numBoundingSet-1]) 281 if (packetRate < _ptrMaxPRBoundingSet[numBoundingSet-1])
347 { 282 {
348 _boundingSet.SetEntry(numBoundingSet, 283 boundingSet->push_back(curCandidate);
349 curCandidateTMMBR,
350 curCandidatePacketOH,
351 curCandidateSSRC);
352 _ptrIntersectionBoundingSet[numBoundingSet] = packetRate; 284 _ptrIntersectionBoundingSet[numBoundingSet] = packetRate;
353 float packet_overhead_bits = 285 float packet_overhead_bits =
354 8 * _boundingSet.PacketOH(numBoundingSet); 286 8 * boundingSet->back().packet_overhead();
355 RTC_DCHECK_NE(packet_overhead_bits, 0.0f); 287 RTC_DCHECK_NE(packet_overhead_bits, 0.0f);
356 _ptrMaxPRBoundingSet[numBoundingSet] = 288 _ptrMaxPRBoundingSet[numBoundingSet] =
357 _boundingSet.Tmmbr(numBoundingSet) * 1000 / 289 boundingSet->back().bitrate_bps() * 1000 /
danilchap 2016/05/20 13:38:53 overlooked removing *1000 here, removed in patchse
358 packet_overhead_bits; 290 packet_overhead_bits;
359 numBoundingSet++; 291 numBoundingSet++;
360 } 292 }
361 numCandidates--; 293 numCandidates--;
362 getNewCandidate = true; 294 getNewCandidate = true;
363 } 295 }
364 296
365 // 9. Go back to step 5 if any tuple remains in candidate list 297 // 9. Go back to step 5 if any tuple remains in candidate list
366 } while (numCandidates > 0); 298 } while (numCandidates > 0);
367 299
368 return numBoundingSet; 300 RTC_DCHECK_EQ(boundingSet->size(), numBoundingSet);
369 } 301 }
370 302
371 bool TMMBRHelp::IsOwner(const uint32_t ssrc, 303 bool TMMBRHelp::IsOwner(const uint32_t ssrc,
372 const uint32_t length) const { 304 const uint32_t length) const {
373 rtc::CritScope lock(&_criticalSection); 305 rtc::CritScope lock(&_criticalSection);
374 306
375 if (length == 0) { 307 if (length == 0) {
376 // Empty bounding set. 308 // Empty bounding set.
377 return false; 309 return false;
378 } 310 }
(...skipping 19 matching lines...) Expand all
398 uint32_t curNetBitRateKbit = _candidateSet.Tmmbr(i); 330 uint32_t curNetBitRateKbit = _candidateSet.Tmmbr(i);
399 if (curNetBitRateKbit < MIN_VIDEO_BW_MANAGEMENT_BITRATE) { 331 if (curNetBitRateKbit < MIN_VIDEO_BW_MANAGEMENT_BITRATE) {
400 curNetBitRateKbit = MIN_VIDEO_BW_MANAGEMENT_BITRATE; 332 curNetBitRateKbit = MIN_VIDEO_BW_MANAGEMENT_BITRATE;
401 } 333 }
402 *minBitrateKbit = curNetBitRateKbit < *minBitrateKbit ? 334 *minBitrateKbit = curNetBitRateKbit < *minBitrateKbit ?
403 curNetBitRateKbit : *minBitrateKbit; 335 curNetBitRateKbit : *minBitrateKbit;
404 } 336 }
405 return true; 337 return true;
406 } 338 }
407 } // namespace webrtc 339 } // namespace webrtc
OLDNEW
« no previous file with comments | « webrtc/modules/rtp_rtcp/source/tmmbr_help.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698