OLD | NEW |
| (Empty) |
1 /* | |
2 * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. | |
3 * | |
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 | |
6 * tree. An additional intellectual property rights grant can be found | |
7 * in the file PATENTS. All contributing project authors may | |
8 * be found in the AUTHORS file in the root of the source tree. | |
9 */ | |
10 | |
11 #include "webrtc/base/checks.h" | |
12 #include "webrtc/base/logging.h" | |
13 #include "webrtc/base/trace_event.h" | |
14 #include "webrtc/common_types.h" | |
15 #include "webrtc/common_video/libyuv/include/webrtc_libyuv.h" | |
16 #include "webrtc/modules/video_coding/codecs/interface/video_codec_interface.h" | |
17 #include "webrtc/modules/video_coding/main/source/encoded_frame.h" | |
18 #include "webrtc/modules/video_coding/main/source/jitter_buffer.h" | |
19 #include "webrtc/modules/video_coding/main/source/packet.h" | |
20 #include "webrtc/modules/video_coding/main/source/video_coding_impl.h" | |
21 #include "webrtc/system_wrappers/include/clock.h" | |
22 | |
23 // #define DEBUG_DECODER_BIT_STREAM | |
24 | |
25 namespace webrtc { | |
26 namespace vcm { | |
27 | |
28 VideoReceiver::VideoReceiver(Clock* clock, EventFactory* event_factory) | |
29 : clock_(clock), | |
30 process_crit_sect_(CriticalSectionWrapper::CreateCriticalSection()), | |
31 _receiveCritSect(CriticalSectionWrapper::CreateCriticalSection()), | |
32 _timing(clock_), | |
33 _receiver(&_timing, clock_, event_factory), | |
34 _decodedFrameCallback(_timing, clock_), | |
35 _frameTypeCallback(NULL), | |
36 _receiveStatsCallback(NULL), | |
37 _decoderTimingCallback(NULL), | |
38 _packetRequestCallback(NULL), | |
39 render_buffer_callback_(NULL), | |
40 _decoder(NULL), | |
41 #ifdef DEBUG_DECODER_BIT_STREAM | |
42 _bitStreamBeforeDecoder(NULL), | |
43 #endif | |
44 _frameFromFile(), | |
45 _scheduleKeyRequest(false), | |
46 max_nack_list_size_(0), | |
47 pre_decode_image_callback_(NULL), | |
48 _codecDataBase(nullptr, nullptr), | |
49 _receiveStatsTimer(1000, clock_), | |
50 _retransmissionTimer(10, clock_), | |
51 _keyRequestTimer(500, clock_) { | |
52 assert(clock_); | |
53 #ifdef DEBUG_DECODER_BIT_STREAM | |
54 _bitStreamBeforeDecoder = fopen("decoderBitStream.bit", "wb"); | |
55 #endif | |
56 } | |
57 | |
58 VideoReceiver::~VideoReceiver() { | |
59 delete _receiveCritSect; | |
60 #ifdef DEBUG_DECODER_BIT_STREAM | |
61 fclose(_bitStreamBeforeDecoder); | |
62 #endif | |
63 } | |
64 | |
65 int32_t VideoReceiver::Process() { | |
66 int32_t returnValue = VCM_OK; | |
67 | |
68 // Receive-side statistics | |
69 if (_receiveStatsTimer.TimeUntilProcess() == 0) { | |
70 _receiveStatsTimer.Processed(); | |
71 CriticalSectionScoped cs(process_crit_sect_.get()); | |
72 if (_receiveStatsCallback != NULL) { | |
73 uint32_t bitRate; | |
74 uint32_t frameRate; | |
75 _receiver.ReceiveStatistics(&bitRate, &frameRate); | |
76 _receiveStatsCallback->OnReceiveRatesUpdated(bitRate, frameRate); | |
77 } | |
78 | |
79 if (_decoderTimingCallback != NULL) { | |
80 int decode_ms; | |
81 int max_decode_ms; | |
82 int current_delay_ms; | |
83 int target_delay_ms; | |
84 int jitter_buffer_ms; | |
85 int min_playout_delay_ms; | |
86 int render_delay_ms; | |
87 _timing.GetTimings(&decode_ms, | |
88 &max_decode_ms, | |
89 ¤t_delay_ms, | |
90 &target_delay_ms, | |
91 &jitter_buffer_ms, | |
92 &min_playout_delay_ms, | |
93 &render_delay_ms); | |
94 _decoderTimingCallback->OnDecoderTiming(decode_ms, | |
95 max_decode_ms, | |
96 current_delay_ms, | |
97 target_delay_ms, | |
98 jitter_buffer_ms, | |
99 min_playout_delay_ms, | |
100 render_delay_ms); | |
101 } | |
102 | |
103 // Size of render buffer. | |
104 if (render_buffer_callback_) { | |
105 int buffer_size_ms = _receiver.RenderBufferSizeMs(); | |
106 render_buffer_callback_->RenderBufferSizeMs(buffer_size_ms); | |
107 } | |
108 } | |
109 | |
110 // Key frame requests | |
111 if (_keyRequestTimer.TimeUntilProcess() == 0) { | |
112 _keyRequestTimer.Processed(); | |
113 bool request_key_frame = false; | |
114 { | |
115 CriticalSectionScoped cs(process_crit_sect_.get()); | |
116 request_key_frame = _scheduleKeyRequest && _frameTypeCallback != NULL; | |
117 } | |
118 if (request_key_frame) { | |
119 const int32_t ret = RequestKeyFrame(); | |
120 if (ret != VCM_OK && returnValue == VCM_OK) { | |
121 returnValue = ret; | |
122 } | |
123 } | |
124 } | |
125 | |
126 // Packet retransmission requests | |
127 // TODO(holmer): Add API for changing Process interval and make sure it's | |
128 // disabled when NACK is off. | |
129 if (_retransmissionTimer.TimeUntilProcess() == 0) { | |
130 _retransmissionTimer.Processed(); | |
131 bool callback_registered = false; | |
132 uint16_t length; | |
133 { | |
134 CriticalSectionScoped cs(process_crit_sect_.get()); | |
135 length = max_nack_list_size_; | |
136 callback_registered = _packetRequestCallback != NULL; | |
137 } | |
138 if (callback_registered && length > 0) { | |
139 // Collect sequence numbers from the default receiver. | |
140 bool request_key_frame = false; | |
141 std::vector<uint16_t> nackList = _receiver.NackList(&request_key_frame); | |
142 int32_t ret = VCM_OK; | |
143 if (request_key_frame) { | |
144 ret = RequestKeyFrame(); | |
145 if (ret != VCM_OK && returnValue == VCM_OK) { | |
146 returnValue = ret; | |
147 } | |
148 } | |
149 if (ret == VCM_OK && !nackList.empty()) { | |
150 CriticalSectionScoped cs(process_crit_sect_.get()); | |
151 if (_packetRequestCallback != NULL) { | |
152 _packetRequestCallback->ResendPackets(&nackList[0], nackList.size()); | |
153 } | |
154 } | |
155 } | |
156 } | |
157 | |
158 return returnValue; | |
159 } | |
160 | |
161 int64_t VideoReceiver::TimeUntilNextProcess() { | |
162 int64_t timeUntilNextProcess = _receiveStatsTimer.TimeUntilProcess(); | |
163 if (_receiver.NackMode() != kNoNack) { | |
164 // We need a Process call more often if we are relying on | |
165 // retransmissions | |
166 timeUntilNextProcess = | |
167 VCM_MIN(timeUntilNextProcess, _retransmissionTimer.TimeUntilProcess()); | |
168 } | |
169 timeUntilNextProcess = | |
170 VCM_MIN(timeUntilNextProcess, _keyRequestTimer.TimeUntilProcess()); | |
171 | |
172 return timeUntilNextProcess; | |
173 } | |
174 | |
175 int32_t VideoReceiver::SetReceiveChannelParameters(int64_t rtt) { | |
176 CriticalSectionScoped receiveCs(_receiveCritSect); | |
177 _receiver.UpdateRtt(rtt); | |
178 return 0; | |
179 } | |
180 | |
181 // Enable or disable a video protection method. | |
182 // Note: This API should be deprecated, as it does not offer a distinction | |
183 // between the protection method and decoding with or without errors. If such a | |
184 // behavior is desired, use the following API: SetReceiverRobustnessMode. | |
185 int32_t VideoReceiver::SetVideoProtection(VCMVideoProtection videoProtection, | |
186 bool enable) { | |
187 // By default, do not decode with errors. | |
188 _receiver.SetDecodeErrorMode(kNoErrors); | |
189 switch (videoProtection) { | |
190 case kProtectionNack: { | |
191 RTC_DCHECK(enable); | |
192 _receiver.SetNackMode(kNack, -1, -1); | |
193 break; | |
194 } | |
195 | |
196 case kProtectionNackFEC: { | |
197 CriticalSectionScoped cs(_receiveCritSect); | |
198 RTC_DCHECK(enable); | |
199 _receiver.SetNackMode(kNack, media_optimization::kLowRttNackMs, -1); | |
200 _receiver.SetDecodeErrorMode(kNoErrors); | |
201 break; | |
202 } | |
203 case kProtectionFEC: | |
204 case kProtectionNone: | |
205 // No receiver-side protection. | |
206 RTC_DCHECK(enable); | |
207 _receiver.SetNackMode(kNoNack, -1, -1); | |
208 _receiver.SetDecodeErrorMode(kWithErrors); | |
209 break; | |
210 } | |
211 return VCM_OK; | |
212 } | |
213 | |
214 // Register a receive callback. Will be called whenever there is a new frame | |
215 // ready for rendering. | |
216 int32_t VideoReceiver::RegisterReceiveCallback( | |
217 VCMReceiveCallback* receiveCallback) { | |
218 CriticalSectionScoped cs(_receiveCritSect); | |
219 _decodedFrameCallback.SetUserReceiveCallback(receiveCallback); | |
220 return VCM_OK; | |
221 } | |
222 | |
223 int32_t VideoReceiver::RegisterReceiveStatisticsCallback( | |
224 VCMReceiveStatisticsCallback* receiveStats) { | |
225 CriticalSectionScoped cs(process_crit_sect_.get()); | |
226 _receiver.RegisterStatsCallback(receiveStats); | |
227 _receiveStatsCallback = receiveStats; | |
228 return VCM_OK; | |
229 } | |
230 | |
231 int32_t VideoReceiver::RegisterDecoderTimingCallback( | |
232 VCMDecoderTimingCallback* decoderTiming) { | |
233 CriticalSectionScoped cs(process_crit_sect_.get()); | |
234 _decoderTimingCallback = decoderTiming; | |
235 return VCM_OK; | |
236 } | |
237 | |
238 // Register an externally defined decoder/render object. | |
239 // Can be a decoder only or a decoder coupled with a renderer. | |
240 int32_t VideoReceiver::RegisterExternalDecoder(VideoDecoder* externalDecoder, | |
241 uint8_t payloadType, | |
242 bool internalRenderTiming) { | |
243 CriticalSectionScoped cs(_receiveCritSect); | |
244 if (externalDecoder == NULL) { | |
245 // Make sure the VCM updates the decoder next time it decodes. | |
246 _decoder = NULL; | |
247 return _codecDataBase.DeregisterExternalDecoder(payloadType) ? 0 : -1; | |
248 } | |
249 return _codecDataBase.RegisterExternalDecoder( | |
250 externalDecoder, payloadType, internalRenderTiming) | |
251 ? 0 | |
252 : -1; | |
253 } | |
254 | |
255 // Register a frame type request callback. | |
256 int32_t VideoReceiver::RegisterFrameTypeCallback( | |
257 VCMFrameTypeCallback* frameTypeCallback) { | |
258 CriticalSectionScoped cs(process_crit_sect_.get()); | |
259 _frameTypeCallback = frameTypeCallback; | |
260 return VCM_OK; | |
261 } | |
262 | |
263 int32_t VideoReceiver::RegisterPacketRequestCallback( | |
264 VCMPacketRequestCallback* callback) { | |
265 CriticalSectionScoped cs(process_crit_sect_.get()); | |
266 _packetRequestCallback = callback; | |
267 return VCM_OK; | |
268 } | |
269 | |
270 int VideoReceiver::RegisterRenderBufferSizeCallback( | |
271 VCMRenderBufferSizeCallback* callback) { | |
272 CriticalSectionScoped cs(process_crit_sect_.get()); | |
273 render_buffer_callback_ = callback; | |
274 return VCM_OK; | |
275 } | |
276 | |
277 void VideoReceiver::TriggerDecoderShutdown() { | |
278 _receiver.TriggerDecoderShutdown(); | |
279 } | |
280 | |
281 // Decode next frame, blocking. | |
282 // Should be called as often as possible to get the most out of the decoder. | |
283 int32_t VideoReceiver::Decode(uint16_t maxWaitTimeMs) { | |
284 int64_t nextRenderTimeMs; | |
285 bool supports_render_scheduling; | |
286 { | |
287 CriticalSectionScoped cs(_receiveCritSect); | |
288 supports_render_scheduling = _codecDataBase.SupportsRenderScheduling(); | |
289 } | |
290 | |
291 VCMEncodedFrame* frame = _receiver.FrameForDecoding( | |
292 maxWaitTimeMs, nextRenderTimeMs, supports_render_scheduling); | |
293 | |
294 if (frame == NULL) { | |
295 return VCM_FRAME_NOT_READY; | |
296 } else { | |
297 CriticalSectionScoped cs(_receiveCritSect); | |
298 | |
299 // If this frame was too late, we should adjust the delay accordingly | |
300 _timing.UpdateCurrentDelay(frame->RenderTimeMs(), | |
301 clock_->TimeInMilliseconds()); | |
302 | |
303 if (pre_decode_image_callback_) { | |
304 EncodedImage encoded_image(frame->EncodedImage()); | |
305 int qp = -1; | |
306 if (qp_parser_.GetQp(*frame, &qp)) { | |
307 encoded_image.qp_ = qp; | |
308 } | |
309 pre_decode_image_callback_->Encoded( | |
310 encoded_image, frame->CodecSpecific(), NULL); | |
311 } | |
312 | |
313 #ifdef DEBUG_DECODER_BIT_STREAM | |
314 if (_bitStreamBeforeDecoder != NULL) { | |
315 // Write bit stream to file for debugging purposes | |
316 if (fwrite( | |
317 frame->Buffer(), 1, frame->Length(), _bitStreamBeforeDecoder) != | |
318 frame->Length()) { | |
319 return -1; | |
320 } | |
321 } | |
322 #endif | |
323 const int32_t ret = Decode(*frame); | |
324 _receiver.ReleaseFrame(frame); | |
325 frame = NULL; | |
326 if (ret != VCM_OK) { | |
327 return ret; | |
328 } | |
329 } | |
330 return VCM_OK; | |
331 } | |
332 | |
333 int32_t VideoReceiver::RequestSliceLossIndication( | |
334 const uint64_t pictureID) const { | |
335 TRACE_EVENT1("webrtc", "RequestSLI", "picture_id", pictureID); | |
336 CriticalSectionScoped cs(process_crit_sect_.get()); | |
337 if (_frameTypeCallback != NULL) { | |
338 const int32_t ret = | |
339 _frameTypeCallback->SliceLossIndicationRequest(pictureID); | |
340 if (ret < 0) { | |
341 return ret; | |
342 } | |
343 } else { | |
344 return VCM_MISSING_CALLBACK; | |
345 } | |
346 return VCM_OK; | |
347 } | |
348 | |
349 int32_t VideoReceiver::RequestKeyFrame() { | |
350 TRACE_EVENT0("webrtc", "RequestKeyFrame"); | |
351 CriticalSectionScoped process_cs(process_crit_sect_.get()); | |
352 if (_frameTypeCallback != NULL) { | |
353 const int32_t ret = _frameTypeCallback->RequestKeyFrame(); | |
354 if (ret < 0) { | |
355 return ret; | |
356 } | |
357 _scheduleKeyRequest = false; | |
358 } else { | |
359 return VCM_MISSING_CALLBACK; | |
360 } | |
361 return VCM_OK; | |
362 } | |
363 | |
364 // Must be called from inside the receive side critical section. | |
365 int32_t VideoReceiver::Decode(const VCMEncodedFrame& frame) { | |
366 TRACE_EVENT_ASYNC_STEP1("webrtc", | |
367 "Video", | |
368 frame.TimeStamp(), | |
369 "Decode", | |
370 "type", | |
371 frame.FrameType()); | |
372 // Change decoder if payload type has changed | |
373 const bool renderTimingBefore = _codecDataBase.SupportsRenderScheduling(); | |
374 _decoder = | |
375 _codecDataBase.GetDecoder(frame.PayloadType(), &_decodedFrameCallback); | |
376 if (renderTimingBefore != _codecDataBase.SupportsRenderScheduling()) { | |
377 // Make sure we reset the decode time estimate since it will | |
378 // be zero for codecs without render timing. | |
379 _timing.ResetDecodeTime(); | |
380 } | |
381 if (_decoder == NULL) { | |
382 return VCM_NO_CODEC_REGISTERED; | |
383 } | |
384 // Decode a frame | |
385 int32_t ret = _decoder->Decode(frame, clock_->TimeInMilliseconds()); | |
386 | |
387 // Check for failed decoding, run frame type request callback if needed. | |
388 bool request_key_frame = false; | |
389 if (ret < 0) { | |
390 if (ret == VCM_ERROR_REQUEST_SLI) { | |
391 return RequestSliceLossIndication( | |
392 _decodedFrameCallback.LastReceivedPictureID() + 1); | |
393 } else { | |
394 request_key_frame = true; | |
395 } | |
396 } else if (ret == VCM_REQUEST_SLI) { | |
397 ret = RequestSliceLossIndication( | |
398 _decodedFrameCallback.LastReceivedPictureID() + 1); | |
399 } | |
400 if (!frame.Complete() || frame.MissingFrame()) { | |
401 request_key_frame = true; | |
402 ret = VCM_OK; | |
403 } | |
404 if (request_key_frame) { | |
405 CriticalSectionScoped cs(process_crit_sect_.get()); | |
406 _scheduleKeyRequest = true; | |
407 } | |
408 TRACE_EVENT_ASYNC_END0("webrtc", "Video", frame.TimeStamp()); | |
409 return ret; | |
410 } | |
411 | |
412 // Reset the decoder state | |
413 int32_t VideoReceiver::ResetDecoder() { | |
414 bool reset_key_request = false; | |
415 { | |
416 CriticalSectionScoped cs(_receiveCritSect); | |
417 if (_decoder != NULL) { | |
418 _receiver.Reset(); | |
419 _timing.Reset(); | |
420 reset_key_request = true; | |
421 _decoder->Reset(); | |
422 } | |
423 } | |
424 if (reset_key_request) { | |
425 CriticalSectionScoped cs(process_crit_sect_.get()); | |
426 _scheduleKeyRequest = false; | |
427 } | |
428 return VCM_OK; | |
429 } | |
430 | |
431 // Register possible receive codecs, can be called multiple times | |
432 int32_t VideoReceiver::RegisterReceiveCodec(const VideoCodec* receiveCodec, | |
433 int32_t numberOfCores, | |
434 bool requireKeyFrame) { | |
435 CriticalSectionScoped cs(_receiveCritSect); | |
436 if (receiveCodec == NULL) { | |
437 return VCM_PARAMETER_ERROR; | |
438 } | |
439 if (!_codecDataBase.RegisterReceiveCodec( | |
440 receiveCodec, numberOfCores, requireKeyFrame)) { | |
441 return -1; | |
442 } | |
443 return 0; | |
444 } | |
445 | |
446 // Get current received codec | |
447 int32_t VideoReceiver::ReceiveCodec(VideoCodec* currentReceiveCodec) const { | |
448 CriticalSectionScoped cs(_receiveCritSect); | |
449 if (currentReceiveCodec == NULL) { | |
450 return VCM_PARAMETER_ERROR; | |
451 } | |
452 return _codecDataBase.ReceiveCodec(currentReceiveCodec) ? 0 : -1; | |
453 } | |
454 | |
455 // Get current received codec | |
456 VideoCodecType VideoReceiver::ReceiveCodec() const { | |
457 CriticalSectionScoped cs(_receiveCritSect); | |
458 return _codecDataBase.ReceiveCodec(); | |
459 } | |
460 | |
461 // Incoming packet from network parsed and ready for decode, non blocking. | |
462 int32_t VideoReceiver::IncomingPacket(const uint8_t* incomingPayload, | |
463 size_t payloadLength, | |
464 const WebRtcRTPHeader& rtpInfo) { | |
465 if (rtpInfo.frameType == kVideoFrameKey) { | |
466 TRACE_EVENT1("webrtc", | |
467 "VCM::PacketKeyFrame", | |
468 "seqnum", | |
469 rtpInfo.header.sequenceNumber); | |
470 } | |
471 if (incomingPayload == NULL) { | |
472 // The jitter buffer doesn't handle non-zero payload lengths for packets | |
473 // without payload. | |
474 // TODO(holmer): We should fix this in the jitter buffer. | |
475 payloadLength = 0; | |
476 } | |
477 const VCMPacket packet(incomingPayload, payloadLength, rtpInfo); | |
478 int32_t ret = _receiver.InsertPacket(packet, rtpInfo.type.Video.width, | |
479 rtpInfo.type.Video.height); | |
480 // TODO(holmer): Investigate if this somehow should use the key frame | |
481 // request scheduling to throttle the requests. | |
482 if (ret == VCM_FLUSH_INDICATOR) { | |
483 RequestKeyFrame(); | |
484 ResetDecoder(); | |
485 } else if (ret < 0) { | |
486 return ret; | |
487 } | |
488 return VCM_OK; | |
489 } | |
490 | |
491 // Minimum playout delay (used for lip-sync). This is the minimum delay required | |
492 // to sync with audio. Not included in VideoCodingModule::Delay() | |
493 // Defaults to 0 ms. | |
494 int32_t VideoReceiver::SetMinimumPlayoutDelay(uint32_t minPlayoutDelayMs) { | |
495 _timing.set_min_playout_delay(minPlayoutDelayMs); | |
496 return VCM_OK; | |
497 } | |
498 | |
499 // The estimated delay caused by rendering, defaults to | |
500 // kDefaultRenderDelayMs = 10 ms | |
501 int32_t VideoReceiver::SetRenderDelay(uint32_t timeMS) { | |
502 _timing.set_render_delay(timeMS); | |
503 return VCM_OK; | |
504 } | |
505 | |
506 // Current video delay | |
507 int32_t VideoReceiver::Delay() const { return _timing.TargetVideoDelay(); } | |
508 | |
509 uint32_t VideoReceiver::DiscardedPackets() const { | |
510 return _receiver.DiscardedPackets(); | |
511 } | |
512 | |
513 int VideoReceiver::SetReceiverRobustnessMode( | |
514 ReceiverRobustness robustnessMode, | |
515 VCMDecodeErrorMode decode_error_mode) { | |
516 CriticalSectionScoped cs(_receiveCritSect); | |
517 switch (robustnessMode) { | |
518 case VideoCodingModule::kNone: | |
519 _receiver.SetNackMode(kNoNack, -1, -1); | |
520 break; | |
521 case VideoCodingModule::kHardNack: | |
522 // Always wait for retransmissions (except when decoding with errors). | |
523 _receiver.SetNackMode(kNack, -1, -1); | |
524 break; | |
525 case VideoCodingModule::kSoftNack: | |
526 #if 1 | |
527 assert(false); // TODO(hlundin): Not completed. | |
528 return VCM_NOT_IMPLEMENTED; | |
529 #else | |
530 // Enable hybrid NACK/FEC. Always wait for retransmissions and don't add | |
531 // extra delay when RTT is above kLowRttNackMs. | |
532 _receiver.SetNackMode(kNack, media_optimization::kLowRttNackMs, -1); | |
533 break; | |
534 #endif | |
535 case VideoCodingModule::kReferenceSelection: | |
536 #if 1 | |
537 assert(false); // TODO(hlundin): Not completed. | |
538 return VCM_NOT_IMPLEMENTED; | |
539 #else | |
540 if (decode_error_mode == kNoErrors) { | |
541 return VCM_PARAMETER_ERROR; | |
542 } | |
543 _receiver.SetNackMode(kNoNack, -1, -1); | |
544 break; | |
545 #endif | |
546 } | |
547 _receiver.SetDecodeErrorMode(decode_error_mode); | |
548 return VCM_OK; | |
549 } | |
550 | |
551 void VideoReceiver::SetDecodeErrorMode(VCMDecodeErrorMode decode_error_mode) { | |
552 CriticalSectionScoped cs(_receiveCritSect); | |
553 _receiver.SetDecodeErrorMode(decode_error_mode); | |
554 } | |
555 | |
556 void VideoReceiver::SetNackSettings(size_t max_nack_list_size, | |
557 int max_packet_age_to_nack, | |
558 int max_incomplete_time_ms) { | |
559 if (max_nack_list_size != 0) { | |
560 CriticalSectionScoped process_cs(process_crit_sect_.get()); | |
561 max_nack_list_size_ = max_nack_list_size; | |
562 } | |
563 _receiver.SetNackSettings( | |
564 max_nack_list_size, max_packet_age_to_nack, max_incomplete_time_ms); | |
565 } | |
566 | |
567 int VideoReceiver::SetMinReceiverDelay(int desired_delay_ms) { | |
568 return _receiver.SetMinReceiverDelay(desired_delay_ms); | |
569 } | |
570 | |
571 void VideoReceiver::RegisterPreDecodeImageCallback( | |
572 EncodedImageCallback* observer) { | |
573 CriticalSectionScoped cs(_receiveCritSect); | |
574 pre_decode_image_callback_ = observer; | |
575 } | |
576 | |
577 } // namespace vcm | |
578 } // namespace webrtc | |
OLD | NEW |