Moving src/webrtc into src/.
In order to eliminate the WebRTC Subtree mirror in Chromium, WebRTC is moving the content of the src/webrtc directory up to the src/ directory. NOPRESUBMIT=true NOTREECHECKS=true NOTRY=true TBR=tommi@webrtc.org Bug: chromium:611808 Change-Id: Iac59c5b51b950f174119565bac87955a7994bc38 Reviewed-on: https://webrtc-review.googlesource.com/1560 Commit-Queue: Mirko Bonadei <mbonadei@webrtc.org> Reviewed-by: Henrik Kjellander <kjellander@webrtc.org> Cr-Commit-Position: refs/heads/master@{#19845}
This commit is contained in:
committed by
Commit Bot
parent
6674846b4a
commit
bb547203bf
2
modules/video_coding/codecs/h264/OWNERS
Normal file
2
modules/video_coding/codecs/h264/OWNERS
Normal file
@ -0,0 +1,2 @@
|
||||
hbos@webrtc.org
|
||||
sprang@webrtc.org
|
||||
79
modules/video_coding/codecs/h264/h264.cc
Normal file
79
modules/video_coding/codecs/h264/h264.cc
Normal file
@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "webrtc/modules/video_coding/codecs/h264/include/h264.h"
|
||||
|
||||
#if defined(WEBRTC_USE_H264)
|
||||
#include "webrtc/modules/video_coding/codecs/h264/h264_decoder_impl.h"
|
||||
#include "webrtc/modules/video_coding/codecs/h264/h264_encoder_impl.h"
|
||||
#endif
|
||||
|
||||
#include "webrtc/rtc_base/checks.h"
|
||||
#include "webrtc/rtc_base/logging.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
|
||||
#if defined(WEBRTC_USE_H264)
|
||||
bool g_rtc_use_h264 = true;
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
void DisableRtcUseH264() {
|
||||
#if defined(WEBRTC_USE_H264)
|
||||
g_rtc_use_h264 = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
// If any H.264 codec is supported (iOS HW or OpenH264/FFmpeg).
|
||||
bool IsH264CodecSupported() {
|
||||
#if defined(WEBRTC_USE_H264)
|
||||
return g_rtc_use_h264;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
H264Encoder* H264Encoder::Create(const cricket::VideoCodec& codec) {
|
||||
RTC_DCHECK(H264Encoder::IsSupported());
|
||||
#if defined(WEBRTC_USE_H264)
|
||||
RTC_CHECK(g_rtc_use_h264);
|
||||
LOG(LS_INFO) << "Creating H264EncoderImpl.";
|
||||
return new H264EncoderImpl(codec);
|
||||
#else
|
||||
RTC_NOTREACHED();
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool H264Encoder::IsSupported() {
|
||||
return IsH264CodecSupported();
|
||||
}
|
||||
|
||||
H264Decoder* H264Decoder::Create() {
|
||||
RTC_DCHECK(H264Decoder::IsSupported());
|
||||
#if defined(WEBRTC_USE_H264)
|
||||
RTC_CHECK(g_rtc_use_h264);
|
||||
LOG(LS_INFO) << "Creating H264DecoderImpl.";
|
||||
return new H264DecoderImpl();
|
||||
#else
|
||||
RTC_NOTREACHED();
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool H264Decoder::IsSupported() {
|
||||
return IsH264CodecSupported();
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
426
modules/video_coding/codecs/h264/h264_decoder_impl.cc
Normal file
426
modules/video_coding/codecs/h264/h264_decoder_impl.cc
Normal file
@ -0,0 +1,426 @@
|
||||
/*
|
||||
* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "webrtc/modules/video_coding/codecs/h264/h264_decoder_impl.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
|
||||
extern "C" {
|
||||
#include "third_party/ffmpeg/libavcodec/avcodec.h"
|
||||
#include "third_party/ffmpeg/libavformat/avformat.h"
|
||||
#include "third_party/ffmpeg/libavutil/imgutils.h"
|
||||
} // extern "C"
|
||||
|
||||
#include "webrtc/api/video/i420_buffer.h"
|
||||
#include "webrtc/common_video/include/video_frame_buffer.h"
|
||||
#include "webrtc/rtc_base/checks.h"
|
||||
#include "webrtc/rtc_base/criticalsection.h"
|
||||
#include "webrtc/rtc_base/keep_ref_until_done.h"
|
||||
#include "webrtc/rtc_base/logging.h"
|
||||
#include "webrtc/system_wrappers/include/metrics.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
|
||||
const AVPixelFormat kPixelFormat = AV_PIX_FMT_YUV420P;
|
||||
const size_t kYPlaneIndex = 0;
|
||||
const size_t kUPlaneIndex = 1;
|
||||
const size_t kVPlaneIndex = 2;
|
||||
|
||||
// Used by histograms. Values of entries should not be changed.
|
||||
enum H264DecoderImplEvent {
|
||||
kH264DecoderEventInit = 0,
|
||||
kH264DecoderEventError = 1,
|
||||
kH264DecoderEventMax = 16,
|
||||
};
|
||||
|
||||
#if defined(WEBRTC_INITIALIZE_FFMPEG)
|
||||
|
||||
rtc::CriticalSection ffmpeg_init_lock;
|
||||
bool ffmpeg_initialized = false;
|
||||
|
||||
// Called by FFmpeg to do mutex operations if initialized using
|
||||
// |InitializeFFmpeg|. Disabling thread safety analysis because void** does not
|
||||
// play nicely with thread_annotations.h macros.
|
||||
int LockManagerOperation(void** lock,
|
||||
AVLockOp op) RTC_NO_THREAD_SAFETY_ANALYSIS {
|
||||
switch (op) {
|
||||
case AV_LOCK_CREATE:
|
||||
*lock = new rtc::CriticalSection();
|
||||
return 0;
|
||||
case AV_LOCK_OBTAIN:
|
||||
static_cast<rtc::CriticalSection*>(*lock)->Enter();
|
||||
return 0;
|
||||
case AV_LOCK_RELEASE:
|
||||
static_cast<rtc::CriticalSection*>(*lock)->Leave();
|
||||
return 0;
|
||||
case AV_LOCK_DESTROY:
|
||||
delete static_cast<rtc::CriticalSection*>(*lock);
|
||||
*lock = nullptr;
|
||||
return 0;
|
||||
}
|
||||
RTC_NOTREACHED() << "Unrecognized AVLockOp.";
|
||||
return -1;
|
||||
}
|
||||
|
||||
void InitializeFFmpeg() {
|
||||
rtc::CritScope cs(&ffmpeg_init_lock);
|
||||
if (!ffmpeg_initialized) {
|
||||
if (av_lockmgr_register(LockManagerOperation) < 0) {
|
||||
RTC_NOTREACHED() << "av_lockmgr_register failed.";
|
||||
return;
|
||||
}
|
||||
av_register_all();
|
||||
ffmpeg_initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // defined(WEBRTC_INITIALIZE_FFMPEG)
|
||||
|
||||
} // namespace
|
||||
|
||||
int H264DecoderImpl::AVGetBuffer2(
|
||||
AVCodecContext* context, AVFrame* av_frame, int flags) {
|
||||
// Set in |InitDecode|.
|
||||
H264DecoderImpl* decoder = static_cast<H264DecoderImpl*>(context->opaque);
|
||||
// DCHECK values set in |InitDecode|.
|
||||
RTC_DCHECK(decoder);
|
||||
RTC_DCHECK_EQ(context->pix_fmt, kPixelFormat);
|
||||
// Necessary capability to be allowed to provide our own buffers.
|
||||
RTC_DCHECK(context->codec->capabilities | AV_CODEC_CAP_DR1);
|
||||
|
||||
// |av_frame->width| and |av_frame->height| are set by FFmpeg. These are the
|
||||
// actual image's dimensions and may be different from |context->width| and
|
||||
// |context->coded_width| due to reordering.
|
||||
int width = av_frame->width;
|
||||
int height = av_frame->height;
|
||||
// See |lowres|, if used the decoder scales the image by 1/2^(lowres). This
|
||||
// has implications on which resolutions are valid, but we don't use it.
|
||||
RTC_CHECK_EQ(context->lowres, 0);
|
||||
// Adjust the |width| and |height| to values acceptable by the decoder.
|
||||
// Without this, FFmpeg may overflow the buffer. If modified, |width| and/or
|
||||
// |height| are larger than the actual image and the image has to be cropped
|
||||
// (top-left corner) after decoding to avoid visible borders to the right and
|
||||
// bottom of the actual image.
|
||||
avcodec_align_dimensions(context, &width, &height);
|
||||
|
||||
RTC_CHECK_GE(width, 0);
|
||||
RTC_CHECK_GE(height, 0);
|
||||
int ret = av_image_check_size(static_cast<unsigned int>(width),
|
||||
static_cast<unsigned int>(height), 0, nullptr);
|
||||
if (ret < 0) {
|
||||
LOG(LS_ERROR) << "Invalid picture size " << width << "x" << height;
|
||||
decoder->ReportError();
|
||||
return ret;
|
||||
}
|
||||
|
||||
// The video frame is stored in |frame_buffer|. |av_frame| is FFmpeg's version
|
||||
// of a video frame and will be set up to reference |frame_buffer|'s data.
|
||||
|
||||
// FFmpeg expects the initial allocation to be zero-initialized according to
|
||||
// http://crbug.com/390941. Our pool is set up to zero-initialize new buffers.
|
||||
// TODO(nisse): Delete that feature from the video pool, instead add
|
||||
// an explicit call to InitializeData here.
|
||||
rtc::scoped_refptr<I420Buffer> frame_buffer =
|
||||
decoder->pool_.CreateBuffer(width, height);
|
||||
|
||||
int y_size = width * height;
|
||||
int uv_size = frame_buffer->ChromaWidth() * frame_buffer->ChromaHeight();
|
||||
// DCHECK that we have a continuous buffer as is required.
|
||||
RTC_DCHECK_EQ(frame_buffer->DataU(), frame_buffer->DataY() + y_size);
|
||||
RTC_DCHECK_EQ(frame_buffer->DataV(), frame_buffer->DataU() + uv_size);
|
||||
int total_size = y_size + 2 * uv_size;
|
||||
|
||||
av_frame->format = context->pix_fmt;
|
||||
av_frame->reordered_opaque = context->reordered_opaque;
|
||||
|
||||
// Set |av_frame| members as required by FFmpeg.
|
||||
av_frame->data[kYPlaneIndex] = frame_buffer->MutableDataY();
|
||||
av_frame->linesize[kYPlaneIndex] = frame_buffer->StrideY();
|
||||
av_frame->data[kUPlaneIndex] = frame_buffer->MutableDataU();
|
||||
av_frame->linesize[kUPlaneIndex] = frame_buffer->StrideU();
|
||||
av_frame->data[kVPlaneIndex] = frame_buffer->MutableDataV();
|
||||
av_frame->linesize[kVPlaneIndex] = frame_buffer->StrideV();
|
||||
RTC_DCHECK_EQ(av_frame->extended_data, av_frame->data);
|
||||
|
||||
// Create a VideoFrame object, to keep a reference to the buffer.
|
||||
// TODO(nisse): The VideoFrame's timestamp and rotation info is not used.
|
||||
// Refactor to do not use a VideoFrame object at all.
|
||||
av_frame->buf[0] = av_buffer_create(
|
||||
av_frame->data[kYPlaneIndex],
|
||||
total_size,
|
||||
AVFreeBuffer2,
|
||||
static_cast<void*>(new VideoFrame(frame_buffer,
|
||||
0 /* timestamp */,
|
||||
0 /* render_time_ms */,
|
||||
kVideoRotation_0)),
|
||||
0);
|
||||
RTC_CHECK(av_frame->buf[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void H264DecoderImpl::AVFreeBuffer2(void* opaque, uint8_t* data) {
|
||||
// The buffer pool recycles the buffer used by |video_frame| when there are no
|
||||
// more references to it. |video_frame| is a thin buffer holder and is not
|
||||
// recycled.
|
||||
VideoFrame* video_frame = static_cast<VideoFrame*>(opaque);
|
||||
delete video_frame;
|
||||
}
|
||||
|
||||
H264DecoderImpl::H264DecoderImpl() : pool_(true),
|
||||
decoded_image_callback_(nullptr),
|
||||
has_reported_init_(false),
|
||||
has_reported_error_(false) {
|
||||
}
|
||||
|
||||
H264DecoderImpl::~H264DecoderImpl() {
|
||||
Release();
|
||||
}
|
||||
|
||||
int32_t H264DecoderImpl::InitDecode(const VideoCodec* codec_settings,
|
||||
int32_t number_of_cores) {
|
||||
ReportInit();
|
||||
if (codec_settings &&
|
||||
codec_settings->codecType != kVideoCodecH264) {
|
||||
ReportError();
|
||||
return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
|
||||
}
|
||||
|
||||
// FFmpeg must have been initialized (with |av_lockmgr_register| and
|
||||
// |av_register_all|) before we proceed. |InitializeFFmpeg| does this, which
|
||||
// makes sense for WebRTC standalone. In other cases, such as Chromium, FFmpeg
|
||||
// is initialized externally and calling |InitializeFFmpeg| would be
|
||||
// thread-unsafe and result in FFmpeg being initialized twice, which could
|
||||
// break other FFmpeg usage. See the |rtc_initialize_ffmpeg| flag.
|
||||
#if defined(WEBRTC_INITIALIZE_FFMPEG)
|
||||
// Make sure FFmpeg has been initialized. Subsequent |InitializeFFmpeg| calls
|
||||
// do nothing.
|
||||
InitializeFFmpeg();
|
||||
#endif
|
||||
|
||||
// Release necessary in case of re-initializing.
|
||||
int32_t ret = Release();
|
||||
if (ret != WEBRTC_VIDEO_CODEC_OK) {
|
||||
ReportError();
|
||||
return ret;
|
||||
}
|
||||
RTC_DCHECK(!av_context_);
|
||||
|
||||
// Initialize AVCodecContext.
|
||||
av_context_.reset(avcodec_alloc_context3(nullptr));
|
||||
|
||||
av_context_->codec_type = AVMEDIA_TYPE_VIDEO;
|
||||
av_context_->codec_id = AV_CODEC_ID_H264;
|
||||
if (codec_settings) {
|
||||
av_context_->coded_width = codec_settings->width;
|
||||
av_context_->coded_height = codec_settings->height;
|
||||
}
|
||||
av_context_->pix_fmt = kPixelFormat;
|
||||
av_context_->extradata = nullptr;
|
||||
av_context_->extradata_size = 0;
|
||||
|
||||
// If this is ever increased, look at |av_context_->thread_safe_callbacks| and
|
||||
// make it possible to disable the thread checker in the frame buffer pool.
|
||||
av_context_->thread_count = 1;
|
||||
av_context_->thread_type = FF_THREAD_SLICE;
|
||||
|
||||
// Function used by FFmpeg to get buffers to store decoded frames in.
|
||||
av_context_->get_buffer2 = AVGetBuffer2;
|
||||
// |get_buffer2| is called with the context, there |opaque| can be used to get
|
||||
// a pointer |this|.
|
||||
av_context_->opaque = this;
|
||||
// Use ref counted frames (av_frame_unref).
|
||||
av_context_->refcounted_frames = 1; // true
|
||||
|
||||
AVCodec* codec = avcodec_find_decoder(av_context_->codec_id);
|
||||
if (!codec) {
|
||||
// This is an indication that FFmpeg has not been initialized or it has not
|
||||
// been compiled/initialized with the correct set of codecs.
|
||||
LOG(LS_ERROR) << "FFmpeg H.264 decoder not found.";
|
||||
Release();
|
||||
ReportError();
|
||||
return WEBRTC_VIDEO_CODEC_ERROR;
|
||||
}
|
||||
int res = avcodec_open2(av_context_.get(), codec, nullptr);
|
||||
if (res < 0) {
|
||||
LOG(LS_ERROR) << "avcodec_open2 error: " << res;
|
||||
Release();
|
||||
ReportError();
|
||||
return WEBRTC_VIDEO_CODEC_ERROR;
|
||||
}
|
||||
|
||||
av_frame_.reset(av_frame_alloc());
|
||||
return WEBRTC_VIDEO_CODEC_OK;
|
||||
}
|
||||
|
||||
int32_t H264DecoderImpl::Release() {
|
||||
av_context_.reset();
|
||||
av_frame_.reset();
|
||||
return WEBRTC_VIDEO_CODEC_OK;
|
||||
}
|
||||
|
||||
int32_t H264DecoderImpl::RegisterDecodeCompleteCallback(
|
||||
DecodedImageCallback* callback) {
|
||||
decoded_image_callback_ = callback;
|
||||
return WEBRTC_VIDEO_CODEC_OK;
|
||||
}
|
||||
|
||||
int32_t H264DecoderImpl::Decode(const EncodedImage& input_image,
|
||||
bool /*missing_frames*/,
|
||||
const RTPFragmentationHeader* /*fragmentation*/,
|
||||
const CodecSpecificInfo* codec_specific_info,
|
||||
int64_t /*render_time_ms*/) {
|
||||
if (!IsInitialized()) {
|
||||
ReportError();
|
||||
return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
|
||||
}
|
||||
if (!decoded_image_callback_) {
|
||||
LOG(LS_WARNING) << "InitDecode() has been called, but a callback function "
|
||||
"has not been set with RegisterDecodeCompleteCallback()";
|
||||
ReportError();
|
||||
return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
|
||||
}
|
||||
if (!input_image._buffer || !input_image._length) {
|
||||
ReportError();
|
||||
return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
|
||||
}
|
||||
if (codec_specific_info &&
|
||||
codec_specific_info->codecType != kVideoCodecH264) {
|
||||
ReportError();
|
||||
return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
|
||||
}
|
||||
|
||||
// FFmpeg requires padding due to some optimized bitstream readers reading 32
|
||||
// or 64 bits at once and could read over the end. See avcodec_decode_video2.
|
||||
RTC_CHECK_GE(input_image._size, input_image._length +
|
||||
EncodedImage::GetBufferPaddingBytes(kVideoCodecH264));
|
||||
// "If the first 23 bits of the additional bytes are not 0, then damaged MPEG
|
||||
// bitstreams could cause overread and segfault." See
|
||||
// AV_INPUT_BUFFER_PADDING_SIZE. We'll zero the entire padding just in case.
|
||||
memset(input_image._buffer + input_image._length,
|
||||
0,
|
||||
EncodedImage::GetBufferPaddingBytes(kVideoCodecH264));
|
||||
|
||||
AVPacket packet;
|
||||
av_init_packet(&packet);
|
||||
packet.data = input_image._buffer;
|
||||
if (input_image._length >
|
||||
static_cast<size_t>(std::numeric_limits<int>::max())) {
|
||||
ReportError();
|
||||
return WEBRTC_VIDEO_CODEC_ERROR;
|
||||
}
|
||||
packet.size = static_cast<int>(input_image._length);
|
||||
av_context_->reordered_opaque = input_image.ntp_time_ms_ * 1000; // ms -> μs
|
||||
|
||||
int frame_decoded = 0;
|
||||
int result = avcodec_decode_video2(av_context_.get(),
|
||||
av_frame_.get(),
|
||||
&frame_decoded,
|
||||
&packet);
|
||||
if (result < 0) {
|
||||
LOG(LS_ERROR) << "avcodec_decode_video2 error: " << result;
|
||||
ReportError();
|
||||
return WEBRTC_VIDEO_CODEC_ERROR;
|
||||
}
|
||||
// |result| is number of bytes used, which should be all of them.
|
||||
if (result != packet.size) {
|
||||
LOG(LS_ERROR) << "avcodec_decode_video2 consumed " << result << " bytes "
|
||||
"when " << packet.size << " bytes were expected.";
|
||||
ReportError();
|
||||
return WEBRTC_VIDEO_CODEC_ERROR;
|
||||
}
|
||||
|
||||
if (!frame_decoded) {
|
||||
LOG(LS_WARNING) << "avcodec_decode_video2 successful but no frame was "
|
||||
"decoded.";
|
||||
return WEBRTC_VIDEO_CODEC_OK;
|
||||
}
|
||||
|
||||
// Obtain the |video_frame| containing the decoded image.
|
||||
VideoFrame* video_frame = static_cast<VideoFrame*>(
|
||||
av_buffer_get_opaque(av_frame_->buf[0]));
|
||||
RTC_DCHECK(video_frame);
|
||||
rtc::scoped_refptr<webrtc::I420BufferInterface> i420_buffer =
|
||||
video_frame->video_frame_buffer()->GetI420();
|
||||
RTC_CHECK_EQ(av_frame_->data[kYPlaneIndex], i420_buffer->DataY());
|
||||
RTC_CHECK_EQ(av_frame_->data[kUPlaneIndex], i420_buffer->DataU());
|
||||
RTC_CHECK_EQ(av_frame_->data[kVPlaneIndex], i420_buffer->DataV());
|
||||
video_frame->set_timestamp(input_image._timeStamp);
|
||||
|
||||
rtc::Optional<uint8_t> qp;
|
||||
// TODO(sakal): Maybe it is possible to get QP directly from FFmpeg.
|
||||
h264_bitstream_parser_.ParseBitstream(input_image._buffer,
|
||||
input_image._length);
|
||||
int qp_int;
|
||||
if (h264_bitstream_parser_.GetLastSliceQp(&qp_int)) {
|
||||
qp.emplace(qp_int);
|
||||
}
|
||||
|
||||
// The decoded image may be larger than what is supposed to be visible, see
|
||||
// |AVGetBuffer2|'s use of |avcodec_align_dimensions|. This crops the image
|
||||
// without copying the underlying buffer.
|
||||
if (av_frame_->width != i420_buffer->width() ||
|
||||
av_frame_->height != i420_buffer->height()) {
|
||||
rtc::scoped_refptr<VideoFrameBuffer> cropped_buf(
|
||||
new rtc::RefCountedObject<WrappedI420Buffer>(
|
||||
av_frame_->width, av_frame_->height,
|
||||
i420_buffer->DataY(), i420_buffer->StrideY(),
|
||||
i420_buffer->DataU(), i420_buffer->StrideU(),
|
||||
i420_buffer->DataV(), i420_buffer->StrideV(),
|
||||
rtc::KeepRefUntilDone(i420_buffer)));
|
||||
VideoFrame cropped_frame(
|
||||
cropped_buf, video_frame->timestamp(), video_frame->render_time_ms(),
|
||||
video_frame->rotation());
|
||||
// TODO(nisse): Timestamp and rotation are all zero here. Change decoder
|
||||
// interface to pass a VideoFrameBuffer instead of a VideoFrame?
|
||||
decoded_image_callback_->Decoded(cropped_frame, rtc::Optional<int32_t>(),
|
||||
qp);
|
||||
} else {
|
||||
// Return decoded frame.
|
||||
decoded_image_callback_->Decoded(*video_frame, rtc::Optional<int32_t>(),
|
||||
qp);
|
||||
}
|
||||
// Stop referencing it, possibly freeing |video_frame|.
|
||||
av_frame_unref(av_frame_.get());
|
||||
video_frame = nullptr;
|
||||
|
||||
return WEBRTC_VIDEO_CODEC_OK;
|
||||
}
|
||||
|
||||
const char* H264DecoderImpl::ImplementationName() const {
|
||||
return "FFmpeg";
|
||||
}
|
||||
|
||||
bool H264DecoderImpl::IsInitialized() const {
|
||||
return av_context_ != nullptr;
|
||||
}
|
||||
|
||||
void H264DecoderImpl::ReportInit() {
|
||||
if (has_reported_init_)
|
||||
return;
|
||||
RTC_HISTOGRAM_ENUMERATION("WebRTC.Video.H264DecoderImpl.Event",
|
||||
kH264DecoderEventInit,
|
||||
kH264DecoderEventMax);
|
||||
has_reported_init_ = true;
|
||||
}
|
||||
|
||||
void H264DecoderImpl::ReportError() {
|
||||
if (has_reported_error_)
|
||||
return;
|
||||
RTC_HISTOGRAM_ENUMERATION("WebRTC.Video.H264DecoderImpl.Event",
|
||||
kH264DecoderEventError,
|
||||
kH264DecoderEventMax);
|
||||
has_reported_error_ = true;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
87
modules/video_coding/codecs/h264/h264_decoder_impl.h
Normal file
87
modules/video_coding/codecs/h264/h264_decoder_impl.h
Normal file
@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_VIDEO_CODING_CODECS_H264_H264_DECODER_IMPL_H_
|
||||
#define WEBRTC_MODULES_VIDEO_CODING_CODECS_H264_H264_DECODER_IMPL_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "webrtc/modules/video_coding/codecs/h264/include/h264.h"
|
||||
|
||||
extern "C" {
|
||||
#include "third_party/ffmpeg/libavcodec/avcodec.h"
|
||||
} // extern "C"
|
||||
|
||||
#include "webrtc/common_video/h264/h264_bitstream_parser.h"
|
||||
#include "webrtc/common_video/include/i420_buffer_pool.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
struct AVCodecContextDeleter {
|
||||
void operator()(AVCodecContext* ptr) const { avcodec_free_context(&ptr); }
|
||||
};
|
||||
struct AVFrameDeleter {
|
||||
void operator()(AVFrame* ptr) const { av_frame_free(&ptr); }
|
||||
};
|
||||
|
||||
class H264DecoderImpl : public H264Decoder {
|
||||
public:
|
||||
H264DecoderImpl();
|
||||
~H264DecoderImpl() override;
|
||||
|
||||
// If |codec_settings| is NULL it is ignored. If it is not NULL,
|
||||
// |codec_settings->codecType| must be |kVideoCodecH264|.
|
||||
int32_t InitDecode(const VideoCodec* codec_settings,
|
||||
int32_t number_of_cores) override;
|
||||
int32_t Release() override;
|
||||
|
||||
int32_t RegisterDecodeCompleteCallback(
|
||||
DecodedImageCallback* callback) override;
|
||||
|
||||
// |missing_frames|, |fragmentation| and |render_time_ms| are ignored.
|
||||
int32_t Decode(const EncodedImage& input_image,
|
||||
bool /*missing_frames*/,
|
||||
const RTPFragmentationHeader* /*fragmentation*/,
|
||||
const CodecSpecificInfo* codec_specific_info = nullptr,
|
||||
int64_t render_time_ms = -1) override;
|
||||
|
||||
const char* ImplementationName() const override;
|
||||
|
||||
private:
|
||||
// Called by FFmpeg when it needs a frame buffer to store decoded frames in.
|
||||
// The |VideoFrame| returned by FFmpeg at |Decode| originate from here. Their
|
||||
// buffers are reference counted and freed by FFmpeg using |AVFreeBuffer2|.
|
||||
static int AVGetBuffer2(
|
||||
AVCodecContext* context, AVFrame* av_frame, int flags);
|
||||
// Called by FFmpeg when it is done with a video frame, see |AVGetBuffer2|.
|
||||
static void AVFreeBuffer2(void* opaque, uint8_t* data);
|
||||
|
||||
bool IsInitialized() const;
|
||||
|
||||
// Reports statistics with histograms.
|
||||
void ReportInit();
|
||||
void ReportError();
|
||||
|
||||
I420BufferPool pool_;
|
||||
std::unique_ptr<AVCodecContext, AVCodecContextDeleter> av_context_;
|
||||
std::unique_ptr<AVFrame, AVFrameDeleter> av_frame_;
|
||||
|
||||
DecodedImageCallback* decoded_image_callback_;
|
||||
|
||||
bool has_reported_init_;
|
||||
bool has_reported_error_;
|
||||
|
||||
webrtc::H264BitstreamParser h264_bitstream_parser_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_VIDEO_CODING_CODECS_H264_H264_DECODER_IMPL_H_
|
||||
507
modules/video_coding/codecs/h264/h264_encoder_impl.cc
Normal file
507
modules/video_coding/codecs/h264/h264_encoder_impl.cc
Normal file
@ -0,0 +1,507 @@
|
||||
/*
|
||||
* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "webrtc/modules/video_coding/codecs/h264/h264_encoder_impl.h"
|
||||
|
||||
#include <limits>
|
||||
#include <string>
|
||||
|
||||
#include "third_party/openh264/src/codec/api/svc/codec_api.h"
|
||||
#include "third_party/openh264/src/codec/api/svc/codec_app_def.h"
|
||||
#include "third_party/openh264/src/codec/api/svc/codec_def.h"
|
||||
#include "third_party/openh264/src/codec/api/svc/codec_ver.h"
|
||||
|
||||
#include "webrtc/common_video/libyuv/include/webrtc_libyuv.h"
|
||||
#include "webrtc/rtc_base/checks.h"
|
||||
#include "webrtc/rtc_base/logging.h"
|
||||
#include "webrtc/rtc_base/timeutils.h"
|
||||
#include "webrtc/system_wrappers/include/metrics.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
|
||||
const bool kOpenH264EncoderDetailedLogging = false;
|
||||
|
||||
// Used by histograms. Values of entries should not be changed.
|
||||
enum H264EncoderImplEvent {
|
||||
kH264EncoderEventInit = 0,
|
||||
kH264EncoderEventError = 1,
|
||||
kH264EncoderEventMax = 16,
|
||||
};
|
||||
|
||||
int NumberOfThreads(int width, int height, int number_of_cores) {
|
||||
// TODO(hbos): In Chromium, multiple threads do not work with sandbox on Mac,
|
||||
// see crbug.com/583348. Until further investigated, only use one thread.
|
||||
// if (width * height >= 1920 * 1080 && number_of_cores > 8) {
|
||||
// return 8; // 8 threads for 1080p on high perf machines.
|
||||
// } else if (width * height > 1280 * 960 && number_of_cores >= 6) {
|
||||
// return 3; // 3 threads for 1080p.
|
||||
// } else if (width * height > 640 * 480 && number_of_cores >= 3) {
|
||||
// return 2; // 2 threads for qHD/HD.
|
||||
// } else {
|
||||
// return 1; // 1 thread for VGA or less.
|
||||
// }
|
||||
// TODO(sprang): Also check sSliceArgument.uiSliceNum om GetEncoderPrams(),
|
||||
// before enabling multithreading here.
|
||||
return 1;
|
||||
}
|
||||
|
||||
FrameType ConvertToVideoFrameType(EVideoFrameType type) {
|
||||
switch (type) {
|
||||
case videoFrameTypeIDR:
|
||||
return kVideoFrameKey;
|
||||
case videoFrameTypeSkip:
|
||||
case videoFrameTypeI:
|
||||
case videoFrameTypeP:
|
||||
case videoFrameTypeIPMixed:
|
||||
return kVideoFrameDelta;
|
||||
case videoFrameTypeInvalid:
|
||||
break;
|
||||
}
|
||||
RTC_NOTREACHED() << "Unexpected/invalid frame type: " << type;
|
||||
return kEmptyFrame;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Helper method used by H264EncoderImpl::Encode.
|
||||
// Copies the encoded bytes from |info| to |encoded_image| and updates the
|
||||
// fragmentation information of |frag_header|. The |encoded_image->_buffer| may
|
||||
// be deleted and reallocated if a bigger buffer is required.
|
||||
//
|
||||
// After OpenH264 encoding, the encoded bytes are stored in |info| spread out
|
||||
// over a number of layers and "NAL units". Each NAL unit is a fragment starting
|
||||
// with the four-byte start code {0,0,0,1}. All of this data (including the
|
||||
// start codes) is copied to the |encoded_image->_buffer| and the |frag_header|
|
||||
// is updated to point to each fragment, with offsets and lengths set as to
|
||||
// exclude the start codes.
|
||||
static void RtpFragmentize(EncodedImage* encoded_image,
|
||||
std::unique_ptr<uint8_t[]>* encoded_image_buffer,
|
||||
const VideoFrameBuffer& frame_buffer,
|
||||
SFrameBSInfo* info,
|
||||
RTPFragmentationHeader* frag_header) {
|
||||
// Calculate minimum buffer size required to hold encoded data.
|
||||
size_t required_size = 0;
|
||||
size_t fragments_count = 0;
|
||||
for (int layer = 0; layer < info->iLayerNum; ++layer) {
|
||||
const SLayerBSInfo& layerInfo = info->sLayerInfo[layer];
|
||||
for (int nal = 0; nal < layerInfo.iNalCount; ++nal, ++fragments_count) {
|
||||
RTC_CHECK_GE(layerInfo.pNalLengthInByte[nal], 0);
|
||||
// Ensure |required_size| will not overflow.
|
||||
RTC_CHECK_LE(layerInfo.pNalLengthInByte[nal],
|
||||
std::numeric_limits<size_t>::max() - required_size);
|
||||
required_size += layerInfo.pNalLengthInByte[nal];
|
||||
}
|
||||
}
|
||||
if (encoded_image->_size < required_size) {
|
||||
// Increase buffer size. Allocate enough to hold an unencoded image, this
|
||||
// should be more than enough to hold any encoded data of future frames of
|
||||
// the same size (avoiding possible future reallocation due to variations in
|
||||
// required size).
|
||||
encoded_image->_size = CalcBufferSize(
|
||||
VideoType::kI420, frame_buffer.width(), frame_buffer.height());
|
||||
if (encoded_image->_size < required_size) {
|
||||
// Encoded data > unencoded data. Allocate required bytes.
|
||||
LOG(LS_WARNING) << "Encoding produced more bytes than the original image "
|
||||
<< "data! Original bytes: " << encoded_image->_size
|
||||
<< ", encoded bytes: " << required_size << ".";
|
||||
encoded_image->_size = required_size;
|
||||
}
|
||||
encoded_image->_buffer = new uint8_t[encoded_image->_size];
|
||||
encoded_image_buffer->reset(encoded_image->_buffer);
|
||||
}
|
||||
|
||||
// Iterate layers and NAL units, note each NAL unit as a fragment and copy
|
||||
// the data to |encoded_image->_buffer|.
|
||||
const uint8_t start_code[4] = {0, 0, 0, 1};
|
||||
frag_header->VerifyAndAllocateFragmentationHeader(fragments_count);
|
||||
size_t frag = 0;
|
||||
encoded_image->_length = 0;
|
||||
for (int layer = 0; layer < info->iLayerNum; ++layer) {
|
||||
const SLayerBSInfo& layerInfo = info->sLayerInfo[layer];
|
||||
// Iterate NAL units making up this layer, noting fragments.
|
||||
size_t layer_len = 0;
|
||||
for (int nal = 0; nal < layerInfo.iNalCount; ++nal, ++frag) {
|
||||
// Because the sum of all layer lengths, |required_size|, fits in a
|
||||
// |size_t|, we know that any indices in-between will not overflow.
|
||||
RTC_DCHECK_GE(layerInfo.pNalLengthInByte[nal], 4);
|
||||
RTC_DCHECK_EQ(layerInfo.pBsBuf[layer_len+0], start_code[0]);
|
||||
RTC_DCHECK_EQ(layerInfo.pBsBuf[layer_len+1], start_code[1]);
|
||||
RTC_DCHECK_EQ(layerInfo.pBsBuf[layer_len+2], start_code[2]);
|
||||
RTC_DCHECK_EQ(layerInfo.pBsBuf[layer_len+3], start_code[3]);
|
||||
frag_header->fragmentationOffset[frag] =
|
||||
encoded_image->_length + layer_len + sizeof(start_code);
|
||||
frag_header->fragmentationLength[frag] =
|
||||
layerInfo.pNalLengthInByte[nal] - sizeof(start_code);
|
||||
layer_len += layerInfo.pNalLengthInByte[nal];
|
||||
}
|
||||
// Copy the entire layer's data (including start codes).
|
||||
memcpy(encoded_image->_buffer + encoded_image->_length,
|
||||
layerInfo.pBsBuf,
|
||||
layer_len);
|
||||
encoded_image->_length += layer_len;
|
||||
}
|
||||
}
|
||||
|
||||
H264EncoderImpl::H264EncoderImpl(const cricket::VideoCodec& codec)
|
||||
: openh264_encoder_(nullptr),
|
||||
width_(0),
|
||||
height_(0),
|
||||
max_frame_rate_(0.0f),
|
||||
target_bps_(0),
|
||||
max_bps_(0),
|
||||
mode_(kRealtimeVideo),
|
||||
frame_dropping_on_(false),
|
||||
key_frame_interval_(0),
|
||||
packetization_mode_(H264PacketizationMode::SingleNalUnit),
|
||||
max_payload_size_(0),
|
||||
number_of_cores_(0),
|
||||
encoded_image_callback_(nullptr),
|
||||
has_reported_init_(false),
|
||||
has_reported_error_(false) {
|
||||
RTC_CHECK(cricket::CodecNamesEq(codec.name, cricket::kH264CodecName));
|
||||
std::string packetization_mode_string;
|
||||
if (codec.GetParam(cricket::kH264FmtpPacketizationMode,
|
||||
&packetization_mode_string) &&
|
||||
packetization_mode_string == "1") {
|
||||
packetization_mode_ = H264PacketizationMode::NonInterleaved;
|
||||
}
|
||||
}
|
||||
|
||||
H264EncoderImpl::~H264EncoderImpl() {
|
||||
Release();
|
||||
}
|
||||
|
||||
int32_t H264EncoderImpl::InitEncode(const VideoCodec* codec_settings,
|
||||
int32_t number_of_cores,
|
||||
size_t max_payload_size) {
|
||||
ReportInit();
|
||||
if (!codec_settings ||
|
||||
codec_settings->codecType != kVideoCodecH264) {
|
||||
ReportError();
|
||||
return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
|
||||
}
|
||||
if (codec_settings->maxFramerate == 0) {
|
||||
ReportError();
|
||||
return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
|
||||
}
|
||||
if (codec_settings->width < 1 || codec_settings->height < 1) {
|
||||
ReportError();
|
||||
return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
|
||||
}
|
||||
|
||||
int32_t release_ret = Release();
|
||||
if (release_ret != WEBRTC_VIDEO_CODEC_OK) {
|
||||
ReportError();
|
||||
return release_ret;
|
||||
}
|
||||
RTC_DCHECK(!openh264_encoder_);
|
||||
|
||||
// Create encoder.
|
||||
if (WelsCreateSVCEncoder(&openh264_encoder_) != 0) {
|
||||
// Failed to create encoder.
|
||||
LOG(LS_ERROR) << "Failed to create OpenH264 encoder";
|
||||
RTC_DCHECK(!openh264_encoder_);
|
||||
ReportError();
|
||||
return WEBRTC_VIDEO_CODEC_ERROR;
|
||||
}
|
||||
RTC_DCHECK(openh264_encoder_);
|
||||
if (kOpenH264EncoderDetailedLogging) {
|
||||
int trace_level = WELS_LOG_DETAIL;
|
||||
openh264_encoder_->SetOption(ENCODER_OPTION_TRACE_LEVEL,
|
||||
&trace_level);
|
||||
}
|
||||
// else WELS_LOG_DEFAULT is used by default.
|
||||
|
||||
number_of_cores_ = number_of_cores;
|
||||
// Set internal settings from codec_settings
|
||||
width_ = codec_settings->width;
|
||||
height_ = codec_settings->height;
|
||||
max_frame_rate_ = static_cast<float>(codec_settings->maxFramerate);
|
||||
mode_ = codec_settings->mode;
|
||||
frame_dropping_on_ = codec_settings->H264().frameDroppingOn;
|
||||
key_frame_interval_ = codec_settings->H264().keyFrameInterval;
|
||||
max_payload_size_ = max_payload_size;
|
||||
|
||||
// Codec_settings uses kbits/second; encoder uses bits/second.
|
||||
max_bps_ = codec_settings->maxBitrate * 1000;
|
||||
if (codec_settings->targetBitrate == 0)
|
||||
target_bps_ = codec_settings->startBitrate * 1000;
|
||||
else
|
||||
target_bps_ = codec_settings->targetBitrate * 1000;
|
||||
|
||||
SEncParamExt encoder_params = CreateEncoderParams();
|
||||
|
||||
// Initialize.
|
||||
if (openh264_encoder_->InitializeExt(&encoder_params) != 0) {
|
||||
LOG(LS_ERROR) << "Failed to initialize OpenH264 encoder";
|
||||
Release();
|
||||
ReportError();
|
||||
return WEBRTC_VIDEO_CODEC_ERROR;
|
||||
}
|
||||
// TODO(pbos): Base init params on these values before submitting.
|
||||
int video_format = EVideoFormatType::videoFormatI420;
|
||||
openh264_encoder_->SetOption(ENCODER_OPTION_DATAFORMAT,
|
||||
&video_format);
|
||||
|
||||
// Initialize encoded image. Default buffer size: size of unencoded data.
|
||||
encoded_image_._size = CalcBufferSize(VideoType::kI420, codec_settings->width,
|
||||
codec_settings->height);
|
||||
encoded_image_._buffer = new uint8_t[encoded_image_._size];
|
||||
encoded_image_buffer_.reset(encoded_image_._buffer);
|
||||
encoded_image_._completeFrame = true;
|
||||
encoded_image_._encodedWidth = 0;
|
||||
encoded_image_._encodedHeight = 0;
|
||||
encoded_image_._length = 0;
|
||||
return WEBRTC_VIDEO_CODEC_OK;
|
||||
}
|
||||
|
||||
int32_t H264EncoderImpl::Release() {
|
||||
if (openh264_encoder_) {
|
||||
RTC_CHECK_EQ(0, openh264_encoder_->Uninitialize());
|
||||
WelsDestroySVCEncoder(openh264_encoder_);
|
||||
openh264_encoder_ = nullptr;
|
||||
}
|
||||
encoded_image_._buffer = nullptr;
|
||||
encoded_image_buffer_.reset();
|
||||
return WEBRTC_VIDEO_CODEC_OK;
|
||||
}
|
||||
|
||||
int32_t H264EncoderImpl::RegisterEncodeCompleteCallback(
|
||||
EncodedImageCallback* callback) {
|
||||
encoded_image_callback_ = callback;
|
||||
return WEBRTC_VIDEO_CODEC_OK;
|
||||
}
|
||||
|
||||
int32_t H264EncoderImpl::SetRateAllocation(
|
||||
const BitrateAllocation& bitrate_allocation,
|
||||
uint32_t framerate) {
|
||||
if (bitrate_allocation.get_sum_bps() <= 0 || framerate <= 0)
|
||||
return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
|
||||
|
||||
target_bps_ = bitrate_allocation.get_sum_bps();
|
||||
max_frame_rate_ = static_cast<float>(framerate);
|
||||
|
||||
SBitrateInfo target_bitrate;
|
||||
memset(&target_bitrate, 0, sizeof(SBitrateInfo));
|
||||
target_bitrate.iLayer = SPATIAL_LAYER_ALL,
|
||||
target_bitrate.iBitrate = target_bps_;
|
||||
openh264_encoder_->SetOption(ENCODER_OPTION_BITRATE,
|
||||
&target_bitrate);
|
||||
openh264_encoder_->SetOption(ENCODER_OPTION_FRAME_RATE, &max_frame_rate_);
|
||||
return WEBRTC_VIDEO_CODEC_OK;
|
||||
}
|
||||
|
||||
int32_t H264EncoderImpl::Encode(const VideoFrame& input_frame,
|
||||
const CodecSpecificInfo* codec_specific_info,
|
||||
const std::vector<FrameType>* frame_types) {
|
||||
if (!IsInitialized()) {
|
||||
ReportError();
|
||||
return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
|
||||
}
|
||||
if (!encoded_image_callback_) {
|
||||
LOG(LS_WARNING) << "InitEncode() has been called, but a callback function "
|
||||
<< "has not been set with RegisterEncodeCompleteCallback()";
|
||||
ReportError();
|
||||
return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
|
||||
}
|
||||
|
||||
bool force_key_frame = false;
|
||||
if (frame_types != nullptr) {
|
||||
// We only support a single stream.
|
||||
RTC_DCHECK_EQ(frame_types->size(), 1);
|
||||
// Skip frame?
|
||||
if ((*frame_types)[0] == kEmptyFrame) {
|
||||
return WEBRTC_VIDEO_CODEC_OK;
|
||||
}
|
||||
// Force key frame?
|
||||
force_key_frame = (*frame_types)[0] == kVideoFrameKey;
|
||||
}
|
||||
if (force_key_frame) {
|
||||
// API doc says ForceIntraFrame(false) does nothing, but calling this
|
||||
// function forces a key frame regardless of the |bIDR| argument's value.
|
||||
// (If every frame is a key frame we get lag/delays.)
|
||||
openh264_encoder_->ForceIntraFrame(true);
|
||||
}
|
||||
rtc::scoped_refptr<const I420BufferInterface> frame_buffer =
|
||||
input_frame.video_frame_buffer()->ToI420();
|
||||
// EncodeFrame input.
|
||||
SSourcePicture picture;
|
||||
memset(&picture, 0, sizeof(SSourcePicture));
|
||||
picture.iPicWidth = frame_buffer->width();
|
||||
picture.iPicHeight = frame_buffer->height();
|
||||
picture.iColorFormat = EVideoFormatType::videoFormatI420;
|
||||
picture.uiTimeStamp = input_frame.ntp_time_ms();
|
||||
picture.iStride[0] = frame_buffer->StrideY();
|
||||
picture.iStride[1] = frame_buffer->StrideU();
|
||||
picture.iStride[2] = frame_buffer->StrideV();
|
||||
picture.pData[0] = const_cast<uint8_t*>(frame_buffer->DataY());
|
||||
picture.pData[1] = const_cast<uint8_t*>(frame_buffer->DataU());
|
||||
picture.pData[2] = const_cast<uint8_t*>(frame_buffer->DataV());
|
||||
|
||||
// EncodeFrame output.
|
||||
SFrameBSInfo info;
|
||||
memset(&info, 0, sizeof(SFrameBSInfo));
|
||||
|
||||
// Encode!
|
||||
int enc_ret = openh264_encoder_->EncodeFrame(&picture, &info);
|
||||
if (enc_ret != 0) {
|
||||
LOG(LS_ERROR) << "OpenH264 frame encoding failed, EncodeFrame returned "
|
||||
<< enc_ret << ".";
|
||||
ReportError();
|
||||
return WEBRTC_VIDEO_CODEC_ERROR;
|
||||
}
|
||||
|
||||
encoded_image_._encodedWidth = frame_buffer->width();
|
||||
encoded_image_._encodedHeight = frame_buffer->height();
|
||||
encoded_image_._timeStamp = input_frame.timestamp();
|
||||
encoded_image_.ntp_time_ms_ = input_frame.ntp_time_ms();
|
||||
encoded_image_.capture_time_ms_ = input_frame.render_time_ms();
|
||||
encoded_image_.rotation_ = input_frame.rotation();
|
||||
encoded_image_.content_type_ = (mode_ == kScreensharing)
|
||||
? VideoContentType::SCREENSHARE
|
||||
: VideoContentType::UNSPECIFIED;
|
||||
encoded_image_.timing_.flags = TimingFrameFlags::kInvalid;
|
||||
encoded_image_._frameType = ConvertToVideoFrameType(info.eFrameType);
|
||||
|
||||
// Split encoded image up into fragments. This also updates |encoded_image_|.
|
||||
RTPFragmentationHeader frag_header;
|
||||
RtpFragmentize(&encoded_image_, &encoded_image_buffer_, *frame_buffer, &info,
|
||||
&frag_header);
|
||||
|
||||
// Encoder can skip frames to save bandwidth in which case
|
||||
// |encoded_image_._length| == 0.
|
||||
if (encoded_image_._length > 0) {
|
||||
// Parse QP.
|
||||
h264_bitstream_parser_.ParseBitstream(encoded_image_._buffer,
|
||||
encoded_image_._length);
|
||||
h264_bitstream_parser_.GetLastSliceQp(&encoded_image_.qp_);
|
||||
|
||||
// Deliver encoded image.
|
||||
CodecSpecificInfo codec_specific;
|
||||
codec_specific.codecType = kVideoCodecH264;
|
||||
codec_specific.codecSpecific.H264.packetization_mode = packetization_mode_;
|
||||
encoded_image_callback_->OnEncodedImage(encoded_image_, &codec_specific,
|
||||
&frag_header);
|
||||
}
|
||||
return WEBRTC_VIDEO_CODEC_OK;
|
||||
}
|
||||
|
||||
const char* H264EncoderImpl::ImplementationName() const {
|
||||
return "OpenH264";
|
||||
}
|
||||
|
||||
bool H264EncoderImpl::IsInitialized() const {
|
||||
return openh264_encoder_ != nullptr;
|
||||
}
|
||||
|
||||
// Initialization parameters.
|
||||
// There are two ways to initialize. There is SEncParamBase (cleared with
|
||||
// memset(&p, 0, sizeof(SEncParamBase)) used in Initialize, and SEncParamExt
|
||||
// which is a superset of SEncParamBase (cleared with GetDefaultParams) used
|
||||
// in InitializeExt.
|
||||
SEncParamExt H264EncoderImpl::CreateEncoderParams() const {
|
||||
RTC_DCHECK(openh264_encoder_);
|
||||
SEncParamExt encoder_params;
|
||||
openh264_encoder_->GetDefaultParams(&encoder_params);
|
||||
if (mode_ == kRealtimeVideo) {
|
||||
encoder_params.iUsageType = CAMERA_VIDEO_REAL_TIME;
|
||||
} else if (mode_ == kScreensharing) {
|
||||
encoder_params.iUsageType = SCREEN_CONTENT_REAL_TIME;
|
||||
} else {
|
||||
RTC_NOTREACHED();
|
||||
}
|
||||
encoder_params.iPicWidth = width_;
|
||||
encoder_params.iPicHeight = height_;
|
||||
encoder_params.iTargetBitrate = target_bps_;
|
||||
encoder_params.iMaxBitrate = max_bps_;
|
||||
// Rate Control mode
|
||||
encoder_params.iRCMode = RC_BITRATE_MODE;
|
||||
encoder_params.fMaxFrameRate = max_frame_rate_;
|
||||
|
||||
// The following parameters are extension parameters (they're in SEncParamExt,
|
||||
// not in SEncParamBase).
|
||||
encoder_params.bEnableFrameSkip = frame_dropping_on_;
|
||||
// |uiIntraPeriod| - multiple of GOP size
|
||||
// |keyFrameInterval| - number of frames
|
||||
encoder_params.uiIntraPeriod = key_frame_interval_;
|
||||
encoder_params.uiMaxNalSize = 0;
|
||||
// Threading model: use auto.
|
||||
// 0: auto (dynamic imp. internal encoder)
|
||||
// 1: single thread (default value)
|
||||
// >1: number of threads
|
||||
encoder_params.iMultipleThreadIdc = NumberOfThreads(
|
||||
encoder_params.iPicWidth, encoder_params.iPicHeight, number_of_cores_);
|
||||
// The base spatial layer 0 is the only one we use.
|
||||
encoder_params.sSpatialLayers[0].iVideoWidth = encoder_params.iPicWidth;
|
||||
encoder_params.sSpatialLayers[0].iVideoHeight = encoder_params.iPicHeight;
|
||||
encoder_params.sSpatialLayers[0].fFrameRate = encoder_params.fMaxFrameRate;
|
||||
encoder_params.sSpatialLayers[0].iSpatialBitrate =
|
||||
encoder_params.iTargetBitrate;
|
||||
encoder_params.sSpatialLayers[0].iMaxSpatialBitrate =
|
||||
encoder_params.iMaxBitrate;
|
||||
LOG(INFO) << "OpenH264 version is " << OPENH264_MAJOR << "."
|
||||
<< OPENH264_MINOR;
|
||||
switch (packetization_mode_) {
|
||||
case H264PacketizationMode::SingleNalUnit:
|
||||
// Limit the size of the packets produced.
|
||||
encoder_params.sSpatialLayers[0].sSliceArgument.uiSliceNum = 1;
|
||||
encoder_params.sSpatialLayers[0].sSliceArgument.uiSliceMode =
|
||||
SM_SIZELIMITED_SLICE;
|
||||
encoder_params.sSpatialLayers[0].sSliceArgument.uiSliceSizeConstraint =
|
||||
static_cast<unsigned int>(max_payload_size_);
|
||||
break;
|
||||
case H264PacketizationMode::NonInterleaved:
|
||||
// When uiSliceMode = SM_FIXEDSLCNUM_SLICE, uiSliceNum = 0 means auto
|
||||
// design it with cpu core number.
|
||||
// TODO(sprang): Set to 0 when we understand why the rate controller borks
|
||||
// when uiSliceNum > 1.
|
||||
encoder_params.sSpatialLayers[0].sSliceArgument.uiSliceNum = 1;
|
||||
encoder_params.sSpatialLayers[0].sSliceArgument.uiSliceMode =
|
||||
SM_FIXEDSLCNUM_SLICE;
|
||||
break;
|
||||
}
|
||||
return encoder_params;
|
||||
}
|
||||
|
||||
void H264EncoderImpl::ReportInit() {
|
||||
if (has_reported_init_)
|
||||
return;
|
||||
RTC_HISTOGRAM_ENUMERATION("WebRTC.Video.H264EncoderImpl.Event",
|
||||
kH264EncoderEventInit,
|
||||
kH264EncoderEventMax);
|
||||
has_reported_init_ = true;
|
||||
}
|
||||
|
||||
void H264EncoderImpl::ReportError() {
|
||||
if (has_reported_error_)
|
||||
return;
|
||||
RTC_HISTOGRAM_ENUMERATION("WebRTC.Video.H264EncoderImpl.Event",
|
||||
kH264EncoderEventError,
|
||||
kH264EncoderEventMax);
|
||||
has_reported_error_ = true;
|
||||
}
|
||||
|
||||
int32_t H264EncoderImpl::SetChannelParameters(
|
||||
uint32_t packet_loss, int64_t rtt) {
|
||||
return WEBRTC_VIDEO_CODEC_OK;
|
||||
}
|
||||
|
||||
int32_t H264EncoderImpl::SetPeriodicKeyFrames(bool enable) {
|
||||
return WEBRTC_VIDEO_CODEC_OK;
|
||||
}
|
||||
|
||||
VideoEncoder::ScalingSettings H264EncoderImpl::GetScalingSettings() const {
|
||||
return VideoEncoder::ScalingSettings(true);
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
104
modules/video_coding/codecs/h264/h264_encoder_impl.h
Normal file
104
modules/video_coding/codecs/h264/h264_encoder_impl.h
Normal file
@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_VIDEO_CODING_CODECS_H264_H264_ENCODER_IMPL_H_
|
||||
#define WEBRTC_MODULES_VIDEO_CODING_CODECS_H264_H264_ENCODER_IMPL_H_
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/common_video/h264/h264_bitstream_parser.h"
|
||||
#include "webrtc/modules/video_coding/codecs/h264/include/h264.h"
|
||||
#include "webrtc/modules/video_coding/utility/quality_scaler.h"
|
||||
|
||||
#include "third_party/openh264/src/codec/api/svc/codec_app_def.h"
|
||||
|
||||
class ISVCEncoder;
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class H264EncoderImpl : public H264Encoder {
|
||||
public:
|
||||
explicit H264EncoderImpl(const cricket::VideoCodec& codec);
|
||||
~H264EncoderImpl() override;
|
||||
|
||||
// |max_payload_size| is ignored.
|
||||
// The following members of |codec_settings| are used. The rest are ignored.
|
||||
// - codecType (must be kVideoCodecH264)
|
||||
// - targetBitrate
|
||||
// - maxFramerate
|
||||
// - width
|
||||
// - height
|
||||
int32_t InitEncode(const VideoCodec* codec_settings,
|
||||
int32_t number_of_cores,
|
||||
size_t max_payload_size) override;
|
||||
int32_t Release() override;
|
||||
|
||||
int32_t RegisterEncodeCompleteCallback(
|
||||
EncodedImageCallback* callback) override;
|
||||
int32_t SetRateAllocation(const BitrateAllocation& bitrate_allocation,
|
||||
uint32_t framerate) override;
|
||||
|
||||
// The result of encoding - an EncodedImage and RTPFragmentationHeader - are
|
||||
// passed to the encode complete callback.
|
||||
int32_t Encode(const VideoFrame& frame,
|
||||
const CodecSpecificInfo* codec_specific_info,
|
||||
const std::vector<FrameType>* frame_types) override;
|
||||
|
||||
const char* ImplementationName() const override;
|
||||
|
||||
VideoEncoder::ScalingSettings GetScalingSettings() const override;
|
||||
|
||||
// Unsupported / Do nothing.
|
||||
int32_t SetChannelParameters(uint32_t packet_loss, int64_t rtt) override;
|
||||
int32_t SetPeriodicKeyFrames(bool enable) override;
|
||||
|
||||
// Exposed for testing.
|
||||
H264PacketizationMode PacketizationModeForTesting() const {
|
||||
return packetization_mode_;
|
||||
}
|
||||
|
||||
private:
|
||||
bool IsInitialized() const;
|
||||
SEncParamExt CreateEncoderParams() const;
|
||||
|
||||
webrtc::H264BitstreamParser h264_bitstream_parser_;
|
||||
// Reports statistics with histograms.
|
||||
void ReportInit();
|
||||
void ReportError();
|
||||
|
||||
ISVCEncoder* openh264_encoder_;
|
||||
// Settings that are used by this encoder.
|
||||
int width_;
|
||||
int height_;
|
||||
float max_frame_rate_;
|
||||
uint32_t target_bps_;
|
||||
uint32_t max_bps_;
|
||||
VideoCodecMode mode_;
|
||||
// H.264 specifc parameters
|
||||
bool frame_dropping_on_;
|
||||
int key_frame_interval_;
|
||||
H264PacketizationMode packetization_mode_;
|
||||
|
||||
size_t max_payload_size_;
|
||||
int32_t number_of_cores_;
|
||||
|
||||
EncodedImage encoded_image_;
|
||||
std::unique_ptr<uint8_t[]> encoded_image_buffer_;
|
||||
EncodedImageCallback* encoded_image_callback_;
|
||||
|
||||
bool has_reported_init_;
|
||||
bool has_reported_error_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_VIDEO_CODING_CODECS_H264_H264_ENCODER_IMPL_H_
|
||||
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "webrtc/modules/video_coding/codecs/h264/h264_encoder_impl.h"
|
||||
|
||||
#include "webrtc/test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
|
||||
const int kMaxPayloadSize = 1024;
|
||||
const int kNumCores = 1;
|
||||
|
||||
void SetDefaultSettings(VideoCodec* codec_settings) {
|
||||
codec_settings->codecType = kVideoCodecH264;
|
||||
codec_settings->maxFramerate = 60;
|
||||
codec_settings->width = 640;
|
||||
codec_settings->height = 480;
|
||||
// If frame dropping is false, we get a warning that bitrate can't
|
||||
// be controlled for RC_QUALITY_MODE; RC_BITRATE_MODE and RC_TIMESTAMP_MODE
|
||||
codec_settings->H264()->frameDroppingOn = true;
|
||||
codec_settings->targetBitrate = 2000;
|
||||
codec_settings->maxBitrate = 4000;
|
||||
}
|
||||
|
||||
TEST(H264EncoderImplTest, CanInitializeWithDefaultParameters) {
|
||||
H264EncoderImpl encoder(cricket::VideoCodec("H264"));
|
||||
VideoCodec codec_settings;
|
||||
SetDefaultSettings(&codec_settings);
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
|
||||
encoder.InitEncode(&codec_settings, kNumCores, kMaxPayloadSize));
|
||||
EXPECT_EQ(H264PacketizationMode::NonInterleaved,
|
||||
encoder.PacketizationModeForTesting());
|
||||
}
|
||||
|
||||
TEST(H264EncoderImplTest, CanInitializeWithNonInterleavedModeExplicitly) {
|
||||
cricket::VideoCodec codec("H264");
|
||||
codec.SetParam(cricket::kH264FmtpPacketizationMode, "1");
|
||||
H264EncoderImpl encoder(codec);
|
||||
VideoCodec codec_settings;
|
||||
SetDefaultSettings(&codec_settings);
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
|
||||
encoder.InitEncode(&codec_settings, kNumCores, kMaxPayloadSize));
|
||||
EXPECT_EQ(H264PacketizationMode::NonInterleaved,
|
||||
encoder.PacketizationModeForTesting());
|
||||
}
|
||||
|
||||
TEST(H264EncoderImplTest, CanInitializeWithSingleNalUnitModeExplicitly) {
|
||||
cricket::VideoCodec codec("H264");
|
||||
codec.SetParam(cricket::kH264FmtpPacketizationMode, "0");
|
||||
H264EncoderImpl encoder(codec);
|
||||
VideoCodec codec_settings;
|
||||
SetDefaultSettings(&codec_settings);
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
|
||||
encoder.InitEncode(&codec_settings, kNumCores, kMaxPayloadSize));
|
||||
EXPECT_EQ(H264PacketizationMode::SingleNalUnit,
|
||||
encoder.PacketizationModeForTesting());
|
||||
}
|
||||
|
||||
TEST(H264EncoderImplTest, CanInitializeWithRemovedParameter) {
|
||||
cricket::VideoCodec codec("H264");
|
||||
codec.RemoveParam(cricket::kH264FmtpPacketizationMode);
|
||||
H264EncoderImpl encoder(codec);
|
||||
VideoCodec codec_settings;
|
||||
SetDefaultSettings(&codec_settings);
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
|
||||
encoder.InitEncode(&codec_settings, kNumCores, kMaxPayloadSize));
|
||||
EXPECT_EQ(H264PacketizationMode::SingleNalUnit,
|
||||
encoder.PacketizationModeForTesting());
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
} // namespace webrtc
|
||||
45
modules/video_coding/codecs/h264/include/h264.h
Normal file
45
modules/video_coding/codecs/h264/include/h264.h
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_VIDEO_CODING_CODECS_H264_INCLUDE_H264_H_
|
||||
#define WEBRTC_MODULES_VIDEO_CODING_CODECS_H264_INCLUDE_H264_H_
|
||||
|
||||
#include "webrtc/media/base/codec.h"
|
||||
#include "webrtc/modules/video_coding/include/video_codec_interface.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Set to disable the H.264 encoder/decoder implementations that are provided if
|
||||
// |rtc_use_h264| build flag is true (if false, this function does nothing).
|
||||
// This function should only be called before or during WebRTC initialization
|
||||
// and is not thread-safe.
|
||||
void DisableRtcUseH264();
|
||||
|
||||
class H264Encoder : public VideoEncoder {
|
||||
public:
|
||||
static H264Encoder* Create(const cricket::VideoCodec& codec);
|
||||
// If H.264 is supported (any implementation).
|
||||
static bool IsSupported();
|
||||
|
||||
~H264Encoder() override {}
|
||||
};
|
||||
|
||||
class H264Decoder : public VideoDecoder {
|
||||
public:
|
||||
static H264Decoder* Create();
|
||||
static bool IsSupported();
|
||||
|
||||
~H264Decoder() override {}
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_VIDEO_CODING_CODECS_H264_INCLUDE_H264_H_
|
||||
82
modules/video_coding/codecs/h264/include/h264_globals.h
Normal file
82
modules/video_coding/codecs/h264/include/h264_globals.h
Normal file
@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
// This file contains codec dependent definitions that are needed in
|
||||
// order to compile the WebRTC codebase, even if this codec is not used.
|
||||
|
||||
#ifndef WEBRTC_MODULES_VIDEO_CODING_CODECS_H264_INCLUDE_H264_GLOBALS_H_
|
||||
#define WEBRTC_MODULES_VIDEO_CODING_CODECS_H264_INCLUDE_H264_GLOBALS_H_
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// The packetization types that we support: single, aggregated, and fragmented.
|
||||
enum H264PacketizationTypes {
|
||||
kH264SingleNalu, // This packet contains a single NAL unit.
|
||||
kH264StapA, // This packet contains STAP-A (single time
|
||||
// aggregation) packets. If this packet has an
|
||||
// associated NAL unit type, it'll be for the
|
||||
// first such aggregated packet.
|
||||
kH264FuA, // This packet contains a FU-A (fragmentation
|
||||
// unit) packet, meaning it is a part of a frame
|
||||
// that was too large to fit into a single packet.
|
||||
};
|
||||
|
||||
// Packetization modes are defined in RFC 6184 section 6
|
||||
// Due to the structure containing this being initialized with zeroes
|
||||
// in some places, and mode 1 being default, mode 1 needs to have the value
|
||||
// zero. https://crbug.com/webrtc/6803
|
||||
enum class H264PacketizationMode {
|
||||
NonInterleaved = 0, // Mode 1 - STAP-A, FU-A is allowed
|
||||
SingleNalUnit // Mode 0 - only single NALU allowed
|
||||
};
|
||||
|
||||
// This function is declared inline because it is not clear which
|
||||
// .cc file it should belong to.
|
||||
// TODO(hta): Refactor. https://bugs.webrtc.org/6842
|
||||
inline std::ostream& operator<<(std::ostream& stream,
|
||||
H264PacketizationMode mode) {
|
||||
switch (mode) {
|
||||
case H264PacketizationMode::NonInterleaved:
|
||||
stream << "NonInterleaved";
|
||||
break;
|
||||
case H264PacketizationMode::SingleNalUnit:
|
||||
stream << "SingleNalUnit";
|
||||
break;
|
||||
}
|
||||
return stream;
|
||||
}
|
||||
|
||||
struct NaluInfo {
|
||||
uint8_t type;
|
||||
int sps_id;
|
||||
int pps_id;
|
||||
};
|
||||
|
||||
const size_t kMaxNalusPerPacket = 10;
|
||||
|
||||
struct RTPVideoHeaderH264 {
|
||||
// The NAL unit type. If this is a header for a
|
||||
// fragmented packet, it's the NAL unit type of
|
||||
// the original data. If this is the header for an
|
||||
// aggregated packet, it's the NAL unit type of
|
||||
// the first NAL unit in the packet.
|
||||
uint8_t nalu_type;
|
||||
// The packetization type of this buffer - single, aggregated or fragmented.
|
||||
H264PacketizationTypes packetization_type;
|
||||
NaluInfo nalus[kMaxNalusPerPacket];
|
||||
size_t nalus_length;
|
||||
// The packetization mode of this transport. Packetization mode
|
||||
// determines which packetization types are allowed when packetizing.
|
||||
H264PacketizationMode packetization_mode;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_VIDEO_CODING_CODECS_H264_INCLUDE_H264_GLOBALS_H_
|
||||
78
modules/video_coding/codecs/h264/test/h264_impl_unittest.cc
Normal file
78
modules/video_coding/codecs/h264/test/h264_impl_unittest.cc
Normal file
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "webrtc/common_video/libyuv/include/webrtc_libyuv.h"
|
||||
#include "webrtc/modules/video_coding/codecs/h264/include/h264.h"
|
||||
#include "webrtc/modules/video_coding/codecs/test/video_codec_test.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class TestH264Impl : public VideoCodecTest {
|
||||
protected:
|
||||
VideoEncoder* CreateEncoder() override {
|
||||
return H264Encoder::Create(cricket::VideoCodec(cricket::kH264CodecName));
|
||||
}
|
||||
|
||||
VideoDecoder* CreateDecoder() override { return H264Decoder::Create(); }
|
||||
|
||||
VideoCodec codec_settings() override {
|
||||
VideoCodec codec_inst;
|
||||
codec_inst.codecType = webrtc::kVideoCodecH264;
|
||||
// If frame dropping is false, we get a warning that bitrate can't
|
||||
// be controlled for RC_QUALITY_MODE; RC_BITRATE_MODE and RC_TIMESTAMP_MODE
|
||||
codec_inst.H264()->frameDroppingOn = true;
|
||||
return codec_inst;
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef WEBRTC_USE_H264
|
||||
#define MAYBE_EncodeDecode EncodeDecode
|
||||
#define MAYBE_DecodedQpEqualsEncodedQp DecodedQpEqualsEncodedQp
|
||||
#else
|
||||
#define MAYBE_EncodeDecode DISABLED_EncodeDecode
|
||||
#define MAYBE_DecodedQpEqualsEncodedQp DISABLED_DecodedQpEqualsEncodedQp
|
||||
#endif
|
||||
|
||||
TEST_F(TestH264Impl, MAYBE_EncodeDecode) {
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
|
||||
encoder_->Encode(*input_frame_, nullptr, nullptr));
|
||||
EncodedImage encoded_frame;
|
||||
CodecSpecificInfo codec_specific_info;
|
||||
ASSERT_TRUE(WaitForEncodedFrame(&encoded_frame, &codec_specific_info));
|
||||
// First frame should be a key frame.
|
||||
encoded_frame._frameType = kVideoFrameKey;
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
|
||||
decoder_->Decode(encoded_frame, false, nullptr));
|
||||
std::unique_ptr<VideoFrame> decoded_frame;
|
||||
rtc::Optional<uint8_t> decoded_qp;
|
||||
ASSERT_TRUE(WaitForDecodedFrame(&decoded_frame, &decoded_qp));
|
||||
ASSERT_TRUE(decoded_frame);
|
||||
EXPECT_GT(I420PSNR(input_frame_.get(), decoded_frame.get()), 36);
|
||||
}
|
||||
|
||||
TEST_F(TestH264Impl, MAYBE_DecodedQpEqualsEncodedQp) {
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
|
||||
encoder_->Encode(*input_frame_, nullptr, nullptr));
|
||||
EncodedImage encoded_frame;
|
||||
CodecSpecificInfo codec_specific_info;
|
||||
ASSERT_TRUE(WaitForEncodedFrame(&encoded_frame, &codec_specific_info));
|
||||
// First frame should be a key frame.
|
||||
encoded_frame._frameType = kVideoFrameKey;
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
|
||||
decoder_->Decode(encoded_frame, false, nullptr));
|
||||
std::unique_ptr<VideoFrame> decoded_frame;
|
||||
rtc::Optional<uint8_t> decoded_qp;
|
||||
ASSERT_TRUE(WaitForDecodedFrame(&decoded_frame, &decoded_qp));
|
||||
ASSERT_TRUE(decoded_frame);
|
||||
ASSERT_TRUE(decoded_qp);
|
||||
EXPECT_EQ(encoded_frame.qp_, *decoded_qp);
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
240
modules/video_coding/codecs/i420/i420.cc
Normal file
240
modules/video_coding/codecs/i420/i420.cc
Normal file
@ -0,0 +1,240 @@
|
||||
/*
|
||||
* Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "webrtc/modules/video_coding/codecs/i420/include/i420.h"
|
||||
|
||||
#include <limits>
|
||||
#include <string>
|
||||
|
||||
#include "webrtc/api/video/i420_buffer.h"
|
||||
#include "webrtc/common_video/libyuv/include/webrtc_libyuv.h"
|
||||
|
||||
namespace {
|
||||
const size_t kI420HeaderSize = 4;
|
||||
}
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
I420Encoder::I420Encoder()
|
||||
: _inited(false), _encodedImage(), _encodedCompleteCallback(NULL) {}
|
||||
|
||||
I420Encoder::~I420Encoder() {
|
||||
_inited = false;
|
||||
delete[] _encodedImage._buffer;
|
||||
}
|
||||
|
||||
int I420Encoder::Release() {
|
||||
// Should allocate an encoded frame and then release it here, for that we
|
||||
// actually need an init flag.
|
||||
if (_encodedImage._buffer != NULL) {
|
||||
delete[] _encodedImage._buffer;
|
||||
_encodedImage._buffer = NULL;
|
||||
}
|
||||
_inited = false;
|
||||
return WEBRTC_VIDEO_CODEC_OK;
|
||||
}
|
||||
|
||||
int I420Encoder::InitEncode(const VideoCodec* codecSettings,
|
||||
int /*numberOfCores*/,
|
||||
size_t /*maxPayloadSize */) {
|
||||
if (codecSettings == NULL) {
|
||||
return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
|
||||
}
|
||||
if (codecSettings->width < 1 || codecSettings->height < 1) {
|
||||
return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
|
||||
}
|
||||
|
||||
// Allocating encoded memory.
|
||||
if (_encodedImage._buffer != NULL) {
|
||||
delete[] _encodedImage._buffer;
|
||||
_encodedImage._buffer = NULL;
|
||||
_encodedImage._size = 0;
|
||||
}
|
||||
const size_t newSize = CalcBufferSize(VideoType::kI420, codecSettings->width,
|
||||
codecSettings->height) +
|
||||
kI420HeaderSize;
|
||||
uint8_t* newBuffer = new uint8_t[newSize];
|
||||
if (newBuffer == NULL) {
|
||||
return WEBRTC_VIDEO_CODEC_MEMORY;
|
||||
}
|
||||
_encodedImage._size = newSize;
|
||||
_encodedImage._buffer = newBuffer;
|
||||
|
||||
// If no memory allocation, no point to init.
|
||||
_inited = true;
|
||||
return WEBRTC_VIDEO_CODEC_OK;
|
||||
}
|
||||
|
||||
int I420Encoder::Encode(const VideoFrame& inputImage,
|
||||
const CodecSpecificInfo* /*codecSpecificInfo*/,
|
||||
const std::vector<FrameType>* /*frame_types*/) {
|
||||
if (!_inited) {
|
||||
return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
|
||||
}
|
||||
if (_encodedCompleteCallback == NULL) {
|
||||
return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
|
||||
}
|
||||
|
||||
_encodedImage._frameType = kVideoFrameKey;
|
||||
_encodedImage._timeStamp = inputImage.timestamp();
|
||||
_encodedImage._encodedHeight = inputImage.height();
|
||||
_encodedImage._encodedWidth = inputImage.width();
|
||||
|
||||
int width = inputImage.width();
|
||||
if (width > std::numeric_limits<uint16_t>::max()) {
|
||||
return WEBRTC_VIDEO_CODEC_ERR_SIZE;
|
||||
}
|
||||
int height = inputImage.height();
|
||||
if (height > std::numeric_limits<uint16_t>::max()) {
|
||||
return WEBRTC_VIDEO_CODEC_ERR_SIZE;
|
||||
}
|
||||
|
||||
size_t req_length = CalcBufferSize(VideoType::kI420, inputImage.width(),
|
||||
inputImage.height()) +
|
||||
kI420HeaderSize;
|
||||
if (_encodedImage._size > req_length) {
|
||||
// Reallocate buffer.
|
||||
delete[] _encodedImage._buffer;
|
||||
|
||||
_encodedImage._buffer = new uint8_t[req_length];
|
||||
_encodedImage._size = req_length;
|
||||
}
|
||||
|
||||
uint8_t* buffer = _encodedImage._buffer;
|
||||
|
||||
buffer = InsertHeader(buffer, width, height);
|
||||
|
||||
int ret_length =
|
||||
ExtractBuffer(inputImage, req_length - kI420HeaderSize, buffer);
|
||||
if (ret_length < 0)
|
||||
return WEBRTC_VIDEO_CODEC_MEMORY;
|
||||
_encodedImage._length = ret_length + kI420HeaderSize;
|
||||
|
||||
_encodedCompleteCallback->OnEncodedImage(_encodedImage, nullptr, nullptr);
|
||||
|
||||
return WEBRTC_VIDEO_CODEC_OK;
|
||||
}
|
||||
|
||||
uint8_t* I420Encoder::InsertHeader(uint8_t* buffer,
|
||||
uint16_t width,
|
||||
uint16_t height) {
|
||||
*buffer++ = static_cast<uint8_t>(width >> 8);
|
||||
*buffer++ = static_cast<uint8_t>(width & 0xFF);
|
||||
*buffer++ = static_cast<uint8_t>(height >> 8);
|
||||
*buffer++ = static_cast<uint8_t>(height & 0xFF);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
int I420Encoder::RegisterEncodeCompleteCallback(
|
||||
EncodedImageCallback* callback) {
|
||||
_encodedCompleteCallback = callback;
|
||||
return WEBRTC_VIDEO_CODEC_OK;
|
||||
}
|
||||
|
||||
I420Decoder::I420Decoder()
|
||||
: _width(0),
|
||||
_height(0),
|
||||
_inited(false),
|
||||
_decodeCompleteCallback(NULL) {}
|
||||
|
||||
I420Decoder::~I420Decoder() {
|
||||
Release();
|
||||
}
|
||||
|
||||
int I420Decoder::InitDecode(const VideoCodec* codecSettings,
|
||||
int /*numberOfCores */) {
|
||||
if (codecSettings == NULL) {
|
||||
return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
|
||||
} else if (codecSettings->width < 1 || codecSettings->height < 1) {
|
||||
return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
|
||||
}
|
||||
_width = codecSettings->width;
|
||||
_height = codecSettings->height;
|
||||
_inited = true;
|
||||
return WEBRTC_VIDEO_CODEC_OK;
|
||||
}
|
||||
|
||||
int I420Decoder::Decode(const EncodedImage& inputImage,
|
||||
bool /*missingFrames*/,
|
||||
const RTPFragmentationHeader* /*fragmentation*/,
|
||||
const CodecSpecificInfo* /*codecSpecificInfo*/,
|
||||
int64_t /*renderTimeMs*/) {
|
||||
if (inputImage._buffer == NULL) {
|
||||
return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
|
||||
}
|
||||
if (_decodeCompleteCallback == NULL) {
|
||||
return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
|
||||
}
|
||||
if (inputImage._length <= 0) {
|
||||
return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
|
||||
}
|
||||
if (inputImage._completeFrame == false) {
|
||||
return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
|
||||
}
|
||||
if (!_inited) {
|
||||
return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
|
||||
}
|
||||
if (inputImage._length < kI420HeaderSize) {
|
||||
return WEBRTC_VIDEO_CODEC_ERROR;
|
||||
}
|
||||
|
||||
const uint8_t* buffer = inputImage._buffer;
|
||||
uint16_t width, height;
|
||||
|
||||
buffer = ExtractHeader(buffer, &width, &height);
|
||||
_width = width;
|
||||
_height = height;
|
||||
|
||||
// Verify that the available length is sufficient:
|
||||
size_t req_length =
|
||||
CalcBufferSize(VideoType::kI420, _width, _height) + kI420HeaderSize;
|
||||
|
||||
if (req_length > inputImage._length) {
|
||||
return WEBRTC_VIDEO_CODEC_ERROR;
|
||||
}
|
||||
// Set decoded image parameters.
|
||||
rtc::scoped_refptr<webrtc::I420Buffer> frame_buffer =
|
||||
I420Buffer::Create(_width, _height);
|
||||
|
||||
// Converting from raw buffer I420Buffer.
|
||||
int ret = ConvertToI420(VideoType::kI420, buffer, 0, 0, _width, _height, 0,
|
||||
kVideoRotation_0, frame_buffer.get());
|
||||
if (ret < 0) {
|
||||
return WEBRTC_VIDEO_CODEC_MEMORY;
|
||||
}
|
||||
|
||||
VideoFrame decoded_image(frame_buffer, inputImage._timeStamp, 0,
|
||||
webrtc::kVideoRotation_0);
|
||||
_decodeCompleteCallback->Decoded(decoded_image);
|
||||
return WEBRTC_VIDEO_CODEC_OK;
|
||||
}
|
||||
|
||||
const uint8_t* I420Decoder::ExtractHeader(const uint8_t* buffer,
|
||||
uint16_t* width,
|
||||
uint16_t* height) {
|
||||
*width = static_cast<uint16_t>(*buffer++) << 8;
|
||||
*width |= *buffer++;
|
||||
*height = static_cast<uint16_t>(*buffer++) << 8;
|
||||
*height |= *buffer++;
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
int I420Decoder::RegisterDecodeCompleteCallback(
|
||||
DecodedImageCallback* callback) {
|
||||
_decodeCompleteCallback = callback;
|
||||
return WEBRTC_VIDEO_CODEC_OK;
|
||||
}
|
||||
|
||||
int I420Decoder::Release() {
|
||||
_inited = false;
|
||||
return WEBRTC_VIDEO_CODEC_OK;
|
||||
}
|
||||
} // namespace webrtc
|
||||
142
modules/video_coding/codecs/i420/include/i420.h
Normal file
142
modules/video_coding/codecs/i420/include/i420.h
Normal file
@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_VIDEO_CODING_CODECS_I420_INCLUDE_I420_H_
|
||||
#define WEBRTC_MODULES_VIDEO_CODING_CODECS_I420_INCLUDE_I420_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/modules/video_coding/include/video_codec_interface.h"
|
||||
#include "webrtc/rtc_base/checks.h"
|
||||
#include "webrtc/typedefs.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class I420Encoder : public VideoEncoder {
|
||||
public:
|
||||
I420Encoder();
|
||||
|
||||
virtual ~I420Encoder();
|
||||
|
||||
// Initialize the encoder with the information from the VideoCodec.
|
||||
//
|
||||
// Input:
|
||||
// - codecSettings : Codec settings.
|
||||
// - numberOfCores : Number of cores available for the encoder.
|
||||
// - maxPayloadSize : The maximum size each payload is allowed
|
||||
// to have. Usually MTU - overhead.
|
||||
//
|
||||
// Return value : WEBRTC_VIDEO_CODEC_OK if OK.
|
||||
// <0 - Error
|
||||
int InitEncode(const VideoCodec* codecSettings,
|
||||
int /*numberOfCores*/,
|
||||
size_t /*maxPayloadSize*/) override;
|
||||
|
||||
// "Encode" an I420 image (as a part of a video stream). The encoded image
|
||||
// will be returned to the user via the encode complete callback.
|
||||
//
|
||||
// Input:
|
||||
// - inputImage : Image to be encoded.
|
||||
// - codecSpecificInfo : Pointer to codec specific data.
|
||||
// - frameType : Frame type to be sent (Key /Delta).
|
||||
//
|
||||
// Return value : WEBRTC_VIDEO_CODEC_OK if OK.
|
||||
// <0 - Error
|
||||
int Encode(const VideoFrame& inputImage,
|
||||
const CodecSpecificInfo* /*codecSpecificInfo*/,
|
||||
const std::vector<FrameType>* /*frame_types*/) override;
|
||||
|
||||
// Register an encode complete callback object.
|
||||
//
|
||||
// Input:
|
||||
// - callback : Callback object which handles encoded images.
|
||||
//
|
||||
// Return value : WEBRTC_VIDEO_CODEC_OK if OK, < 0 otherwise.
|
||||
int RegisterEncodeCompleteCallback(EncodedImageCallback* callback) override;
|
||||
|
||||
// Free encoder memory.
|
||||
//
|
||||
// Return value : WEBRTC_VIDEO_CODEC_OK if OK, < 0 otherwise.
|
||||
int Release() override;
|
||||
|
||||
int SetChannelParameters(uint32_t /*packetLoss*/, int64_t /*rtt*/) override {
|
||||
return WEBRTC_VIDEO_CODEC_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
static uint8_t* InsertHeader(uint8_t* buffer,
|
||||
uint16_t width,
|
||||
uint16_t height);
|
||||
|
||||
bool _inited;
|
||||
EncodedImage _encodedImage;
|
||||
EncodedImageCallback* _encodedCompleteCallback;
|
||||
}; // class I420Encoder
|
||||
|
||||
class I420Decoder : public VideoDecoder {
|
||||
public:
|
||||
I420Decoder();
|
||||
|
||||
virtual ~I420Decoder();
|
||||
|
||||
// Initialize the decoder.
|
||||
// The user must notify the codec of width and height values.
|
||||
//
|
||||
// Return value : WEBRTC_VIDEO_CODEC_OK.
|
||||
// <0 - Errors
|
||||
int InitDecode(const VideoCodec* codecSettings,
|
||||
int /*numberOfCores*/) override;
|
||||
|
||||
// Decode encoded image (as a part of a video stream). The decoded image
|
||||
// will be returned to the user through the decode complete callback.
|
||||
//
|
||||
// Input:
|
||||
// - inputImage : Encoded image to be decoded
|
||||
// - missingFrames : True if one or more frames have been lost
|
||||
// since the previous decode call.
|
||||
// - codecSpecificInfo : pointer to specific codec data
|
||||
// - renderTimeMs : Render time in Ms
|
||||
//
|
||||
// Return value : WEBRTC_VIDEO_CODEC_OK if OK
|
||||
// <0 - Error
|
||||
int Decode(const EncodedImage& inputImage,
|
||||
bool missingFrames,
|
||||
const RTPFragmentationHeader* /*fragmentation*/,
|
||||
const CodecSpecificInfo* /*codecSpecificInfo*/,
|
||||
int64_t /*renderTimeMs*/) override;
|
||||
|
||||
// Register a decode complete callback object.
|
||||
//
|
||||
// Input:
|
||||
// - callback : Callback object which handles decoded images.
|
||||
//
|
||||
// Return value : WEBRTC_VIDEO_CODEC_OK if OK, < 0 otherwise.
|
||||
int RegisterDecodeCompleteCallback(DecodedImageCallback* callback) override;
|
||||
|
||||
// Free decoder memory.
|
||||
//
|
||||
// Return value : WEBRTC_VIDEO_CODEC_OK if OK.
|
||||
// <0 - Error
|
||||
int Release() override;
|
||||
|
||||
private:
|
||||
static const uint8_t* ExtractHeader(const uint8_t* buffer,
|
||||
uint16_t* width,
|
||||
uint16_t* height);
|
||||
|
||||
int _width;
|
||||
int _height;
|
||||
bool _inited;
|
||||
DecodedImageCallback* _decodeCompleteCallback;
|
||||
}; // class I420Decoder
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_VIDEO_CODING_CODECS_I420_INCLUDE_I420_H_
|
||||
26
modules/video_coding/codecs/interface/common_constants.h
Normal file
26
modules/video_coding/codecs/interface/common_constants.h
Normal file
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
// This file contains constants that are used by multiple global
|
||||
// codec definitions (modules/video_coding/codecs/*/include/*_globals.h)
|
||||
|
||||
#ifndef WEBRTC_MODULES_VIDEO_CODING_CODECS_INTERFACE_COMMON_CONSTANTS_H_
|
||||
#define WEBRTC_MODULES_VIDEO_CODING_CODECS_INTERFACE_COMMON_CONSTANTS_H_
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
const int16_t kNoPictureId = -1;
|
||||
const int16_t kNoTl0PicIdx = -1;
|
||||
const uint8_t kNoTemporalIdx = 0xFF;
|
||||
const int kNoKeyIdx = -1;
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_VIDEO_CODING_CODECS_INTERFACE_COMMON_CONSTANTS_H_
|
||||
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_VIDEO_CODING_CODECS_INTERFACE_MOCK_MOCK_VIDEO_CODEC_INTERFACE_H_
|
||||
#define WEBRTC_MODULES_VIDEO_CODING_CODECS_INTERFACE_MOCK_MOCK_VIDEO_CODEC_INTERFACE_H_
|
||||
|
||||
#pragma message("WARNING: video_coding/codecs/interface is DEPRECATED; "
|
||||
"use video_coding/include")
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/modules/video_coding/include/video_codec_interface.h"
|
||||
#include "webrtc/test/gmock.h"
|
||||
#include "webrtc/typedefs.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class MockEncodedImageCallback : public EncodedImageCallback {
|
||||
public:
|
||||
MOCK_METHOD3(Encoded,
|
||||
int32_t(const EncodedImage& encodedImage,
|
||||
const CodecSpecificInfo* codecSpecificInfo,
|
||||
const RTPFragmentationHeader* fragmentation));
|
||||
};
|
||||
|
||||
class MockVideoEncoder : public VideoEncoder {
|
||||
public:
|
||||
MOCK_CONST_METHOD2(Version, int32_t(int8_t* version, int32_t length));
|
||||
MOCK_METHOD3(InitEncode,
|
||||
int32_t(const VideoCodec* codecSettings,
|
||||
int32_t numberOfCores,
|
||||
size_t maxPayloadSize));
|
||||
MOCK_METHOD3(Encode,
|
||||
int32_t(const VideoFrame& inputImage,
|
||||
const CodecSpecificInfo* codecSpecificInfo,
|
||||
const std::vector<FrameType>* frame_types));
|
||||
MOCK_METHOD1(RegisterEncodeCompleteCallback,
|
||||
int32_t(EncodedImageCallback* callback));
|
||||
MOCK_METHOD0(Release, int32_t());
|
||||
MOCK_METHOD0(Reset, int32_t());
|
||||
MOCK_METHOD2(SetChannelParameters, int32_t(uint32_t packetLoss, int64_t rtt));
|
||||
MOCK_METHOD2(SetRates, int32_t(uint32_t newBitRate, uint32_t frameRate));
|
||||
MOCK_METHOD1(SetPeriodicKeyFrames, int32_t(bool enable));
|
||||
};
|
||||
|
||||
class MockDecodedImageCallback : public DecodedImageCallback {
|
||||
public:
|
||||
MOCK_METHOD1(Decoded, int32_t(const VideoFrame& decodedImage));
|
||||
MOCK_METHOD2(Decoded,
|
||||
int32_t(const VideoFrame& decodedImage, int64_t decode_time_ms));
|
||||
MOCK_METHOD1(ReceivedDecodedReferenceFrame,
|
||||
int32_t(const uint64_t pictureId));
|
||||
MOCK_METHOD1(ReceivedDecodedFrame, int32_t(const uint64_t pictureId));
|
||||
};
|
||||
|
||||
class MockVideoDecoder : public VideoDecoder {
|
||||
public:
|
||||
MOCK_METHOD2(InitDecode,
|
||||
int32_t(const VideoCodec* codecSettings, int32_t numberOfCores));
|
||||
MOCK_METHOD5(Decode,
|
||||
int32_t(const EncodedImage& inputImage,
|
||||
bool missingFrames,
|
||||
const RTPFragmentationHeader* fragmentation,
|
||||
const CodecSpecificInfo* codecSpecificInfo,
|
||||
int64_t renderTimeMs));
|
||||
MOCK_METHOD1(RegisterDecodeCompleteCallback,
|
||||
int32_t(DecodedImageCallback* callback));
|
||||
MOCK_METHOD0(Release, int32_t());
|
||||
MOCK_METHOD0(Copy, VideoDecoder*());
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_VIDEO_CODING_CODECS_INTERFACE_MOCK_MOCK_VIDEO_CODEC_INTERFACE_H_
|
||||
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_VIDEO_CODING_CODECS_INTERFACE_VIDEO_CODEC_INTERFACE_H_
|
||||
#define WEBRTC_MODULES_VIDEO_CODING_CODECS_INTERFACE_VIDEO_CODEC_INTERFACE_H_
|
||||
|
||||
#pragma message("WARNING: video_coding/codecs/interface is DEPRECATED; "
|
||||
"use video_coding/include")
|
||||
|
||||
#include "webrtc/modules/video_coding/include/video_codec_interface.h"
|
||||
|
||||
#endif // WEBRTC_MODULES_VIDEO_CODING_CODECS_INTERFACE_VIDEO_CODEC_INTERFACE_H_
|
||||
35
modules/video_coding/codecs/interface/video_error_codes.h
Normal file
35
modules/video_coding/codecs/interface/video_error_codes.h
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (c) 2011 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_VIDEO_CODING_CODECS_INTERFACE_VIDEO_ERROR_CODES_H_
|
||||
#define WEBRTC_MODULES_VIDEO_CODING_CODECS_INTERFACE_VIDEO_ERROR_CODES_H_
|
||||
|
||||
#pragma message("WARNING: video_coding/codecs/interface is DEPRECATED; "
|
||||
"use video_coding/include")
|
||||
|
||||
// NOTE: in sync with video_coding_module_defines.h
|
||||
|
||||
// Define return values
|
||||
|
||||
#define WEBRTC_VIDEO_CODEC_REQUEST_SLI 2
|
||||
#define WEBRTC_VIDEO_CODEC_NO_OUTPUT 1
|
||||
#define WEBRTC_VIDEO_CODEC_OK 0
|
||||
#define WEBRTC_VIDEO_CODEC_ERROR -1
|
||||
#define WEBRTC_VIDEO_CODEC_LEVEL_EXCEEDED -2
|
||||
#define WEBRTC_VIDEO_CODEC_MEMORY -3
|
||||
#define WEBRTC_VIDEO_CODEC_ERR_PARAMETER -4
|
||||
#define WEBRTC_VIDEO_CODEC_ERR_SIZE -5
|
||||
#define WEBRTC_VIDEO_CODEC_TIMEOUT -6
|
||||
#define WEBRTC_VIDEO_CODEC_UNINITIALIZED -7
|
||||
#define WEBRTC_VIDEO_CODEC_ERR_REQUEST_SLI -12
|
||||
#define WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE -13
|
||||
#define WEBRTC_VIDEO_CODEC_TARGET_BITRATE_OVERSHOOT -14
|
||||
|
||||
#endif // WEBRTC_MODULES_VIDEO_CODING_CODECS_INTERFACE_VIDEO_ERROR_CODES_H_
|
||||
55
modules/video_coding/codecs/test/android_test_initializer.cc
Normal file
55
modules/video_coding/codecs/test/android_test_initializer.cc
Normal file
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2017 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#include "webrtc/modules/video_coding/codecs/test/android_test_initializer.h"
|
||||
#include "webrtc/rtc_base/ignore_wundef.h"
|
||||
#include "webrtc/sdk/android/src/jni/classreferenceholder.h"
|
||||
#include "webrtc/sdk/android/src/jni/jni_helpers.h"
|
||||
|
||||
// Note: this dependency is dangerous since it reaches into Chromium's base.
|
||||
// There's a risk of e.g. macro clashes. This file may only be used in tests.
|
||||
// Since we use Chrome's build system for creating the gtest binary, this should
|
||||
// be fine.
|
||||
RTC_PUSH_IGNORING_WUNDEF()
|
||||
#include "base/android/jni_android.h"
|
||||
RTC_POP_IGNORING_WUNDEF()
|
||||
|
||||
#include "webrtc/rtc_base/checks.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
|
||||
static pthread_once_t g_initialize_once = PTHREAD_ONCE_INIT;
|
||||
|
||||
// There can only be one JNI_OnLoad in each binary. So since this is a GTEST
|
||||
// C++ runner binary, we want to initialize the same global objects we normally
|
||||
// do if this had been a Java binary.
|
||||
void EnsureInitializedOnce() {
|
||||
RTC_CHECK(::base::android::IsVMInitialized());
|
||||
JNIEnv* jni = ::base::android::AttachCurrentThread();
|
||||
JavaVM* jvm = NULL;
|
||||
RTC_CHECK_EQ(0, jni->GetJavaVM(&jvm));
|
||||
|
||||
jint ret = jni::InitGlobalJniVariables(jvm);
|
||||
RTC_DCHECK_GE(ret, 0);
|
||||
|
||||
jni::LoadGlobalClassReferenceHolder();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void InitializeAndroidObjects() {
|
||||
RTC_CHECK_EQ(0, pthread_once(&g_initialize_once, &EnsureInitializedOnce));
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
20
modules/video_coding/codecs/test/android_test_initializer.h
Normal file
20
modules/video_coding/codecs/test/android_test_initializer.h
Normal file
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2017 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_VIDEO_CODING_CODECS_TEST_ANDROID_TEST_INITIALIZER_H_
|
||||
#define WEBRTC_MODULES_VIDEO_CODING_CODECS_TEST_ANDROID_TEST_INITIALIZER_H_
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
void InitializeAndroidObjects();
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_VIDEO_CODING_CODECS_TEST_ANDROID_TEST_INITIALIZER_H_
|
||||
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2011 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_VIDEO_CODING_CODECS_TEST_MOCK_MOCK_PACKET_MANIPULATOR_H_
|
||||
#define WEBRTC_MODULES_VIDEO_CODING_CODECS_TEST_MOCK_MOCK_PACKET_MANIPULATOR_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "webrtc/modules/video_coding/codecs/test/packet_manipulator.h"
|
||||
#include "webrtc/test/gmock.h"
|
||||
#include "webrtc/typedefs.h"
|
||||
#include "webrtc/common_video/include/video_frame.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace test {
|
||||
|
||||
class MockPacketManipulator : public PacketManipulator {
|
||||
public:
|
||||
MOCK_METHOD1(ManipulatePackets, int(webrtc::EncodedImage* encoded_image));
|
||||
};
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_VIDEO_CODING_CODECS_TEST_MOCK_MOCK_PACKET_MANIPULATOR_H_
|
||||
26
modules/video_coding/codecs/test/objc_codec_h264_test.h
Normal file
26
modules/video_coding/codecs/test/objc_codec_h264_test.h
Normal file
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2017 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_VIDEO_CODING_CODECS_TEST_OBJC_CODEC_H264_TEST_H_
|
||||
#define WEBRTC_MODULES_VIDEO_CODING_CODECS_TEST_OBJC_CODEC_H264_TEST_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "webrtc/media/engine/webrtcvideodecoderfactory.h"
|
||||
#include "webrtc/media/engine/webrtcvideoencoderfactory.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
std::unique_ptr<cricket::WebRtcVideoEncoderFactory> CreateObjCEncoderFactory();
|
||||
std::unique_ptr<cricket::WebRtcVideoDecoderFactory> CreateObjCDecoderFactory();
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_VIDEO_CODING_CODECS_TEST_OBJC_CODEC_H264_TEST_H_
|
||||
29
modules/video_coding/codecs/test/objc_codec_h264_test.mm
Normal file
29
modules/video_coding/codecs/test/objc_codec_h264_test.mm
Normal file
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2017 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "webrtc/modules/video_coding/codecs/test/objc_codec_h264_test.h"
|
||||
|
||||
#import "WebRTC/RTCVideoCodecH264.h"
|
||||
#include "webrtc/sdk/objc/Framework/Classes/VideoToolbox/objc_video_decoder_factory.h"
|
||||
#include "webrtc/sdk/objc/Framework/Classes/VideoToolbox/objc_video_encoder_factory.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
std::unique_ptr<cricket::WebRtcVideoEncoderFactory> CreateObjCEncoderFactory() {
|
||||
return std::unique_ptr<cricket::WebRtcVideoEncoderFactory>(
|
||||
new ObjCVideoEncoderFactory([[RTCVideoEncoderFactoryH264 alloc] init]));
|
||||
}
|
||||
|
||||
std::unique_ptr<cricket::WebRtcVideoDecoderFactory> CreateObjCDecoderFactory() {
|
||||
return std::unique_ptr<cricket::WebRtcVideoDecoderFactory>(
|
||||
new ObjCVideoDecoderFactory([[RTCVideoDecoderFactoryH264 alloc] init]));
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
107
modules/video_coding/codecs/test/packet_manipulator.cc
Normal file
107
modules/video_coding/codecs/test/packet_manipulator.cc
Normal file
@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "webrtc/modules/video_coding/codecs/test/packet_manipulator.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "webrtc/rtc_base/format_macros.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace test {
|
||||
|
||||
PacketManipulatorImpl::PacketManipulatorImpl(PacketReader* packet_reader,
|
||||
const NetworkingConfig& config,
|
||||
bool verbose)
|
||||
: packet_reader_(packet_reader),
|
||||
config_(config),
|
||||
active_burst_packets_(0),
|
||||
random_seed_(1),
|
||||
verbose_(verbose) {
|
||||
assert(packet_reader);
|
||||
}
|
||||
|
||||
int PacketManipulatorImpl::ManipulatePackets(
|
||||
webrtc::EncodedImage* encoded_image) {
|
||||
int nbr_packets_dropped = 0;
|
||||
// There's no need to build a copy of the image data since viewing an
|
||||
// EncodedImage object, setting the length to a new lower value represents
|
||||
// that everything is dropped after that position in the byte array.
|
||||
// EncodedImage._size is the allocated bytes.
|
||||
// EncodedImage._length is how many that are filled with data.
|
||||
int new_length = 0;
|
||||
packet_reader_->InitializeReading(encoded_image->_buffer,
|
||||
encoded_image->_length,
|
||||
config_.packet_size_in_bytes);
|
||||
uint8_t* packet = NULL;
|
||||
int nbr_bytes_to_read;
|
||||
// keep track of if we've lost any packets, since then we shall loose
|
||||
// the remains of the current frame:
|
||||
bool packet_loss_has_occurred = false;
|
||||
while ((nbr_bytes_to_read = packet_reader_->NextPacket(&packet)) > 0) {
|
||||
// Check if we're currently in a packet loss burst that is not completed:
|
||||
if (active_burst_packets_ > 0) {
|
||||
active_burst_packets_--;
|
||||
nbr_packets_dropped++;
|
||||
} else if (RandomUniform() < config_.packet_loss_probability ||
|
||||
packet_loss_has_occurred) {
|
||||
packet_loss_has_occurred = true;
|
||||
nbr_packets_dropped++;
|
||||
if (config_.packet_loss_mode == kBurst) {
|
||||
// Initiate a new burst
|
||||
active_burst_packets_ = config_.packet_loss_burst_length - 1;
|
||||
}
|
||||
} else {
|
||||
new_length += nbr_bytes_to_read;
|
||||
}
|
||||
}
|
||||
encoded_image->_length = new_length;
|
||||
if (nbr_packets_dropped > 0) {
|
||||
// Must set completeFrame to false to inform the decoder about this:
|
||||
encoded_image->_completeFrame = false;
|
||||
if (verbose_) {
|
||||
printf("Dropped %d packets for frame %d (frame length: %" PRIuS ")\n",
|
||||
nbr_packets_dropped, encoded_image->_timeStamp,
|
||||
encoded_image->_length);
|
||||
}
|
||||
}
|
||||
return nbr_packets_dropped;
|
||||
}
|
||||
|
||||
void PacketManipulatorImpl::InitializeRandomSeed(unsigned int seed) {
|
||||
random_seed_ = seed;
|
||||
}
|
||||
|
||||
inline double PacketManipulatorImpl::RandomUniform() {
|
||||
// Use the previous result as new seed before each rand() call. Doing this
|
||||
// it doesn't matter if other threads are calling rand() since we'll always
|
||||
// get the same behavior as long as we're using a fixed initial seed.
|
||||
critsect_.Enter();
|
||||
srand(random_seed_);
|
||||
random_seed_ = rand(); // NOLINT (rand_r instead of rand)
|
||||
critsect_.Leave();
|
||||
return (random_seed_ + 1.0) / (RAND_MAX + 1.0);
|
||||
}
|
||||
|
||||
const char* PacketLossModeToStr(PacketLossMode e) {
|
||||
switch (e) {
|
||||
case kUniform:
|
||||
return "Uniform";
|
||||
case kBurst:
|
||||
return "Burst";
|
||||
default:
|
||||
assert(false);
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
115
modules/video_coding/codecs/test/packet_manipulator.h
Normal file
115
modules/video_coding/codecs/test/packet_manipulator.h
Normal file
@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_VIDEO_CODING_CODECS_TEST_PACKET_MANIPULATOR_H_
|
||||
#define WEBRTC_MODULES_VIDEO_CODING_CODECS_TEST_PACKET_MANIPULATOR_H_
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "webrtc/modules/video_coding/include/video_codec_interface.h"
|
||||
#include "webrtc/rtc_base/criticalsection.h"
|
||||
#include "webrtc/test/testsupport/packet_reader.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace test {
|
||||
|
||||
// Which mode the packet loss shall be performed according to.
|
||||
enum PacketLossMode {
|
||||
// Drops packets with a configured probability independently for each packet
|
||||
kUniform,
|
||||
// Drops packets similar to uniform but when a packet is being dropped,
|
||||
// the number of lost packets in a row is equal to the configured burst
|
||||
// length.
|
||||
kBurst
|
||||
};
|
||||
// Returns a string representation of the enum value.
|
||||
const char* PacketLossModeToStr(PacketLossMode e);
|
||||
|
||||
// Contains configurations related to networking and simulation of
|
||||
// scenarios caused by network interference.
|
||||
struct NetworkingConfig {
|
||||
NetworkingConfig()
|
||||
: packet_size_in_bytes(1500),
|
||||
max_payload_size_in_bytes(1440),
|
||||
packet_loss_mode(kUniform),
|
||||
packet_loss_probability(0.0),
|
||||
packet_loss_burst_length(1) {}
|
||||
|
||||
// Packet size in bytes. Default: 1500 bytes.
|
||||
size_t packet_size_in_bytes;
|
||||
|
||||
// Encoder specific setting of maximum size in bytes of each payload.
|
||||
// Default: 1440 bytes.
|
||||
size_t max_payload_size_in_bytes;
|
||||
|
||||
// Packet loss mode. Two different packet loss models are supported:
|
||||
// uniform or burst. This setting has no effect unless
|
||||
// packet_loss_probability is >0.
|
||||
// Default: uniform.
|
||||
PacketLossMode packet_loss_mode;
|
||||
|
||||
// Packet loss probability. A value between 0.0 and 1.0 that defines the
|
||||
// probability of a packet being lost. 0.1 means 10% and so on.
|
||||
// Default: 0 (no loss).
|
||||
double packet_loss_probability;
|
||||
|
||||
// Packet loss burst length. Defines how many packets will be lost in a burst
|
||||
// when a packet has been decided to be lost. Must be >=1. Default: 1.
|
||||
int packet_loss_burst_length;
|
||||
};
|
||||
|
||||
// Class for simulating packet loss on the encoded frame data.
|
||||
// When a packet loss has occurred in a frame, the remaining data in that
|
||||
// frame is lost (even if burst length is only a single packet).
|
||||
// TODO(kjellander): Support discarding only individual packets in the frame
|
||||
// when CL 172001 has been submitted. This also requires a correct
|
||||
// fragmentation header to be passed to the decoder.
|
||||
//
|
||||
// To get a repeatable packet drop pattern, re-initialize the random seed
|
||||
// using InitializeRandomSeed before each test run.
|
||||
class PacketManipulator {
|
||||
public:
|
||||
virtual ~PacketManipulator() {}
|
||||
|
||||
// Manipulates the data of the encoded_image to simulate parts being lost
|
||||
// during transport.
|
||||
// If packets are dropped from frame data, the completedFrame field will be
|
||||
// set to false.
|
||||
// Returns the number of packets being dropped.
|
||||
virtual int ManipulatePackets(webrtc::EncodedImage* encoded_image) = 0;
|
||||
};
|
||||
|
||||
class PacketManipulatorImpl : public PacketManipulator {
|
||||
public:
|
||||
PacketManipulatorImpl(PacketReader* packet_reader,
|
||||
const NetworkingConfig& config,
|
||||
bool verbose);
|
||||
~PacketManipulatorImpl() = default;
|
||||
int ManipulatePackets(webrtc::EncodedImage* encoded_image) override;
|
||||
virtual void InitializeRandomSeed(unsigned int seed);
|
||||
|
||||
protected:
|
||||
// Returns a uniformly distributed random value between 0.0 and 1.0
|
||||
virtual double RandomUniform();
|
||||
|
||||
private:
|
||||
PacketReader* packet_reader_;
|
||||
const NetworkingConfig& config_;
|
||||
// Used to simulate a burst over several frames.
|
||||
int active_burst_packets_;
|
||||
rtc::CriticalSection critsect_;
|
||||
unsigned int random_seed_;
|
||||
bool verbose_;
|
||||
};
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_VIDEO_CODING_CODECS_TEST_PACKET_MANIPULATOR_H_
|
||||
148
modules/video_coding/codecs/test/packet_manipulator_unittest.cc
Normal file
148
modules/video_coding/codecs/test/packet_manipulator_unittest.cc
Normal file
@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "webrtc/modules/video_coding/codecs/test/packet_manipulator.h"
|
||||
|
||||
#include <queue>
|
||||
|
||||
#include "webrtc/modules/video_coding/codecs/test/predictive_packet_manipulator.h"
|
||||
#include "webrtc/modules/video_coding/include/video_codec_interface.h"
|
||||
#include "webrtc/test/gtest.h"
|
||||
#include "webrtc/test/testsupport/unittest_utils.h"
|
||||
#include "webrtc/typedefs.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace test {
|
||||
|
||||
const double kNeverDropProbability = 0.0;
|
||||
const double kAlwaysDropProbability = 1.0;
|
||||
const int kBurstLength = 1;
|
||||
|
||||
class PacketManipulatorTest : public PacketRelatedTest {
|
||||
protected:
|
||||
PacketReader packet_reader_;
|
||||
EncodedImage image_;
|
||||
NetworkingConfig drop_config_;
|
||||
NetworkingConfig no_drop_config_;
|
||||
|
||||
PacketManipulatorTest() {
|
||||
image_._buffer = packet_data_;
|
||||
image_._length = kPacketDataLength;
|
||||
image_._size = kPacketDataLength;
|
||||
|
||||
drop_config_.packet_size_in_bytes = kPacketSizeInBytes;
|
||||
drop_config_.packet_loss_probability = kAlwaysDropProbability;
|
||||
drop_config_.packet_loss_burst_length = kBurstLength;
|
||||
drop_config_.packet_loss_mode = kUniform;
|
||||
|
||||
no_drop_config_.packet_size_in_bytes = kPacketSizeInBytes;
|
||||
no_drop_config_.packet_loss_probability = kNeverDropProbability;
|
||||
no_drop_config_.packet_loss_burst_length = kBurstLength;
|
||||
no_drop_config_.packet_loss_mode = kUniform;
|
||||
}
|
||||
|
||||
virtual ~PacketManipulatorTest() {}
|
||||
|
||||
void SetUp() { PacketRelatedTest::SetUp(); }
|
||||
|
||||
void TearDown() { PacketRelatedTest::TearDown(); }
|
||||
|
||||
void VerifyPacketLoss(int expected_nbr_packets_dropped,
|
||||
int actual_nbr_packets_dropped,
|
||||
size_t expected_packet_data_length,
|
||||
uint8_t* expected_packet_data,
|
||||
const EncodedImage& actual_image) {
|
||||
EXPECT_EQ(expected_nbr_packets_dropped, actual_nbr_packets_dropped);
|
||||
EXPECT_EQ(expected_packet_data_length, image_._length);
|
||||
EXPECT_EQ(0, memcmp(expected_packet_data, actual_image._buffer,
|
||||
expected_packet_data_length));
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(PacketManipulatorTest, Constructor) {
|
||||
PacketManipulatorImpl manipulator(&packet_reader_, no_drop_config_, false);
|
||||
}
|
||||
|
||||
TEST_F(PacketManipulatorTest, DropNone) {
|
||||
PacketManipulatorImpl manipulator(&packet_reader_, no_drop_config_, false);
|
||||
int nbr_packets_dropped = manipulator.ManipulatePackets(&image_);
|
||||
VerifyPacketLoss(0, nbr_packets_dropped, kPacketDataLength, packet_data_,
|
||||
image_);
|
||||
}
|
||||
|
||||
TEST_F(PacketManipulatorTest, UniformDropNoneSmallFrame) {
|
||||
size_t data_length = 400; // smaller than the packet size
|
||||
image_._length = data_length;
|
||||
PacketManipulatorImpl manipulator(&packet_reader_, no_drop_config_, false);
|
||||
int nbr_packets_dropped = manipulator.ManipulatePackets(&image_);
|
||||
|
||||
VerifyPacketLoss(0, nbr_packets_dropped, data_length, packet_data_, image_);
|
||||
}
|
||||
|
||||
TEST_F(PacketManipulatorTest, UniformDropAll) {
|
||||
PacketManipulatorImpl manipulator(&packet_reader_, drop_config_, false);
|
||||
int nbr_packets_dropped = manipulator.ManipulatePackets(&image_);
|
||||
VerifyPacketLoss(kPacketDataNumberOfPackets, nbr_packets_dropped, 0,
|
||||
packet_data_, image_);
|
||||
}
|
||||
|
||||
// Use our customized test class to make the second packet being lost
|
||||
TEST_F(PacketManipulatorTest, UniformDropSinglePacket) {
|
||||
drop_config_.packet_loss_probability = 0.5;
|
||||
PredictivePacketManipulator manipulator(&packet_reader_, drop_config_);
|
||||
manipulator.AddRandomResult(1.0);
|
||||
manipulator.AddRandomResult(0.3); // less than 0.5 will cause packet loss
|
||||
manipulator.AddRandomResult(1.0);
|
||||
|
||||
// Execute the test target method:
|
||||
int nbr_packets_dropped = manipulator.ManipulatePackets(&image_);
|
||||
|
||||
// Since we setup the predictive packet manipulator, it will throw away the
|
||||
// second packet. The third packet is also lost because when we have lost one,
|
||||
// the remains shall also be discarded (in the current implementation).
|
||||
VerifyPacketLoss(2, nbr_packets_dropped, kPacketSizeInBytes, packet1_,
|
||||
image_);
|
||||
}
|
||||
|
||||
// Use our customized test class to make the second packet being lost
|
||||
TEST_F(PacketManipulatorTest, BurstDropNinePackets) {
|
||||
// Create a longer packet data structure (10 packets)
|
||||
const int kNbrPackets = 10;
|
||||
const size_t kDataLength = kPacketSizeInBytes * kNbrPackets;
|
||||
uint8_t data[kDataLength];
|
||||
uint8_t* data_pointer = data;
|
||||
// Fill with 0s, 1s and so on to be able to easily verify which were dropped:
|
||||
for (int i = 0; i < kNbrPackets; ++i) {
|
||||
memset(data_pointer + i * kPacketSizeInBytes, i, kPacketSizeInBytes);
|
||||
}
|
||||
// Overwrite the defaults from the test fixture:
|
||||
image_._buffer = data;
|
||||
image_._length = kDataLength;
|
||||
image_._size = kDataLength;
|
||||
|
||||
drop_config_.packet_loss_probability = 0.5;
|
||||
drop_config_.packet_loss_burst_length = 5;
|
||||
drop_config_.packet_loss_mode = kBurst;
|
||||
PredictivePacketManipulator manipulator(&packet_reader_, drop_config_);
|
||||
manipulator.AddRandomResult(1.0);
|
||||
manipulator.AddRandomResult(0.3); // less than 0.5 will cause packet loss
|
||||
for (int i = 0; i < kNbrPackets - 2; ++i) {
|
||||
manipulator.AddRandomResult(1.0);
|
||||
}
|
||||
|
||||
// Execute the test target method:
|
||||
int nbr_packets_dropped = manipulator.ManipulatePackets(&image_);
|
||||
|
||||
// Should discard every packet after the first one.
|
||||
VerifyPacketLoss(9, nbr_packets_dropped, kPacketSizeInBytes, data, image_);
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
452
modules/video_coding/codecs/test/plot_webrtc_test_logs.py
Executable file
452
modules/video_coding/codecs/test/plot_webrtc_test_logs.py
Executable file
@ -0,0 +1,452 @@
|
||||
# Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
|
||||
#
|
||||
# Use of this source code is governed by a BSD-style license
|
||||
# that can be found in the LICENSE file in the root of the source
|
||||
# tree. An additional intellectual property rights grant can be found
|
||||
# in the file PATENTS. All contributing project authors may
|
||||
# be found in the AUTHORS file in the root of the source tree.
|
||||
|
||||
"""Plots statistics from WebRTC integration test logs.
|
||||
|
||||
Usage: $ python plot_webrtc_test_logs.py filename.txt
|
||||
"""
|
||||
|
||||
import numpy
|
||||
import sys
|
||||
import re
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
# Log events.
|
||||
EVENT_START = \
|
||||
'RUN ] CodecSettings/VideoProcessorIntegrationTestParameterized.'
|
||||
EVENT_END = 'OK ] CodecSettings/VideoProcessorIntegrationTestParameterized.'
|
||||
|
||||
# Metrics to plot, tuple: (name to parse in file, label to use when plotting).
|
||||
BITRATE = ('Target bitrate', 'target bitrate (kbps)')
|
||||
WIDTH = ('Width', 'width')
|
||||
HEIGHT = ('Height', 'height')
|
||||
FILENAME = ('Filename', 'clip')
|
||||
CODEC_TYPE = ('Codec type', 'Codec')
|
||||
ENCODER_IMPLEMENTATION_NAME = ('Encoder implementation name', 'enc name')
|
||||
DECODER_IMPLEMENTATION_NAME = ('Decoder implementation name', 'dec name')
|
||||
CODEC_IMPLEMENTATION_NAME = ('Codec implementation name', 'codec name')
|
||||
CORES = ('# CPU cores used', 'CPU cores used')
|
||||
DENOISING = ('Denoising', 'denoising')
|
||||
RESILIENCE = ('Resilience', 'resilience')
|
||||
ERROR_CONCEALMENT = ('Error concealment', 'error concealment')
|
||||
QP = ('Average QP', 'avg QP')
|
||||
PSNR = ('PSNR avg', 'PSNR (dB)')
|
||||
SSIM = ('SSIM avg', 'SSIM')
|
||||
ENC_BITRATE = ('Encoded bitrate', 'encoded bitrate (kbps)')
|
||||
FRAMERATE = ('Frame rate', 'fps')
|
||||
NUM_FRAMES = ('# processed frames', 'num frames')
|
||||
NUM_DROPPED_FRAMES = ('# dropped frames', 'num dropped frames')
|
||||
NUM_FRAMES_TO_TARGET = ('# frames to convergence',
|
||||
'frames to reach target rate')
|
||||
ENCODE_TIME = ('Encoding time', 'encode time (us)')
|
||||
ENCODE_TIME_AVG = ('Encoding time', 'encode time (us) avg')
|
||||
DECODE_TIME = ('Decoding time', 'decode time (us)')
|
||||
DECODE_TIME_AVG = ('Decoding time', 'decode time (us) avg')
|
||||
FRAME_SIZE = ('Frame sizes', 'frame size (bytes)')
|
||||
FRAME_SIZE_AVG = ('Frame sizes', 'frame size (bytes) avg')
|
||||
AVG_KEY_FRAME_SIZE = ('Average key frame size', 'avg key frame size (bytes)')
|
||||
AVG_NON_KEY_FRAME_SIZE = ('Average non-key frame size',
|
||||
'avg non-key frame size (bytes)')
|
||||
|
||||
# Settings.
|
||||
SETTINGS = [
|
||||
WIDTH,
|
||||
HEIGHT,
|
||||
FILENAME,
|
||||
NUM_FRAMES,
|
||||
ENCODE_TIME,
|
||||
DECODE_TIME,
|
||||
FRAME_SIZE,
|
||||
]
|
||||
|
||||
# Settings, options for x-axis.
|
||||
X_SETTINGS = [
|
||||
CORES,
|
||||
FRAMERATE,
|
||||
DENOISING,
|
||||
RESILIENCE,
|
||||
ERROR_CONCEALMENT,
|
||||
BITRATE, # TODO(asapersson): Needs to be last.
|
||||
]
|
||||
|
||||
# Settings, options for subplots.
|
||||
SUBPLOT_SETTINGS = [
|
||||
CODEC_TYPE,
|
||||
ENCODER_IMPLEMENTATION_NAME,
|
||||
DECODER_IMPLEMENTATION_NAME,
|
||||
CODEC_IMPLEMENTATION_NAME,
|
||||
] + X_SETTINGS
|
||||
|
||||
# Results.
|
||||
RESULTS = [
|
||||
PSNR,
|
||||
SSIM,
|
||||
ENC_BITRATE,
|
||||
NUM_DROPPED_FRAMES,
|
||||
NUM_FRAMES_TO_TARGET,
|
||||
ENCODE_TIME_AVG,
|
||||
DECODE_TIME_AVG,
|
||||
QP,
|
||||
AVG_KEY_FRAME_SIZE,
|
||||
AVG_NON_KEY_FRAME_SIZE,
|
||||
]
|
||||
|
||||
METRICS_TO_PARSE = SETTINGS + SUBPLOT_SETTINGS + RESULTS
|
||||
|
||||
Y_METRICS = [res[1] for res in RESULTS]
|
||||
|
||||
# Parameters for plotting.
|
||||
FIG_SIZE_SCALE_FACTOR_X = 1.6
|
||||
FIG_SIZE_SCALE_FACTOR_Y = 1.8
|
||||
GRID_COLOR = [0.45, 0.45, 0.45]
|
||||
|
||||
|
||||
def ParseSetting(filename, setting):
|
||||
"""Parses setting from file.
|
||||
|
||||
Args:
|
||||
filename: The name of the file.
|
||||
setting: Name of setting to parse (e.g. width).
|
||||
|
||||
Returns:
|
||||
A list holding parsed settings, e.g. ['width: 128.0', 'width: 160.0'] """
|
||||
|
||||
settings = []
|
||||
|
||||
settings_file = open(filename)
|
||||
while True:
|
||||
line = settings_file.readline()
|
||||
if not line:
|
||||
break
|
||||
if re.search(r'%s' % EVENT_START, line):
|
||||
# Parse event.
|
||||
parsed = {}
|
||||
while True:
|
||||
line = settings_file.readline()
|
||||
if not line:
|
||||
break
|
||||
if re.search(r'%s' % EVENT_END, line):
|
||||
# Add parsed setting to list.
|
||||
if setting in parsed:
|
||||
s = setting + ': ' + str(parsed[setting])
|
||||
if s not in settings:
|
||||
settings.append(s)
|
||||
break
|
||||
|
||||
TryFindMetric(parsed, line, settings_file)
|
||||
|
||||
settings_file.close()
|
||||
return settings
|
||||
|
||||
|
||||
def ParseMetrics(filename, setting1, setting2):
|
||||
"""Parses metrics from file.
|
||||
|
||||
Args:
|
||||
filename: The name of the file.
|
||||
setting1: First setting for sorting metrics (e.g. width).
|
||||
setting2: Second setting for sorting metrics (e.g. CPU cores used).
|
||||
|
||||
Returns:
|
||||
A dictionary holding parsed metrics.
|
||||
|
||||
For example:
|
||||
metrics[key1][key2][measurement]
|
||||
|
||||
metrics = {
|
||||
"width: 352": {
|
||||
"CPU cores used: 1.0": {
|
||||
"encode time (us)": [0.718005, 0.806925, 0.909726, 0.931835, 0.953642],
|
||||
"PSNR (dB)": [25.546029, 29.465518, 34.723535, 36.428493, 38.686551],
|
||||
"bitrate (kbps)": [50, 100, 300, 500, 1000]
|
||||
},
|
||||
"CPU cores used: 2.0": {
|
||||
"encode time (us)": [0.718005, 0.806925, 0.909726, 0.931835, 0.953642],
|
||||
"PSNR (dB)": [25.546029, 29.465518, 34.723535, 36.428493, 38.686551],
|
||||
"bitrate (kbps)": [50, 100, 300, 500, 1000]
|
||||
},
|
||||
},
|
||||
"width: 176": {
|
||||
"CPU cores used: 1.0": {
|
||||
"encode time (us)": [0.857897, 0.91608, 0.959173, 0.971116, 0.980961],
|
||||
"PSNR (dB)": [30.243646, 33.375592, 37.574387, 39.42184, 41.437897],
|
||||
"bitrate (kbps)": [50, 100, 300, 500, 1000]
|
||||
},
|
||||
}
|
||||
} """
|
||||
|
||||
metrics = {}
|
||||
|
||||
# Parse events.
|
||||
settings_file = open(filename)
|
||||
while True:
|
||||
line = settings_file.readline()
|
||||
if not line:
|
||||
break
|
||||
if re.search(r'%s' % EVENT_START, line):
|
||||
# Parse event.
|
||||
parsed = {}
|
||||
while True:
|
||||
line = settings_file.readline()
|
||||
if not line:
|
||||
break
|
||||
if re.search(r'%s' % EVENT_END, line):
|
||||
# Add parsed values to metrics.
|
||||
key1 = setting1 + ': ' + str(parsed[setting1])
|
||||
key2 = setting2 + ': ' + str(parsed[setting2])
|
||||
if key1 not in metrics:
|
||||
metrics[key1] = {}
|
||||
if key2 not in metrics[key1]:
|
||||
metrics[key1][key2] = {}
|
||||
|
||||
for label in parsed:
|
||||
if label not in metrics[key1][key2]:
|
||||
metrics[key1][key2][label] = []
|
||||
metrics[key1][key2][label].append(parsed[label])
|
||||
|
||||
break
|
||||
|
||||
TryFindMetric(parsed, line, settings_file)
|
||||
|
||||
settings_file.close()
|
||||
return metrics
|
||||
|
||||
|
||||
def TryFindMetric(parsed, line, settings_file):
|
||||
for metric in METRICS_TO_PARSE:
|
||||
name = metric[0]
|
||||
label = metric[1]
|
||||
if re.search(r'%s' % name, line):
|
||||
found, value = GetMetric(name, line)
|
||||
if not found:
|
||||
# TODO(asapersson): Change format.
|
||||
# Try find min, max, average stats.
|
||||
found, minimum = GetMetric("Min", settings_file.readline())
|
||||
if not found:
|
||||
return
|
||||
found, maximum = GetMetric("Max", settings_file.readline())
|
||||
if not found:
|
||||
return
|
||||
found, average = GetMetric("Average", settings_file.readline())
|
||||
if not found:
|
||||
return
|
||||
|
||||
parsed[label + ' min'] = minimum
|
||||
parsed[label + ' max'] = maximum
|
||||
parsed[label + ' avg'] = average
|
||||
|
||||
parsed[label] = value
|
||||
return
|
||||
|
||||
|
||||
def GetMetric(name, string):
|
||||
# Float (e.g. bitrate = 98.8253).
|
||||
pattern = r'%s\s*[:=]\s*([+-]?\d+\.*\d*)' % name
|
||||
m = re.search(r'%s' % pattern, string)
|
||||
if m is not None:
|
||||
return StringToFloat(m.group(1))
|
||||
|
||||
# Alphanumeric characters (e.g. codec type : VP8).
|
||||
pattern = r'%s\s*[:=]\s*(\w+)' % name
|
||||
m = re.search(r'%s' % pattern, string)
|
||||
if m is not None:
|
||||
return True, m.group(1)
|
||||
|
||||
return False, -1
|
||||
|
||||
|
||||
def StringToFloat(value):
|
||||
try:
|
||||
value = float(value)
|
||||
except ValueError:
|
||||
print "Not a float, skipped %s" % value
|
||||
return False, -1
|
||||
|
||||
return True, value
|
||||
|
||||
|
||||
def Plot(y_metric, x_metric, metrics):
|
||||
"""Plots y_metric vs x_metric per key in metrics.
|
||||
|
||||
For example:
|
||||
y_metric = 'PSNR (dB)'
|
||||
x_metric = 'bitrate (kbps)'
|
||||
metrics = {
|
||||
"CPU cores used: 1.0": {
|
||||
"PSNR (dB)": [25.546029, 29.465518, 34.723535, 36.428493, 38.686551],
|
||||
"bitrate (kbps)": [50, 100, 300, 500, 1000]
|
||||
},
|
||||
"CPU cores used: 2.0": {
|
||||
"PSNR (dB)": [25.546029, 29.465518, 34.723535, 36.428493, 38.686551],
|
||||
"bitrate (kbps)": [50, 100, 300, 500, 1000]
|
||||
},
|
||||
}
|
||||
"""
|
||||
for key in sorted(metrics):
|
||||
data = metrics[key]
|
||||
if y_metric not in data:
|
||||
print "Failed to find metric: %s" % y_metric
|
||||
continue
|
||||
|
||||
y = numpy.array(data[y_metric])
|
||||
x = numpy.array(data[x_metric])
|
||||
if len(y) != len(x):
|
||||
print "Length mismatch for %s, %s" % (y, x)
|
||||
continue
|
||||
|
||||
label = y_metric + ' - ' + str(key)
|
||||
|
||||
plt.plot(x, y, label=label, linewidth=1.5, marker='o', markersize=5,
|
||||
markeredgewidth=0.0)
|
||||
|
||||
|
||||
def PlotFigure(settings, y_metrics, x_metric, metrics, title):
|
||||
"""Plots metrics in y_metrics list. One figure is plotted and each entry
|
||||
in the list is plotted in a subplot (and sorted per settings).
|
||||
|
||||
For example:
|
||||
settings = ['width: 128.0', 'width: 160.0']. Sort subplot per setting.
|
||||
y_metrics = ['PSNR (dB)', 'PSNR (dB)']. Metric to plot per subplot.
|
||||
x_metric = 'bitrate (kbps)'
|
||||
|
||||
"""
|
||||
|
||||
plt.figure()
|
||||
plt.suptitle(title, fontsize='large', fontweight='bold')
|
||||
settings.sort()
|
||||
rows = len(settings)
|
||||
cols = 1
|
||||
pos = 1
|
||||
while pos <= rows:
|
||||
plt.rc('grid', color=GRID_COLOR)
|
||||
ax = plt.subplot(rows, cols, pos)
|
||||
plt.grid()
|
||||
plt.setp(ax.get_xticklabels(), visible=(pos == rows), fontsize='large')
|
||||
plt.setp(ax.get_yticklabels(), fontsize='large')
|
||||
setting = settings[pos - 1]
|
||||
Plot(y_metrics[pos - 1], x_metric, metrics[setting])
|
||||
if setting.startswith(WIDTH[1]):
|
||||
plt.title(setting, fontsize='medium')
|
||||
plt.legend(fontsize='large', loc='best')
|
||||
pos += 1
|
||||
|
||||
plt.xlabel(x_metric, fontsize='large')
|
||||
plt.subplots_adjust(left=0.06, right=0.98, bottom=0.05, top=0.94, hspace=0.08)
|
||||
|
||||
|
||||
def GetTitle(filename, setting):
|
||||
title = ''
|
||||
if setting != CODEC_IMPLEMENTATION_NAME[1] and setting != CODEC_TYPE[1]:
|
||||
codec_types = ParseSetting(filename, CODEC_TYPE[1])
|
||||
for i in range(0, len(codec_types)):
|
||||
title += codec_types[i] + ', '
|
||||
|
||||
if setting != CORES[1]:
|
||||
cores = ParseSetting(filename, CORES[1])
|
||||
for i in range(0, len(cores)):
|
||||
title += cores[i].split('.')[0] + ', '
|
||||
|
||||
if setting != FRAMERATE[1]:
|
||||
framerate = ParseSetting(filename, FRAMERATE[1])
|
||||
for i in range(0, len(framerate)):
|
||||
title += framerate[i].split('.')[0] + ', '
|
||||
|
||||
if (setting != CODEC_IMPLEMENTATION_NAME[1] and
|
||||
setting != ENCODER_IMPLEMENTATION_NAME[1]):
|
||||
enc_names = ParseSetting(filename, ENCODER_IMPLEMENTATION_NAME[1])
|
||||
for i in range(0, len(enc_names)):
|
||||
title += enc_names[i] + ', '
|
||||
|
||||
if (setting != CODEC_IMPLEMENTATION_NAME[1] and
|
||||
setting != DECODER_IMPLEMENTATION_NAME[1]):
|
||||
dec_names = ParseSetting(filename, DECODER_IMPLEMENTATION_NAME[1])
|
||||
for i in range(0, len(dec_names)):
|
||||
title += dec_names[i] + ', '
|
||||
|
||||
filenames = ParseSetting(filename, FILENAME[1])
|
||||
title += filenames[0].split('_')[0]
|
||||
|
||||
num_frames = ParseSetting(filename, NUM_FRAMES[1])
|
||||
for i in range(0, len(num_frames)):
|
||||
title += ' (' + num_frames[i].split('.')[0] + ')'
|
||||
|
||||
return title
|
||||
|
||||
|
||||
def ToString(input_list):
|
||||
return ToStringWithoutMetric(input_list, ('', ''))
|
||||
|
||||
|
||||
def ToStringWithoutMetric(input_list, metric):
|
||||
i = 1
|
||||
output_str = ""
|
||||
for m in input_list:
|
||||
if m != metric:
|
||||
output_str = output_str + ("%s. %s\n" % (i, m[1]))
|
||||
i += 1
|
||||
return output_str
|
||||
|
||||
|
||||
def GetIdx(text_list):
|
||||
return int(raw_input(text_list)) - 1
|
||||
|
||||
|
||||
def main():
|
||||
filename = sys.argv[1]
|
||||
|
||||
# Setup.
|
||||
idx_metric = GetIdx("Choose metric:\n0. All\n%s" % ToString(RESULTS))
|
||||
if idx_metric == -1:
|
||||
# Plot all metrics. One subplot for each metric.
|
||||
# Per subplot: metric vs bitrate (per resolution).
|
||||
cores = ParseSetting(filename, CORES[1])
|
||||
setting1 = CORES[1]
|
||||
setting2 = WIDTH[1]
|
||||
sub_keys = [cores[0]] * len(Y_METRICS)
|
||||
y_metrics = Y_METRICS
|
||||
x_metric = BITRATE[1]
|
||||
else:
|
||||
resolutions = ParseSetting(filename, WIDTH[1])
|
||||
idx = GetIdx("Select metric for x-axis:\n%s" % ToString(X_SETTINGS))
|
||||
if X_SETTINGS[idx] == BITRATE:
|
||||
idx = GetIdx("Plot per:\n%s" % ToStringWithoutMetric(SUBPLOT_SETTINGS,
|
||||
BITRATE))
|
||||
idx_setting = METRICS_TO_PARSE.index(SUBPLOT_SETTINGS[idx])
|
||||
# Plot one metric. One subplot for each resolution.
|
||||
# Per subplot: metric vs bitrate (per setting).
|
||||
setting1 = WIDTH[1]
|
||||
setting2 = METRICS_TO_PARSE[idx_setting][1]
|
||||
sub_keys = resolutions
|
||||
y_metrics = [RESULTS[idx_metric][1]] * len(sub_keys)
|
||||
x_metric = BITRATE[1]
|
||||
else:
|
||||
# Plot one metric. One subplot for each resolution.
|
||||
# Per subplot: metric vs setting (per bitrate).
|
||||
setting1 = WIDTH[1]
|
||||
setting2 = BITRATE[1]
|
||||
sub_keys = resolutions
|
||||
y_metrics = [RESULTS[idx_metric][1]] * len(sub_keys)
|
||||
x_metric = X_SETTINGS[idx][1]
|
||||
|
||||
metrics = ParseMetrics(filename, setting1, setting2)
|
||||
|
||||
# Stretch fig size.
|
||||
figsize = plt.rcParams["figure.figsize"]
|
||||
figsize[0] *= FIG_SIZE_SCALE_FACTOR_X
|
||||
figsize[1] *= FIG_SIZE_SCALE_FACTOR_Y
|
||||
plt.rcParams["figure.figsize"] = figsize
|
||||
|
||||
PlotFigure(sub_keys, y_metrics, x_metric, metrics,
|
||||
GetTitle(filename, setting2))
|
||||
|
||||
plt.show()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "webrtc/modules/video_coding/codecs/test/predictive_packet_manipulator.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "webrtc/test/testsupport/packet_reader.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace test {
|
||||
|
||||
PredictivePacketManipulator::PredictivePacketManipulator(
|
||||
PacketReader* packet_reader,
|
||||
const NetworkingConfig& config)
|
||||
: PacketManipulatorImpl(packet_reader, config, false) {}
|
||||
|
||||
PredictivePacketManipulator::~PredictivePacketManipulator() {}
|
||||
|
||||
void PredictivePacketManipulator::AddRandomResult(double result) {
|
||||
assert(result >= 0.0 && result <= 1.0);
|
||||
random_results_.push(result);
|
||||
}
|
||||
|
||||
double PredictivePacketManipulator::RandomUniform() {
|
||||
if (random_results_.size() == 0u) {
|
||||
fprintf(stderr,
|
||||
"No more stored results, please make sure AddRandomResult()"
|
||||
"is called same amount of times you're going to invoke the "
|
||||
"RandomUniform() function, i.e. once per packet.\n");
|
||||
assert(false);
|
||||
}
|
||||
double result = random_results_.front();
|
||||
random_results_.pop();
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_VIDEO_CODING_CODECS_TEST_PREDICTIVE_PACKET_MANIPULATOR_H_
|
||||
#define WEBRTC_MODULES_VIDEO_CODING_CODECS_TEST_PREDICTIVE_PACKET_MANIPULATOR_H_
|
||||
|
||||
#include <queue>
|
||||
|
||||
#include "webrtc/modules/video_coding/codecs/test/packet_manipulator.h"
|
||||
#include "webrtc/test/testsupport/packet_reader.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace test {
|
||||
|
||||
// Predictive packet manipulator that allows for setup of the result of
|
||||
// the random invocations.
|
||||
class PredictivePacketManipulator : public PacketManipulatorImpl {
|
||||
public:
|
||||
PredictivePacketManipulator(PacketReader* packet_reader,
|
||||
const NetworkingConfig& config);
|
||||
virtual ~PredictivePacketManipulator();
|
||||
// Adds a result. You must add at least the same number of results as the
|
||||
// expected calls to the RandomUniform method. The results are added to a
|
||||
// FIFO queue so they will be returned in the same order they were added.
|
||||
// Result parameter must be 0.0 to 1.0.
|
||||
void AddRandomResult(double result);
|
||||
|
||||
protected:
|
||||
// Returns a uniformly distributed random value between 0.0 and 1.0
|
||||
double RandomUniform() override;
|
||||
|
||||
private:
|
||||
std::queue<double> random_results_;
|
||||
};
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_VIDEO_CODING_CODECS_TEST_PREDICTIVE_PACKET_MANIPULATOR_H_
|
||||
180
modules/video_coding/codecs/test/stats.cc
Normal file
180
modules/video_coding/codecs/test/stats.cc
Normal file
@ -0,0 +1,180 @@
|
||||
/*
|
||||
* Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "webrtc/modules/video_coding/codecs/test/stats.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "webrtc/rtc_base/checks.h"
|
||||
#include "webrtc/rtc_base/format_macros.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace test {
|
||||
|
||||
namespace {
|
||||
|
||||
bool LessForEncodeTime(const FrameStatistic& s1, const FrameStatistic& s2) {
|
||||
RTC_DCHECK_NE(s1.frame_number, s2.frame_number);
|
||||
return s1.encode_time_us < s2.encode_time_us;
|
||||
}
|
||||
|
||||
bool LessForDecodeTime(const FrameStatistic& s1, const FrameStatistic& s2) {
|
||||
RTC_DCHECK_NE(s1.frame_number, s2.frame_number);
|
||||
return s1.decode_time_us < s2.decode_time_us;
|
||||
}
|
||||
|
||||
bool LessForEncodedSize(const FrameStatistic& s1, const FrameStatistic& s2) {
|
||||
RTC_DCHECK_NE(s1.frame_number, s2.frame_number);
|
||||
return s1.encoded_frame_size_bytes < s2.encoded_frame_size_bytes;
|
||||
}
|
||||
|
||||
bool LessForBitRate(const FrameStatistic& s1, const FrameStatistic& s2) {
|
||||
RTC_DCHECK_NE(s1.frame_number, s2.frame_number);
|
||||
return s1.bitrate_kbps < s2.bitrate_kbps;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
FrameStatistic* Stats::AddFrame() {
|
||||
// We don't expect more frames than what can be stored in an int.
|
||||
stats_.emplace_back(static_cast<int>(stats_.size()));
|
||||
return &stats_.back();
|
||||
}
|
||||
|
||||
FrameStatistic* Stats::GetFrame(int frame_number) {
|
||||
RTC_CHECK_GE(frame_number, 0);
|
||||
RTC_CHECK_LT(frame_number, stats_.size());
|
||||
return &stats_[frame_number];
|
||||
}
|
||||
|
||||
size_t Stats::size() const {
|
||||
return stats_.size();
|
||||
}
|
||||
|
||||
void Stats::PrintSummary() const {
|
||||
if (stats_.empty()) {
|
||||
printf("No frame statistics have been logged yet.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate min, max, average and total encoding time.
|
||||
int total_encoding_time_us = 0;
|
||||
int total_decoding_time_us = 0;
|
||||
size_t total_encoded_frame_size_bytes = 0;
|
||||
size_t total_encoded_key_frame_size_bytes = 0;
|
||||
size_t total_encoded_delta_frame_size_bytes = 0;
|
||||
size_t num_key_frames = 0;
|
||||
size_t num_delta_frames = 0;
|
||||
|
||||
for (const FrameStatistic& stat : stats_) {
|
||||
total_encoding_time_us += stat.encode_time_us;
|
||||
total_decoding_time_us += stat.decode_time_us;
|
||||
total_encoded_frame_size_bytes += stat.encoded_frame_size_bytes;
|
||||
if (stat.frame_type == webrtc::kVideoFrameKey) {
|
||||
total_encoded_key_frame_size_bytes += stat.encoded_frame_size_bytes;
|
||||
++num_key_frames;
|
||||
} else {
|
||||
total_encoded_delta_frame_size_bytes += stat.encoded_frame_size_bytes;
|
||||
++num_delta_frames;
|
||||
}
|
||||
}
|
||||
|
||||
// Encoding stats.
|
||||
printf("Encoding time:\n");
|
||||
auto frame_it =
|
||||
std::min_element(stats_.begin(), stats_.end(), LessForEncodeTime);
|
||||
printf(" Min : %7d us (frame %d)\n", frame_it->encode_time_us,
|
||||
frame_it->frame_number);
|
||||
frame_it = std::max_element(stats_.begin(), stats_.end(), LessForEncodeTime);
|
||||
printf(" Max : %7d us (frame %d)\n", frame_it->encode_time_us,
|
||||
frame_it->frame_number);
|
||||
printf(" Average : %7d us\n",
|
||||
static_cast<int>(total_encoding_time_us / stats_.size()));
|
||||
|
||||
// Decoding stats.
|
||||
printf("Decoding time:\n");
|
||||
// Only consider successfully decoded frames (packet loss may cause failures).
|
||||
std::vector<FrameStatistic> decoded_frames;
|
||||
for (const FrameStatistic& stat : stats_) {
|
||||
if (stat.decoding_successful) {
|
||||
decoded_frames.push_back(stat);
|
||||
}
|
||||
}
|
||||
if (decoded_frames.empty()) {
|
||||
printf("No successfully decoded frames exist in this statistics.\n");
|
||||
} else {
|
||||
frame_it = std::min_element(decoded_frames.begin(), decoded_frames.end(),
|
||||
LessForDecodeTime);
|
||||
printf(" Min : %7d us (frame %d)\n", frame_it->decode_time_us,
|
||||
frame_it->frame_number);
|
||||
frame_it = std::max_element(decoded_frames.begin(), decoded_frames.end(),
|
||||
LessForDecodeTime);
|
||||
printf(" Max : %7d us (frame %d)\n", frame_it->decode_time_us,
|
||||
frame_it->frame_number);
|
||||
printf(" Average : %7d us\n",
|
||||
static_cast<int>(total_decoding_time_us / decoded_frames.size()));
|
||||
printf(" Failures: %d frames failed to decode.\n",
|
||||
static_cast<int>(stats_.size() - decoded_frames.size()));
|
||||
}
|
||||
|
||||
// Frame size stats.
|
||||
printf("Frame sizes:\n");
|
||||
frame_it = std::min_element(stats_.begin(), stats_.end(), LessForEncodedSize);
|
||||
printf(" Min : %7" PRIuS " bytes (frame %d)\n",
|
||||
frame_it->encoded_frame_size_bytes, frame_it->frame_number);
|
||||
frame_it = std::max_element(stats_.begin(), stats_.end(), LessForEncodedSize);
|
||||
printf(" Max : %7" PRIuS " bytes (frame %d)\n",
|
||||
frame_it->encoded_frame_size_bytes, frame_it->frame_number);
|
||||
printf(" Average : %7" PRIuS " bytes\n",
|
||||
total_encoded_frame_size_bytes / stats_.size());
|
||||
if (num_key_frames > 0) {
|
||||
printf(" Average key frame size : %7" PRIuS " bytes (%" PRIuS
|
||||
" keyframes)\n",
|
||||
total_encoded_key_frame_size_bytes / num_key_frames, num_key_frames);
|
||||
}
|
||||
if (num_delta_frames > 0) {
|
||||
printf(" Average non-key frame size: %7" PRIuS " bytes (%" PRIuS
|
||||
" frames)\n",
|
||||
total_encoded_delta_frame_size_bytes / num_delta_frames,
|
||||
num_delta_frames);
|
||||
}
|
||||
|
||||
// Bitrate stats.
|
||||
printf("Bitrates:\n");
|
||||
frame_it = std::min_element(stats_.begin(), stats_.end(), LessForBitRate);
|
||||
printf(" Min bitrate: %7d kbps (frame %d)\n", frame_it->bitrate_kbps,
|
||||
frame_it->frame_number);
|
||||
frame_it = std::max_element(stats_.begin(), stats_.end(), LessForBitRate);
|
||||
printf(" Max bitrate: %7d kbps (frame %d)\n", frame_it->bitrate_kbps,
|
||||
frame_it->frame_number);
|
||||
|
||||
printf("\n");
|
||||
printf("Total encoding time : %7d ms.\n", total_encoding_time_us / 1000);
|
||||
printf("Total decoding time : %7d ms.\n", total_decoding_time_us / 1000);
|
||||
printf("Total processing time: %7d ms.\n",
|
||||
(total_encoding_time_us + total_decoding_time_us) / 1000);
|
||||
|
||||
// QP stats.
|
||||
int total_qp = 0;
|
||||
int total_qp_count = 0;
|
||||
for (const FrameStatistic& stat : stats_) {
|
||||
if (stat.qp >= 0) {
|
||||
total_qp += stat.qp;
|
||||
++total_qp_count;
|
||||
}
|
||||
}
|
||||
int avg_qp = (total_qp_count > 0) ? (total_qp / total_qp_count) : -1;
|
||||
printf("Average QP: %d\n", avg_qp);
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
76
modules/video_coding/codecs/test/stats.h
Normal file
76
modules/video_coding/codecs/test/stats.h
Normal file
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (c) 2011 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_VIDEO_CODING_CODECS_TEST_STATS_H_
|
||||
#define WEBRTC_MODULES_VIDEO_CODING_CODECS_TEST_STATS_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/common_types.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace test {
|
||||
|
||||
// Statistics for one processed frame.
|
||||
struct FrameStatistic {
|
||||
explicit FrameStatistic(int frame_number) : frame_number(frame_number) {}
|
||||
const int frame_number = 0;
|
||||
|
||||
// Encoding.
|
||||
int64_t encode_start_ns = 0;
|
||||
int encode_return_code = 0;
|
||||
bool encoding_successful = false;
|
||||
int encode_time_us = 0;
|
||||
int bitrate_kbps = 0;
|
||||
size_t encoded_frame_size_bytes = 0;
|
||||
webrtc::FrameType frame_type = kVideoFrameDelta;
|
||||
|
||||
// Decoding.
|
||||
int64_t decode_start_ns = 0;
|
||||
int decode_return_code = 0;
|
||||
bool decoding_successful = false;
|
||||
int decode_time_us = 0;
|
||||
int decoded_width = 0;
|
||||
int decoded_height = 0;
|
||||
|
||||
// Quantization.
|
||||
int qp = -1;
|
||||
|
||||
// How many packets were discarded of the encoded frame data (if any).
|
||||
int packets_dropped = 0;
|
||||
size_t total_packets = 0;
|
||||
size_t manipulated_length = 0;
|
||||
};
|
||||
|
||||
// Statistics for a sequence of processed frames. This class is not thread safe.
|
||||
class Stats {
|
||||
public:
|
||||
Stats() = default;
|
||||
~Stats() = default;
|
||||
|
||||
// Creates a FrameStatistic for the next frame to be processed.
|
||||
FrameStatistic* AddFrame();
|
||||
|
||||
// Returns the FrameStatistic corresponding to |frame_number|.
|
||||
FrameStatistic* GetFrame(int frame_number);
|
||||
|
||||
size_t size() const;
|
||||
|
||||
// TODO(brandtr): Add output as CSV.
|
||||
void PrintSummary() const;
|
||||
|
||||
private:
|
||||
std::vector<FrameStatistic> stats_;
|
||||
};
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_VIDEO_CODING_CODECS_TEST_STATS_H_
|
||||
43
modules/video_coding/codecs/test/stats_unittest.cc
Normal file
43
modules/video_coding/codecs/test/stats_unittest.cc
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (c) 2011 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "webrtc/modules/video_coding/codecs/test/stats.h"
|
||||
|
||||
#include "webrtc/test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace test {
|
||||
|
||||
TEST(StatsTest, TestEmptyObject) {
|
||||
Stats stats;
|
||||
stats.PrintSummary(); // Should not crash.
|
||||
}
|
||||
|
||||
TEST(StatsTest, AddSingleFrame) {
|
||||
Stats stats;
|
||||
FrameStatistic* frame_stat = stats.AddFrame();
|
||||
EXPECT_EQ(0, frame_stat->frame_number);
|
||||
EXPECT_EQ(1u, stats.size());
|
||||
}
|
||||
|
||||
TEST(StatsTest, AddMultipleFrames) {
|
||||
Stats stats;
|
||||
const int kNumFrames = 1000;
|
||||
for (int i = 0; i < kNumFrames; ++i) {
|
||||
FrameStatistic* frame_stat = stats.AddFrame();
|
||||
EXPECT_EQ(i, frame_stat->frame_number);
|
||||
}
|
||||
EXPECT_EQ(kNumFrames, static_cast<int>(stats.size()));
|
||||
|
||||
stats.PrintSummary(); // Should not crash.
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
126
modules/video_coding/codecs/test/video_codec_test.cc
Normal file
126
modules/video_coding/codecs/test/video_codec_test.cc
Normal file
@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "webrtc/modules/video_coding/codecs/test/video_codec_test.h"
|
||||
|
||||
#include "webrtc/api/video/i420_buffer.h"
|
||||
#include "webrtc/modules/video_coding/include/video_error_codes.h"
|
||||
#include "webrtc/test/frame_utils.h"
|
||||
#include "webrtc/test/testsupport/fileutils.h"
|
||||
|
||||
static const int kEncodeTimeoutMs = 100;
|
||||
static const int kDecodeTimeoutMs = 25;
|
||||
// Set bitrate to get higher quality.
|
||||
static const int kStartBitrate = 300;
|
||||
static const int kTargetBitrate = 2000;
|
||||
static const int kMaxBitrate = 4000;
|
||||
static const int kWidth = 172; // Width of the input image.
|
||||
static const int kHeight = 144; // Height of the input image.
|
||||
static const int kMaxFramerate = 30; // Arbitrary value.
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
EncodedImageCallback::Result
|
||||
VideoCodecTest::FakeEncodeCompleteCallback::OnEncodedImage(
|
||||
const EncodedImage& frame,
|
||||
const CodecSpecificInfo* codec_specific_info,
|
||||
const RTPFragmentationHeader* fragmentation) {
|
||||
rtc::CritScope lock(&test_->encoded_frame_section_);
|
||||
test_->encoded_frame_.emplace(frame);
|
||||
RTC_DCHECK(codec_specific_info);
|
||||
test_->codec_specific_info_.codecType = codec_specific_info->codecType;
|
||||
// Skip |codec_name|, to avoid allocating.
|
||||
test_->codec_specific_info_.codecSpecific =
|
||||
codec_specific_info->codecSpecific;
|
||||
test_->encoded_frame_event_.Set();
|
||||
return Result(Result::OK);
|
||||
}
|
||||
|
||||
void VideoCodecTest::FakeDecodeCompleteCallback::Decoded(
|
||||
VideoFrame& frame,
|
||||
rtc::Optional<int32_t> decode_time_ms,
|
||||
rtc::Optional<uint8_t> qp) {
|
||||
rtc::CritScope lock(&test_->decoded_frame_section_);
|
||||
test_->decoded_frame_.emplace(frame);
|
||||
test_->decoded_qp_ = qp;
|
||||
test_->decoded_frame_event_.Set();
|
||||
}
|
||||
|
||||
void VideoCodecTest::SetUp() {
|
||||
// Using a QCIF image. Processing only one frame.
|
||||
FILE* source_file_ =
|
||||
fopen(test::ResourcePath("paris_qcif", "yuv").c_str(), "rb");
|
||||
ASSERT_TRUE(source_file_ != NULL);
|
||||
rtc::scoped_refptr<VideoFrameBuffer> video_frame_buffer(
|
||||
test::ReadI420Buffer(kWidth, kHeight, source_file_));
|
||||
input_frame_.reset(new VideoFrame(video_frame_buffer, kVideoRotation_0, 0));
|
||||
fclose(source_file_);
|
||||
|
||||
encoder_.reset(CreateEncoder());
|
||||
decoder_.reset(CreateDecoder());
|
||||
encoder_->RegisterEncodeCompleteCallback(&encode_complete_callback_);
|
||||
decoder_->RegisterDecodeCompleteCallback(&decode_complete_callback_);
|
||||
|
||||
InitCodecs();
|
||||
}
|
||||
|
||||
bool VideoCodecTest::WaitForEncodedFrame(
|
||||
EncodedImage* frame,
|
||||
CodecSpecificInfo* codec_specific_info) {
|
||||
bool ret = encoded_frame_event_.Wait(kEncodeTimeoutMs);
|
||||
EXPECT_TRUE(ret) << "Timed out while waiting for an encoded frame.";
|
||||
// This becomes unsafe if there are multiple threads waiting for frames.
|
||||
rtc::CritScope lock(&encoded_frame_section_);
|
||||
EXPECT_TRUE(encoded_frame_);
|
||||
if (encoded_frame_) {
|
||||
*frame = std::move(*encoded_frame_);
|
||||
encoded_frame_.reset();
|
||||
RTC_DCHECK(codec_specific_info);
|
||||
codec_specific_info->codecType = codec_specific_info_.codecType;
|
||||
codec_specific_info->codecSpecific = codec_specific_info_.codecSpecific;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool VideoCodecTest::WaitForDecodedFrame(std::unique_ptr<VideoFrame>* frame,
|
||||
rtc::Optional<uint8_t>* qp) {
|
||||
bool ret = decoded_frame_event_.Wait(kDecodeTimeoutMs);
|
||||
EXPECT_TRUE(ret) << "Timed out while waiting for a decoded frame.";
|
||||
// This becomes unsafe if there are multiple threads waiting for frames.
|
||||
rtc::CritScope lock(&decoded_frame_section_);
|
||||
EXPECT_TRUE(decoded_frame_);
|
||||
if (decoded_frame_) {
|
||||
frame->reset(new VideoFrame(std::move(*decoded_frame_)));
|
||||
*qp = decoded_qp_;
|
||||
decoded_frame_.reset();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void VideoCodecTest::InitCodecs() {
|
||||
codec_settings_ = codec_settings();
|
||||
codec_settings_.startBitrate = kStartBitrate;
|
||||
codec_settings_.targetBitrate = kTargetBitrate;
|
||||
codec_settings_.maxBitrate = kMaxBitrate;
|
||||
codec_settings_.maxFramerate = kMaxFramerate;
|
||||
codec_settings_.width = kWidth;
|
||||
codec_settings_.height = kHeight;
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
|
||||
encoder_->InitEncode(&codec_settings_, 1 /* number of cores */,
|
||||
0 /* max payload size (unused) */));
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
|
||||
decoder_->InitDecode(&codec_settings_, 1 /* number of cores */));
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
111
modules/video_coding/codecs/test/video_codec_test.h
Normal file
111
modules/video_coding/codecs/test/video_codec_test.h
Normal file
@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_VIDEO_CODING_CODECS_TEST_VIDEO_CODEC_TEST_H_
|
||||
#define WEBRTC_MODULES_VIDEO_CODING_CODECS_TEST_VIDEO_CODEC_TEST_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "webrtc/api/video_codecs/video_decoder.h"
|
||||
#include "webrtc/api/video_codecs/video_encoder.h"
|
||||
#include "webrtc/modules/video_coding/include/video_codec_interface.h"
|
||||
#include "webrtc/modules/video_coding/utility/vp8_header_parser.h"
|
||||
#include "webrtc/modules/video_coding/utility/vp9_uncompressed_header_parser.h"
|
||||
#include "webrtc/rtc_base/criticalsection.h"
|
||||
#include "webrtc/rtc_base/event.h"
|
||||
#include "webrtc/rtc_base/thread_annotations.h"
|
||||
#include "webrtc/test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class VideoCodecTest : public ::testing::Test {
|
||||
public:
|
||||
VideoCodecTest()
|
||||
: encode_complete_callback_(this),
|
||||
decode_complete_callback_(this),
|
||||
encoded_frame_event_(false /* manual reset */,
|
||||
false /* initially signaled */),
|
||||
decoded_frame_event_(false /* manual reset */,
|
||||
false /* initially signaled */) {}
|
||||
|
||||
protected:
|
||||
class FakeEncodeCompleteCallback : public webrtc::EncodedImageCallback {
|
||||
public:
|
||||
explicit FakeEncodeCompleteCallback(VideoCodecTest* test) : test_(test) {}
|
||||
|
||||
Result OnEncodedImage(const EncodedImage& frame,
|
||||
const CodecSpecificInfo* codec_specific_info,
|
||||
const RTPFragmentationHeader* fragmentation);
|
||||
|
||||
private:
|
||||
VideoCodecTest* const test_;
|
||||
};
|
||||
|
||||
class FakeDecodeCompleteCallback : public webrtc::DecodedImageCallback {
|
||||
public:
|
||||
explicit FakeDecodeCompleteCallback(VideoCodecTest* test) : test_(test) {}
|
||||
|
||||
int32_t Decoded(VideoFrame& frame) override {
|
||||
RTC_NOTREACHED();
|
||||
return -1;
|
||||
}
|
||||
int32_t Decoded(VideoFrame& frame, int64_t decode_time_ms) override {
|
||||
RTC_NOTREACHED();
|
||||
return -1;
|
||||
}
|
||||
void Decoded(VideoFrame& frame,
|
||||
rtc::Optional<int32_t> decode_time_ms,
|
||||
rtc::Optional<uint8_t> qp) override;
|
||||
|
||||
private:
|
||||
VideoCodecTest* const test_;
|
||||
};
|
||||
|
||||
virtual VideoEncoder* CreateEncoder() = 0;
|
||||
virtual VideoDecoder* CreateDecoder() = 0;
|
||||
virtual VideoCodec codec_settings() = 0;
|
||||
|
||||
void SetUp() override;
|
||||
|
||||
bool WaitForEncodedFrame(EncodedImage* frame,
|
||||
CodecSpecificInfo* codec_specific_info);
|
||||
bool WaitForDecodedFrame(std::unique_ptr<VideoFrame>* frame,
|
||||
rtc::Optional<uint8_t>* qp);
|
||||
|
||||
// Populated by InitCodecs().
|
||||
VideoCodec codec_settings_;
|
||||
|
||||
std::unique_ptr<VideoFrame> input_frame_;
|
||||
|
||||
std::unique_ptr<VideoEncoder> encoder_;
|
||||
std::unique_ptr<VideoDecoder> decoder_;
|
||||
|
||||
private:
|
||||
void InitCodecs();
|
||||
|
||||
FakeEncodeCompleteCallback encode_complete_callback_;
|
||||
FakeDecodeCompleteCallback decode_complete_callback_;
|
||||
|
||||
rtc::Event encoded_frame_event_;
|
||||
rtc::CriticalSection encoded_frame_section_;
|
||||
rtc::Optional<EncodedImage> encoded_frame_
|
||||
RTC_GUARDED_BY(encoded_frame_section_);
|
||||
CodecSpecificInfo codec_specific_info_ RTC_GUARDED_BY(encoded_frame_section_);
|
||||
|
||||
rtc::Event decoded_frame_event_;
|
||||
rtc::CriticalSection decoded_frame_section_;
|
||||
rtc::Optional<VideoFrame> decoded_frame_
|
||||
RTC_GUARDED_BY(decoded_frame_section_);
|
||||
rtc::Optional<uint8_t> decoded_qp_ RTC_GUARDED_BY(decoded_frame_section_);
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_VIDEO_CODING_CODECS_TEST_VIDEO_CODEC_TEST_H_
|
||||
486
modules/video_coding/codecs/test/videoprocessor.cc
Normal file
486
modules/video_coding/codecs/test/videoprocessor.cc
Normal file
@ -0,0 +1,486 @@
|
||||
/*
|
||||
* Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "webrtc/modules/video_coding/codecs/test/videoprocessor.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/api/video/i420_buffer.h"
|
||||
#include "webrtc/common_types.h"
|
||||
#include "webrtc/modules/video_coding/codecs/vp8/simulcast_rate_allocator.h"
|
||||
#include "webrtc/modules/video_coding/include/video_codec_initializer.h"
|
||||
#include "webrtc/modules/video_coding/utility/default_video_bitrate_allocator.h"
|
||||
#include "webrtc/rtc_base/checks.h"
|
||||
#include "webrtc/rtc_base/logging.h"
|
||||
#include "webrtc/rtc_base/timeutils.h"
|
||||
#include "webrtc/system_wrappers/include/cpu_info.h"
|
||||
#include "webrtc/test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace test {
|
||||
|
||||
namespace {
|
||||
|
||||
const int kRtpClockRateHz = 90000;
|
||||
|
||||
std::unique_ptr<VideoBitrateAllocator> CreateBitrateAllocator(
|
||||
TestConfig* config) {
|
||||
std::unique_ptr<TemporalLayersFactory> tl_factory;
|
||||
if (config->codec_settings.codecType == VideoCodecType::kVideoCodecVP8) {
|
||||
tl_factory.reset(new TemporalLayersFactory());
|
||||
config->codec_settings.VP8()->tl_factory = tl_factory.get();
|
||||
}
|
||||
return std::unique_ptr<VideoBitrateAllocator>(
|
||||
VideoCodecInitializer::CreateBitrateAllocator(config->codec_settings,
|
||||
std::move(tl_factory)));
|
||||
}
|
||||
|
||||
void PrintCodecSettings(const VideoCodec& codec_settings) {
|
||||
printf(" Codec settings:\n");
|
||||
printf(" Codec type : %s\n",
|
||||
CodecTypeToPayloadString(codec_settings.codecType));
|
||||
printf(" Start bitrate : %d kbps\n", codec_settings.startBitrate);
|
||||
printf(" Max bitrate : %d kbps\n", codec_settings.maxBitrate);
|
||||
printf(" Min bitrate : %d kbps\n", codec_settings.minBitrate);
|
||||
printf(" Width : %d\n", codec_settings.width);
|
||||
printf(" Height : %d\n", codec_settings.height);
|
||||
printf(" Max frame rate : %d\n", codec_settings.maxFramerate);
|
||||
printf(" QPmax : %d\n", codec_settings.qpMax);
|
||||
if (codec_settings.codecType == kVideoCodecVP8) {
|
||||
printf(" Complexity : %d\n", codec_settings.VP8().complexity);
|
||||
printf(" Resilience : %d\n", codec_settings.VP8().resilience);
|
||||
printf(" # temporal layers : %d\n",
|
||||
codec_settings.VP8().numberOfTemporalLayers);
|
||||
printf(" Denoising : %d\n", codec_settings.VP8().denoisingOn);
|
||||
printf(" Error concealment : %d\n",
|
||||
codec_settings.VP8().errorConcealmentOn);
|
||||
printf(" Automatic resize : %d\n",
|
||||
codec_settings.VP8().automaticResizeOn);
|
||||
printf(" Frame dropping : %d\n", codec_settings.VP8().frameDroppingOn);
|
||||
printf(" Key frame interval: %d\n", codec_settings.VP8().keyFrameInterval);
|
||||
} else if (codec_settings.codecType == kVideoCodecVP9) {
|
||||
printf(" Complexity : %d\n", codec_settings.VP9().complexity);
|
||||
printf(" Resilience : %d\n", codec_settings.VP9().resilienceOn);
|
||||
printf(" # temporal layers : %d\n",
|
||||
codec_settings.VP9().numberOfTemporalLayers);
|
||||
printf(" Denoising : %d\n", codec_settings.VP9().denoisingOn);
|
||||
printf(" Frame dropping : %d\n", codec_settings.VP9().frameDroppingOn);
|
||||
printf(" Key frame interval: %d\n", codec_settings.VP9().keyFrameInterval);
|
||||
printf(" Adaptive QP mode : %d\n", codec_settings.VP9().adaptiveQpMode);
|
||||
printf(" Automatic resize : %d\n",
|
||||
codec_settings.VP9().automaticResizeOn);
|
||||
printf(" # spatial layers : %d\n",
|
||||
codec_settings.VP9().numberOfSpatialLayers);
|
||||
printf(" Flexible mode : %d\n", codec_settings.VP9().flexibleMode);
|
||||
} else if (codec_settings.codecType == kVideoCodecH264) {
|
||||
printf(" Frame dropping : %d\n", codec_settings.H264().frameDroppingOn);
|
||||
printf(" Key frame interval: %d\n",
|
||||
codec_settings.H264().keyFrameInterval);
|
||||
printf(" Profile : %d\n", codec_settings.H264().profile);
|
||||
}
|
||||
}
|
||||
|
||||
void VerifyQpParser(const EncodedImage& encoded_frame,
|
||||
const TestConfig& config) {
|
||||
if (config.hw_encoder)
|
||||
return;
|
||||
|
||||
int qp;
|
||||
if (config.codec_settings.codecType == kVideoCodecVP8) {
|
||||
ASSERT_TRUE(vp8::GetQp(encoded_frame._buffer, encoded_frame._length, &qp));
|
||||
} else if (config.codec_settings.codecType == kVideoCodecVP9) {
|
||||
ASSERT_TRUE(vp9::GetQp(encoded_frame._buffer, encoded_frame._length, &qp));
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
EXPECT_EQ(encoded_frame.qp_, qp) << "Encoder QP != parsed bitstream QP.";
|
||||
}
|
||||
|
||||
int GetElapsedTimeMicroseconds(int64_t start_ns, int64_t stop_ns) {
|
||||
int64_t diff_us = (stop_ns - start_ns) / rtc::kNumNanosecsPerMicrosec;
|
||||
RTC_DCHECK_GE(diff_us, std::numeric_limits<int>::min());
|
||||
RTC_DCHECK_LE(diff_us, std::numeric_limits<int>::max());
|
||||
return static_cast<int>(diff_us);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
const char* ExcludeFrameTypesToStr(ExcludeFrameTypes e) {
|
||||
switch (e) {
|
||||
case kExcludeOnlyFirstKeyFrame:
|
||||
return "ExcludeOnlyFirstKeyFrame";
|
||||
case kExcludeAllKeyFrames:
|
||||
return "ExcludeAllKeyFrames";
|
||||
default:
|
||||
RTC_NOTREACHED();
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
VideoProcessor::VideoProcessor(webrtc::VideoEncoder* encoder,
|
||||
webrtc::VideoDecoder* decoder,
|
||||
FrameReader* analysis_frame_reader,
|
||||
FrameWriter* analysis_frame_writer,
|
||||
PacketManipulator* packet_manipulator,
|
||||
const TestConfig& config,
|
||||
Stats* stats,
|
||||
IvfFileWriter* encoded_frame_writer,
|
||||
FrameWriter* decoded_frame_writer)
|
||||
: initialized_(false),
|
||||
config_(config),
|
||||
encoder_(encoder),
|
||||
decoder_(decoder),
|
||||
bitrate_allocator_(CreateBitrateAllocator(&config_)),
|
||||
encode_callback_(this),
|
||||
decode_callback_(this),
|
||||
packet_manipulator_(packet_manipulator),
|
||||
analysis_frame_reader_(analysis_frame_reader),
|
||||
analysis_frame_writer_(analysis_frame_writer),
|
||||
encoded_frame_writer_(encoded_frame_writer),
|
||||
decoded_frame_writer_(decoded_frame_writer),
|
||||
last_inputed_frame_num_(-1),
|
||||
last_encoded_frame_num_(-1),
|
||||
last_decoded_frame_num_(-1),
|
||||
first_key_frame_has_been_excluded_(false),
|
||||
last_decoded_frame_buffer_(analysis_frame_reader->FrameLength()),
|
||||
stats_(stats),
|
||||
rate_update_index_(-1) {
|
||||
RTC_DCHECK(encoder);
|
||||
RTC_DCHECK(decoder);
|
||||
RTC_DCHECK(packet_manipulator);
|
||||
RTC_DCHECK(analysis_frame_reader);
|
||||
RTC_DCHECK(analysis_frame_writer);
|
||||
RTC_DCHECK(stats);
|
||||
}
|
||||
|
||||
VideoProcessor::~VideoProcessor() = default;
|
||||
|
||||
void VideoProcessor::Init() {
|
||||
RTC_DCHECK_CALLED_SEQUENTIALLY(&sequence_checker_);
|
||||
RTC_DCHECK(!initialized_) << "VideoProcessor already initialized.";
|
||||
initialized_ = true;
|
||||
|
||||
// Setup required callbacks for the encoder and decoder.
|
||||
RTC_CHECK_EQ(encoder_->RegisterEncodeCompleteCallback(&encode_callback_),
|
||||
WEBRTC_VIDEO_CODEC_OK)
|
||||
<< "Failed to register encode complete callback";
|
||||
RTC_CHECK_EQ(decoder_->RegisterDecodeCompleteCallback(&decode_callback_),
|
||||
WEBRTC_VIDEO_CODEC_OK)
|
||||
<< "Failed to register decode complete callback";
|
||||
|
||||
// Initialize the encoder and decoder.
|
||||
uint32_t num_cores =
|
||||
config_.use_single_core ? 1 : CpuInfo::DetectNumberOfCores();
|
||||
RTC_CHECK_EQ(
|
||||
encoder_->InitEncode(&config_.codec_settings, num_cores,
|
||||
config_.networking_config.max_payload_size_in_bytes),
|
||||
WEBRTC_VIDEO_CODEC_OK)
|
||||
<< "Failed to initialize VideoEncoder";
|
||||
|
||||
RTC_CHECK_EQ(decoder_->InitDecode(&config_.codec_settings, num_cores),
|
||||
WEBRTC_VIDEO_CODEC_OK)
|
||||
<< "Failed to initialize VideoDecoder";
|
||||
|
||||
if (config_.verbose) {
|
||||
printf("Video Processor:\n");
|
||||
printf(" Filename : %s\n", config_.filename.c_str());
|
||||
printf(" Total # of frames: %d\n",
|
||||
analysis_frame_reader_->NumberOfFrames());
|
||||
printf(" # CPU cores used : %d\n", num_cores);
|
||||
const char* encoder_name = encoder_->ImplementationName();
|
||||
printf(" Encoder implementation name: %s\n", encoder_name);
|
||||
const char* decoder_name = decoder_->ImplementationName();
|
||||
printf(" Decoder implementation name: %s\n", decoder_name);
|
||||
if (strcmp(encoder_name, decoder_name) == 0) {
|
||||
printf(" Codec implementation name : %s_%s\n",
|
||||
CodecTypeToPayloadString(config_.codec_settings.codecType),
|
||||
encoder_->ImplementationName());
|
||||
}
|
||||
PrintCodecSettings(config_.codec_settings);
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
void VideoProcessor::Release() {
|
||||
RTC_DCHECK_CALLED_SEQUENTIALLY(&sequence_checker_);
|
||||
|
||||
RTC_CHECK_EQ(encoder_->Release(), WEBRTC_VIDEO_CODEC_OK);
|
||||
RTC_CHECK_EQ(decoder_->Release(), WEBRTC_VIDEO_CODEC_OK);
|
||||
|
||||
encoder_->RegisterEncodeCompleteCallback(nullptr);
|
||||
decoder_->RegisterDecodeCompleteCallback(nullptr);
|
||||
|
||||
initialized_ = false;
|
||||
}
|
||||
|
||||
void VideoProcessor::ProcessFrame() {
|
||||
RTC_DCHECK_CALLED_SEQUENTIALLY(&sequence_checker_);
|
||||
RTC_DCHECK(initialized_) << "VideoProcessor not initialized.";
|
||||
++last_inputed_frame_num_;
|
||||
|
||||
// Get frame from file.
|
||||
rtc::scoped_refptr<I420BufferInterface> buffer(
|
||||
analysis_frame_reader_->ReadFrame());
|
||||
RTC_CHECK(buffer) << "Tried to read too many frames from the file.";
|
||||
// Use the frame number as the basis for timestamp to identify frames. Let the
|
||||
// first timestamp be non-zero, to not make the IvfFileWriter believe that we
|
||||
// want to use capture timestamps in the IVF files.
|
||||
const uint32_t rtp_timestamp = (last_inputed_frame_num_ + 1) *
|
||||
kRtpClockRateHz /
|
||||
config_.codec_settings.maxFramerate;
|
||||
rtp_timestamp_to_frame_num_[rtp_timestamp] = last_inputed_frame_num_;
|
||||
const int64_t kNoRenderTime = 0;
|
||||
VideoFrame source_frame(buffer, rtp_timestamp, kNoRenderTime,
|
||||
webrtc::kVideoRotation_0);
|
||||
|
||||
// Decide if we are going to force a keyframe.
|
||||
std::vector<FrameType> frame_types(1, kVideoFrameDelta);
|
||||
if (config_.keyframe_interval > 0 &&
|
||||
last_inputed_frame_num_ % config_.keyframe_interval == 0) {
|
||||
frame_types[0] = kVideoFrameKey;
|
||||
}
|
||||
|
||||
// Create frame statistics object used for aggregation at end of test run.
|
||||
FrameStatistic* frame_stat = stats_->AddFrame();
|
||||
|
||||
// For the highest measurement accuracy of the encode time, the start/stop
|
||||
// time recordings should wrap the Encode call as tightly as possible.
|
||||
frame_stat->encode_start_ns = rtc::TimeNanos();
|
||||
frame_stat->encode_return_code =
|
||||
encoder_->Encode(source_frame, nullptr, &frame_types);
|
||||
|
||||
if (frame_stat->encode_return_code != WEBRTC_VIDEO_CODEC_OK) {
|
||||
LOG(LS_WARNING) << "Failed to encode frame " << last_inputed_frame_num_
|
||||
<< ", return code: " << frame_stat->encode_return_code
|
||||
<< ".";
|
||||
}
|
||||
}
|
||||
|
||||
void VideoProcessor::SetRates(int bitrate_kbps, int framerate_fps) {
|
||||
RTC_DCHECK_CALLED_SEQUENTIALLY(&sequence_checker_);
|
||||
config_.codec_settings.maxFramerate = framerate_fps;
|
||||
int set_rates_result = encoder_->SetRateAllocation(
|
||||
bitrate_allocator_->GetAllocation(bitrate_kbps * 1000, framerate_fps),
|
||||
framerate_fps);
|
||||
RTC_DCHECK_GE(set_rates_result, 0)
|
||||
<< "Failed to update encoder with new rate " << bitrate_kbps << ".";
|
||||
++rate_update_index_;
|
||||
num_dropped_frames_.push_back(0);
|
||||
num_spatial_resizes_.push_back(0);
|
||||
}
|
||||
|
||||
std::vector<int> VideoProcessor::NumberDroppedFramesPerRateUpdate() const {
|
||||
RTC_DCHECK_CALLED_SEQUENTIALLY(&sequence_checker_);
|
||||
return num_dropped_frames_;
|
||||
}
|
||||
|
||||
std::vector<int> VideoProcessor::NumberSpatialResizesPerRateUpdate() const {
|
||||
RTC_DCHECK_CALLED_SEQUENTIALLY(&sequence_checker_);
|
||||
return num_spatial_resizes_;
|
||||
}
|
||||
|
||||
void VideoProcessor::FrameEncoded(webrtc::VideoCodecType codec,
|
||||
const EncodedImage& encoded_image) {
|
||||
RTC_DCHECK_CALLED_SEQUENTIALLY(&sequence_checker_);
|
||||
|
||||
// For the highest measurement accuracy of the encode time, the start/stop
|
||||
// time recordings should wrap the Encode call as tightly as possible.
|
||||
int64_t encode_stop_ns = rtc::TimeNanos();
|
||||
|
||||
// Take the opportunity to verify the QP bitstream parser.
|
||||
VerifyQpParser(encoded_image, config_);
|
||||
|
||||
// Check for dropped frames.
|
||||
const int frame_number =
|
||||
rtp_timestamp_to_frame_num_[encoded_image._timeStamp];
|
||||
bool last_frame_missing = false;
|
||||
if (frame_number > 0) {
|
||||
RTC_DCHECK_GE(last_encoded_frame_num_, 0);
|
||||
int num_dropped_from_last_encode =
|
||||
frame_number - last_encoded_frame_num_ - 1;
|
||||
RTC_DCHECK_GE(num_dropped_from_last_encode, 0);
|
||||
RTC_CHECK_GE(rate_update_index_, 0);
|
||||
num_dropped_frames_[rate_update_index_] += num_dropped_from_last_encode;
|
||||
if (num_dropped_from_last_encode > 0) {
|
||||
// For dropped frames, we write out the last decoded frame to avoid
|
||||
// getting out of sync for the computation of PSNR and SSIM.
|
||||
for (int i = 0; i < num_dropped_from_last_encode; i++) {
|
||||
RTC_DCHECK_EQ(last_decoded_frame_buffer_.size(),
|
||||
analysis_frame_writer_->FrameLength());
|
||||
RTC_CHECK(analysis_frame_writer_->WriteFrame(
|
||||
last_decoded_frame_buffer_.data()));
|
||||
if (decoded_frame_writer_) {
|
||||
RTC_DCHECK_EQ(last_decoded_frame_buffer_.size(),
|
||||
decoded_frame_writer_->FrameLength());
|
||||
RTC_CHECK(decoded_frame_writer_->WriteFrame(
|
||||
last_decoded_frame_buffer_.data()));
|
||||
}
|
||||
}
|
||||
}
|
||||
const FrameStatistic* last_encoded_frame_stat =
|
||||
stats_->GetFrame(last_encoded_frame_num_);
|
||||
last_frame_missing = (last_encoded_frame_stat->manipulated_length == 0);
|
||||
}
|
||||
// Ensure strict monotonicity.
|
||||
RTC_CHECK_GT(frame_number, last_encoded_frame_num_);
|
||||
last_encoded_frame_num_ = frame_number;
|
||||
|
||||
// Update frame statistics.
|
||||
FrameStatistic* frame_stat = stats_->GetFrame(frame_number);
|
||||
frame_stat->encode_time_us =
|
||||
GetElapsedTimeMicroseconds(frame_stat->encode_start_ns, encode_stop_ns);
|
||||
frame_stat->encoding_successful = true;
|
||||
frame_stat->encoded_frame_size_bytes = encoded_image._length;
|
||||
frame_stat->frame_type = encoded_image._frameType;
|
||||
frame_stat->qp = encoded_image.qp_;
|
||||
frame_stat->bitrate_kbps = static_cast<int>(
|
||||
encoded_image._length * config_.codec_settings.maxFramerate * 8 / 1000);
|
||||
frame_stat->total_packets =
|
||||
encoded_image._length / config_.networking_config.packet_size_in_bytes +
|
||||
1;
|
||||
|
||||
// Simulate packet loss.
|
||||
bool exclude_this_frame = false;
|
||||
if (encoded_image._frameType == kVideoFrameKey) {
|
||||
// Only keyframes can be excluded.
|
||||
switch (config_.exclude_frame_types) {
|
||||
case kExcludeOnlyFirstKeyFrame:
|
||||
if (!first_key_frame_has_been_excluded_) {
|
||||
first_key_frame_has_been_excluded_ = true;
|
||||
exclude_this_frame = true;
|
||||
}
|
||||
break;
|
||||
case kExcludeAllKeyFrames:
|
||||
exclude_this_frame = true;
|
||||
break;
|
||||
default:
|
||||
RTC_NOTREACHED();
|
||||
}
|
||||
}
|
||||
|
||||
// Make a raw copy of the |encoded_image| buffer.
|
||||
size_t copied_buffer_size = encoded_image._length +
|
||||
EncodedImage::GetBufferPaddingBytes(codec);
|
||||
std::unique_ptr<uint8_t[]> copied_buffer(new uint8_t[copied_buffer_size]);
|
||||
memcpy(copied_buffer.get(), encoded_image._buffer, encoded_image._length);
|
||||
// The image to feed to the decoder.
|
||||
EncodedImage copied_image;
|
||||
memcpy(&copied_image, &encoded_image, sizeof(copied_image));
|
||||
copied_image._size = copied_buffer_size;
|
||||
copied_image._buffer = copied_buffer.get();
|
||||
|
||||
if (!exclude_this_frame) {
|
||||
frame_stat->packets_dropped =
|
||||
packet_manipulator_->ManipulatePackets(&copied_image);
|
||||
}
|
||||
frame_stat->manipulated_length = copied_image._length;
|
||||
|
||||
// For the highest measurement accuracy of the decode time, the start/stop
|
||||
// time recordings should wrap the Decode call as tightly as possible.
|
||||
frame_stat->decode_start_ns = rtc::TimeNanos();
|
||||
frame_stat->decode_return_code =
|
||||
decoder_->Decode(copied_image, last_frame_missing, nullptr);
|
||||
|
||||
if (frame_stat->decode_return_code != WEBRTC_VIDEO_CODEC_OK) {
|
||||
// Write the last successful frame the output file to avoid getting it out
|
||||
// of sync with the source file for SSIM and PSNR comparisons.
|
||||
RTC_DCHECK_EQ(last_decoded_frame_buffer_.size(),
|
||||
analysis_frame_writer_->FrameLength());
|
||||
RTC_CHECK(
|
||||
analysis_frame_writer_->WriteFrame(last_decoded_frame_buffer_.data()));
|
||||
if (decoded_frame_writer_) {
|
||||
RTC_DCHECK_EQ(last_decoded_frame_buffer_.size(),
|
||||
decoded_frame_writer_->FrameLength());
|
||||
RTC_CHECK(
|
||||
decoded_frame_writer_->WriteFrame(last_decoded_frame_buffer_.data()));
|
||||
}
|
||||
}
|
||||
|
||||
if (encoded_frame_writer_) {
|
||||
RTC_CHECK(encoded_frame_writer_->WriteFrame(encoded_image, codec));
|
||||
}
|
||||
}
|
||||
|
||||
void VideoProcessor::FrameDecoded(const VideoFrame& image) {
|
||||
RTC_DCHECK_CALLED_SEQUENTIALLY(&sequence_checker_);
|
||||
|
||||
// For the highest measurement accuracy of the decode time, the start/stop
|
||||
// time recordings should wrap the Decode call as tightly as possible.
|
||||
int64_t decode_stop_ns = rtc::TimeNanos();
|
||||
|
||||
// Update frame statistics.
|
||||
const int frame_number = rtp_timestamp_to_frame_num_[image.timestamp()];
|
||||
FrameStatistic* frame_stat = stats_->GetFrame(frame_number);
|
||||
frame_stat->decoded_width = image.width();
|
||||
frame_stat->decoded_height = image.height();
|
||||
frame_stat->decode_time_us =
|
||||
GetElapsedTimeMicroseconds(frame_stat->decode_start_ns, decode_stop_ns);
|
||||
frame_stat->decoding_successful = true;
|
||||
|
||||
// Check if the codecs have resized the frame since previously decoded frame.
|
||||
if (frame_number > 0) {
|
||||
RTC_CHECK_GE(last_decoded_frame_num_, 0);
|
||||
const FrameStatistic* last_decoded_frame_stat =
|
||||
stats_->GetFrame(last_decoded_frame_num_);
|
||||
if (static_cast<int>(image.width()) !=
|
||||
last_decoded_frame_stat->decoded_width ||
|
||||
static_cast<int>(image.height()) !=
|
||||
last_decoded_frame_stat->decoded_height) {
|
||||
RTC_CHECK_GE(rate_update_index_, 0);
|
||||
++num_spatial_resizes_[rate_update_index_];
|
||||
}
|
||||
}
|
||||
// Ensure strict monotonicity.
|
||||
RTC_CHECK_GT(frame_number, last_decoded_frame_num_);
|
||||
last_decoded_frame_num_ = frame_number;
|
||||
|
||||
// Check if frame size is different from the original size, and if so,
|
||||
// scale back to original size. This is needed for the PSNR and SSIM
|
||||
// calculations.
|
||||
size_t extracted_length;
|
||||
rtc::Buffer extracted_buffer;
|
||||
if (image.width() != config_.codec_settings.width ||
|
||||
image.height() != config_.codec_settings.height) {
|
||||
rtc::scoped_refptr<I420Buffer> scaled_buffer(I420Buffer::Create(
|
||||
config_.codec_settings.width, config_.codec_settings.height));
|
||||
// Should be the same aspect ratio, no cropping needed.
|
||||
scaled_buffer->ScaleFrom(*image.video_frame_buffer()->ToI420());
|
||||
|
||||
size_t length = CalcBufferSize(VideoType::kI420, scaled_buffer->width(),
|
||||
scaled_buffer->height());
|
||||
extracted_buffer.SetSize(length);
|
||||
extracted_length =
|
||||
ExtractBuffer(scaled_buffer, length, extracted_buffer.data());
|
||||
} else {
|
||||
// No resize.
|
||||
size_t length =
|
||||
CalcBufferSize(VideoType::kI420, image.width(), image.height());
|
||||
extracted_buffer.SetSize(length);
|
||||
extracted_length = ExtractBuffer(image.video_frame_buffer()->ToI420(),
|
||||
length, extracted_buffer.data());
|
||||
}
|
||||
|
||||
RTC_DCHECK_EQ(extracted_length, analysis_frame_writer_->FrameLength());
|
||||
RTC_CHECK(analysis_frame_writer_->WriteFrame(extracted_buffer.data()));
|
||||
if (decoded_frame_writer_) {
|
||||
RTC_DCHECK_EQ(extracted_length, decoded_frame_writer_->FrameLength());
|
||||
RTC_CHECK(decoded_frame_writer_->WriteFrame(extracted_buffer.data()));
|
||||
}
|
||||
|
||||
last_decoded_frame_buffer_ = std::move(extracted_buffer);
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
313
modules/video_coding/codecs/test/videoprocessor.h
Normal file
313
modules/video_coding/codecs/test/videoprocessor.h
Normal file
@ -0,0 +1,313 @@
|
||||
/*
|
||||
* Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_VIDEO_CODING_CODECS_TEST_VIDEOPROCESSOR_H_
|
||||
#define WEBRTC_MODULES_VIDEO_CODING_CODECS_TEST_VIDEOPROCESSOR_H_
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/api/video/video_frame.h"
|
||||
#include "webrtc/common_video/libyuv/include/webrtc_libyuv.h"
|
||||
#include "webrtc/modules/video_coding/codecs/test/packet_manipulator.h"
|
||||
#include "webrtc/modules/video_coding/codecs/test/stats.h"
|
||||
#include "webrtc/modules/video_coding/include/video_codec_interface.h"
|
||||
#include "webrtc/modules/video_coding/utility/ivf_file_writer.h"
|
||||
#include "webrtc/modules/video_coding/utility/vp8_header_parser.h"
|
||||
#include "webrtc/modules/video_coding/utility/vp9_uncompressed_header_parser.h"
|
||||
#include "webrtc/rtc_base/buffer.h"
|
||||
#include "webrtc/rtc_base/checks.h"
|
||||
#include "webrtc/rtc_base/constructormagic.h"
|
||||
#include "webrtc/rtc_base/sequenced_task_checker.h"
|
||||
#include "webrtc/rtc_base/task_queue.h"
|
||||
#include "webrtc/test/testsupport/frame_reader.h"
|
||||
#include "webrtc/test/testsupport/frame_writer.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class VideoBitrateAllocator;
|
||||
|
||||
namespace test {
|
||||
|
||||
// Defines which frame types shall be excluded from packet loss and when.
|
||||
enum ExcludeFrameTypes {
|
||||
// Will exclude the first keyframe in the video sequence from packet loss.
|
||||
// Following keyframes will be targeted for packet loss.
|
||||
kExcludeOnlyFirstKeyFrame,
|
||||
// Exclude all keyframes from packet loss, no matter where in the video
|
||||
// sequence they occur.
|
||||
kExcludeAllKeyFrames
|
||||
};
|
||||
|
||||
// Returns a string representation of the enum value.
|
||||
const char* ExcludeFrameTypesToStr(ExcludeFrameTypes e);
|
||||
|
||||
// Test configuration for a test run.
|
||||
struct TestConfig {
|
||||
// Plain name of YUV file to process without file extension.
|
||||
std::string filename;
|
||||
|
||||
// File to process. This must be a video file in the YUV format.
|
||||
std::string input_filename;
|
||||
|
||||
// File to write to during processing for the test. Will be a video file
|
||||
// in the YUV format.
|
||||
std::string output_filename;
|
||||
|
||||
// Configurations related to networking.
|
||||
NetworkingConfig networking_config;
|
||||
|
||||
// Decides how the packet loss simulations shall exclude certain frames
|
||||
// from packet loss.
|
||||
ExcludeFrameTypes exclude_frame_types = kExcludeOnlyFirstKeyFrame;
|
||||
|
||||
// Force the encoder and decoder to use a single core for processing.
|
||||
// Using a single core is necessary to get a deterministic behavior for the
|
||||
// encoded frames - using multiple cores will produce different encoded frames
|
||||
// since multiple cores are competing to consume the byte budget for each
|
||||
// frame in parallel.
|
||||
// If set to false, the maximum number of available cores will be used.
|
||||
bool use_single_core = false;
|
||||
|
||||
// If > 0: forces the encoder to create a keyframe every Nth frame.
|
||||
// Note that the encoder may create a keyframe in other locations in addition
|
||||
// to this setting. Forcing key frames may also affect encoder planning
|
||||
// optimizations in a negative way, since it will suddenly be forced to
|
||||
// produce an expensive key frame.
|
||||
int keyframe_interval = 0;
|
||||
|
||||
// The codec settings to use for the test (target bitrate, video size,
|
||||
// framerate and so on). This struct should be filled in using the
|
||||
// VideoCodingModule::Codec() method.
|
||||
webrtc::VideoCodec codec_settings;
|
||||
|
||||
// If printing of information to stdout shall be performed during processing.
|
||||
bool verbose = true;
|
||||
|
||||
// Should hardware accelerated codecs be used?
|
||||
bool hw_encoder = false;
|
||||
bool hw_decoder = false;
|
||||
|
||||
// Should the hardware codecs be wrapped in software fallbacks?
|
||||
bool sw_fallback_encoder = false;
|
||||
// TODO(brandtr): Add support for SW decoder fallbacks, when
|
||||
// webrtc::VideoDecoder's can be wrapped in std::unique_ptr's.
|
||||
};
|
||||
|
||||
// Handles encoding/decoding of video using the VideoEncoder/VideoDecoder
|
||||
// interfaces. This is done in a sequential manner in order to be able to
|
||||
// measure times properly.
|
||||
// The class processes a frame at the time for the configured input file.
|
||||
// It maintains state of where in the source input file the processing is at.
|
||||
//
|
||||
// Regarding packet loss: Note that keyframes are excluded (first or all
|
||||
// depending on the ExcludeFrameTypes setting). This is because if key frames
|
||||
// would be altered, all the following delta frames would be pretty much
|
||||
// worthless. VP8 has an error-resilience feature that makes it able to handle
|
||||
// packet loss in key non-first keyframes, which is why only the first is
|
||||
// excluded by default.
|
||||
// Packet loss in such important frames is handled on a higher level in the
|
||||
// Video Engine, where signaling would request a retransmit of the lost packets,
|
||||
// since they're so important.
|
||||
//
|
||||
// Note this class is not thread safe in any way and is meant for simple testing
|
||||
// purposes.
|
||||
class VideoProcessor {
|
||||
public:
|
||||
VideoProcessor(webrtc::VideoEncoder* encoder,
|
||||
webrtc::VideoDecoder* decoder,
|
||||
FrameReader* analysis_frame_reader,
|
||||
FrameWriter* analysis_frame_writer,
|
||||
PacketManipulator* packet_manipulator,
|
||||
const TestConfig& config,
|
||||
Stats* stats,
|
||||
IvfFileWriter* encoded_frame_writer,
|
||||
FrameWriter* decoded_frame_writer);
|
||||
~VideoProcessor();
|
||||
|
||||
// Sets up callbacks and initializes the encoder and decoder.
|
||||
void Init();
|
||||
|
||||
// Tears down callbacks and releases the encoder and decoder.
|
||||
void Release();
|
||||
|
||||
// Reads a frame from the analysis frame reader and sends it to the encoder.
|
||||
// When the encode callback is received, the encoded frame is sent to the
|
||||
// decoder. The decoded frame is written to disk by the analysis frame writer.
|
||||
// Objective video quality metrics can thus be calculated after the fact.
|
||||
void ProcessFrame();
|
||||
|
||||
// Updates the encoder with target rates. Must be called at least once.
|
||||
void SetRates(int bitrate_kbps, int framerate_fps);
|
||||
|
||||
// Returns the number of dropped frames.
|
||||
std::vector<int> NumberDroppedFramesPerRateUpdate() const;
|
||||
|
||||
// Returns the number of spatial resizes.
|
||||
std::vector<int> NumberSpatialResizesPerRateUpdate() const;
|
||||
|
||||
private:
|
||||
class VideoProcessorEncodeCompleteCallback
|
||||
: public webrtc::EncodedImageCallback {
|
||||
public:
|
||||
explicit VideoProcessorEncodeCompleteCallback(
|
||||
VideoProcessor* video_processor)
|
||||
: video_processor_(video_processor),
|
||||
task_queue_(rtc::TaskQueue::Current()) {}
|
||||
|
||||
Result OnEncodedImage(
|
||||
const webrtc::EncodedImage& encoded_image,
|
||||
const webrtc::CodecSpecificInfo* codec_specific_info,
|
||||
const webrtc::RTPFragmentationHeader* fragmentation) override {
|
||||
RTC_CHECK(codec_specific_info);
|
||||
|
||||
if (task_queue_ && !task_queue_->IsCurrent()) {
|
||||
task_queue_->PostTask(
|
||||
std::unique_ptr<rtc::QueuedTask>(new EncodeCallbackTask(
|
||||
video_processor_, encoded_image, codec_specific_info)));
|
||||
return Result(Result::OK, 0);
|
||||
}
|
||||
|
||||
video_processor_->FrameEncoded(codec_specific_info->codecType,
|
||||
encoded_image);
|
||||
return Result(Result::OK, 0);
|
||||
}
|
||||
|
||||
private:
|
||||
class EncodeCallbackTask : public rtc::QueuedTask {
|
||||
public:
|
||||
EncodeCallbackTask(VideoProcessor* video_processor,
|
||||
const webrtc::EncodedImage& encoded_image,
|
||||
const webrtc::CodecSpecificInfo* codec_specific_info)
|
||||
: video_processor_(video_processor),
|
||||
buffer_(encoded_image._buffer, encoded_image._length),
|
||||
encoded_image_(encoded_image),
|
||||
codec_specific_info_(*codec_specific_info) {
|
||||
encoded_image_._buffer = buffer_.data();
|
||||
}
|
||||
|
||||
bool Run() override {
|
||||
video_processor_->FrameEncoded(codec_specific_info_.codecType,
|
||||
encoded_image_);
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
VideoProcessor* const video_processor_;
|
||||
rtc::Buffer buffer_;
|
||||
webrtc::EncodedImage encoded_image_;
|
||||
const webrtc::CodecSpecificInfo codec_specific_info_;
|
||||
};
|
||||
|
||||
VideoProcessor* const video_processor_;
|
||||
rtc::TaskQueue* const task_queue_;
|
||||
};
|
||||
|
||||
class VideoProcessorDecodeCompleteCallback
|
||||
: public webrtc::DecodedImageCallback {
|
||||
public:
|
||||
explicit VideoProcessorDecodeCompleteCallback(
|
||||
VideoProcessor* video_processor)
|
||||
: video_processor_(video_processor),
|
||||
task_queue_(rtc::TaskQueue::Current()) {}
|
||||
|
||||
int32_t Decoded(webrtc::VideoFrame& image) override {
|
||||
if (task_queue_ && !task_queue_->IsCurrent()) {
|
||||
task_queue_->PostTask(
|
||||
[this, image]() { video_processor_->FrameDecoded(image); });
|
||||
return 0;
|
||||
}
|
||||
video_processor_->FrameDecoded(image);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t Decoded(webrtc::VideoFrame& image,
|
||||
int64_t decode_time_ms) override {
|
||||
return Decoded(image);
|
||||
}
|
||||
|
||||
void Decoded(webrtc::VideoFrame& image,
|
||||
rtc::Optional<int32_t> decode_time_ms,
|
||||
rtc::Optional<uint8_t> qp) override {
|
||||
Decoded(image);
|
||||
}
|
||||
|
||||
private:
|
||||
VideoProcessor* const video_processor_;
|
||||
rtc::TaskQueue* const task_queue_;
|
||||
};
|
||||
|
||||
// Invoked by the callback adapter when a frame has completed encoding.
|
||||
void FrameEncoded(webrtc::VideoCodecType codec,
|
||||
const webrtc::EncodedImage& encodedImage);
|
||||
|
||||
// Invoked by the callback adapter when a frame has completed decoding.
|
||||
void FrameDecoded(const webrtc::VideoFrame& image);
|
||||
|
||||
bool initialized_ RTC_GUARDED_BY(sequence_checker_);
|
||||
TestConfig config_ RTC_GUARDED_BY(sequence_checker_);
|
||||
|
||||
webrtc::VideoEncoder* const encoder_;
|
||||
webrtc::VideoDecoder* const decoder_;
|
||||
const std::unique_ptr<VideoBitrateAllocator> bitrate_allocator_;
|
||||
|
||||
// Adapters for the codec callbacks.
|
||||
VideoProcessorEncodeCompleteCallback encode_callback_;
|
||||
VideoProcessorDecodeCompleteCallback decode_callback_;
|
||||
|
||||
// Fake network.
|
||||
PacketManipulator* const packet_manipulator_;
|
||||
|
||||
// These (mandatory) file manipulators are used for, e.g., objective PSNR and
|
||||
// SSIM calculations at the end of a test run.
|
||||
FrameReader* const analysis_frame_reader_;
|
||||
FrameWriter* const analysis_frame_writer_;
|
||||
|
||||
// These (optional) file writers are used to persistently store the encoded
|
||||
// and decoded bitstreams. The purpose is to give the experimenter an option
|
||||
// to subjectively evaluate the quality of the processing. Each frame writer
|
||||
// is enabled by being non-null.
|
||||
IvfFileWriter* const encoded_frame_writer_;
|
||||
FrameWriter* const decoded_frame_writer_;
|
||||
|
||||
// Keep track of inputed/encoded/decoded frames, so we can detect frame drops.
|
||||
int last_inputed_frame_num_ RTC_GUARDED_BY(sequence_checker_);
|
||||
int last_encoded_frame_num_ RTC_GUARDED_BY(sequence_checker_);
|
||||
int last_decoded_frame_num_ RTC_GUARDED_BY(sequence_checker_);
|
||||
|
||||
// Store an RTP timestamp -> frame number map, since the timestamps are
|
||||
// based off of the frame rate, which can change mid-test.
|
||||
std::map<uint32_t, int> rtp_timestamp_to_frame_num_
|
||||
RTC_GUARDED_BY(sequence_checker_);
|
||||
|
||||
// Keep track of if we have excluded the first key frame from packet loss.
|
||||
bool first_key_frame_has_been_excluded_ RTC_GUARDED_BY(sequence_checker_);
|
||||
|
||||
// Keep track of the last successfully decoded frame, since we write that
|
||||
// frame to disk when decoding fails.
|
||||
rtc::Buffer last_decoded_frame_buffer_ RTC_GUARDED_BY(sequence_checker_);
|
||||
|
||||
// Statistics.
|
||||
Stats* stats_;
|
||||
std::vector<int> num_dropped_frames_ RTC_GUARDED_BY(sequence_checker_);
|
||||
std::vector<int> num_spatial_resizes_ RTC_GUARDED_BY(sequence_checker_);
|
||||
int rate_update_index_ RTC_GUARDED_BY(sequence_checker_);
|
||||
|
||||
rtc::SequencedTaskChecker sequence_checker_;
|
||||
|
||||
RTC_DISALLOW_COPY_AND_ASSIGN(VideoProcessor);
|
||||
};
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_VIDEO_CODING_CODECS_TEST_VIDEOPROCESSOR_H_
|
||||
@ -0,0 +1,657 @@
|
||||
/*
|
||||
* Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "webrtc/modules/video_coding/codecs/test/videoprocessor_integrationtest.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#if defined(WEBRTC_ANDROID)
|
||||
#include "webrtc/modules/video_coding/codecs/test/android_test_initializer.h"
|
||||
#include "webrtc/sdk/android/src/jni/androidmediadecoder_jni.h"
|
||||
#include "webrtc/sdk/android/src/jni/androidmediaencoder_jni.h"
|
||||
#elif defined(WEBRTC_IOS)
|
||||
#include "webrtc/modules/video_coding/codecs/test/objc_codec_h264_test.h"
|
||||
#endif
|
||||
|
||||
#include "webrtc/media/engine/internaldecoderfactory.h"
|
||||
#include "webrtc/media/engine/internalencoderfactory.h"
|
||||
#include "webrtc/media/engine/videoencodersoftwarefallbackwrapper.h"
|
||||
#include "webrtc/modules/video_coding/codecs/vp8/include/vp8_common_types.h"
|
||||
#include "webrtc/modules/video_coding/include/video_codec_interface.h"
|
||||
#include "webrtc/modules/video_coding/include/video_coding.h"
|
||||
#include "webrtc/rtc_base/checks.h"
|
||||
#include "webrtc/rtc_base/event.h"
|
||||
#include "webrtc/rtc_base/file.h"
|
||||
#include "webrtc/rtc_base/logging.h"
|
||||
#include "webrtc/rtc_base/ptr_util.h"
|
||||
#include "webrtc/system_wrappers/include/sleep.h"
|
||||
#include "webrtc/test/testsupport/fileutils.h"
|
||||
#include "webrtc/test/testsupport/metrics/video_metrics.h"
|
||||
#include "webrtc/test/video_codec_settings.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace test {
|
||||
|
||||
namespace {
|
||||
|
||||
const int kPercTargetvsActualMismatch = 20;
|
||||
const int kBaseKeyFrameInterval = 3000;
|
||||
|
||||
// Parameters from VP8 wrapper, which control target size of key frames.
|
||||
const float kInitialBufferSize = 0.5f;
|
||||
const float kOptimalBufferSize = 0.6f;
|
||||
const float kScaleKeyFrameSize = 0.5f;
|
||||
|
||||
void VerifyQuality(const QualityMetricsResult& psnr_result,
|
||||
const QualityMetricsResult& ssim_result,
|
||||
const QualityThresholds& quality_thresholds) {
|
||||
EXPECT_GT(psnr_result.average, quality_thresholds.min_avg_psnr);
|
||||
EXPECT_GT(psnr_result.min, quality_thresholds.min_min_psnr);
|
||||
EXPECT_GT(ssim_result.average, quality_thresholds.min_avg_ssim);
|
||||
EXPECT_GT(ssim_result.min, quality_thresholds.min_min_ssim);
|
||||
}
|
||||
|
||||
int NumberOfTemporalLayers(const VideoCodec& codec_settings) {
|
||||
if (codec_settings.codecType == kVideoCodecVP8) {
|
||||
return codec_settings.VP8().numberOfTemporalLayers;
|
||||
} else if (codec_settings.codecType == kVideoCodecVP9) {
|
||||
return codec_settings.VP9().numberOfTemporalLayers;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
VideoProcessorIntegrationTest::VideoProcessorIntegrationTest() {
|
||||
#if defined(WEBRTC_ANDROID)
|
||||
InitializeAndroidObjects();
|
||||
#endif
|
||||
}
|
||||
|
||||
VideoProcessorIntegrationTest::~VideoProcessorIntegrationTest() = default;
|
||||
|
||||
void VideoProcessorIntegrationTest::SetCodecSettings(TestConfig* config,
|
||||
VideoCodecType codec_type,
|
||||
int num_temporal_layers,
|
||||
bool error_concealment_on,
|
||||
bool denoising_on,
|
||||
bool frame_dropper_on,
|
||||
bool spatial_resize_on,
|
||||
bool resilience_on,
|
||||
int width,
|
||||
int height) {
|
||||
webrtc::test::CodecSettings(codec_type, &config->codec_settings);
|
||||
|
||||
// TODO(brandtr): Move the setting of |width| and |height| to the tests, and
|
||||
// DCHECK that they are set before initializing the codec instead.
|
||||
config->codec_settings.width = width;
|
||||
config->codec_settings.height = height;
|
||||
|
||||
switch (config->codec_settings.codecType) {
|
||||
case kVideoCodecVP8:
|
||||
config->codec_settings.VP8()->resilience =
|
||||
resilience_on ? kResilientStream : kResilienceOff;
|
||||
config->codec_settings.VP8()->numberOfTemporalLayers =
|
||||
num_temporal_layers;
|
||||
config->codec_settings.VP8()->denoisingOn = denoising_on;
|
||||
config->codec_settings.VP8()->errorConcealmentOn = error_concealment_on;
|
||||
config->codec_settings.VP8()->automaticResizeOn = spatial_resize_on;
|
||||
config->codec_settings.VP8()->frameDroppingOn = frame_dropper_on;
|
||||
config->codec_settings.VP8()->keyFrameInterval = kBaseKeyFrameInterval;
|
||||
break;
|
||||
case kVideoCodecVP9:
|
||||
config->codec_settings.VP9()->resilienceOn = resilience_on;
|
||||
config->codec_settings.VP9()->numberOfTemporalLayers =
|
||||
num_temporal_layers;
|
||||
config->codec_settings.VP9()->denoisingOn = denoising_on;
|
||||
config->codec_settings.VP9()->frameDroppingOn = frame_dropper_on;
|
||||
config->codec_settings.VP9()->keyFrameInterval = kBaseKeyFrameInterval;
|
||||
config->codec_settings.VP9()->automaticResizeOn = spatial_resize_on;
|
||||
break;
|
||||
case kVideoCodecH264:
|
||||
config->codec_settings.H264()->frameDroppingOn = frame_dropper_on;
|
||||
config->codec_settings.H264()->keyFrameInterval = kBaseKeyFrameInterval;
|
||||
break;
|
||||
default:
|
||||
RTC_NOTREACHED();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void VideoProcessorIntegrationTest::SetRateProfile(
|
||||
RateProfile* rate_profile,
|
||||
int rate_update_index,
|
||||
int bitrate_kbps,
|
||||
int framerate_fps,
|
||||
int frame_index_rate_update) {
|
||||
rate_profile->target_bit_rate[rate_update_index] = bitrate_kbps;
|
||||
rate_profile->input_frame_rate[rate_update_index] = framerate_fps;
|
||||
rate_profile->frame_index_rate_update[rate_update_index] =
|
||||
frame_index_rate_update;
|
||||
}
|
||||
|
||||
void VideoProcessorIntegrationTest::AddRateControlThresholds(
|
||||
int max_num_dropped_frames,
|
||||
int max_key_frame_size_mismatch,
|
||||
int max_delta_frame_size_mismatch,
|
||||
int max_encoding_rate_mismatch,
|
||||
int max_time_hit_target,
|
||||
int num_spatial_resizes,
|
||||
int num_key_frames,
|
||||
std::vector<RateControlThresholds>* rc_thresholds) {
|
||||
RTC_DCHECK(rc_thresholds);
|
||||
|
||||
rc_thresholds->emplace_back();
|
||||
RateControlThresholds* rc_threshold = &rc_thresholds->back();
|
||||
rc_threshold->max_num_dropped_frames = max_num_dropped_frames;
|
||||
rc_threshold->max_key_frame_size_mismatch = max_key_frame_size_mismatch;
|
||||
rc_threshold->max_delta_frame_size_mismatch = max_delta_frame_size_mismatch;
|
||||
rc_threshold->max_encoding_rate_mismatch = max_encoding_rate_mismatch;
|
||||
rc_threshold->max_time_hit_target = max_time_hit_target;
|
||||
rc_threshold->num_spatial_resizes = num_spatial_resizes;
|
||||
rc_threshold->num_key_frames = num_key_frames;
|
||||
}
|
||||
|
||||
// Processes all frames in the clip and verifies the result.
|
||||
void VideoProcessorIntegrationTest::ProcessFramesAndMaybeVerify(
|
||||
const RateProfile& rate_profile,
|
||||
const std::vector<RateControlThresholds>* rc_thresholds,
|
||||
const QualityThresholds* quality_thresholds,
|
||||
const VisualizationParams* visualization_params) {
|
||||
// The Android HW codec needs to be run on a task queue, so we simply always
|
||||
// run the test on a task queue.
|
||||
rtc::TaskQueue task_queue("VidProc TQ");
|
||||
rtc::Event sync_event(false, false);
|
||||
|
||||
SetUpAndInitObjects(&task_queue, rate_profile.target_bit_rate[0],
|
||||
rate_profile.input_frame_rate[0], visualization_params);
|
||||
|
||||
// Set initial rates.
|
||||
int rate_update_index = 0;
|
||||
task_queue.PostTask([this, &rate_profile, rate_update_index] {
|
||||
processor_->SetRates(rate_profile.target_bit_rate[rate_update_index],
|
||||
rate_profile.input_frame_rate[rate_update_index]);
|
||||
});
|
||||
|
||||
// Process all frames.
|
||||
int frame_number = 0;
|
||||
const int num_frames = rate_profile.num_frames;
|
||||
RTC_DCHECK_GE(num_frames, 1);
|
||||
while (frame_number < num_frames) {
|
||||
// In order to not overwhelm the OpenMAX buffers in the Android
|
||||
// MediaCodec API, we roughly pace the frames here. The downside
|
||||
// of this is that the encode run will be done in real-time.
|
||||
#if defined(WEBRTC_ANDROID)
|
||||
if (config_.hw_encoder || config_.hw_decoder) {
|
||||
SleepMs(rtc::kNumMillisecsPerSec /
|
||||
rate_profile.input_frame_rate[rate_update_index]);
|
||||
}
|
||||
#endif
|
||||
|
||||
task_queue.PostTask([this] { processor_->ProcessFrame(); });
|
||||
++frame_number;
|
||||
|
||||
if (frame_number ==
|
||||
rate_profile.frame_index_rate_update[rate_update_index + 1]) {
|
||||
++rate_update_index;
|
||||
|
||||
task_queue.PostTask([this, &rate_profile, rate_update_index] {
|
||||
processor_->SetRates(rate_profile.target_bit_rate[rate_update_index],
|
||||
rate_profile.input_frame_rate[rate_update_index]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Give the VideoProcessor pipeline some time to process the last frame,
|
||||
// and then release the codecs.
|
||||
if (config_.hw_encoder || config_.hw_decoder) {
|
||||
SleepMs(1 * rtc::kNumMillisecsPerSec);
|
||||
}
|
||||
ReleaseAndCloseObjects(&task_queue);
|
||||
|
||||
// Calculate and print rate control statistics.
|
||||
rate_update_index = 0;
|
||||
frame_number = 0;
|
||||
ResetRateControlMetrics(rate_update_index, rate_profile);
|
||||
std::vector<int> num_dropped_frames;
|
||||
std::vector<int> num_resize_actions;
|
||||
sync_event.Reset();
|
||||
task_queue.PostTask(
|
||||
[this, &num_dropped_frames, &num_resize_actions, &sync_event]() {
|
||||
num_dropped_frames = processor_->NumberDroppedFramesPerRateUpdate();
|
||||
num_resize_actions = processor_->NumberSpatialResizesPerRateUpdate();
|
||||
sync_event.Set();
|
||||
});
|
||||
sync_event.Wait(rtc::Event::kForever);
|
||||
while (frame_number < num_frames) {
|
||||
UpdateRateControlMetrics(frame_number);
|
||||
|
||||
++frame_number;
|
||||
|
||||
if (frame_number ==
|
||||
rate_profile.frame_index_rate_update[rate_update_index + 1]) {
|
||||
PrintAndMaybeVerifyRateControlMetrics(rate_update_index, rc_thresholds,
|
||||
num_dropped_frames,
|
||||
num_resize_actions);
|
||||
++rate_update_index;
|
||||
ResetRateControlMetrics(rate_update_index, rate_profile);
|
||||
}
|
||||
}
|
||||
PrintAndMaybeVerifyRateControlMetrics(rate_update_index, rc_thresholds,
|
||||
num_dropped_frames, num_resize_actions);
|
||||
|
||||
// Calculate and print other statistics.
|
||||
EXPECT_EQ(num_frames, static_cast<int>(stats_.size()));
|
||||
stats_.PrintSummary();
|
||||
|
||||
// Calculate and print image quality statistics.
|
||||
// TODO(marpan): Should compute these quality metrics per SetRates update.
|
||||
QualityMetricsResult psnr_result, ssim_result;
|
||||
EXPECT_EQ(0, I420MetricsFromFiles(config_.input_filename.c_str(),
|
||||
config_.output_filename.c_str(),
|
||||
config_.codec_settings.width,
|
||||
config_.codec_settings.height, &psnr_result,
|
||||
&ssim_result));
|
||||
if (quality_thresholds) {
|
||||
VerifyQuality(psnr_result, ssim_result, *quality_thresholds);
|
||||
}
|
||||
printf("PSNR avg: %f, min: %f\nSSIM avg: %f, min: %f\n", psnr_result.average,
|
||||
psnr_result.min, ssim_result.average, ssim_result.min);
|
||||
printf("\n");
|
||||
|
||||
// Remove analysis file.
|
||||
if (remove(config_.output_filename.c_str()) < 0) {
|
||||
fprintf(stderr, "Failed to remove temporary file!\n");
|
||||
}
|
||||
}
|
||||
|
||||
void VideoProcessorIntegrationTest::CreateEncoderAndDecoder() {
|
||||
std::unique_ptr<cricket::WebRtcVideoEncoderFactory> encoder_factory;
|
||||
if (config_.hw_encoder) {
|
||||
#if defined(WEBRTC_ANDROID)
|
||||
encoder_factory.reset(new jni::MediaCodecVideoEncoderFactory());
|
||||
#elif defined(WEBRTC_IOS)
|
||||
EXPECT_EQ(kVideoCodecH264, config_.codec_settings.codecType)
|
||||
<< "iOS HW codecs only support H264.";
|
||||
encoder_factory = CreateObjCEncoderFactory();
|
||||
#else
|
||||
RTC_NOTREACHED() << "Only support HW encoder on Android and iOS.";
|
||||
#endif
|
||||
} else {
|
||||
encoder_factory.reset(new cricket::InternalEncoderFactory());
|
||||
}
|
||||
|
||||
if (config_.hw_decoder) {
|
||||
#if defined(WEBRTC_ANDROID)
|
||||
decoder_factory_.reset(new jni::MediaCodecVideoDecoderFactory());
|
||||
#elif defined(WEBRTC_IOS)
|
||||
EXPECT_EQ(kVideoCodecH264, config_.codec_settings.codecType)
|
||||
<< "iOS HW codecs only support H264.";
|
||||
decoder_factory_ = CreateObjCDecoderFactory();
|
||||
#else
|
||||
RTC_NOTREACHED() << "Only support HW decoder on Android and iOS.";
|
||||
#endif
|
||||
} else {
|
||||
decoder_factory_.reset(new cricket::InternalDecoderFactory());
|
||||
}
|
||||
|
||||
cricket::VideoCodec codec;
|
||||
cricket::VideoDecoderParams decoder_params; // Empty.
|
||||
switch (config_.codec_settings.codecType) {
|
||||
case kVideoCodecVP8:
|
||||
codec = cricket::VideoCodec(cricket::kVp8CodecName);
|
||||
encoder_.reset(encoder_factory->CreateVideoEncoder(codec));
|
||||
decoder_ =
|
||||
decoder_factory_->CreateVideoDecoderWithParams(codec, decoder_params);
|
||||
break;
|
||||
case kVideoCodecVP9:
|
||||
codec = cricket::VideoCodec(cricket::kVp9CodecName);
|
||||
encoder_.reset(encoder_factory->CreateVideoEncoder(codec));
|
||||
decoder_ =
|
||||
decoder_factory_->CreateVideoDecoderWithParams(codec, decoder_params);
|
||||
break;
|
||||
case kVideoCodecH264:
|
||||
// TODO(brandtr): Generalize so that we support multiple profiles here.
|
||||
codec = cricket::VideoCodec(cricket::kH264CodecName);
|
||||
encoder_.reset(encoder_factory->CreateVideoEncoder(codec));
|
||||
decoder_ =
|
||||
decoder_factory_->CreateVideoDecoderWithParams(codec, decoder_params);
|
||||
break;
|
||||
default:
|
||||
RTC_NOTREACHED();
|
||||
break;
|
||||
}
|
||||
|
||||
if (config_.sw_fallback_encoder) {
|
||||
encoder_ = rtc::MakeUnique<VideoEncoderSoftwareFallbackWrapper>(
|
||||
codec, std::move(encoder_));
|
||||
}
|
||||
|
||||
EXPECT_TRUE(encoder_) << "Encoder not successfully created.";
|
||||
EXPECT_TRUE(decoder_) << "Decoder not successfully created.";
|
||||
}
|
||||
|
||||
void VideoProcessorIntegrationTest::DestroyEncoderAndDecoder() {
|
||||
encoder_.reset();
|
||||
decoder_factory_->DestroyVideoDecoder(decoder_);
|
||||
}
|
||||
|
||||
void VideoProcessorIntegrationTest::SetUpAndInitObjects(
|
||||
rtc::TaskQueue* task_queue,
|
||||
const int initial_bitrate_kbps,
|
||||
const int initial_framerate_fps,
|
||||
const VisualizationParams* visualization_params) {
|
||||
CreateEncoderAndDecoder();
|
||||
|
||||
// Create file objects for quality analysis.
|
||||
analysis_frame_reader_.reset(new YuvFrameReaderImpl(
|
||||
config_.input_filename, config_.codec_settings.width,
|
||||
config_.codec_settings.height));
|
||||
analysis_frame_writer_.reset(new YuvFrameWriterImpl(
|
||||
config_.output_filename, config_.codec_settings.width,
|
||||
config_.codec_settings.height));
|
||||
EXPECT_TRUE(analysis_frame_reader_->Init());
|
||||
EXPECT_TRUE(analysis_frame_writer_->Init());
|
||||
|
||||
if (visualization_params) {
|
||||
const std::string codec_name =
|
||||
CodecTypeToPayloadString(config_.codec_settings.codecType);
|
||||
const std::string implementation_type = config_.hw_encoder ? "hw" : "sw";
|
||||
// clang-format off
|
||||
const std::string output_filename_base =
|
||||
OutputPath() + config_.filename + "-" +
|
||||
codec_name + "-" + implementation_type + "-" +
|
||||
std::to_string(initial_bitrate_kbps);
|
||||
// clang-format on
|
||||
if (visualization_params->save_encoded_ivf) {
|
||||
rtc::File post_encode_file =
|
||||
rtc::File::Create(output_filename_base + ".ivf");
|
||||
encoded_frame_writer_ =
|
||||
IvfFileWriter::Wrap(std::move(post_encode_file), 0);
|
||||
}
|
||||
if (visualization_params->save_decoded_y4m) {
|
||||
decoded_frame_writer_.reset(new Y4mFrameWriterImpl(
|
||||
output_filename_base + ".y4m", config_.codec_settings.width,
|
||||
config_.codec_settings.height, initial_framerate_fps));
|
||||
EXPECT_TRUE(decoded_frame_writer_->Init());
|
||||
}
|
||||
}
|
||||
|
||||
packet_manipulator_.reset(new PacketManipulatorImpl(
|
||||
&packet_reader_, config_.networking_config, config_.verbose));
|
||||
|
||||
config_.codec_settings.minBitrate = 0;
|
||||
config_.codec_settings.startBitrate = initial_bitrate_kbps;
|
||||
config_.codec_settings.maxFramerate = initial_framerate_fps;
|
||||
|
||||
rtc::Event sync_event(false, false);
|
||||
task_queue->PostTask([this, &sync_event]() {
|
||||
// TODO(brandtr): std::move |encoder_| and |decoder_| into the
|
||||
// VideoProcessor when we are able to store |decoder_| in a
|
||||
// std::unique_ptr. That is, when https://codereview.webrtc.org/3009973002
|
||||
// has been relanded.
|
||||
processor_ = rtc::MakeUnique<VideoProcessor>(
|
||||
encoder_.get(), decoder_, analysis_frame_reader_.get(),
|
||||
analysis_frame_writer_.get(), packet_manipulator_.get(), config_,
|
||||
&stats_, encoded_frame_writer_.get(), decoded_frame_writer_.get());
|
||||
processor_->Init();
|
||||
sync_event.Set();
|
||||
});
|
||||
sync_event.Wait(rtc::Event::kForever);
|
||||
}
|
||||
|
||||
void VideoProcessorIntegrationTest::ReleaseAndCloseObjects(
|
||||
rtc::TaskQueue* task_queue) {
|
||||
rtc::Event sync_event(false, false);
|
||||
task_queue->PostTask([this, &sync_event]() {
|
||||
processor_->Release();
|
||||
sync_event.Set();
|
||||
});
|
||||
sync_event.Wait(rtc::Event::kForever);
|
||||
|
||||
// The VideoProcessor must be ::Release()'d before we destroy the codecs.
|
||||
DestroyEncoderAndDecoder();
|
||||
|
||||
// Close the analysis files before we use them for SSIM/PSNR calculations.
|
||||
analysis_frame_reader_->Close();
|
||||
analysis_frame_writer_->Close();
|
||||
|
||||
// Close visualization files.
|
||||
if (encoded_frame_writer_) {
|
||||
EXPECT_TRUE(encoded_frame_writer_->Close());
|
||||
}
|
||||
if (decoded_frame_writer_) {
|
||||
decoded_frame_writer_->Close();
|
||||
}
|
||||
}
|
||||
|
||||
// For every encoded frame, update the rate control metrics.
|
||||
void VideoProcessorIntegrationTest::UpdateRateControlMetrics(int frame_number) {
|
||||
RTC_CHECK_GE(frame_number, 0);
|
||||
|
||||
const int tl_idx = TemporalLayerIndexForFrame(frame_number);
|
||||
++num_frames_per_update_[tl_idx];
|
||||
++num_frames_total_;
|
||||
|
||||
const FrameStatistic* frame_stat = stats_.GetFrame(frame_number);
|
||||
FrameType frame_type = frame_stat->frame_type;
|
||||
float encoded_size_kbits =
|
||||
frame_stat->encoded_frame_size_bytes * 8.0f / 1000.0f;
|
||||
|
||||
// Update layer data.
|
||||
// Update rate mismatch relative to per-frame bandwidth for delta frames.
|
||||
if (frame_type == kVideoFrameDelta) {
|
||||
// TODO(marpan): Should we count dropped (zero size) frames in mismatch?
|
||||
sum_frame_size_mismatch_[tl_idx] +=
|
||||
fabs(encoded_size_kbits - per_frame_bandwidth_[tl_idx]) /
|
||||
per_frame_bandwidth_[tl_idx];
|
||||
} else {
|
||||
float target_size = (frame_number == 0) ? target_size_key_frame_initial_
|
||||
: target_size_key_frame_;
|
||||
sum_key_frame_size_mismatch_ +=
|
||||
fabs(encoded_size_kbits - target_size) / target_size;
|
||||
num_key_frames_ += 1;
|
||||
}
|
||||
sum_encoded_frame_size_[tl_idx] += encoded_size_kbits;
|
||||
// Encoding bit rate per temporal layer: from the start of the update/run
|
||||
// to the current frame.
|
||||
encoding_bitrate_[tl_idx] = sum_encoded_frame_size_[tl_idx] *
|
||||
framerate_layer_[tl_idx] /
|
||||
num_frames_per_update_[tl_idx];
|
||||
// Total encoding rate: from the start of the update/run to current frame.
|
||||
sum_encoded_frame_size_total_ += encoded_size_kbits;
|
||||
encoding_bitrate_total_ =
|
||||
sum_encoded_frame_size_total_ * framerate_ / num_frames_total_;
|
||||
perc_encoding_rate_mismatch_ =
|
||||
100 * fabs(encoding_bitrate_total_ - bitrate_kbps_) / bitrate_kbps_;
|
||||
if (perc_encoding_rate_mismatch_ < kPercTargetvsActualMismatch &&
|
||||
!encoding_rate_within_target_) {
|
||||
num_frames_to_hit_target_ = num_frames_total_;
|
||||
encoding_rate_within_target_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Verify expected behavior of rate control and print out data.
|
||||
void VideoProcessorIntegrationTest::PrintAndMaybeVerifyRateControlMetrics(
|
||||
int rate_update_index,
|
||||
const std::vector<RateControlThresholds>* rc_thresholds,
|
||||
const std::vector<int>& num_dropped_frames,
|
||||
const std::vector<int>& num_resize_actions) {
|
||||
printf(
|
||||
"Rate update #%d:\n"
|
||||
" Target bitrate : %d\n"
|
||||
" Encoded bitrate : %f\n"
|
||||
" Frame rate : %d\n",
|
||||
rate_update_index, bitrate_kbps_, encoding_bitrate_total_, framerate_);
|
||||
printf(
|
||||
" # processed frames : %d\n"
|
||||
" # frames to convergence: %d\n"
|
||||
" # dropped frames : %d\n"
|
||||
" # spatial resizes : %d\n",
|
||||
num_frames_total_, num_frames_to_hit_target_,
|
||||
num_dropped_frames[rate_update_index],
|
||||
num_resize_actions[rate_update_index]);
|
||||
|
||||
const RateControlThresholds* rc_threshold = nullptr;
|
||||
if (rc_thresholds) {
|
||||
rc_threshold = &(*rc_thresholds)[rate_update_index];
|
||||
|
||||
EXPECT_LE(perc_encoding_rate_mismatch_,
|
||||
rc_threshold->max_encoding_rate_mismatch);
|
||||
}
|
||||
if (num_key_frames_ > 0) {
|
||||
int perc_key_frame_size_mismatch =
|
||||
100 * sum_key_frame_size_mismatch_ / num_key_frames_;
|
||||
printf(
|
||||
" # key frames : %d\n"
|
||||
" Key frame rate mismatch: %d\n",
|
||||
num_key_frames_, perc_key_frame_size_mismatch);
|
||||
if (rc_threshold) {
|
||||
EXPECT_LE(perc_key_frame_size_mismatch,
|
||||
rc_threshold->max_key_frame_size_mismatch);
|
||||
}
|
||||
}
|
||||
|
||||
const int num_temporal_layers =
|
||||
NumberOfTemporalLayers(config_.codec_settings);
|
||||
for (int i = 0; i < num_temporal_layers; i++) {
|
||||
int perc_frame_size_mismatch =
|
||||
100 * sum_frame_size_mismatch_[i] / num_frames_per_update_[i];
|
||||
int perc_encoding_rate_mismatch =
|
||||
100 * fabs(encoding_bitrate_[i] - bitrate_layer_[i]) /
|
||||
bitrate_layer_[i];
|
||||
printf(
|
||||
" Temporal layer #%d:\n"
|
||||
" Target layer bitrate : %f\n"
|
||||
" Layer frame rate : %f\n"
|
||||
" Layer per frame bandwidth : %f\n"
|
||||
" Layer encoding bitrate : %f\n"
|
||||
" Layer percent frame size mismatch : %d\n"
|
||||
" Layer percent encoding rate mismatch: %d\n"
|
||||
" # frames processed per layer : %d\n",
|
||||
i, bitrate_layer_[i], framerate_layer_[i], per_frame_bandwidth_[i],
|
||||
encoding_bitrate_[i], perc_frame_size_mismatch,
|
||||
perc_encoding_rate_mismatch, num_frames_per_update_[i]);
|
||||
if (rc_threshold) {
|
||||
EXPECT_LE(perc_frame_size_mismatch,
|
||||
rc_threshold->max_delta_frame_size_mismatch);
|
||||
EXPECT_LE(perc_encoding_rate_mismatch,
|
||||
rc_threshold->max_encoding_rate_mismatch);
|
||||
}
|
||||
}
|
||||
printf("\n");
|
||||
|
||||
if (rc_threshold) {
|
||||
EXPECT_LE(num_frames_to_hit_target_, rc_threshold->max_time_hit_target);
|
||||
EXPECT_LE(num_dropped_frames[rate_update_index],
|
||||
rc_threshold->max_num_dropped_frames);
|
||||
EXPECT_EQ(rc_threshold->num_spatial_resizes,
|
||||
num_resize_actions[rate_update_index]);
|
||||
EXPECT_EQ(rc_threshold->num_key_frames, num_key_frames_);
|
||||
}
|
||||
}
|
||||
|
||||
// Temporal layer index corresponding to frame number, for up to 3 layers.
|
||||
int VideoProcessorIntegrationTest::TemporalLayerIndexForFrame(
|
||||
int frame_number) const {
|
||||
const int num_temporal_layers =
|
||||
NumberOfTemporalLayers(config_.codec_settings);
|
||||
int tl_idx = -1;
|
||||
switch (num_temporal_layers) {
|
||||
case 1:
|
||||
tl_idx = 0;
|
||||
break;
|
||||
case 2:
|
||||
// temporal layer 0: 0 2 4 ...
|
||||
// temporal layer 1: 1 3
|
||||
tl_idx = (frame_number % 2 == 0) ? 0 : 1;
|
||||
break;
|
||||
case 3:
|
||||
// temporal layer 0: 0 4 8 ...
|
||||
// temporal layer 1: 2 6
|
||||
// temporal layer 2: 1 3 5 7
|
||||
if (frame_number % 4 == 0) {
|
||||
tl_idx = 0;
|
||||
} else if ((frame_number + 2) % 4 == 0) {
|
||||
tl_idx = 1;
|
||||
} else if ((frame_number + 1) % 2 == 0) {
|
||||
tl_idx = 2;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
RTC_NOTREACHED();
|
||||
break;
|
||||
}
|
||||
return tl_idx;
|
||||
}
|
||||
|
||||
// Reset quantities before each encoder rate update.
|
||||
void VideoProcessorIntegrationTest::ResetRateControlMetrics(
|
||||
int rate_update_index,
|
||||
const RateProfile& rate_profile) {
|
||||
// Set new rates.
|
||||
bitrate_kbps_ = rate_profile.target_bit_rate[rate_update_index];
|
||||
framerate_ = rate_profile.input_frame_rate[rate_update_index];
|
||||
const int num_temporal_layers =
|
||||
NumberOfTemporalLayers(config_.codec_settings);
|
||||
RTC_DCHECK_LE(num_temporal_layers, kMaxNumTemporalLayers);
|
||||
for (int i = 0; i < num_temporal_layers; i++) {
|
||||
float bit_rate_ratio = kVp8LayerRateAlloction[num_temporal_layers - 1][i];
|
||||
if (i > 0) {
|
||||
float bit_rate_delta_ratio =
|
||||
kVp8LayerRateAlloction[num_temporal_layers - 1][i] -
|
||||
kVp8LayerRateAlloction[num_temporal_layers - 1][i - 1];
|
||||
bitrate_layer_[i] = bitrate_kbps_ * bit_rate_delta_ratio;
|
||||
} else {
|
||||
bitrate_layer_[i] = bitrate_kbps_ * bit_rate_ratio;
|
||||
}
|
||||
framerate_layer_[i] =
|
||||
framerate_ / static_cast<float>(1 << (num_temporal_layers - 1));
|
||||
}
|
||||
if (num_temporal_layers == 3) {
|
||||
framerate_layer_[2] = framerate_ / 2.0f;
|
||||
}
|
||||
if (rate_update_index == 0) {
|
||||
target_size_key_frame_initial_ =
|
||||
0.5 * kInitialBufferSize * bitrate_layer_[0];
|
||||
}
|
||||
|
||||
// Reset rate control metrics.
|
||||
for (int i = 0; i < num_temporal_layers; i++) {
|
||||
num_frames_per_update_[i] = 0;
|
||||
sum_frame_size_mismatch_[i] = 0.0f;
|
||||
sum_encoded_frame_size_[i] = 0.0f;
|
||||
encoding_bitrate_[i] = 0.0f;
|
||||
// Update layer per-frame-bandwidth.
|
||||
per_frame_bandwidth_[i] = static_cast<float>(bitrate_layer_[i]) /
|
||||
static_cast<float>(framerate_layer_[i]);
|
||||
}
|
||||
// Set maximum size of key frames, following setting in the VP8 wrapper.
|
||||
float max_key_size = kScaleKeyFrameSize * kOptimalBufferSize * framerate_;
|
||||
// We don't know exact target size of the key frames (except for first one),
|
||||
// but the minimum in libvpx is ~|3 * per_frame_bandwidth| and maximum is
|
||||
// set by |max_key_size_ * per_frame_bandwidth|. Take middle point/average
|
||||
// as reference for mismatch. Note key frames always correspond to base
|
||||
// layer frame in this test.
|
||||
target_size_key_frame_ = 0.5 * (3 + max_key_size) * per_frame_bandwidth_[0];
|
||||
num_frames_total_ = 0;
|
||||
sum_encoded_frame_size_total_ = 0.0f;
|
||||
encoding_bitrate_total_ = 0.0f;
|
||||
perc_encoding_rate_mismatch_ = 0.0f;
|
||||
num_frames_to_hit_target_ =
|
||||
rate_profile.frame_index_rate_update[rate_update_index + 1];
|
||||
encoding_rate_within_target_ = false;
|
||||
sum_key_frame_size_mismatch_ = 0.0;
|
||||
num_key_frames_ = 0;
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
@ -0,0 +1,192 @@
|
||||
/*
|
||||
* Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_VIDEO_CODING_CODECS_TEST_VIDEOPROCESSOR_INTEGRATIONTEST_H_
|
||||
#define WEBRTC_MODULES_VIDEO_CODING_CODECS_TEST_VIDEOPROCESSOR_INTEGRATIONTEST_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/common_types.h"
|
||||
#include "webrtc/media/engine/webrtcvideodecoderfactory.h"
|
||||
#include "webrtc/media/engine/webrtcvideoencoderfactory.h"
|
||||
#include "webrtc/modules/video_coding/codecs/test/packet_manipulator.h"
|
||||
#include "webrtc/modules/video_coding/codecs/test/stats.h"
|
||||
#include "webrtc/modules/video_coding/codecs/test/videoprocessor.h"
|
||||
#include "webrtc/modules/video_coding/utility/ivf_file_writer.h"
|
||||
#include "webrtc/test/gtest.h"
|
||||
#include "webrtc/test/testsupport/frame_reader.h"
|
||||
#include "webrtc/test/testsupport/frame_writer.h"
|
||||
#include "webrtc/test/testsupport/packet_reader.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace test {
|
||||
|
||||
// The sequence of bit rate and frame rate changes for the encoder, the frame
|
||||
// number where the changes are made, and the total number of frames for the
|
||||
// test.
|
||||
struct RateProfile {
|
||||
static const int kMaxNumRateUpdates = 3;
|
||||
|
||||
int target_bit_rate[kMaxNumRateUpdates];
|
||||
int input_frame_rate[kMaxNumRateUpdates];
|
||||
int frame_index_rate_update[kMaxNumRateUpdates + 1];
|
||||
int num_frames;
|
||||
};
|
||||
|
||||
// Thresholds for the rate control metrics. The rate mismatch thresholds are
|
||||
// defined as percentages. |max_time_hit_target| is defined as number of frames,
|
||||
// after a rate update is made to the encoder, for the encoder to reach within
|
||||
// |kPercTargetvsActualMismatch| of new target rate. The thresholds are defined
|
||||
// for each rate update sequence.
|
||||
struct RateControlThresholds {
|
||||
int max_num_dropped_frames;
|
||||
int max_key_frame_size_mismatch;
|
||||
int max_delta_frame_size_mismatch;
|
||||
int max_encoding_rate_mismatch;
|
||||
int max_time_hit_target;
|
||||
int num_spatial_resizes;
|
||||
int num_key_frames;
|
||||
};
|
||||
|
||||
// Thresholds for the quality metrics.
|
||||
struct QualityThresholds {
|
||||
QualityThresholds(double min_avg_psnr,
|
||||
double min_min_psnr,
|
||||
double min_avg_ssim,
|
||||
double min_min_ssim)
|
||||
: min_avg_psnr(min_avg_psnr),
|
||||
min_min_psnr(min_min_psnr),
|
||||
min_avg_ssim(min_avg_ssim),
|
||||
min_min_ssim(min_min_ssim) {}
|
||||
double min_avg_psnr;
|
||||
double min_min_psnr;
|
||||
double min_avg_ssim;
|
||||
double min_min_ssim;
|
||||
};
|
||||
|
||||
// Should video files be saved persistently to disk for post-run visualization?
|
||||
struct VisualizationParams {
|
||||
bool save_encoded_ivf;
|
||||
bool save_decoded_y4m;
|
||||
};
|
||||
|
||||
// Integration test for video processor. Encodes+decodes a clip and
|
||||
// writes it to the output directory. After completion, quality metrics
|
||||
// (PSNR and SSIM) and rate control metrics are computed and compared to given
|
||||
// thresholds, to verify that the quality and encoder response is acceptable.
|
||||
// The rate control tests allow us to verify the behavior for changing bit rate,
|
||||
// changing frame rate, frame dropping/spatial resize, and temporal layers.
|
||||
// The thresholds for the rate control metrics are set to be fairly
|
||||
// conservative, so failure should only happen when some significant regression
|
||||
// or breakdown occurs.
|
||||
class VideoProcessorIntegrationTest : public testing::Test {
|
||||
protected:
|
||||
VideoProcessorIntegrationTest();
|
||||
~VideoProcessorIntegrationTest() override;
|
||||
|
||||
static void SetCodecSettings(TestConfig* config,
|
||||
VideoCodecType codec_type,
|
||||
int num_temporal_layers,
|
||||
bool error_concealment_on,
|
||||
bool denoising_on,
|
||||
bool frame_dropper_on,
|
||||
bool spatial_resize_on,
|
||||
bool resilience_on,
|
||||
int width,
|
||||
int height);
|
||||
|
||||
static void SetRateProfile(RateProfile* rate_profile,
|
||||
int rate_update_index,
|
||||
int bitrate_kbps,
|
||||
int framerate_fps,
|
||||
int frame_index_rate_update);
|
||||
|
||||
static void AddRateControlThresholds(
|
||||
int max_num_dropped_frames,
|
||||
int max_key_frame_size_mismatch,
|
||||
int max_delta_frame_size_mismatch,
|
||||
int max_encoding_rate_mismatch,
|
||||
int max_time_hit_target,
|
||||
int num_spatial_resizes,
|
||||
int num_key_frames,
|
||||
std::vector<RateControlThresholds>* rc_thresholds);
|
||||
|
||||
void ProcessFramesAndMaybeVerify(
|
||||
const RateProfile& rate_profile,
|
||||
const std::vector<RateControlThresholds>* rc_thresholds,
|
||||
const QualityThresholds* quality_thresholds,
|
||||
const VisualizationParams* visualization_params);
|
||||
|
||||
// Config.
|
||||
TestConfig config_;
|
||||
|
||||
private:
|
||||
static const int kMaxNumTemporalLayers = 3;
|
||||
|
||||
void CreateEncoderAndDecoder();
|
||||
void DestroyEncoderAndDecoder();
|
||||
void SetUpAndInitObjects(rtc::TaskQueue* task_queue,
|
||||
const int initial_bitrate_kbps,
|
||||
const int initial_framerate_fps,
|
||||
const VisualizationParams* visualization_params);
|
||||
void ReleaseAndCloseObjects(rtc::TaskQueue* task_queue);
|
||||
void UpdateRateControlMetrics(int frame_number);
|
||||
void PrintAndMaybeVerifyRateControlMetrics(
|
||||
int rate_update_index,
|
||||
const std::vector<RateControlThresholds>* rc_thresholds,
|
||||
const std::vector<int>& num_dropped_frames,
|
||||
const std::vector<int>& num_resize_actions);
|
||||
int TemporalLayerIndexForFrame(int frame_number) const;
|
||||
void ResetRateControlMetrics(int rate_update_index,
|
||||
const RateProfile& rate_profile);
|
||||
|
||||
// Codecs.
|
||||
std::unique_ptr<VideoEncoder> encoder_;
|
||||
std::unique_ptr<cricket::WebRtcVideoDecoderFactory> decoder_factory_;
|
||||
VideoDecoder* decoder_;
|
||||
|
||||
// Helper objects.
|
||||
std::unique_ptr<FrameReader> analysis_frame_reader_;
|
||||
std::unique_ptr<FrameWriter> analysis_frame_writer_;
|
||||
std::unique_ptr<IvfFileWriter> encoded_frame_writer_;
|
||||
std::unique_ptr<FrameWriter> decoded_frame_writer_;
|
||||
PacketReader packet_reader_;
|
||||
std::unique_ptr<PacketManipulator> packet_manipulator_;
|
||||
Stats stats_;
|
||||
std::unique_ptr<VideoProcessor> processor_;
|
||||
|
||||
// Quantities defined/updated for every encoder rate update.
|
||||
int num_frames_per_update_[kMaxNumTemporalLayers];
|
||||
float sum_frame_size_mismatch_[kMaxNumTemporalLayers];
|
||||
float sum_encoded_frame_size_[kMaxNumTemporalLayers];
|
||||
float encoding_bitrate_[kMaxNumTemporalLayers];
|
||||
float per_frame_bandwidth_[kMaxNumTemporalLayers];
|
||||
float bitrate_layer_[kMaxNumTemporalLayers];
|
||||
float framerate_layer_[kMaxNumTemporalLayers];
|
||||
int num_frames_total_;
|
||||
float sum_encoded_frame_size_total_;
|
||||
float encoding_bitrate_total_;
|
||||
float perc_encoding_rate_mismatch_;
|
||||
int num_frames_to_hit_target_;
|
||||
bool encoding_rate_within_target_;
|
||||
int bitrate_kbps_;
|
||||
int framerate_;
|
||||
float target_size_key_frame_initial_;
|
||||
float target_size_key_frame_;
|
||||
float sum_key_frame_size_mismatch_;
|
||||
int num_key_frames_;
|
||||
};
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_VIDEO_CODING_CODECS_TEST_VIDEOPROCESSOR_INTEGRATIONTEST_H_
|
||||
@ -0,0 +1,379 @@
|
||||
/*
|
||||
* Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "webrtc/modules/video_coding/codecs/test/videoprocessor_integrationtest.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/test/testsupport/fileutils.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace test {
|
||||
|
||||
namespace {
|
||||
|
||||
// Codec settings.
|
||||
const bool kResilienceOn = true;
|
||||
const int kCifWidth = 352;
|
||||
const int kCifHeight = 288;
|
||||
#if !defined(WEBRTC_IOS)
|
||||
const int kNumFramesShort = 100;
|
||||
#endif
|
||||
const int kNumFramesLong = 300;
|
||||
|
||||
const std::nullptr_t kNoVisualizationParams = nullptr;
|
||||
|
||||
} // namespace
|
||||
|
||||
class VideoProcessorIntegrationTestLibvpx
|
||||
: public VideoProcessorIntegrationTest {
|
||||
protected:
|
||||
VideoProcessorIntegrationTestLibvpx() {
|
||||
config_.filename = "foreman_cif";
|
||||
config_.input_filename = ResourcePath(config_.filename, "yuv");
|
||||
config_.output_filename =
|
||||
TempFilename(OutputPath(), "videoprocessor_integrationtest_libvpx");
|
||||
config_.networking_config.packet_loss_probability = 0.0;
|
||||
// Only allow encoder/decoder to use single core, for predictability.
|
||||
config_.use_single_core = true;
|
||||
config_.verbose = false;
|
||||
config_.hw_encoder = false;
|
||||
config_.hw_decoder = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Fails on iOS. See webrtc:4755.
|
||||
#if !defined(WEBRTC_IOS)
|
||||
|
||||
#if !defined(RTC_DISABLE_VP9)
|
||||
// VP9: Run with no packet loss and fixed bitrate. Quality should be very high.
|
||||
// One key frame (first frame only) in sequence.
|
||||
TEST_F(VideoProcessorIntegrationTestLibvpx, Process0PercentPacketLossVP9) {
|
||||
SetCodecSettings(&config_, kVideoCodecVP9, 1, false, false, true, false,
|
||||
kResilienceOn, kCifWidth, kCifHeight);
|
||||
|
||||
RateProfile rate_profile;
|
||||
SetRateProfile(&rate_profile, 0, 500, 30, 0);
|
||||
rate_profile.frame_index_rate_update[1] = kNumFramesShort + 1;
|
||||
rate_profile.num_frames = kNumFramesShort;
|
||||
|
||||
std::vector<RateControlThresholds> rc_thresholds;
|
||||
AddRateControlThresholds(0, 40, 20, 10, 20, 0, 1, &rc_thresholds);
|
||||
|
||||
QualityThresholds quality_thresholds(37.0, 36.0, 0.93, 0.92);
|
||||
|
||||
ProcessFramesAndMaybeVerify(rate_profile, &rc_thresholds, &quality_thresholds,
|
||||
kNoVisualizationParams);
|
||||
}
|
||||
|
||||
// VP9: Run with 5% packet loss and fixed bitrate. Quality should be a bit
|
||||
// lower. One key frame (first frame only) in sequence.
|
||||
TEST_F(VideoProcessorIntegrationTestLibvpx, Process5PercentPacketLossVP9) {
|
||||
config_.networking_config.packet_loss_probability = 0.05f;
|
||||
SetCodecSettings(&config_, kVideoCodecVP9, 1, false, false, true, false,
|
||||
kResilienceOn, kCifWidth, kCifHeight);
|
||||
|
||||
RateProfile rate_profile;
|
||||
SetRateProfile(&rate_profile, 0, 500, 30, 0);
|
||||
rate_profile.frame_index_rate_update[1] = kNumFramesShort + 1;
|
||||
rate_profile.num_frames = kNumFramesShort;
|
||||
|
||||
std::vector<RateControlThresholds> rc_thresholds;
|
||||
AddRateControlThresholds(0, 40, 20, 10, 20, 0, 1, &rc_thresholds);
|
||||
|
||||
QualityThresholds quality_thresholds(17.0, 14.0, 0.45, 0.36);
|
||||
|
||||
ProcessFramesAndMaybeVerify(rate_profile, &rc_thresholds, &quality_thresholds,
|
||||
kNoVisualizationParams);
|
||||
}
|
||||
|
||||
// VP9: Run with no packet loss, with varying bitrate (3 rate updates):
|
||||
// low to high to medium. Check that quality and encoder response to the new
|
||||
// target rate/per-frame bandwidth (for each rate update) is within limits.
|
||||
// One key frame (first frame only) in sequence.
|
||||
TEST_F(VideoProcessorIntegrationTestLibvpx, ProcessNoLossChangeBitRateVP9) {
|
||||
SetCodecSettings(&config_, kVideoCodecVP9, 1, false, false, true, false,
|
||||
kResilienceOn, kCifWidth, kCifHeight);
|
||||
|
||||
RateProfile rate_profile;
|
||||
SetRateProfile(&rate_profile, 0, 200, 30, 0);
|
||||
SetRateProfile(&rate_profile, 1, 700, 30, 100);
|
||||
SetRateProfile(&rate_profile, 2, 500, 30, 200);
|
||||
rate_profile.frame_index_rate_update[3] = kNumFramesLong + 1;
|
||||
rate_profile.num_frames = kNumFramesLong;
|
||||
|
||||
std::vector<RateControlThresholds> rc_thresholds;
|
||||
AddRateControlThresholds(0, 30, 20, 20, 35, 0, 1, &rc_thresholds);
|
||||
AddRateControlThresholds(2, 0, 20, 20, 60, 0, 0, &rc_thresholds);
|
||||
AddRateControlThresholds(0, 0, 25, 20, 40, 0, 0, &rc_thresholds);
|
||||
|
||||
QualityThresholds quality_thresholds(35.5, 30.0, 0.90, 0.85);
|
||||
|
||||
ProcessFramesAndMaybeVerify(rate_profile, &rc_thresholds, &quality_thresholds,
|
||||
kNoVisualizationParams);
|
||||
}
|
||||
|
||||
// VP9: Run with no packet loss, with an update (decrease) in frame rate.
|
||||
// Lower frame rate means higher per-frame-bandwidth, so easier to encode.
|
||||
// At the low bitrate in this test, this means better rate control after the
|
||||
// update(s) to lower frame rate. So expect less frame drops, and max values
|
||||
// for the rate control metrics can be lower. One key frame (first frame only).
|
||||
// Note: quality after update should be higher but we currently compute quality
|
||||
// metrics averaged over whole sequence run.
|
||||
TEST_F(VideoProcessorIntegrationTestLibvpx,
|
||||
ProcessNoLossChangeFrameRateFrameDropVP9) {
|
||||
SetCodecSettings(&config_, kVideoCodecVP9, 1, false, false, true, false,
|
||||
kResilienceOn, kCifWidth, kCifHeight);
|
||||
|
||||
RateProfile rate_profile;
|
||||
SetRateProfile(&rate_profile, 0, 100, 24, 0);
|
||||
SetRateProfile(&rate_profile, 1, 100, 15, 100);
|
||||
SetRateProfile(&rate_profile, 2, 100, 10, 200);
|
||||
rate_profile.frame_index_rate_update[3] = kNumFramesLong + 1;
|
||||
rate_profile.num_frames = kNumFramesLong;
|
||||
|
||||
std::vector<RateControlThresholds> rc_thresholds;
|
||||
AddRateControlThresholds(45, 50, 95, 15, 45, 0, 1, &rc_thresholds);
|
||||
AddRateControlThresholds(20, 0, 50, 10, 30, 0, 0, &rc_thresholds);
|
||||
AddRateControlThresholds(5, 0, 30, 5, 25, 0, 0, &rc_thresholds);
|
||||
|
||||
QualityThresholds quality_thresholds(31.5, 18.0, 0.80, 0.43);
|
||||
|
||||
ProcessFramesAndMaybeVerify(rate_profile, &rc_thresholds, &quality_thresholds,
|
||||
kNoVisualizationParams);
|
||||
}
|
||||
|
||||
// VP9: Run with no packet loss and denoiser on. One key frame (first frame).
|
||||
TEST_F(VideoProcessorIntegrationTestLibvpx, ProcessNoLossDenoiserOnVP9) {
|
||||
SetCodecSettings(&config_, kVideoCodecVP9, 1, false, true, true, false,
|
||||
kResilienceOn, kCifWidth, kCifHeight);
|
||||
|
||||
RateProfile rate_profile;
|
||||
SetRateProfile(&rate_profile, 0, 500, 30, 0);
|
||||
rate_profile.frame_index_rate_update[1] = kNumFramesShort + 1;
|
||||
rate_profile.num_frames = kNumFramesShort;
|
||||
|
||||
std::vector<RateControlThresholds> rc_thresholds;
|
||||
AddRateControlThresholds(0, 40, 20, 10, 20, 0, 1, &rc_thresholds);
|
||||
|
||||
QualityThresholds quality_thresholds(36.8, 35.8, 0.92, 0.91);
|
||||
|
||||
ProcessFramesAndMaybeVerify(rate_profile, &rc_thresholds, &quality_thresholds,
|
||||
kNoVisualizationParams);
|
||||
}
|
||||
|
||||
// Run with no packet loss, at low bitrate.
|
||||
// spatial_resize is on, for this low bitrate expect one resize in sequence.
|
||||
// Resize happens on delta frame. Expect only one key frame (first frame).
|
||||
TEST_F(VideoProcessorIntegrationTestLibvpx,
|
||||
DISABLED_ProcessNoLossSpatialResizeFrameDropVP9) {
|
||||
SetCodecSettings(&config_, kVideoCodecVP9, 1, false, false, true, true,
|
||||
kResilienceOn, kCifWidth, kCifHeight);
|
||||
|
||||
RateProfile rate_profile;
|
||||
SetRateProfile(&rate_profile, 0, 50, 30, 0);
|
||||
rate_profile.frame_index_rate_update[1] = kNumFramesLong + 1;
|
||||
rate_profile.num_frames = kNumFramesLong;
|
||||
|
||||
std::vector<RateControlThresholds> rc_thresholds;
|
||||
AddRateControlThresholds(228, 70, 160, 15, 80, 1, 1, &rc_thresholds);
|
||||
|
||||
QualityThresholds quality_thresholds(24.0, 13.0, 0.65, 0.37);
|
||||
|
||||
ProcessFramesAndMaybeVerify(rate_profile, &rc_thresholds, &quality_thresholds,
|
||||
kNoVisualizationParams);
|
||||
}
|
||||
|
||||
// TODO(marpan): Add temporal layer test for VP9, once changes are in
|
||||
// vp9 wrapper for this.
|
||||
|
||||
#endif // !defined(RTC_DISABLE_VP9)
|
||||
|
||||
// VP8: Run with no packet loss and fixed bitrate. Quality should be very high.
|
||||
// One key frame (first frame only) in sequence. Setting |key_frame_interval|
|
||||
// to -1 below means no periodic key frames in test.
|
||||
TEST_F(VideoProcessorIntegrationTestLibvpx, ProcessZeroPacketLoss) {
|
||||
SetCodecSettings(&config_, kVideoCodecVP8, 1, false, true, true, false,
|
||||
kResilienceOn, kCifWidth, kCifHeight);
|
||||
|
||||
RateProfile rate_profile;
|
||||
SetRateProfile(&rate_profile, 0, 500, 30, 0);
|
||||
rate_profile.frame_index_rate_update[1] = kNumFramesShort + 1;
|
||||
rate_profile.num_frames = kNumFramesShort;
|
||||
|
||||
std::vector<RateControlThresholds> rc_thresholds;
|
||||
AddRateControlThresholds(0, 40, 20, 10, 15, 0, 1, &rc_thresholds);
|
||||
|
||||
QualityThresholds quality_thresholds(34.95, 33.0, 0.90, 0.89);
|
||||
|
||||
ProcessFramesAndMaybeVerify(rate_profile, &rc_thresholds, &quality_thresholds,
|
||||
kNoVisualizationParams);
|
||||
}
|
||||
|
||||
// VP8: Run with 5% packet loss and fixed bitrate. Quality should be a bit
|
||||
// lower. One key frame (first frame only) in sequence.
|
||||
TEST_F(VideoProcessorIntegrationTestLibvpx, Process5PercentPacketLoss) {
|
||||
config_.networking_config.packet_loss_probability = 0.05f;
|
||||
SetCodecSettings(&config_, kVideoCodecVP8, 1, false, true, true, false,
|
||||
kResilienceOn, kCifWidth, kCifHeight);
|
||||
|
||||
RateProfile rate_profile;
|
||||
SetRateProfile(&rate_profile, 0, 500, 30, 0);
|
||||
rate_profile.frame_index_rate_update[1] = kNumFramesShort + 1;
|
||||
rate_profile.num_frames = kNumFramesShort;
|
||||
|
||||
std::vector<RateControlThresholds> rc_thresholds;
|
||||
AddRateControlThresholds(0, 40, 20, 10, 15, 0, 1, &rc_thresholds);
|
||||
|
||||
QualityThresholds quality_thresholds(20.0, 16.0, 0.60, 0.40);
|
||||
|
||||
ProcessFramesAndMaybeVerify(rate_profile, &rc_thresholds, &quality_thresholds,
|
||||
kNoVisualizationParams);
|
||||
}
|
||||
|
||||
// VP8: Run with 10% packet loss and fixed bitrate. Quality should be lower.
|
||||
// One key frame (first frame only) in sequence.
|
||||
TEST_F(VideoProcessorIntegrationTestLibvpx, Process10PercentPacketLoss) {
|
||||
config_.networking_config.packet_loss_probability = 0.1f;
|
||||
SetCodecSettings(&config_, kVideoCodecVP8, 1, false, true, true, false,
|
||||
kResilienceOn, kCifWidth, kCifHeight);
|
||||
|
||||
RateProfile rate_profile;
|
||||
SetRateProfile(&rate_profile, 0, 500, 30, 0);
|
||||
rate_profile.frame_index_rate_update[1] = kNumFramesShort + 1;
|
||||
rate_profile.num_frames = kNumFramesShort;
|
||||
|
||||
std::vector<RateControlThresholds> rc_thresholds;
|
||||
AddRateControlThresholds(0, 40, 20, 10, 15, 0, 1, &rc_thresholds);
|
||||
|
||||
QualityThresholds quality_thresholds(19.0, 16.0, 0.50, 0.35);
|
||||
|
||||
ProcessFramesAndMaybeVerify(rate_profile, &rc_thresholds, &quality_thresholds,
|
||||
kNoVisualizationParams);
|
||||
}
|
||||
|
||||
#endif // !defined(WEBRTC_IOS)
|
||||
|
||||
// The tests below are currently disabled for Android. For ARM, the encoder
|
||||
// uses |cpu_speed| = 12, as opposed to default |cpu_speed| <= 6 for x86,
|
||||
// which leads to significantly different quality. The quality and rate control
|
||||
// settings in the tests below are defined for encoder speed setting
|
||||
// |cpu_speed| <= ~6. A number of settings would need to be significantly
|
||||
// modified for the |cpu_speed| = 12 case. For now, keep the tests below
|
||||
// disabled on Android. Some quality parameter in the above test has been
|
||||
// adjusted to also pass for |cpu_speed| <= 12.
|
||||
|
||||
// VP8: Run with no packet loss, with varying bitrate (3 rate updates):
|
||||
// low to high to medium. Check that quality and encoder response to the new
|
||||
// target rate/per-frame bandwidth (for each rate update) is within limits.
|
||||
// One key frame (first frame only) in sequence.
|
||||
// Too slow to finish before timeout on iOS. See webrtc:4755.
|
||||
#if defined(WEBRTC_ANDROID) || defined(WEBRTC_IOS)
|
||||
#define MAYBE_ProcessNoLossChangeBitRateVP8 \
|
||||
DISABLED_ProcessNoLossChangeBitRateVP8
|
||||
#else
|
||||
#define MAYBE_ProcessNoLossChangeBitRateVP8 ProcessNoLossChangeBitRateVP8
|
||||
#endif
|
||||
TEST_F(VideoProcessorIntegrationTestLibvpx,
|
||||
MAYBE_ProcessNoLossChangeBitRateVP8) {
|
||||
SetCodecSettings(&config_, kVideoCodecVP8, 1, false, true, true, false,
|
||||
kResilienceOn, kCifWidth, kCifHeight);
|
||||
|
||||
RateProfile rate_profile;
|
||||
SetRateProfile(&rate_profile, 0, 200, 30, 0);
|
||||
SetRateProfile(&rate_profile, 1, 800, 30, 100);
|
||||
SetRateProfile(&rate_profile, 2, 500, 30, 200);
|
||||
rate_profile.frame_index_rate_update[3] = kNumFramesLong + 1;
|
||||
rate_profile.num_frames = kNumFramesLong;
|
||||
|
||||
std::vector<RateControlThresholds> rc_thresholds;
|
||||
AddRateControlThresholds(0, 45, 20, 10, 15, 0, 1, &rc_thresholds);
|
||||
AddRateControlThresholds(0, 0, 25, 20, 10, 0, 0, &rc_thresholds);
|
||||
AddRateControlThresholds(0, 0, 25, 15, 10, 0, 0, &rc_thresholds);
|
||||
|
||||
QualityThresholds quality_thresholds(34.0, 32.0, 0.85, 0.80);
|
||||
|
||||
ProcessFramesAndMaybeVerify(rate_profile, &rc_thresholds, &quality_thresholds,
|
||||
kNoVisualizationParams);
|
||||
}
|
||||
|
||||
// VP8: Run with no packet loss, with an update (decrease) in frame rate.
|
||||
// Lower frame rate means higher per-frame-bandwidth, so easier to encode.
|
||||
// At the bitrate in this test, this means better rate control after the
|
||||
// update(s) to lower frame rate. So expect less frame drops, and max values
|
||||
// for the rate control metrics can be lower. One key frame (first frame only).
|
||||
// Note: quality after update should be higher but we currently compute quality
|
||||
// metrics averaged over whole sequence run.
|
||||
// Too slow to finish before timeout on iOS. See webrtc:4755.
|
||||
#if defined(WEBRTC_ANDROID) || defined(WEBRTC_IOS)
|
||||
#define MAYBE_ProcessNoLossChangeFrameRateFrameDropVP8 \
|
||||
DISABLED_ProcessNoLossChangeFrameRateFrameDropVP8
|
||||
#else
|
||||
#define MAYBE_ProcessNoLossChangeFrameRateFrameDropVP8 \
|
||||
ProcessNoLossChangeFrameRateFrameDropVP8
|
||||
#endif
|
||||
TEST_F(VideoProcessorIntegrationTestLibvpx,
|
||||
MAYBE_ProcessNoLossChangeFrameRateFrameDropVP8) {
|
||||
SetCodecSettings(&config_, kVideoCodecVP8, 1, false, true, true, false,
|
||||
kResilienceOn, kCifWidth, kCifHeight);
|
||||
|
||||
RateProfile rate_profile;
|
||||
SetRateProfile(&rate_profile, 0, 80, 24, 0);
|
||||
SetRateProfile(&rate_profile, 1, 80, 15, 100);
|
||||
SetRateProfile(&rate_profile, 2, 80, 10, 200);
|
||||
rate_profile.frame_index_rate_update[3] = kNumFramesLong + 1;
|
||||
rate_profile.num_frames = kNumFramesLong;
|
||||
|
||||
std::vector<RateControlThresholds> rc_thresholds;
|
||||
AddRateControlThresholds(40, 20, 75, 15, 60, 0, 1, &rc_thresholds);
|
||||
AddRateControlThresholds(10, 0, 25, 10, 35, 0, 0, &rc_thresholds);
|
||||
AddRateControlThresholds(0, 0, 20, 10, 15, 0, 0, &rc_thresholds);
|
||||
|
||||
QualityThresholds quality_thresholds(31.0, 22.0, 0.80, 0.65);
|
||||
|
||||
ProcessFramesAndMaybeVerify(rate_profile, &rc_thresholds, &quality_thresholds,
|
||||
kNoVisualizationParams);
|
||||
}
|
||||
|
||||
// VP8: Run with no packet loss, with 3 temporal layers, with a rate update in
|
||||
// the middle of the sequence. The max values for the frame size mismatch and
|
||||
// encoding rate mismatch are applied to each layer.
|
||||
// No dropped frames in this test, and internal spatial resizer is off.
|
||||
// One key frame (first frame only) in sequence, so no spatial resizing.
|
||||
// Too slow to finish before timeout on iOS. See webrtc:4755.
|
||||
#if defined(WEBRTC_ANDROID) || defined(WEBRTC_IOS)
|
||||
#define MAYBE_ProcessNoLossTemporalLayersVP8 \
|
||||
DISABLED_ProcessNoLossTemporalLayersVP8
|
||||
#else
|
||||
#define MAYBE_ProcessNoLossTemporalLayersVP8 ProcessNoLossTemporalLayersVP8
|
||||
#endif
|
||||
TEST_F(VideoProcessorIntegrationTestLibvpx,
|
||||
MAYBE_ProcessNoLossTemporalLayersVP8) {
|
||||
SetCodecSettings(&config_, kVideoCodecVP8, 3, false, true, true, false,
|
||||
kResilienceOn, kCifWidth, kCifHeight);
|
||||
|
||||
RateProfile rate_profile;
|
||||
SetRateProfile(&rate_profile, 0, 200, 30, 0);
|
||||
SetRateProfile(&rate_profile, 1, 400, 30, 150);
|
||||
rate_profile.frame_index_rate_update[2] = kNumFramesLong + 1;
|
||||
rate_profile.num_frames = kNumFramesLong;
|
||||
|
||||
std::vector<RateControlThresholds> rc_thresholds;
|
||||
AddRateControlThresholds(0, 20, 30, 10, 10, 0, 1, &rc_thresholds);
|
||||
AddRateControlThresholds(0, 0, 30, 15, 10, 0, 0, &rc_thresholds);
|
||||
|
||||
QualityThresholds quality_thresholds(32.5, 30.0, 0.85, 0.80);
|
||||
|
||||
ProcessFramesAndMaybeVerify(rate_profile, &rc_thresholds, &quality_thresholds,
|
||||
kNoVisualizationParams);
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "webrtc/modules/video_coding/codecs/test/videoprocessor_integrationtest.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/test/field_trial.h"
|
||||
#include "webrtc/test/testsupport/fileutils.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace test {
|
||||
|
||||
#if defined(WEBRTC_ANDROID)
|
||||
|
||||
namespace {
|
||||
const int kForemanNumFrames = 300;
|
||||
const std::nullptr_t kNoVisualizationParams = nullptr;
|
||||
} // namespace
|
||||
|
||||
class VideoProcessorIntegrationTestMediaCodec
|
||||
: public VideoProcessorIntegrationTest {
|
||||
protected:
|
||||
VideoProcessorIntegrationTestMediaCodec() {
|
||||
config_.filename = "foreman_cif";
|
||||
config_.input_filename = ResourcePath(config_.filename, "yuv");
|
||||
config_.output_filename =
|
||||
TempFilename(OutputPath(), "videoprocessor_integrationtest_mediacodec");
|
||||
config_.verbose = false;
|
||||
config_.hw_encoder = true;
|
||||
config_.hw_decoder = true;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(VideoProcessorIntegrationTestMediaCodec, ForemanCif500kbpsVp8) {
|
||||
SetCodecSettings(&config_, kVideoCodecVP8, 1, false, false, false, false,
|
||||
false, 352, 288);
|
||||
|
||||
RateProfile rate_profile;
|
||||
SetRateProfile(&rate_profile, 0, 500, 30, 0); // Start below |low_kbps|.
|
||||
rate_profile.frame_index_rate_update[1] = kForemanNumFrames + 1;
|
||||
rate_profile.num_frames = kForemanNumFrames;
|
||||
|
||||
// The thresholds below may have to be tweaked to let even poor MediaCodec
|
||||
// implementations pass. If this test fails on the bots, disable it and
|
||||
// ping brandtr@.
|
||||
std::vector<RateControlThresholds> rc_thresholds;
|
||||
AddRateControlThresholds(5, 95, 20, 10, 10, 0, 1, &rc_thresholds);
|
||||
|
||||
QualityThresholds quality_thresholds(30.0, 15.0, 0.90, 0.40);
|
||||
|
||||
ProcessFramesAndMaybeVerify(rate_profile, &rc_thresholds, &quality_thresholds,
|
||||
kNoVisualizationParams);
|
||||
}
|
||||
|
||||
TEST_F(VideoProcessorIntegrationTestMediaCodec,
|
||||
Foreman240p100kbpsVp8WithForcedSwFallback) {
|
||||
ScopedFieldTrials override_field_trials(
|
||||
"WebRTC-VP8-Forced-Fallback-Encoder/Enabled-150,175,10000,1/");
|
||||
|
||||
config_.filename = "foreman_320x240";
|
||||
config_.input_filename = ResourcePath(config_.filename, "yuv");
|
||||
config_.sw_fallback_encoder = true;
|
||||
SetCodecSettings(&config_, kVideoCodecVP8, 1, false, false, false, false,
|
||||
false, 320, 240);
|
||||
|
||||
RateProfile rate_profile;
|
||||
SetRateProfile(&rate_profile, 0, 100, 10, 0); // Start below |low_kbps|.
|
||||
SetRateProfile(&rate_profile, 1, 100, 10, 80); // Fallback in this bucket.
|
||||
SetRateProfile(&rate_profile, 2, 200, 10, 200); // Switch back here.
|
||||
rate_profile.frame_index_rate_update[3] = kForemanNumFrames + 1;
|
||||
rate_profile.num_frames = kForemanNumFrames;
|
||||
|
||||
// The thresholds below may have to be tweaked to let even poor MediaCodec
|
||||
// implementations pass. If this test fails on the bots, disable it and
|
||||
// ping brandtr@.
|
||||
std::vector<RateControlThresholds> rc_thresholds;
|
||||
AddRateControlThresholds(0, 50, 75, 70, 10, 0, 1, &rc_thresholds);
|
||||
AddRateControlThresholds(0, 50, 25, 12, 60, 0, 1, &rc_thresholds);
|
||||
AddRateControlThresholds(0, 65, 15, 5, 5, 0, 1, &rc_thresholds);
|
||||
|
||||
QualityThresholds quality_thresholds(33.0, 30.0, 0.90, 0.85);
|
||||
|
||||
ProcessFramesAndMaybeVerify(rate_profile, &rc_thresholds, &quality_thresholds,
|
||||
kNoVisualizationParams);
|
||||
}
|
||||
|
||||
#endif // defined(WEBRTC_ANDROID)
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "webrtc/modules/video_coding/codecs/test/videoprocessor_integrationtest.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/test/testsupport/fileutils.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace test {
|
||||
|
||||
#if defined(WEBRTC_USE_H264)
|
||||
|
||||
namespace {
|
||||
|
||||
// Codec settings.
|
||||
const bool kResilienceOn = true;
|
||||
const int kCifWidth = 352;
|
||||
const int kCifHeight = 288;
|
||||
const int kNumFrames = 100;
|
||||
|
||||
const std::nullptr_t kNoVisualizationParams = nullptr;
|
||||
|
||||
} // namespace
|
||||
|
||||
class VideoProcessorIntegrationTestOpenH264
|
||||
: public VideoProcessorIntegrationTest {
|
||||
protected:
|
||||
VideoProcessorIntegrationTestOpenH264() {
|
||||
config_.filename = "foreman_cif";
|
||||
config_.input_filename = ResourcePath(config_.filename, "yuv");
|
||||
config_.output_filename =
|
||||
TempFilename(OutputPath(), "videoprocessor_integrationtest_libvpx");
|
||||
config_.networking_config.packet_loss_probability = 0.0;
|
||||
// Only allow encoder/decoder to use single core, for predictability.
|
||||
config_.use_single_core = true;
|
||||
config_.verbose = false;
|
||||
config_.hw_encoder = false;
|
||||
config_.hw_decoder = false;
|
||||
}
|
||||
};
|
||||
|
||||
// H264: Run with no packet loss and fixed bitrate. Quality should be very high.
|
||||
// Note(hbos): The PacketManipulatorImpl code used to simulate packet loss in
|
||||
// these unittests appears to drop "packets" in a way that is not compatible
|
||||
// with H264. Therefore ProcessXPercentPacketLossH264, X != 0, unittests have
|
||||
// not been added.
|
||||
TEST_F(VideoProcessorIntegrationTestOpenH264, Process0PercentPacketLossH264) {
|
||||
SetCodecSettings(&config_, kVideoCodecH264, 1, false, false, true, false,
|
||||
kResilienceOn, kCifWidth, kCifHeight);
|
||||
|
||||
RateProfile rate_profile;
|
||||
SetRateProfile(&rate_profile, 0, 500, 30, 0);
|
||||
rate_profile.frame_index_rate_update[1] = kNumFrames + 1;
|
||||
rate_profile.num_frames = kNumFrames;
|
||||
|
||||
std::vector<RateControlThresholds> rc_thresholds;
|
||||
AddRateControlThresholds(2, 60, 20, 10, 20, 0, 1, &rc_thresholds);
|
||||
|
||||
QualityThresholds quality_thresholds(35.0, 25.0, 0.93, 0.70);
|
||||
|
||||
ProcessFramesAndMaybeVerify(rate_profile, &rc_thresholds, &quality_thresholds,
|
||||
kNoVisualizationParams);
|
||||
}
|
||||
|
||||
#endif // defined(WEBRTC_USE_H264)
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "webrtc/modules/video_coding/codecs/test/videoprocessor_integrationtest.h"
|
||||
|
||||
#include "webrtc/test/testsupport/fileutils.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace test {
|
||||
|
||||
namespace {
|
||||
|
||||
// Loop variables.
|
||||
const int kBitrates[] = {500};
|
||||
const VideoCodecType kVideoCodecType[] = {kVideoCodecVP8};
|
||||
const bool kHwCodec[] = {false};
|
||||
|
||||
// Codec settings.
|
||||
const bool kResilienceOn = false;
|
||||
const int kNumTemporalLayers = 1;
|
||||
const bool kDenoisingOn = false;
|
||||
const bool kErrorConcealmentOn = false;
|
||||
const bool kSpatialResizeOn = false;
|
||||
const bool kFrameDropperOn = false;
|
||||
|
||||
// Test settings.
|
||||
const bool kUseSingleCore = false;
|
||||
const VisualizationParams kVisualizationParams = {
|
||||
false, // save_encoded_ivf
|
||||
false, // save_decoded_y4m
|
||||
};
|
||||
|
||||
const int kNumFrames = 30;
|
||||
|
||||
} // namespace
|
||||
|
||||
// Tests for plotting statistics from logs.
|
||||
class VideoProcessorIntegrationTestParameterized
|
||||
: public VideoProcessorIntegrationTest,
|
||||
public ::testing::WithParamInterface<
|
||||
::testing::tuple<int, VideoCodecType, bool>> {
|
||||
protected:
|
||||
VideoProcessorIntegrationTestParameterized()
|
||||
: bitrate_(::testing::get<0>(GetParam())),
|
||||
codec_type_(::testing::get<1>(GetParam())),
|
||||
hw_codec_(::testing::get<2>(GetParam())) {}
|
||||
~VideoProcessorIntegrationTestParameterized() override = default;
|
||||
|
||||
void RunTest(int width,
|
||||
int height,
|
||||
int framerate,
|
||||
const std::string& filename) {
|
||||
config_.filename = filename;
|
||||
config_.input_filename = ResourcePath(filename, "yuv");
|
||||
config_.output_filename =
|
||||
TempFilename(OutputPath(), "plot_videoprocessor_integrationtest");
|
||||
config_.use_single_core = kUseSingleCore;
|
||||
config_.verbose = true;
|
||||
config_.hw_encoder = hw_codec_;
|
||||
config_.hw_decoder = hw_codec_;
|
||||
SetCodecSettings(&config_, codec_type_, kNumTemporalLayers,
|
||||
kErrorConcealmentOn, kDenoisingOn, kFrameDropperOn,
|
||||
kSpatialResizeOn, kResilienceOn, width, height);
|
||||
|
||||
RateProfile rate_profile;
|
||||
SetRateProfile(&rate_profile,
|
||||
0, // update_index
|
||||
bitrate_, framerate,
|
||||
0); // frame_index_rate_update
|
||||
rate_profile.frame_index_rate_update[1] = kNumFrames + 1;
|
||||
rate_profile.num_frames = kNumFrames;
|
||||
|
||||
ProcessFramesAndMaybeVerify(rate_profile, nullptr, nullptr,
|
||||
&kVisualizationParams);
|
||||
}
|
||||
|
||||
const int bitrate_;
|
||||
const VideoCodecType codec_type_;
|
||||
const bool hw_codec_;
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(CodecSettings,
|
||||
VideoProcessorIntegrationTestParameterized,
|
||||
::testing::Combine(::testing::ValuesIn(kBitrates),
|
||||
::testing::ValuesIn(kVideoCodecType),
|
||||
::testing::ValuesIn(kHwCodec)));
|
||||
|
||||
TEST_P(VideoProcessorIntegrationTestParameterized, Process_128x96_30fps) {
|
||||
RunTest(128, 96, 30, "foreman_128x96");
|
||||
}
|
||||
|
||||
TEST_P(VideoProcessorIntegrationTestParameterized, Process_160x120_30fps) {
|
||||
RunTest(160, 120, 30, "foreman_160x120");
|
||||
}
|
||||
|
||||
TEST_P(VideoProcessorIntegrationTestParameterized, Process_176x144_30fps) {
|
||||
RunTest(176, 144, 30, "foreman_176x144");
|
||||
}
|
||||
|
||||
TEST_P(VideoProcessorIntegrationTestParameterized, Process_320x240_30fps) {
|
||||
RunTest(320, 240, 30, "foreman_320x240");
|
||||
}
|
||||
|
||||
TEST_P(VideoProcessorIntegrationTestParameterized, Process_352x288_30fps) {
|
||||
RunTest(352, 288, 30, "foreman_cif");
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
188
modules/video_coding/codecs/test/videoprocessor_unittest.cc
Normal file
188
modules/video_coding/codecs/test/videoprocessor_unittest.cc
Normal file
@ -0,0 +1,188 @@
|
||||
/*
|
||||
* Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "webrtc/api/video/i420_buffer.h"
|
||||
#include "webrtc/common_types.h"
|
||||
#include "webrtc/modules/video_coding/codecs/test/mock/mock_packet_manipulator.h"
|
||||
#include "webrtc/modules/video_coding/codecs/test/videoprocessor.h"
|
||||
#include "webrtc/modules/video_coding/include/mock/mock_video_codec_interface.h"
|
||||
#include "webrtc/modules/video_coding/include/video_coding.h"
|
||||
#include "webrtc/rtc_base/ptr_util.h"
|
||||
#include "webrtc/test/gmock.h"
|
||||
#include "webrtc/test/gtest.h"
|
||||
#include "webrtc/test/testsupport/mock/mock_frame_reader.h"
|
||||
#include "webrtc/test/testsupport/mock/mock_frame_writer.h"
|
||||
#include "webrtc/test/testsupport/packet_reader.h"
|
||||
#include "webrtc/test/testsupport/unittest_utils.h"
|
||||
#include "webrtc/test/video_codec_settings.h"
|
||||
#include "webrtc/typedefs.h"
|
||||
|
||||
using ::testing::_;
|
||||
using ::testing::AtLeast;
|
||||
using ::testing::ElementsAre;
|
||||
using ::testing::Property;
|
||||
using ::testing::Return;
|
||||
|
||||
namespace webrtc {
|
||||
namespace test {
|
||||
|
||||
namespace {
|
||||
|
||||
const int kWidth = 352;
|
||||
const int kHeight = 288;
|
||||
const int kFrameSize = kWidth * kHeight * 3 / 2; // I420.
|
||||
const int kNumFrames = 2;
|
||||
|
||||
} // namespace
|
||||
|
||||
class VideoProcessorTest : public testing::Test {
|
||||
protected:
|
||||
VideoProcessorTest() {
|
||||
// Get a codec configuration struct and configure it.
|
||||
webrtc::test::CodecSettings(kVideoCodecVP8, &config_.codec_settings);
|
||||
config_.codec_settings.width = kWidth;
|
||||
config_.codec_settings.height = kHeight;
|
||||
|
||||
EXPECT_CALL(frame_reader_mock_, NumberOfFrames())
|
||||
.WillRepeatedly(Return(kNumFrames));
|
||||
EXPECT_CALL(frame_reader_mock_, FrameLength())
|
||||
.WillRepeatedly(Return(kFrameSize));
|
||||
video_processor_ = rtc::MakeUnique<VideoProcessor>(
|
||||
&encoder_mock_, &decoder_mock_, &frame_reader_mock_,
|
||||
&frame_writer_mock_, &packet_manipulator_mock_, config_, &stats_,
|
||||
nullptr /* encoded_frame_writer */, nullptr /* decoded_frame_writer */);
|
||||
}
|
||||
|
||||
void ExpectInit() {
|
||||
EXPECT_CALL(encoder_mock_, InitEncode(_, _, _)).Times(1);
|
||||
EXPECT_CALL(encoder_mock_, RegisterEncodeCompleteCallback(_)).Times(1);
|
||||
EXPECT_CALL(decoder_mock_, InitDecode(_, _)).Times(1);
|
||||
EXPECT_CALL(decoder_mock_, RegisterDecodeCompleteCallback(_)).Times(1);
|
||||
}
|
||||
|
||||
void ExpectRelease() {
|
||||
EXPECT_CALL(encoder_mock_, Release()).Times(1);
|
||||
EXPECT_CALL(encoder_mock_, RegisterEncodeCompleteCallback(_)).Times(1);
|
||||
EXPECT_CALL(decoder_mock_, Release()).Times(1);
|
||||
EXPECT_CALL(decoder_mock_, RegisterDecodeCompleteCallback(_)).Times(1);
|
||||
}
|
||||
|
||||
TestConfig config_;
|
||||
|
||||
MockVideoEncoder encoder_mock_;
|
||||
MockVideoDecoder decoder_mock_;
|
||||
MockFrameReader frame_reader_mock_;
|
||||
MockFrameWriter frame_writer_mock_;
|
||||
MockPacketManipulator packet_manipulator_mock_;
|
||||
Stats stats_;
|
||||
std::unique_ptr<VideoProcessor> video_processor_;
|
||||
};
|
||||
|
||||
TEST_F(VideoProcessorTest, InitRelease) {
|
||||
ExpectInit();
|
||||
video_processor_->Init();
|
||||
|
||||
ExpectRelease();
|
||||
video_processor_->Release();
|
||||
}
|
||||
|
||||
TEST_F(VideoProcessorTest, ProcessFrames_FixedFramerate) {
|
||||
ExpectInit();
|
||||
video_processor_->Init();
|
||||
|
||||
const int kBitrateKbps = 456;
|
||||
const int kFramerateFps = 31;
|
||||
video_processor_->SetRates(kBitrateKbps, kFramerateFps);
|
||||
|
||||
EXPECT_CALL(frame_reader_mock_, ReadFrame())
|
||||
.WillRepeatedly(Return(I420Buffer::Create(kWidth, kHeight)));
|
||||
EXPECT_CALL(
|
||||
encoder_mock_,
|
||||
Encode(Property(&VideoFrame::timestamp, 1 * 90000 / kFramerateFps), _, _))
|
||||
.Times(1);
|
||||
video_processor_->ProcessFrame();
|
||||
|
||||
EXPECT_CALL(
|
||||
encoder_mock_,
|
||||
Encode(Property(&VideoFrame::timestamp, 2 * 90000 / kFramerateFps), _, _))
|
||||
.Times(1);
|
||||
video_processor_->ProcessFrame();
|
||||
|
||||
ExpectRelease();
|
||||
video_processor_->Release();
|
||||
}
|
||||
|
||||
TEST_F(VideoProcessorTest, ProcessFrames_VariableFramerate) {
|
||||
ExpectInit();
|
||||
video_processor_->Init();
|
||||
|
||||
const int kBitrateKbps = 456;
|
||||
const int kStartFramerateFps = 27;
|
||||
video_processor_->SetRates(kBitrateKbps, kStartFramerateFps);
|
||||
|
||||
EXPECT_CALL(frame_reader_mock_, ReadFrame())
|
||||
.WillRepeatedly(Return(I420Buffer::Create(kWidth, kHeight)));
|
||||
EXPECT_CALL(encoder_mock_, Encode(Property(&VideoFrame::timestamp,
|
||||
1 * 90000 / kStartFramerateFps),
|
||||
_, _))
|
||||
.Times(1);
|
||||
video_processor_->ProcessFrame();
|
||||
|
||||
const int kNewFramerateFps = 13;
|
||||
video_processor_->SetRates(kBitrateKbps, kNewFramerateFps);
|
||||
|
||||
EXPECT_CALL(encoder_mock_, Encode(Property(&VideoFrame::timestamp,
|
||||
2 * 90000 / kNewFramerateFps),
|
||||
_, _))
|
||||
.Times(1);
|
||||
video_processor_->ProcessFrame();
|
||||
|
||||
ExpectRelease();
|
||||
video_processor_->Release();
|
||||
}
|
||||
|
||||
TEST_F(VideoProcessorTest, SetRates) {
|
||||
ExpectInit();
|
||||
video_processor_->Init();
|
||||
|
||||
const int kBitrateKbps = 123;
|
||||
const int kFramerateFps = 17;
|
||||
EXPECT_CALL(encoder_mock_,
|
||||
SetRateAllocation(
|
||||
Property(&BitrateAllocation::get_sum_kbps, kBitrateKbps),
|
||||
kFramerateFps))
|
||||
.Times(1);
|
||||
video_processor_->SetRates(kBitrateKbps, kFramerateFps);
|
||||
EXPECT_THAT(video_processor_->NumberDroppedFramesPerRateUpdate(),
|
||||
ElementsAre(0));
|
||||
EXPECT_THAT(video_processor_->NumberSpatialResizesPerRateUpdate(),
|
||||
ElementsAre(0));
|
||||
|
||||
const int kNewBitrateKbps = 456;
|
||||
const int kNewFramerateFps = 34;
|
||||
EXPECT_CALL(encoder_mock_,
|
||||
SetRateAllocation(
|
||||
Property(&BitrateAllocation::get_sum_kbps, kNewBitrateKbps),
|
||||
kNewFramerateFps))
|
||||
.Times(1);
|
||||
video_processor_->SetRates(kNewBitrateKbps, kNewFramerateFps);
|
||||
EXPECT_THAT(video_processor_->NumberDroppedFramesPerRateUpdate(),
|
||||
ElementsAre(0, 0));
|
||||
EXPECT_THAT(video_processor_->NumberSpatialResizesPerRateUpdate(),
|
||||
ElementsAre(0, 0));
|
||||
|
||||
ExpectRelease();
|
||||
video_processor_->Release();
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
431
modules/video_coding/codecs/vp8/default_temporal_layers.cc
Normal file
431
modules/video_coding/codecs/vp8/default_temporal_layers.cc
Normal file
@ -0,0 +1,431 @@
|
||||
/* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "webrtc/modules/video_coding/codecs/vp8/default_temporal_layers.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/modules/include/module_common_types.h"
|
||||
#include "webrtc/modules/video_coding/codecs/vp8/include/vp8_common_types.h"
|
||||
#include "webrtc/modules/video_coding/include/video_codec_interface.h"
|
||||
#include "webrtc/rtc_base/checks.h"
|
||||
#include "webrtc/system_wrappers/include/field_trial.h"
|
||||
|
||||
#include "vpx/vpx_encoder.h"
|
||||
#include "vpx/vp8cx.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
TemporalLayers::FrameConfig::FrameConfig()
|
||||
: FrameConfig(kNone, kNone, kNone, false) {}
|
||||
|
||||
TemporalLayers::FrameConfig::FrameConfig(TemporalLayers::BufferFlags last,
|
||||
TemporalLayers::BufferFlags golden,
|
||||
TemporalLayers::BufferFlags arf)
|
||||
: FrameConfig(last, golden, arf, false) {}
|
||||
|
||||
TemporalLayers::FrameConfig::FrameConfig(TemporalLayers::BufferFlags last,
|
||||
TemporalLayers::BufferFlags golden,
|
||||
TemporalLayers::BufferFlags arf,
|
||||
FreezeEntropy)
|
||||
: FrameConfig(last, golden, arf, true) {}
|
||||
|
||||
TemporalLayers::FrameConfig::FrameConfig(TemporalLayers::BufferFlags last,
|
||||
TemporalLayers::BufferFlags golden,
|
||||
TemporalLayers::BufferFlags arf,
|
||||
bool freeze_entropy)
|
||||
: drop_frame(last == TemporalLayers::kNone &&
|
||||
golden == TemporalLayers::kNone &&
|
||||
arf == TemporalLayers::kNone),
|
||||
last_buffer_flags(last),
|
||||
golden_buffer_flags(golden),
|
||||
arf_buffer_flags(arf),
|
||||
encoder_layer_id(0),
|
||||
packetizer_temporal_idx(kNoTemporalIdx),
|
||||
layer_sync(false),
|
||||
freeze_entropy(freeze_entropy) {}
|
||||
|
||||
namespace {
|
||||
|
||||
std::vector<unsigned int> GetTemporalIds(size_t num_layers) {
|
||||
switch (num_layers) {
|
||||
case 1:
|
||||
// Temporal layer structure (single layer):
|
||||
// 0 0 0 0 ...
|
||||
return {0};
|
||||
case 2:
|
||||
// Temporal layer structure:
|
||||
// 1 1 ...
|
||||
// 0 0 ...
|
||||
return {0, 1};
|
||||
case 3:
|
||||
// Temporal layer structure:
|
||||
// 2 2 2 2 ...
|
||||
// 1 1 ...
|
||||
// 0 0 ...
|
||||
return {0, 2, 1, 2};
|
||||
case 4:
|
||||
// Temporal layer structure:
|
||||
// 3 3 3 3 3 3 3 3 ...
|
||||
// 2 2 2 2 ...
|
||||
// 1 1 ...
|
||||
// 0 0 ...
|
||||
return {0, 3, 2, 3, 1, 3, 2, 3};
|
||||
default:
|
||||
RTC_NOTREACHED();
|
||||
break;
|
||||
}
|
||||
RTC_NOTREACHED();
|
||||
return {0};
|
||||
}
|
||||
|
||||
std::vector<bool> GetTemporalLayerSync(size_t num_layers) {
|
||||
switch (num_layers) {
|
||||
case 1:
|
||||
return {false};
|
||||
case 2:
|
||||
return {false, true, false, false, false, false, false, false};
|
||||
case 3:
|
||||
if (field_trial::IsEnabled("WebRTC-UseShortVP8TL3Pattern")) {
|
||||
return {false, true, true, false};
|
||||
} else {
|
||||
return {false, true, true, false, false, false, false, false};
|
||||
}
|
||||
case 4:
|
||||
return {false, true, true, false, true, false, false, false,
|
||||
false, false, false, false, false, false, false, false};
|
||||
default:
|
||||
break;
|
||||
}
|
||||
RTC_NOTREACHED() << num_layers;
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<TemporalLayers::FrameConfig> GetTemporalPattern(size_t num_layers) {
|
||||
// For indexing in the patterns described below (which temporal layers they
|
||||
// belong to), see the diagram above.
|
||||
// Layer sync is done similarly for all patterns (except single stream) and
|
||||
// happens every 8 frames:
|
||||
// TL1 layer syncs by periodically by only referencing TL0 ('last'), but still
|
||||
// updating 'golden', so it can be used as a reference by future TL1 frames.
|
||||
// TL2 layer syncs just before TL1 by only depending on TL0 (and not depending
|
||||
// on TL1's buffer before TL1 has layer synced).
|
||||
// TODO(pbos): Consider cyclically updating 'arf' (and 'golden' for 1TL) for
|
||||
// the base layer in 1-3TL instead of 'last' periodically on long intervals,
|
||||
// so that if scene changes occur (user walks between rooms or rotates webcam)
|
||||
// the 'arf' (or 'golden' respectively) is not stuck on a no-longer relevant
|
||||
// keyframe.
|
||||
switch (num_layers) {
|
||||
case 1:
|
||||
// All frames reference all buffers and the 'last' buffer is updated.
|
||||
return {TemporalLayers::FrameConfig(TemporalLayers::kReferenceAndUpdate,
|
||||
TemporalLayers::kReference,
|
||||
TemporalLayers::kReference)};
|
||||
case 2:
|
||||
// All layers can reference but not update the 'alt' buffer, this means
|
||||
// that the 'alt' buffer reference is effectively the last keyframe.
|
||||
// TL0 also references and updates the 'last' buffer.
|
||||
// TL1 also references 'last' and references and updates 'golden'.
|
||||
return {TemporalLayers::FrameConfig(TemporalLayers::kReferenceAndUpdate,
|
||||
TemporalLayers::kNone,
|
||||
TemporalLayers::kReference),
|
||||
TemporalLayers::FrameConfig(TemporalLayers::kReference,
|
||||
TemporalLayers::kUpdate,
|
||||
TemporalLayers::kReference),
|
||||
TemporalLayers::FrameConfig(TemporalLayers::kReferenceAndUpdate,
|
||||
TemporalLayers::kNone,
|
||||
TemporalLayers::kReference),
|
||||
TemporalLayers::FrameConfig(TemporalLayers::kReference,
|
||||
TemporalLayers::kReferenceAndUpdate,
|
||||
TemporalLayers::kReference),
|
||||
TemporalLayers::FrameConfig(TemporalLayers::kReferenceAndUpdate,
|
||||
TemporalLayers::kNone,
|
||||
TemporalLayers::kReference),
|
||||
TemporalLayers::FrameConfig(TemporalLayers::kReference,
|
||||
TemporalLayers::kReferenceAndUpdate,
|
||||
TemporalLayers::kReference),
|
||||
TemporalLayers::FrameConfig(TemporalLayers::kReferenceAndUpdate,
|
||||
TemporalLayers::kNone,
|
||||
TemporalLayers::kReference),
|
||||
TemporalLayers::FrameConfig(
|
||||
TemporalLayers::kReference, TemporalLayers::kReference,
|
||||
TemporalLayers::kReference, TemporalLayers::kFreezeEntropy)};
|
||||
case 3:
|
||||
if (field_trial::IsEnabled("WebRTC-UseShortVP8TL3Pattern")) {
|
||||
// This field trial is intended to check if it is worth using a shorter
|
||||
// temporal pattern, trading some coding efficiency for less risk of
|
||||
// dropped frames.
|
||||
// The coding efficiency will decrease somewhat since the higher layer
|
||||
// state is more volatile, but it will be offset slightly by updating
|
||||
// the altref buffer with TL2 frames, instead of just referencing lower
|
||||
// layers.
|
||||
// If a frame is dropped in a higher layer, the jitter
|
||||
// buffer on the receive side won't be able to decode any higher layer
|
||||
// frame until the next sync frame. So we expect a noticeable decrease
|
||||
// in frame drops on links with high packet loss.
|
||||
|
||||
// TL0 references and updates the 'last' buffer.
|
||||
// TL1 references 'last' and references and updates 'golden'.
|
||||
// TL2 references both 'last' & 'golden' and references and updates
|
||||
// 'arf'.
|
||||
return {TemporalLayers::FrameConfig(TemporalLayers::kReferenceAndUpdate,
|
||||
TemporalLayers::kNone,
|
||||
TemporalLayers::kNone),
|
||||
TemporalLayers::FrameConfig(TemporalLayers::kReference,
|
||||
TemporalLayers::kNone,
|
||||
TemporalLayers::kUpdate),
|
||||
TemporalLayers::FrameConfig(TemporalLayers::kReference,
|
||||
TemporalLayers::kUpdate,
|
||||
TemporalLayers::kNone),
|
||||
TemporalLayers::FrameConfig(TemporalLayers::kReference,
|
||||
TemporalLayers::kReference,
|
||||
TemporalLayers::kReference,
|
||||
TemporalLayers::kFreezeEntropy)};
|
||||
} else {
|
||||
// All layers can reference but not update the 'alt' buffer, this means
|
||||
// that the 'alt' buffer reference is effectively the last keyframe.
|
||||
// TL0 also references and updates the 'last' buffer.
|
||||
// TL1 also references 'last' and references and updates 'golden'.
|
||||
// TL2 references both 'last' and 'golden' but updates no buffer.
|
||||
return {TemporalLayers::FrameConfig(TemporalLayers::kReferenceAndUpdate,
|
||||
TemporalLayers::kNone,
|
||||
TemporalLayers::kReference),
|
||||
TemporalLayers::FrameConfig(
|
||||
TemporalLayers::kReference, TemporalLayers::kNone,
|
||||
TemporalLayers::kReference, TemporalLayers::kFreezeEntropy),
|
||||
TemporalLayers::FrameConfig(TemporalLayers::kReference,
|
||||
TemporalLayers::kUpdate,
|
||||
TemporalLayers::kReference),
|
||||
TemporalLayers::FrameConfig(
|
||||
TemporalLayers::kReference, TemporalLayers::kReference,
|
||||
TemporalLayers::kReference, TemporalLayers::kFreezeEntropy),
|
||||
TemporalLayers::FrameConfig(TemporalLayers::kReferenceAndUpdate,
|
||||
TemporalLayers::kNone,
|
||||
TemporalLayers::kReference),
|
||||
TemporalLayers::FrameConfig(
|
||||
TemporalLayers::kReference, TemporalLayers::kReference,
|
||||
TemporalLayers::kReference, TemporalLayers::kFreezeEntropy),
|
||||
TemporalLayers::FrameConfig(TemporalLayers::kReference,
|
||||
TemporalLayers::kReferenceAndUpdate,
|
||||
TemporalLayers::kReference),
|
||||
TemporalLayers::FrameConfig(TemporalLayers::kReference,
|
||||
TemporalLayers::kReference,
|
||||
TemporalLayers::kReference,
|
||||
TemporalLayers::kFreezeEntropy)};
|
||||
}
|
||||
case 4:
|
||||
// TL0 references and updates only the 'last' buffer.
|
||||
// TL1 references 'last' and updates and references 'golden'.
|
||||
// TL2 references 'last' and 'golden', and references and updates 'arf'.
|
||||
// TL3 references all buffers but update none of them.
|
||||
return {TemporalLayers::FrameConfig(TemporalLayers::kReferenceAndUpdate,
|
||||
TemporalLayers::kNone,
|
||||
TemporalLayers::kNone),
|
||||
TemporalLayers::FrameConfig(
|
||||
TemporalLayers::kReference, TemporalLayers::kNone,
|
||||
TemporalLayers::kNone, TemporalLayers::kFreezeEntropy),
|
||||
TemporalLayers::FrameConfig(TemporalLayers::kReference,
|
||||
TemporalLayers::kNone,
|
||||
TemporalLayers::kUpdate),
|
||||
TemporalLayers::FrameConfig(
|
||||
TemporalLayers::kReference, TemporalLayers::kNone,
|
||||
TemporalLayers::kReference, TemporalLayers::kFreezeEntropy),
|
||||
TemporalLayers::FrameConfig(TemporalLayers::kReference,
|
||||
TemporalLayers::kUpdate,
|
||||
TemporalLayers::kNone),
|
||||
TemporalLayers::FrameConfig(
|
||||
TemporalLayers::kReference, TemporalLayers::kReference,
|
||||
TemporalLayers::kReference, TemporalLayers::kFreezeEntropy),
|
||||
TemporalLayers::FrameConfig(TemporalLayers::kReference,
|
||||
TemporalLayers::kReference,
|
||||
TemporalLayers::kReferenceAndUpdate),
|
||||
TemporalLayers::FrameConfig(
|
||||
TemporalLayers::kReference, TemporalLayers::kReference,
|
||||
TemporalLayers::kReference, TemporalLayers::kFreezeEntropy),
|
||||
TemporalLayers::FrameConfig(TemporalLayers::kReferenceAndUpdate,
|
||||
TemporalLayers::kNone,
|
||||
TemporalLayers::kNone),
|
||||
TemporalLayers::FrameConfig(
|
||||
TemporalLayers::kReference, TemporalLayers::kReference,
|
||||
TemporalLayers::kReference, TemporalLayers::kFreezeEntropy),
|
||||
TemporalLayers::FrameConfig(TemporalLayers::kReference,
|
||||
TemporalLayers::kReference,
|
||||
TemporalLayers::kReferenceAndUpdate),
|
||||
TemporalLayers::FrameConfig(
|
||||
TemporalLayers::kReference, TemporalLayers::kReference,
|
||||
TemporalLayers::kReference, TemporalLayers::kFreezeEntropy),
|
||||
TemporalLayers::FrameConfig(TemporalLayers::kReference,
|
||||
TemporalLayers::kReferenceAndUpdate,
|
||||
TemporalLayers::kNone),
|
||||
TemporalLayers::FrameConfig(
|
||||
TemporalLayers::kReference, TemporalLayers::kReference,
|
||||
TemporalLayers::kReference, TemporalLayers::kFreezeEntropy),
|
||||
TemporalLayers::FrameConfig(TemporalLayers::kReference,
|
||||
TemporalLayers::kReference,
|
||||
TemporalLayers::kReferenceAndUpdate),
|
||||
TemporalLayers::FrameConfig(
|
||||
TemporalLayers::kReference, TemporalLayers::kReference,
|
||||
TemporalLayers::kReference, TemporalLayers::kFreezeEntropy)};
|
||||
default:
|
||||
RTC_NOTREACHED();
|
||||
break;
|
||||
}
|
||||
RTC_NOTREACHED();
|
||||
return {TemporalLayers::FrameConfig(
|
||||
TemporalLayers::kNone, TemporalLayers::kNone, TemporalLayers::kNone)};
|
||||
}
|
||||
|
||||
// Temporary fix for forced SW fallback.
|
||||
// For VP8 SW codec, |TemporalLayers| is created and reported to
|
||||
// SimulcastRateAllocator::OnTemporalLayersCreated but not for VP8 HW.
|
||||
// Causes an issue when going from forced SW -> HW as |TemporalLayers| is not
|
||||
// deregistred when deleted by SW codec (tl factory might not exist, owned by
|
||||
// SimulcastRateAllocator).
|
||||
bool ExcludeOnTemporalLayersCreated(int num_temporal_layers) {
|
||||
return webrtc::field_trial::IsEnabled("WebRTC-VP8-Forced-Fallback-Encoder") &&
|
||||
num_temporal_layers == 1;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
DefaultTemporalLayers::DefaultTemporalLayers(int number_of_temporal_layers,
|
||||
uint8_t initial_tl0_pic_idx)
|
||||
: num_layers_(std::max(1, number_of_temporal_layers)),
|
||||
temporal_ids_(GetTemporalIds(num_layers_)),
|
||||
temporal_layer_sync_(GetTemporalLayerSync(num_layers_)),
|
||||
temporal_pattern_(GetTemporalPattern(num_layers_)),
|
||||
tl0_pic_idx_(initial_tl0_pic_idx),
|
||||
pattern_idx_(255),
|
||||
last_base_layer_sync_(false) {
|
||||
RTC_DCHECK_EQ(temporal_pattern_.size(), temporal_layer_sync_.size());
|
||||
RTC_CHECK_GE(kMaxTemporalStreams, number_of_temporal_layers);
|
||||
RTC_CHECK_GE(number_of_temporal_layers, 0);
|
||||
RTC_CHECK_LE(number_of_temporal_layers, 4);
|
||||
// pattern_idx_ wraps around temporal_pattern_.size, this is incorrect if
|
||||
// temporal_ids_ are ever longer. If this is no longer correct it needs to
|
||||
// wrap at max(temporal_ids_.size(), temporal_pattern_.size()).
|
||||
RTC_DCHECK_LE(temporal_ids_.size(), temporal_pattern_.size());
|
||||
}
|
||||
|
||||
uint8_t DefaultTemporalLayers::Tl0PicIdx() const {
|
||||
return tl0_pic_idx_;
|
||||
}
|
||||
|
||||
std::vector<uint32_t> DefaultTemporalLayers::OnRatesUpdated(
|
||||
int bitrate_kbps,
|
||||
int max_bitrate_kbps,
|
||||
int framerate) {
|
||||
std::vector<uint32_t> bitrates;
|
||||
for (size_t i = 0; i < num_layers_; ++i) {
|
||||
float layer_bitrate =
|
||||
bitrate_kbps * kVp8LayerRateAlloction[num_layers_ - 1][i];
|
||||
bitrates.push_back(static_cast<uint32_t>(layer_bitrate + 0.5));
|
||||
}
|
||||
new_bitrates_kbps_ = rtc::Optional<std::vector<uint32_t>>(bitrates);
|
||||
|
||||
// Allocation table is of aggregates, transform to individual rates.
|
||||
uint32_t sum = 0;
|
||||
for (size_t i = 0; i < num_layers_; ++i) {
|
||||
uint32_t layer_bitrate = bitrates[i];
|
||||
RTC_DCHECK_LE(sum, bitrates[i]);
|
||||
bitrates[i] -= sum;
|
||||
sum = layer_bitrate;
|
||||
|
||||
if (sum >= static_cast<uint32_t>(bitrate_kbps)) {
|
||||
// Sum adds up; any subsequent layers will be 0.
|
||||
bitrates.resize(i + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return bitrates;
|
||||
}
|
||||
|
||||
bool DefaultTemporalLayers::UpdateConfiguration(vpx_codec_enc_cfg_t* cfg) {
|
||||
if (!new_bitrates_kbps_)
|
||||
return false;
|
||||
|
||||
for (size_t i = 0; i < num_layers_; ++i) {
|
||||
cfg->ts_target_bitrate[i] = (*new_bitrates_kbps_)[i];
|
||||
// ..., 4, 2, 1
|
||||
cfg->ts_rate_decimator[i] = 1 << (num_layers_ - i - 1);
|
||||
}
|
||||
|
||||
cfg->ts_number_layers = num_layers_;
|
||||
cfg->ts_periodicity = temporal_ids_.size();
|
||||
memcpy(cfg->ts_layer_id, &temporal_ids_[0],
|
||||
sizeof(unsigned int) * temporal_ids_.size());
|
||||
|
||||
new_bitrates_kbps_ = rtc::Optional<std::vector<uint32_t>>();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
TemporalLayers::FrameConfig DefaultTemporalLayers::UpdateLayerConfig(
|
||||
uint32_t timestamp) {
|
||||
RTC_DCHECK_GT(num_layers_, 0);
|
||||
RTC_DCHECK_LT(0, temporal_pattern_.size());
|
||||
pattern_idx_ = (pattern_idx_ + 1) % temporal_pattern_.size();
|
||||
TemporalLayers::FrameConfig tl_config = temporal_pattern_[pattern_idx_];
|
||||
tl_config.layer_sync =
|
||||
temporal_layer_sync_[pattern_idx_ % temporal_layer_sync_.size()];
|
||||
tl_config.encoder_layer_id = tl_config.packetizer_temporal_idx =
|
||||
temporal_ids_[pattern_idx_ % temporal_ids_.size()];
|
||||
return tl_config;
|
||||
}
|
||||
|
||||
void DefaultTemporalLayers::PopulateCodecSpecific(
|
||||
bool frame_is_keyframe,
|
||||
const TemporalLayers::FrameConfig& tl_config,
|
||||
CodecSpecificInfoVP8* vp8_info,
|
||||
uint32_t timestamp) {
|
||||
RTC_DCHECK_GT(num_layers_, 0);
|
||||
|
||||
if (num_layers_ == 1) {
|
||||
vp8_info->temporalIdx = kNoTemporalIdx;
|
||||
vp8_info->layerSync = false;
|
||||
vp8_info->tl0PicIdx = kNoTl0PicIdx;
|
||||
} else {
|
||||
vp8_info->temporalIdx = tl_config.packetizer_temporal_idx;
|
||||
vp8_info->layerSync = tl_config.layer_sync;
|
||||
if (frame_is_keyframe) {
|
||||
vp8_info->temporalIdx = 0;
|
||||
vp8_info->layerSync = true;
|
||||
}
|
||||
if (last_base_layer_sync_ && vp8_info->temporalIdx != 0) {
|
||||
// Regardless of pattern the frame after a base layer sync will always
|
||||
// be a layer sync.
|
||||
vp8_info->layerSync = true;
|
||||
}
|
||||
if (vp8_info->temporalIdx == 0)
|
||||
tl0_pic_idx_++;
|
||||
last_base_layer_sync_ = frame_is_keyframe;
|
||||
vp8_info->tl0PicIdx = tl0_pic_idx_;
|
||||
}
|
||||
}
|
||||
|
||||
TemporalLayers* TemporalLayersFactory::Create(
|
||||
int simulcast_id,
|
||||
int temporal_layers,
|
||||
uint8_t initial_tl0_pic_idx) const {
|
||||
TemporalLayers* tl =
|
||||
new DefaultTemporalLayers(temporal_layers, initial_tl0_pic_idx);
|
||||
if (listener_ && !ExcludeOnTemporalLayersCreated(temporal_layers))
|
||||
listener_->OnTemporalLayersCreated(simulcast_id, tl);
|
||||
return tl;
|
||||
}
|
||||
|
||||
void TemporalLayersFactory::SetListener(TemporalLayersListener* listener) {
|
||||
listener_ = listener;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
63
modules/video_coding/codecs/vp8/default_temporal_layers.h
Normal file
63
modules/video_coding/codecs/vp8/default_temporal_layers.h
Normal file
@ -0,0 +1,63 @@
|
||||
/* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
/*
|
||||
* This file defines classes for doing temporal layers with VP8.
|
||||
*/
|
||||
#ifndef WEBRTC_MODULES_VIDEO_CODING_CODECS_VP8_DEFAULT_TEMPORAL_LAYERS_H_
|
||||
#define WEBRTC_MODULES_VIDEO_CODING_CODECS_VP8_DEFAULT_TEMPORAL_LAYERS_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/modules/video_coding/codecs/vp8/temporal_layers.h"
|
||||
|
||||
#include "webrtc/api/optional.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class DefaultTemporalLayers : public TemporalLayers {
|
||||
public:
|
||||
DefaultTemporalLayers(int number_of_temporal_layers,
|
||||
uint8_t initial_tl0_pic_idx);
|
||||
virtual ~DefaultTemporalLayers() {}
|
||||
|
||||
// Returns the recommended VP8 encode flags needed. May refresh the decoder
|
||||
// and/or update the reference buffers.
|
||||
TemporalLayers::FrameConfig UpdateLayerConfig(uint32_t timestamp) override;
|
||||
|
||||
// Update state based on new bitrate target and incoming framerate.
|
||||
// Returns the bitrate allocation for the active temporal layers.
|
||||
std::vector<uint32_t> OnRatesUpdated(int bitrate_kbps,
|
||||
int max_bitrate_kbps,
|
||||
int framerate) override;
|
||||
|
||||
bool UpdateConfiguration(vpx_codec_enc_cfg_t* cfg) override;
|
||||
|
||||
void PopulateCodecSpecific(bool frame_is_keyframe,
|
||||
const TemporalLayers::FrameConfig& tl_config,
|
||||
CodecSpecificInfoVP8* vp8_info,
|
||||
uint32_t timestamp) override;
|
||||
|
||||
void FrameEncoded(unsigned int size, int qp) override {}
|
||||
|
||||
uint8_t Tl0PicIdx() const override;
|
||||
|
||||
private:
|
||||
const size_t num_layers_;
|
||||
const std::vector<unsigned int> temporal_ids_;
|
||||
const std::vector<bool> temporal_layer_sync_;
|
||||
const std::vector<TemporalLayers::FrameConfig> temporal_pattern_;
|
||||
|
||||
uint8_t tl0_pic_idx_;
|
||||
uint8_t pattern_idx_;
|
||||
bool last_base_layer_sync_;
|
||||
rtc::Optional<std::vector<uint32_t>> new_bitrates_kbps_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
#endif // WEBRTC_MODULES_VIDEO_CODING_CODECS_VP8_DEFAULT_TEMPORAL_LAYERS_H_
|
||||
@ -0,0 +1,398 @@
|
||||
/*
|
||||
* Copyright (c) 2011 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "webrtc/modules/video_coding/codecs/vp8/default_temporal_layers.h"
|
||||
#include "vpx/vp8cx.h"
|
||||
#include "vpx/vpx_encoder.h"
|
||||
#include "webrtc/modules/video_coding/codecs/vp8/vp8_impl.h"
|
||||
#include "webrtc/modules/video_coding/include/video_codec_interface.h"
|
||||
#include "webrtc/test/field_trial.h"
|
||||
#include "webrtc/test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace test {
|
||||
|
||||
enum {
|
||||
kTemporalUpdateLast = VP8_EFLAG_NO_UPD_GF | VP8_EFLAG_NO_UPD_ARF |
|
||||
VP8_EFLAG_NO_REF_GF | VP8_EFLAG_NO_REF_ARF,
|
||||
kTemporalUpdateGoldenWithoutDependency =
|
||||
VP8_EFLAG_NO_REF_GF | VP8_EFLAG_NO_REF_ARF | VP8_EFLAG_NO_UPD_ARF |
|
||||
VP8_EFLAG_NO_UPD_LAST,
|
||||
kTemporalUpdateGolden =
|
||||
VP8_EFLAG_NO_REF_ARF | VP8_EFLAG_NO_UPD_ARF | VP8_EFLAG_NO_UPD_LAST,
|
||||
kTemporalUpdateAltrefWithoutDependency =
|
||||
VP8_EFLAG_NO_REF_ARF | VP8_EFLAG_NO_REF_GF | VP8_EFLAG_NO_UPD_GF |
|
||||
VP8_EFLAG_NO_UPD_LAST,
|
||||
kTemporalUpdateAltref = VP8_EFLAG_NO_UPD_GF | VP8_EFLAG_NO_UPD_LAST,
|
||||
kTemporalUpdateNone = VP8_EFLAG_NO_UPD_GF | VP8_EFLAG_NO_UPD_ARF |
|
||||
VP8_EFLAG_NO_UPD_LAST | VP8_EFLAG_NO_UPD_ENTROPY,
|
||||
kTemporalUpdateNoneNoRefAltRef =
|
||||
VP8_EFLAG_NO_REF_ARF | VP8_EFLAG_NO_UPD_GF | VP8_EFLAG_NO_UPD_ARF |
|
||||
VP8_EFLAG_NO_UPD_LAST | VP8_EFLAG_NO_UPD_ENTROPY,
|
||||
kTemporalUpdateNoneNoRefGolden =
|
||||
VP8_EFLAG_NO_REF_GF | VP8_EFLAG_NO_UPD_GF | VP8_EFLAG_NO_UPD_ARF |
|
||||
VP8_EFLAG_NO_UPD_LAST | VP8_EFLAG_NO_UPD_ENTROPY,
|
||||
kTemporalUpdateNoneNoRefGoldenAltRef =
|
||||
VP8_EFLAG_NO_REF_GF | VP8_EFLAG_NO_UPD_GF | VP8_EFLAG_NO_REF_ARF |
|
||||
VP8_EFLAG_NO_UPD_ARF | VP8_EFLAG_NO_UPD_LAST | VP8_EFLAG_NO_UPD_ENTROPY,
|
||||
kTemporalUpdateGoldenWithoutDependencyRefAltRef =
|
||||
VP8_EFLAG_NO_REF_GF | VP8_EFLAG_NO_UPD_ARF | VP8_EFLAG_NO_UPD_LAST,
|
||||
kTemporalUpdateGoldenRefAltRef = VP8_EFLAG_NO_UPD_ARF | VP8_EFLAG_NO_UPD_LAST,
|
||||
kTemporalUpdateLastRefAltRef =
|
||||
VP8_EFLAG_NO_UPD_GF | VP8_EFLAG_NO_UPD_ARF | VP8_EFLAG_NO_REF_GF,
|
||||
kTemporalUpdateLastAndGoldenRefAltRef =
|
||||
VP8_EFLAG_NO_UPD_ARF | VP8_EFLAG_NO_REF_GF,
|
||||
};
|
||||
|
||||
TEST(TemporalLayersTest, 2Layers) {
|
||||
DefaultTemporalLayers tl(2, 0);
|
||||
vpx_codec_enc_cfg_t cfg;
|
||||
CodecSpecificInfoVP8 vp8_info;
|
||||
tl.OnRatesUpdated(500, 500, 30);
|
||||
tl.UpdateConfiguration(&cfg);
|
||||
|
||||
int expected_flags[16] = {
|
||||
kTemporalUpdateLastRefAltRef,
|
||||
kTemporalUpdateGoldenWithoutDependencyRefAltRef,
|
||||
kTemporalUpdateLastRefAltRef,
|
||||
kTemporalUpdateGoldenRefAltRef,
|
||||
kTemporalUpdateLastRefAltRef,
|
||||
kTemporalUpdateGoldenRefAltRef,
|
||||
kTemporalUpdateLastRefAltRef,
|
||||
kTemporalUpdateNone,
|
||||
kTemporalUpdateLastRefAltRef,
|
||||
kTemporalUpdateGoldenWithoutDependencyRefAltRef,
|
||||
kTemporalUpdateLastRefAltRef,
|
||||
kTemporalUpdateGoldenRefAltRef,
|
||||
kTemporalUpdateLastRefAltRef,
|
||||
kTemporalUpdateGoldenRefAltRef,
|
||||
kTemporalUpdateLastRefAltRef,
|
||||
kTemporalUpdateNone,
|
||||
};
|
||||
int expected_temporal_idx[16] = {0, 1, 0, 1, 0, 1, 0, 1,
|
||||
0, 1, 0, 1, 0, 1, 0, 1};
|
||||
|
||||
bool expected_layer_sync[16] = {false, true, false, false, false, false,
|
||||
false, false, false, true, false, false,
|
||||
false, false, false, false};
|
||||
|
||||
uint32_t timestamp = 0;
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
TemporalLayers::FrameConfig tl_config = tl.UpdateLayerConfig(timestamp);
|
||||
EXPECT_EQ(expected_flags[i], VP8EncoderImpl::EncodeFlags(tl_config)) << i;
|
||||
tl.PopulateCodecSpecific(false, tl_config, &vp8_info, 0);
|
||||
EXPECT_EQ(expected_temporal_idx[i], vp8_info.temporalIdx);
|
||||
EXPECT_EQ(expected_temporal_idx[i], tl_config.packetizer_temporal_idx);
|
||||
EXPECT_EQ(expected_temporal_idx[i], tl_config.encoder_layer_id);
|
||||
EXPECT_EQ(expected_layer_sync[i], vp8_info.layerSync);
|
||||
EXPECT_EQ(expected_layer_sync[i], tl_config.layer_sync);
|
||||
timestamp += 3000;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TemporalLayersTest, 3Layers) {
|
||||
DefaultTemporalLayers tl(3, 0);
|
||||
vpx_codec_enc_cfg_t cfg;
|
||||
CodecSpecificInfoVP8 vp8_info;
|
||||
tl.OnRatesUpdated(500, 500, 30);
|
||||
tl.UpdateConfiguration(&cfg);
|
||||
|
||||
int expected_flags[16] = {
|
||||
kTemporalUpdateLastRefAltRef,
|
||||
kTemporalUpdateNoneNoRefGolden,
|
||||
kTemporalUpdateGoldenWithoutDependencyRefAltRef,
|
||||
kTemporalUpdateNone,
|
||||
kTemporalUpdateLastRefAltRef,
|
||||
kTemporalUpdateNone,
|
||||
kTemporalUpdateGoldenRefAltRef,
|
||||
kTemporalUpdateNone,
|
||||
kTemporalUpdateLastRefAltRef,
|
||||
kTemporalUpdateNoneNoRefGolden,
|
||||
kTemporalUpdateGoldenWithoutDependencyRefAltRef,
|
||||
kTemporalUpdateNone,
|
||||
kTemporalUpdateLastRefAltRef,
|
||||
kTemporalUpdateNone,
|
||||
kTemporalUpdateGoldenRefAltRef,
|
||||
kTemporalUpdateNone,
|
||||
};
|
||||
int expected_temporal_idx[16] = {0, 2, 1, 2, 0, 2, 1, 2,
|
||||
0, 2, 1, 2, 0, 2, 1, 2};
|
||||
|
||||
bool expected_layer_sync[16] = {false, true, true, false, false, false,
|
||||
false, false, false, true, true, false,
|
||||
false, false, false, false};
|
||||
|
||||
unsigned int timestamp = 0;
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
TemporalLayers::FrameConfig tl_config = tl.UpdateLayerConfig(timestamp);
|
||||
EXPECT_EQ(expected_flags[i], VP8EncoderImpl::EncodeFlags(tl_config)) << i;
|
||||
tl.PopulateCodecSpecific(false, tl_config, &vp8_info, 0);
|
||||
EXPECT_EQ(expected_temporal_idx[i], vp8_info.temporalIdx);
|
||||
EXPECT_EQ(expected_temporal_idx[i], tl_config.packetizer_temporal_idx);
|
||||
EXPECT_EQ(expected_temporal_idx[i], tl_config.encoder_layer_id);
|
||||
EXPECT_EQ(expected_layer_sync[i], vp8_info.layerSync);
|
||||
EXPECT_EQ(expected_layer_sync[i], tl_config.layer_sync);
|
||||
timestamp += 3000;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TemporalLayersTest, Alternative3Layers) {
|
||||
ScopedFieldTrials field_trial("WebRTC-UseShortVP8TL3Pattern/Enabled/");
|
||||
DefaultTemporalLayers tl(3, 0);
|
||||
vpx_codec_enc_cfg_t cfg;
|
||||
CodecSpecificInfoVP8 vp8_info;
|
||||
tl.OnRatesUpdated(500, 500, 30);
|
||||
tl.UpdateConfiguration(&cfg);
|
||||
|
||||
int expected_flags[8] = {kTemporalUpdateLast,
|
||||
kTemporalUpdateAltrefWithoutDependency,
|
||||
kTemporalUpdateGoldenWithoutDependency,
|
||||
kTemporalUpdateNone,
|
||||
kTemporalUpdateLast,
|
||||
kTemporalUpdateAltrefWithoutDependency,
|
||||
kTemporalUpdateGoldenWithoutDependency,
|
||||
kTemporalUpdateNone};
|
||||
int expected_temporal_idx[8] = {0, 2, 1, 2, 0, 2, 1, 2};
|
||||
|
||||
bool expected_layer_sync[8] = {false, true, true, false,
|
||||
false, true, true, false};
|
||||
|
||||
unsigned int timestamp = 0;
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
TemporalLayers::FrameConfig tl_config = tl.UpdateLayerConfig(timestamp);
|
||||
EXPECT_EQ(expected_flags[i], VP8EncoderImpl::EncodeFlags(tl_config)) << i;
|
||||
tl.PopulateCodecSpecific(false, tl_config, &vp8_info, 0);
|
||||
EXPECT_EQ(expected_temporal_idx[i], vp8_info.temporalIdx);
|
||||
EXPECT_EQ(expected_temporal_idx[i], tl_config.packetizer_temporal_idx);
|
||||
EXPECT_EQ(expected_temporal_idx[i], tl_config.encoder_layer_id);
|
||||
EXPECT_EQ(expected_layer_sync[i], vp8_info.layerSync);
|
||||
EXPECT_EQ(expected_layer_sync[i], tl_config.layer_sync);
|
||||
timestamp += 3000;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TemporalLayersTest, 4Layers) {
|
||||
DefaultTemporalLayers tl(4, 0);
|
||||
vpx_codec_enc_cfg_t cfg;
|
||||
CodecSpecificInfoVP8 vp8_info;
|
||||
tl.OnRatesUpdated(500, 500, 30);
|
||||
tl.UpdateConfiguration(&cfg);
|
||||
int expected_flags[16] = {
|
||||
kTemporalUpdateLast,
|
||||
kTemporalUpdateNoneNoRefGoldenAltRef,
|
||||
kTemporalUpdateAltrefWithoutDependency,
|
||||
kTemporalUpdateNoneNoRefGolden,
|
||||
kTemporalUpdateGoldenWithoutDependency,
|
||||
kTemporalUpdateNone,
|
||||
kTemporalUpdateAltref,
|
||||
kTemporalUpdateNone,
|
||||
kTemporalUpdateLast,
|
||||
kTemporalUpdateNone,
|
||||
kTemporalUpdateAltref,
|
||||
kTemporalUpdateNone,
|
||||
kTemporalUpdateGolden,
|
||||
kTemporalUpdateNone,
|
||||
kTemporalUpdateAltref,
|
||||
kTemporalUpdateNone,
|
||||
};
|
||||
int expected_temporal_idx[16] = {0, 3, 2, 3, 1, 3, 2, 3,
|
||||
0, 3, 2, 3, 1, 3, 2, 3};
|
||||
|
||||
bool expected_layer_sync[16] = {false, true, true, false, true, false,
|
||||
false, false, false, false, false, false,
|
||||
false, false, false, false};
|
||||
|
||||
uint32_t timestamp = 0;
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
TemporalLayers::FrameConfig tl_config = tl.UpdateLayerConfig(timestamp);
|
||||
EXPECT_EQ(expected_flags[i], VP8EncoderImpl::EncodeFlags(tl_config)) << i;
|
||||
tl.PopulateCodecSpecific(false, tl_config, &vp8_info, 0);
|
||||
EXPECT_EQ(expected_temporal_idx[i], vp8_info.temporalIdx);
|
||||
EXPECT_EQ(expected_temporal_idx[i], tl_config.packetizer_temporal_idx);
|
||||
EXPECT_EQ(expected_temporal_idx[i], tl_config.encoder_layer_id);
|
||||
EXPECT_EQ(expected_layer_sync[i], vp8_info.layerSync);
|
||||
EXPECT_EQ(expected_layer_sync[i], tl_config.layer_sync);
|
||||
timestamp += 3000;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TemporalLayersTest, KeyFrame) {
|
||||
DefaultTemporalLayers tl(3, 0);
|
||||
vpx_codec_enc_cfg_t cfg;
|
||||
CodecSpecificInfoVP8 vp8_info;
|
||||
tl.OnRatesUpdated(500, 500, 30);
|
||||
tl.UpdateConfiguration(&cfg);
|
||||
|
||||
int expected_flags[8] = {
|
||||
kTemporalUpdateLastRefAltRef,
|
||||
kTemporalUpdateNoneNoRefGolden,
|
||||
kTemporalUpdateGoldenWithoutDependencyRefAltRef,
|
||||
kTemporalUpdateNone,
|
||||
kTemporalUpdateLastRefAltRef,
|
||||
kTemporalUpdateNone,
|
||||
kTemporalUpdateGoldenRefAltRef,
|
||||
kTemporalUpdateNone,
|
||||
};
|
||||
int expected_temporal_idx[8] = {0, 2, 1, 2, 0, 2, 1, 2};
|
||||
bool expected_layer_sync[8] = {false, true, true, false,
|
||||
false, false, false, false};
|
||||
|
||||
uint32_t timestamp = 0;
|
||||
for (int i = 0; i < 7; ++i) {
|
||||
TemporalLayers::FrameConfig tl_config = tl.UpdateLayerConfig(timestamp);
|
||||
EXPECT_EQ(expected_flags[i], VP8EncoderImpl::EncodeFlags(tl_config)) << i;
|
||||
tl.PopulateCodecSpecific(true, tl_config, &vp8_info, 0);
|
||||
EXPECT_EQ(expected_temporal_idx[i], tl_config.packetizer_temporal_idx);
|
||||
EXPECT_EQ(expected_temporal_idx[i], tl_config.encoder_layer_id);
|
||||
EXPECT_EQ(0, vp8_info.temporalIdx)
|
||||
<< "Key frame should always be packetized as layer 0";
|
||||
EXPECT_EQ(expected_layer_sync[i], tl_config.layer_sync);
|
||||
EXPECT_TRUE(vp8_info.layerSync) << "Key frame should be marked layer sync.";
|
||||
timestamp += 3000;
|
||||
}
|
||||
TemporalLayers::FrameConfig tl_config = tl.UpdateLayerConfig(timestamp);
|
||||
EXPECT_EQ(expected_flags[7], VP8EncoderImpl::EncodeFlags(tl_config));
|
||||
tl.PopulateCodecSpecific(false, tl_config, &vp8_info, 0);
|
||||
EXPECT_NE(0, vp8_info.temporalIdx)
|
||||
<< "To test something useful, this frame should not use layer 0.";
|
||||
EXPECT_EQ(expected_temporal_idx[7], vp8_info.temporalIdx)
|
||||
<< "Non-keyframe, should use frame temporal index.";
|
||||
EXPECT_EQ(expected_temporal_idx[7], tl_config.packetizer_temporal_idx);
|
||||
EXPECT_EQ(expected_temporal_idx[7], tl_config.encoder_layer_id);
|
||||
EXPECT_FALSE(tl_config.layer_sync);
|
||||
EXPECT_TRUE(vp8_info.layerSync) << "Frame after keyframe should always be "
|
||||
"marked layer sync since it only depends "
|
||||
"on the base layer.";
|
||||
}
|
||||
|
||||
class TemporalLayersReferenceTest : public ::testing::TestWithParam<int> {
|
||||
public:
|
||||
TemporalLayersReferenceTest()
|
||||
: timestamp_(1),
|
||||
last_sync_timestamp_(timestamp_),
|
||||
tl0_reference_(nullptr) {}
|
||||
virtual ~TemporalLayersReferenceTest() {}
|
||||
|
||||
protected:
|
||||
static const int kMaxPatternLength = 32;
|
||||
|
||||
struct BufferState {
|
||||
BufferState() : BufferState(-1, 0, false) {}
|
||||
BufferState(int temporal_idx, uint32_t timestamp, bool sync)
|
||||
: temporal_idx(temporal_idx), timestamp(timestamp), sync(sync) {}
|
||||
int temporal_idx;
|
||||
uint32_t timestamp;
|
||||
bool sync;
|
||||
};
|
||||
|
||||
bool UpdateSyncRefState(const TemporalLayers::BufferFlags& flags,
|
||||
BufferState* buffer_state) {
|
||||
if (flags & TemporalLayers::kReference) {
|
||||
if (buffer_state->temporal_idx == -1)
|
||||
return true; // References key-frame.
|
||||
if (buffer_state->temporal_idx == 0) {
|
||||
// No more than one reference to TL0 frame.
|
||||
EXPECT_EQ(nullptr, tl0_reference_);
|
||||
tl0_reference_ = buffer_state;
|
||||
return true;
|
||||
}
|
||||
return false; // References higher layer.
|
||||
}
|
||||
return true; // No reference, does not affect sync frame status.
|
||||
}
|
||||
|
||||
void ValidateReference(const TemporalLayers::BufferFlags& flags,
|
||||
const BufferState& buffer_state,
|
||||
int temporal_layer) {
|
||||
if (flags & TemporalLayers::kReference) {
|
||||
if (temporal_layer > 0 && buffer_state.timestamp > 0) {
|
||||
// Check that high layer reference does not go past last sync frame.
|
||||
EXPECT_GE(buffer_state.timestamp, last_sync_timestamp_);
|
||||
}
|
||||
// No reference to buffer in higher layer.
|
||||
EXPECT_LE(buffer_state.temporal_idx, temporal_layer);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t timestamp_ = 1;
|
||||
uint32_t last_sync_timestamp_ = timestamp_;
|
||||
BufferState* tl0_reference_;
|
||||
|
||||
BufferState last_state;
|
||||
BufferState golden_state;
|
||||
BufferState altref_state;
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DefaultTemporalLayersTest,
|
||||
TemporalLayersReferenceTest,
|
||||
::testing::Range(1, kMaxTemporalStreams + 1));
|
||||
|
||||
TEST_P(TemporalLayersReferenceTest, ValidFrameConfigs) {
|
||||
const int num_layers = GetParam();
|
||||
DefaultTemporalLayers tl(num_layers, 0);
|
||||
vpx_codec_enc_cfg_t cfg;
|
||||
tl.OnRatesUpdated(500, 500, 30);
|
||||
tl.UpdateConfiguration(&cfg);
|
||||
|
||||
// Run through the pattern and store the frame dependencies, plus keep track
|
||||
// of the buffer state; which buffers references which temporal layers (if
|
||||
// (any). If a given buffer is never updated, it is legal to reference it
|
||||
// even for sync frames. In order to be general, don't assume TL0 always
|
||||
// updates |last|.
|
||||
std::vector<TemporalLayers::FrameConfig> tl_configs(kMaxPatternLength);
|
||||
for (int i = 0; i < kMaxPatternLength; ++i) {
|
||||
TemporalLayers::FrameConfig tl_config = tl.UpdateLayerConfig(timestamp_++);
|
||||
EXPECT_FALSE(tl_config.drop_frame);
|
||||
tl_configs.push_back(tl_config);
|
||||
int temporal_idx = tl_config.encoder_layer_id;
|
||||
// For the default layers, always keep encoder and rtp layers in sync.
|
||||
EXPECT_EQ(tl_config.packetizer_temporal_idx, temporal_idx);
|
||||
|
||||
// Determine if this frame is in a higher layer but references only TL0
|
||||
// or untouched buffers, if so verify it is marked as a layer sync.
|
||||
bool is_sync_frame = true;
|
||||
tl0_reference_ = nullptr;
|
||||
if (temporal_idx <= 0) {
|
||||
is_sync_frame = false; // TL0 by definition not a sync frame.
|
||||
} else if (!UpdateSyncRefState(tl_config.last_buffer_flags, &last_state)) {
|
||||
is_sync_frame = false;
|
||||
} else if (!UpdateSyncRefState(tl_config.golden_buffer_flags,
|
||||
&golden_state)) {
|
||||
is_sync_frame = false;
|
||||
} else if (!UpdateSyncRefState(tl_config.arf_buffer_flags, &altref_state)) {
|
||||
is_sync_frame = false;
|
||||
}
|
||||
if (is_sync_frame) {
|
||||
// Cache timestamp for last found sync frame, so that we can verify no
|
||||
// references back past this frame.
|
||||
ASSERT_TRUE(tl0_reference_);
|
||||
last_sync_timestamp_ = tl0_reference_->timestamp;
|
||||
}
|
||||
EXPECT_EQ(tl_config.layer_sync, is_sync_frame);
|
||||
|
||||
// Validate no reference from lower to high temporal layer, or backwards
|
||||
// past last reference frame.
|
||||
ValidateReference(tl_config.last_buffer_flags, last_state, temporal_idx);
|
||||
ValidateReference(tl_config.golden_buffer_flags, golden_state,
|
||||
temporal_idx);
|
||||
ValidateReference(tl_config.arf_buffer_flags, altref_state, temporal_idx);
|
||||
|
||||
// Update the current layer state.
|
||||
BufferState state = {temporal_idx, timestamp_, is_sync_frame};
|
||||
if (tl_config.last_buffer_flags & TemporalLayers::kUpdate)
|
||||
last_state = state;
|
||||
if (tl_config.golden_buffer_flags & TemporalLayers::kUpdate)
|
||||
golden_state = state;
|
||||
if (tl_config.arf_buffer_flags & TemporalLayers::kUpdate)
|
||||
altref_state = state;
|
||||
}
|
||||
}
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
35
modules/video_coding/codecs/vp8/include/vp8.h
Normal file
35
modules/video_coding/codecs/vp8/include/vp8.h
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*
|
||||
* WEBRTC VP8 wrapper interface
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_VIDEO_CODING_CODECS_VP8_INCLUDE_VP8_H_
|
||||
#define WEBRTC_MODULES_VIDEO_CODING_CODECS_VP8_INCLUDE_VP8_H_
|
||||
|
||||
#include "webrtc/modules/video_coding/include/video_codec_interface.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class VP8Encoder : public VideoEncoder {
|
||||
public:
|
||||
static VP8Encoder* Create();
|
||||
|
||||
virtual ~VP8Encoder() {}
|
||||
}; // end of VP8Encoder class
|
||||
|
||||
class VP8Decoder : public VideoDecoder {
|
||||
public:
|
||||
static VP8Decoder* Create();
|
||||
|
||||
virtual ~VP8Decoder() {}
|
||||
}; // end of VP8Decoder class
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_VIDEO_CODING_CODECS_VP8_INCLUDE_VP8_H_
|
||||
29
modules/video_coding/codecs/vp8/include/vp8_common_types.h
Normal file
29
modules/video_coding/codecs/vp8/include/vp8_common_types.h
Normal file
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (c) 2011 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_VIDEO_CODING_CODECS_VP8_INCLUDE_VP8_COMMON_TYPES_H_
|
||||
#define WEBRTC_MODULES_VIDEO_CODING_CODECS_VP8_INCLUDE_VP8_COMMON_TYPES_H_
|
||||
|
||||
#include "webrtc/common_types.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Ratio allocation between temporal streams:
|
||||
// Values as required for the VP8 codec (accumulating).
|
||||
static const float
|
||||
kVp8LayerRateAlloction[kMaxSimulcastStreams][kMaxTemporalStreams] = {
|
||||
{1.0f, 1.0f, 1.0f, 1.0f}, // 1 layer
|
||||
{0.6f, 1.0f, 1.0f, 1.0f}, // 2 layers {60%, 40%}
|
||||
{0.4f, 0.6f, 1.0f, 1.0f}, // 3 layers {40%, 20%, 40%}
|
||||
{0.25f, 0.4f, 0.6f, 1.0f} // 4 layers {25%, 15%, 20%, 40%}
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
#endif // WEBRTC_MODULES_VIDEO_CODING_CODECS_VP8_INCLUDE_VP8_COMMON_TYPES_H_
|
||||
49
modules/video_coding/codecs/vp8/include/vp8_globals.h
Normal file
49
modules/video_coding/codecs/vp8/include/vp8_globals.h
Normal file
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
// This file contains codec dependent definitions that are needed in
|
||||
// order to compile the WebRTC codebase, even if this codec is not used.
|
||||
|
||||
#ifndef WEBRTC_MODULES_VIDEO_CODING_CODECS_VP8_INCLUDE_VP8_GLOBALS_H_
|
||||
#define WEBRTC_MODULES_VIDEO_CODING_CODECS_VP8_INCLUDE_VP8_GLOBALS_H_
|
||||
|
||||
#include "webrtc/modules/video_coding/codecs/interface/common_constants.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
struct RTPVideoHeaderVP8 {
|
||||
void InitRTPVideoHeaderVP8() {
|
||||
nonReference = false;
|
||||
pictureId = kNoPictureId;
|
||||
tl0PicIdx = kNoTl0PicIdx;
|
||||
temporalIdx = kNoTemporalIdx;
|
||||
layerSync = false;
|
||||
keyIdx = kNoKeyIdx;
|
||||
partitionId = 0;
|
||||
beginningOfPartition = false;
|
||||
}
|
||||
|
||||
bool nonReference; // Frame is discardable.
|
||||
int16_t pictureId; // Picture ID index, 15 bits;
|
||||
// kNoPictureId if PictureID does not exist.
|
||||
int16_t tl0PicIdx; // TL0PIC_IDX, 8 bits;
|
||||
// kNoTl0PicIdx means no value provided.
|
||||
uint8_t temporalIdx; // Temporal layer index, or kNoTemporalIdx.
|
||||
bool layerSync; // This frame is a layer sync frame.
|
||||
// Disabled if temporalIdx == kNoTemporalIdx.
|
||||
int keyIdx; // 5 bits; kNoKeyIdx means not used.
|
||||
int partitionId; // VP8 partition ID
|
||||
bool beginningOfPartition; // True if this packet is the first
|
||||
// in a VP8 partition. Otherwise false
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_VIDEO_CODING_CODECS_VP8_INCLUDE_VP8_GLOBALS_H_
|
||||
459
modules/video_coding/codecs/vp8/screenshare_layers.cc
Normal file
459
modules/video_coding/codecs/vp8/screenshare_layers.cc
Normal file
@ -0,0 +1,459 @@
|
||||
/* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "webrtc/modules/video_coding/codecs/vp8/screenshare_layers.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "vpx/vp8cx.h"
|
||||
#include "vpx/vpx_encoder.h"
|
||||
#include "webrtc/modules/video_coding/include/video_codec_interface.h"
|
||||
#include "webrtc/rtc_base/checks.h"
|
||||
#include "webrtc/system_wrappers/include/clock.h"
|
||||
#include "webrtc/system_wrappers/include/metrics.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
static const int kOneSecond90Khz = 90000;
|
||||
static const int kMinTimeBetweenSyncs = kOneSecond90Khz * 5;
|
||||
static const int kMaxTimeBetweenSyncs = kOneSecond90Khz * 10;
|
||||
static const int kQpDeltaThresholdForSync = 8;
|
||||
static const int kMinBitrateKbpsForQpBoost = 500;
|
||||
|
||||
const double ScreenshareLayers::kMaxTL0FpsReduction = 2.5;
|
||||
const double ScreenshareLayers::kAcceptableTargetOvershoot = 2.0;
|
||||
|
||||
constexpr int ScreenshareLayers::kMaxNumTemporalLayers;
|
||||
|
||||
// Always emit a frame with certain interval, even if bitrate targets have
|
||||
// been exceeded. This prevents needless keyframe requests.
|
||||
const int ScreenshareLayers::kMaxFrameIntervalMs = 2750;
|
||||
|
||||
webrtc::TemporalLayers* ScreenshareTemporalLayersFactory::Create(
|
||||
int simulcast_id,
|
||||
int num_temporal_layers,
|
||||
uint8_t initial_tl0_pic_idx) const {
|
||||
webrtc::TemporalLayers* tl;
|
||||
if (simulcast_id == 0) {
|
||||
tl = new webrtc::ScreenshareLayers(num_temporal_layers, rand(),
|
||||
webrtc::Clock::GetRealTimeClock());
|
||||
} else {
|
||||
TemporalLayersFactory rt_tl_factory;
|
||||
tl = rt_tl_factory.Create(simulcast_id, num_temporal_layers, rand());
|
||||
}
|
||||
if (listener_)
|
||||
listener_->OnTemporalLayersCreated(simulcast_id, tl);
|
||||
return tl;
|
||||
}
|
||||
|
||||
ScreenshareLayers::ScreenshareLayers(int num_temporal_layers,
|
||||
uint8_t initial_tl0_pic_idx,
|
||||
Clock* clock)
|
||||
: clock_(clock),
|
||||
number_of_temporal_layers_(
|
||||
std::min(kMaxNumTemporalLayers, num_temporal_layers)),
|
||||
last_base_layer_sync_(false),
|
||||
tl0_pic_idx_(initial_tl0_pic_idx),
|
||||
active_layer_(-1),
|
||||
last_timestamp_(-1),
|
||||
last_sync_timestamp_(-1),
|
||||
last_emitted_tl0_timestamp_(-1),
|
||||
min_qp_(-1),
|
||||
max_qp_(-1),
|
||||
max_debt_bytes_(0),
|
||||
encode_framerate_(1000.0f, 1000.0f), // 1 second window, second scale.
|
||||
bitrate_updated_(false) {
|
||||
RTC_CHECK_GT(number_of_temporal_layers_, 0);
|
||||
RTC_CHECK_LE(number_of_temporal_layers_, kMaxNumTemporalLayers);
|
||||
}
|
||||
|
||||
ScreenshareLayers::~ScreenshareLayers() {
|
||||
UpdateHistograms();
|
||||
}
|
||||
|
||||
uint8_t ScreenshareLayers::Tl0PicIdx() const {
|
||||
return tl0_pic_idx_;
|
||||
}
|
||||
|
||||
TemporalLayers::FrameConfig ScreenshareLayers::UpdateLayerConfig(
|
||||
uint32_t timestamp) {
|
||||
if (number_of_temporal_layers_ <= 1) {
|
||||
// No flags needed for 1 layer screenshare.
|
||||
// TODO(pbos): Consider updating only last, and not all buffers.
|
||||
TemporalLayers::FrameConfig tl_config(
|
||||
kReferenceAndUpdate, kReferenceAndUpdate, kReferenceAndUpdate);
|
||||
return tl_config;
|
||||
}
|
||||
|
||||
const int64_t now_ms = clock_->TimeInMilliseconds();
|
||||
if (target_framerate_.value_or(0) > 0 &&
|
||||
encode_framerate_.Rate(now_ms).value_or(0) > *target_framerate_) {
|
||||
// Max framerate exceeded, drop frame.
|
||||
return TemporalLayers::FrameConfig(kNone, kNone, kNone);
|
||||
}
|
||||
|
||||
if (stats_.first_frame_time_ms_ == -1)
|
||||
stats_.first_frame_time_ms_ = now_ms;
|
||||
|
||||
int64_t unwrapped_timestamp = time_wrap_handler_.Unwrap(timestamp);
|
||||
int64_t ts_diff;
|
||||
if (last_timestamp_ == -1) {
|
||||
ts_diff = kOneSecond90Khz / capture_framerate_.value_or(*target_framerate_);
|
||||
} else {
|
||||
ts_diff = unwrapped_timestamp - last_timestamp_;
|
||||
}
|
||||
// Make sure both frame droppers leak out bits.
|
||||
layers_[0].UpdateDebt(ts_diff / 90);
|
||||
layers_[1].UpdateDebt(ts_diff / 90);
|
||||
last_timestamp_ = timestamp;
|
||||
|
||||
TemporalLayerState layer_state = TemporalLayerState::kDrop;
|
||||
|
||||
if (active_layer_ == -1 ||
|
||||
layers_[active_layer_].state != TemporalLayer::State::kDropped) {
|
||||
if (last_emitted_tl0_timestamp_ != -1 &&
|
||||
(unwrapped_timestamp - last_emitted_tl0_timestamp_) / 90 >
|
||||
kMaxFrameIntervalMs) {
|
||||
// Too long time has passed since the last frame was emitted, cancel
|
||||
// enough debt to allow a single frame.
|
||||
layers_[0].debt_bytes_ = max_debt_bytes_ - 1;
|
||||
}
|
||||
if (layers_[0].debt_bytes_ > max_debt_bytes_) {
|
||||
// Must drop TL0, encode TL1 instead.
|
||||
if (layers_[1].debt_bytes_ > max_debt_bytes_) {
|
||||
// Must drop both TL0 and TL1.
|
||||
active_layer_ = -1;
|
||||
} else {
|
||||
active_layer_ = 1;
|
||||
}
|
||||
} else {
|
||||
active_layer_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
switch (active_layer_) {
|
||||
case 0:
|
||||
layer_state = TemporalLayerState::kTl0;
|
||||
last_emitted_tl0_timestamp_ = unwrapped_timestamp;
|
||||
break;
|
||||
case 1:
|
||||
if (layers_[1].state != TemporalLayer::State::kDropped) {
|
||||
if (TimeToSync(unwrapped_timestamp)) {
|
||||
last_sync_timestamp_ = unwrapped_timestamp;
|
||||
layer_state = TemporalLayerState::kTl1Sync;
|
||||
} else {
|
||||
layer_state = TemporalLayerState::kTl1;
|
||||
}
|
||||
} else {
|
||||
layer_state = last_sync_timestamp_ == unwrapped_timestamp
|
||||
? TemporalLayerState::kTl1Sync
|
||||
: TemporalLayerState::kTl1;
|
||||
}
|
||||
break;
|
||||
case -1:
|
||||
layer_state = TemporalLayerState::kDrop;
|
||||
++stats_.num_dropped_frames_;
|
||||
break;
|
||||
default:
|
||||
RTC_NOTREACHED();
|
||||
}
|
||||
|
||||
TemporalLayers::FrameConfig tl_config;
|
||||
// TODO(pbos): Consider referencing but not updating the 'alt' buffer for all
|
||||
// layers.
|
||||
switch (layer_state) {
|
||||
case TemporalLayerState::kDrop:
|
||||
tl_config = TemporalLayers::FrameConfig(kNone, kNone, kNone);
|
||||
break;
|
||||
case TemporalLayerState::kTl0:
|
||||
// TL0 only references and updates 'last'.
|
||||
tl_config =
|
||||
TemporalLayers::FrameConfig(kReferenceAndUpdate, kNone, kNone);
|
||||
tl_config.packetizer_temporal_idx = 0;
|
||||
break;
|
||||
case TemporalLayerState::kTl1:
|
||||
// TL1 references both 'last' and 'golden' but only updates 'golden'.
|
||||
tl_config =
|
||||
TemporalLayers::FrameConfig(kReference, kReferenceAndUpdate, kNone);
|
||||
tl_config.packetizer_temporal_idx = 1;
|
||||
break;
|
||||
case TemporalLayerState::kTl1Sync:
|
||||
// Predict from only TL0 to allow participants to switch to the high
|
||||
// bitrate stream. Updates 'golden' so that TL1 can continue to refer to
|
||||
// and update 'golden' from this point on.
|
||||
tl_config = TemporalLayers::FrameConfig(kReference, kUpdate, kNone);
|
||||
tl_config.packetizer_temporal_idx = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
tl_config.layer_sync = layer_state == TemporalLayerState::kTl1Sync;
|
||||
return tl_config;
|
||||
}
|
||||
|
||||
std::vector<uint32_t> ScreenshareLayers::OnRatesUpdated(int bitrate_kbps,
|
||||
int max_bitrate_kbps,
|
||||
int framerate) {
|
||||
RTC_DCHECK_GT(framerate, 0);
|
||||
if (!target_framerate_) {
|
||||
// First OnRatesUpdated() is called during construction, with the configured
|
||||
// targets as parameters.
|
||||
target_framerate_.emplace(framerate);
|
||||
capture_framerate_ = target_framerate_;
|
||||
bitrate_updated_ = true;
|
||||
} else {
|
||||
bitrate_updated_ =
|
||||
bitrate_kbps != static_cast<int>(layers_[0].target_rate_kbps_) ||
|
||||
max_bitrate_kbps != static_cast<int>(layers_[1].target_rate_kbps_) ||
|
||||
(capture_framerate_ &&
|
||||
framerate != static_cast<int>(*capture_framerate_));
|
||||
if (framerate < 0) {
|
||||
capture_framerate_.reset();
|
||||
} else {
|
||||
capture_framerate_.emplace(framerate);
|
||||
}
|
||||
}
|
||||
|
||||
layers_[0].target_rate_kbps_ = bitrate_kbps;
|
||||
layers_[1].target_rate_kbps_ = max_bitrate_kbps;
|
||||
|
||||
std::vector<uint32_t> allocation;
|
||||
allocation.push_back(bitrate_kbps);
|
||||
if (max_bitrate_kbps > bitrate_kbps)
|
||||
allocation.push_back(max_bitrate_kbps - bitrate_kbps);
|
||||
return allocation;
|
||||
}
|
||||
|
||||
void ScreenshareLayers::FrameEncoded(unsigned int size, int qp) {
|
||||
if (size > 0)
|
||||
encode_framerate_.Update(1, clock_->TimeInMilliseconds());
|
||||
|
||||
if (number_of_temporal_layers_ == 1)
|
||||
return;
|
||||
|
||||
RTC_DCHECK_NE(-1, active_layer_);
|
||||
if (size == 0) {
|
||||
layers_[active_layer_].state = TemporalLayer::State::kDropped;
|
||||
++stats_.num_overshoots_;
|
||||
return;
|
||||
}
|
||||
|
||||
if (layers_[active_layer_].state == TemporalLayer::State::kDropped) {
|
||||
layers_[active_layer_].state = TemporalLayer::State::kQualityBoost;
|
||||
}
|
||||
|
||||
if (qp != -1)
|
||||
layers_[active_layer_].last_qp = qp;
|
||||
|
||||
if (active_layer_ == 0) {
|
||||
layers_[0].debt_bytes_ += size;
|
||||
layers_[1].debt_bytes_ += size;
|
||||
++stats_.num_tl0_frames_;
|
||||
stats_.tl0_target_bitrate_sum_ += layers_[0].target_rate_kbps_;
|
||||
stats_.tl0_qp_sum_ += qp;
|
||||
} else if (active_layer_ == 1) {
|
||||
layers_[1].debt_bytes_ += size;
|
||||
++stats_.num_tl1_frames_;
|
||||
stats_.tl1_target_bitrate_sum_ += layers_[1].target_rate_kbps_;
|
||||
stats_.tl1_qp_sum_ += qp;
|
||||
}
|
||||
}
|
||||
|
||||
void ScreenshareLayers::PopulateCodecSpecific(
|
||||
bool frame_is_keyframe,
|
||||
const TemporalLayers::FrameConfig& tl_config,
|
||||
CodecSpecificInfoVP8* vp8_info,
|
||||
uint32_t timestamp) {
|
||||
if (number_of_temporal_layers_ == 1) {
|
||||
vp8_info->temporalIdx = kNoTemporalIdx;
|
||||
vp8_info->layerSync = false;
|
||||
vp8_info->tl0PicIdx = kNoTl0PicIdx;
|
||||
} else {
|
||||
int64_t unwrapped_timestamp = time_wrap_handler_.Unwrap(timestamp);
|
||||
vp8_info->temporalIdx = tl_config.packetizer_temporal_idx;
|
||||
vp8_info->layerSync = tl_config.layer_sync;
|
||||
if (frame_is_keyframe) {
|
||||
vp8_info->temporalIdx = 0;
|
||||
last_sync_timestamp_ = unwrapped_timestamp;
|
||||
vp8_info->layerSync = true;
|
||||
} else if (last_base_layer_sync_ && vp8_info->temporalIdx != 0) {
|
||||
// Regardless of pattern the frame after a base layer sync will always
|
||||
// be a layer sync.
|
||||
last_sync_timestamp_ = unwrapped_timestamp;
|
||||
vp8_info->layerSync = true;
|
||||
}
|
||||
if (vp8_info->temporalIdx == 0) {
|
||||
tl0_pic_idx_++;
|
||||
}
|
||||
last_base_layer_sync_ = frame_is_keyframe;
|
||||
vp8_info->tl0PicIdx = tl0_pic_idx_;
|
||||
}
|
||||
}
|
||||
|
||||
bool ScreenshareLayers::TimeToSync(int64_t timestamp) const {
|
||||
RTC_DCHECK_EQ(1, active_layer_);
|
||||
RTC_DCHECK_NE(-1, layers_[0].last_qp);
|
||||
if (layers_[1].last_qp == -1) {
|
||||
// First frame in TL1 should only depend on TL0 since there are no
|
||||
// previous frames in TL1.
|
||||
return true;
|
||||
}
|
||||
|
||||
RTC_DCHECK_NE(-1, last_sync_timestamp_);
|
||||
int64_t timestamp_diff = timestamp - last_sync_timestamp_;
|
||||
if (timestamp_diff > kMaxTimeBetweenSyncs) {
|
||||
// After a certain time, force a sync frame.
|
||||
return true;
|
||||
} else if (timestamp_diff < kMinTimeBetweenSyncs) {
|
||||
// If too soon from previous sync frame, don't issue a new one.
|
||||
return false;
|
||||
}
|
||||
// Issue a sync frame if difference in quality between TL0 and TL1 isn't too
|
||||
// large.
|
||||
if (layers_[0].last_qp - layers_[1].last_qp < kQpDeltaThresholdForSync)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t ScreenshareLayers::GetCodecTargetBitrateKbps() const {
|
||||
uint32_t target_bitrate_kbps = layers_[0].target_rate_kbps_;
|
||||
|
||||
if (number_of_temporal_layers_ > 1) {
|
||||
// Calculate a codec target bitrate. This may be higher than TL0, gaining
|
||||
// quality at the expense of frame rate at TL0. Constraints:
|
||||
// - TL0 frame rate no less than framerate / kMaxTL0FpsReduction.
|
||||
// - Target rate * kAcceptableTargetOvershoot should not exceed TL1 rate.
|
||||
target_bitrate_kbps =
|
||||
std::min(layers_[0].target_rate_kbps_ * kMaxTL0FpsReduction,
|
||||
layers_[1].target_rate_kbps_ / kAcceptableTargetOvershoot);
|
||||
}
|
||||
|
||||
return std::max(layers_[0].target_rate_kbps_, target_bitrate_kbps);
|
||||
}
|
||||
|
||||
bool ScreenshareLayers::UpdateConfiguration(vpx_codec_enc_cfg_t* cfg) {
|
||||
bool cfg_updated = false;
|
||||
uint32_t target_bitrate_kbps = GetCodecTargetBitrateKbps();
|
||||
if (bitrate_updated_ || cfg->rc_target_bitrate != target_bitrate_kbps) {
|
||||
cfg->rc_target_bitrate = target_bitrate_kbps;
|
||||
|
||||
// Don't reconfigure qp limits during quality boost frames.
|
||||
if (active_layer_ == -1 ||
|
||||
layers_[active_layer_].state != TemporalLayer::State::kQualityBoost) {
|
||||
min_qp_ = cfg->rc_min_quantizer;
|
||||
max_qp_ = cfg->rc_max_quantizer;
|
||||
// After a dropped frame, a frame with max qp will be encoded and the
|
||||
// quality will then ramp up from there. To boost the speed of recovery,
|
||||
// encode the next frame with lower max qp, if there is sufficient
|
||||
// bandwidth to do so without causing excessive delay.
|
||||
// TL0 is the most important to improve since the errors in this layer
|
||||
// will propagate to TL1.
|
||||
// Currently, reduce max qp by 20% for TL0 and 15% for TL1.
|
||||
if (layers_[1].target_rate_kbps_ >= kMinBitrateKbpsForQpBoost) {
|
||||
layers_[0].enhanced_max_qp =
|
||||
min_qp_ + (((max_qp_ - min_qp_) * 80) / 100);
|
||||
layers_[1].enhanced_max_qp =
|
||||
min_qp_ + (((max_qp_ - min_qp_) * 85) / 100);
|
||||
} else {
|
||||
layers_[0].enhanced_max_qp = -1;
|
||||
layers_[1].enhanced_max_qp = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (capture_framerate_) {
|
||||
int avg_frame_size =
|
||||
(target_bitrate_kbps * 1000) / (8 * *capture_framerate_);
|
||||
// Allow max debt to be the size of a single optimal frame.
|
||||
// TODO(sprang): Determine if this needs to be adjusted by some factor.
|
||||
// (Lower values may cause more frame drops, higher may lead to queuing
|
||||
// delays.)
|
||||
max_debt_bytes_ = avg_frame_size;
|
||||
}
|
||||
|
||||
bitrate_updated_ = false;
|
||||
cfg_updated = true;
|
||||
}
|
||||
|
||||
// Don't try to update boosts state if not active yet.
|
||||
if (active_layer_ == -1)
|
||||
return cfg_updated;
|
||||
|
||||
if (max_qp_ == -1 || number_of_temporal_layers_ <= 1)
|
||||
return cfg_updated;
|
||||
|
||||
// If layer is in the quality boost state (following a dropped frame), update
|
||||
// the configuration with the adjusted (lower) qp and set the state back to
|
||||
// normal.
|
||||
unsigned int adjusted_max_qp;
|
||||
if (layers_[active_layer_].state == TemporalLayer::State::kQualityBoost &&
|
||||
layers_[active_layer_].enhanced_max_qp != -1) {
|
||||
adjusted_max_qp = layers_[active_layer_].enhanced_max_qp;
|
||||
layers_[active_layer_].state = TemporalLayer::State::kNormal;
|
||||
} else {
|
||||
adjusted_max_qp = max_qp_; // Set the normal max qp.
|
||||
}
|
||||
|
||||
if (adjusted_max_qp == cfg->rc_max_quantizer)
|
||||
return cfg_updated;
|
||||
|
||||
cfg->rc_max_quantizer = adjusted_max_qp;
|
||||
cfg_updated = true;
|
||||
|
||||
return cfg_updated;
|
||||
}
|
||||
|
||||
void ScreenshareLayers::TemporalLayer::UpdateDebt(int64_t delta_ms) {
|
||||
uint32_t debt_reduction_bytes = target_rate_kbps_ * delta_ms / 8;
|
||||
if (debt_reduction_bytes >= debt_bytes_) {
|
||||
debt_bytes_ = 0;
|
||||
} else {
|
||||
debt_bytes_ -= debt_reduction_bytes;
|
||||
}
|
||||
}
|
||||
|
||||
void ScreenshareLayers::UpdateHistograms() {
|
||||
if (stats_.first_frame_time_ms_ == -1)
|
||||
return;
|
||||
int64_t duration_sec =
|
||||
(clock_->TimeInMilliseconds() - stats_.first_frame_time_ms_ + 500) / 1000;
|
||||
if (duration_sec >= metrics::kMinRunTimeInSeconds) {
|
||||
RTC_HISTOGRAM_COUNTS_10000(
|
||||
"WebRTC.Video.Screenshare.Layer0.FrameRate",
|
||||
(stats_.num_tl0_frames_ + (duration_sec / 2)) / duration_sec);
|
||||
RTC_HISTOGRAM_COUNTS_10000(
|
||||
"WebRTC.Video.Screenshare.Layer1.FrameRate",
|
||||
(stats_.num_tl1_frames_ + (duration_sec / 2)) / duration_sec);
|
||||
int total_frames = stats_.num_tl0_frames_ + stats_.num_tl1_frames_;
|
||||
RTC_HISTOGRAM_COUNTS_10000(
|
||||
"WebRTC.Video.Screenshare.FramesPerDrop",
|
||||
(stats_.num_dropped_frames_ == 0 ? 0 : total_frames /
|
||||
stats_.num_dropped_frames_));
|
||||
RTC_HISTOGRAM_COUNTS_10000(
|
||||
"WebRTC.Video.Screenshare.FramesPerOvershoot",
|
||||
(stats_.num_overshoots_ == 0 ? 0
|
||||
: total_frames / stats_.num_overshoots_));
|
||||
if (stats_.num_tl0_frames_ > 0) {
|
||||
RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.Screenshare.Layer0.Qp",
|
||||
stats_.tl0_qp_sum_ / stats_.num_tl0_frames_);
|
||||
RTC_HISTOGRAM_COUNTS_10000(
|
||||
"WebRTC.Video.Screenshare.Layer0.TargetBitrate",
|
||||
stats_.tl0_target_bitrate_sum_ / stats_.num_tl0_frames_);
|
||||
}
|
||||
if (stats_.num_tl1_frames_ > 0) {
|
||||
RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.Screenshare.Layer1.Qp",
|
||||
stats_.tl1_qp_sum_ / stats_.num_tl1_frames_);
|
||||
RTC_HISTOGRAM_COUNTS_10000(
|
||||
"WebRTC.Video.Screenshare.Layer1.TargetBitrate",
|
||||
stats_.tl1_target_bitrate_sum_ / stats_.num_tl1_frames_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
127
modules/video_coding/codecs/vp8/screenshare_layers.h
Normal file
127
modules/video_coding/codecs/vp8/screenshare_layers.h
Normal file
@ -0,0 +1,127 @@
|
||||
/* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
#ifndef WEBRTC_MODULES_VIDEO_CODING_CODECS_VP8_SCREENSHARE_LAYERS_H_
|
||||
#define WEBRTC_MODULES_VIDEO_CODING_CODECS_VP8_SCREENSHARE_LAYERS_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/modules/video_coding/codecs/vp8/temporal_layers.h"
|
||||
#include "webrtc/modules/video_coding/utility/frame_dropper.h"
|
||||
#include "webrtc/rtc_base/rate_statistics.h"
|
||||
#include "webrtc/rtc_base/timeutils.h"
|
||||
#include "webrtc/typedefs.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
struct CodecSpecificInfoVP8;
|
||||
class Clock;
|
||||
|
||||
class ScreenshareLayers : public TemporalLayers {
|
||||
public:
|
||||
static const double kMaxTL0FpsReduction;
|
||||
static const double kAcceptableTargetOvershoot;
|
||||
static const int kMaxFrameIntervalMs;
|
||||
|
||||
ScreenshareLayers(int num_temporal_layers,
|
||||
uint8_t initial_tl0_pic_idx,
|
||||
Clock* clock);
|
||||
virtual ~ScreenshareLayers();
|
||||
|
||||
// Returns the recommended VP8 encode flags needed. May refresh the decoder
|
||||
// and/or update the reference buffers.
|
||||
TemporalLayers::FrameConfig UpdateLayerConfig(uint32_t timestamp) override;
|
||||
|
||||
// Update state based on new bitrate target and incoming framerate.
|
||||
// Returns the bitrate allocation for the active temporal layers.
|
||||
std::vector<uint32_t> OnRatesUpdated(int bitrate_kbps,
|
||||
int max_bitrate_kbps,
|
||||
int framerate) override;
|
||||
|
||||
// Update the encoder configuration with target bitrates or other parameters.
|
||||
// Returns true iff the configuration was actually modified.
|
||||
bool UpdateConfiguration(vpx_codec_enc_cfg_t* cfg) override;
|
||||
|
||||
void PopulateCodecSpecific(bool base_layer_sync,
|
||||
const TemporalLayers::FrameConfig& tl_config,
|
||||
CodecSpecificInfoVP8* vp8_info,
|
||||
uint32_t timestamp) override;
|
||||
|
||||
void FrameEncoded(unsigned int size, int qp) override;
|
||||
|
||||
uint8_t Tl0PicIdx() const override;
|
||||
|
||||
private:
|
||||
enum class TemporalLayerState : int { kDrop, kTl0, kTl1, kTl1Sync };
|
||||
|
||||
bool TimeToSync(int64_t timestamp) const;
|
||||
uint32_t GetCodecTargetBitrateKbps() const;
|
||||
|
||||
Clock* const clock_;
|
||||
|
||||
int number_of_temporal_layers_;
|
||||
bool last_base_layer_sync_;
|
||||
uint8_t tl0_pic_idx_;
|
||||
int active_layer_;
|
||||
int64_t last_timestamp_;
|
||||
int64_t last_sync_timestamp_;
|
||||
int64_t last_emitted_tl0_timestamp_;
|
||||
rtc::TimestampWrapAroundHandler time_wrap_handler_;
|
||||
int min_qp_;
|
||||
int max_qp_;
|
||||
uint32_t max_debt_bytes_;
|
||||
|
||||
// Configured max framerate.
|
||||
rtc::Optional<uint32_t> target_framerate_;
|
||||
// Incoming framerate from capturer.
|
||||
rtc::Optional<uint32_t> capture_framerate_;
|
||||
// Tracks what framerate we actually encode, and drops frames on overshoot.
|
||||
RateStatistics encode_framerate_;
|
||||
bool bitrate_updated_;
|
||||
|
||||
static constexpr int kMaxNumTemporalLayers = 2;
|
||||
struct TemporalLayer {
|
||||
TemporalLayer()
|
||||
: state(State::kNormal),
|
||||
enhanced_max_qp(-1),
|
||||
last_qp(-1),
|
||||
debt_bytes_(0),
|
||||
target_rate_kbps_(0) {}
|
||||
|
||||
enum class State {
|
||||
kNormal,
|
||||
kDropped,
|
||||
kReencoded,
|
||||
kQualityBoost,
|
||||
} state;
|
||||
|
||||
int enhanced_max_qp;
|
||||
int last_qp;
|
||||
uint32_t debt_bytes_;
|
||||
uint32_t target_rate_kbps_;
|
||||
|
||||
void UpdateDebt(int64_t delta_ms);
|
||||
} layers_[kMaxNumTemporalLayers];
|
||||
|
||||
void UpdateHistograms();
|
||||
// Data for histogram statistics.
|
||||
struct Stats {
|
||||
int64_t first_frame_time_ms_ = -1;
|
||||
int64_t num_tl0_frames_ = 0;
|
||||
int64_t num_tl1_frames_ = 0;
|
||||
int64_t num_dropped_frames_ = 0;
|
||||
int64_t num_overshoots_ = 0;
|
||||
int64_t tl0_qp_sum_ = 0;
|
||||
int64_t tl1_qp_sum_ = 0;
|
||||
int64_t tl0_target_bitrate_sum_ = 0;
|
||||
int64_t tl1_target_bitrate_sum_ = 0;
|
||||
} stats_;
|
||||
};
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_VIDEO_CODING_CODECS_VP8_SCREENSHARE_LAYERS_H_
|
||||
607
modules/video_coding/codecs/vp8/screenshare_layers_unittest.cc
Normal file
607
modules/video_coding/codecs/vp8/screenshare_layers_unittest.cc
Normal file
@ -0,0 +1,607 @@
|
||||
/*
|
||||
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "vpx/vp8cx.h"
|
||||
#include "vpx/vpx_encoder.h"
|
||||
#include "webrtc/modules/video_coding/codecs/vp8/screenshare_layers.h"
|
||||
#include "webrtc/modules/video_coding/codecs/vp8/vp8_impl.h"
|
||||
#include "webrtc/modules/video_coding/include/video_codec_interface.h"
|
||||
#include "webrtc/modules/video_coding/utility/mock/mock_frame_dropper.h"
|
||||
#include "webrtc/system_wrappers/include/clock.h"
|
||||
#include "webrtc/system_wrappers/include/metrics.h"
|
||||
#include "webrtc/system_wrappers/include/metrics_default.h"
|
||||
#include "webrtc/test/gtest.h"
|
||||
|
||||
using ::testing::_;
|
||||
using ::testing::ElementsAre;
|
||||
using ::testing::NiceMock;
|
||||
using ::testing::Return;
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// 5 frames per second at 90 kHz.
|
||||
const uint32_t kTimestampDelta5Fps = 90000 / 5;
|
||||
const int kDefaultQp = 54;
|
||||
const int kDefaultTl0BitrateKbps = 200;
|
||||
const int kDefaultTl1BitrateKbps = 2000;
|
||||
const int kFrameRate = 5;
|
||||
const int kSyncPeriodSeconds = 5;
|
||||
const int kMaxSyncPeriodSeconds = 10;
|
||||
|
||||
// Expected flags for corresponding temporal layers.
|
||||
const int kTl0Flags = VP8_EFLAG_NO_UPD_GF | VP8_EFLAG_NO_UPD_ARF |
|
||||
VP8_EFLAG_NO_REF_GF | VP8_EFLAG_NO_REF_ARF;
|
||||
const int kTl1Flags =
|
||||
VP8_EFLAG_NO_REF_ARF | VP8_EFLAG_NO_UPD_ARF | VP8_EFLAG_NO_UPD_LAST;
|
||||
const int kTl1SyncFlags = VP8_EFLAG_NO_REF_ARF | VP8_EFLAG_NO_REF_GF |
|
||||
VP8_EFLAG_NO_UPD_ARF | VP8_EFLAG_NO_UPD_LAST;
|
||||
|
||||
class ScreenshareLayerTest : public ::testing::Test {
|
||||
protected:
|
||||
ScreenshareLayerTest()
|
||||
: min_qp_(2),
|
||||
max_qp_(kDefaultQp),
|
||||
frame_size_(-1),
|
||||
clock_(1),
|
||||
timestamp_(90),
|
||||
config_updated_(false) {}
|
||||
virtual ~ScreenshareLayerTest() {}
|
||||
|
||||
void SetUp() override {
|
||||
layers_.reset(new ScreenshareLayers(2, 0, &clock_));
|
||||
cfg_ = ConfigureBitrates();
|
||||
}
|
||||
|
||||
int EncodeFrame(bool base_sync) {
|
||||
int flags = ConfigureFrame(base_sync);
|
||||
if (flags != -1)
|
||||
layers_->FrameEncoded(frame_size_, kDefaultQp);
|
||||
return flags;
|
||||
}
|
||||
|
||||
int ConfigureFrame(bool key_frame) {
|
||||
tl_config_ = layers_->UpdateLayerConfig(timestamp_);
|
||||
EXPECT_EQ(0, tl_config_.encoder_layer_id)
|
||||
<< "ScreenshareLayers always encodes using the bitrate allocator for "
|
||||
"layer 0, but may reference different buffers and packetize "
|
||||
"differently.";
|
||||
if (tl_config_.drop_frame) {
|
||||
return -1;
|
||||
}
|
||||
config_updated_ = layers_->UpdateConfiguration(&cfg_);
|
||||
int flags = VP8EncoderImpl::EncodeFlags(tl_config_);
|
||||
layers_->PopulateCodecSpecific(key_frame, tl_config_, &vp8_info_,
|
||||
timestamp_);
|
||||
EXPECT_NE(-1, frame_size_);
|
||||
return flags;
|
||||
}
|
||||
|
||||
int FrameSizeForBitrate(int bitrate_kbps) {
|
||||
return ((bitrate_kbps * 1000) / 8) / kFrameRate;
|
||||
}
|
||||
|
||||
vpx_codec_enc_cfg_t ConfigureBitrates() {
|
||||
vpx_codec_enc_cfg_t vpx_cfg;
|
||||
memset(&vpx_cfg, 0, sizeof(vpx_codec_enc_cfg_t));
|
||||
vpx_cfg.rc_min_quantizer = min_qp_;
|
||||
vpx_cfg.rc_max_quantizer = max_qp_;
|
||||
EXPECT_THAT(layers_->OnRatesUpdated(kDefaultTl0BitrateKbps,
|
||||
kDefaultTl1BitrateKbps, kFrameRate),
|
||||
ElementsAre(kDefaultTl0BitrateKbps,
|
||||
kDefaultTl1BitrateKbps - kDefaultTl0BitrateKbps));
|
||||
EXPECT_TRUE(layers_->UpdateConfiguration(&vpx_cfg));
|
||||
frame_size_ = FrameSizeForBitrate(vpx_cfg.rc_target_bitrate);
|
||||
return vpx_cfg;
|
||||
}
|
||||
|
||||
void WithQpLimits(int min_qp, int max_qp) {
|
||||
min_qp_ = min_qp;
|
||||
max_qp_ = max_qp;
|
||||
}
|
||||
|
||||
// Runs a few initial frames and makes sure we have seen frames on both
|
||||
// temporal layers.
|
||||
bool RunGracePeriod() {
|
||||
bool got_tl0 = false;
|
||||
bool got_tl1 = false;
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
EXPECT_NE(-1, EncodeFrame(false));
|
||||
timestamp_ += kTimestampDelta5Fps;
|
||||
if (vp8_info_.temporalIdx == 0) {
|
||||
got_tl0 = true;
|
||||
} else {
|
||||
got_tl1 = true;
|
||||
}
|
||||
if (got_tl0 && got_tl1)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Adds frames until we get one in the specified temporal layer. The last
|
||||
// FrameEncoded() call will be omitted and needs to be done by the caller.
|
||||
// Returns the flags for the last frame.
|
||||
int SkipUntilTl(int layer) {
|
||||
return SkipUntilTlAndSync(layer, rtc::Optional<bool>());
|
||||
}
|
||||
|
||||
// Same as SkipUntilTl, but also waits until the sync bit condition is met.
|
||||
int SkipUntilTlAndSync(int layer, rtc::Optional<bool> sync) {
|
||||
int flags = 0;
|
||||
const int kMaxFramesToSkip =
|
||||
1 + (sync.value_or(false) ? kMaxSyncPeriodSeconds : 1) * kFrameRate;
|
||||
for (int i = 0; i < kMaxFramesToSkip; ++i) {
|
||||
flags = ConfigureFrame(false);
|
||||
timestamp_ += kTimestampDelta5Fps;
|
||||
if (vp8_info_.temporalIdx != layer ||
|
||||
(sync && *sync != vp8_info_.layerSync)) {
|
||||
layers_->FrameEncoded(frame_size_, kDefaultQp);
|
||||
} else {
|
||||
// Found frame from sought after layer.
|
||||
return flags;
|
||||
}
|
||||
}
|
||||
ADD_FAILURE() << "Did not get a frame of TL" << layer << " in time.";
|
||||
return -1;
|
||||
}
|
||||
|
||||
int min_qp_;
|
||||
int max_qp_;
|
||||
int frame_size_;
|
||||
SimulatedClock clock_;
|
||||
std::unique_ptr<ScreenshareLayers> layers_;
|
||||
|
||||
uint32_t timestamp_;
|
||||
TemporalLayers::FrameConfig tl_config_;
|
||||
vpx_codec_enc_cfg_t cfg_;
|
||||
bool config_updated_;
|
||||
CodecSpecificInfoVP8 vp8_info_;
|
||||
};
|
||||
|
||||
TEST_F(ScreenshareLayerTest, 1Layer) {
|
||||
layers_.reset(new ScreenshareLayers(1, 0, &clock_));
|
||||
ConfigureBitrates();
|
||||
// One layer screenshare should not use the frame dropper as all frames will
|
||||
// belong to the base layer.
|
||||
const int kSingleLayerFlags = 0;
|
||||
int flags = EncodeFrame(false);
|
||||
timestamp_ += kTimestampDelta5Fps;
|
||||
EXPECT_EQ(static_cast<uint8_t>(kNoTemporalIdx), vp8_info_.temporalIdx);
|
||||
EXPECT_FALSE(vp8_info_.layerSync);
|
||||
EXPECT_EQ(kNoTl0PicIdx, vp8_info_.tl0PicIdx);
|
||||
|
||||
flags = EncodeFrame(false);
|
||||
EXPECT_EQ(kSingleLayerFlags, flags);
|
||||
EXPECT_EQ(static_cast<uint8_t>(kNoTemporalIdx), vp8_info_.temporalIdx);
|
||||
EXPECT_FALSE(vp8_info_.layerSync);
|
||||
EXPECT_EQ(kNoTl0PicIdx, vp8_info_.tl0PicIdx);
|
||||
}
|
||||
|
||||
TEST_F(ScreenshareLayerTest, 2LayersPeriodicSync) {
|
||||
std::vector<int> sync_times;
|
||||
const int kNumFrames = kSyncPeriodSeconds * kFrameRate * 2 - 1;
|
||||
for (int i = 0; i < kNumFrames; ++i) {
|
||||
EncodeFrame(false);
|
||||
timestamp_ += kTimestampDelta5Fps;
|
||||
if (vp8_info_.temporalIdx == 1 && vp8_info_.layerSync) {
|
||||
sync_times.push_back(timestamp_);
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT_EQ(2u, sync_times.size());
|
||||
EXPECT_GE(sync_times[1] - sync_times[0], 90000 * kSyncPeriodSeconds);
|
||||
}
|
||||
|
||||
TEST_F(ScreenshareLayerTest, 2LayersSyncAfterTimeout) {
|
||||
std::vector<int> sync_times;
|
||||
const int kNumFrames = kMaxSyncPeriodSeconds * kFrameRate * 2 - 1;
|
||||
for (int i = 0; i < kNumFrames; ++i) {
|
||||
tl_config_ = layers_->UpdateLayerConfig(timestamp_);
|
||||
config_updated_ = layers_->UpdateConfiguration(&cfg_);
|
||||
layers_->PopulateCodecSpecific(false, tl_config_, &vp8_info_, timestamp_);
|
||||
|
||||
// Simulate TL1 being at least 8 qp steps better.
|
||||
if (vp8_info_.temporalIdx == 0) {
|
||||
layers_->FrameEncoded(frame_size_, kDefaultQp);
|
||||
} else {
|
||||
layers_->FrameEncoded(frame_size_, kDefaultQp - 8);
|
||||
}
|
||||
|
||||
if (vp8_info_.temporalIdx == 1 && vp8_info_.layerSync)
|
||||
sync_times.push_back(timestamp_);
|
||||
|
||||
timestamp_ += kTimestampDelta5Fps;
|
||||
}
|
||||
|
||||
ASSERT_EQ(2u, sync_times.size());
|
||||
EXPECT_GE(sync_times[1] - sync_times[0], 90000 * kMaxSyncPeriodSeconds);
|
||||
}
|
||||
|
||||
TEST_F(ScreenshareLayerTest, 2LayersSyncAfterSimilarQP) {
|
||||
std::vector<int> sync_times;
|
||||
|
||||
const int kNumFrames = (kSyncPeriodSeconds +
|
||||
((kMaxSyncPeriodSeconds - kSyncPeriodSeconds) / 2)) *
|
||||
kFrameRate;
|
||||
for (int i = 0; i < kNumFrames; ++i) {
|
||||
ConfigureFrame(false);
|
||||
|
||||
// Simulate TL1 being at least 8 qp steps better.
|
||||
if (vp8_info_.temporalIdx == 0) {
|
||||
layers_->FrameEncoded(frame_size_, kDefaultQp);
|
||||
} else {
|
||||
layers_->FrameEncoded(frame_size_, kDefaultQp - 8);
|
||||
}
|
||||
|
||||
if (vp8_info_.temporalIdx == 1 && vp8_info_.layerSync)
|
||||
sync_times.push_back(timestamp_);
|
||||
|
||||
timestamp_ += kTimestampDelta5Fps;
|
||||
}
|
||||
|
||||
ASSERT_EQ(1u, sync_times.size());
|
||||
|
||||
bool bumped_tl0_quality = false;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
int flags = ConfigureFrame(false);
|
||||
if (vp8_info_.temporalIdx == 0) {
|
||||
// Bump TL0 to same quality as TL1.
|
||||
layers_->FrameEncoded(frame_size_, kDefaultQp - 8);
|
||||
bumped_tl0_quality = true;
|
||||
} else {
|
||||
layers_->FrameEncoded(frame_size_, kDefaultQp - 8);
|
||||
if (bumped_tl0_quality) {
|
||||
EXPECT_TRUE(vp8_info_.layerSync);
|
||||
EXPECT_EQ(kTl1SyncFlags, flags);
|
||||
return;
|
||||
}
|
||||
}
|
||||
timestamp_ += kTimestampDelta5Fps;
|
||||
}
|
||||
ADD_FAILURE() << "No TL1 frame arrived within time limit.";
|
||||
}
|
||||
|
||||
TEST_F(ScreenshareLayerTest, 2LayersToggling) {
|
||||
EXPECT_TRUE(RunGracePeriod());
|
||||
|
||||
// Insert 50 frames. 2/5 should be TL0.
|
||||
int tl0_frames = 0;
|
||||
int tl1_frames = 0;
|
||||
for (int i = 0; i < 50; ++i) {
|
||||
EncodeFrame(false);
|
||||
timestamp_ += kTimestampDelta5Fps;
|
||||
switch (vp8_info_.temporalIdx) {
|
||||
case 0:
|
||||
++tl0_frames;
|
||||
break;
|
||||
case 1:
|
||||
++tl1_frames;
|
||||
break;
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
}
|
||||
EXPECT_EQ(20, tl0_frames);
|
||||
EXPECT_EQ(30, tl1_frames);
|
||||
}
|
||||
|
||||
TEST_F(ScreenshareLayerTest, AllFitsLayer0) {
|
||||
frame_size_ = FrameSizeForBitrate(kDefaultTl0BitrateKbps);
|
||||
|
||||
// Insert 50 frames, small enough that all fits in TL0.
|
||||
for (int i = 0; i < 50; ++i) {
|
||||
int flags = EncodeFrame(false);
|
||||
timestamp_ += kTimestampDelta5Fps;
|
||||
EXPECT_EQ(kTl0Flags, flags);
|
||||
EXPECT_EQ(0, vp8_info_.temporalIdx);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ScreenshareLayerTest, TooHighBitrate) {
|
||||
frame_size_ = 2 * FrameSizeForBitrate(kDefaultTl1BitrateKbps);
|
||||
|
||||
// Insert 100 frames. Half should be dropped.
|
||||
int tl0_frames = 0;
|
||||
int tl1_frames = 0;
|
||||
int dropped_frames = 0;
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
int flags = EncodeFrame(false);
|
||||
timestamp_ += kTimestampDelta5Fps;
|
||||
if (flags == -1) {
|
||||
++dropped_frames;
|
||||
} else {
|
||||
switch (vp8_info_.temporalIdx) {
|
||||
case 0:
|
||||
++tl0_frames;
|
||||
break;
|
||||
case 1:
|
||||
++tl1_frames;
|
||||
break;
|
||||
default:
|
||||
ADD_FAILURE() << "Unexpected temporal id";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_NEAR(50, tl0_frames + tl1_frames, 1);
|
||||
EXPECT_NEAR(50, dropped_frames, 1);
|
||||
}
|
||||
|
||||
TEST_F(ScreenshareLayerTest, TargetBitrateCappedByTL0) {
|
||||
const int kTl0_kbps = 100;
|
||||
const int kTl1_kbps = 1000;
|
||||
layers_->OnRatesUpdated(kTl0_kbps, kTl1_kbps, 5);
|
||||
|
||||
EXPECT_THAT(layers_->OnRatesUpdated(kTl0_kbps, kTl1_kbps, 5),
|
||||
ElementsAre(kTl0_kbps, kTl1_kbps - kTl0_kbps));
|
||||
EXPECT_TRUE(layers_->UpdateConfiguration(&cfg_));
|
||||
|
||||
EXPECT_EQ(static_cast<unsigned int>(
|
||||
ScreenshareLayers::kMaxTL0FpsReduction * kTl0_kbps + 0.5),
|
||||
cfg_.rc_target_bitrate);
|
||||
}
|
||||
|
||||
TEST_F(ScreenshareLayerTest, TargetBitrateCappedByTL1) {
|
||||
const int kTl0_kbps = 100;
|
||||
const int kTl1_kbps = 450;
|
||||
EXPECT_THAT(layers_->OnRatesUpdated(kTl0_kbps, kTl1_kbps, 5),
|
||||
ElementsAre(kTl0_kbps, kTl1_kbps - kTl0_kbps));
|
||||
EXPECT_TRUE(layers_->UpdateConfiguration(&cfg_));
|
||||
|
||||
EXPECT_EQ(static_cast<unsigned int>(
|
||||
kTl1_kbps / ScreenshareLayers::kAcceptableTargetOvershoot),
|
||||
cfg_.rc_target_bitrate);
|
||||
}
|
||||
|
||||
TEST_F(ScreenshareLayerTest, TargetBitrateBelowTL0) {
|
||||
const int kTl0_kbps = 100;
|
||||
const int kTl1_kbps = 100;
|
||||
EXPECT_THAT(layers_->OnRatesUpdated(kTl0_kbps, kTl1_kbps, 5),
|
||||
ElementsAre(kTl0_kbps));
|
||||
EXPECT_TRUE(layers_->UpdateConfiguration(&cfg_));
|
||||
|
||||
EXPECT_EQ(static_cast<uint32_t>(kTl1_kbps), cfg_.rc_target_bitrate);
|
||||
}
|
||||
|
||||
TEST_F(ScreenshareLayerTest, EncoderDrop) {
|
||||
EXPECT_TRUE(RunGracePeriod());
|
||||
SkipUntilTl(0);
|
||||
|
||||
// Size 0 indicates dropped frame.
|
||||
layers_->FrameEncoded(0, kDefaultQp);
|
||||
|
||||
// Re-encode frame (so don't advance timestamp).
|
||||
int flags = EncodeFrame(false);
|
||||
timestamp_ += kTimestampDelta5Fps;
|
||||
EXPECT_FALSE(config_updated_);
|
||||
EXPECT_EQ(kTl0Flags, flags);
|
||||
|
||||
// Next frame should have boosted quality...
|
||||
SkipUntilTl(0);
|
||||
EXPECT_TRUE(config_updated_);
|
||||
EXPECT_LT(cfg_.rc_max_quantizer, static_cast<unsigned int>(kDefaultQp));
|
||||
layers_->FrameEncoded(frame_size_, kDefaultQp);
|
||||
timestamp_ += kTimestampDelta5Fps;
|
||||
|
||||
// ...then back to standard setup.
|
||||
SkipUntilTl(0);
|
||||
layers_->FrameEncoded(frame_size_, kDefaultQp);
|
||||
timestamp_ += kTimestampDelta5Fps;
|
||||
EXPECT_EQ(cfg_.rc_max_quantizer, static_cast<unsigned int>(kDefaultQp));
|
||||
|
||||
// Next drop in TL1.
|
||||
SkipUntilTl(1);
|
||||
layers_->FrameEncoded(0, kDefaultQp);
|
||||
|
||||
// Re-encode frame (so don't advance timestamp).
|
||||
flags = EncodeFrame(false);
|
||||
timestamp_ += kTimestampDelta5Fps;
|
||||
EXPECT_FALSE(config_updated_);
|
||||
EXPECT_EQ(kTl1Flags, flags);
|
||||
|
||||
// Next frame should have boosted QP.
|
||||
SkipUntilTl(1);
|
||||
EXPECT_TRUE(config_updated_);
|
||||
EXPECT_LT(cfg_.rc_max_quantizer, static_cast<unsigned int>(kDefaultQp));
|
||||
layers_->FrameEncoded(frame_size_, kDefaultQp);
|
||||
timestamp_ += kTimestampDelta5Fps;
|
||||
|
||||
// ...and back to normal.
|
||||
SkipUntilTl(1);
|
||||
EXPECT_EQ(cfg_.rc_max_quantizer, static_cast<unsigned int>(kDefaultQp));
|
||||
layers_->FrameEncoded(frame_size_, kDefaultQp);
|
||||
timestamp_ += kTimestampDelta5Fps;
|
||||
}
|
||||
|
||||
TEST_F(ScreenshareLayerTest, RespectsMaxIntervalBetweenFrames) {
|
||||
const int kLowBitrateKbps = 50;
|
||||
const int kLargeFrameSizeBytes = 100000;
|
||||
const uint32_t kStartTimestamp = 1234;
|
||||
|
||||
layers_->OnRatesUpdated(kLowBitrateKbps, kLowBitrateKbps, 5);
|
||||
layers_->UpdateConfiguration(&cfg_);
|
||||
|
||||
EXPECT_EQ(kTl0Flags, VP8EncoderImpl::EncodeFlags(
|
||||
layers_->UpdateLayerConfig(kStartTimestamp)));
|
||||
layers_->FrameEncoded(kLargeFrameSizeBytes, kDefaultQp);
|
||||
|
||||
const uint32_t kTwoSecondsLater =
|
||||
kStartTimestamp + (ScreenshareLayers::kMaxFrameIntervalMs * 90);
|
||||
|
||||
// Sanity check, repayment time should exceed kMaxFrameIntervalMs.
|
||||
ASSERT_GT(kStartTimestamp + 90 * (kLargeFrameSizeBytes * 8) / kLowBitrateKbps,
|
||||
kStartTimestamp + (ScreenshareLayers::kMaxFrameIntervalMs * 90));
|
||||
|
||||
EXPECT_TRUE(layers_->UpdateLayerConfig(kTwoSecondsLater).drop_frame);
|
||||
// More than two seconds has passed since last frame, one should be emitted
|
||||
// even if bitrate target is then exceeded.
|
||||
EXPECT_EQ(kTl0Flags, VP8EncoderImpl::EncodeFlags(
|
||||
layers_->UpdateLayerConfig(kTwoSecondsLater + 90)));
|
||||
}
|
||||
|
||||
TEST_F(ScreenshareLayerTest, UpdatesHistograms) {
|
||||
metrics::Reset();
|
||||
bool trigger_drop = false;
|
||||
bool dropped_frame = false;
|
||||
bool overshoot = false;
|
||||
const int kTl0Qp = 35;
|
||||
const int kTl1Qp = 30;
|
||||
for (int64_t timestamp = 0;
|
||||
timestamp < kTimestampDelta5Fps * 5 * metrics::kMinRunTimeInSeconds;
|
||||
timestamp += kTimestampDelta5Fps) {
|
||||
tl_config_ = layers_->UpdateLayerConfig(timestamp);
|
||||
if (tl_config_.drop_frame) {
|
||||
dropped_frame = true;
|
||||
continue;
|
||||
}
|
||||
int flags = VP8EncoderImpl::EncodeFlags(tl_config_);
|
||||
if (flags != -1)
|
||||
layers_->UpdateConfiguration(&cfg_);
|
||||
|
||||
if (timestamp >= kTimestampDelta5Fps * 5 && !overshoot && flags != -1) {
|
||||
// Simulate one overshoot.
|
||||
layers_->FrameEncoded(0, 0);
|
||||
overshoot = true;
|
||||
flags =
|
||||
VP8EncoderImpl::EncodeFlags(layers_->UpdateLayerConfig(timestamp));
|
||||
}
|
||||
|
||||
if (flags == kTl0Flags) {
|
||||
if (timestamp >= kTimestampDelta5Fps * 20 && !trigger_drop) {
|
||||
// Simulate a too large frame, to cause frame drop.
|
||||
layers_->FrameEncoded(frame_size_ * 10, kTl0Qp);
|
||||
trigger_drop = true;
|
||||
} else {
|
||||
layers_->FrameEncoded(frame_size_, kTl0Qp);
|
||||
}
|
||||
} else if (flags == kTl1Flags || flags == kTl1SyncFlags) {
|
||||
layers_->FrameEncoded(frame_size_, kTl1Qp);
|
||||
} else if (flags == -1) {
|
||||
dropped_frame = true;
|
||||
} else {
|
||||
RTC_NOTREACHED() << "Unexpected flags";
|
||||
}
|
||||
clock_.AdvanceTimeMilliseconds(1000 / 5);
|
||||
}
|
||||
|
||||
EXPECT_TRUE(overshoot);
|
||||
EXPECT_TRUE(dropped_frame);
|
||||
|
||||
layers_.reset(); // Histograms are reported on destruction.
|
||||
|
||||
EXPECT_EQ(1,
|
||||
metrics::NumSamples("WebRTC.Video.Screenshare.Layer0.FrameRate"));
|
||||
EXPECT_EQ(1,
|
||||
metrics::NumSamples("WebRTC.Video.Screenshare.Layer1.FrameRate"));
|
||||
EXPECT_EQ(1, metrics::NumSamples("WebRTC.Video.Screenshare.FramesPerDrop"));
|
||||
EXPECT_EQ(1,
|
||||
metrics::NumSamples("WebRTC.Video.Screenshare.FramesPerOvershoot"));
|
||||
EXPECT_EQ(1, metrics::NumSamples("WebRTC.Video.Screenshare.Layer0.Qp"));
|
||||
EXPECT_EQ(1, metrics::NumSamples("WebRTC.Video.Screenshare.Layer1.Qp"));
|
||||
EXPECT_EQ(
|
||||
1, metrics::NumSamples("WebRTC.Video.Screenshare.Layer0.TargetBitrate"));
|
||||
EXPECT_EQ(
|
||||
1, metrics::NumSamples("WebRTC.Video.Screenshare.Layer1.TargetBitrate"));
|
||||
|
||||
EXPECT_GT(metrics::MinSample("WebRTC.Video.Screenshare.Layer0.FrameRate"), 1);
|
||||
EXPECT_GT(metrics::MinSample("WebRTC.Video.Screenshare.Layer1.FrameRate"), 1);
|
||||
EXPECT_GT(metrics::MinSample("WebRTC.Video.Screenshare.FramesPerDrop"), 1);
|
||||
EXPECT_GT(metrics::MinSample("WebRTC.Video.Screenshare.FramesPerOvershoot"),
|
||||
1);
|
||||
EXPECT_EQ(1,
|
||||
metrics::NumEvents("WebRTC.Video.Screenshare.Layer0.Qp", kTl0Qp));
|
||||
EXPECT_EQ(1,
|
||||
metrics::NumEvents("WebRTC.Video.Screenshare.Layer1.Qp", kTl1Qp));
|
||||
EXPECT_EQ(1,
|
||||
metrics::NumEvents("WebRTC.Video.Screenshare.Layer0.TargetBitrate",
|
||||
kDefaultTl0BitrateKbps));
|
||||
EXPECT_EQ(1,
|
||||
metrics::NumEvents("WebRTC.Video.Screenshare.Layer1.TargetBitrate",
|
||||
kDefaultTl1BitrateKbps));
|
||||
}
|
||||
|
||||
TEST_F(ScreenshareLayerTest, AllowsUpdateConfigBeforeSetRates) {
|
||||
layers_.reset(new ScreenshareLayers(2, 0, &clock_));
|
||||
// New layer instance, OnRatesUpdated() never called.
|
||||
// UpdateConfiguration() call should not cause crash.
|
||||
layers_->UpdateConfiguration(&cfg_);
|
||||
}
|
||||
|
||||
TEST_F(ScreenshareLayerTest, RespectsConfiguredFramerate) {
|
||||
int64_t kTestSpanMs = 2000;
|
||||
int64_t kFrameIntervalsMs = 1000 / kFrameRate;
|
||||
|
||||
uint32_t timestamp = 1234;
|
||||
int num_input_frames = 0;
|
||||
int num_discarded_frames = 0;
|
||||
|
||||
// Send at regular rate - no drops expected.
|
||||
for (int64_t i = 0; i < kTestSpanMs; i += kFrameIntervalsMs) {
|
||||
if (layers_->UpdateLayerConfig(timestamp).drop_frame) {
|
||||
++num_discarded_frames;
|
||||
} else {
|
||||
size_t frame_size_bytes = kDefaultTl0BitrateKbps * kFrameIntervalsMs / 8;
|
||||
layers_->FrameEncoded(frame_size_bytes, kDefaultQp);
|
||||
}
|
||||
timestamp += kFrameIntervalsMs * 90;
|
||||
clock_.AdvanceTimeMilliseconds(kFrameIntervalsMs);
|
||||
++num_input_frames;
|
||||
}
|
||||
EXPECT_EQ(0, num_discarded_frames);
|
||||
|
||||
// Send at twice the configured rate - drop every other frame.
|
||||
num_input_frames = 0;
|
||||
num_discarded_frames = 0;
|
||||
for (int64_t i = 0; i < kTestSpanMs; i += kFrameIntervalsMs / 2) {
|
||||
if (layers_->UpdateLayerConfig(timestamp).drop_frame) {
|
||||
++num_discarded_frames;
|
||||
} else {
|
||||
size_t frame_size_bytes = kDefaultTl0BitrateKbps * kFrameIntervalsMs / 8;
|
||||
layers_->FrameEncoded(frame_size_bytes, kDefaultQp);
|
||||
}
|
||||
timestamp += kFrameIntervalsMs * 90 / 2;
|
||||
clock_.AdvanceTimeMilliseconds(kFrameIntervalsMs / 2);
|
||||
++num_input_frames;
|
||||
}
|
||||
|
||||
// Allow for some rounding errors in the measurements.
|
||||
EXPECT_NEAR(num_discarded_frames, num_input_frames / 2, 2);
|
||||
}
|
||||
|
||||
TEST_F(ScreenshareLayerTest, 2LayersSyncAtOvershootDrop) {
|
||||
// Run grace period so we have existing frames in both TL0 and Tl1.
|
||||
EXPECT_TRUE(RunGracePeriod());
|
||||
|
||||
// Move ahead until we have a sync frame in TL1.
|
||||
EXPECT_EQ(kTl1SyncFlags, SkipUntilTlAndSync(1, rtc::Optional<bool>(true)));
|
||||
ASSERT_TRUE(vp8_info_.layerSync);
|
||||
|
||||
// Simulate overshoot of this frame.
|
||||
layers_->FrameEncoded(0, -1);
|
||||
|
||||
// Reencode, frame config, flags and codec specific info should remain the
|
||||
// same as for the dropped frame.
|
||||
timestamp_ -= kTimestampDelta5Fps; // Undo last timestamp increment.
|
||||
TemporalLayers::FrameConfig new_tl_config =
|
||||
layers_->UpdateLayerConfig(timestamp_);
|
||||
EXPECT_EQ(tl_config_, new_tl_config);
|
||||
|
||||
config_updated_ = layers_->UpdateConfiguration(&cfg_);
|
||||
EXPECT_EQ(kTl1SyncFlags, VP8EncoderImpl::EncodeFlags(tl_config_));
|
||||
|
||||
CodecSpecificInfoVP8 new_vp8_info;
|
||||
layers_->PopulateCodecSpecific(false, tl_config_, &new_vp8_info, timestamp_);
|
||||
EXPECT_TRUE(new_vp8_info.layerSync);
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
163
modules/video_coding/codecs/vp8/simulcast_rate_allocator.cc
Normal file
163
modules/video_coding/codecs/vp8/simulcast_rate_allocator.cc
Normal file
@ -0,0 +1,163 @@
|
||||
/*
|
||||
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "webrtc/modules/video_coding/codecs/vp8/simulcast_rate_allocator.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
|
||||
#include "webrtc/rtc_base/checks.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
SimulcastRateAllocator::SimulcastRateAllocator(
|
||||
const VideoCodec& codec,
|
||||
std::unique_ptr<TemporalLayersFactory> tl_factory)
|
||||
: codec_(codec), tl_factory_(std::move(tl_factory)) {
|
||||
if (tl_factory_.get())
|
||||
tl_factory_->SetListener(this);
|
||||
}
|
||||
|
||||
void SimulcastRateAllocator::OnTemporalLayersCreated(int simulcast_id,
|
||||
TemporalLayers* layers) {
|
||||
RTC_DCHECK(temporal_layers_.find(simulcast_id) == temporal_layers_.end());
|
||||
RTC_DCHECK(layers);
|
||||
temporal_layers_[simulcast_id] = layers;
|
||||
}
|
||||
|
||||
BitrateAllocation SimulcastRateAllocator::GetAllocation(
|
||||
uint32_t total_bitrate_bps,
|
||||
uint32_t framerate) {
|
||||
uint32_t left_to_allocate = total_bitrate_bps;
|
||||
if (codec_.maxBitrate && codec_.maxBitrate * 1000 < left_to_allocate)
|
||||
left_to_allocate = codec_.maxBitrate * 1000;
|
||||
|
||||
BitrateAllocation allocated_bitrates_bps;
|
||||
if (codec_.numberOfSimulcastStreams == 0) {
|
||||
// No simulcast, just set the target as this has been capped already.
|
||||
allocated_bitrates_bps.SetBitrate(
|
||||
0, 0, std::max(codec_.minBitrate * 1000, left_to_allocate));
|
||||
} else {
|
||||
// Always allocate enough bitrate for the minimum bitrate of the first
|
||||
// layer. Suspending below min bitrate is controlled outside the codec
|
||||
// implementation and is not overridden by this.
|
||||
left_to_allocate =
|
||||
std::max(codec_.simulcastStream[0].minBitrate * 1000, left_to_allocate);
|
||||
|
||||
// Begin by allocating bitrate to simulcast streams, putting all bitrate in
|
||||
// temporal layer 0. We'll then distribute this bitrate, across potential
|
||||
// temporal layers, when stream allocation is done.
|
||||
|
||||
// Allocate up to the target bitrate for each simulcast layer.
|
||||
size_t layer = 0;
|
||||
for (; layer < codec_.numberOfSimulcastStreams; ++layer) {
|
||||
const SimulcastStream& stream = codec_.simulcastStream[layer];
|
||||
if (left_to_allocate < stream.minBitrate * 1000)
|
||||
break;
|
||||
uint32_t allocation =
|
||||
std::min(left_to_allocate, stream.targetBitrate * 1000);
|
||||
allocated_bitrates_bps.SetBitrate(layer, 0, allocation);
|
||||
RTC_DCHECK_LE(allocation, left_to_allocate);
|
||||
left_to_allocate -= allocation;
|
||||
}
|
||||
|
||||
// Next, try allocate remaining bitrate, up to max bitrate, in top stream.
|
||||
// TODO(sprang): Allocate up to max bitrate for all layers once we have a
|
||||
// better idea of possible performance implications.
|
||||
if (left_to_allocate > 0) {
|
||||
size_t active_layer = layer - 1;
|
||||
const SimulcastStream& stream = codec_.simulcastStream[active_layer];
|
||||
uint32_t bitrate_bps =
|
||||
allocated_bitrates_bps.GetSpatialLayerSum(active_layer);
|
||||
uint32_t allocation =
|
||||
std::min(left_to_allocate, stream.maxBitrate * 1000 - bitrate_bps);
|
||||
bitrate_bps += allocation;
|
||||
RTC_DCHECK_LE(allocation, left_to_allocate);
|
||||
left_to_allocate -= allocation;
|
||||
allocated_bitrates_bps.SetBitrate(active_layer, 0, bitrate_bps);
|
||||
}
|
||||
}
|
||||
|
||||
const int num_spatial_streams =
|
||||
std::max(1, static_cast<int>(codec_.numberOfSimulcastStreams));
|
||||
|
||||
// Finally, distribute the bitrate for the simulcast streams across the
|
||||
// available temporal layers.
|
||||
for (int simulcast_id = 0; simulcast_id < num_spatial_streams;
|
||||
++simulcast_id) {
|
||||
auto tl_it = temporal_layers_.find(simulcast_id);
|
||||
if (tl_it == temporal_layers_.end())
|
||||
continue; // TODO(sprang): If > 1 SS, assume default TL alloc?
|
||||
|
||||
uint32_t target_bitrate_kbps =
|
||||
allocated_bitrates_bps.GetBitrate(simulcast_id, 0) / 1000;
|
||||
const uint32_t expected_allocated_bitrate_kbps = target_bitrate_kbps;
|
||||
RTC_DCHECK_EQ(
|
||||
target_bitrate_kbps,
|
||||
allocated_bitrates_bps.GetSpatialLayerSum(simulcast_id) / 1000);
|
||||
const int num_temporal_streams = std::max<uint8_t>(
|
||||
1, codec_.numberOfSimulcastStreams == 0
|
||||
? codec_.VP8().numberOfTemporalLayers
|
||||
: codec_.simulcastStream[simulcast_id].numberOfTemporalLayers);
|
||||
|
||||
uint32_t max_bitrate_kbps;
|
||||
// Legacy temporal-layered only screenshare, or simulcast screenshare
|
||||
// with legacy mode for simulcast stream 0.
|
||||
if (codec_.mode == kScreensharing && codec_.targetBitrate > 0 &&
|
||||
((num_spatial_streams == 1 && num_temporal_streams == 2) || // Legacy.
|
||||
(num_spatial_streams > 1 && simulcast_id == 0))) { // Simulcast.
|
||||
// TODO(holmer): This is a "temporary" hack for screensharing, where we
|
||||
// interpret the startBitrate as the encoder target bitrate. This is
|
||||
// to allow for a different max bitrate, so if the codec can't meet
|
||||
// the target we still allow it to overshoot up to the max before dropping
|
||||
// frames. This hack should be improved.
|
||||
int tl0_bitrate = std::min(codec_.targetBitrate, target_bitrate_kbps);
|
||||
max_bitrate_kbps = std::min(codec_.maxBitrate, target_bitrate_kbps);
|
||||
target_bitrate_kbps = tl0_bitrate;
|
||||
} else if (num_spatial_streams == 1) {
|
||||
max_bitrate_kbps = codec_.maxBitrate;
|
||||
} else {
|
||||
max_bitrate_kbps = codec_.simulcastStream[simulcast_id].maxBitrate;
|
||||
}
|
||||
|
||||
std::vector<uint32_t> tl_allocation = tl_it->second->OnRatesUpdated(
|
||||
target_bitrate_kbps, max_bitrate_kbps, framerate);
|
||||
RTC_DCHECK_GT(tl_allocation.size(), 0);
|
||||
RTC_DCHECK_LE(tl_allocation.size(), num_temporal_streams);
|
||||
|
||||
uint64_t tl_allocation_sum_kbps = 0;
|
||||
for (size_t tl_index = 0; tl_index < tl_allocation.size(); ++tl_index) {
|
||||
uint32_t layer_rate_kbps = tl_allocation[tl_index];
|
||||
allocated_bitrates_bps.SetBitrate(simulcast_id, tl_index,
|
||||
layer_rate_kbps * 1000);
|
||||
tl_allocation_sum_kbps += layer_rate_kbps;
|
||||
}
|
||||
RTC_DCHECK_LE(tl_allocation_sum_kbps, expected_allocated_bitrate_kbps);
|
||||
}
|
||||
|
||||
return allocated_bitrates_bps;
|
||||
}
|
||||
|
||||
uint32_t SimulcastRateAllocator::GetPreferredBitrateBps(uint32_t framerate) {
|
||||
// Create a temporary instance without temporal layers, as they may be
|
||||
// stateful, and updating the bitrate to max here can cause side effects.
|
||||
SimulcastRateAllocator temp_allocator(codec_, nullptr);
|
||||
BitrateAllocation allocation =
|
||||
temp_allocator.GetAllocation(codec_.maxBitrate * 1000, framerate);
|
||||
return allocation.get_sum_bps();
|
||||
}
|
||||
|
||||
const VideoCodec& webrtc::SimulcastRateAllocator::GetCodec() const {
|
||||
return codec_;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
51
modules/video_coding/codecs/vp8/simulcast_rate_allocator.h
Normal file
51
modules/video_coding/codecs/vp8/simulcast_rate_allocator.h
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_VIDEO_CODING_CODECS_VP8_SIMULCAST_RATE_ALLOCATOR_H_
|
||||
#define WEBRTC_MODULES_VIDEO_CODING_CODECS_VP8_SIMULCAST_RATE_ALLOCATOR_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
||||
#include "webrtc/api/video_codecs/video_encoder.h"
|
||||
#include "webrtc/common_video/include/video_bitrate_allocator.h"
|
||||
#include "webrtc/modules/video_coding/codecs/vp8/temporal_layers.h"
|
||||
#include "webrtc/rtc_base/constructormagic.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class SimulcastRateAllocator : public VideoBitrateAllocator,
|
||||
public TemporalLayersListener {
|
||||
public:
|
||||
explicit SimulcastRateAllocator(
|
||||
const VideoCodec& codec,
|
||||
std::unique_ptr<TemporalLayersFactory> tl_factory);
|
||||
|
||||
void OnTemporalLayersCreated(int simulcast_id,
|
||||
TemporalLayers* layers) override;
|
||||
|
||||
BitrateAllocation GetAllocation(uint32_t total_bitrate_bps,
|
||||
uint32_t framerate) override;
|
||||
uint32_t GetPreferredBitrateBps(uint32_t framerate) override;
|
||||
const VideoCodec& GetCodec() const;
|
||||
|
||||
private:
|
||||
const VideoCodec codec_;
|
||||
std::map<uint32_t, TemporalLayers*> temporal_layers_;
|
||||
std::unique_ptr<TemporalLayersFactory> tl_factory_;
|
||||
|
||||
RTC_DISALLOW_COPY_AND_ASSIGN(SimulcastRateAllocator);
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_VIDEO_CODING_CODECS_VP8_SIMULCAST_RATE_ALLOCATOR_H_
|
||||
755
modules/video_coding/codecs/vp8/simulcast_test_utility.h
Normal file
755
modules/video_coding/codecs/vp8/simulcast_test_utility.h
Normal file
@ -0,0 +1,755 @@
|
||||
/*
|
||||
* Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_VIDEO_CODING_CODECS_VP8_SIMULCAST_TEST_UTILITY_H_
|
||||
#define WEBRTC_MODULES_VIDEO_CODING_CODECS_VP8_SIMULCAST_TEST_UTILITY_H_
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/api/video/i420_buffer.h"
|
||||
#include "webrtc/api/video/video_frame.h"
|
||||
#include "webrtc/common_video/include/video_frame.h"
|
||||
#include "webrtc/common_video/libyuv/include/webrtc_libyuv.h"
|
||||
#include "webrtc/modules/video_coding/codecs/vp8/include/vp8.h"
|
||||
#include "webrtc/modules/video_coding/codecs/vp8/simulcast_rate_allocator.h"
|
||||
#include "webrtc/modules/video_coding/codecs/vp8/temporal_layers.h"
|
||||
#include "webrtc/modules/video_coding/include/mock/mock_video_codec_interface.h"
|
||||
#include "webrtc/modules/video_coding/include/video_coding_defines.h"
|
||||
#include "webrtc/rtc_base/checks.h"
|
||||
#include "webrtc/test/gtest.h"
|
||||
|
||||
using ::testing::_;
|
||||
using ::testing::AllOf;
|
||||
using ::testing::Field;
|
||||
using ::testing::Return;
|
||||
|
||||
namespace webrtc {
|
||||
namespace testing {
|
||||
|
||||
const int kDefaultWidth = 1280;
|
||||
const int kDefaultHeight = 720;
|
||||
const int kNumberOfSimulcastStreams = 3;
|
||||
const int kColorY = 66;
|
||||
const int kColorU = 22;
|
||||
const int kColorV = 33;
|
||||
const int kMaxBitrates[kNumberOfSimulcastStreams] = {150, 600, 1200};
|
||||
const int kMinBitrates[kNumberOfSimulcastStreams] = {50, 150, 600};
|
||||
const int kTargetBitrates[kNumberOfSimulcastStreams] = {100, 450, 1000};
|
||||
const int kDefaultTemporalLayerProfile[3] = {3, 3, 3};
|
||||
|
||||
template <typename T>
|
||||
void SetExpectedValues3(T value0, T value1, T value2, T* expected_values) {
|
||||
expected_values[0] = value0;
|
||||
expected_values[1] = value1;
|
||||
expected_values[2] = value2;
|
||||
}
|
||||
|
||||
enum PlaneType {
|
||||
kYPlane = 0,
|
||||
kUPlane = 1,
|
||||
kVPlane = 2,
|
||||
kNumOfPlanes = 3,
|
||||
};
|
||||
|
||||
class Vp8TestEncodedImageCallback : public EncodedImageCallback {
|
||||
public:
|
||||
Vp8TestEncodedImageCallback() : picture_id_(-1) {
|
||||
memset(temporal_layer_, -1, sizeof(temporal_layer_));
|
||||
memset(layer_sync_, false, sizeof(layer_sync_));
|
||||
}
|
||||
|
||||
~Vp8TestEncodedImageCallback() {
|
||||
delete[] encoded_key_frame_._buffer;
|
||||
delete[] encoded_frame_._buffer;
|
||||
}
|
||||
|
||||
virtual Result OnEncodedImage(const EncodedImage& encoded_image,
|
||||
const CodecSpecificInfo* codec_specific_info,
|
||||
const RTPFragmentationHeader* fragmentation) {
|
||||
// Only store the base layer.
|
||||
if (codec_specific_info->codecSpecific.VP8.simulcastIdx == 0) {
|
||||
if (encoded_image._frameType == kVideoFrameKey) {
|
||||
delete[] encoded_key_frame_._buffer;
|
||||
encoded_key_frame_._buffer = new uint8_t[encoded_image._size];
|
||||
encoded_key_frame_._size = encoded_image._size;
|
||||
encoded_key_frame_._length = encoded_image._length;
|
||||
encoded_key_frame_._frameType = kVideoFrameKey;
|
||||
encoded_key_frame_._completeFrame = encoded_image._completeFrame;
|
||||
memcpy(encoded_key_frame_._buffer, encoded_image._buffer,
|
||||
encoded_image._length);
|
||||
} else {
|
||||
delete[] encoded_frame_._buffer;
|
||||
encoded_frame_._buffer = new uint8_t[encoded_image._size];
|
||||
encoded_frame_._size = encoded_image._size;
|
||||
encoded_frame_._length = encoded_image._length;
|
||||
memcpy(encoded_frame_._buffer, encoded_image._buffer,
|
||||
encoded_image._length);
|
||||
}
|
||||
}
|
||||
picture_id_ = codec_specific_info->codecSpecific.VP8.pictureId;
|
||||
layer_sync_[codec_specific_info->codecSpecific.VP8.simulcastIdx] =
|
||||
codec_specific_info->codecSpecific.VP8.layerSync;
|
||||
temporal_layer_[codec_specific_info->codecSpecific.VP8.simulcastIdx] =
|
||||
codec_specific_info->codecSpecific.VP8.temporalIdx;
|
||||
return Result(Result::OK, encoded_image._timeStamp);
|
||||
}
|
||||
void GetLastEncodedFrameInfo(int* picture_id,
|
||||
int* temporal_layer,
|
||||
bool* layer_sync,
|
||||
int stream) {
|
||||
*picture_id = picture_id_;
|
||||
*temporal_layer = temporal_layer_[stream];
|
||||
*layer_sync = layer_sync_[stream];
|
||||
}
|
||||
void GetLastEncodedKeyFrame(EncodedImage* encoded_key_frame) {
|
||||
*encoded_key_frame = encoded_key_frame_;
|
||||
}
|
||||
void GetLastEncodedFrame(EncodedImage* encoded_frame) {
|
||||
*encoded_frame = encoded_frame_;
|
||||
}
|
||||
|
||||
private:
|
||||
EncodedImage encoded_key_frame_;
|
||||
EncodedImage encoded_frame_;
|
||||
int picture_id_;
|
||||
int temporal_layer_[kNumberOfSimulcastStreams];
|
||||
bool layer_sync_[kNumberOfSimulcastStreams];
|
||||
};
|
||||
|
||||
class Vp8TestDecodedImageCallback : public DecodedImageCallback {
|
||||
public:
|
||||
Vp8TestDecodedImageCallback() : decoded_frames_(0) {}
|
||||
int32_t Decoded(VideoFrame& decoded_image) override {
|
||||
rtc::scoped_refptr<I420BufferInterface> i420_buffer =
|
||||
decoded_image.video_frame_buffer()->ToI420();
|
||||
for (int i = 0; i < decoded_image.width(); ++i) {
|
||||
EXPECT_NEAR(kColorY, i420_buffer->DataY()[i], 1);
|
||||
}
|
||||
|
||||
// TODO(mikhal): Verify the difference between U,V and the original.
|
||||
for (int i = 0; i < i420_buffer->ChromaWidth(); ++i) {
|
||||
EXPECT_NEAR(kColorU, i420_buffer->DataU()[i], 4);
|
||||
EXPECT_NEAR(kColorV, i420_buffer->DataV()[i], 4);
|
||||
}
|
||||
decoded_frames_++;
|
||||
return 0;
|
||||
}
|
||||
int32_t Decoded(VideoFrame& decoded_image, int64_t decode_time_ms) override {
|
||||
RTC_NOTREACHED();
|
||||
return -1;
|
||||
}
|
||||
void Decoded(VideoFrame& decoded_image,
|
||||
rtc::Optional<int32_t> decode_time_ms,
|
||||
rtc::Optional<uint8_t> qp) override {
|
||||
Decoded(decoded_image);
|
||||
}
|
||||
int DecodedFrames() { return decoded_frames_; }
|
||||
|
||||
private:
|
||||
int decoded_frames_;
|
||||
};
|
||||
|
||||
class TestVp8Simulcast : public ::testing::Test {
|
||||
public:
|
||||
static void SetPlane(uint8_t* data,
|
||||
uint8_t value,
|
||||
int width,
|
||||
int height,
|
||||
int stride) {
|
||||
for (int i = 0; i < height; i++, data += stride) {
|
||||
// Setting allocated area to zero - setting only image size to
|
||||
// requested values - will make it easier to distinguish between image
|
||||
// size and frame size (accounting for stride).
|
||||
memset(data, value, width);
|
||||
memset(data + width, 0, stride - width);
|
||||
}
|
||||
}
|
||||
|
||||
// Fills in an I420Buffer from |plane_colors|.
|
||||
static void CreateImage(const rtc::scoped_refptr<I420Buffer>& buffer,
|
||||
int plane_colors[kNumOfPlanes]) {
|
||||
SetPlane(buffer->MutableDataY(), plane_colors[0], buffer->width(),
|
||||
buffer->height(), buffer->StrideY());
|
||||
|
||||
SetPlane(buffer->MutableDataU(), plane_colors[1], buffer->ChromaWidth(),
|
||||
buffer->ChromaHeight(), buffer->StrideU());
|
||||
|
||||
SetPlane(buffer->MutableDataV(), plane_colors[2], buffer->ChromaWidth(),
|
||||
buffer->ChromaHeight(), buffer->StrideV());
|
||||
}
|
||||
|
||||
static void DefaultSettings(VideoCodec* settings,
|
||||
const int* temporal_layer_profile) {
|
||||
RTC_CHECK(settings);
|
||||
memset(settings, 0, sizeof(VideoCodec));
|
||||
strncpy(settings->plName, "VP8", 4);
|
||||
settings->codecType = kVideoCodecVP8;
|
||||
// 96 to 127 dynamic payload types for video codecs
|
||||
settings->plType = 120;
|
||||
settings->startBitrate = 300;
|
||||
settings->minBitrate = 30;
|
||||
settings->maxBitrate = 0;
|
||||
settings->maxFramerate = 30;
|
||||
settings->width = kDefaultWidth;
|
||||
settings->height = kDefaultHeight;
|
||||
settings->numberOfSimulcastStreams = kNumberOfSimulcastStreams;
|
||||
ASSERT_EQ(3, kNumberOfSimulcastStreams);
|
||||
settings->timing_frame_thresholds = {kDefaultTimingFramesDelayMs,
|
||||
kDefaultOutlierFrameSizePercent};
|
||||
ConfigureStream(kDefaultWidth / 4, kDefaultHeight / 4, kMaxBitrates[0],
|
||||
kMinBitrates[0], kTargetBitrates[0],
|
||||
&settings->simulcastStream[0], temporal_layer_profile[0]);
|
||||
ConfigureStream(kDefaultWidth / 2, kDefaultHeight / 2, kMaxBitrates[1],
|
||||
kMinBitrates[1], kTargetBitrates[1],
|
||||
&settings->simulcastStream[1], temporal_layer_profile[1]);
|
||||
ConfigureStream(kDefaultWidth, kDefaultHeight, kMaxBitrates[2],
|
||||
kMinBitrates[2], kTargetBitrates[2],
|
||||
&settings->simulcastStream[2], temporal_layer_profile[2]);
|
||||
settings->VP8()->resilience = kResilientStream;
|
||||
settings->VP8()->denoisingOn = true;
|
||||
settings->VP8()->errorConcealmentOn = false;
|
||||
settings->VP8()->automaticResizeOn = false;
|
||||
settings->VP8()->frameDroppingOn = true;
|
||||
settings->VP8()->keyFrameInterval = 3000;
|
||||
}
|
||||
|
||||
static void ConfigureStream(int width,
|
||||
int height,
|
||||
int max_bitrate,
|
||||
int min_bitrate,
|
||||
int target_bitrate,
|
||||
SimulcastStream* stream,
|
||||
int num_temporal_layers) {
|
||||
assert(stream);
|
||||
stream->width = width;
|
||||
stream->height = height;
|
||||
stream->maxBitrate = max_bitrate;
|
||||
stream->minBitrate = min_bitrate;
|
||||
stream->targetBitrate = target_bitrate;
|
||||
stream->numberOfTemporalLayers = num_temporal_layers;
|
||||
stream->qpMax = 45;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual VP8Encoder* CreateEncoder() = 0;
|
||||
virtual VP8Decoder* CreateDecoder() = 0;
|
||||
|
||||
void SetUp() override {
|
||||
encoder_.reset(CreateEncoder());
|
||||
decoder_.reset(CreateDecoder());
|
||||
SetUpCodec(kDefaultTemporalLayerProfile);
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
encoder_->Release();
|
||||
decoder_->Release();
|
||||
encoder_.reset();
|
||||
decoder_.reset();
|
||||
}
|
||||
|
||||
void SetUpCodec(const int* temporal_layer_profile) {
|
||||
encoder_->RegisterEncodeCompleteCallback(&encoder_callback_);
|
||||
decoder_->RegisterDecodeCompleteCallback(&decoder_callback_);
|
||||
DefaultSettings(&settings_, temporal_layer_profile);
|
||||
SetUpRateAllocator();
|
||||
EXPECT_EQ(0, encoder_->InitEncode(&settings_, 1, 1200));
|
||||
EXPECT_EQ(0, decoder_->InitDecode(&settings_, 1));
|
||||
input_buffer_ = I420Buffer::Create(kDefaultWidth, kDefaultHeight);
|
||||
input_buffer_->InitializeData();
|
||||
input_frame_.reset(
|
||||
new VideoFrame(input_buffer_, 0, 0, webrtc::kVideoRotation_0));
|
||||
}
|
||||
|
||||
void SetUpRateAllocator() {
|
||||
TemporalLayersFactory* tl_factory = new TemporalLayersFactory();
|
||||
rate_allocator_.reset(new SimulcastRateAllocator(
|
||||
settings_, std::unique_ptr<TemporalLayersFactory>(tl_factory)));
|
||||
settings_.VP8()->tl_factory = tl_factory;
|
||||
}
|
||||
|
||||
void SetRates(uint32_t bitrate_kbps, uint32_t fps) {
|
||||
encoder_->SetRateAllocation(
|
||||
rate_allocator_->GetAllocation(bitrate_kbps * 1000, fps), fps);
|
||||
}
|
||||
|
||||
void ExpectStreams(FrameType frame_type, int expected_video_streams) {
|
||||
ASSERT_GE(expected_video_streams, 0);
|
||||
ASSERT_LE(expected_video_streams, kNumberOfSimulcastStreams);
|
||||
if (expected_video_streams >= 1) {
|
||||
EXPECT_CALL(
|
||||
encoder_callback_,
|
||||
OnEncodedImage(
|
||||
AllOf(Field(&EncodedImage::_frameType, frame_type),
|
||||
Field(&EncodedImage::_encodedWidth, kDefaultWidth / 4),
|
||||
Field(&EncodedImage::_encodedHeight, kDefaultHeight / 4)),
|
||||
_, _))
|
||||
.Times(1)
|
||||
.WillRepeatedly(Return(EncodedImageCallback::Result(
|
||||
EncodedImageCallback::Result::OK, 0)));
|
||||
}
|
||||
if (expected_video_streams >= 2) {
|
||||
EXPECT_CALL(
|
||||
encoder_callback_,
|
||||
OnEncodedImage(
|
||||
AllOf(Field(&EncodedImage::_frameType, frame_type),
|
||||
Field(&EncodedImage::_encodedWidth, kDefaultWidth / 2),
|
||||
Field(&EncodedImage::_encodedHeight, kDefaultHeight / 2)),
|
||||
_, _))
|
||||
.Times(1)
|
||||
.WillRepeatedly(Return(EncodedImageCallback::Result(
|
||||
EncodedImageCallback::Result::OK, 0)));
|
||||
}
|
||||
if (expected_video_streams >= 3) {
|
||||
EXPECT_CALL(
|
||||
encoder_callback_,
|
||||
OnEncodedImage(
|
||||
AllOf(Field(&EncodedImage::_frameType, frame_type),
|
||||
Field(&EncodedImage::_encodedWidth, kDefaultWidth),
|
||||
Field(&EncodedImage::_encodedHeight, kDefaultHeight)),
|
||||
_, _))
|
||||
.Times(1)
|
||||
.WillRepeatedly(Return(EncodedImageCallback::Result(
|
||||
EncodedImageCallback::Result::OK, 0)));
|
||||
}
|
||||
}
|
||||
|
||||
void VerifyTemporalIdxAndSyncForAllSpatialLayers(
|
||||
Vp8TestEncodedImageCallback* encoder_callback,
|
||||
const int* expected_temporal_idx,
|
||||
const bool* expected_layer_sync,
|
||||
int num_spatial_layers) {
|
||||
int picture_id = -1;
|
||||
int temporal_layer = -1;
|
||||
bool layer_sync = false;
|
||||
for (int i = 0; i < num_spatial_layers; i++) {
|
||||
encoder_callback->GetLastEncodedFrameInfo(&picture_id, &temporal_layer,
|
||||
&layer_sync, i);
|
||||
EXPECT_EQ(expected_temporal_idx[i], temporal_layer);
|
||||
EXPECT_EQ(expected_layer_sync[i], layer_sync);
|
||||
}
|
||||
}
|
||||
|
||||
// We currently expect all active streams to generate a key frame even though
|
||||
// a key frame was only requested for some of them.
|
||||
void TestKeyFrameRequestsOnAllStreams() {
|
||||
SetRates(kMaxBitrates[2], 30); // To get all three streams.
|
||||
std::vector<FrameType> frame_types(kNumberOfSimulcastStreams,
|
||||
kVideoFrameDelta);
|
||||
ExpectStreams(kVideoFrameKey, kNumberOfSimulcastStreams);
|
||||
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL, &frame_types));
|
||||
|
||||
ExpectStreams(kVideoFrameDelta, kNumberOfSimulcastStreams);
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
|
||||
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL, &frame_types));
|
||||
|
||||
frame_types[0] = kVideoFrameKey;
|
||||
ExpectStreams(kVideoFrameKey, kNumberOfSimulcastStreams);
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
|
||||
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL, &frame_types));
|
||||
|
||||
std::fill(frame_types.begin(), frame_types.end(), kVideoFrameDelta);
|
||||
frame_types[1] = kVideoFrameKey;
|
||||
ExpectStreams(kVideoFrameKey, kNumberOfSimulcastStreams);
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
|
||||
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL, &frame_types));
|
||||
|
||||
std::fill(frame_types.begin(), frame_types.end(), kVideoFrameDelta);
|
||||
frame_types[2] = kVideoFrameKey;
|
||||
ExpectStreams(kVideoFrameKey, kNumberOfSimulcastStreams);
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
|
||||
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL, &frame_types));
|
||||
|
||||
std::fill(frame_types.begin(), frame_types.end(), kVideoFrameDelta);
|
||||
ExpectStreams(kVideoFrameDelta, kNumberOfSimulcastStreams);
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
|
||||
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL, &frame_types));
|
||||
}
|
||||
|
||||
void TestPaddingAllStreams() {
|
||||
// We should always encode the base layer.
|
||||
SetRates(kMinBitrates[0] - 1, 30);
|
||||
std::vector<FrameType> frame_types(kNumberOfSimulcastStreams,
|
||||
kVideoFrameDelta);
|
||||
ExpectStreams(kVideoFrameKey, 1);
|
||||
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL, &frame_types));
|
||||
|
||||
ExpectStreams(kVideoFrameDelta, 1);
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
|
||||
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL, &frame_types));
|
||||
}
|
||||
|
||||
void TestPaddingTwoStreams() {
|
||||
// We have just enough to get only the first stream and padding for two.
|
||||
SetRates(kMinBitrates[0], 30);
|
||||
std::vector<FrameType> frame_types(kNumberOfSimulcastStreams,
|
||||
kVideoFrameDelta);
|
||||
ExpectStreams(kVideoFrameKey, 1);
|
||||
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL, &frame_types));
|
||||
|
||||
ExpectStreams(kVideoFrameDelta, 1);
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
|
||||
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL, &frame_types));
|
||||
}
|
||||
|
||||
void TestPaddingTwoStreamsOneMaxedOut() {
|
||||
// We are just below limit of sending second stream, so we should get
|
||||
// the first stream maxed out (at |maxBitrate|), and padding for two.
|
||||
SetRates(kTargetBitrates[0] + kMinBitrates[1] - 1, 30);
|
||||
std::vector<FrameType> frame_types(kNumberOfSimulcastStreams,
|
||||
kVideoFrameDelta);
|
||||
ExpectStreams(kVideoFrameKey, 1);
|
||||
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL, &frame_types));
|
||||
|
||||
ExpectStreams(kVideoFrameDelta, 1);
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
|
||||
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL, &frame_types));
|
||||
}
|
||||
|
||||
void TestPaddingOneStream() {
|
||||
// We have just enough to send two streams, so padding for one stream.
|
||||
SetRates(kTargetBitrates[0] + kMinBitrates[1], 30);
|
||||
std::vector<FrameType> frame_types(kNumberOfSimulcastStreams,
|
||||
kVideoFrameDelta);
|
||||
ExpectStreams(kVideoFrameKey, 2);
|
||||
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL, &frame_types));
|
||||
|
||||
ExpectStreams(kVideoFrameDelta, 2);
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
|
||||
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL, &frame_types));
|
||||
}
|
||||
|
||||
void TestPaddingOneStreamTwoMaxedOut() {
|
||||
// We are just below limit of sending third stream, so we should get
|
||||
// first stream's rate maxed out at |targetBitrate|, second at |maxBitrate|.
|
||||
SetRates(kTargetBitrates[0] + kTargetBitrates[1] + kMinBitrates[2] - 1, 30);
|
||||
std::vector<FrameType> frame_types(kNumberOfSimulcastStreams,
|
||||
kVideoFrameDelta);
|
||||
ExpectStreams(kVideoFrameKey, 2);
|
||||
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL, &frame_types));
|
||||
|
||||
ExpectStreams(kVideoFrameDelta, 2);
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
|
||||
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL, &frame_types));
|
||||
}
|
||||
|
||||
void TestSendAllStreams() {
|
||||
// We have just enough to send all streams.
|
||||
SetRates(kTargetBitrates[0] + kTargetBitrates[1] + kMinBitrates[2], 30);
|
||||
std::vector<FrameType> frame_types(kNumberOfSimulcastStreams,
|
||||
kVideoFrameDelta);
|
||||
ExpectStreams(kVideoFrameKey, 3);
|
||||
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL, &frame_types));
|
||||
|
||||
ExpectStreams(kVideoFrameDelta, 3);
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
|
||||
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL, &frame_types));
|
||||
}
|
||||
|
||||
void TestDisablingStreams() {
|
||||
// We should get three media streams.
|
||||
SetRates(kMaxBitrates[0] + kMaxBitrates[1] + kMaxBitrates[2], 30);
|
||||
std::vector<FrameType> frame_types(kNumberOfSimulcastStreams,
|
||||
kVideoFrameDelta);
|
||||
ExpectStreams(kVideoFrameKey, 3);
|
||||
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL, &frame_types));
|
||||
|
||||
ExpectStreams(kVideoFrameDelta, 3);
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
|
||||
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL, &frame_types));
|
||||
|
||||
// We should only get two streams and padding for one.
|
||||
SetRates(kTargetBitrates[0] + kTargetBitrates[1] + kMinBitrates[2] / 2, 30);
|
||||
ExpectStreams(kVideoFrameDelta, 2);
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
|
||||
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL, &frame_types));
|
||||
|
||||
// We should only get the first stream and padding for two.
|
||||
SetRates(kTargetBitrates[0] + kMinBitrates[1] / 2, 30);
|
||||
ExpectStreams(kVideoFrameDelta, 1);
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
|
||||
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL, &frame_types));
|
||||
|
||||
// We don't have enough bitrate for the thumbnail stream, but we should get
|
||||
// it anyway with current configuration.
|
||||
SetRates(kTargetBitrates[0] - 1, 30);
|
||||
ExpectStreams(kVideoFrameDelta, 1);
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
|
||||
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL, &frame_types));
|
||||
|
||||
// We should only get two streams and padding for one.
|
||||
SetRates(kTargetBitrates[0] + kTargetBitrates[1] + kMinBitrates[2] / 2, 30);
|
||||
// We get a key frame because a new stream is being enabled.
|
||||
ExpectStreams(kVideoFrameKey, 2);
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
|
||||
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL, &frame_types));
|
||||
|
||||
// We should get all three streams.
|
||||
SetRates(kTargetBitrates[0] + kTargetBitrates[1] + kTargetBitrates[2], 30);
|
||||
// We get a key frame because a new stream is being enabled.
|
||||
ExpectStreams(kVideoFrameKey, 3);
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
|
||||
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL, &frame_types));
|
||||
}
|
||||
|
||||
void SwitchingToOneStream(int width, int height) {
|
||||
// Disable all streams except the last and set the bitrate of the last to
|
||||
// 100 kbps. This verifies the way GTP switches to screenshare mode.
|
||||
settings_.VP8()->numberOfTemporalLayers = 1;
|
||||
settings_.maxBitrate = 100;
|
||||
settings_.startBitrate = 100;
|
||||
settings_.width = width;
|
||||
settings_.height = height;
|
||||
for (int i = 0; i < settings_.numberOfSimulcastStreams - 1; ++i) {
|
||||
settings_.simulcastStream[i].maxBitrate = 0;
|
||||
settings_.simulcastStream[i].width = settings_.width;
|
||||
settings_.simulcastStream[i].height = settings_.height;
|
||||
}
|
||||
// Setting input image to new resolution.
|
||||
input_buffer_ = I420Buffer::Create(settings_.width, settings_.height);
|
||||
input_buffer_->InitializeData();
|
||||
|
||||
input_frame_.reset(
|
||||
new VideoFrame(input_buffer_, 0, 0, webrtc::kVideoRotation_0));
|
||||
|
||||
// The for loop above did not set the bitrate of the highest layer.
|
||||
settings_.simulcastStream[settings_.numberOfSimulcastStreams - 1]
|
||||
.maxBitrate = 0;
|
||||
// The highest layer has to correspond to the non-simulcast resolution.
|
||||
settings_.simulcastStream[settings_.numberOfSimulcastStreams - 1].width =
|
||||
settings_.width;
|
||||
settings_.simulcastStream[settings_.numberOfSimulcastStreams - 1].height =
|
||||
settings_.height;
|
||||
SetUpRateAllocator();
|
||||
EXPECT_EQ(0, encoder_->InitEncode(&settings_, 1, 1200));
|
||||
|
||||
// Encode one frame and verify.
|
||||
SetRates(kMaxBitrates[0] + kMaxBitrates[1], 30);
|
||||
std::vector<FrameType> frame_types(kNumberOfSimulcastStreams,
|
||||
kVideoFrameDelta);
|
||||
EXPECT_CALL(
|
||||
encoder_callback_,
|
||||
OnEncodedImage(AllOf(Field(&EncodedImage::_frameType, kVideoFrameKey),
|
||||
Field(&EncodedImage::_encodedWidth, width),
|
||||
Field(&EncodedImage::_encodedHeight, height)),
|
||||
_, _))
|
||||
.Times(1)
|
||||
.WillRepeatedly(Return(
|
||||
EncodedImageCallback::Result(EncodedImageCallback::Result::OK, 0)));
|
||||
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL, &frame_types));
|
||||
|
||||
// Switch back.
|
||||
DefaultSettings(&settings_, kDefaultTemporalLayerProfile);
|
||||
// Start at the lowest bitrate for enabling base stream.
|
||||
settings_.startBitrate = kMinBitrates[0];
|
||||
SetUpRateAllocator();
|
||||
EXPECT_EQ(0, encoder_->InitEncode(&settings_, 1, 1200));
|
||||
SetRates(settings_.startBitrate, 30);
|
||||
ExpectStreams(kVideoFrameKey, 1);
|
||||
// Resize |input_frame_| to the new resolution.
|
||||
input_buffer_ = I420Buffer::Create(settings_.width, settings_.height);
|
||||
input_buffer_->InitializeData();
|
||||
input_frame_.reset(
|
||||
new VideoFrame(input_buffer_, 0, 0, webrtc::kVideoRotation_0));
|
||||
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL, &frame_types));
|
||||
}
|
||||
|
||||
void TestSwitchingToOneStream() { SwitchingToOneStream(1024, 768); }
|
||||
|
||||
void TestSwitchingToOneOddStream() { SwitchingToOneStream(1023, 769); }
|
||||
|
||||
void TestSwitchingToOneSmallStream() { SwitchingToOneStream(4, 4); }
|
||||
|
||||
// Test the layer pattern and sync flag for various spatial-temporal patterns.
|
||||
// 3-3-3 pattern: 3 temporal layers for all spatial streams, so same
|
||||
// temporal_layer id and layer_sync is expected for all streams.
|
||||
void TestSaptioTemporalLayers333PatternEncoder() {
|
||||
Vp8TestEncodedImageCallback encoder_callback;
|
||||
encoder_->RegisterEncodeCompleteCallback(&encoder_callback);
|
||||
SetRates(kMaxBitrates[2], 30); // To get all three streams.
|
||||
|
||||
int expected_temporal_idx[3] = {-1, -1, -1};
|
||||
bool expected_layer_sync[3] = {false, false, false};
|
||||
|
||||
// First frame: #0.
|
||||
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL, NULL));
|
||||
SetExpectedValues3<int>(0, 0, 0, expected_temporal_idx);
|
||||
SetExpectedValues3<bool>(true, true, true, expected_layer_sync);
|
||||
VerifyTemporalIdxAndSyncForAllSpatialLayers(
|
||||
&encoder_callback, expected_temporal_idx, expected_layer_sync, 3);
|
||||
|
||||
// Next frame: #1.
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
|
||||
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL, NULL));
|
||||
SetExpectedValues3<int>(2, 2, 2, expected_temporal_idx);
|
||||
SetExpectedValues3<bool>(true, true, true, expected_layer_sync);
|
||||
VerifyTemporalIdxAndSyncForAllSpatialLayers(
|
||||
&encoder_callback, expected_temporal_idx, expected_layer_sync, 3);
|
||||
|
||||
// Next frame: #2.
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
|
||||
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL, NULL));
|
||||
SetExpectedValues3<int>(1, 1, 1, expected_temporal_idx);
|
||||
SetExpectedValues3<bool>(true, true, true, expected_layer_sync);
|
||||
VerifyTemporalIdxAndSyncForAllSpatialLayers(
|
||||
&encoder_callback, expected_temporal_idx, expected_layer_sync, 3);
|
||||
|
||||
// Next frame: #3.
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
|
||||
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL, NULL));
|
||||
SetExpectedValues3<int>(2, 2, 2, expected_temporal_idx);
|
||||
SetExpectedValues3<bool>(false, false, false, expected_layer_sync);
|
||||
VerifyTemporalIdxAndSyncForAllSpatialLayers(
|
||||
&encoder_callback, expected_temporal_idx, expected_layer_sync, 3);
|
||||
|
||||
// Next frame: #4.
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
|
||||
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL, NULL));
|
||||
SetExpectedValues3<int>(0, 0, 0, expected_temporal_idx);
|
||||
SetExpectedValues3<bool>(false, false, false, expected_layer_sync);
|
||||
VerifyTemporalIdxAndSyncForAllSpatialLayers(
|
||||
&encoder_callback, expected_temporal_idx, expected_layer_sync, 3);
|
||||
|
||||
// Next frame: #5.
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
|
||||
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL, NULL));
|
||||
SetExpectedValues3<int>(2, 2, 2, expected_temporal_idx);
|
||||
SetExpectedValues3<bool>(false, false, false, expected_layer_sync);
|
||||
VerifyTemporalIdxAndSyncForAllSpatialLayers(
|
||||
&encoder_callback, expected_temporal_idx, expected_layer_sync, 3);
|
||||
}
|
||||
|
||||
// Test the layer pattern and sync flag for various spatial-temporal patterns.
|
||||
// 3-2-1 pattern: 3 temporal layers for lowest resolution, 2 for middle, and
|
||||
// 1 temporal layer for highest resolution.
|
||||
// For this profile, we expect the temporal index pattern to be:
|
||||
// 1st stream: 0, 2, 1, 2, ....
|
||||
// 2nd stream: 0, 1, 0, 1, ...
|
||||
// 3rd stream: -1, -1, -1, -1, ....
|
||||
// Regarding the 3rd stream, note that a stream/encoder with 1 temporal layer
|
||||
// should always have temporal layer idx set to kNoTemporalIdx = -1.
|
||||
// Since CodecSpecificInfoVP8.temporalIdx is uint8_t, this will wrap to 255.
|
||||
// TODO(marpan): Although this seems safe for now, we should fix this.
|
||||
void TestSpatioTemporalLayers321PatternEncoder() {
|
||||
int temporal_layer_profile[3] = {3, 2, 1};
|
||||
SetUpCodec(temporal_layer_profile);
|
||||
Vp8TestEncodedImageCallback encoder_callback;
|
||||
encoder_->RegisterEncodeCompleteCallback(&encoder_callback);
|
||||
SetRates(kMaxBitrates[2], 30); // To get all three streams.
|
||||
|
||||
int expected_temporal_idx[3] = {-1, -1, -1};
|
||||
bool expected_layer_sync[3] = {false, false, false};
|
||||
|
||||
// First frame: #0.
|
||||
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL, NULL));
|
||||
SetExpectedValues3<int>(0, 0, 255, expected_temporal_idx);
|
||||
SetExpectedValues3<bool>(true, true, false, expected_layer_sync);
|
||||
VerifyTemporalIdxAndSyncForAllSpatialLayers(
|
||||
&encoder_callback, expected_temporal_idx, expected_layer_sync, 3);
|
||||
|
||||
// Next frame: #1.
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
|
||||
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL, NULL));
|
||||
SetExpectedValues3<int>(2, 1, 255, expected_temporal_idx);
|
||||
SetExpectedValues3<bool>(true, true, false, expected_layer_sync);
|
||||
VerifyTemporalIdxAndSyncForAllSpatialLayers(
|
||||
&encoder_callback, expected_temporal_idx, expected_layer_sync, 3);
|
||||
|
||||
// Next frame: #2.
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
|
||||
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL, NULL));
|
||||
SetExpectedValues3<int>(1, 0, 255, expected_temporal_idx);
|
||||
SetExpectedValues3<bool>(true, false, false, expected_layer_sync);
|
||||
VerifyTemporalIdxAndSyncForAllSpatialLayers(
|
||||
&encoder_callback, expected_temporal_idx, expected_layer_sync, 3);
|
||||
|
||||
// Next frame: #3.
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
|
||||
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL, NULL));
|
||||
SetExpectedValues3<int>(2, 1, 255, expected_temporal_idx);
|
||||
SetExpectedValues3<bool>(false, false, false, expected_layer_sync);
|
||||
VerifyTemporalIdxAndSyncForAllSpatialLayers(
|
||||
&encoder_callback, expected_temporal_idx, expected_layer_sync, 3);
|
||||
|
||||
// Next frame: #4.
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
|
||||
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL, NULL));
|
||||
SetExpectedValues3<int>(0, 0, 255, expected_temporal_idx);
|
||||
SetExpectedValues3<bool>(false, false, false, expected_layer_sync);
|
||||
VerifyTemporalIdxAndSyncForAllSpatialLayers(
|
||||
&encoder_callback, expected_temporal_idx, expected_layer_sync, 3);
|
||||
|
||||
// Next frame: #5.
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
|
||||
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL, NULL));
|
||||
SetExpectedValues3<int>(2, 1, 255, expected_temporal_idx);
|
||||
SetExpectedValues3<bool>(false, false, false, expected_layer_sync);
|
||||
VerifyTemporalIdxAndSyncForAllSpatialLayers(
|
||||
&encoder_callback, expected_temporal_idx, expected_layer_sync, 3);
|
||||
}
|
||||
|
||||
void TestStrideEncodeDecode() {
|
||||
Vp8TestEncodedImageCallback encoder_callback;
|
||||
Vp8TestDecodedImageCallback decoder_callback;
|
||||
encoder_->RegisterEncodeCompleteCallback(&encoder_callback);
|
||||
decoder_->RegisterDecodeCompleteCallback(&decoder_callback);
|
||||
|
||||
SetRates(kMaxBitrates[2], 30); // To get all three streams.
|
||||
// Setting two (possibly) problematic use cases for stride:
|
||||
// 1. stride > width 2. stride_y != stride_uv/2
|
||||
int stride_y = kDefaultWidth + 20;
|
||||
int stride_uv = ((kDefaultWidth + 1) / 2) + 5;
|
||||
input_buffer_ = I420Buffer::Create(kDefaultWidth, kDefaultHeight, stride_y,
|
||||
stride_uv, stride_uv);
|
||||
input_frame_.reset(
|
||||
new VideoFrame(input_buffer_, 0, 0, webrtc::kVideoRotation_0));
|
||||
|
||||
// Set color.
|
||||
int plane_offset[kNumOfPlanes];
|
||||
plane_offset[kYPlane] = kColorY;
|
||||
plane_offset[kUPlane] = kColorU;
|
||||
plane_offset[kVPlane] = kColorV;
|
||||
CreateImage(input_buffer_, plane_offset);
|
||||
|
||||
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL, NULL));
|
||||
|
||||
// Change color.
|
||||
plane_offset[kYPlane] += 1;
|
||||
plane_offset[kUPlane] += 1;
|
||||
plane_offset[kVPlane] += 1;
|
||||
CreateImage(input_buffer_, plane_offset);
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
|
||||
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL, NULL));
|
||||
|
||||
EncodedImage encoded_frame;
|
||||
// Only encoding one frame - so will be a key frame.
|
||||
encoder_callback.GetLastEncodedKeyFrame(&encoded_frame);
|
||||
EXPECT_EQ(0, decoder_->Decode(encoded_frame, false, NULL));
|
||||
encoder_callback.GetLastEncodedFrame(&encoded_frame);
|
||||
decoder_->Decode(encoded_frame, false, NULL);
|
||||
EXPECT_EQ(2, decoder_callback.DecodedFrames());
|
||||
}
|
||||
|
||||
std::unique_ptr<VP8Encoder> encoder_;
|
||||
MockEncodedImageCallback encoder_callback_;
|
||||
std::unique_ptr<VP8Decoder> decoder_;
|
||||
MockDecodedImageCallback decoder_callback_;
|
||||
VideoCodec settings_;
|
||||
rtc::scoped_refptr<I420Buffer> input_buffer_;
|
||||
std::unique_ptr<VideoFrame> input_frame_;
|
||||
std::unique_ptr<SimulcastRateAllocator> rate_allocator_;
|
||||
};
|
||||
|
||||
} // namespace testing
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_VIDEO_CODING_CODECS_VP8_SIMULCAST_TEST_UTILITY_H_
|
||||
78
modules/video_coding/codecs/vp8/simulcast_unittest.cc
Normal file
78
modules/video_coding/codecs/vp8/simulcast_unittest.cc
Normal file
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "webrtc/modules/video_coding/codecs/vp8/simulcast_test_utility.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace testing {
|
||||
|
||||
class TestVp8Impl : public TestVp8Simulcast {
|
||||
protected:
|
||||
VP8Encoder* CreateEncoder() override { return VP8Encoder::Create(); }
|
||||
VP8Decoder* CreateDecoder() override { return VP8Decoder::Create(); }
|
||||
};
|
||||
|
||||
TEST_F(TestVp8Impl, TestKeyFrameRequestsOnAllStreams) {
|
||||
TestVp8Simulcast::TestKeyFrameRequestsOnAllStreams();
|
||||
}
|
||||
|
||||
TEST_F(TestVp8Impl, TestPaddingAllStreams) {
|
||||
TestVp8Simulcast::TestPaddingAllStreams();
|
||||
}
|
||||
|
||||
TEST_F(TestVp8Impl, TestPaddingTwoStreams) {
|
||||
TestVp8Simulcast::TestPaddingTwoStreams();
|
||||
}
|
||||
|
||||
TEST_F(TestVp8Impl, TestPaddingTwoStreamsOneMaxedOut) {
|
||||
TestVp8Simulcast::TestPaddingTwoStreamsOneMaxedOut();
|
||||
}
|
||||
|
||||
TEST_F(TestVp8Impl, TestPaddingOneStream) {
|
||||
TestVp8Simulcast::TestPaddingOneStream();
|
||||
}
|
||||
|
||||
TEST_F(TestVp8Impl, TestPaddingOneStreamTwoMaxedOut) {
|
||||
TestVp8Simulcast::TestPaddingOneStreamTwoMaxedOut();
|
||||
}
|
||||
|
||||
TEST_F(TestVp8Impl, TestSendAllStreams) {
|
||||
TestVp8Simulcast::TestSendAllStreams();
|
||||
}
|
||||
|
||||
TEST_F(TestVp8Impl, TestDisablingStreams) {
|
||||
TestVp8Simulcast::TestDisablingStreams();
|
||||
}
|
||||
|
||||
TEST_F(TestVp8Impl, TestSwitchingToOneStream) {
|
||||
TestVp8Simulcast::TestSwitchingToOneStream();
|
||||
}
|
||||
|
||||
TEST_F(TestVp8Impl, TestSwitchingToOneOddStream) {
|
||||
TestVp8Simulcast::TestSwitchingToOneOddStream();
|
||||
}
|
||||
|
||||
TEST_F(TestVp8Impl, TestSwitchingToOneSmallStream) {
|
||||
TestVp8Simulcast::TestSwitchingToOneSmallStream();
|
||||
}
|
||||
|
||||
TEST_F(TestVp8Impl, TestSaptioTemporalLayers333PatternEncoder) {
|
||||
TestVp8Simulcast::TestSaptioTemporalLayers333PatternEncoder();
|
||||
}
|
||||
|
||||
TEST_F(TestVp8Impl, TestSpatioTemporalLayers321PatternEncoder) {
|
||||
TestVp8Simulcast::TestSpatioTemporalLayers321PatternEncoder();
|
||||
}
|
||||
|
||||
TEST_F(TestVp8Impl, TestStrideEncodeDecode) {
|
||||
TestVp8Simulcast::TestStrideEncodeDecode();
|
||||
}
|
||||
} // namespace testing
|
||||
} // namespace webrtc
|
||||
151
modules/video_coding/codecs/vp8/temporal_layers.h
Normal file
151
modules/video_coding/codecs/vp8/temporal_layers.h
Normal file
@ -0,0 +1,151 @@
|
||||
/* Copyright (c) 2011 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
/*
|
||||
* This file defines the interface for doing temporal layers with VP8.
|
||||
*/
|
||||
#ifndef WEBRTC_MODULES_VIDEO_CODING_CODECS_VP8_TEMPORAL_LAYERS_H_
|
||||
#define WEBRTC_MODULES_VIDEO_CODING_CODECS_VP8_TEMPORAL_LAYERS_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/typedefs.h"
|
||||
|
||||
struct vpx_codec_enc_cfg;
|
||||
typedef struct vpx_codec_enc_cfg vpx_codec_enc_cfg_t;
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
struct CodecSpecificInfoVP8;
|
||||
|
||||
class TemporalLayers {
|
||||
public:
|
||||
enum BufferFlags {
|
||||
kNone = 0,
|
||||
kReference = 1,
|
||||
kUpdate = 2,
|
||||
kReferenceAndUpdate = kReference | kUpdate,
|
||||
};
|
||||
enum FreezeEntropy { kFreezeEntropy };
|
||||
|
||||
struct FrameConfig {
|
||||
FrameConfig();
|
||||
|
||||
FrameConfig(BufferFlags last, BufferFlags golden, BufferFlags arf);
|
||||
FrameConfig(BufferFlags last,
|
||||
BufferFlags golden,
|
||||
BufferFlags arf,
|
||||
FreezeEntropy);
|
||||
|
||||
bool drop_frame;
|
||||
BufferFlags last_buffer_flags;
|
||||
BufferFlags golden_buffer_flags;
|
||||
BufferFlags arf_buffer_flags;
|
||||
|
||||
// The encoder layer ID is used to utilize the correct bitrate allocator
|
||||
// inside the encoder. It does not control references nor determine which
|
||||
// "actual" temporal layer this is. The packetizer temporal index determines
|
||||
// which layer the encoded frame should be packetized into.
|
||||
// Normally these are the same, but current temporal-layer strategies for
|
||||
// screenshare use one bitrate allocator for all layers, but attempt to
|
||||
// packetize / utilize references to split a stream into multiple layers,
|
||||
// with different quantizer settings, to hit target bitrate.
|
||||
// TODO(pbos): Screenshare layers are being reconsidered at the time of
|
||||
// writing, we might be able to remove this distinction, and have a temporal
|
||||
// layer imply both (the normal case).
|
||||
int encoder_layer_id;
|
||||
int packetizer_temporal_idx;
|
||||
|
||||
bool layer_sync;
|
||||
|
||||
bool freeze_entropy;
|
||||
|
||||
bool operator==(const FrameConfig& o) const {
|
||||
return drop_frame == o.drop_frame &&
|
||||
last_buffer_flags == o.last_buffer_flags &&
|
||||
golden_buffer_flags == o.golden_buffer_flags &&
|
||||
arf_buffer_flags == o.arf_buffer_flags &&
|
||||
layer_sync == o.layer_sync && freeze_entropy == o.freeze_entropy &&
|
||||
encoder_layer_id == o.encoder_layer_id &&
|
||||
packetizer_temporal_idx == o.packetizer_temporal_idx;
|
||||
}
|
||||
bool operator!=(const FrameConfig& o) const { return !(*this == o); }
|
||||
|
||||
private:
|
||||
FrameConfig(BufferFlags last,
|
||||
BufferFlags golden,
|
||||
BufferFlags arf,
|
||||
bool freeze_entropy);
|
||||
};
|
||||
|
||||
// Factory for TemporalLayer strategy. Default behavior is a fixed pattern
|
||||
// of temporal layers. See default_temporal_layers.cc
|
||||
virtual ~TemporalLayers() {}
|
||||
|
||||
// Returns the recommended VP8 encode flags needed. May refresh the decoder
|
||||
// and/or update the reference buffers.
|
||||
virtual FrameConfig UpdateLayerConfig(uint32_t timestamp) = 0;
|
||||
|
||||
// Update state based on new bitrate target and incoming framerate.
|
||||
// Returns the bitrate allocation for the active temporal layers.
|
||||
virtual std::vector<uint32_t> OnRatesUpdated(int bitrate_kbps,
|
||||
int max_bitrate_kbps,
|
||||
int framerate) = 0;
|
||||
|
||||
// Update the encoder configuration with target bitrates or other parameters.
|
||||
// Returns true iff the configuration was actually modified.
|
||||
virtual bool UpdateConfiguration(vpx_codec_enc_cfg_t* cfg) = 0;
|
||||
|
||||
virtual void PopulateCodecSpecific(
|
||||
bool is_keyframe,
|
||||
const TemporalLayers::FrameConfig& tl_config,
|
||||
CodecSpecificInfoVP8* vp8_info,
|
||||
uint32_t timestamp) = 0;
|
||||
|
||||
virtual void FrameEncoded(unsigned int size, int qp) = 0;
|
||||
|
||||
// Returns the current tl0_pic_idx, so it can be reused in future
|
||||
// instantiations.
|
||||
virtual uint8_t Tl0PicIdx() const = 0;
|
||||
};
|
||||
|
||||
class TemporalLayersListener;
|
||||
class TemporalLayersFactory {
|
||||
public:
|
||||
TemporalLayersFactory() : listener_(nullptr) {}
|
||||
virtual ~TemporalLayersFactory() {}
|
||||
virtual TemporalLayers* Create(int simulcast_id,
|
||||
int temporal_layers,
|
||||
uint8_t initial_tl0_pic_idx) const;
|
||||
void SetListener(TemporalLayersListener* listener);
|
||||
|
||||
protected:
|
||||
TemporalLayersListener* listener_;
|
||||
};
|
||||
|
||||
class ScreenshareTemporalLayersFactory : public webrtc::TemporalLayersFactory {
|
||||
public:
|
||||
ScreenshareTemporalLayersFactory() {}
|
||||
virtual ~ScreenshareTemporalLayersFactory() {}
|
||||
|
||||
webrtc::TemporalLayers* Create(int simulcast_id,
|
||||
int num_temporal_layers,
|
||||
uint8_t initial_tl0_pic_idx) const override;
|
||||
};
|
||||
|
||||
class TemporalLayersListener {
|
||||
public:
|
||||
TemporalLayersListener() {}
|
||||
virtual ~TemporalLayersListener() {}
|
||||
|
||||
virtual void OnTemporalLayersCreated(int simulcast_id,
|
||||
TemporalLayers* layers) = 0;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
#endif // WEBRTC_MODULES_VIDEO_CODING_CODECS_VP8_TEMPORAL_LAYERS_H_
|
||||
467
modules/video_coding/codecs/vp8/test/vp8_impl_unittest.cc
Normal file
467
modules/video_coding/codecs/vp8/test/vp8_impl_unittest.cc
Normal file
@ -0,0 +1,467 @@
|
||||
/*
|
||||
* Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "webrtc/api/optional.h"
|
||||
#include "webrtc/api/video/i420_buffer.h"
|
||||
#include "webrtc/common_video/libyuv/include/webrtc_libyuv.h"
|
||||
#include "webrtc/modules/video_coding/codecs/test/video_codec_test.h"
|
||||
#include "webrtc/modules/video_coding/codecs/vp8/include/vp8.h"
|
||||
#include "webrtc/modules/video_coding/codecs/vp8/temporal_layers.h"
|
||||
#include "webrtc/rtc_base/checks.h"
|
||||
#include "webrtc/rtc_base/timeutils.h"
|
||||
#include "webrtc/test/field_trial.h"
|
||||
#include "webrtc/test/frame_utils.h"
|
||||
#include "webrtc/test/gtest.h"
|
||||
#include "webrtc/test/testsupport/fileutils.h"
|
||||
#include "webrtc/test/video_codec_settings.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
constexpr uint32_t kInitialTimestampRtp = 123;
|
||||
constexpr int64_t kTestNtpTimeMs = 456;
|
||||
constexpr int64_t kInitialTimestampMs = 789;
|
||||
constexpr uint32_t kTimestampIncrement = 3000;
|
||||
constexpr int kNumCores = 1;
|
||||
constexpr size_t kMaxPayloadSize = 1440;
|
||||
constexpr int kMinPixelsPerFrame = 12345;
|
||||
constexpr int kDefaultMinPixelsPerFrame = 320 * 180;
|
||||
constexpr int kWidth = 172;
|
||||
constexpr int kHeight = 144;
|
||||
|
||||
void Calc16ByteAlignedStride(int width, int* stride_y, int* stride_uv) {
|
||||
*stride_y = 16 * ((width + 15) / 16);
|
||||
*stride_uv = 16 * ((width + 31) / 32);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
class EncodedImageCallbackTestImpl : public webrtc::EncodedImageCallback {
|
||||
public:
|
||||
Result OnEncodedImage(const EncodedImage& encoded_frame,
|
||||
const CodecSpecificInfo* codec_specific_info,
|
||||
const RTPFragmentationHeader* fragmentation) override {
|
||||
EXPECT_GT(encoded_frame._length, 0u);
|
||||
VerifyQpParser(encoded_frame);
|
||||
|
||||
if (encoded_frame_._size != encoded_frame._size) {
|
||||
delete[] encoded_frame_._buffer;
|
||||
frame_buffer_.reset(new uint8_t[encoded_frame._size]);
|
||||
}
|
||||
RTC_DCHECK(frame_buffer_);
|
||||
memcpy(frame_buffer_.get(), encoded_frame._buffer, encoded_frame._length);
|
||||
encoded_frame_ = encoded_frame;
|
||||
encoded_frame_._buffer = frame_buffer_.get();
|
||||
|
||||
// Skip |codec_name|, to avoid allocating.
|
||||
EXPECT_STREQ("libvpx", codec_specific_info->codec_name);
|
||||
EXPECT_EQ(kVideoCodecVP8, codec_specific_info->codecType);
|
||||
EXPECT_EQ(0u, codec_specific_info->codecSpecific.VP8.simulcastIdx);
|
||||
codec_specific_info_.codecType = codec_specific_info->codecType;
|
||||
codec_specific_info_.codecSpecific = codec_specific_info->codecSpecific;
|
||||
complete_ = true;
|
||||
return Result(Result::OK, 0);
|
||||
}
|
||||
|
||||
void VerifyQpParser(const EncodedImage& encoded_frame) const {
|
||||
int qp;
|
||||
ASSERT_TRUE(vp8::GetQp(encoded_frame._buffer, encoded_frame._length, &qp));
|
||||
EXPECT_EQ(encoded_frame.qp_, qp) << "Encoder QP != parsed bitstream QP.";
|
||||
}
|
||||
|
||||
bool EncodeComplete() {
|
||||
if (complete_) {
|
||||
complete_ = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
EncodedImage encoded_frame_;
|
||||
CodecSpecificInfo codec_specific_info_;
|
||||
std::unique_ptr<uint8_t[]> frame_buffer_;
|
||||
bool complete_ = false;
|
||||
};
|
||||
|
||||
class DecodedImageCallbackTestImpl : public webrtc::DecodedImageCallback {
|
||||
public:
|
||||
int32_t Decoded(VideoFrame& frame) override {
|
||||
RTC_NOTREACHED();
|
||||
return -1;
|
||||
}
|
||||
int32_t Decoded(VideoFrame& frame, int64_t decode_time_ms) override {
|
||||
RTC_NOTREACHED();
|
||||
return -1;
|
||||
}
|
||||
void Decoded(VideoFrame& frame,
|
||||
rtc::Optional<int32_t> decode_time_ms,
|
||||
rtc::Optional<uint8_t> qp) override {
|
||||
EXPECT_GT(frame.width(), 0);
|
||||
EXPECT_GT(frame.height(), 0);
|
||||
EXPECT_TRUE(qp);
|
||||
frame_ = rtc::Optional<VideoFrame>(frame);
|
||||
qp_ = qp;
|
||||
complete_ = true;
|
||||
}
|
||||
|
||||
bool DecodeComplete() {
|
||||
if (complete_) {
|
||||
complete_ = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
rtc::Optional<VideoFrame> frame_;
|
||||
rtc::Optional<uint8_t> qp_;
|
||||
bool complete_ = false;
|
||||
};
|
||||
|
||||
class TestVp8Impl : public ::testing::Test {
|
||||
public:
|
||||
TestVp8Impl() : TestVp8Impl("") {}
|
||||
explicit TestVp8Impl(const std::string& field_trials)
|
||||
: override_field_trials_(field_trials),
|
||||
encoder_(VP8Encoder::Create()),
|
||||
decoder_(VP8Decoder::Create()) {}
|
||||
virtual ~TestVp8Impl() {}
|
||||
|
||||
protected:
|
||||
virtual void SetUp() {
|
||||
encoder_->RegisterEncodeCompleteCallback(&encoded_cb_);
|
||||
decoder_->RegisterDecodeCompleteCallback(&decoded_cb_);
|
||||
SetupCodecSettings();
|
||||
SetupInputFrame();
|
||||
}
|
||||
|
||||
void SetupInputFrame() {
|
||||
// Using a QCIF image (aligned stride (u,v planes) > width).
|
||||
// Processing only one frame.
|
||||
FILE* file = fopen(test::ResourcePath("paris_qcif", "yuv").c_str(), "rb");
|
||||
ASSERT_TRUE(file != nullptr);
|
||||
rtc::scoped_refptr<I420BufferInterface> compact_buffer(
|
||||
test::ReadI420Buffer(kWidth, kHeight, file));
|
||||
ASSERT_TRUE(compact_buffer);
|
||||
|
||||
// Setting aligned stride values.
|
||||
int stride_uv;
|
||||
int stride_y;
|
||||
Calc16ByteAlignedStride(kWidth, &stride_y, &stride_uv);
|
||||
EXPECT_EQ(stride_y, 176);
|
||||
EXPECT_EQ(stride_uv, 96);
|
||||
rtc::scoped_refptr<I420Buffer> stride_buffer(
|
||||
I420Buffer::Create(kWidth, kHeight, stride_y, stride_uv, stride_uv));
|
||||
|
||||
// No scaling in our case, just a copy, to add stride to the image.
|
||||
stride_buffer->ScaleFrom(*compact_buffer);
|
||||
|
||||
input_frame_.reset(new VideoFrame(stride_buffer, kInitialTimestampRtp,
|
||||
kInitialTimestampMs, kVideoRotation_0));
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
void SetupCodecSettings() {
|
||||
webrtc::test::CodecSettings(kVideoCodecVP8, &codec_settings_);
|
||||
codec_settings_.maxBitrate = 4000;
|
||||
codec_settings_.width = kWidth;
|
||||
codec_settings_.height = kHeight;
|
||||
codec_settings_.VP8()->denoisingOn = true;
|
||||
codec_settings_.VP8()->frameDroppingOn = false;
|
||||
codec_settings_.VP8()->automaticResizeOn = false;
|
||||
codec_settings_.VP8()->complexity = kComplexityNormal;
|
||||
codec_settings_.VP8()->tl_factory = &tl_factory_;
|
||||
}
|
||||
|
||||
void InitEncodeDecode() {
|
||||
EXPECT_EQ(
|
||||
WEBRTC_VIDEO_CODEC_OK,
|
||||
encoder_->InitEncode(&codec_settings_, kNumCores, kMaxPayloadSize));
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
|
||||
decoder_->InitDecode(&codec_settings_, kNumCores));
|
||||
}
|
||||
|
||||
void EncodeFrame() {
|
||||
EXPECT_FALSE(encoded_cb_.EncodeComplete());
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
|
||||
encoder_->Encode(*input_frame_, nullptr, nullptr));
|
||||
EXPECT_TRUE(encoded_cb_.EncodeComplete());
|
||||
}
|
||||
|
||||
void ExpectFrameWith(int16_t picture_id,
|
||||
int tl0_pic_idx,
|
||||
uint8_t temporal_idx) {
|
||||
EXPECT_EQ(picture_id % (1 << 15),
|
||||
encoded_cb_.codec_specific_info_.codecSpecific.VP8.pictureId);
|
||||
EXPECT_EQ(tl0_pic_idx % (1 << 8),
|
||||
encoded_cb_.codec_specific_info_.codecSpecific.VP8.tl0PicIdx);
|
||||
EXPECT_EQ(temporal_idx,
|
||||
encoded_cb_.codec_specific_info_.codecSpecific.VP8.temporalIdx);
|
||||
}
|
||||
|
||||
test::ScopedFieldTrials override_field_trials_;
|
||||
EncodedImageCallbackTestImpl encoded_cb_;
|
||||
DecodedImageCallbackTestImpl decoded_cb_;
|
||||
std::unique_ptr<VideoFrame> input_frame_;
|
||||
const std::unique_ptr<VideoEncoder> encoder_;
|
||||
const std::unique_ptr<VideoDecoder> decoder_;
|
||||
VideoCodec codec_settings_;
|
||||
TemporalLayersFactory tl_factory_;
|
||||
};
|
||||
|
||||
TEST_F(TestVp8Impl, SetRateAllocation) {
|
||||
const int kBitrateBps = 300000;
|
||||
BitrateAllocation bitrate_allocation;
|
||||
bitrate_allocation.SetBitrate(0, 0, kBitrateBps);
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_UNINITIALIZED,
|
||||
encoder_->SetRateAllocation(bitrate_allocation,
|
||||
codec_settings_.maxFramerate));
|
||||
InitEncodeDecode();
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
|
||||
encoder_->SetRateAllocation(bitrate_allocation,
|
||||
codec_settings_.maxFramerate));
|
||||
}
|
||||
|
||||
TEST_F(TestVp8Impl, EncodeFrameAndRelease) {
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK, encoder_->Release());
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
|
||||
encoder_->InitEncode(&codec_settings_, kNumCores, kMaxPayloadSize));
|
||||
EncodeFrame();
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK, encoder_->Release());
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_UNINITIALIZED,
|
||||
encoder_->Encode(*input_frame_, nullptr, nullptr));
|
||||
}
|
||||
|
||||
TEST_F(TestVp8Impl, InitDecode) {
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK, decoder_->Release());
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
|
||||
decoder_->InitDecode(&codec_settings_, kNumCores));
|
||||
}
|
||||
|
||||
TEST_F(TestVp8Impl, OnEncodedImageReportsInfo) {
|
||||
InitEncodeDecode();
|
||||
EncodeFrame();
|
||||
EXPECT_EQ(kInitialTimestampRtp, encoded_cb_.encoded_frame_._timeStamp);
|
||||
EXPECT_EQ(kInitialTimestampMs, encoded_cb_.encoded_frame_.capture_time_ms_);
|
||||
EXPECT_EQ(kWidth, static_cast<int>(encoded_cb_.encoded_frame_._encodedWidth));
|
||||
EXPECT_EQ(kHeight,
|
||||
static_cast<int>(encoded_cb_.encoded_frame_._encodedHeight));
|
||||
EXPECT_EQ(-1, // Disabled for single stream.
|
||||
encoded_cb_.encoded_frame_.adapt_reason_.bw_resolutions_disabled);
|
||||
}
|
||||
|
||||
// We only test the encoder here, since the decoded frame rotation is set based
|
||||
// on the CVO RTP header extension in VCMDecodedFrameCallback::Decoded.
|
||||
// TODO(brandtr): Consider passing through the rotation flag through the decoder
|
||||
// in the same way as done in the encoder.
|
||||
TEST_F(TestVp8Impl, EncodedRotationEqualsInputRotation) {
|
||||
InitEncodeDecode();
|
||||
input_frame_->set_rotation(kVideoRotation_0);
|
||||
EncodeFrame();
|
||||
EXPECT_EQ(kVideoRotation_0, encoded_cb_.encoded_frame_.rotation_);
|
||||
|
||||
input_frame_->set_rotation(kVideoRotation_90);
|
||||
EncodeFrame();
|
||||
EXPECT_EQ(kVideoRotation_90, encoded_cb_.encoded_frame_.rotation_);
|
||||
}
|
||||
|
||||
TEST_F(TestVp8Impl, DecodedQpEqualsEncodedQp) {
|
||||
InitEncodeDecode();
|
||||
EncodeFrame();
|
||||
// First frame should be a key frame.
|
||||
encoded_cb_.encoded_frame_._frameType = kVideoFrameKey;
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
|
||||
decoder_->Decode(encoded_cb_.encoded_frame_, false, nullptr));
|
||||
EXPECT_TRUE(decoded_cb_.DecodeComplete());
|
||||
EXPECT_GT(I420PSNR(input_frame_.get(), &*decoded_cb_.frame_), 36);
|
||||
EXPECT_EQ(encoded_cb_.encoded_frame_.qp_, *decoded_cb_.qp_);
|
||||
}
|
||||
|
||||
#if defined(WEBRTC_ANDROID)
|
||||
#define MAYBE_AlignedStrideEncodeDecode DISABLED_AlignedStrideEncodeDecode
|
||||
#else
|
||||
#define MAYBE_AlignedStrideEncodeDecode AlignedStrideEncodeDecode
|
||||
#endif
|
||||
TEST_F(TestVp8Impl, MAYBE_AlignedStrideEncodeDecode) {
|
||||
InitEncodeDecode();
|
||||
EncodeFrame();
|
||||
// First frame should be a key frame.
|
||||
encoded_cb_.encoded_frame_._frameType = kVideoFrameKey;
|
||||
encoded_cb_.encoded_frame_.ntp_time_ms_ = kTestNtpTimeMs;
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
|
||||
decoder_->Decode(encoded_cb_.encoded_frame_, false, nullptr));
|
||||
EXPECT_TRUE(decoded_cb_.DecodeComplete());
|
||||
// Compute PSNR on all planes (faster than SSIM).
|
||||
EXPECT_GT(I420PSNR(input_frame_.get(), &*decoded_cb_.frame_), 36);
|
||||
EXPECT_EQ(kInitialTimestampRtp, decoded_cb_.frame_->timestamp());
|
||||
EXPECT_EQ(kTestNtpTimeMs, decoded_cb_.frame_->ntp_time_ms());
|
||||
}
|
||||
|
||||
#if defined(WEBRTC_ANDROID)
|
||||
#define MAYBE_DecodeWithACompleteKeyFrame DISABLED_DecodeWithACompleteKeyFrame
|
||||
#else
|
||||
#define MAYBE_DecodeWithACompleteKeyFrame DecodeWithACompleteKeyFrame
|
||||
#endif
|
||||
TEST_F(TestVp8Impl, MAYBE_DecodeWithACompleteKeyFrame) {
|
||||
InitEncodeDecode();
|
||||
EncodeFrame();
|
||||
// Setting complete to false -> should return an error.
|
||||
encoded_cb_.encoded_frame_._completeFrame = false;
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_ERROR,
|
||||
decoder_->Decode(encoded_cb_.encoded_frame_, false, nullptr));
|
||||
// Setting complete back to true. Forcing a delta frame.
|
||||
encoded_cb_.encoded_frame_._frameType = kVideoFrameDelta;
|
||||
encoded_cb_.encoded_frame_._completeFrame = true;
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_ERROR,
|
||||
decoder_->Decode(encoded_cb_.encoded_frame_, false, nullptr));
|
||||
// Now setting a key frame.
|
||||
encoded_cb_.encoded_frame_._frameType = kVideoFrameKey;
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
|
||||
decoder_->Decode(encoded_cb_.encoded_frame_, false, nullptr));
|
||||
ASSERT_TRUE(decoded_cb_.frame_);
|
||||
EXPECT_GT(I420PSNR(input_frame_.get(), &*decoded_cb_.frame_), 36);
|
||||
}
|
||||
|
||||
TEST_F(TestVp8Impl, EncoderWith2TemporalLayersRetainsRtpStateAfterRelease) {
|
||||
codec_settings_.VP8()->numberOfTemporalLayers = 2;
|
||||
InitEncodeDecode();
|
||||
|
||||
// Temporal layer 0.
|
||||
EncodeFrame();
|
||||
EXPECT_EQ(0, encoded_cb_.codec_specific_info_.codecSpecific.VP8.temporalIdx);
|
||||
int16_t picture_id =
|
||||
encoded_cb_.codec_specific_info_.codecSpecific.VP8.pictureId;
|
||||
int tl0_pic_idx =
|
||||
encoded_cb_.codec_specific_info_.codecSpecific.VP8.tl0PicIdx;
|
||||
// Temporal layer 1.
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() + kTimestampIncrement);
|
||||
EncodeFrame();
|
||||
ExpectFrameWith(picture_id + 1, tl0_pic_idx + 0, 1);
|
||||
// Temporal layer 0.
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() + kTimestampIncrement);
|
||||
EncodeFrame();
|
||||
ExpectFrameWith(picture_id + 2, tl0_pic_idx + 1, 0);
|
||||
// Temporal layer 1.
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() + kTimestampIncrement);
|
||||
EncodeFrame();
|
||||
ExpectFrameWith(picture_id + 3, tl0_pic_idx + 1, 1);
|
||||
|
||||
// Reinit.
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK, encoder_->Release());
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
|
||||
encoder_->InitEncode(&codec_settings_, kNumCores, kMaxPayloadSize));
|
||||
|
||||
// Temporal layer 0.
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() + kTimestampIncrement);
|
||||
EncodeFrame();
|
||||
ExpectFrameWith(picture_id + 4, tl0_pic_idx + 2, 0);
|
||||
// Temporal layer 1.
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() + kTimestampIncrement);
|
||||
EncodeFrame();
|
||||
ExpectFrameWith(picture_id + 5, tl0_pic_idx + 2, 1);
|
||||
// Temporal layer 0.
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() + kTimestampIncrement);
|
||||
EncodeFrame();
|
||||
ExpectFrameWith(picture_id + 6, tl0_pic_idx + 3, 0);
|
||||
// Temporal layer 1.
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() + kTimestampIncrement);
|
||||
EncodeFrame();
|
||||
ExpectFrameWith(picture_id + 7, tl0_pic_idx + 3, 1);
|
||||
}
|
||||
|
||||
TEST_F(TestVp8Impl, EncoderWith3TemporalLayersRetainsRtpStateAfterRelease) {
|
||||
codec_settings_.VP8()->numberOfTemporalLayers = 3;
|
||||
InitEncodeDecode();
|
||||
|
||||
// Temporal layer 0.
|
||||
EncodeFrame();
|
||||
EXPECT_EQ(0, encoded_cb_.codec_specific_info_.codecSpecific.VP8.temporalIdx);
|
||||
int16_t picture_id =
|
||||
encoded_cb_.codec_specific_info_.codecSpecific.VP8.pictureId;
|
||||
int tl0_pic_idx =
|
||||
encoded_cb_.codec_specific_info_.codecSpecific.VP8.tl0PicIdx;
|
||||
// Temporal layer 2.
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() + kTimestampIncrement);
|
||||
EncodeFrame();
|
||||
ExpectFrameWith(picture_id + 1, tl0_pic_idx + 0, 2);
|
||||
// Temporal layer 1.
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() + kTimestampIncrement);
|
||||
EncodeFrame();
|
||||
ExpectFrameWith(picture_id + 2, tl0_pic_idx + 0, 1);
|
||||
// Temporal layer 2.
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() + kTimestampIncrement);
|
||||
EncodeFrame();
|
||||
ExpectFrameWith(picture_id + 3, tl0_pic_idx + 0, 2);
|
||||
|
||||
// Reinit.
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK, encoder_->Release());
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
|
||||
encoder_->InitEncode(&codec_settings_, kNumCores, kMaxPayloadSize));
|
||||
|
||||
// Temporal layer 0.
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() + kTimestampIncrement);
|
||||
EncodeFrame();
|
||||
ExpectFrameWith(picture_id + 4, tl0_pic_idx + 1, 0);
|
||||
// Temporal layer 2.
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() + kTimestampIncrement);
|
||||
EncodeFrame();
|
||||
ExpectFrameWith(picture_id + 5, tl0_pic_idx + 1, 2);
|
||||
// Temporal layer 1.
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() + kTimestampIncrement);
|
||||
EncodeFrame();
|
||||
ExpectFrameWith(picture_id + 6, tl0_pic_idx + 1, 1);
|
||||
// Temporal layer 2.
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() + kTimestampIncrement);
|
||||
EncodeFrame();
|
||||
ExpectFrameWith(picture_id + 7, tl0_pic_idx + 1, 2);
|
||||
}
|
||||
|
||||
TEST_F(TestVp8Impl, ScalingDisabledIfAutomaticResizeOff) {
|
||||
codec_settings_.VP8()->frameDroppingOn = true;
|
||||
codec_settings_.VP8()->automaticResizeOn = false;
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
|
||||
encoder_->InitEncode(&codec_settings_, kNumCores, kMaxPayloadSize));
|
||||
|
||||
VideoEncoder::ScalingSettings settings = encoder_->GetScalingSettings();
|
||||
EXPECT_FALSE(settings.enabled);
|
||||
}
|
||||
|
||||
TEST_F(TestVp8Impl, ScalingEnabledIfAutomaticResizeOn) {
|
||||
codec_settings_.VP8()->frameDroppingOn = true;
|
||||
codec_settings_.VP8()->automaticResizeOn = true;
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
|
||||
encoder_->InitEncode(&codec_settings_, kNumCores, kMaxPayloadSize));
|
||||
|
||||
VideoEncoder::ScalingSettings settings = encoder_->GetScalingSettings();
|
||||
EXPECT_TRUE(settings.enabled);
|
||||
EXPECT_EQ(kDefaultMinPixelsPerFrame, settings.min_pixels_per_frame);
|
||||
}
|
||||
|
||||
class TestVp8ImplWithForcedFallbackEnabled : public TestVp8Impl {
|
||||
public:
|
||||
TestVp8ImplWithForcedFallbackEnabled()
|
||||
: TestVp8Impl("WebRTC-VP8-Forced-Fallback-Encoder/Enabled-1,2,3," +
|
||||
std::to_string(kMinPixelsPerFrame) + "/") {}
|
||||
};
|
||||
|
||||
TEST_F(TestVp8ImplWithForcedFallbackEnabled, MinPixelsPerFrameConfigured) {
|
||||
codec_settings_.VP8()->frameDroppingOn = true;
|
||||
codec_settings_.VP8()->automaticResizeOn = true;
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
|
||||
encoder_->InitEncode(&codec_settings_, kNumCores, kMaxPayloadSize));
|
||||
|
||||
VideoEncoder::ScalingSettings settings = encoder_->GetScalingSettings();
|
||||
EXPECT_TRUE(settings.enabled);
|
||||
EXPECT_EQ(kMinPixelsPerFrame, settings.min_pixels_per_frame);
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
1270
modules/video_coding/codecs/vp8/vp8_impl.cc
Normal file
1270
modules/video_coding/codecs/vp8/vp8_impl.cc
Normal file
File diff suppressed because it is too large
Load Diff
167
modules/video_coding/codecs/vp8/vp8_impl.h
Normal file
167
modules/video_coding/codecs/vp8/vp8_impl.h
Normal file
@ -0,0 +1,167 @@
|
||||
/*
|
||||
* Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*
|
||||
* WEBRTC VP8 wrapper interface
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_VIDEO_CODING_CODECS_VP8_VP8_IMPL_H_
|
||||
#define WEBRTC_MODULES_VIDEO_CODING_CODECS_VP8_VP8_IMPL_H_
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
// NOTE: This include order must remain to avoid compile errors, even though
|
||||
// it breaks the style guide.
|
||||
#include "vpx/vpx_encoder.h"
|
||||
#include "vpx/vpx_decoder.h"
|
||||
#include "vpx/vp8cx.h"
|
||||
#include "vpx/vp8dx.h"
|
||||
|
||||
#include "webrtc/api/video/video_frame.h"
|
||||
#include "webrtc/common_video/include/i420_buffer_pool.h"
|
||||
#include "webrtc/common_video/include/video_frame.h"
|
||||
#include "webrtc/modules/video_coding/codecs/vp8/include/vp8.h"
|
||||
#include "webrtc/modules/video_coding/codecs/vp8/temporal_layers.h"
|
||||
#include "webrtc/modules/video_coding/include/video_codec_interface.h"
|
||||
#include "webrtc/modules/video_coding/utility/quality_scaler.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class TemporalLayers;
|
||||
|
||||
class VP8EncoderImpl : public VP8Encoder {
|
||||
public:
|
||||
VP8EncoderImpl();
|
||||
|
||||
virtual ~VP8EncoderImpl();
|
||||
|
||||
int Release() override;
|
||||
|
||||
int InitEncode(const VideoCodec* codec_settings,
|
||||
int number_of_cores,
|
||||
size_t max_payload_size) override;
|
||||
|
||||
int Encode(const VideoFrame& input_image,
|
||||
const CodecSpecificInfo* codec_specific_info,
|
||||
const std::vector<FrameType>* frame_types) override;
|
||||
|
||||
int RegisterEncodeCompleteCallback(EncodedImageCallback* callback) override;
|
||||
|
||||
int SetChannelParameters(uint32_t packet_loss, int64_t rtt) override;
|
||||
|
||||
int SetRateAllocation(const BitrateAllocation& bitrate,
|
||||
uint32_t new_framerate) override;
|
||||
|
||||
ScalingSettings GetScalingSettings() const override;
|
||||
|
||||
const char* ImplementationName() const override;
|
||||
|
||||
static vpx_enc_frame_flags_t EncodeFlags(
|
||||
const TemporalLayers::FrameConfig& references);
|
||||
|
||||
private:
|
||||
void SetupTemporalLayers(int num_streams,
|
||||
int num_temporal_layers,
|
||||
const VideoCodec& codec);
|
||||
|
||||
// Set the cpu_speed setting for encoder based on resolution and/or platform.
|
||||
int SetCpuSpeed(int width, int height);
|
||||
|
||||
// Determine number of encoder threads to use.
|
||||
int NumberOfThreads(int width, int height, int number_of_cores);
|
||||
|
||||
// Call encoder initialize function and set control settings.
|
||||
int InitAndSetControlSettings();
|
||||
|
||||
void PopulateCodecSpecific(CodecSpecificInfo* codec_specific,
|
||||
const TemporalLayers::FrameConfig& tl_config,
|
||||
const vpx_codec_cx_pkt& pkt,
|
||||
int stream_idx,
|
||||
uint32_t timestamp);
|
||||
|
||||
int GetEncodedPartitions(const TemporalLayers::FrameConfig tl_configs[],
|
||||
const VideoFrame& input_image);
|
||||
|
||||
// Set the stream state for stream |stream_idx|.
|
||||
void SetStreamState(bool send_stream, int stream_idx);
|
||||
|
||||
uint32_t MaxIntraTarget(uint32_t optimal_buffer_size);
|
||||
|
||||
const bool use_gf_boost_;
|
||||
const rtc::Optional<int> min_pixels_per_frame_;
|
||||
|
||||
EncodedImageCallback* encoded_complete_callback_;
|
||||
VideoCodec codec_;
|
||||
bool inited_;
|
||||
int64_t timestamp_;
|
||||
int qp_max_;
|
||||
int cpu_speed_default_;
|
||||
int number_of_cores_;
|
||||
uint32_t rc_max_intra_target_;
|
||||
std::vector<std::unique_ptr<TemporalLayers>> temporal_layers_;
|
||||
std::vector<uint16_t> picture_id_;
|
||||
std::vector<uint8_t> tl0_pic_idx_;
|
||||
std::vector<bool> key_frame_request_;
|
||||
std::vector<bool> send_stream_;
|
||||
std::vector<int> cpu_speed_;
|
||||
std::vector<vpx_image_t> raw_images_;
|
||||
std::vector<EncodedImage> encoded_images_;
|
||||
std::vector<vpx_codec_ctx_t> encoders_;
|
||||
std::vector<vpx_codec_enc_cfg_t> configurations_;
|
||||
std::vector<vpx_rational_t> downsampling_factors_;
|
||||
};
|
||||
|
||||
class VP8DecoderImpl : public VP8Decoder {
|
||||
public:
|
||||
VP8DecoderImpl();
|
||||
|
||||
virtual ~VP8DecoderImpl();
|
||||
|
||||
int InitDecode(const VideoCodec* inst, int number_of_cores) override;
|
||||
|
||||
int Decode(const EncodedImage& input_image,
|
||||
bool missing_frames,
|
||||
const RTPFragmentationHeader* fragmentation,
|
||||
const CodecSpecificInfo* codec_specific_info,
|
||||
int64_t /*render_time_ms*/) override;
|
||||
|
||||
int RegisterDecodeCompleteCallback(DecodedImageCallback* callback) override;
|
||||
int Release() override;
|
||||
|
||||
const char* ImplementationName() const override;
|
||||
|
||||
struct DeblockParams {
|
||||
int max_level = 6; // Deblocking strength: [0, 16].
|
||||
int degrade_qp = 1; // If QP value is below, start lowering |max_level|.
|
||||
int min_qp = 0; // If QP value is below, turn off deblocking.
|
||||
};
|
||||
|
||||
private:
|
||||
class QpSmoother;
|
||||
int ReturnFrame(const vpx_image_t* img,
|
||||
uint32_t timeStamp,
|
||||
int64_t ntp_time_ms,
|
||||
int qp);
|
||||
|
||||
const bool use_postproc_arm_;
|
||||
|
||||
I420BufferPool buffer_pool_;
|
||||
DecodedImageCallback* decode_complete_callback_;
|
||||
bool inited_;
|
||||
vpx_codec_ctx_t* decoder_;
|
||||
int propagation_cnt_;
|
||||
int last_frame_width_;
|
||||
int last_frame_height_;
|
||||
bool key_frame_required_;
|
||||
DeblockParams deblock_;
|
||||
const std::unique_ptr<QpSmoother> qp_smoother_;
|
||||
};
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_VIDEO_CODING_CODECS_VP8_VP8_IMPL_H_
|
||||
36
modules/video_coding/codecs/vp9/include/vp9.h
Normal file
36
modules/video_coding/codecs/vp9/include/vp9.h
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_VIDEO_CODING_CODECS_VP9_INCLUDE_VP9_H_
|
||||
#define WEBRTC_MODULES_VIDEO_CODING_CODECS_VP9_INCLUDE_VP9_H_
|
||||
|
||||
#include "webrtc/modules/video_coding/include/video_codec_interface.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class VP9Encoder : public VideoEncoder {
|
||||
public:
|
||||
static bool IsSupported();
|
||||
static VP9Encoder* Create();
|
||||
|
||||
virtual ~VP9Encoder() {}
|
||||
};
|
||||
|
||||
class VP9Decoder : public VideoDecoder {
|
||||
public:
|
||||
static bool IsSupported();
|
||||
static VP9Decoder* Create();
|
||||
|
||||
virtual ~VP9Decoder() {}
|
||||
};
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_VIDEO_CODING_CODECS_VP9_INCLUDE_VP9_H_
|
||||
210
modules/video_coding/codecs/vp9/include/vp9_globals.h
Normal file
210
modules/video_coding/codecs/vp9/include/vp9_globals.h
Normal file
@ -0,0 +1,210 @@
|
||||
/*
|
||||
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
// This file contains codec dependent definitions that are needed in
|
||||
// order to compile the WebRTC codebase, even if this codec is not used.
|
||||
|
||||
#ifndef WEBRTC_MODULES_VIDEO_CODING_CODECS_VP9_INCLUDE_VP9_GLOBALS_H_
|
||||
#define WEBRTC_MODULES_VIDEO_CODING_CODECS_VP9_INCLUDE_VP9_GLOBALS_H_
|
||||
|
||||
#include "webrtc/modules/video_coding/codecs/interface/common_constants.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
const int16_t kMaxOneBytePictureId = 0x7F; // 7 bits
|
||||
const int16_t kMaxTwoBytePictureId = 0x7FFF; // 15 bits
|
||||
const uint8_t kNoSpatialIdx = 0xFF;
|
||||
const uint8_t kNoGofIdx = 0xFF;
|
||||
const uint8_t kNumVp9Buffers = 8;
|
||||
const size_t kMaxVp9RefPics = 3;
|
||||
const size_t kMaxVp9FramesInGof = 0xFF; // 8 bits
|
||||
const size_t kMaxVp9NumberOfSpatialLayers = 8;
|
||||
|
||||
enum TemporalStructureMode {
|
||||
kTemporalStructureMode1, // 1 temporal layer structure - i.e., IPPP...
|
||||
kTemporalStructureMode2, // 2 temporal layers 01...
|
||||
kTemporalStructureMode3, // 3 temporal layers 0212...
|
||||
kTemporalStructureMode4 // 3 temporal layers 02120212...
|
||||
};
|
||||
|
||||
struct GofInfoVP9 {
|
||||
void SetGofInfoVP9(TemporalStructureMode tm) {
|
||||
switch (tm) {
|
||||
case kTemporalStructureMode1:
|
||||
num_frames_in_gof = 1;
|
||||
temporal_idx[0] = 0;
|
||||
temporal_up_switch[0] = false;
|
||||
num_ref_pics[0] = 1;
|
||||
pid_diff[0][0] = 1;
|
||||
break;
|
||||
case kTemporalStructureMode2:
|
||||
num_frames_in_gof = 2;
|
||||
temporal_idx[0] = 0;
|
||||
temporal_up_switch[0] = false;
|
||||
num_ref_pics[0] = 1;
|
||||
pid_diff[0][0] = 2;
|
||||
|
||||
temporal_idx[1] = 1;
|
||||
temporal_up_switch[1] = true;
|
||||
num_ref_pics[1] = 1;
|
||||
pid_diff[1][0] = 1;
|
||||
break;
|
||||
case kTemporalStructureMode3:
|
||||
num_frames_in_gof = 4;
|
||||
temporal_idx[0] = 0;
|
||||
temporal_up_switch[0] = false;
|
||||
num_ref_pics[0] = 1;
|
||||
pid_diff[0][0] = 4;
|
||||
|
||||
temporal_idx[1] = 2;
|
||||
temporal_up_switch[1] = true;
|
||||
num_ref_pics[1] = 1;
|
||||
pid_diff[1][0] = 1;
|
||||
|
||||
temporal_idx[2] = 1;
|
||||
temporal_up_switch[2] = true;
|
||||
num_ref_pics[2] = 1;
|
||||
pid_diff[2][0] = 2;
|
||||
|
||||
temporal_idx[3] = 2;
|
||||
temporal_up_switch[3] = false;
|
||||
num_ref_pics[3] = 2;
|
||||
pid_diff[3][0] = 1;
|
||||
pid_diff[3][1] = 2;
|
||||
break;
|
||||
case kTemporalStructureMode4:
|
||||
num_frames_in_gof = 8;
|
||||
temporal_idx[0] = 0;
|
||||
temporal_up_switch[0] = false;
|
||||
num_ref_pics[0] = 1;
|
||||
pid_diff[0][0] = 4;
|
||||
|
||||
temporal_idx[1] = 2;
|
||||
temporal_up_switch[1] = true;
|
||||
num_ref_pics[1] = 1;
|
||||
pid_diff[1][0] = 1;
|
||||
|
||||
temporal_idx[2] = 1;
|
||||
temporal_up_switch[2] = true;
|
||||
num_ref_pics[2] = 1;
|
||||
pid_diff[2][0] = 2;
|
||||
|
||||
temporal_idx[3] = 2;
|
||||
temporal_up_switch[3] = false;
|
||||
num_ref_pics[3] = 2;
|
||||
pid_diff[3][0] = 1;
|
||||
pid_diff[3][1] = 2;
|
||||
|
||||
temporal_idx[4] = 0;
|
||||
temporal_up_switch[0] = false;
|
||||
num_ref_pics[4] = 1;
|
||||
pid_diff[4][0] = 4;
|
||||
|
||||
temporal_idx[5] = 2;
|
||||
temporal_up_switch[1] = false;
|
||||
num_ref_pics[5] = 2;
|
||||
pid_diff[5][0] = 1;
|
||||
pid_diff[5][1] = 2;
|
||||
|
||||
temporal_idx[6] = 1;
|
||||
temporal_up_switch[2] = false;
|
||||
num_ref_pics[6] = 2;
|
||||
pid_diff[6][0] = 2;
|
||||
pid_diff[6][1] = 4;
|
||||
|
||||
temporal_idx[7] = 2;
|
||||
temporal_up_switch[3] = false;
|
||||
num_ref_pics[7] = 2;
|
||||
pid_diff[7][0] = 1;
|
||||
pid_diff[7][1] = 2;
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
void CopyGofInfoVP9(const GofInfoVP9& src) {
|
||||
num_frames_in_gof = src.num_frames_in_gof;
|
||||
for (size_t i = 0; i < num_frames_in_gof; ++i) {
|
||||
temporal_idx[i] = src.temporal_idx[i];
|
||||
temporal_up_switch[i] = src.temporal_up_switch[i];
|
||||
num_ref_pics[i] = src.num_ref_pics[i];
|
||||
for (uint8_t r = 0; r < num_ref_pics[i]; ++r) {
|
||||
pid_diff[i][r] = src.pid_diff[i][r];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t num_frames_in_gof;
|
||||
uint8_t temporal_idx[kMaxVp9FramesInGof];
|
||||
bool temporal_up_switch[kMaxVp9FramesInGof];
|
||||
uint8_t num_ref_pics[kMaxVp9FramesInGof];
|
||||
uint8_t pid_diff[kMaxVp9FramesInGof][kMaxVp9RefPics];
|
||||
uint16_t pid_start;
|
||||
};
|
||||
|
||||
struct RTPVideoHeaderVP9 {
|
||||
void InitRTPVideoHeaderVP9() {
|
||||
inter_pic_predicted = false;
|
||||
flexible_mode = false;
|
||||
beginning_of_frame = false;
|
||||
end_of_frame = false;
|
||||
ss_data_available = false;
|
||||
picture_id = kNoPictureId;
|
||||
max_picture_id = kMaxTwoBytePictureId;
|
||||
tl0_pic_idx = kNoTl0PicIdx;
|
||||
temporal_idx = kNoTemporalIdx;
|
||||
spatial_idx = kNoSpatialIdx;
|
||||
temporal_up_switch = false;
|
||||
inter_layer_predicted = false;
|
||||
gof_idx = kNoGofIdx;
|
||||
num_ref_pics = 0;
|
||||
num_spatial_layers = 1;
|
||||
}
|
||||
|
||||
bool inter_pic_predicted; // This layer frame is dependent on previously
|
||||
// coded frame(s).
|
||||
bool flexible_mode; // This frame is in flexible mode.
|
||||
bool beginning_of_frame; // True if this packet is the first in a VP9 layer
|
||||
// frame.
|
||||
bool end_of_frame; // True if this packet is the last in a VP9 layer frame.
|
||||
bool ss_data_available; // True if SS data is available in this payload
|
||||
// descriptor.
|
||||
int16_t picture_id; // PictureID index, 15 bits;
|
||||
// kNoPictureId if PictureID does not exist.
|
||||
int16_t max_picture_id; // Maximum picture ID index; either 0x7F or 0x7FFF;
|
||||
int16_t tl0_pic_idx; // TL0PIC_IDX, 8 bits;
|
||||
// kNoTl0PicIdx means no value provided.
|
||||
uint8_t temporal_idx; // Temporal layer index, or kNoTemporalIdx.
|
||||
uint8_t spatial_idx; // Spatial layer index, or kNoSpatialIdx.
|
||||
bool temporal_up_switch; // True if upswitch to higher frame rate is possible
|
||||
// starting from this frame.
|
||||
bool inter_layer_predicted; // Frame is dependent on directly lower spatial
|
||||
// layer frame.
|
||||
|
||||
uint8_t gof_idx; // Index to predefined temporal frame info in SS data.
|
||||
|
||||
uint8_t num_ref_pics; // Number of reference pictures used by this layer
|
||||
// frame.
|
||||
uint8_t pid_diff[kMaxVp9RefPics]; // P_DIFF signaled to derive the PictureID
|
||||
// of the reference pictures.
|
||||
int16_t ref_picture_id[kMaxVp9RefPics]; // PictureID of reference pictures.
|
||||
|
||||
// SS data.
|
||||
size_t num_spatial_layers; // Always populated.
|
||||
bool spatial_layer_resolution_present;
|
||||
uint16_t width[kMaxVp9NumberOfSpatialLayers];
|
||||
uint16_t height[kMaxVp9NumberOfSpatialLayers];
|
||||
GofInfoVP9 gof;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_VIDEO_CODING_CODECS_VP9_INCLUDE_VP9_GLOBALS_H_
|
||||
93
modules/video_coding/codecs/vp9/screenshare_layers.cc
Normal file
93
modules/video_coding/codecs/vp9/screenshare_layers.cc
Normal file
@ -0,0 +1,93 @@
|
||||
/* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "webrtc/modules/video_coding/codecs/vp9/screenshare_layers.h"
|
||||
#include <algorithm>
|
||||
#include "webrtc/rtc_base/checks.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
ScreenshareLayersVP9::ScreenshareLayersVP9(uint8_t num_layers)
|
||||
: num_layers_(num_layers),
|
||||
start_layer_(0),
|
||||
last_timestamp_(0),
|
||||
timestamp_initialized_(false) {
|
||||
RTC_DCHECK_GT(num_layers, 0);
|
||||
RTC_DCHECK_LE(num_layers, kMaxVp9NumberOfSpatialLayers);
|
||||
memset(bits_used_, 0, sizeof(bits_used_));
|
||||
memset(threshold_kbps_, 0, sizeof(threshold_kbps_));
|
||||
}
|
||||
|
||||
uint8_t ScreenshareLayersVP9::GetStartLayer() const {
|
||||
return start_layer_;
|
||||
}
|
||||
|
||||
void ScreenshareLayersVP9::ConfigureBitrate(int threshold_kbps,
|
||||
uint8_t layer_id) {
|
||||
// The upper layer is always the layer we spill frames
|
||||
// to when the bitrate becomes to high, therefore setting
|
||||
// a max limit is not allowed. The top layer bitrate is
|
||||
// never used either so configuring it makes no difference.
|
||||
RTC_DCHECK_LT(layer_id, num_layers_ - 1);
|
||||
threshold_kbps_[layer_id] = threshold_kbps;
|
||||
}
|
||||
|
||||
void ScreenshareLayersVP9::LayerFrameEncoded(unsigned int size_bytes,
|
||||
uint8_t layer_id) {
|
||||
RTC_DCHECK_LT(layer_id, num_layers_);
|
||||
bits_used_[layer_id] += size_bytes * 8;
|
||||
}
|
||||
|
||||
VP9EncoderImpl::SuperFrameRefSettings
|
||||
ScreenshareLayersVP9::GetSuperFrameSettings(uint32_t timestamp,
|
||||
bool is_keyframe) {
|
||||
VP9EncoderImpl::SuperFrameRefSettings settings;
|
||||
if (!timestamp_initialized_) {
|
||||
last_timestamp_ = timestamp;
|
||||
timestamp_initialized_ = true;
|
||||
}
|
||||
float time_diff = (timestamp - last_timestamp_) / 90.f;
|
||||
float total_bits_used = 0;
|
||||
float total_threshold_kbps = 0;
|
||||
start_layer_ = 0;
|
||||
|
||||
// Up to (num_layers - 1) because we only have
|
||||
// (num_layers - 1) thresholds to check.
|
||||
for (int layer_id = 0; layer_id < num_layers_ - 1; ++layer_id) {
|
||||
bits_used_[layer_id] = std::max(
|
||||
0.f, bits_used_[layer_id] - time_diff * threshold_kbps_[layer_id]);
|
||||
total_bits_used += bits_used_[layer_id];
|
||||
total_threshold_kbps += threshold_kbps_[layer_id];
|
||||
|
||||
// If this is a keyframe then there should be no
|
||||
// references to any previous frames.
|
||||
if (!is_keyframe) {
|
||||
settings.layer[layer_id].ref_buf1 = layer_id;
|
||||
if (total_bits_used > total_threshold_kbps * 1000)
|
||||
start_layer_ = layer_id + 1;
|
||||
}
|
||||
|
||||
settings.layer[layer_id].upd_buf = layer_id;
|
||||
}
|
||||
// Since the above loop does not iterate over the last layer
|
||||
// the reference of the last layer has to be set after the loop,
|
||||
// and if this is a keyframe there should be no references to
|
||||
// any previous frames.
|
||||
if (!is_keyframe)
|
||||
settings.layer[num_layers_ - 1].ref_buf1 = num_layers_ - 1;
|
||||
|
||||
settings.layer[num_layers_ - 1].upd_buf = num_layers_ - 1;
|
||||
settings.is_keyframe = is_keyframe;
|
||||
settings.start_layer = start_layer_;
|
||||
settings.stop_layer = num_layers_ - 1;
|
||||
last_timestamp_ = timestamp;
|
||||
return settings;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
66
modules/video_coding/codecs/vp9/screenshare_layers.h
Normal file
66
modules/video_coding/codecs/vp9/screenshare_layers.h
Normal file
@ -0,0 +1,66 @@
|
||||
/* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_VIDEO_CODING_CODECS_VP9_SCREENSHARE_LAYERS_H_
|
||||
#define WEBRTC_MODULES_VIDEO_CODING_CODECS_VP9_SCREENSHARE_LAYERS_H_
|
||||
|
||||
#include "webrtc/modules/video_coding/codecs/vp9/vp9_impl.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class ScreenshareLayersVP9 {
|
||||
public:
|
||||
explicit ScreenshareLayersVP9(uint8_t num_layers);
|
||||
|
||||
// The target bitrate for layer with id layer_id.
|
||||
void ConfigureBitrate(int threshold_kbps, uint8_t layer_id);
|
||||
|
||||
// The current start layer.
|
||||
uint8_t GetStartLayer() const;
|
||||
|
||||
// Update the layer with the size of the layer frame.
|
||||
void LayerFrameEncoded(unsigned int size_bytes, uint8_t layer_id);
|
||||
|
||||
// Get the layer settings for the next superframe.
|
||||
//
|
||||
// In short, each time the GetSuperFrameSettings is called the
|
||||
// bitrate of every layer is calculated and if the cummulative
|
||||
// bitrate exceeds the configured cummulative bitrates
|
||||
// (ConfigureBitrate to configure) up to and including that
|
||||
// layer then the resulting encoding settings for the
|
||||
// superframe will only encode layers above that layer.
|
||||
VP9EncoderImpl::SuperFrameRefSettings GetSuperFrameSettings(
|
||||
uint32_t timestamp,
|
||||
bool is_keyframe);
|
||||
|
||||
private:
|
||||
// How many layers that are used.
|
||||
uint8_t num_layers_;
|
||||
|
||||
// The index of the first layer to encode.
|
||||
uint8_t start_layer_;
|
||||
|
||||
// Cummulative target kbps for the different layers.
|
||||
float threshold_kbps_[kMaxVp9NumberOfSpatialLayers - 1];
|
||||
|
||||
// How many bits that has been used for a certain layer. Increased in
|
||||
// FrameEncoded() by the size of the encoded frame and decreased in
|
||||
// GetSuperFrameSettings() depending on the time between frames.
|
||||
float bits_used_[kMaxVp9NumberOfSpatialLayers];
|
||||
|
||||
// Timestamp of last frame.
|
||||
uint32_t last_timestamp_;
|
||||
|
||||
// If the last_timestamp_ has been set.
|
||||
bool timestamp_initialized_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_VIDEO_CODING_CODECS_VP9_SCREENSHARE_LAYERS_H_
|
||||
201
modules/video_coding/codecs/vp9/test/vp9_impl_unittest.cc
Normal file
201
modules/video_coding/codecs/vp9/test/vp9_impl_unittest.cc
Normal file
@ -0,0 +1,201 @@
|
||||
/*
|
||||
* Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "webrtc/common_video/libyuv/include/webrtc_libyuv.h"
|
||||
#include "webrtc/modules/video_coding/codecs/vp9/include/vp9.h"
|
||||
#include "webrtc/modules/video_coding/codecs/test/video_codec_test.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
constexpr uint32_t kTimestampIncrementPerFrame = 3000;
|
||||
} // namespace
|
||||
|
||||
class TestVp9Impl : public VideoCodecTest {
|
||||
protected:
|
||||
VideoEncoder* CreateEncoder() override { return VP9Encoder::Create(); }
|
||||
|
||||
VideoDecoder* CreateDecoder() override { return VP9Decoder::Create(); }
|
||||
|
||||
VideoCodec codec_settings() override {
|
||||
VideoCodec codec_settings;
|
||||
codec_settings.codecType = webrtc::kVideoCodecVP9;
|
||||
codec_settings.VP9()->numberOfTemporalLayers = 1;
|
||||
codec_settings.VP9()->numberOfSpatialLayers = 1;
|
||||
return codec_settings;
|
||||
}
|
||||
|
||||
void ExpectFrameWith(int16_t picture_id,
|
||||
int tl0_pic_idx,
|
||||
uint8_t temporal_idx) {
|
||||
EncodedImage encoded_frame;
|
||||
CodecSpecificInfo codec_specific_info;
|
||||
ASSERT_TRUE(WaitForEncodedFrame(&encoded_frame, &codec_specific_info));
|
||||
EXPECT_EQ(picture_id, codec_specific_info.codecSpecific.VP9.picture_id);
|
||||
EXPECT_EQ(tl0_pic_idx, codec_specific_info.codecSpecific.VP9.tl0_pic_idx);
|
||||
EXPECT_EQ(temporal_idx, codec_specific_info.codecSpecific.VP9.temporal_idx);
|
||||
}
|
||||
};
|
||||
|
||||
// Disabled on ios as flake, see https://crbug.com/webrtc/7057
|
||||
#if defined(WEBRTC_IOS)
|
||||
TEST_F(TestVp9Impl, DISABLED_EncodeDecode) {
|
||||
#else
|
||||
TEST_F(TestVp9Impl, EncodeDecode) {
|
||||
#endif
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
|
||||
encoder_->Encode(*input_frame_, nullptr, nullptr));
|
||||
EncodedImage encoded_frame;
|
||||
CodecSpecificInfo codec_specific_info;
|
||||
ASSERT_TRUE(WaitForEncodedFrame(&encoded_frame, &codec_specific_info));
|
||||
// First frame should be a key frame.
|
||||
encoded_frame._frameType = kVideoFrameKey;
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
|
||||
decoder_->Decode(encoded_frame, false, nullptr));
|
||||
std::unique_ptr<VideoFrame> decoded_frame;
|
||||
rtc::Optional<uint8_t> decoded_qp;
|
||||
ASSERT_TRUE(WaitForDecodedFrame(&decoded_frame, &decoded_qp));
|
||||
ASSERT_TRUE(decoded_frame);
|
||||
EXPECT_GT(I420PSNR(input_frame_.get(), decoded_frame.get()), 36);
|
||||
}
|
||||
|
||||
// We only test the encoder here, since the decoded frame rotation is set based
|
||||
// on the CVO RTP header extension in VCMDecodedFrameCallback::Decoded.
|
||||
// TODO(brandtr): Consider passing through the rotation flag through the decoder
|
||||
// in the same way as done in the encoder.
|
||||
TEST_F(TestVp9Impl, EncodedRotationEqualsInputRotation) {
|
||||
input_frame_->set_rotation(kVideoRotation_0);
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
|
||||
encoder_->Encode(*input_frame_, nullptr, nullptr));
|
||||
EncodedImage encoded_frame;
|
||||
CodecSpecificInfo codec_specific_info;
|
||||
ASSERT_TRUE(WaitForEncodedFrame(&encoded_frame, &codec_specific_info));
|
||||
EXPECT_EQ(kVideoRotation_0, encoded_frame.rotation_);
|
||||
|
||||
input_frame_->set_rotation(kVideoRotation_90);
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
|
||||
encoder_->Encode(*input_frame_, nullptr, nullptr));
|
||||
ASSERT_TRUE(WaitForEncodedFrame(&encoded_frame, &codec_specific_info));
|
||||
EXPECT_EQ(kVideoRotation_90, encoded_frame.rotation_);
|
||||
}
|
||||
|
||||
TEST_F(TestVp9Impl, DecodedQpEqualsEncodedQp) {
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
|
||||
encoder_->Encode(*input_frame_, nullptr, nullptr));
|
||||
EncodedImage encoded_frame;
|
||||
CodecSpecificInfo codec_specific_info;
|
||||
ASSERT_TRUE(WaitForEncodedFrame(&encoded_frame, &codec_specific_info));
|
||||
// First frame should be a key frame.
|
||||
encoded_frame._frameType = kVideoFrameKey;
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
|
||||
decoder_->Decode(encoded_frame, false, nullptr));
|
||||
std::unique_ptr<VideoFrame> decoded_frame;
|
||||
rtc::Optional<uint8_t> decoded_qp;
|
||||
ASSERT_TRUE(WaitForDecodedFrame(&decoded_frame, &decoded_qp));
|
||||
ASSERT_TRUE(decoded_frame);
|
||||
ASSERT_TRUE(decoded_qp);
|
||||
EXPECT_EQ(encoded_frame.qp_, *decoded_qp);
|
||||
}
|
||||
|
||||
TEST_F(TestVp9Impl, ParserQpEqualsEncodedQp) {
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
|
||||
encoder_->Encode(*input_frame_, nullptr, nullptr));
|
||||
EncodedImage encoded_frame;
|
||||
CodecSpecificInfo codec_specific_info;
|
||||
ASSERT_TRUE(WaitForEncodedFrame(&encoded_frame, &codec_specific_info));
|
||||
|
||||
int qp = 0;
|
||||
ASSERT_TRUE(vp9::GetQp(encoded_frame._buffer, encoded_frame._length, &qp));
|
||||
|
||||
EXPECT_EQ(encoded_frame.qp_, qp);
|
||||
}
|
||||
|
||||
TEST_F(TestVp9Impl, EncoderRetainsRtpStateAfterRelease) {
|
||||
// Override default settings.
|
||||
codec_settings_.VP9()->numberOfTemporalLayers = 2;
|
||||
// Tl0PidIdx is only used in non-flexible mode.
|
||||
codec_settings_.VP9()->flexibleMode = false;
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
|
||||
encoder_->InitEncode(&codec_settings_, 1 /* number of cores */,
|
||||
0 /* max payload size (unused) */));
|
||||
|
||||
// Temporal layer 0.
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
|
||||
encoder_->Encode(*input_frame_, nullptr, nullptr));
|
||||
EncodedImage encoded_frame;
|
||||
CodecSpecificInfo codec_specific_info;
|
||||
ASSERT_TRUE(WaitForEncodedFrame(&encoded_frame, &codec_specific_info));
|
||||
int16_t picture_id = codec_specific_info.codecSpecific.VP9.picture_id;
|
||||
int tl0_pic_idx = codec_specific_info.codecSpecific.VP9.tl0_pic_idx;
|
||||
EXPECT_EQ(0, codec_specific_info.codecSpecific.VP9.temporal_idx);
|
||||
|
||||
// Temporal layer 1.
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() +
|
||||
kTimestampIncrementPerFrame);
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
|
||||
encoder_->Encode(*input_frame_, nullptr, nullptr));
|
||||
ExpectFrameWith((picture_id + 1) % (1 << 15), tl0_pic_idx, 1);
|
||||
|
||||
// Temporal layer 0.
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() +
|
||||
kTimestampIncrementPerFrame);
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
|
||||
encoder_->Encode(*input_frame_, nullptr, nullptr));
|
||||
ExpectFrameWith((picture_id + 2) % (1 << 15), (tl0_pic_idx + 1) % (1 << 8),
|
||||
0);
|
||||
|
||||
// Temporal layer 1.
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() +
|
||||
kTimestampIncrementPerFrame);
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
|
||||
encoder_->Encode(*input_frame_, nullptr, nullptr));
|
||||
ExpectFrameWith((picture_id + 3) % (1 << 15), (tl0_pic_idx + 1) % (1 << 8),
|
||||
1);
|
||||
|
||||
// Reinit.
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK, encoder_->Release());
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
|
||||
encoder_->InitEncode(&codec_settings_, 1 /* number of cores */,
|
||||
0 /* max payload size (unused) */));
|
||||
|
||||
// Temporal layer 0.
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() +
|
||||
kTimestampIncrementPerFrame);
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
|
||||
encoder_->Encode(*input_frame_, nullptr, nullptr));
|
||||
ExpectFrameWith((picture_id + 4) % (1 << 15), (tl0_pic_idx + 2) % (1 << 8),
|
||||
0);
|
||||
|
||||
// Temporal layer 1.
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() +
|
||||
kTimestampIncrementPerFrame);
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
|
||||
encoder_->Encode(*input_frame_, nullptr, nullptr));
|
||||
ExpectFrameWith((picture_id + 5) % (1 << 15), (tl0_pic_idx + 2) % (1 << 8),
|
||||
1);
|
||||
|
||||
// Temporal layer 0.
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() +
|
||||
kTimestampIncrementPerFrame);
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
|
||||
encoder_->Encode(*input_frame_, nullptr, nullptr));
|
||||
ExpectFrameWith((picture_id + 6) % (1 << 15), (tl0_pic_idx + 3) % (1 << 8),
|
||||
0);
|
||||
|
||||
// Temporal layer 1.
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() +
|
||||
kTimestampIncrementPerFrame);
|
||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
|
||||
encoder_->Encode(*input_frame_, nullptr, nullptr));
|
||||
ExpectFrameWith((picture_id + 7) % (1 << 15), (tl0_pic_idx + 3) % (1 << 8),
|
||||
1);
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
139
modules/video_coding/codecs/vp9/vp9_frame_buffer_pool.cc
Normal file
139
modules/video_coding/codecs/vp9/vp9_frame_buffer_pool.cc
Normal file
@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "webrtc/modules/video_coding/codecs/vp9/vp9_frame_buffer_pool.h"
|
||||
|
||||
#include "vpx/vpx_codec.h"
|
||||
#include "vpx/vpx_decoder.h"
|
||||
#include "vpx/vpx_frame_buffer.h"
|
||||
|
||||
#include "webrtc/rtc_base/checks.h"
|
||||
#include "webrtc/rtc_base/logging.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
uint8_t* Vp9FrameBufferPool::Vp9FrameBuffer::GetData() {
|
||||
return data_.data<uint8_t>();
|
||||
}
|
||||
|
||||
size_t Vp9FrameBufferPool::Vp9FrameBuffer::GetDataSize() const {
|
||||
return data_.size();
|
||||
}
|
||||
|
||||
void Vp9FrameBufferPool::Vp9FrameBuffer::SetSize(size_t size) {
|
||||
data_.SetSize(size);
|
||||
}
|
||||
|
||||
bool Vp9FrameBufferPool::InitializeVpxUsePool(
|
||||
vpx_codec_ctx* vpx_codec_context) {
|
||||
RTC_DCHECK(vpx_codec_context);
|
||||
// Tell libvpx to use this pool.
|
||||
if (vpx_codec_set_frame_buffer_functions(
|
||||
// In which context to use these callback functions.
|
||||
vpx_codec_context,
|
||||
// Called by libvpx when it needs another frame buffer.
|
||||
&Vp9FrameBufferPool::VpxGetFrameBuffer,
|
||||
// Called by libvpx when it no longer uses a frame buffer.
|
||||
&Vp9FrameBufferPool::VpxReleaseFrameBuffer,
|
||||
// |this| will be passed as |user_priv| to VpxGetFrameBuffer.
|
||||
this)) {
|
||||
// Failed to configure libvpx to use Vp9FrameBufferPool.
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
rtc::scoped_refptr<Vp9FrameBufferPool::Vp9FrameBuffer>
|
||||
Vp9FrameBufferPool::GetFrameBuffer(size_t min_size) {
|
||||
RTC_DCHECK_GT(min_size, 0);
|
||||
rtc::scoped_refptr<Vp9FrameBuffer> available_buffer = nullptr;
|
||||
{
|
||||
rtc::CritScope cs(&buffers_lock_);
|
||||
// Do we have a buffer we can recycle?
|
||||
for (const auto& buffer : allocated_buffers_) {
|
||||
if (buffer->HasOneRef()) {
|
||||
available_buffer = buffer;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Otherwise create one.
|
||||
if (available_buffer == nullptr) {
|
||||
available_buffer = new rtc::RefCountedObject<Vp9FrameBuffer>();
|
||||
allocated_buffers_.push_back(available_buffer);
|
||||
if (allocated_buffers_.size() > max_num_buffers_) {
|
||||
LOG(LS_WARNING)
|
||||
<< allocated_buffers_.size() << " Vp9FrameBuffers have been "
|
||||
<< "allocated by a Vp9FrameBufferPool (exceeding what is "
|
||||
<< "considered reasonable, " << max_num_buffers_ << ").";
|
||||
|
||||
// TODO(phoglund): this limit is being hit in tests since Oct 5 2016.
|
||||
// See https://bugs.chromium.org/p/webrtc/issues/detail?id=6484.
|
||||
// RTC_NOTREACHED();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
available_buffer->SetSize(min_size);
|
||||
return available_buffer;
|
||||
}
|
||||
|
||||
int Vp9FrameBufferPool::GetNumBuffersInUse() const {
|
||||
int num_buffers_in_use = 0;
|
||||
rtc::CritScope cs(&buffers_lock_);
|
||||
for (const auto& buffer : allocated_buffers_) {
|
||||
if (!buffer->HasOneRef())
|
||||
++num_buffers_in_use;
|
||||
}
|
||||
return num_buffers_in_use;
|
||||
}
|
||||
|
||||
void Vp9FrameBufferPool::ClearPool() {
|
||||
rtc::CritScope cs(&buffers_lock_);
|
||||
allocated_buffers_.clear();
|
||||
}
|
||||
|
||||
// static
|
||||
int32_t Vp9FrameBufferPool::VpxGetFrameBuffer(void* user_priv,
|
||||
size_t min_size,
|
||||
vpx_codec_frame_buffer* fb) {
|
||||
RTC_DCHECK(user_priv);
|
||||
RTC_DCHECK(fb);
|
||||
Vp9FrameBufferPool* pool = static_cast<Vp9FrameBufferPool*>(user_priv);
|
||||
|
||||
rtc::scoped_refptr<Vp9FrameBuffer> buffer = pool->GetFrameBuffer(min_size);
|
||||
fb->data = buffer->GetData();
|
||||
fb->size = buffer->GetDataSize();
|
||||
// Store Vp9FrameBuffer* in |priv| for use in VpxReleaseFrameBuffer.
|
||||
// This also makes vpx_codec_get_frame return images with their |fb_priv| set
|
||||
// to |buffer| which is important for external reference counting.
|
||||
// Release from refptr so that the buffer's |ref_count_| remains 1 when
|
||||
// |buffer| goes out of scope.
|
||||
fb->priv = static_cast<void*>(buffer.release());
|
||||
return 0;
|
||||
}
|
||||
|
||||
// static
|
||||
int32_t Vp9FrameBufferPool::VpxReleaseFrameBuffer(void* user_priv,
|
||||
vpx_codec_frame_buffer* fb) {
|
||||
RTC_DCHECK(user_priv);
|
||||
RTC_DCHECK(fb);
|
||||
Vp9FrameBuffer* buffer = static_cast<Vp9FrameBuffer*>(fb->priv);
|
||||
if (buffer != nullptr) {
|
||||
buffer->Release();
|
||||
// When libvpx fails to decode and you continue to try to decode (and fail)
|
||||
// libvpx can for some reason try to release the same buffer multiple times.
|
||||
// Setting |priv| to null protects against trying to Release multiple times.
|
||||
fb->priv = nullptr;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
124
modules/video_coding/codecs/vp9/vp9_frame_buffer_pool.h
Normal file
124
modules/video_coding/codecs/vp9/vp9_frame_buffer_pool.h
Normal file
@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_VIDEO_CODING_CODECS_VP9_VP9_FRAME_BUFFER_POOL_H_
|
||||
#define WEBRTC_MODULES_VIDEO_CODING_CODECS_VP9_VP9_FRAME_BUFFER_POOL_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/rtc_base/basictypes.h"
|
||||
#include "webrtc/rtc_base/buffer.h"
|
||||
#include "webrtc/rtc_base/criticalsection.h"
|
||||
#include "webrtc/rtc_base/refcount.h"
|
||||
#include "webrtc/rtc_base/scoped_ref_ptr.h"
|
||||
|
||||
struct vpx_codec_ctx;
|
||||
struct vpx_codec_frame_buffer;
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// This memory pool is used to serve buffers to libvpx for decoding purposes in
|
||||
// VP9, which is set up in InitializeVPXUsePool. After the initialization any
|
||||
// time libvpx wants to decode a frame it will use buffers provided and released
|
||||
// through VpxGetFrameBuffer and VpxReleaseFrameBuffer.
|
||||
// The benefit of owning the pool that libvpx relies on for decoding is that the
|
||||
// decoded frames returned by libvpx (from vpx_codec_get_frame) use parts of our
|
||||
// buffers for the decoded image data. By retaining ownership of this buffer
|
||||
// using scoped_refptr, the image buffer can be reused by VideoFrames and no
|
||||
// frame copy has to occur during decoding and frame delivery.
|
||||
//
|
||||
// Pseudo example usage case:
|
||||
// Vp9FrameBufferPool pool;
|
||||
// pool.InitializeVpxUsePool(decoder_ctx);
|
||||
// ...
|
||||
//
|
||||
// // During decoding, libvpx will get and release buffers from the pool.
|
||||
// vpx_codec_decode(decoder_ctx, ...);
|
||||
//
|
||||
// vpx_image_t* img = vpx_codec_get_frame(decoder_ctx, &iter);
|
||||
// // Important to use scoped_refptr to protect it against being recycled by
|
||||
// // the pool.
|
||||
// scoped_refptr<Vp9FrameBuffer> img_buffer = (Vp9FrameBuffer*)img->fb_priv;
|
||||
// ...
|
||||
//
|
||||
// // Destroying the codec will make libvpx release any buffers it was using.
|
||||
// vpx_codec_destroy(decoder_ctx);
|
||||
class Vp9FrameBufferPool {
|
||||
public:
|
||||
class Vp9FrameBuffer : public rtc::RefCountInterface {
|
||||
public:
|
||||
uint8_t* GetData();
|
||||
size_t GetDataSize() const;
|
||||
void SetSize(size_t size);
|
||||
|
||||
virtual bool HasOneRef() const = 0;
|
||||
|
||||
private:
|
||||
// Data as an easily resizable buffer.
|
||||
rtc::Buffer data_;
|
||||
};
|
||||
|
||||
// Configures libvpx to, in the specified context, use this memory pool for
|
||||
// buffers used to decompress frames. This is only supported for VP9.
|
||||
bool InitializeVpxUsePool(vpx_codec_ctx* vpx_codec_context);
|
||||
|
||||
// Gets a frame buffer of at least |min_size|, recycling an available one or
|
||||
// creating a new one. When no longer referenced from the outside the buffer
|
||||
// becomes recyclable.
|
||||
rtc::scoped_refptr<Vp9FrameBuffer> GetFrameBuffer(size_t min_size);
|
||||
// Gets the number of buffers currently in use (not ready to be recycled).
|
||||
int GetNumBuffersInUse() const;
|
||||
// Releases allocated buffers, deleting available buffers. Buffers in use are
|
||||
// not deleted until they are no longer referenced.
|
||||
void ClearPool();
|
||||
|
||||
// InitializeVpxUsePool configures libvpx to call this function when it needs
|
||||
// a new frame buffer. Parameters:
|
||||
// |user_priv| Private data passed to libvpx, InitializeVpxUsePool sets it up
|
||||
// to be a pointer to the pool.
|
||||
// |min_size| Minimum size needed by libvpx (to decompress a frame).
|
||||
// |fb| Pointer to the libvpx frame buffer object, this is updated to
|
||||
// use the pool's buffer.
|
||||
// Returns 0 on success. Returns < 0 on failure.
|
||||
static int32_t VpxGetFrameBuffer(void* user_priv,
|
||||
size_t min_size,
|
||||
vpx_codec_frame_buffer* fb);
|
||||
|
||||
// InitializeVpxUsePool configures libvpx to call this function when it has
|
||||
// finished using one of the pool's frame buffer. Parameters:
|
||||
// |user_priv| Private data passed to libvpx, InitializeVpxUsePool sets it up
|
||||
// to be a pointer to the pool.
|
||||
// |fb| Pointer to the libvpx frame buffer object, its |priv| will be
|
||||
// a pointer to one of the pool's Vp9FrameBuffer.
|
||||
static int32_t VpxReleaseFrameBuffer(void* user_priv,
|
||||
vpx_codec_frame_buffer* fb);
|
||||
|
||||
private:
|
||||
// Protects |allocated_buffers_|.
|
||||
rtc::CriticalSection buffers_lock_;
|
||||
// All buffers, in use or ready to be recycled.
|
||||
std::vector<rtc::scoped_refptr<Vp9FrameBuffer>> allocated_buffers_
|
||||
RTC_GUARDED_BY(buffers_lock_);
|
||||
// If more buffers than this are allocated we print warnings and crash if in
|
||||
// debug mode. VP9 is defined to have 8 reference buffers, of which 3 can be
|
||||
// referenced by any frame, see
|
||||
// https://tools.ietf.org/html/draft-grange-vp9-bitstream-00#section-2.2.2.
|
||||
// Assuming VP9 holds on to at most 8 buffers, any more buffers than that
|
||||
// would have to be by application code. Decoded frames should not be
|
||||
// referenced for longer than necessary. If we allow ~60 additional buffers
|
||||
// then the application has ~1 second to e.g. render each frame of a 60 fps
|
||||
// video.
|
||||
static const size_t max_num_buffers_ = 68;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_VIDEO_CODING_CODECS_VP9_VP9_FRAME_BUFFER_POOL_H_
|
||||
1018
modules/video_coding/codecs/vp9/vp9_impl.cc
Normal file
1018
modules/video_coding/codecs/vp9/vp9_impl.cc
Normal file
File diff suppressed because it is too large
Load Diff
172
modules/video_coding/codecs/vp9/vp9_impl.h
Normal file
172
modules/video_coding/codecs/vp9/vp9_impl.h
Normal file
@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_VIDEO_CODING_CODECS_VP9_VP9_IMPL_H_
|
||||
#define WEBRTC_MODULES_VIDEO_CODING_CODECS_VP9_VP9_IMPL_H_
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/modules/video_coding/codecs/vp9/include/vp9.h"
|
||||
#include "webrtc/modules/video_coding/codecs/vp9/vp9_frame_buffer_pool.h"
|
||||
|
||||
#include "vpx/vp8cx.h"
|
||||
#include "vpx/vpx_decoder.h"
|
||||
#include "vpx/vpx_encoder.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class ScreenshareLayersVP9;
|
||||
|
||||
class VP9EncoderImpl : public VP9Encoder {
|
||||
public:
|
||||
VP9EncoderImpl();
|
||||
|
||||
virtual ~VP9EncoderImpl();
|
||||
|
||||
int Release() override;
|
||||
|
||||
int InitEncode(const VideoCodec* codec_settings,
|
||||
int number_of_cores,
|
||||
size_t max_payload_size) override;
|
||||
|
||||
int Encode(const VideoFrame& input_image,
|
||||
const CodecSpecificInfo* codec_specific_info,
|
||||
const std::vector<FrameType>* frame_types) override;
|
||||
|
||||
int RegisterEncodeCompleteCallback(EncodedImageCallback* callback) override;
|
||||
|
||||
int SetChannelParameters(uint32_t packet_loss, int64_t rtt) override;
|
||||
|
||||
int SetRateAllocation(const BitrateAllocation& bitrate_allocation,
|
||||
uint32_t frame_rate) override;
|
||||
|
||||
const char* ImplementationName() const override;
|
||||
|
||||
struct LayerFrameRefSettings {
|
||||
int8_t upd_buf = -1; // -1 - no update, 0..7 - update buffer 0..7
|
||||
int8_t ref_buf1 = -1; // -1 - no reference, 0..7 - reference buffer 0..7
|
||||
int8_t ref_buf2 = -1; // -1 - no reference, 0..7 - reference buffer 0..7
|
||||
int8_t ref_buf3 = -1; // -1 - no reference, 0..7 - reference buffer 0..7
|
||||
};
|
||||
|
||||
struct SuperFrameRefSettings {
|
||||
LayerFrameRefSettings layer[kMaxVp9NumberOfSpatialLayers];
|
||||
uint8_t start_layer = 0; // The first spatial layer to be encoded.
|
||||
uint8_t stop_layer = 0; // The last spatial layer to be encoded.
|
||||
bool is_keyframe = false;
|
||||
};
|
||||
|
||||
private:
|
||||
// Determine number of encoder threads to use.
|
||||
int NumberOfThreads(int width, int height, int number_of_cores);
|
||||
|
||||
// Call encoder initialize function and set control settings.
|
||||
int InitAndSetControlSettings(const VideoCodec* inst);
|
||||
|
||||
void PopulateCodecSpecific(CodecSpecificInfo* codec_specific,
|
||||
const vpx_codec_cx_pkt& pkt,
|
||||
uint32_t timestamp);
|
||||
|
||||
bool ExplicitlyConfiguredSpatialLayers() const;
|
||||
bool SetSvcRates();
|
||||
|
||||
// Used for flexible mode to set the flags and buffer references used
|
||||
// by the encoder. Also calculates the references used by the RTP
|
||||
// packetizer.
|
||||
//
|
||||
// Has to be called for every frame (keyframes included) to update the
|
||||
// state used to calculate references.
|
||||
vpx_svc_ref_frame_config GenerateRefsAndFlags(
|
||||
const SuperFrameRefSettings& settings);
|
||||
|
||||
virtual int GetEncodedLayerFrame(const vpx_codec_cx_pkt* pkt);
|
||||
|
||||
// Callback function for outputting packets per spatial layer.
|
||||
static void EncoderOutputCodedPacketCallback(vpx_codec_cx_pkt* pkt,
|
||||
void* user_data);
|
||||
|
||||
// Determine maximum target for Intra frames
|
||||
//
|
||||
// Input:
|
||||
// - optimal_buffer_size : Optimal buffer size
|
||||
// Return Value : Max target size for Intra frames represented as
|
||||
// percentage of the per frame bandwidth
|
||||
uint32_t MaxIntraTarget(uint32_t optimal_buffer_size);
|
||||
|
||||
EncodedImage encoded_image_;
|
||||
EncodedImageCallback* encoded_complete_callback_;
|
||||
VideoCodec codec_;
|
||||
bool inited_;
|
||||
int64_t timestamp_;
|
||||
int cpu_speed_;
|
||||
uint32_t rc_max_intra_target_;
|
||||
vpx_codec_ctx_t* encoder_;
|
||||
vpx_codec_enc_cfg_t* config_;
|
||||
vpx_image_t* raw_;
|
||||
vpx_svc_extra_cfg_t svc_params_;
|
||||
const VideoFrame* input_image_;
|
||||
GofInfoVP9 gof_; // Contains each frame's temporal information for
|
||||
// non-flexible mode.
|
||||
size_t frames_since_kf_;
|
||||
uint8_t num_temporal_layers_;
|
||||
uint8_t num_spatial_layers_;
|
||||
|
||||
// Used for flexible mode.
|
||||
bool is_flexible_mode_;
|
||||
int64_t buffer_updated_at_frame_[kNumVp9Buffers];
|
||||
int64_t frames_encoded_;
|
||||
uint8_t num_ref_pics_[kMaxVp9NumberOfSpatialLayers];
|
||||
uint8_t p_diff_[kMaxVp9NumberOfSpatialLayers][kMaxVp9RefPics];
|
||||
std::unique_ptr<ScreenshareLayersVP9> spatial_layer_;
|
||||
|
||||
// RTP state.
|
||||
uint16_t picture_id_;
|
||||
uint8_t tl0_pic_idx_; // Only used in non-flexible mode.
|
||||
};
|
||||
|
||||
class VP9DecoderImpl : public VP9Decoder {
|
||||
public:
|
||||
VP9DecoderImpl();
|
||||
|
||||
virtual ~VP9DecoderImpl();
|
||||
|
||||
int InitDecode(const VideoCodec* inst, int number_of_cores) override;
|
||||
|
||||
int Decode(const EncodedImage& input_image,
|
||||
bool missing_frames,
|
||||
const RTPFragmentationHeader* fragmentation,
|
||||
const CodecSpecificInfo* codec_specific_info,
|
||||
int64_t /*render_time_ms*/) override;
|
||||
|
||||
int RegisterDecodeCompleteCallback(DecodedImageCallback* callback) override;
|
||||
|
||||
int Release() override;
|
||||
|
||||
const char* ImplementationName() const override;
|
||||
|
||||
private:
|
||||
int ReturnFrame(const vpx_image_t* img,
|
||||
uint32_t timestamp,
|
||||
int64_t ntp_time_ms,
|
||||
int qp);
|
||||
|
||||
// Memory pool used to share buffers between libvpx and webrtc.
|
||||
Vp9FrameBufferPool frame_buffer_pool_;
|
||||
DecodedImageCallback* decode_complete_callback_;
|
||||
bool inited_;
|
||||
vpx_codec_ctx_t* decoder_;
|
||||
VideoCodec codec_;
|
||||
bool key_frame_required_;
|
||||
};
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_VIDEO_CODING_CODECS_VP9_VP9_IMPL_H_
|
||||
39
modules/video_coding/codecs/vp9/vp9_noop.cc
Normal file
39
modules/video_coding/codecs/vp9/vp9_noop.cc
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*
|
||||
*/
|
||||
|
||||
#if !defined(RTC_DISABLE_VP9)
|
||||
#error
|
||||
#endif // !defined(RTC_DISABLE_VP9)
|
||||
|
||||
#include "webrtc/modules/video_coding/codecs/vp9/include/vp9.h"
|
||||
#include "webrtc/rtc_base/checks.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
bool VP9Encoder::IsSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
VP9Encoder* VP9Encoder::Create() {
|
||||
RTC_NOTREACHED();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool VP9Decoder::IsSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
VP9Decoder* VP9Decoder::Create() {
|
||||
RTC_NOTREACHED();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -0,0 +1,324 @@
|
||||
/*
|
||||
* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
|
||||
#include "vpx/vp8cx.h"
|
||||
#include "webrtc/modules/video_coding/codecs/vp9/screenshare_layers.h"
|
||||
#include "webrtc/modules/video_coding/codecs/vp9/vp9_impl.h"
|
||||
#include "webrtc/rtc_base/logging.h"
|
||||
#include "webrtc/system_wrappers/include/clock.h"
|
||||
#include "webrtc/test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
typedef VP9EncoderImpl::SuperFrameRefSettings Settings;
|
||||
|
||||
const uint32_t kTickFrequency = 90000;
|
||||
|
||||
class ScreenshareLayerTestVP9 : public ::testing::Test {
|
||||
protected:
|
||||
ScreenshareLayerTestVP9() : clock_(0) {}
|
||||
virtual ~ScreenshareLayerTestVP9() {}
|
||||
|
||||
void InitScreenshareLayers(int layers) {
|
||||
layers_.reset(new ScreenshareLayersVP9(layers));
|
||||
}
|
||||
|
||||
void ConfigureBitrateForLayer(int kbps, uint8_t layer_id) {
|
||||
layers_->ConfigureBitrate(kbps, layer_id);
|
||||
}
|
||||
|
||||
void AdvanceTime(int64_t milliseconds) {
|
||||
clock_.AdvanceTimeMilliseconds(milliseconds);
|
||||
}
|
||||
|
||||
void AddKilobitsToLayer(int kilobits, uint8_t layer_id) {
|
||||
layers_->LayerFrameEncoded(kilobits * 1000 / 8, layer_id);
|
||||
}
|
||||
|
||||
void EqualRefsForLayer(const Settings& actual, uint8_t layer_id) {
|
||||
EXPECT_EQ(expected_.layer[layer_id].upd_buf,
|
||||
actual.layer[layer_id].upd_buf);
|
||||
EXPECT_EQ(expected_.layer[layer_id].ref_buf1,
|
||||
actual.layer[layer_id].ref_buf1);
|
||||
EXPECT_EQ(expected_.layer[layer_id].ref_buf2,
|
||||
actual.layer[layer_id].ref_buf2);
|
||||
EXPECT_EQ(expected_.layer[layer_id].ref_buf3,
|
||||
actual.layer[layer_id].ref_buf3);
|
||||
}
|
||||
|
||||
void EqualRefs(const Settings& actual) {
|
||||
for (unsigned int layer_id = 0; layer_id < kMaxVp9NumberOfSpatialLayers;
|
||||
++layer_id) {
|
||||
EqualRefsForLayer(actual, layer_id);
|
||||
}
|
||||
}
|
||||
|
||||
void EqualStartStopKeyframe(const Settings& actual) {
|
||||
EXPECT_EQ(expected_.start_layer, actual.start_layer);
|
||||
EXPECT_EQ(expected_.stop_layer, actual.stop_layer);
|
||||
EXPECT_EQ(expected_.is_keyframe, actual.is_keyframe);
|
||||
}
|
||||
|
||||
// Check that the settings returned by GetSuperFrameSettings() is
|
||||
// equal to the expected_ settings.
|
||||
void EqualToExpected() {
|
||||
uint32_t frame_timestamp_ =
|
||||
clock_.TimeInMilliseconds() * (kTickFrequency / 1000);
|
||||
Settings actual =
|
||||
layers_->GetSuperFrameSettings(frame_timestamp_, expected_.is_keyframe);
|
||||
EqualRefs(actual);
|
||||
EqualStartStopKeyframe(actual);
|
||||
}
|
||||
|
||||
Settings expected_;
|
||||
SimulatedClock clock_;
|
||||
std::unique_ptr<ScreenshareLayersVP9> layers_;
|
||||
};
|
||||
|
||||
TEST_F(ScreenshareLayerTestVP9, NoRefsOnKeyFrame) {
|
||||
const int kNumLayers = kMaxVp9NumberOfSpatialLayers;
|
||||
InitScreenshareLayers(kNumLayers);
|
||||
expected_.start_layer = 0;
|
||||
expected_.stop_layer = kNumLayers - 1;
|
||||
|
||||
for (int l = 0; l < kNumLayers; ++l) {
|
||||
expected_.layer[l].upd_buf = l;
|
||||
}
|
||||
expected_.is_keyframe = true;
|
||||
EqualToExpected();
|
||||
|
||||
for (int l = 0; l < kNumLayers; ++l) {
|
||||
expected_.layer[l].ref_buf1 = l;
|
||||
}
|
||||
expected_.is_keyframe = false;
|
||||
EqualToExpected();
|
||||
}
|
||||
|
||||
// Test if it is possible to send at a high bitrate (over the threshold)
|
||||
// after a longer period of low bitrate. This should not be possible.
|
||||
TEST_F(ScreenshareLayerTestVP9, DontAccumelateAvailableBitsOverTime) {
|
||||
InitScreenshareLayers(2);
|
||||
ConfigureBitrateForLayer(100, 0);
|
||||
|
||||
expected_.layer[0].upd_buf = 0;
|
||||
expected_.layer[0].ref_buf1 = 0;
|
||||
expected_.layer[1].upd_buf = 1;
|
||||
expected_.layer[1].ref_buf1 = 1;
|
||||
expected_.start_layer = 0;
|
||||
expected_.stop_layer = 1;
|
||||
|
||||
// Send 10 frames at a low bitrate (50 kbps)
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
AdvanceTime(200);
|
||||
EqualToExpected();
|
||||
AddKilobitsToLayer(10, 0);
|
||||
}
|
||||
|
||||
AdvanceTime(200);
|
||||
EqualToExpected();
|
||||
AddKilobitsToLayer(301, 0);
|
||||
|
||||
// Send 10 frames at a high bitrate (200 kbps)
|
||||
expected_.start_layer = 1;
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
AdvanceTime(200);
|
||||
EqualToExpected();
|
||||
AddKilobitsToLayer(40, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Test if used bits are accumelated over layers, as they should;
|
||||
TEST_F(ScreenshareLayerTestVP9, AccumelateUsedBitsOverLayers) {
|
||||
const int kNumLayers = kMaxVp9NumberOfSpatialLayers;
|
||||
InitScreenshareLayers(kNumLayers);
|
||||
for (int l = 0; l < kNumLayers - 1; ++l)
|
||||
ConfigureBitrateForLayer(100, l);
|
||||
for (int l = 0; l < kNumLayers; ++l) {
|
||||
expected_.layer[l].upd_buf = l;
|
||||
expected_.layer[l].ref_buf1 = l;
|
||||
}
|
||||
|
||||
expected_.start_layer = 0;
|
||||
expected_.stop_layer = kNumLayers - 1;
|
||||
EqualToExpected();
|
||||
|
||||
for (int layer = 0; layer < kNumLayers - 1; ++layer) {
|
||||
expected_.start_layer = layer;
|
||||
EqualToExpected();
|
||||
AddKilobitsToLayer(101, layer);
|
||||
}
|
||||
}
|
||||
|
||||
// General testing of the bitrate controller.
|
||||
TEST_F(ScreenshareLayerTestVP9, 2LayerBitrate) {
|
||||
InitScreenshareLayers(2);
|
||||
ConfigureBitrateForLayer(100, 0);
|
||||
|
||||
expected_.layer[0].upd_buf = 0;
|
||||
expected_.layer[1].upd_buf = 1;
|
||||
expected_.layer[0].ref_buf1 = -1;
|
||||
expected_.layer[1].ref_buf1 = -1;
|
||||
expected_.start_layer = 0;
|
||||
expected_.stop_layer = 1;
|
||||
|
||||
expected_.is_keyframe = true;
|
||||
EqualToExpected();
|
||||
AddKilobitsToLayer(100, 0);
|
||||
|
||||
expected_.layer[0].ref_buf1 = 0;
|
||||
expected_.layer[1].ref_buf1 = 1;
|
||||
expected_.is_keyframe = false;
|
||||
AdvanceTime(199);
|
||||
EqualToExpected();
|
||||
AddKilobitsToLayer(100, 0);
|
||||
|
||||
expected_.start_layer = 1;
|
||||
for (int frame = 0; frame < 3; ++frame) {
|
||||
AdvanceTime(200);
|
||||
EqualToExpected();
|
||||
AddKilobitsToLayer(100, 1);
|
||||
}
|
||||
|
||||
// Just before enough bits become available for L0 @0.999 seconds.
|
||||
AdvanceTime(199);
|
||||
EqualToExpected();
|
||||
AddKilobitsToLayer(100, 1);
|
||||
|
||||
// Just after enough bits become available for L0 @1.0001 seconds.
|
||||
expected_.start_layer = 0;
|
||||
AdvanceTime(2);
|
||||
EqualToExpected();
|
||||
AddKilobitsToLayer(100, 0);
|
||||
|
||||
// Keyframes always encode all layers, even if it is over budget.
|
||||
expected_.layer[0].ref_buf1 = -1;
|
||||
expected_.layer[1].ref_buf1 = -1;
|
||||
expected_.is_keyframe = true;
|
||||
AdvanceTime(499);
|
||||
EqualToExpected();
|
||||
expected_.layer[0].ref_buf1 = 0;
|
||||
expected_.layer[1].ref_buf1 = 1;
|
||||
expected_.start_layer = 1;
|
||||
expected_.is_keyframe = false;
|
||||
EqualToExpected();
|
||||
AddKilobitsToLayer(100, 0);
|
||||
|
||||
// 400 kb in L0 --> @3 second mark to fall below the threshold..
|
||||
// just before @2.999 seconds.
|
||||
expected_.is_keyframe = false;
|
||||
AdvanceTime(1499);
|
||||
EqualToExpected();
|
||||
AddKilobitsToLayer(100, 1);
|
||||
|
||||
// just after @3.001 seconds.
|
||||
expected_.start_layer = 0;
|
||||
AdvanceTime(2);
|
||||
EqualToExpected();
|
||||
AddKilobitsToLayer(100, 0);
|
||||
}
|
||||
|
||||
// General testing of the bitrate controller.
|
||||
TEST_F(ScreenshareLayerTestVP9, 3LayerBitrate) {
|
||||
InitScreenshareLayers(3);
|
||||
ConfigureBitrateForLayer(100, 0);
|
||||
ConfigureBitrateForLayer(100, 1);
|
||||
|
||||
for (int l = 0; l < 3; ++l) {
|
||||
expected_.layer[l].upd_buf = l;
|
||||
expected_.layer[l].ref_buf1 = l;
|
||||
}
|
||||
expected_.start_layer = 0;
|
||||
expected_.stop_layer = 2;
|
||||
|
||||
EqualToExpected();
|
||||
AddKilobitsToLayer(105, 0);
|
||||
AddKilobitsToLayer(30, 1);
|
||||
|
||||
AdvanceTime(199);
|
||||
EqualToExpected();
|
||||
AddKilobitsToLayer(105, 0);
|
||||
AddKilobitsToLayer(30, 1);
|
||||
|
||||
expected_.start_layer = 1;
|
||||
AdvanceTime(200);
|
||||
EqualToExpected();
|
||||
AddKilobitsToLayer(130, 1);
|
||||
|
||||
expected_.start_layer = 2;
|
||||
AdvanceTime(200);
|
||||
EqualToExpected();
|
||||
|
||||
// 400 kb in L1 --> @1.0 second mark to fall below threshold.
|
||||
// 210 kb in L0 --> @1.1 second mark to fall below threshold.
|
||||
// Just before L1 @0.999 seconds.
|
||||
AdvanceTime(399);
|
||||
EqualToExpected();
|
||||
|
||||
// Just after L1 @1.001 seconds.
|
||||
expected_.start_layer = 1;
|
||||
AdvanceTime(2);
|
||||
EqualToExpected();
|
||||
|
||||
// Just before L0 @1.099 seconds.
|
||||
AdvanceTime(99);
|
||||
EqualToExpected();
|
||||
|
||||
// Just after L0 @1.101 seconds.
|
||||
expected_.start_layer = 0;
|
||||
AdvanceTime(2);
|
||||
EqualToExpected();
|
||||
|
||||
// @1.1 seconds
|
||||
AdvanceTime(99);
|
||||
EqualToExpected();
|
||||
AddKilobitsToLayer(200, 1);
|
||||
|
||||
expected_.is_keyframe = true;
|
||||
for (int l = 0; l < 3; ++l)
|
||||
expected_.layer[l].ref_buf1 = -1;
|
||||
AdvanceTime(200);
|
||||
EqualToExpected();
|
||||
|
||||
expected_.is_keyframe = false;
|
||||
expected_.start_layer = 2;
|
||||
for (int l = 0; l < 3; ++l)
|
||||
expected_.layer[l].ref_buf1 = l;
|
||||
AdvanceTime(200);
|
||||
EqualToExpected();
|
||||
}
|
||||
|
||||
// Test that the bitrate calculations are
|
||||
// correct when the timestamp wrap.
|
||||
TEST_F(ScreenshareLayerTestVP9, TimestampWrap) {
|
||||
InitScreenshareLayers(2);
|
||||
ConfigureBitrateForLayer(100, 0);
|
||||
|
||||
expected_.layer[0].upd_buf = 0;
|
||||
expected_.layer[0].ref_buf1 = 0;
|
||||
expected_.layer[1].upd_buf = 1;
|
||||
expected_.layer[1].ref_buf1 = 1;
|
||||
expected_.start_layer = 0;
|
||||
expected_.stop_layer = 1;
|
||||
|
||||
// Advance time to just before the timestamp wraps.
|
||||
AdvanceTime(std::numeric_limits<uint32_t>::max() / (kTickFrequency / 1000));
|
||||
EqualToExpected();
|
||||
AddKilobitsToLayer(200, 0);
|
||||
|
||||
// Wrap
|
||||
expected_.start_layer = 1;
|
||||
AdvanceTime(1);
|
||||
EqualToExpected();
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
Reference in New Issue
Block a user