Chromium Code Reviews| 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/external_video_encoder.h" | 5 #include "media/cast/sender/external_video_encoder.h" |
| 6 | 6 |
| 7 #include <cmath> | 7 #include <cmath> |
| 8 #include <utility> | 8 #include <utility> |
| 9 | 9 |
| 10 #include "base/bind.h" | 10 #include "base/bind.h" |
| 11 #include "base/debug/crash_logging.h" | 11 #include "base/debug/crash_logging.h" |
| 12 #include "base/debug/dump_without_crashing.h" | 12 #include "base/debug/dump_without_crashing.h" |
| 13 #include "base/format_macros.h" | 13 #include "base/format_macros.h" |
| 14 #include "base/logging.h" | 14 #include "base/logging.h" |
| 15 #include "base/macros.h" | 15 #include "base/macros.h" |
| 16 #include "base/memory/scoped_vector.h" | 16 #include "base/memory/scoped_vector.h" |
| 17 #include "base/memory/shared_memory.h" | 17 #include "base/memory/shared_memory.h" |
| 18 #include "base/message_loop/message_loop.h" | 18 #include "base/message_loop/message_loop.h" |
| 19 #include "base/metrics/histogram.h" | 19 #include "base/metrics/histogram.h" |
| 20 #include "base/strings/stringprintf.h" | 20 #include "base/strings/stringprintf.h" |
| 21 #include "build/build_config.h" | 21 #include "build/build_config.h" |
| 22 #include "media/base/bind_to_current_loop.h" | |
| 22 #include "media/base/video_frame.h" | 23 #include "media/base/video_frame.h" |
| 23 #include "media/base/video_types.h" | 24 #include "media/base/video_types.h" |
| 24 #include "media/base/video_util.h" | 25 #include "media/base/video_util.h" |
| 25 #include "media/cast/cast_config.h" | 26 #include "media/cast/cast_config.h" |
| 26 #include "media/cast/cast_defines.h" | 27 #include "media/cast/cast_defines.h" |
| 27 #include "media/cast/common/rtp_time.h" | 28 #include "media/cast/common/rtp_time.h" |
| 28 #include "media/cast/logging/logging_defines.h" | 29 #include "media/cast/logging/logging_defines.h" |
| 29 #include "media/cast/net/cast_transport_config.h" | 30 #include "media/cast/net/cast_transport_config.h" |
| 30 #include "media/cast/sender/vp8_quantizer_parser.h" | 31 #include "media/cast/sender/vp8_quantizer_parser.h" |
| 31 #include "media/filters/h264_parser.h" | 32 #include "media/filters/h264_parser.h" |
| 32 | 33 |
| 33 namespace { | 34 namespace { |
| 34 | 35 |
| 35 enum { MAX_H264_QUANTIZER = 51 }; | 36 enum { MAX_H264_QUANTIZER = 51 }; |
| 37 | |
| 38 // Number of buffers for encoded bit stream. | |
| 36 static const size_t kOutputBufferCount = 3; | 39 static const size_t kOutputBufferCount = 3; |
| 37 | 40 |
| 41 // Number of extra buffers for encoder input. The input buffers are used to copy | |
| 42 // the video stream to match the required coded size. | |
| 43 static const size_t kExtraInputBufferCount = 3; | |
| 44 | |
| 38 } // namespace | 45 } // namespace |
| 39 | 46 |
| 40 namespace media { | 47 namespace media { |
| 41 namespace cast { | 48 namespace cast { |
| 42 | 49 |
| 43 // Container for the associated data of a video frame being processed. | 50 // Container for the associated data of a video frame being processed. |
| 44 struct InProgressFrameEncode { | 51 struct InProgressFrameEncode { |
| 45 // The source content to encode. | 52 // The source content to encode. |
| 46 const scoped_refptr<VideoFrame> video_frame; | 53 const scoped_refptr<VideoFrame> video_frame; |
| 47 | 54 |
| (...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 128 } | 135 } |
| 129 | 136 |
| 130 void SetBitRate(int bit_rate) { | 137 void SetBitRate(int bit_rate) { |
| 131 DCHECK(task_runner_->RunsTasksOnCurrentThread()); | 138 DCHECK(task_runner_->RunsTasksOnCurrentThread()); |
| 132 | 139 |
| 133 requested_bit_rate_ = bit_rate; | 140 requested_bit_rate_ = bit_rate; |
| 134 video_encode_accelerator_->RequestEncodingParametersChange(bit_rate, | 141 video_encode_accelerator_->RequestEncodingParametersChange(bit_rate, |
| 135 max_frame_rate_); | 142 max_frame_rate_); |
| 136 } | 143 } |
| 137 | 144 |
| 145 // The destruction call back of the copied video frame to free its use of | |
| 146 // the input buffer. | |
| 147 void OnFinishEncodeFrame(int index) { | |
|
miu
2016/04/30 00:41:14
naming nit: Since this is only used when we have t
xjz
2016/05/03 01:17:59
Done.
| |
| 148 DCHECK(task_runner_->RunsTasksOnCurrentThread()); | |
| 149 DCHECK_GE(index, 0); | |
| 150 DCHECK_LT(index, static_cast<int>(input_buffers_.size())); | |
| 151 free_input_buffer_index_.push_back(index); | |
| 152 } | |
| 153 | |
| 138 void EncodeVideoFrame( | 154 void EncodeVideoFrame( |
| 139 const scoped_refptr<media::VideoFrame>& video_frame, | 155 const scoped_refptr<media::VideoFrame>& video_frame, |
| 140 const base::TimeTicks& reference_time, | 156 const base::TimeTicks& reference_time, |
| 141 bool key_frame_requested, | 157 bool key_frame_requested, |
| 142 const VideoEncoder::FrameEncodedCallback& frame_encoded_callback) { | 158 const VideoEncoder::FrameEncodedCallback& frame_encoded_callback) { |
| 143 DCHECK(task_runner_->RunsTasksOnCurrentThread()); | 159 DCHECK(task_runner_->RunsTasksOnCurrentThread()); |
| 144 | 160 |
| 145 if (!encoder_active_) | 161 if (!encoder_active_) |
| 146 return; | 162 return; |
|
miu
2016/04/30 00:41:14
There are a bunch of early returns in this method.
xjz
2016/05/03 01:17:59
Done. 1. Put all the necessary early return operat
| |
| 147 | 163 |
| 164 DCHECK(!free_input_buffer_index_.empty()); | |
| 165 | |
| 148 in_progress_frame_encodes_.push_back(InProgressFrameEncode( | 166 in_progress_frame_encodes_.push_back(InProgressFrameEncode( |
| 149 video_frame, reference_time, frame_encoded_callback, | 167 video_frame, reference_time, frame_encoded_callback, |
| 150 requested_bit_rate_)); | 168 requested_bit_rate_)); |
| 151 | 169 |
| 170 scoped_refptr<media::VideoFrame> frame = video_frame; | |
| 171 if (video_frame->coded_size() != frame_coded_size_) { | |
| 172 DCHECK_GE(frame_coded_size_.width(), video_frame->visible_rect().width()); | |
| 173 DCHECK_GE(frame_coded_size_.height(), | |
| 174 video_frame->visible_rect().height()); | |
| 175 | |
| 176 int index = free_input_buffer_index_.back(); | |
| 177 base::SharedMemory* input_buffer = input_buffers_[index]; | |
| 178 frame = VideoFrame::WrapExternalSharedMemory( | |
| 179 video_frame->format(), frame_coded_size_, video_frame->visible_rect(), | |
| 180 video_frame->visible_rect().size(), | |
| 181 static_cast<uint8_t*>(input_buffer->memory()), | |
| 182 input_buffer->mapped_size(), input_buffer->handle(), 0, | |
| 183 video_frame->timestamp()); | |
| 184 if (!frame.get()) { | |
| 185 VLOG(1) << "Error: ExternalVideoEncoder: CreateFrame failed."; | |
|
miu
2016/04/30 00:41:14
This should probably be LOG(DFATAL) since either t
xjz
2016/05/03 01:17:59
Done.
| |
| 186 return; | |
| 187 } | |
| 188 | |
| 189 // Copy the input frame to match the input requirements for the encoder. | |
| 190 if (!media::I420CopyWithPadding(video_frame, frame)) { | |
| 191 VLOG(1) << "ERROR: ExternalVideoEncoder: Copy failed."; | |
|
miu
2016/04/30 00:41:14
ditto: LOG(DFATAL).
xjz
2016/05/03 01:17:59
Done. Combined this check with the above check on
| |
| 192 return; | |
| 193 } | |
| 194 | |
| 195 frame->AddDestructionObserver(media::BindToCurrentLoop( | |
| 196 base::Bind(&ExternalVideoEncoder::VEAClientImpl::OnFinishEncodeFrame, | |
| 197 this, index))); | |
| 198 free_input_buffer_index_.pop_back(); | |
| 199 } | |
| 200 | |
| 152 // BitstreamBufferReady will be called once the encoder is done. | 201 // BitstreamBufferReady will be called once the encoder is done. |
| 153 video_encode_accelerator_->Encode(video_frame, key_frame_requested); | 202 video_encode_accelerator_->Encode(frame, key_frame_requested); |
| 154 } | 203 } |
| 155 | 204 |
| 205 // Return true if there is available input buffer. | |
| 206 bool IsInputBufferReady() { return !free_input_buffer_index_.empty(); } | |
| 207 | |
| 156 protected: | 208 protected: |
| 157 void NotifyError(VideoEncodeAccelerator::Error error) final { | 209 void NotifyError(VideoEncodeAccelerator::Error error) final { |
| 158 DCHECK(task_runner_->RunsTasksOnCurrentThread()); | 210 DCHECK(task_runner_->RunsTasksOnCurrentThread()); |
| 159 | 211 |
| 160 DCHECK(error != VideoEncodeAccelerator::kInvalidArgumentError && | 212 DCHECK(error != VideoEncodeAccelerator::kInvalidArgumentError && |
| 161 error != VideoEncodeAccelerator::kIllegalStateError); | 213 error != VideoEncodeAccelerator::kIllegalStateError); |
| 162 | 214 |
| 163 encoder_active_ = false; | 215 encoder_active_ = false; |
| 164 | 216 |
| 165 cast_environment_->PostTask( | 217 cast_environment_->PostTask( |
| 166 CastEnvironment::MAIN, | 218 CastEnvironment::MAIN, |
| 167 FROM_HERE, | 219 FROM_HERE, |
| 168 base::Bind(status_change_cb_, STATUS_CODEC_RUNTIME_ERROR)); | 220 base::Bind(status_change_cb_, STATUS_CODEC_RUNTIME_ERROR)); |
| 169 | 221 |
| 170 // TODO(miu): Force-flush all |in_progress_frame_encodes_| immediately so | 222 // TODO(miu): Force-flush all |in_progress_frame_encodes_| immediately so |
| 171 // pending frames do not become stuck, freezing VideoSender. | 223 // pending frames do not become stuck, freezing VideoSender. |
| 172 } | 224 } |
| 173 | 225 |
| 174 // Called to allocate the input and output buffers. | 226 // Called to allocate the input and output buffers. |
| 175 void RequireBitstreamBuffers(unsigned int input_count, | 227 void RequireBitstreamBuffers(unsigned int input_count, |
| 176 const gfx::Size& input_coded_size, | 228 const gfx::Size& input_coded_size, |
| 177 size_t output_buffer_size) final { | 229 size_t output_buffer_size) final { |
| 178 DCHECK(task_runner_->RunsTasksOnCurrentThread()); | 230 DCHECK(task_runner_->RunsTasksOnCurrentThread()); |
| 179 | 231 |
| 180 // TODO(miu): Investigate why we are ignoring |input_count| (4) and instead | 232 frame_coded_size_ = input_coded_size; |
| 181 // using |kOutputBufferCount| (3) here. | 233 |
| 234 for (size_t j = 0; j < input_count + kExtraInputBufferCount; ++j) { | |
|
miu
2016/04/30 00:41:14
Instead of allocating these up-front, can we alloc
xjz
2016/05/03 01:17:59
Done.
| |
| 235 create_video_encode_memory_cb_.Run( | |
| 236 media::VideoFrame::AllocationSize(media::PIXEL_FORMAT_I420, | |
| 237 input_coded_size), | |
| 238 base::Bind(&VEAClientImpl::OnCreateInputSharedMemory, this)); | |
| 239 } | |
| 240 | |
| 182 for (size_t j = 0; j < kOutputBufferCount; ++j) { | 241 for (size_t j = 0; j < kOutputBufferCount; ++j) { |
| 183 create_video_encode_memory_cb_.Run( | 242 create_video_encode_memory_cb_.Run( |
| 184 output_buffer_size, | 243 output_buffer_size, |
| 185 base::Bind(&VEAClientImpl::OnCreateSharedMemory, this)); | 244 base::Bind(&VEAClientImpl::OnCreateSharedMemory, this)); |
| 186 } | 245 } |
| 187 } | 246 } |
| 188 | 247 |
| 189 // Encoder has encoded a frame and it's available in one of the output | 248 // Encoder has encoded a frame and it's available in one of the output |
| 190 // buffers. Package the result in a media::cast::EncodedFrame and post it | 249 // buffers. Package the result in a media::cast::EncodedFrame and post it |
| 191 // to the Cast MAIN thread via the supplied callback. | 250 // to the Cast MAIN thread via the supplied callback. |
| (...skipping 169 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 361 } | 420 } |
| 362 | 421 |
| 363 // Note: This method can be called on any thread. | 422 // Note: This method can be called on any thread. |
| 364 void OnCreateSharedMemory(scoped_ptr<base::SharedMemory> memory) { | 423 void OnCreateSharedMemory(scoped_ptr<base::SharedMemory> memory) { |
| 365 task_runner_->PostTask(FROM_HERE, | 424 task_runner_->PostTask(FROM_HERE, |
| 366 base::Bind(&VEAClientImpl::OnReceivedSharedMemory, | 425 base::Bind(&VEAClientImpl::OnReceivedSharedMemory, |
| 367 this, | 426 this, |
| 368 base::Passed(&memory))); | 427 base::Passed(&memory))); |
| 369 } | 428 } |
| 370 | 429 |
| 430 void OnCreateInputSharedMemory(scoped_ptr<base::SharedMemory> memory) { | |
| 431 task_runner_->PostTask( | |
| 432 FROM_HERE, base::Bind(&VEAClientImpl::OnReceivedInputSharedMemory, this, | |
| 433 base::Passed(&memory))); | |
| 434 } | |
| 435 | |
| 371 void OnReceivedSharedMemory(scoped_ptr<base::SharedMemory> memory) { | 436 void OnReceivedSharedMemory(scoped_ptr<base::SharedMemory> memory) { |
| 372 DCHECK(task_runner_->RunsTasksOnCurrentThread()); | 437 DCHECK(task_runner_->RunsTasksOnCurrentThread()); |
| 373 | 438 |
| 374 output_buffers_.push_back(std::move(memory)); | 439 output_buffers_.push_back(std::move(memory)); |
| 375 | 440 |
| 376 // Wait until all requested buffers are received. | 441 // Wait until all requested buffers are received. |
| 377 if (output_buffers_.size() < kOutputBufferCount) | 442 if (output_buffers_.size() < kOutputBufferCount) |
| 378 return; | 443 return; |
| 379 | 444 |
| 380 // Immediately provide all output buffers to the VEA. | 445 // Immediately provide all output buffers to the VEA. |
| 381 for (size_t i = 0; i < output_buffers_.size(); ++i) { | 446 for (size_t i = 0; i < output_buffers_.size(); ++i) { |
| 382 video_encode_accelerator_->UseOutputBitstreamBuffer( | 447 video_encode_accelerator_->UseOutputBitstreamBuffer( |
| 383 media::BitstreamBuffer(static_cast<int32_t>(i), | 448 media::BitstreamBuffer(static_cast<int32_t>(i), |
| 384 output_buffers_[i]->handle(), | 449 output_buffers_[i]->handle(), |
| 385 output_buffers_[i]->mapped_size())); | 450 output_buffers_[i]->mapped_size())); |
| 386 } | 451 } |
| 387 } | 452 } |
| 388 | 453 |
| 454 void OnReceivedInputSharedMemory(scoped_ptr<base::SharedMemory> memory) { | |
| 455 DCHECK(task_runner_->RunsTasksOnCurrentThread()); | |
| 456 | |
| 457 input_buffers_.push_back(std::move(memory)); | |
| 458 free_input_buffer_index_.push_back(input_buffers_.size() - 1); | |
| 459 } | |
| 460 | |
| 389 // Parse H264 SPS, PPS, and Slice header, and return the averaged frame | 461 // Parse H264 SPS, PPS, and Slice header, and return the averaged frame |
| 390 // quantizer in the range of [0, 51], or -1 on parse error. | 462 // quantizer in the range of [0, 51], or -1 on parse error. |
| 391 double GetH264FrameQuantizer(const uint8_t* encoded_data, off_t size) { | 463 double GetH264FrameQuantizer(const uint8_t* encoded_data, off_t size) { |
| 392 DCHECK(encoded_data); | 464 DCHECK(encoded_data); |
| 393 if (!size) | 465 if (!size) |
| 394 return -1; | 466 return -1; |
| 395 h264_parser_.SetStream(encoded_data, size); | 467 h264_parser_.SetStream(encoded_data, size); |
| 396 double total_quantizer = 0; | 468 double total_quantizer = 0; |
| 397 int num_slices = 0; | 469 int num_slices = 0; |
| 398 | 470 |
| (...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 455 uint32_t next_frame_id_; | 527 uint32_t next_frame_id_; |
| 456 bool key_frame_encountered_; | 528 bool key_frame_encountered_; |
| 457 std::string stream_header_; | 529 std::string stream_header_; |
| 458 VideoCodecProfile codec_profile_; | 530 VideoCodecProfile codec_profile_; |
| 459 bool key_frame_quantizer_parsable_; | 531 bool key_frame_quantizer_parsable_; |
| 460 H264Parser h264_parser_; | 532 H264Parser h264_parser_; |
| 461 | 533 |
| 462 // Shared memory buffers for output with the VideoAccelerator. | 534 // Shared memory buffers for output with the VideoAccelerator. |
| 463 ScopedVector<base::SharedMemory> output_buffers_; | 535 ScopedVector<base::SharedMemory> output_buffers_; |
| 464 | 536 |
| 537 // Shared memory buffers for input video frames with the VideoAccelerator. | |
| 538 ScopedVector<base::SharedMemory> input_buffers_; | |
|
miu
2016/04/30 00:41:14
ScopedVector is deprecated. Please use std::vector
xjz
2016/05/03 01:17:59
Done.
| |
| 539 | |
| 540 // Available input buffer index. These buffers are used in FILO order. | |
| 541 std::vector<int> free_input_buffer_index_; | |
| 542 | |
| 465 // FIFO list. | 543 // FIFO list. |
| 466 std::list<InProgressFrameEncode> in_progress_frame_encodes_; | 544 std::list<InProgressFrameEncode> in_progress_frame_encodes_; |
| 467 | 545 |
| 468 // The requested encode bit rate for the next frame. | 546 // The requested encode bit rate for the next frame. |
| 469 int requested_bit_rate_; | 547 int requested_bit_rate_; |
| 470 | 548 |
| 471 // Used to compute utilization metrics for each frame. | 549 // Used to compute utilization metrics for each frame. |
| 472 QuantizerEstimator quantizer_estimator_; | 550 QuantizerEstimator quantizer_estimator_; |
| 473 | 551 |
| 474 // Set to true once a frame with zero-length encoded data has been | 552 // Set to true once a frame with zero-length encoded data has been |
| 475 // encountered. | 553 // encountered. |
| 476 // TODO(miu): Remove after discovering cause. http://crbug.com/519022 | 554 // TODO(miu): Remove after discovering cause. http://crbug.com/519022 |
| 477 bool has_seen_zero_length_encoded_frame_; | 555 bool has_seen_zero_length_encoded_frame_; |
| 478 | 556 |
| 557 // The coded size of the video frame required by Encoder. This size is | |
| 558 // obtained from VEA through |RequireBitstreamBuffers()|. | |
| 559 gfx::Size frame_coded_size_; | |
| 560 | |
| 479 DISALLOW_COPY_AND_ASSIGN(VEAClientImpl); | 561 DISALLOW_COPY_AND_ASSIGN(VEAClientImpl); |
| 480 }; | 562 }; |
| 481 | 563 |
| 482 // static | 564 // static |
| 483 bool ExternalVideoEncoder::IsSupported(const VideoSenderConfig& video_config) { | 565 bool ExternalVideoEncoder::IsSupported(const VideoSenderConfig& video_config) { |
| 484 if (video_config.codec != CODEC_VIDEO_VP8 && | 566 if (video_config.codec != CODEC_VIDEO_VP8 && |
| 485 video_config.codec != CODEC_VIDEO_H264) | 567 video_config.codec != CODEC_VIDEO_H264) |
| 486 return false; | 568 return false; |
| 487 | 569 |
| 488 // TODO(miu): "Layering hooks" are needed to be able to query outside of | 570 // TODO(miu): "Layering hooks" are needed to be able to query outside of |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 528 bool ExternalVideoEncoder::EncodeVideoFrame( | 610 bool ExternalVideoEncoder::EncodeVideoFrame( |
| 529 const scoped_refptr<media::VideoFrame>& video_frame, | 611 const scoped_refptr<media::VideoFrame>& video_frame, |
| 530 const base::TimeTicks& reference_time, | 612 const base::TimeTicks& reference_time, |
| 531 const FrameEncodedCallback& frame_encoded_callback) { | 613 const FrameEncodedCallback& frame_encoded_callback) { |
| 532 DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); | 614 DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); |
| 533 DCHECK(!frame_encoded_callback.is_null()); | 615 DCHECK(!frame_encoded_callback.is_null()); |
| 534 | 616 |
| 535 if (!client_ || video_frame->visible_rect().size() != frame_size_) | 617 if (!client_ || video_frame->visible_rect().size() != frame_size_) |
| 536 return false; | 618 return false; |
| 537 | 619 |
| 620 // Drop frame until an input buffer is available. | |
| 621 if (!client_->IsInputBufferReady()) | |
|
miu
2016/04/30 00:41:14
See comments above. If input buffers are allocated
xjz
2016/05/03 01:17:59
Removed. The purpose of using this was actually tr
| |
| 622 return false; | |
| 623 | |
| 538 client_->task_runner()->PostTask(FROM_HERE, | 624 client_->task_runner()->PostTask(FROM_HERE, |
| 539 base::Bind(&VEAClientImpl::EncodeVideoFrame, | 625 base::Bind(&VEAClientImpl::EncodeVideoFrame, |
| 540 client_, | 626 client_, |
| 541 video_frame, | 627 video_frame, |
| 542 reference_time, | 628 reference_time, |
| 543 key_frame_requested_, | 629 key_frame_requested_, |
| 544 frame_encoded_callback)); | 630 frame_encoded_callback)); |
| 545 key_frame_requested_ = false; | 631 key_frame_requested_ = false; |
| 546 return true; | 632 return true; |
| 547 } | 633 } |
| (...skipping 239 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 787 const double kEntropyAtMaxQuantizer = 7.5; | 873 const double kEntropyAtMaxQuantizer = 7.5; |
| 788 const double slope = | 874 const double slope = |
| 789 (MAX_VP8_QUANTIZER - MIN_VP8_QUANTIZER) / kEntropyAtMaxQuantizer; | 875 (MAX_VP8_QUANTIZER - MIN_VP8_QUANTIZER) / kEntropyAtMaxQuantizer; |
| 790 const double quantizer = std::min<double>( | 876 const double quantizer = std::min<double>( |
| 791 MAX_VP8_QUANTIZER, MIN_VP8_QUANTIZER + slope * shannon_entropy); | 877 MAX_VP8_QUANTIZER, MIN_VP8_QUANTIZER + slope * shannon_entropy); |
| 792 return quantizer; | 878 return quantizer; |
| 793 } | 879 } |
| 794 | 880 |
| 795 } // namespace cast | 881 } // namespace cast |
| 796 } // namespace media | 882 } // namespace media |
| OLD | NEW |