1 /*
2 * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
11 #include "webrtc/modules/video_coding/main/test/quality_modes_test.h"
12
13 #include <iostream>
14 #include <sstream>
15 #include <string>
16 #include <time.h>
17
18 #include "webrtc/common_video/libyuv/include/webrtc_libyuv.h"
19 #include "webrtc/modules/video_coding/main/interface/video_coding.h"
20 #include "webrtc/modules/video_coding/main/test/test_callbacks.h"
21 #include "webrtc/modules/video_coding/main/test/test_macros.h"
22 #include "webrtc/modules/video_coding/main/test/test_util.h"
23 #include "webrtc/system_wrappers/interface/clock.h"
24 #include "webrtc/system_wrappers/interface/data_log.h"
25 #include "webrtc/system_wrappers/interface/data_log.h"
26 #include "webrtc/test/testsupport/fileutils.h"
27 #include "webrtc/test/testsupport/metrics/video_metrics.h"
28
29 using namespace webrtc;
30
qualityModeTest(const CmdArgs & args)31 int qualityModeTest(const CmdArgs& args)
32 {
33 SimulatedClock clock(0);
34 NullEventFactory event_factory;
35 VideoCodingModule* vcm = VideoCodingModule::Create(&clock, &event_factory);
36 QualityModesTest QMTest(vcm, &clock);
37 QMTest.Perform(args);
38 VideoCodingModule::Destroy(vcm);
39 return 0;
40 }
41
QualityModesTest(VideoCodingModule * vcm,Clock * clock)42 QualityModesTest::QualityModesTest(VideoCodingModule* vcm,
43 Clock* clock):
44 NormalTest(vcm, clock),
45 _vpm()
46 {
47 //
48 }
49
~QualityModesTest()50 QualityModesTest::~QualityModesTest()
51 {
52 //
53 }
54
55 void
Setup(const CmdArgs & args)56 QualityModesTest::Setup(const CmdArgs& args)
57 {
58 NormalTest::Setup(args);
59 _inname = args.inputFile;
60 _outname = args.outputFile;
61 fv_outfilename_ = args.fv_outputfile;
62
63 filename_testvideo_ =
64 _inname.substr(_inname.find_last_of("\\/") + 1,_inname.length());
65
66 _encodedName = test::OutputPath() + "encoded_qmtest.yuv";
67
68 //NATIVE/SOURCE VALUES
69 _nativeWidth = args.width;
70 _nativeHeight = args.height;
71 _nativeFrameRate =args.frameRate;
72
73 //TARGET/ENCODER VALUES
74 _width = args.width;
75 _height = args.height;
76 _frameRate = args.frameRate;
77
78 _bitRate = args.bitRate;
79
80 _flagSSIM = true;
81
82 _lengthSourceFrame = 3*_nativeWidth*_nativeHeight/2;
83
84 if ((_sourceFile = fopen(_inname.c_str(), "rb")) == NULL)
85 {
86 printf("Cannot read file %s.\n", _inname.c_str());
87 exit(1);
88 }
89 if ((_encodedFile = fopen(_encodedName.c_str(), "wb")) == NULL)
90 {
91 printf("Cannot write encoded file.\n");
92 exit(1);
93 }
94 if ((_decodedFile = fopen(_outname.c_str(), "wb")) == NULL)
95 {
96 printf("Cannot write file %s.\n", _outname.c_str());
97 exit(1);
98 }
99
100 DataLog::CreateLog();
101
102 feature_table_name_ = fv_outfilename_;
103
104 DataLog::AddTable(feature_table_name_);
105
106 DataLog::AddColumn(feature_table_name_, "motion magnitude", 1);
107 DataLog::AddColumn(feature_table_name_, "spatial prediction error", 1);
108 DataLog::AddColumn(feature_table_name_, "spatial pred err horizontal", 1);
109 DataLog::AddColumn(feature_table_name_, "spatial pred err vertical", 1);
110 DataLog::AddColumn(feature_table_name_, "width", 1);
111 DataLog::AddColumn(feature_table_name_, "height", 1);
112 DataLog::AddColumn(feature_table_name_, "num pixels", 1);
113 DataLog::AddColumn(feature_table_name_, "frame rate", 1);
114 DataLog::AddColumn(feature_table_name_, "num frames since drop", 1);
115
116 _log.open((test::OutputPath() + "TestLog.txt").c_str(),
117 std::fstream::out | std::fstream::app);
118 }
119
120 void
Print()121 QualityModesTest::Print()
122 {
123 std::cout << "Quality Modes Test Completed!" << std::endl;
124 (_log) << "Quality Modes Test Completed!" << std::endl;
125 (_log) << "Input file: " << _inname << std::endl;
126 (_log) << "Output file: " << _outname << std::endl;
127 (_log) << "Total run time: " << _testTotalTime << std::endl;
128 printf("Total run time: %f s \n", _testTotalTime);
129 double ActualBitRate = 8.0*( _sumEncBytes / (_frameCnt / _nativeFrameRate));
130 double actualBitRate = ActualBitRate / 1000.0;
131 double avgEncTime = _totalEncodeTime / _frameCnt;
132 double avgDecTime = _totalDecodeTime / _frameCnt;
133 webrtc::test::QualityMetricsResult psnr,ssim;
134 I420PSNRFromFiles(_inname.c_str(), _outname.c_str(), _nativeWidth,
135 _nativeHeight, &psnr);
136 printf("Actual bitrate: %f kbps\n", actualBitRate);
137 printf("Target bitrate: %f kbps\n", _bitRate);
138 ( _log) << "Actual bitrate: " << actualBitRate<< " kbps\tTarget: " <<
139 _bitRate << " kbps" << std::endl;
140 printf("Average encode time: %f s\n", avgEncTime);
141 ( _log) << "Average encode time: " << avgEncTime << " s" << std::endl;
142 printf("Average decode time: %f s\n", avgDecTime);
143 ( _log) << "Average decode time: " << avgDecTime << " s" << std::endl;
144 printf("PSNR: %f \n", psnr.average);
145 printf("**Number of frames dropped in VPM***%d \n",_numFramesDroppedVPM);
146 ( _log) << "PSNR: " << psnr.average << std::endl;
147 if (_flagSSIM == 1)
148 {
149 printf("***computing SSIM***\n");
150 I420SSIMFromFiles(_inname.c_str(), _outname.c_str(), _nativeWidth,
151 _nativeHeight, &ssim);
152 printf("SSIM: %f \n", ssim.average);
153 }
154 (_log) << std::endl;
155
156 printf("\nVCM Quality Modes Test: \n\n%i tests completed\n", vcmMacrosTests);
157 if (vcmMacrosErrors > 0)
158 {
159 printf("%i FAILED\n\n", vcmMacrosErrors);
160 }
161 else
162 {
163 printf("ALL PASSED\n\n");
164 }
165 }
166 void
Teardown()167 QualityModesTest::Teardown()
168 {
169 _log.close();
170 fclose(_sourceFile);
171 fclose(_decodedFile);
172 fclose(_encodedFile);
173 return;
174 }
175
176 int32_t
Perform(const CmdArgs & args)177 QualityModesTest::Perform(const CmdArgs& args)
178 {
179 Setup(args);
180 // changing bit/frame rate during the test
181 const float bitRateUpdate[] = {1000};
182 const float frameRateUpdate[] = {30};
183 // frame num at which an update will occur
184 const int updateFrameNum[] = {10000};
185
186 uint32_t numChanges = sizeof(updateFrameNum)/sizeof(*updateFrameNum);
187 uint8_t change = 0;// change counter
188
189 _vpm = VideoProcessingModule::Create(1);
190 EventWrapper* waitEvent = EventWrapper::Create();
191 VideoCodec codec;//both send and receive
192 _vcm->InitializeReceiver();
193 _vcm->InitializeSender();
194 int32_t NumberOfCodecs = _vcm->NumberOfCodecs();
195 for (int i = 0; i < NumberOfCodecs; i++)
196 {
197 _vcm->Codec(i, &codec);
198 if(strncmp(codec.plName,"VP8" , 5) == 0)
199 {
200 codec.startBitrate = (int)_bitRate;
201 codec.maxFramerate = (uint8_t) _frameRate;
202 codec.width = (uint16_t)_width;
203 codec.height = (uint16_t)_height;
204 codec.codecSpecific.VP8.frameDroppingOn = false;
205
206 // Will also set and init the desired codec
207 TEST(_vcm->RegisterSendCodec(&codec, 2, 1440) == VCM_OK);
208 i = NumberOfCodecs;
209 }
210 }
211
212 // register a decoder (same codec for decoder and encoder )
213 TEST(_vcm->RegisterReceiveCodec(&codec, 2) == VCM_OK);
214 /* Callback Settings */
215 VCMQMDecodeCompleCallback _decodeCallback(
216 _decodedFile, _nativeFrameRate, feature_table_name_);
217 _vcm->RegisterReceiveCallback(&_decodeCallback);
218 VCMNTEncodeCompleteCallback _encodeCompleteCallback(_encodedFile, *this);
219 _vcm->RegisterTransportCallback(&_encodeCompleteCallback);
220 // encode and decode with the same vcm
221 _encodeCompleteCallback.RegisterReceiverVCM(_vcm);
222
223 //quality modes callback
224 QMTestVideoSettingsCallback QMCallback;
225 QMCallback.RegisterVCM(_vcm);
226 QMCallback.RegisterVPM(_vpm);
227 //_vcm->RegisterVideoQMCallback(&QMCallback);
228
229 ///////////////////////
230 /// Start Test
231 ///////////////////////
232 _vpm->EnableTemporalDecimation(true);
233 _vpm->EnableContentAnalysis(true);
234 _vpm->SetInputFrameResampleMode(kFastRescaling);
235
236 // disabling internal VCM frame dropper
237 _vcm->EnableFrameDropper(false);
238
239 I420VideoFrame sourceFrame;
240 I420VideoFrame *decimatedFrame = NULL;
241 uint8_t* tmpBuffer = new uint8_t[_lengthSourceFrame];
242 double startTime = clock()/(double)CLOCKS_PER_SEC;
243 _vcm->SetChannelParameters(static_cast<uint32_t>(1000 * _bitRate), 0, 0);
244
245 SendStatsTest sendStats;
246 sendStats.set_framerate(static_cast<uint32_t>(_frameRate));
247 sendStats.set_bitrate(1000 * _bitRate);
248 _vcm->RegisterSendStatisticsCallback(&sendStats);
249
250 VideoContentMetrics* contentMetrics = NULL;
251 // setting user frame rate
252 // for starters: keeping native values:
253 _vpm->SetTargetResolution(_width, _height,
254 (uint32_t)(_frameRate+ 0.5f));
255 _decodeCallback.SetOriginalFrameDimensions(_nativeWidth, _nativeHeight);
256
257 //tmp - disabling VPM frame dropping
258 _vpm->EnableTemporalDecimation(false);
259
260 int32_t ret = 0;
261 _numFramesDroppedVPM = 0;
262
263 do {
264 if (fread(tmpBuffer, 1, _lengthSourceFrame, _sourceFile) > 0) {
265 _frameCnt++;
266 int size_y = _nativeWidth * _nativeHeight;
267 int size_uv = ((_nativeWidth + 1) / 2) * ((_nativeHeight + 1) / 2);
268 sourceFrame.CreateFrame(size_y, tmpBuffer,
269 size_uv, tmpBuffer + size_y,
270 size_uv, tmpBuffer + size_y + size_uv,
271 _nativeWidth, _nativeHeight,
272 _nativeWidth, (_nativeWidth + 1) / 2,
273 (_nativeWidth + 1) / 2);
274
275 _timeStamp +=
276 (uint32_t)(9e4 / static_cast<float>(codec.maxFramerate));
277 sourceFrame.set_timestamp(_timeStamp);
278
279 ret = _vpm->PreprocessFrame(sourceFrame, &decimatedFrame);
280 if (ret == 1)
281 {
282 printf("VD: frame drop %d \n",_frameCnt);
283 _numFramesDroppedVPM += 1;
284 continue; // frame drop
285 }
286 else if (ret < 0)
287 {
288 printf("Error in PreprocessFrame: %d\n", ret);
289 //exit(1);
290 }
291 // Frame was not re-sampled => use original.
292 if (decimatedFrame == NULL)
293 {
294 decimatedFrame = &sourceFrame;
295 }
296 contentMetrics = _vpm->ContentMetrics();
297 if (contentMetrics == NULL)
298 {
299 printf("error: contentMetrics = NULL\n");
300 }
301
302 // counting only encoding time
303 _encodeTimes[int(sourceFrame.timestamp())] =
304 clock()/(double)CLOCKS_PER_SEC;
305
306 int32_t ret = _vcm->AddVideoFrame(*decimatedFrame, contentMetrics);
307
308 _totalEncodeTime += clock()/(double)CLOCKS_PER_SEC -
309 _encodeTimes[int(sourceFrame.timestamp())];
310
311 if (ret < 0)
312 {
313 printf("Error in AddFrame: %d\n", ret);
314 //exit(1);
315 }
316
317 // Same timestamp value for encode and decode
318 _decodeTimes[int(sourceFrame.timestamp())] =
319 clock()/(double)CLOCKS_PER_SEC;
320 ret = _vcm->Decode();
321
322 _totalDecodeTime += clock()/(double)CLOCKS_PER_SEC -
323 _decodeTimes[int(sourceFrame.timestamp())];
324
325 if (ret < 0)
326 {
327 printf("Error in Decode: %d\n", ret);
328 //exit(1);
329 }
330 if (_vcm->TimeUntilNextProcess() <= 0)
331 {
332 _vcm->Process();
333 }
334 // mimicking setTargetRates - update every 1 sec
335 // this will trigger QMSelect
336 if (_frameCnt%((int)_frameRate) == 0)
337 {
338 _vcm->SetChannelParameters(static_cast<uint32_t>(1000 * _bitRate), 0,
339 1);
340 }
341
342 // check for bit rate update
343 if (change < numChanges && _frameCnt == updateFrameNum[change])
344 {
345 _bitRate = bitRateUpdate[change];
346 _frameRate = frameRateUpdate[change];
347 codec.startBitrate = (int)_bitRate;
348 codec.maxFramerate = (uint8_t) _frameRate;
349 // Will also set and init the desired codec
350 TEST(_vcm->RegisterSendCodec(&codec, 2, 1440) == VCM_OK);
351 change++;
352 }
353
354 DataLog::InsertCell(feature_table_name_, "motion magnitude",
355 contentMetrics->motion_magnitude);
356 DataLog::InsertCell(feature_table_name_, "spatial prediction error",
357 contentMetrics->spatial_pred_err);
358 DataLog::InsertCell(feature_table_name_, "spatial pred err horizontal",
359 contentMetrics->spatial_pred_err_h);
360 DataLog::InsertCell(feature_table_name_, "spatial pred err vertical",
361 contentMetrics->spatial_pred_err_v);
362
363 DataLog::InsertCell(feature_table_name_, "width", _nativeHeight);
364 DataLog::InsertCell(feature_table_name_, "height", _nativeWidth);
365
366 DataLog::InsertCell(feature_table_name_, "num pixels",
367 _nativeHeight * _nativeWidth);
368
369 DataLog::InsertCell(feature_table_name_, "frame rate", _nativeFrameRate);
370 DataLog::NextRow(feature_table_name_);
371
372 static_cast<SimulatedClock*>(_clock)->AdvanceTimeMilliseconds(
373 1000 / _nativeFrameRate);
374 }
375
376 } while (feof(_sourceFile) == 0);
377 _decodeCallback.WriteEnd(_frameCnt);
378
379
380 double endTime = clock()/(double)CLOCKS_PER_SEC;
381 _testTotalTime = endTime - startTime;
382 _sumEncBytes = _encodeCompleteCallback.EncodedBytes();
383
384 delete tmpBuffer;
385 delete waitEvent;
386 _vpm->Reset();
387 Teardown();
388 Print();
389 VideoProcessingModule::Destroy(_vpm);
390 return 0;
391 }
392
393 // implementing callback to be called from
394 // VCM to update VPM of frame rate and size
QMTestVideoSettingsCallback()395 QMTestVideoSettingsCallback::QMTestVideoSettingsCallback():
396 _vpm(NULL),
397 _vcm(NULL)
398 {
399 //
400 }
401
402 void
RegisterVPM(VideoProcessingModule * vpm)403 QMTestVideoSettingsCallback::RegisterVPM(VideoProcessingModule *vpm)
404 {
405 _vpm = vpm;
406 }
407 void
RegisterVCM(VideoCodingModule * vcm)408 QMTestVideoSettingsCallback::RegisterVCM(VideoCodingModule *vcm)
409 {
410 _vcm = vcm;
411 }
412
413 bool
Updated()414 QMTestVideoSettingsCallback::Updated()
415 {
416 if (_updated)
417 {
418 _updated = false;
419 return true;
420 }
421 return false;
422 }
423
424 int32_t
SetVideoQMSettings(const uint32_t frameRate,const uint32_t width,const uint32_t height)425 QMTestVideoSettingsCallback::SetVideoQMSettings(const uint32_t frameRate,
426 const uint32_t width,
427 const uint32_t height)
428 {
429 int32_t retVal = 0;
430 printf("QM updates: W = %d, H = %d, FR = %d, \n", width, height, frameRate);
431 retVal = _vpm->SetTargetResolution(width, height, frameRate);
432 //Initialize codec with new values - is this the best place to do it?
433 if (!retVal)
434 {
435 // first get current settings
436 VideoCodec currentCodec;
437 _vcm->SendCodec(¤tCodec);
438 // now set new values:
439 currentCodec.height = (uint16_t)height;
440 currentCodec.width = (uint16_t)width;
441 currentCodec.maxFramerate = (uint8_t)frameRate;
442
443 // re-register encoder
444 retVal = _vcm->RegisterSendCodec(¤tCodec, 2, 1440);
445 _updated = true;
446 }
447
448 return retVal;
449 }
450
451 // Decoded Frame Callback Implementation
VCMQMDecodeCompleCallback(FILE * decodedFile,int frame_rate,std::string feature_table_name)452 VCMQMDecodeCompleCallback::VCMQMDecodeCompleCallback(
453 FILE* decodedFile, int frame_rate, std::string feature_table_name):
454 _decodedFile(decodedFile),
455 _decodedBytes(0),
456 //_test(test),
457 _origWidth(0),
458 _origHeight(0),
459 _decWidth(0),
460 _decHeight(0),
461 //_interpolator(NULL),
462 _decBuffer(NULL),
463 _frameCnt(0),
464 frame_rate_(frame_rate),
465 frames_cnt_since_drop_(0),
466 feature_table_name_(feature_table_name)
467 {
468 //
469 }
470
~VCMQMDecodeCompleCallback()471 VCMQMDecodeCompleCallback::~VCMQMDecodeCompleCallback()
472 {
473 // if (_interpolator != NULL)
474 // {
475 // deleteInterpolator(_interpolator);
476 // _interpolator = NULL;
477 // }
478 if (_decBuffer != NULL)
479 {
480 delete [] _decBuffer;
481 _decBuffer = NULL;
482 }
483 }
484
485 int32_t
FrameToRender(I420VideoFrame & videoFrame)486 VCMQMDecodeCompleCallback::FrameToRender(I420VideoFrame& videoFrame)
487 {
488 ++frames_cnt_since_drop_;
489
490 // When receiving the first coded frame the last_frame variable is not set
491 if (last_frame_.IsZeroSize()) {
492 last_frame_.CopyFrame(videoFrame);
493 }
494
495 // Check if there were frames skipped.
496 int num_frames_skipped = static_cast<int>( 0.5f +
497 (videoFrame.timestamp() - (last_frame_.timestamp() + (9e4 / frame_rate_))) /
498 (9e4 / frame_rate_));
499
500 // If so...put the last frames into the encoded stream to make up for the
501 // skipped frame(s)
502 while (num_frames_skipped > 0) {
503 PrintI420VideoFrame(last_frame_, _decodedFile);
504 _frameCnt++;
505 --num_frames_skipped;
506 frames_cnt_since_drop_ = 1; // Reset counter
507
508 }
509
510 DataLog::InsertCell(
511 feature_table_name_,"num frames since drop",frames_cnt_since_drop_);
512
513 if (_origWidth == videoFrame.width() && _origHeight == videoFrame.height())
514 {
515 if (PrintI420VideoFrame(videoFrame, _decodedFile) < 0) {
516 return -1;
517 }
518 _frameCnt++;
519 // no need for interpolator and decBuffer
520 if (_decBuffer != NULL)
521 {
522 delete [] _decBuffer;
523 _decBuffer = NULL;
524 }
525 _decWidth = 0;
526 _decHeight = 0;
527 }
528 else
529 {
530 // TODO(mikhal): Add support for scaling.
531 return -1;
532 }
533
534 _decodedBytes += CalcBufferSize(kI420, videoFrame.width(),
535 videoFrame.height());
536 videoFrame.SwapFrame(&last_frame_);
537 return VCM_OK;
538 }
539
DecodedBytes()540 int32_t VCMQMDecodeCompleCallback::DecodedBytes()
541 {
542 return _decodedBytes;
543 }
544
SetOriginalFrameDimensions(int32_t width,int32_t height)545 void VCMQMDecodeCompleCallback::SetOriginalFrameDimensions(int32_t width,
546 int32_t height)
547 {
548 _origWidth = width;
549 _origHeight = height;
550 }
551
buildInterpolator()552 int32_t VCMQMDecodeCompleCallback::buildInterpolator()
553 {
554 uint32_t decFrameLength = _origWidth*_origHeight*3 >> 1;
555 if (_decBuffer != NULL)
556 {
557 delete [] _decBuffer;
558 }
559 _decBuffer = new uint8_t[decFrameLength];
560 if (_decBuffer == NULL)
561 {
562 return -1;
563 }
564 return 0;
565 }
566
567 // This function checks if the total number of frames processed in the encoding
568 // process is the same as the number of frames rendered. If not, the last
569 // frame (or several consecutive frames from the end) must have been dropped. If
570 // this is the case, the last frame is repeated so that there are as many
571 // frames rendered as there are number of frames encoded.
WriteEnd(int input_frame_count)572 void VCMQMDecodeCompleCallback::WriteEnd(int input_frame_count)
573 {
574 int num_missing_frames = input_frame_count - _frameCnt;
575
576 for (int n = num_missing_frames; n > 0; --n) {
577 PrintI420VideoFrame(last_frame_, _decodedFile);
578 _frameCnt++;
579 }
580 }
581