blob: 2993e53aea9e70692918461cdf19dc02ef38f953 [file] [log] [blame]
/*
* 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/main/test/quality_modes_test.h"
#include <iostream>
#include <sstream>
#include <string>
#include <time.h>
#include "webrtc/common_video/libyuv/include/webrtc_libyuv.h"
#include "webrtc/modules/video_coding/main/interface/video_coding.h"
#include "webrtc/modules/video_coding/main/test/test_callbacks.h"
#include "webrtc/modules/video_coding/main/test/test_macros.h"
#include "webrtc/modules/video_coding/main/test/test_util.h"
#include "webrtc/system_wrappers/interface/clock.h"
#include "webrtc/system_wrappers/interface/data_log.h"
#include "webrtc/system_wrappers/interface/data_log.h"
#include "webrtc/test/testsupport/fileutils.h"
#include "webrtc/test/testsupport/metrics/video_metrics.h"
using namespace webrtc;
int qualityModeTest(const CmdArgs& args)
{
SimulatedClock clock(0);
NullEventFactory event_factory;
VideoCodingModule* vcm = VideoCodingModule::Create(&clock, &event_factory);
QualityModesTest QMTest(vcm, &clock);
QMTest.Perform(args);
VideoCodingModule::Destroy(vcm);
return 0;
}
QualityModesTest::QualityModesTest(VideoCodingModule* vcm,
Clock* clock):
NormalTest(vcm, clock),
_vpm()
{
//
}
QualityModesTest::~QualityModesTest()
{
//
}
void
QualityModesTest::Setup(const CmdArgs& args)
{
NormalTest::Setup(args);
_inname = args.inputFile;
_outname = args.outputFile;
fv_outfilename_ = args.fv_outputfile;
filename_testvideo_ =
_inname.substr(_inname.find_last_of("\\/") + 1,_inname.length());
_encodedName = test::OutputPath() + "encoded_qmtest.yuv";
//NATIVE/SOURCE VALUES
_nativeWidth = args.width;
_nativeHeight = args.height;
_nativeFrameRate =args.frameRate;
//TARGET/ENCODER VALUES
_width = args.width;
_height = args.height;
_frameRate = args.frameRate;
_bitRate = args.bitRate;
_flagSSIM = true;
_lengthSourceFrame = 3*_nativeWidth*_nativeHeight/2;
if ((_sourceFile = fopen(_inname.c_str(), "rb")) == NULL)
{
printf("Cannot read file %s.\n", _inname.c_str());
exit(1);
}
if ((_encodedFile = fopen(_encodedName.c_str(), "wb")) == NULL)
{
printf("Cannot write encoded file.\n");
exit(1);
}
if ((_decodedFile = fopen(_outname.c_str(), "wb")) == NULL)
{
printf("Cannot write file %s.\n", _outname.c_str());
exit(1);
}
DataLog::CreateLog();
feature_table_name_ = fv_outfilename_;
DataLog::AddTable(feature_table_name_);
DataLog::AddColumn(feature_table_name_, "motion magnitude", 1);
DataLog::AddColumn(feature_table_name_, "spatial prediction error", 1);
DataLog::AddColumn(feature_table_name_, "spatial pred err horizontal", 1);
DataLog::AddColumn(feature_table_name_, "spatial pred err vertical", 1);
DataLog::AddColumn(feature_table_name_, "width", 1);
DataLog::AddColumn(feature_table_name_, "height", 1);
DataLog::AddColumn(feature_table_name_, "num pixels", 1);
DataLog::AddColumn(feature_table_name_, "frame rate", 1);
DataLog::AddColumn(feature_table_name_, "num frames since drop", 1);
_log.open((test::OutputPath() + "TestLog.txt").c_str(),
std::fstream::out | std::fstream::app);
}
void
QualityModesTest::Print()
{
std::cout << "Quality Modes Test Completed!" << std::endl;
(_log) << "Quality Modes Test Completed!" << std::endl;
(_log) << "Input file: " << _inname << std::endl;
(_log) << "Output file: " << _outname << std::endl;
(_log) << "Total run time: " << _testTotalTime << std::endl;
printf("Total run time: %f s \n", _testTotalTime);
double ActualBitRate = 8.0*( _sumEncBytes / (_frameCnt / _nativeFrameRate));
double actualBitRate = ActualBitRate / 1000.0;
double avgEncTime = _totalEncodeTime / _frameCnt;
double avgDecTime = _totalDecodeTime / _frameCnt;
webrtc::test::QualityMetricsResult psnr,ssim;
I420PSNRFromFiles(_inname.c_str(), _outname.c_str(), _nativeWidth,
_nativeHeight, &psnr);
printf("Actual bitrate: %f kbps\n", actualBitRate);
printf("Target bitrate: %f kbps\n", _bitRate);
( _log) << "Actual bitrate: " << actualBitRate<< " kbps\tTarget: " <<
_bitRate << " kbps" << std::endl;
printf("Average encode time: %f s\n", avgEncTime);
( _log) << "Average encode time: " << avgEncTime << " s" << std::endl;
printf("Average decode time: %f s\n", avgDecTime);
( _log) << "Average decode time: " << avgDecTime << " s" << std::endl;
printf("PSNR: %f \n", psnr.average);
printf("**Number of frames dropped in VPM***%d \n",_numFramesDroppedVPM);
( _log) << "PSNR: " << psnr.average << std::endl;
if (_flagSSIM == 1)
{
printf("***computing SSIM***\n");
I420SSIMFromFiles(_inname.c_str(), _outname.c_str(), _nativeWidth,
_nativeHeight, &ssim);
printf("SSIM: %f \n", ssim.average);
}
(_log) << std::endl;
printf("\nVCM Quality Modes Test: \n\n%i tests completed\n", vcmMacrosTests);
if (vcmMacrosErrors > 0)
{
printf("%i FAILED\n\n", vcmMacrosErrors);
}
else
{
printf("ALL PASSED\n\n");
}
}
void
QualityModesTest::Teardown()
{
_log.close();
fclose(_sourceFile);
fclose(_decodedFile);
fclose(_encodedFile);
return;
}
int32_t
QualityModesTest::Perform(const CmdArgs& args)
{
Setup(args);
// changing bit/frame rate during the test
const float bitRateUpdate[] = {1000};
const float frameRateUpdate[] = {30};
// frame num at which an update will occur
const int updateFrameNum[] = {10000};
uint32_t numChanges = sizeof(updateFrameNum)/sizeof(*updateFrameNum);
uint8_t change = 0;// change counter
_vpm = VideoProcessingModule::Create(1);
EventWrapper* waitEvent = EventWrapper::Create();
VideoCodec codec;//both send and receive
_vcm->InitializeReceiver();
_vcm->InitializeSender();
int32_t NumberOfCodecs = _vcm->NumberOfCodecs();
for (int i = 0; i < NumberOfCodecs; i++)
{
_vcm->Codec(i, &codec);
if(strncmp(codec.plName,"VP8" , 5) == 0)
{
codec.startBitrate = (int)_bitRate;
codec.maxFramerate = (uint8_t) _frameRate;
codec.width = (uint16_t)_width;
codec.height = (uint16_t)_height;
codec.codecSpecific.VP8.frameDroppingOn = false;
// Will also set and init the desired codec
TEST(_vcm->RegisterSendCodec(&codec, 2, 1440) == VCM_OK);
i = NumberOfCodecs;
}
}
// register a decoder (same codec for decoder and encoder )
TEST(_vcm->RegisterReceiveCodec(&codec, 2) == VCM_OK);
/* Callback Settings */
VCMQMDecodeCompleteCallback _decodeCallback(
_decodedFile, _nativeFrameRate, feature_table_name_);
_vcm->RegisterReceiveCallback(&_decodeCallback);
VCMNTEncodeCompleteCallback _encodeCompleteCallback(_encodedFile, *this);
_vcm->RegisterTransportCallback(&_encodeCompleteCallback);
// encode and decode with the same vcm
_encodeCompleteCallback.RegisterReceiverVCM(_vcm);
//quality modes callback
QMTestVideoSettingsCallback QMCallback;
QMCallback.RegisterVCM(_vcm);
QMCallback.RegisterVPM(_vpm);
//_vcm->RegisterVideoQMCallback(&QMCallback);
///////////////////////
/// Start Test
///////////////////////
_vpm->EnableTemporalDecimation(true);
_vpm->EnableContentAnalysis(true);
_vpm->SetInputFrameResampleMode(kFastRescaling);
// disabling internal VCM frame dropper
_vcm->EnableFrameDropper(false);
I420VideoFrame sourceFrame;
I420VideoFrame *decimatedFrame = NULL;
uint8_t* tmpBuffer = new uint8_t[_lengthSourceFrame];
double startTime = clock()/(double)CLOCKS_PER_SEC;
_vcm->SetChannelParameters(static_cast<uint32_t>(1000 * _bitRate), 0, 0);
SendStatsTest sendStats;
sendStats.set_framerate(static_cast<uint32_t>(_frameRate));
sendStats.set_bitrate(1000 * _bitRate);
_vcm->RegisterSendStatisticsCallback(&sendStats);
VideoContentMetrics* contentMetrics = NULL;
// setting user frame rate
// for starters: keeping native values:
_vpm->SetTargetResolution(_width, _height,
(uint32_t)(_frameRate+ 0.5f));
_decodeCallback.SetOriginalFrameDimensions(_nativeWidth, _nativeHeight);
//tmp - disabling VPM frame dropping
_vpm->EnableTemporalDecimation(false);
int32_t ret = 0;
_numFramesDroppedVPM = 0;
do {
if (fread(tmpBuffer, 1, _lengthSourceFrame, _sourceFile) > 0) {
_frameCnt++;
int size_y = _nativeWidth * _nativeHeight;
int size_uv = ((_nativeWidth + 1) / 2) * ((_nativeHeight + 1) / 2);
sourceFrame.CreateFrame(size_y, tmpBuffer,
size_uv, tmpBuffer + size_y,
size_uv, tmpBuffer + size_y + size_uv,
_nativeWidth, _nativeHeight,
_nativeWidth, (_nativeWidth + 1) / 2,
(_nativeWidth + 1) / 2);
_timeStamp +=
(uint32_t)(9e4 / static_cast<float>(codec.maxFramerate));
sourceFrame.set_timestamp(_timeStamp);
ret = _vpm->PreprocessFrame(sourceFrame, &decimatedFrame);
if (ret == 1)
{
printf("VD: frame drop %d \n",_frameCnt);
_numFramesDroppedVPM += 1;
continue; // frame drop
}
else if (ret < 0)
{
printf("Error in PreprocessFrame: %d\n", ret);
//exit(1);
}
// Frame was not re-sampled => use original.
if (decimatedFrame == NULL)
{
decimatedFrame = &sourceFrame;
}
contentMetrics = _vpm->ContentMetrics();
if (contentMetrics == NULL)
{
printf("error: contentMetrics = NULL\n");
}
// counting only encoding time
_encodeTimes[int(sourceFrame.timestamp())] =
clock()/(double)CLOCKS_PER_SEC;
int32_t ret = _vcm->AddVideoFrame(*decimatedFrame, contentMetrics);
_totalEncodeTime += clock()/(double)CLOCKS_PER_SEC -
_encodeTimes[int(sourceFrame.timestamp())];
if (ret < 0)
{
printf("Error in AddFrame: %d\n", ret);
//exit(1);
}
// Same timestamp value for encode and decode
_decodeTimes[int(sourceFrame.timestamp())] =
clock()/(double)CLOCKS_PER_SEC;
ret = _vcm->Decode();
_totalDecodeTime += clock()/(double)CLOCKS_PER_SEC -
_decodeTimes[int(sourceFrame.timestamp())];
if (ret < 0)
{
printf("Error in Decode: %d\n", ret);
//exit(1);
}
if (_vcm->TimeUntilNextProcess() <= 0)
{
_vcm->Process();
}
// mimicking setTargetRates - update every 1 sec
// this will trigger QMSelect
if (_frameCnt%((int)_frameRate) == 0)
{
_vcm->SetChannelParameters(static_cast<uint32_t>(1000 * _bitRate), 0,
1);
}
// check for bit rate update
if (change < numChanges && _frameCnt == updateFrameNum[change])
{
_bitRate = bitRateUpdate[change];
_frameRate = frameRateUpdate[change];
codec.startBitrate = (int)_bitRate;
codec.maxFramerate = (uint8_t) _frameRate;
// Will also set and init the desired codec
TEST(_vcm->RegisterSendCodec(&codec, 2, 1440) == VCM_OK);
change++;
}
DataLog::InsertCell(feature_table_name_, "motion magnitude",
contentMetrics->motion_magnitude);
DataLog::InsertCell(feature_table_name_, "spatial prediction error",
contentMetrics->spatial_pred_err);
DataLog::InsertCell(feature_table_name_, "spatial pred err horizontal",
contentMetrics->spatial_pred_err_h);
DataLog::InsertCell(feature_table_name_, "spatial pred err vertical",
contentMetrics->spatial_pred_err_v);
DataLog::InsertCell(feature_table_name_, "width", _nativeHeight);
DataLog::InsertCell(feature_table_name_, "height", _nativeWidth);
DataLog::InsertCell(feature_table_name_, "num pixels",
_nativeHeight * _nativeWidth);
DataLog::InsertCell(feature_table_name_, "frame rate", _nativeFrameRate);
DataLog::NextRow(feature_table_name_);
static_cast<SimulatedClock*>(_clock)->AdvanceTimeMilliseconds(
1000 / _nativeFrameRate);
}
} while (feof(_sourceFile) == 0);
_decodeCallback.WriteEnd(_frameCnt);
double endTime = clock()/(double)CLOCKS_PER_SEC;
_testTotalTime = endTime - startTime;
_sumEncBytes = _encodeCompleteCallback.EncodedBytes();
delete tmpBuffer;
delete waitEvent;
_vpm->Reset();
Teardown();
Print();
VideoProcessingModule::Destroy(_vpm);
return 0;
}
// implementing callback to be called from
// VCM to update VPM of frame rate and size
QMTestVideoSettingsCallback::QMTestVideoSettingsCallback():
_vpm(NULL),
_vcm(NULL)
{
//
}
void
QMTestVideoSettingsCallback::RegisterVPM(VideoProcessingModule *vpm)
{
_vpm = vpm;
}
void
QMTestVideoSettingsCallback::RegisterVCM(VideoCodingModule *vcm)
{
_vcm = vcm;
}
bool
QMTestVideoSettingsCallback::Updated()
{
if (_updated)
{
_updated = false;
return true;
}
return false;
}
int32_t
QMTestVideoSettingsCallback::SetVideoQMSettings(const uint32_t frameRate,
const uint32_t width,
const uint32_t height)
{
int32_t retVal = 0;
printf("QM updates: W = %d, H = %d, FR = %d, \n", width, height, frameRate);
retVal = _vpm->SetTargetResolution(width, height, frameRate);
//Initialize codec with new values - is this the best place to do it?
if (!retVal)
{
// first get current settings
VideoCodec currentCodec;
_vcm->SendCodec(&currentCodec);
// now set new values:
currentCodec.height = (uint16_t)height;
currentCodec.width = (uint16_t)width;
currentCodec.maxFramerate = (uint8_t)frameRate;
// re-register encoder
retVal = _vcm->RegisterSendCodec(&currentCodec, 2, 1440);
_updated = true;
}
return retVal;
}
// Decoded Frame Callback Implementation
VCMQMDecodeCompleteCallback::VCMQMDecodeCompleteCallback(
FILE* decodedFile, int frame_rate, std::string feature_table_name):
_decodedFile(decodedFile),
_decodedBytes(0),
//_test(test),
_origWidth(0),
_origHeight(0),
_decWidth(0),
_decHeight(0),
//_interpolator(NULL),
_decBuffer(NULL),
_frameCnt(0),
frame_rate_(frame_rate),
frames_cnt_since_drop_(0),
feature_table_name_(feature_table_name)
{
//
}
VCMQMDecodeCompleteCallback::~VCMQMDecodeCompleteCallback()
{
// if (_interpolator != NULL)
// {
// deleteInterpolator(_interpolator);
// _interpolator = NULL;
// }
if (_decBuffer != NULL)
{
delete [] _decBuffer;
_decBuffer = NULL;
}
}
int32_t
VCMQMDecodeCompleteCallback::FrameToRender(I420VideoFrame& videoFrame)
{
++frames_cnt_since_drop_;
// When receiving the first coded frame the last_frame variable is not set
if (last_frame_.IsZeroSize()) {
last_frame_.CopyFrame(videoFrame);
}
// Check if there were frames skipped.
int num_frames_skipped = static_cast<int>( 0.5f +
(videoFrame.timestamp() - (last_frame_.timestamp() + (9e4 / frame_rate_))) /
(9e4 / frame_rate_));
// If so...put the last frames into the encoded stream to make up for the
// skipped frame(s)
while (num_frames_skipped > 0) {
PrintI420VideoFrame(last_frame_, _decodedFile);
_frameCnt++;
--num_frames_skipped;
frames_cnt_since_drop_ = 1; // Reset counter
}
DataLog::InsertCell(
feature_table_name_,"num frames since drop",frames_cnt_since_drop_);
if (_origWidth == videoFrame.width() && _origHeight == videoFrame.height())
{
if (PrintI420VideoFrame(videoFrame, _decodedFile) < 0) {
return -1;
}
_frameCnt++;
// no need for interpolator and decBuffer
if (_decBuffer != NULL)
{
delete [] _decBuffer;
_decBuffer = NULL;
}
_decWidth = 0;
_decHeight = 0;
}
else
{
// TODO(mikhal): Add support for scaling.
return -1;
}
_decodedBytes += CalcBufferSize(kI420, videoFrame.width(),
videoFrame.height());
videoFrame.SwapFrame(&last_frame_);
return VCM_OK;
}
size_t VCMQMDecodeCompleteCallback::DecodedBytes()
{
return _decodedBytes;
}
void VCMQMDecodeCompleteCallback::SetOriginalFrameDimensions(int32_t width,
int32_t height)
{
_origWidth = width;
_origHeight = height;
}
int32_t VCMQMDecodeCompleteCallback::buildInterpolator()
{
uint32_t decFrameLength = _origWidth*_origHeight*3 >> 1;
if (_decBuffer != NULL)
{
delete [] _decBuffer;
}
_decBuffer = new uint8_t[decFrameLength];
if (_decBuffer == NULL)
{
return -1;
}
return 0;
}
// This function checks if the total number of frames processed in the encoding
// process is the same as the number of frames rendered. If not, the last
// frame (or several consecutive frames from the end) must have been dropped. If
// this is the case, the last frame is repeated so that there are as many
// frames rendered as there are number of frames encoded.
void VCMQMDecodeCompleteCallback::WriteEnd(int input_frame_count)
{
int num_missing_frames = input_frame_count - _frameCnt;
for (int n = num_missing_frames; n > 0; --n) {
PrintI420VideoFrame(last_frame_, _decodedFile);
_frameCnt++;
}
}