OLD | NEW |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 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/cast/sender/vp8_encoder.h" | 5 #include "media/cast/sender/vp8_encoder.h" |
6 | 6 |
7 #include "base/logging.h" | 7 #include "base/logging.h" |
8 #include "media/base/video_frame.h" | 8 #include "media/base/video_frame.h" |
9 #include "media/cast/cast_defines.h" | 9 #include "media/cast/cast_defines.h" |
10 #include "media/cast/net/cast_transport_config.h" | 10 #include "media/cast/net/cast_transport_config.h" |
(...skipping 10 matching lines...) Expand all Loading... |
21 // pause in the video stream. | 21 // pause in the video stream. |
22 const int kRestartFramePeriods = 3; | 22 const int kRestartFramePeriods = 3; |
23 | 23 |
24 } // namespace | 24 } // namespace |
25 | 25 |
26 Vp8Encoder::Vp8Encoder(const VideoSenderConfig& video_config) | 26 Vp8Encoder::Vp8Encoder(const VideoSenderConfig& video_config) |
27 : cast_config_(video_config), | 27 : cast_config_(video_config), |
28 use_multiple_video_buffers_( | 28 use_multiple_video_buffers_( |
29 cast_config_.max_number_of_video_buffers_used == | 29 cast_config_.max_number_of_video_buffers_used == |
30 kNumberOfVp8VideoBuffers), | 30 kNumberOfVp8VideoBuffers), |
31 raw_image_(nullptr), | |
32 key_frame_requested_(true), | 31 key_frame_requested_(true), |
| 32 bitrate_kbit_(cast_config_.start_bitrate / 1000), |
33 last_encoded_frame_id_(kStartFrameId), | 33 last_encoded_frame_id_(kStartFrameId), |
34 last_acked_frame_id_(kStartFrameId), | 34 last_acked_frame_id_(kStartFrameId), |
35 undroppable_frames_(0) { | 35 undroppable_frames_(0) { |
36 config_.g_timebase.den = 0; // Not initialized. | 36 config_.g_timebase.den = 0; // Not initialized. |
37 | 37 |
| 38 for (int i = 0; i < kNumberOfVp8VideoBuffers; ++i) { |
| 39 buffer_state_[i].frame_id = last_encoded_frame_id_; |
| 40 buffer_state_[i].state = kBufferStartState; |
| 41 } |
| 42 |
38 // VP8 have 3 buffers available for prediction, with | 43 // VP8 have 3 buffers available for prediction, with |
39 // max_number_of_video_buffers_used set to 1 we maximize the coding efficiency | 44 // max_number_of_video_buffers_used set to 1 we maximize the coding efficiency |
40 // however in this mode we can not skip frames in the receiver to catch up | 45 // however in this mode we can not skip frames in the receiver to catch up |
41 // after a temporary network outage; with max_number_of_video_buffers_used | 46 // after a temporary network outage; with max_number_of_video_buffers_used |
42 // set to 3 we allow 2 frames to be skipped by the receiver without error | 47 // set to 3 we allow 2 frames to be skipped by the receiver without error |
43 // propagation. | 48 // propagation. |
44 DCHECK(cast_config_.max_number_of_video_buffers_used == 1 || | 49 DCHECK(cast_config_.max_number_of_video_buffers_used == 1 || |
45 cast_config_.max_number_of_video_buffers_used == | 50 cast_config_.max_number_of_video_buffers_used == |
46 kNumberOfVp8VideoBuffers) | 51 kNumberOfVp8VideoBuffers) |
47 << "Invalid argument"; | 52 << "Invalid argument"; |
48 | 53 |
49 thread_checker_.DetachFromThread(); | 54 thread_checker_.DetachFromThread(); |
50 } | 55 } |
51 | 56 |
52 Vp8Encoder::~Vp8Encoder() { | 57 Vp8Encoder::~Vp8Encoder() { |
53 DCHECK(thread_checker_.CalledOnValidThread()); | 58 DCHECK(thread_checker_.CalledOnValidThread()); |
54 if (is_initialized()) | 59 if (is_initialized()) |
55 vpx_codec_destroy(&encoder_); | 60 vpx_codec_destroy(&encoder_); |
56 vpx_img_free(raw_image_); | |
57 } | 61 } |
58 | 62 |
59 void Vp8Encoder::Initialize() { | 63 void Vp8Encoder::Initialize() { |
60 DCHECK(thread_checker_.CalledOnValidThread()); | 64 DCHECK(thread_checker_.CalledOnValidThread()); |
61 DCHECK(!is_initialized()); | 65 DCHECK(!is_initialized()); |
| 66 // The encoder will be created/configured when the first frame encode is |
| 67 // requested. |
| 68 } |
62 | 69 |
63 // Creating a wrapper to the image - setting image data to NULL. Actual | 70 void Vp8Encoder::ConfigureForNewFrameSize(const gfx::Size& frame_size) { |
64 // pointer will be set during encode. Setting align to 1, as it is | 71 if (is_initialized()) { |
65 // meaningless (actual memory is not allocated). | 72 // Workaround for VP8 bug: If the new size is strictly less-than-or-equal to |
66 raw_image_ = vpx_img_wrap( | 73 // the old size, in terms of area, the existing encoder instance can |
67 NULL, VPX_IMG_FMT_I420, cast_config_.width, cast_config_.height, 1, NULL); | 74 // continue. Otherwise, completely tear-down and re-create a new encoder to |
| 75 // avoid a shutdown crash. |
| 76 if (frame_size.GetArea() <= gfx::Size(config_.g_w, config_.g_h).GetArea() && |
| 77 !use_multiple_video_buffers_) { |
| 78 DVLOG(1) << "Continuing to use existing encoder at smaller frame size: " |
| 79 << gfx::Size(config_.g_w, config_.g_h).ToString() << " --> " |
| 80 << frame_size.ToString(); |
| 81 config_.g_w = frame_size.width(); |
| 82 config_.g_h = frame_size.height(); |
| 83 CHECK_EQ(vpx_codec_enc_config_set(&encoder_, &config_), VPX_CODEC_OK) |
| 84 << "Failed to update frame size in encoder config."; |
| 85 return; |
| 86 } |
68 | 87 |
| 88 DVLOG(1) << "Destroying/Re-Creating encoder for larger frame size: " |
| 89 << gfx::Size(config_.g_w, config_.g_h).ToString() << " --> " |
| 90 << frame_size.ToString(); |
| 91 vpx_codec_destroy(&encoder_); |
| 92 } else { |
| 93 DVLOG(1) << "Creating encoder for the first frame; size: " |
| 94 << frame_size.ToString(); |
| 95 } |
| 96 |
| 97 // Reset multi-buffer mode state. |
| 98 last_acked_frame_id_ = last_encoded_frame_id_; |
| 99 undroppable_frames_ = 0; |
69 for (int i = 0; i < kNumberOfVp8VideoBuffers; ++i) { | 100 for (int i = 0; i < kNumberOfVp8VideoBuffers; ++i) { |
70 buffer_state_[i].frame_id = kStartFrameId; | 101 buffer_state_[i].frame_id = last_encoded_frame_id_; |
71 buffer_state_[i].state = kBufferStartState; | 102 buffer_state_[i].state = kBufferStartState; |
72 } | 103 } |
73 | 104 |
74 // Populate encoder configuration with default values. | 105 // Populate encoder configuration with default values. |
75 if (vpx_codec_enc_config_default(vpx_codec_vp8_cx(), &config_, 0)) { | 106 CHECK_EQ(vpx_codec_enc_config_default(vpx_codec_vp8_cx(), &config_, 0), |
76 NOTREACHED() << "Invalid return value"; | 107 VPX_CODEC_OK); |
77 config_.g_timebase.den = 0; // Do not call vpx_codec_destroy() in dtor. | |
78 return; | |
79 } | |
80 | 108 |
81 config_.g_threads = cast_config_.number_of_encode_threads; | 109 config_.g_threads = cast_config_.number_of_encode_threads; |
82 config_.g_w = cast_config_.width; | 110 config_.g_w = frame_size.width(); |
83 config_.g_h = cast_config_.height; | 111 config_.g_h = frame_size.height(); |
84 // Set the timebase to match that of base::TimeDelta. | 112 // Set the timebase to match that of base::TimeDelta. |
85 config_.g_timebase.num = 1; | 113 config_.g_timebase.num = 1; |
86 config_.g_timebase.den = base::Time::kMicrosecondsPerSecond; | 114 config_.g_timebase.den = base::Time::kMicrosecondsPerSecond; |
87 if (use_multiple_video_buffers_) { | 115 if (use_multiple_video_buffers_) { |
88 // We must enable error resilience when we use multiple buffers, due to | 116 // We must enable error resilience when we use multiple buffers, due to |
89 // codec requirements. | 117 // codec requirements. |
90 config_.g_error_resilient = 1; | 118 config_.g_error_resilient = 1; |
91 } | 119 } |
| 120 // |g_pass| and |g_lag_in_frames| must be "one pass" and zero, respectively, |
| 121 // in order for VP8 to support changing frame sizes during encoding: |
92 config_.g_pass = VPX_RC_ONE_PASS; | 122 config_.g_pass = VPX_RC_ONE_PASS; |
93 config_.g_lag_in_frames = 0; // Immediate data output for each frame. | 123 config_.g_lag_in_frames = 0; // Immediate data output for each frame. |
94 | 124 |
95 // Rate control settings. | 125 // Rate control settings. |
96 config_.rc_dropframe_thresh = 0; // The encoder may not drop any frames. | 126 config_.rc_dropframe_thresh = 0; // The encoder may not drop any frames. |
97 config_.rc_resize_allowed = 0; // TODO(miu): Why not? Investigate this. | 127 config_.rc_resize_allowed = 0; // TODO(miu): Why not? Investigate this. |
98 config_.rc_end_usage = VPX_CBR; | 128 config_.rc_end_usage = VPX_CBR; |
99 config_.rc_target_bitrate = cast_config_.start_bitrate / 1000; // In kbit/s. | 129 config_.rc_target_bitrate = bitrate_kbit_; |
100 config_.rc_min_quantizer = cast_config_.min_qp; | 130 config_.rc_min_quantizer = cast_config_.min_qp; |
101 config_.rc_max_quantizer = cast_config_.max_qp; | 131 config_.rc_max_quantizer = cast_config_.max_qp; |
102 // TODO(miu): Revisit these now that the encoder is being successfully | 132 // TODO(miu): Revisit these now that the encoder is being successfully |
103 // micro-managed. | 133 // micro-managed. |
104 config_.rc_undershoot_pct = 100; | 134 config_.rc_undershoot_pct = 100; |
105 config_.rc_overshoot_pct = 15; | 135 config_.rc_overshoot_pct = 15; |
106 // TODO(miu): Document why these rc_buf_*_sz values were chosen and/or | 136 // TODO(miu): Document why these rc_buf_*_sz values were chosen and/or |
107 // research for better values. Should they be computed from the target | 137 // research for better values. Should they be computed from the target |
108 // playout delay? | 138 // playout delay? |
109 config_.rc_buf_initial_sz = 500; | 139 config_.rc_buf_initial_sz = 500; |
110 config_.rc_buf_optimal_sz = 600; | 140 config_.rc_buf_optimal_sz = 600; |
111 config_.rc_buf_sz = 1000; | 141 config_.rc_buf_sz = 1000; |
112 | 142 |
113 config_.kf_mode = VPX_KF_DISABLED; | 143 config_.kf_mode = VPX_KF_DISABLED; |
114 | 144 |
115 vpx_codec_flags_t flags = 0; | 145 vpx_codec_flags_t flags = 0; |
116 if (vpx_codec_enc_init(&encoder_, vpx_codec_vp8_cx(), &config_, flags)) { | 146 CHECK_EQ(vpx_codec_enc_init(&encoder_, vpx_codec_vp8_cx(), &config_, flags), |
117 NOTREACHED() << "vpx_codec_enc_init() failed."; | 147 VPX_CODEC_OK); |
118 config_.g_timebase.den = 0; // Do not call vpx_codec_destroy() in dtor. | |
119 return; | |
120 } | |
121 | 148 |
122 // Raise the threshold for considering macroblocks as static. The default is | 149 // Raise the threshold for considering macroblocks as static. The default is |
123 // zero, so this setting makes the encoder less sensitive to motion. This | 150 // zero, so this setting makes the encoder less sensitive to motion. This |
124 // lowers the probability of needing to utilize more CPU to search for motion | 151 // lowers the probability of needing to utilize more CPU to search for motion |
125 // vectors. | 152 // vectors. |
126 vpx_codec_control(&encoder_, VP8E_SET_STATIC_THRESHOLD, 1); | 153 CHECK_EQ(vpx_codec_control(&encoder_, VP8E_SET_STATIC_THRESHOLD, 1), |
| 154 VPX_CODEC_OK); |
127 | 155 |
128 // Improve quality by enabling sets of codec features that utilize more CPU. | 156 // Improve quality by enabling sets of codec features that utilize more CPU. |
129 // The default is zero, with increasingly more CPU to be used as the value is | 157 // The default is zero, with increasingly more CPU to be used as the value is |
130 // more negative. | 158 // more negative. |
131 // TODO(miu): Document why this value was chosen and expected behaviors. | 159 // TODO(miu): Document why this value was chosen and expected behaviors. |
132 // Should this be dynamic w.r.t. hardware performance? | 160 // Should this be dynamic w.r.t. hardware performance? |
133 vpx_codec_control(&encoder_, VP8E_SET_CPUUSED, -6); | 161 CHECK_EQ(vpx_codec_control(&encoder_, VP8E_SET_CPUUSED, -6), VPX_CODEC_OK); |
134 } | 162 } |
135 | 163 |
136 void Vp8Encoder::Encode(const scoped_refptr<media::VideoFrame>& video_frame, | 164 void Vp8Encoder::Encode(const scoped_refptr<media::VideoFrame>& video_frame, |
137 const base::TimeTicks& reference_time, | 165 const base::TimeTicks& reference_time, |
138 EncodedFrame* encoded_frame) { | 166 EncodedFrame* encoded_frame) { |
139 DCHECK(thread_checker_.CalledOnValidThread()); | 167 DCHECK(thread_checker_.CalledOnValidThread()); |
140 DCHECK(encoded_frame); | 168 DCHECK(encoded_frame); |
141 | 169 |
142 CHECK(is_initialized()); // No illegal reference to |config_| or |encoder_|. | 170 // Initialize on-demand. Later, if the video frame size has changed, update |
143 | 171 // the encoder configuration. |
144 // Image in vpx_image_t format. | 172 const gfx::Size frame_size = video_frame->visible_rect().size(); |
145 // Input image is const. VP8's raw image is not defined as const. | 173 if (!is_initialized() || gfx::Size(config_.g_w, config_.g_h) != frame_size) |
146 raw_image_->planes[VPX_PLANE_Y] = | 174 ConfigureForNewFrameSize(frame_size); |
147 const_cast<uint8*>(video_frame->data(VideoFrame::kYPlane)); | |
148 raw_image_->planes[VPX_PLANE_U] = | |
149 const_cast<uint8*>(video_frame->data(VideoFrame::kUPlane)); | |
150 raw_image_->planes[VPX_PLANE_V] = | |
151 const_cast<uint8*>(video_frame->data(VideoFrame::kVPlane)); | |
152 | |
153 raw_image_->stride[VPX_PLANE_Y] = video_frame->stride(VideoFrame::kYPlane); | |
154 raw_image_->stride[VPX_PLANE_U] = video_frame->stride(VideoFrame::kUPlane); | |
155 raw_image_->stride[VPX_PLANE_V] = video_frame->stride(VideoFrame::kVPlane); | |
156 | 175 |
157 uint32 latest_frame_id_to_reference; | 176 uint32 latest_frame_id_to_reference; |
158 Vp8Buffers buffer_to_update; | 177 Vp8Buffers buffer_to_update; |
159 vpx_codec_flags_t flags = 0; | 178 vpx_codec_flags_t flags = 0; |
160 if (key_frame_requested_) { | 179 if (key_frame_requested_) { |
161 flags = VPX_EFLAG_FORCE_KF; | 180 flags = VPX_EFLAG_FORCE_KF; |
162 // Self reference. | 181 // Self reference. |
163 latest_frame_id_to_reference = last_encoded_frame_id_ + 1; | 182 latest_frame_id_to_reference = last_encoded_frame_id_ + 1; |
164 // We can pick any buffer as buffer_to_update since we update | 183 // We can pick any buffer as buffer_to_update since we update |
165 // them all. | 184 // them all. |
166 buffer_to_update = kLastBuffer; | 185 buffer_to_update = kLastBuffer; |
167 } else { | 186 } else { |
168 // Reference all acked frames (buffers). | 187 // Reference all acked frames (buffers). |
169 latest_frame_id_to_reference = GetCodecReferenceFlags(&flags); | 188 latest_frame_id_to_reference = GetCodecReferenceFlags(&flags); |
170 buffer_to_update = GetNextBufferToUpdate(); | 189 buffer_to_update = GetNextBufferToUpdate(); |
171 GetCodecUpdateFlags(buffer_to_update, &flags); | 190 GetCodecUpdateFlags(buffer_to_update, &flags); |
172 } | 191 } |
173 | 192 |
| 193 // Wrapper for vpx_codec_encode() to access the YUV data in the |video_frame|. |
| 194 // Only the VISIBLE rectangle within |video_frame| is exposed to the codec. |
| 195 vpx_image_t vpx_image; |
| 196 vpx_image_t* const result = vpx_img_wrap( |
| 197 &vpx_image, |
| 198 VPX_IMG_FMT_I420, |
| 199 frame_size.width(), |
| 200 frame_size.height(), |
| 201 1, |
| 202 video_frame->data(VideoFrame::kYPlane)); |
| 203 DCHECK_EQ(result, &vpx_image); |
| 204 vpx_image.planes[VPX_PLANE_Y] = |
| 205 video_frame->visible_data(VideoFrame::kYPlane); |
| 206 vpx_image.planes[VPX_PLANE_U] = |
| 207 video_frame->visible_data(VideoFrame::kUPlane); |
| 208 vpx_image.planes[VPX_PLANE_V] = |
| 209 video_frame->visible_data(VideoFrame::kVPlane); |
| 210 vpx_image.stride[VPX_PLANE_Y] = video_frame->stride(VideoFrame::kYPlane); |
| 211 vpx_image.stride[VPX_PLANE_U] = video_frame->stride(VideoFrame::kUPlane); |
| 212 vpx_image.stride[VPX_PLANE_V] = video_frame->stride(VideoFrame::kVPlane); |
| 213 |
174 // The frame duration given to the VP8 codec affects a number of important | 214 // The frame duration given to the VP8 codec affects a number of important |
175 // behaviors, including: per-frame bandwidth, CPU time spent encoding, | 215 // behaviors, including: per-frame bandwidth, CPU time spent encoding, |
176 // temporal quality trade-offs, and key/golden/alt-ref frame generation | 216 // temporal quality trade-offs, and key/golden/alt-ref frame generation |
177 // intervals. Use the actual amount of time between the current and previous | 217 // intervals. Use the actual amount of time between the current and previous |
178 // frames as a prediction for the next frame's duration, but bound the | 218 // frames as a prediction for the next frame's duration, but bound the |
179 // prediction to account for the fact that the frame rate can be highly | 219 // prediction to account for the fact that the frame rate can be highly |
180 // variable, including long pauses in the video stream. | 220 // variable, including long pauses in the video stream. |
181 const base::TimeDelta minimum_frame_duration = | 221 const base::TimeDelta minimum_frame_duration = |
182 base::TimeDelta::FromSecondsD(1.0 / cast_config_.max_frame_rate); | 222 base::TimeDelta::FromSecondsD(1.0 / cast_config_.max_frame_rate); |
183 const base::TimeDelta maximum_frame_duration = | 223 const base::TimeDelta maximum_frame_duration = |
184 base::TimeDelta::FromSecondsD(static_cast<double>(kRestartFramePeriods) / | 224 base::TimeDelta::FromSecondsD(static_cast<double>(kRestartFramePeriods) / |
185 cast_config_.max_frame_rate); | 225 cast_config_.max_frame_rate); |
186 const base::TimeDelta last_frame_duration = | 226 const base::TimeDelta last_frame_duration = |
187 video_frame->timestamp() - last_frame_timestamp_; | 227 video_frame->timestamp() - last_frame_timestamp_; |
188 const base::TimeDelta predicted_frame_duration = | 228 const base::TimeDelta predicted_frame_duration = |
189 std::max(minimum_frame_duration, | 229 std::max(minimum_frame_duration, |
190 std::min(maximum_frame_duration, last_frame_duration)); | 230 std::min(maximum_frame_duration, last_frame_duration)); |
191 last_frame_timestamp_ = video_frame->timestamp(); | 231 last_frame_timestamp_ = video_frame->timestamp(); |
192 | 232 |
193 // Encode the frame. The presentation time stamp argument here is fixed to | 233 // Encode the frame. The presentation time stamp argument here is fixed to |
194 // zero to force the encoder to base its single-frame bandwidth calculations | 234 // zero to force the encoder to base its single-frame bandwidth calculations |
195 // entirely on |predicted_frame_duration| and the target bitrate setting being | 235 // entirely on |predicted_frame_duration| and the target bitrate setting being |
196 // micro-managed via calls to UpdateRates(). | 236 // micro-managed via calls to UpdateRates(). |
197 CHECK_EQ(vpx_codec_encode(&encoder_, | 237 CHECK_EQ(vpx_codec_encode(&encoder_, |
198 raw_image_, | 238 &vpx_image, |
199 0, | 239 0, |
200 predicted_frame_duration.InMicroseconds(), | 240 predicted_frame_duration.InMicroseconds(), |
201 flags, | 241 flags, |
202 VPX_DL_REALTIME), | 242 VPX_DL_REALTIME), |
203 VPX_CODEC_OK) | 243 VPX_CODEC_OK) |
204 << "BUG: Invalid arguments passed to vpx_codec_encode()."; | 244 << "BUG: Invalid arguments passed to vpx_codec_encode()."; |
205 | 245 |
206 // Pull data from the encoder, populating a new EncodedFrame. | 246 // Pull data from the encoder, populating a new EncodedFrame. |
207 encoded_frame->frame_id = ++last_encoded_frame_id_; | 247 encoded_frame->frame_id = ++last_encoded_frame_id_; |
208 const vpx_codec_cx_pkt_t* pkt = NULL; | 248 const vpx_codec_cx_pkt_t* pkt = NULL; |
(...skipping 186 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
395 void Vp8Encoder::UpdateRates(uint32 new_bitrate) { | 435 void Vp8Encoder::UpdateRates(uint32 new_bitrate) { |
396 DCHECK(thread_checker_.CalledOnValidThread()); | 436 DCHECK(thread_checker_.CalledOnValidThread()); |
397 | 437 |
398 if (!is_initialized()) | 438 if (!is_initialized()) |
399 return; | 439 return; |
400 | 440 |
401 uint32 new_bitrate_kbit = new_bitrate / 1000; | 441 uint32 new_bitrate_kbit = new_bitrate / 1000; |
402 if (config_.rc_target_bitrate == new_bitrate_kbit) | 442 if (config_.rc_target_bitrate == new_bitrate_kbit) |
403 return; | 443 return; |
404 | 444 |
405 config_.rc_target_bitrate = new_bitrate_kbit; | 445 config_.rc_target_bitrate = bitrate_kbit_ = new_bitrate_kbit; |
406 | 446 |
407 // Update encoder context. | 447 // Update encoder context. |
408 if (vpx_codec_enc_config_set(&encoder_, &config_)) { | 448 if (vpx_codec_enc_config_set(&encoder_, &config_)) { |
409 NOTREACHED() << "Invalid return value"; | 449 NOTREACHED() << "Invalid return value"; |
410 } | 450 } |
411 | 451 |
412 VLOG(1) << "VP8 new rc_target_bitrate: " << new_bitrate_kbit << " kbps"; | 452 VLOG(1) << "VP8 new rc_target_bitrate: " << new_bitrate_kbit << " kbps"; |
413 } | 453 } |
414 | 454 |
415 void Vp8Encoder::LatestFrameIdToReference(uint32 frame_id) { | 455 void Vp8Encoder::LatestFrameIdToReference(uint32 frame_id) { |
(...skipping 13 matching lines...) Expand all Loading... |
429 } | 469 } |
430 } | 470 } |
431 | 471 |
432 void Vp8Encoder::GenerateKeyFrame() { | 472 void Vp8Encoder::GenerateKeyFrame() { |
433 DCHECK(thread_checker_.CalledOnValidThread()); | 473 DCHECK(thread_checker_.CalledOnValidThread()); |
434 key_frame_requested_ = true; | 474 key_frame_requested_ = true; |
435 } | 475 } |
436 | 476 |
437 } // namespace cast | 477 } // namespace cast |
438 } // namespace media | 478 } // namespace media |
OLD | NEW |