| /* |
| * 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/normal_test.h" |
| |
| #include <assert.h> |
| #include <iostream> |
| #include <sstream> |
| #include <time.h> |
| |
| #include "webrtc/common_types.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/trace.h" |
| #include "webrtc/test/testsupport/fileutils.h" |
| #include "webrtc/test/testsupport/metrics/video_metrics.h" |
| |
| using namespace webrtc; |
| |
| int NormalTest::RunTest(const CmdArgs& args) |
| { |
| SimulatedClock sim_clock(0); |
| SimulatedClock* clock = &sim_clock; |
| NullEventFactory event_factory; |
| Trace::CreateTrace(); |
| Trace::SetTraceFile( |
| (test::OutputPath() + "VCMNormalTestTrace.txt").c_str()); |
| Trace::set_level_filter(webrtc::kTraceAll); |
| VideoCodingModule* vcm = VideoCodingModule::Create(clock, &event_factory); |
| NormalTest VCMNTest(vcm, clock); |
| VCMNTest.Perform(args); |
| VideoCodingModule::Destroy(vcm); |
| Trace::ReturnTrace(); |
| return 0; |
| } |
| |
| //////////////// |
| // Callback Implementation |
| ////////////// |
| |
| VCMNTEncodeCompleteCallback::VCMNTEncodeCompleteCallback(FILE* encodedFile, |
| NormalTest& test): |
| _encodedFile(encodedFile), |
| _encodedBytes(0), |
| _skipCnt(0), |
| _VCMReceiver(NULL), |
| _seqNo(0), |
| _test(test) |
| { |
| // |
| } |
| VCMNTEncodeCompleteCallback::~VCMNTEncodeCompleteCallback() |
| { |
| } |
| |
| void VCMNTEncodeCompleteCallback::RegisterTransportCallback( |
| VCMPacketizationCallback* transport) |
| { |
| } |
| |
| int32_t |
| VCMNTEncodeCompleteCallback::SendData( |
| FrameType frameType, |
| uint8_t payloadType, |
| uint32_t timeStamp, |
| int64_t capture_time_ms, |
| const uint8_t* payloadData, |
| size_t payloadSize, |
| const RTPFragmentationHeader& /*fragmentationHeader*/, |
| const webrtc::RTPVideoHeader* videoHdr) |
| |
| { |
| // will call the VCMReceiver input packet |
| _frameType = frameType; |
| // writing encodedData into file |
| if (fwrite(payloadData, 1, payloadSize, _encodedFile) != payloadSize) { |
| return -1; |
| } |
| WebRtcRTPHeader rtpInfo; |
| rtpInfo.header.markerBit = true; |
| rtpInfo.type.Video.width = 0; |
| rtpInfo.type.Video.height = 0; |
| switch (_test.VideoType()) |
| { |
| case kVideoCodecVP8: |
| rtpInfo.type.Video.codec = kRtpVideoVp8; |
| rtpInfo.type.Video.codecHeader.VP8.InitRTPVideoHeaderVP8(); |
| rtpInfo.type.Video.codecHeader.VP8.nonReference = |
| videoHdr->codecHeader.VP8.nonReference; |
| rtpInfo.type.Video.codecHeader.VP8.pictureId = |
| videoHdr->codecHeader.VP8.pictureId; |
| break; |
| case kVideoCodecVP9: |
| // Leave for now, until we add kRtpVideoVp9 to RTP. |
| break; |
| default: |
| assert(false); |
| return -1; |
| } |
| rtpInfo.header.payloadType = payloadType; |
| rtpInfo.header.sequenceNumber = _seqNo++; |
| rtpInfo.header.ssrc = 0; |
| rtpInfo.header.timestamp = timeStamp; |
| rtpInfo.frameType = frameType; |
| rtpInfo.type.Video.isFirstPacket = true; |
| // Size should also be received from that table, since the payload type |
| // defines the size. |
| |
| _encodedBytes += payloadSize; |
| if (payloadSize < 20) |
| { |
| _skipCnt++; |
| } |
| _VCMReceiver->IncomingPacket(payloadData, payloadSize, rtpInfo); |
| return 0; |
| } |
| void |
| VCMNTEncodeCompleteCallback::RegisterReceiverVCM(VideoCodingModule *vcm) |
| { |
| _VCMReceiver = vcm; |
| return; |
| } |
| size_t |
| VCMNTEncodeCompleteCallback::EncodedBytes() |
| { |
| return _encodedBytes; |
| } |
| |
| uint32_t |
| VCMNTEncodeCompleteCallback::SkipCnt() |
| { |
| return _skipCnt; |
| } |
| |
| // Decoded Frame Callback Implementation |
| VCMNTDecodeCompleteCallback::~VCMNTDecodeCompleteCallback() |
| { |
| if (_decodedFile) |
| fclose(_decodedFile); |
| } |
| int32_t |
| VCMNTDecodeCompleteCallback::FrameToRender(webrtc::I420VideoFrame& videoFrame) |
| { |
| if (videoFrame.width() != _currentWidth || |
| videoFrame.height() != _currentHeight) |
| { |
| _currentWidth = videoFrame.width(); |
| _currentHeight = videoFrame.height(); |
| if (_decodedFile != NULL) |
| { |
| fclose(_decodedFile); |
| _decodedFile = NULL; |
| } |
| _decodedFile = fopen(_outname.c_str(), "wb"); |
| } |
| if (PrintI420VideoFrame(videoFrame, _decodedFile) < 0) { |
| return -1; |
| } |
| _decodedBytes += webrtc::CalcBufferSize(webrtc::kI420, videoFrame.width(), |
| videoFrame.height()); |
| return VCM_OK; |
| } |
| |
| size_t |
| VCMNTDecodeCompleteCallback::DecodedBytes() |
| { |
| return _decodedBytes; |
| } |
| |
| //VCM Normal Test Class implementation |
| |
| NormalTest::NormalTest(VideoCodingModule* vcm, Clock* clock) |
| : |
| _clock(clock), |
| _vcm(vcm), |
| _sumEncBytes(0), |
| _timeStamp(0), |
| _totalEncodeTime(0), |
| _totalDecodeTime(0), |
| _decodeCompleteTime(0), |
| _encodeCompleteTime(0), |
| _totalEncodePipeTime(0), |
| _totalDecodePipeTime(0), |
| _frameCnt(0), |
| _encFrameCnt(0), |
| _decFrameCnt(0) |
| { |
| // |
| } |
| |
| NormalTest::~NormalTest() |
| { |
| // |
| } |
| void |
| NormalTest::Setup(const CmdArgs& args) |
| { |
| _inname = args.inputFile; |
| _encodedName = test::OutputPath() + "encoded_normaltest.yuv"; |
| _width = args.width; |
| _height = args.height; |
| _frameRate = args.frameRate; |
| _bitRate = args.bitRate; |
| if (args.outputFile == "") |
| { |
| std::ostringstream filename; |
| filename << test::OutputPath() << "NormalTest_" << |
| _width << "x" << _height << "_" << _frameRate << "Hz_P420.yuv"; |
| _outname = filename.str(); |
| } |
| else |
| { |
| _outname = args.outputFile; |
| } |
| _lengthSourceFrame = 3*_width*_height/2; |
| _videoType = args.codecType; |
| |
| 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); |
| } |
| |
| _log.open((test::OutputPath() + "TestLog.txt").c_str(), |
| std::fstream::out | std::fstream::app); |
| } |
| |
| int32_t |
| NormalTest::Perform(const CmdArgs& args) |
| { |
| Setup(args); |
| EventWrapper* waitEvent = EventWrapper::Create(); |
| VideoCodec _sendCodec; |
| _vcm->InitializeReceiver(); |
| _vcm->InitializeSender(); |
| TEST(VideoCodingModule::Codec(_videoType, &_sendCodec) == VCM_OK); |
| // should be later on changed via the API |
| _sendCodec.startBitrate = (int)_bitRate; |
| _sendCodec.width = static_cast<uint16_t>(_width); |
| _sendCodec.height = static_cast<uint16_t>(_height); |
| _sendCodec.maxFramerate = _frameRate; |
| // will also set and init the desired codec |
| TEST(_vcm->RegisterSendCodec(&_sendCodec, 4, 1400) == VCM_OK); |
| // register a decoder (same codec for decoder and encoder ) |
| TEST(_vcm->RegisterReceiveCodec(&_sendCodec, 1) == VCM_OK); |
| /* Callback Settings */ |
| VCMNTDecodeCompleteCallback _decodeCallback(_outname); |
| _vcm->RegisterReceiveCallback(&_decodeCallback); |
| VCMNTEncodeCompleteCallback _encodeCompleteCallback(_encodedFile, *this); |
| _vcm->RegisterTransportCallback(&_encodeCompleteCallback); |
| // encode and decode with the same vcm |
| _encodeCompleteCallback.RegisterReceiverVCM(_vcm); |
| /////////////////////// |
| /// Start Test |
| /////////////////////// |
| I420VideoFrame sourceFrame; |
| int size_y = _width * _height; |
| int half_width = (_width + 1) / 2; |
| int half_height = (_height + 1) / 2; |
| int size_uv = half_width * half_height; |
| sourceFrame.CreateEmptyFrame(_width, _height, |
| _width, half_width, half_width); |
| 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); |
| |
| while (feof(_sourceFile) == 0) { |
| TEST(fread(tmpBuffer, 1, _lengthSourceFrame, _sourceFile) > 0 || |
| feof(_sourceFile)); |
| _frameCnt++; |
| sourceFrame.CreateFrame(size_y, tmpBuffer, |
| size_uv, tmpBuffer + size_y, |
| size_uv, tmpBuffer + size_y + size_uv, |
| _width, _height, |
| _width, half_width, half_width); |
| _timeStamp += |
| (uint32_t)(9e4 / static_cast<float>(_sendCodec.maxFramerate)); |
| sourceFrame.set_timestamp(_timeStamp); |
| _encodeTimes[int(sourceFrame.timestamp())] = |
| clock()/(double)CLOCKS_PER_SEC; |
| int32_t ret = _vcm->AddVideoFrame(sourceFrame); |
| double encodeTime = clock()/(double)CLOCKS_PER_SEC - |
| _encodeTimes[int(sourceFrame.timestamp())]; |
| _totalEncodeTime += encodeTime; |
| if (ret < 0) |
| { |
| printf("Error in AddFrame: %d\n", ret); |
| //exit(1); |
| } |
| _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(); |
| } |
| uint32_t framePeriod = |
| static_cast<uint32_t>( |
| 1000.0f / static_cast<float>(_sendCodec.maxFramerate) + 0.5f); |
| static_cast<SimulatedClock*>(_clock)->AdvanceTimeMilliseconds(framePeriod); |
| } |
| double endTime = clock()/(double)CLOCKS_PER_SEC; |
| _testTotalTime = endTime - startTime; |
| _sumEncBytes = _encodeCompleteCallback.EncodedBytes(); |
| |
| delete [] tmpBuffer; |
| delete waitEvent; |
| Teardown(); |
| Print(); |
| return 0; |
| } |
| |
| void |
| NormalTest::FrameEncoded(uint32_t timeStamp) |
| { |
| _encodeCompleteTime = clock()/(double)CLOCKS_PER_SEC; |
| _encFrameCnt++; |
| _totalEncodePipeTime += _encodeCompleteTime - _encodeTimes[int(timeStamp)]; |
| |
| } |
| |
| void |
| NormalTest::FrameDecoded(uint32_t timeStamp) |
| { |
| _decodeCompleteTime = clock()/(double)CLOCKS_PER_SEC; |
| _decFrameCnt++; |
| _totalDecodePipeTime += _decodeCompleteTime - _decodeTimes[timeStamp]; |
| } |
| |
| void |
| NormalTest::Print() |
| { |
| std::cout << "Normal Test Completed!" << std::endl; |
| (_log) << "Normal 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 / _frameRate)); |
| 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(), _width, _height, |
| &psnr); |
| I420SSIMFromFiles(_inname.c_str(), _outname.c_str(), _width, _height, |
| &ssim); |
| 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); |
| ( _log) << "PSNR: " << psnr.average << std::endl; |
| printf("SSIM: %f \n", ssim.average); |
| ( _log) << "SSIM: " << ssim.average << std::endl; |
| (_log) << std::endl; |
| |
| printf("\nVCM Normal Test: \n\n%i tests completed\n", vcmMacrosTests); |
| if (vcmMacrosErrors > 0) |
| { |
| printf("%i FAILED\n\n", vcmMacrosErrors); |
| } |
| else |
| { |
| printf("ALL PASSED\n\n"); |
| } |
| } |
| void |
| NormalTest::Teardown() |
| { |
| //_log.close(); |
| fclose(_sourceFile); |
| fclose(_encodedFile); |
| return; |
| } |