diff options
Diffstat (limited to 'media/cast/video_sender/codecs/vp8/vp8_encoder.cc')
-rw-r--r-- | media/cast/video_sender/codecs/vp8/vp8_encoder.cc | 352 |
1 files changed, 352 insertions, 0 deletions
diff --git a/media/cast/video_sender/codecs/vp8/vp8_encoder.cc b/media/cast/video_sender/codecs/vp8/vp8_encoder.cc new file mode 100644 index 0000000..d24ef4b --- /dev/null +++ b/media/cast/video_sender/codecs/vp8/vp8_encoder.cc @@ -0,0 +1,352 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// TODO (pwestin): add a link to the design document describing the generic +// protocol and the VP8 specific details. +#include "media/cast/video_sender/codecs/vp8/vp8_encoder.h" + +#include <vector> + +#include "base/logging.h" +#include "media/cast/cast_defines.h" +#include "media/cast/rtp_common/rtp_defines.h" +#include "third_party/libvpx/source/libvpx/vpx/vp8cx.h" + +namespace media { +namespace cast { + +static const uint32 kMinIntra = 300; + +Vp8Encoder::Vp8Encoder(const VideoSenderConfig& video_config, + uint8 max_unacked_frames) + : cast_config_(video_config), + use_multiple_video_buffers_( + cast_config_.max_number_of_video_buffers_used == + kNumberOfVp8VideoBuffers), + max_number_of_repeated_buffers_in_a_row_( + (max_unacked_frames > kNumberOfVp8VideoBuffers) ? + ((max_unacked_frames - 1) / kNumberOfVp8VideoBuffers) : 0), + config_(new vpx_codec_enc_cfg_t()), + encoder_(new vpx_codec_ctx_t()), + // Creating a wrapper to the image - setting image data to NULL. Actual + // pointer will be set during encode. Setting align to 1, as it is + // meaningless (actual memory is not allocated). + raw_image_(vpx_img_wrap(NULL, IMG_FMT_I420, video_config.width, + video_config.height, 1, NULL)), + key_frame_requested_(true), + timestamp_(0), + last_encoded_frame_id_(kStartFrameId), + number_of_repeated_buffers_(0) { + // VP8 have 3 buffers available for prediction, with + // max_number_of_video_buffers_used set to 1 we maximize the coding efficiency + // however in this mode we can not skip frames in the receiver to catch up + // after a temporary network outage; with max_number_of_video_buffers_used + // set to 3 we allow 2 frames to be skipped by the receiver without error + // propagation. + DCHECK(cast_config_.max_number_of_video_buffers_used == 1 || + cast_config_.max_number_of_video_buffers_used == + kNumberOfVp8VideoBuffers) << "Invalid argument"; + + for (int i = 0; i < kNumberOfVp8VideoBuffers; ++i) { + acked_frame_buffers_[i] = true; + used_buffers_frame_id_[i] = kStartFrameId; + } + InitEncode(video_config.number_of_cores); +} + +Vp8Encoder::~Vp8Encoder() { + vpx_codec_destroy(encoder_); + vpx_img_free(raw_image_); +} + +void Vp8Encoder::InitEncode(int number_of_cores) { + // Populate encoder configuration with default values. + if (vpx_codec_enc_config_default(vpx_codec_vp8_cx(), config_.get(), 0)) { + DCHECK(false) << "Invalid return value"; + } + config_->g_w = cast_config_.width; + config_->g_h = cast_config_.height; + config_->rc_target_bitrate = cast_config_.start_bitrate / 1000; // In kbit/s. + + // Setting the codec time base. + config_->g_timebase.num = 1; + config_->g_timebase.den = kVideoFrequency; + config_->g_lag_in_frames = 0; + config_->kf_mode = VPX_KF_DISABLED; + if (use_multiple_video_buffers_) { + // We must enable error resilience when we use multiple buffers, due to + // codec requirements. + config_->g_error_resilient = 1; + } + + if (cast_config_.width * cast_config_.height > 640 * 480 + && number_of_cores >= 2) { + config_->g_threads = 2; // 2 threads for qHD/HD. + } else { + config_->g_threads = 1; // 1 thread for VGA or less. + } + + // Rate control settings. + // TODO(pwestin): revisit these constants. Currently identical to webrtc. + config_->rc_dropframe_thresh = 30; + config_->rc_end_usage = VPX_CBR; + config_->g_pass = VPX_RC_ONE_PASS; + config_->rc_resize_allowed = 0; + config_->rc_min_quantizer = cast_config_.min_qp; + config_->rc_max_quantizer = cast_config_.max_qp; + config_->rc_undershoot_pct = 100; + config_->rc_overshoot_pct = 15; + config_->rc_buf_initial_sz = 500; + config_->rc_buf_optimal_sz = 600; + config_->rc_buf_sz = 1000; + + // set the maximum target size of any key-frame. + uint32 rc_max_intra_target = MaxIntraTarget(config_->rc_buf_optimal_sz); + vpx_codec_flags_t flags = 0; + // TODO(mikhal): Tune settings. + if (vpx_codec_enc_init(encoder_, vpx_codec_vp8_cx(), config_.get(), flags)) { + DCHECK(false) << "Invalid return value"; + } + vpx_codec_control(encoder_, VP8E_SET_STATIC_THRESHOLD, 1); + vpx_codec_control(encoder_, VP8E_SET_NOISE_SENSITIVITY, 0); + vpx_codec_control(encoder_, VP8E_SET_CPUUSED, -6); + vpx_codec_control(encoder_, VP8E_SET_MAX_INTRA_BITRATE_PCT, + rc_max_intra_target); +} + +bool Vp8Encoder::Encode(const I420VideoFrame& input_image, + EncodedVideoFrame* encoded_image) { + // Image in vpx_image_t format. + // Input image is const. VP8's raw image is not defined as const. + raw_image_->planes[PLANE_Y] = const_cast<uint8*>(input_image.y_plane.data); + raw_image_->planes[PLANE_U] = const_cast<uint8*>(input_image.u_plane.data); + raw_image_->planes[PLANE_V] = const_cast<uint8*>(input_image.v_plane.data); + + raw_image_->stride[VPX_PLANE_Y] = input_image.y_plane.stride; + raw_image_->stride[VPX_PLANE_U] = input_image.u_plane.stride; + raw_image_->stride[VPX_PLANE_V] = input_image.v_plane.stride; + + uint8 latest_frame_id_to_reference; + Vp8Buffers buffer_to_update; + vpx_codec_flags_t flags = 0; + if (key_frame_requested_) { + flags = VPX_EFLAG_FORCE_KF; + // Self reference. + latest_frame_id_to_reference = + static_cast<uint8>(last_encoded_frame_id_ + 1); + // We can pick any buffer as buffer_to_update since we update + // them all. + buffer_to_update = kLastBuffer; + } else { + // Reference all acked frames (buffers). + latest_frame_id_to_reference = GetLatestFrameIdToReference(); + GetCodecReferenceFlags(&flags); + buffer_to_update = GetNextBufferToUpdate(); + GetCodecUpdateFlags(buffer_to_update, &flags); + } + + // Note: The duration does not reflect the real time between frames. This is + // done to keep the encoder happy. + uint32 duration = kVideoFrequency / cast_config_.max_frame_rate; + if (vpx_codec_encode(encoder_, raw_image_, timestamp_, duration, flags, + VPX_DL_REALTIME)) { + return false; + } + timestamp_ += duration; + + // Get encoded frame. + const vpx_codec_cx_pkt_t *pkt = NULL; + vpx_codec_iter_t iter = NULL; + int total_size = 0; + while ((pkt = vpx_codec_get_cx_data(encoder_, &iter)) != NULL) { + if (pkt->kind == VPX_CODEC_CX_FRAME_PKT) { + total_size += pkt->data.frame.sz; + encoded_image->data.reserve(total_size); + encoded_image->data.insert( + encoded_image->data.end(), + static_cast<const uint8*>(pkt->data.frame.buf), + static_cast<const uint8*>(pkt->data.frame.buf) + + pkt->data.frame.sz); + if (pkt->data.frame.flags & VPX_FRAME_IS_KEY) { + encoded_image->key_frame = true; + } else { + encoded_image->key_frame = false; + } + } + } + // Don't update frame_id for zero size frames. + if (total_size == 0) return true; + + // Populate the encoded frame. + encoded_image->codec = kVp8; + encoded_image->last_referenced_frame_id = latest_frame_id_to_reference; + encoded_image->frame_id = ++last_encoded_frame_id_; + + if (encoded_image->key_frame) { + key_frame_requested_ = false; + + for (int i = 0; i < kNumberOfVp8VideoBuffers; ++i) { + used_buffers_frame_id_[i] = encoded_image->frame_id; + } + // We can pick any buffer as last_used_vp8_buffer_ since we update + // them all. + last_used_vp8_buffer_ = buffer_to_update; + } else { + if (buffer_to_update != kNoBuffer) { + acked_frame_buffers_[buffer_to_update] = false; + used_buffers_frame_id_[buffer_to_update] = encoded_image->frame_id; + last_used_vp8_buffer_ = buffer_to_update; + } + } + return true; +} + +void Vp8Encoder::GetCodecReferenceFlags(vpx_codec_flags_t* flags) { + if (!use_multiple_video_buffers_) return; + + // We need to reference something. + DCHECK(acked_frame_buffers_[kAltRefBuffer] || + acked_frame_buffers_[kGoldenBuffer] || + acked_frame_buffers_[kLastBuffer]) << "Invalid state"; + + if (!acked_frame_buffers_[kAltRefBuffer]) { + *flags |= VP8_EFLAG_NO_REF_ARF; + } + if (!acked_frame_buffers_[kGoldenBuffer]) { + *flags |= VP8_EFLAG_NO_REF_GF; + } + if (!acked_frame_buffers_[kLastBuffer]) { + *flags |= VP8_EFLAG_NO_REF_LAST; + } +} + +uint8 Vp8Encoder::GetLatestFrameIdToReference() { + if (!use_multiple_video_buffers_) return last_encoded_frame_id_; + + int latest_frame_id_to_reference = -1; + if (acked_frame_buffers_[kAltRefBuffer]) { + latest_frame_id_to_reference = used_buffers_frame_id_[kAltRefBuffer]; + } + if (acked_frame_buffers_[kGoldenBuffer]) { + if (latest_frame_id_to_reference == -1) { + latest_frame_id_to_reference = used_buffers_frame_id_[kGoldenBuffer]; + } else { + if (IsNewerFrameId(used_buffers_frame_id_[kGoldenBuffer], + latest_frame_id_to_reference)) { + latest_frame_id_to_reference = used_buffers_frame_id_[kGoldenBuffer]; + } + } + } + if (acked_frame_buffers_[kLastBuffer]) { + if (latest_frame_id_to_reference == -1) { + latest_frame_id_to_reference = used_buffers_frame_id_[kLastBuffer]; + } else { + if (IsNewerFrameId(used_buffers_frame_id_[kLastBuffer], + latest_frame_id_to_reference)) { + latest_frame_id_to_reference = used_buffers_frame_id_[kLastBuffer]; + } + } + } + DCHECK(latest_frame_id_to_reference != -1) << "Invalid state"; + return static_cast<uint8>(latest_frame_id_to_reference); +} + +Vp8Encoder::Vp8Buffers Vp8Encoder::GetNextBufferToUpdate() { + // Update at most one buffer, except for key-frames. + + Vp8Buffers buffer_to_update; + if (number_of_repeated_buffers_ < max_number_of_repeated_buffers_in_a_row_) { + // TODO(pwestin): experiment with this. The issue with only this change is + // that we can end up with only 4 frames in flight when we expect 6. + // buffer_to_update = last_used_vp8_buffer_; + buffer_to_update = kNoBuffer; + ++number_of_repeated_buffers_; + } else { + number_of_repeated_buffers_ = 0; + switch (last_used_vp8_buffer_) { + case kAltRefBuffer: + buffer_to_update = kLastBuffer; + break; + case kLastBuffer: + buffer_to_update = kGoldenBuffer; + break; + case kGoldenBuffer: + buffer_to_update = kAltRefBuffer; + break; + case kNoBuffer: + DCHECK(false) << "Invalid state"; + break; + } + } + return buffer_to_update; +} + +void Vp8Encoder::GetCodecUpdateFlags(Vp8Buffers buffer_to_update, + vpx_codec_flags_t* flags) { + if (!use_multiple_video_buffers_) return; + + // Update at most one buffer, except for key-frames. + switch (buffer_to_update) { + case kAltRefBuffer: + *flags |= VP8_EFLAG_NO_UPD_GF; + *flags |= VP8_EFLAG_NO_UPD_LAST; + break; + case kLastBuffer: + *flags |= VP8_EFLAG_NO_UPD_GF; + *flags |= VP8_EFLAG_NO_UPD_ARF; + break; + case kGoldenBuffer: + *flags |= VP8_EFLAG_NO_UPD_ARF; + *flags |= VP8_EFLAG_NO_UPD_LAST; + break; + case kNoBuffer: + *flags |= VP8_EFLAG_NO_UPD_ARF; + *flags |= VP8_EFLAG_NO_UPD_GF; + *flags |= VP8_EFLAG_NO_UPD_LAST; + *flags |= VP8_EFLAG_NO_UPD_ENTROPY; + break; + } +} + +void Vp8Encoder::UpdateRates(uint32 new_bitrate) { + config_->rc_target_bitrate = new_bitrate / 1000; // In kbit/s. + // Update encoder context. + if (vpx_codec_enc_config_set(encoder_, config_.get())) { + DCHECK(false) << "Invalid return value"; + } +} + +void Vp8Encoder::LatestFrameIdToReference(uint8 frame_id) { + if (!use_multiple_video_buffers_) return; + + for (int i = 0; i < kNumberOfVp8VideoBuffers; ++i) { + if (frame_id == used_buffers_frame_id_[i]) { + acked_frame_buffers_[i] = true; + } + } +} + +void Vp8Encoder::RequestKeyFrame() { + key_frame_requested_ = true; +} + +// Calculate the max size of the key frame relative to a normal delta frame. +uint32 Vp8Encoder::MaxIntraTarget(uint32 optimal_buffer_size_ms) const { + // Set max to the optimal buffer level (normalized by target BR), + // and scaled by a scale_parameter. + // Max target size = scalePar * optimalBufferSize * targetBR[Kbps]. + // This values is presented in percentage of perFrameBw: + // perFrameBw = targetBR[Kbps] * 1000 / frameRate. + // The target in % is as follows: + + float scale_parameter = 0.5; + uint32 target_pct = optimal_buffer_size_ms * scale_parameter * + cast_config_.max_frame_rate / 10; + + // Don't go below 3 times the per frame bandwidth. + return std::max(target_pct, kMinIntra); +} + +} // namespace cast +} // namespace media |