| /* |
| * 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 "test.h" |
| #include "video_source.h" |
| #include "vplib.h" |
| #include "event_wrapper.h" |
| #include "thread_wrapper.h" |
| #include <iostream> |
| #include <fstream> |
| #include <cmath> |
| #include <ctime> |
| #include <string.h> |
| #include <cassert> |
| #include <vector> |
| |
| using namespace webrtc; |
| |
| long filesize(const char *filename); // local function defined at end of file |
| |
| struct SSIMcontext |
| { |
| SSIMcontext() : |
| refFileName(NULL), testFileName(NULL), width(0), height(0), |
| SSIMptr(NULL), startFrame(-1), endFrame(-1), evnt(NULL) {}; |
| SSIMcontext(const char *ref, const char *test, int w, int h, double *Sptr, |
| int start, int end, EventWrapper* ev) : |
| refFileName(ref), testFileName(test), width(w), height(h), |
| SSIMptr(Sptr), startFrame(start), endFrame(end), evnt(ev) {}; |
| const char *refFileName; |
| const char *testFileName; |
| int width; |
| int height; |
| double *SSIMptr; |
| int startFrame; |
| int endFrame; |
| EventWrapper* evnt; |
| }; |
| |
| Test::Test(std::string name, std::string description) |
| : |
| _name(name), |
| _description(description), |
| _bitRate(0), |
| _inname(""), |
| _outname(""), |
| _encodedName("") |
| { |
| memset(&_inst, 0, sizeof(_inst)); |
| unsigned int seed = static_cast<unsigned int>(0); |
| std::srand(seed); |
| } |
| |
| Test::Test(std::string name, std::string description, WebRtc_UWord32 bitRate) |
| : |
| _name(name), |
| _description(description), |
| _bitRate(bitRate), |
| _inname(""), |
| _outname(""), |
| _encodedName("") |
| { |
| memset(&_inst, 0, sizeof(_inst)); |
| unsigned int seed = static_cast<unsigned int>(0); |
| std::srand(seed); |
| } |
| |
| void |
| Test::Print() |
| { |
| std::cout << _name << " completed!" << std::endl; |
| (*_log) << _name << std::endl; |
| (*_log) << _description << std::endl; |
| (*_log) << "Input file: " << _inname << std::endl; |
| (*_log) << "Output file: " << _outname << std::endl; |
| double psnr = -1.0, ssim = -1.0; |
| PSNRfromFiles(_inname.c_str(), _outname.c_str(), _inst.width, _inst.height, &psnr); |
| ssim = SSIMfromFilesMT(4 /* number of threads*/); |
| |
| (*_log) << "PSNR: " << psnr << std::endl; |
| std::cout << "PSNR: " << psnr << std::endl << std::endl; |
| (*_log) << "SSIM: " << ssim << std::endl; |
| std::cout << "SSIM: " << ssim << std::endl << std::endl; |
| (*_log) << std::endl; |
| } |
| |
| void |
| Test::Setup() |
| { |
| int widhei = _inst.width*_inst.height; |
| _lengthSourceFrame = 3*widhei/2; |
| _sourceBuffer = new unsigned char[_lengthSourceFrame]; |
| } |
| |
| void |
| Test::CodecSettings(int width, int height, WebRtc_UWord32 frameRate /*=30*/, WebRtc_UWord32 bitRate /*=0*/) |
| { |
| if (bitRate > 0) |
| { |
| _bitRate = bitRate; |
| } |
| else if (_bitRate == 0) |
| { |
| _bitRate = 600; |
| } |
| _inst.maxFramerate = (unsigned char)frameRate; |
| _inst.startBitrate = (int)_bitRate; |
| _inst.maxBitrate = 8000; |
| _inst.width = width; |
| _inst.height = height; |
| } |
| |
| void |
| Test::Teardown() |
| { |
| delete [] _sourceBuffer; |
| } |
| |
| void |
| Test::SetEncoder(webrtc::VideoEncoder*encoder) |
| { |
| _encoder = encoder; |
| } |
| |
| void |
| Test::SetDecoder(VideoDecoder*decoder) |
| { |
| _decoder = decoder; |
| } |
| |
| void |
| Test::SetLog(std::fstream* log) |
| { |
| _log = log; |
| } |
| |
| int |
| Test::PSNRfromFiles(const char *refFileName, const char *testFileName, int width, int height, double *YPSNRptr) |
| { |
| FILE *refFp = fopen(refFileName, "rb"); |
| if( refFp == NULL ) { |
| // cannot open reference file |
| fprintf(stderr, "Cannot open file %s\n", refFileName); |
| return -1; |
| } |
| |
| FILE *testFp = fopen(testFileName, "rb"); |
| if( testFp == NULL ) { |
| // cannot open test file |
| fprintf(stderr, "Cannot open file %s\n", testFileName); |
| return -2; |
| } |
| |
| double mse = 0.0; |
| double mseLogSum = 0.0; |
| int frames = 0; |
| |
| int frameBytes = 3*width*height/2; // bytes in one frame I420 |
| unsigned char *ref = new unsigned char[frameBytes]; // space for one frame I420 |
| unsigned char *test = new unsigned char[frameBytes]; // space for one frame I420 |
| |
| int refBytes = (int) fread(ref, 1, frameBytes, refFp); |
| int testBytes = (int) fread(test, 1, frameBytes, testFp); |
| |
| while( refBytes == frameBytes && testBytes == frameBytes ) |
| { |
| mse = 0.0; |
| |
| // calculate Y sum-square-difference |
| for( int k = 0; k < width * height; k++ ) |
| { |
| mse += (test[k] - ref[k]) * (test[k] - ref[k]); |
| } |
| |
| // divide by number of pixels |
| mse /= (double) (width * height); |
| |
| // accumulate for total average |
| mseLogSum += std::log10( mse ); |
| frames++; |
| |
| refBytes = (int) fread(ref, 1, frameBytes, refFp); |
| testBytes = (int) fread(test, 1, frameBytes, testFp); |
| } |
| |
| // ypsnrAvg = sum( 10 log (255^2 / MSE) ) / frames |
| // = 20 * log(255) - 10 * mseLogSum / frames |
| *YPSNRptr = 20.0 * std::log10(255.0) - 10.0 * mseLogSum / frames; |
| |
| delete [] ref; |
| delete [] test; |
| |
| fclose(refFp); |
| fclose(testFp); |
| |
| return 0; |
| } |
| int |
| Test::SSIMfromFiles(const char *refFileName, const char *testFileName, int width, int height, double *SSIMptr, |
| int startFrame /*= -1*/, int endFrame /*= -1*/) |
| { |
| FILE *refFp = fopen(refFileName, "rb"); |
| if( refFp == NULL ) { |
| // cannot open reference file |
| fprintf(stderr, "Cannot open file %s\n", refFileName); |
| return -1; |
| } |
| |
| FILE *testFp = fopen(testFileName, "rb"); |
| if( testFp == NULL ) { |
| // cannot open test file |
| fprintf(stderr, "Cannot open file %s\n", testFileName); |
| return -2; |
| } |
| |
| int frames = 0; |
| |
| int frameBytes = 3*width*height/2; // bytes in one frame I420 |
| unsigned char *ref = new unsigned char[frameBytes]; // space for one frame I420 |
| unsigned char *test = new unsigned char[frameBytes]; // space for one frame I420 |
| |
| if (startFrame >= 0) |
| { |
| if (fseek(refFp, frameBytes * startFrame, SEEK_SET) != 0){ |
| fprintf(stderr, "Cannot go to frame %i in %s\n", startFrame, refFileName); |
| return -1; |
| } |
| if (fseek(testFp, frameBytes * startFrame, SEEK_SET) != 0){ |
| fprintf(stderr, "Cannot go to frame %i in %s\n", startFrame, testFileName); |
| return -1; |
| } |
| } |
| |
| int refBytes = (int) fread(ref, 1, frameBytes, refFp); |
| int testBytes = (int) fread(test, 1, frameBytes, testFp); |
| |
| // |
| // SSIM: variable definition, window function, initialization |
| int window = 10; |
| int flag_window = 0; //0 for uniform window filter, 1 for gaussian symmetric window |
| float variance_window = 2.0; //variance for window function |
| float ssimFilter[121]; //2d window filter: typically 11x11 = (window+1)*(window+1) |
| //statistics per column of window (#columns = window+1), 0 element for avg over all columns |
| float avgTest[12]; |
| float avgRef[12]; |
| float contrastTest[12]; |
| float contrastRef[12]; |
| float crossCorr[12]; |
| // |
| //offsets for stability |
| float offset1 = 0.1f; |
| float offset2 = 0.1f; |
| float offset3 = offset2/2; |
| // |
| //define window for SSIM: take uniform filter for now |
| float sumfil = 0.0; |
| int nn=-1; |
| for(int j=-window/2;j<=window/2;j++) |
| for(int i=-window/2;i<=window/2;i++) |
| { |
| nn+=1; |
| if (flag_window == 0) |
| ssimFilter[nn] = 1.0; |
| else |
| { |
| float dist = (float)(i*i) + (float)(j*j); |
| float tmp = 0.5f*dist/variance_window; |
| ssimFilter[nn] = exp(-tmp); |
| } |
| sumfil +=ssimFilter[nn]; |
| } |
| //normalize window |
| nn=-1; |
| for(int j=-window/2;j<=window/2;j++) |
| for(int i=-window/2;i<=window/2;i++) |
| { |
| nn+=1; |
| ssimFilter[nn] = ssimFilter[nn]/((float)sumfil); |
| } |
| // |
| float ssimScene = 0.0; //avgerage SSIM for sequence |
| // |
| //SSIM: done with variables and defintion |
| // |
| |
| while( refBytes == frameBytes && testBytes == frameBytes && |
| !(endFrame >= 0 && frames > endFrame - startFrame)) |
| { |
| float ssimFrame = 0.0; |
| int sh = window/2+1; |
| int numPixels = 0; |
| for(int i=sh;i<height-sh;i++) |
| for(int j=sh;j<width-sh;j++) |
| { |
| avgTest[0] = 0.0; |
| avgRef[0] = 0.0; |
| contrastTest[0] = 0.0; |
| contrastRef[0] = 0.0; |
| crossCorr[0] = 0.0; |
| |
| numPixels +=1; |
| |
| //for uniform window, only need to loop over whole window for first column pixel in image, and then shift |
| if (j == sh || flag_window == 1) |
| { |
| //initialize statistics |
| for(int k=1;k<window+2;k++) |
| { |
| avgTest[k] = 0.0; |
| avgRef[k] = 0.0; |
| contrastTest[k] = 0.0; |
| contrastRef[k] = 0.0; |
| crossCorr[k] = 0.0; |
| } |
| int nn=-1; |
| //compute contrast and correlation |
| for(int jj=-window/2;jj<=window/2;jj++) |
| for(int ii=-window/2;ii<=window/2;ii++) |
| { |
| nn+=1; |
| int i2 = i+ii; |
| int j2 = j+jj; |
| float tmp1 = (float)test[i2*width+j2]; |
| float tmp2 = (float)ref[i2*width+j2]; |
| //local average of each signal |
| avgTest[jj+window/2+1] += ssimFilter[nn]*tmp1; |
| avgRef[jj+window/2+1] += ssimFilter[nn]*tmp2; |
| //local correlation/contrast of each signal |
| contrastTest[jj+window/2+1] += ssimFilter[nn]*tmp1*tmp1; |
| contrastRef[jj+window/2+1] += ssimFilter[nn]*tmp2*tmp2; |
| //local cross correlation |
| crossCorr[jj+window/2+1] += ssimFilter[nn]*tmp1*tmp2; |
| } |
| } |
| //for uniform window case, can shift window horiz, then compute statistics for last column in window |
| else |
| { |
| //shift statistics horiz. |
| for(int k=1;k<window+1;k++) |
| { |
| avgTest[k]=avgTest[k+1]; |
| avgRef[k]=avgRef[k+1]; |
| contrastTest[k] = contrastTest[k+1]; |
| contrastRef[k] = contrastRef[k+1]; |
| crossCorr[k] = crossCorr[k+1]; |
| } |
| //compute statistics for last column |
| avgTest[window+1] = 0.0; |
| avgRef[window+1] = 0.0; |
| contrastTest[window+1] = 0.0; |
| contrastRef[window+1] = 0.0; |
| crossCorr[window+1] = 0.0; |
| int nn = (window+1)*window - 1; |
| int jj = window/2; |
| int j2 = j + jj; |
| for(int ii=-window/2;ii<=window/2;ii++) |
| { |
| nn+=1; |
| int i2 = i+ii; |
| float tmp1 = (float)test[i2*width+j2]; |
| float tmp2 = (float)ref[i2*width+j2]; |
| //local average of each signal |
| avgTest[jj+window/2+1] += ssimFilter[nn]*tmp1; |
| avgRef[jj+window/2+1] += ssimFilter[nn]*tmp2; |
| //local correlation/contrast of each signal |
| contrastTest[jj+window/2+1] += ssimFilter[nn]*tmp1*tmp1; |
| contrastRef[jj+window/2+1] += ssimFilter[nn]*tmp2*tmp2; |
| //local cross correlation |
| crossCorr[jj+window/2+1] += ssimFilter[nn]*tmp1*tmp2; |
| } |
| } |
| |
| //sum over all columns |
| for(int k=1;k<window+2;k++) |
| { |
| avgTest[0] += avgTest[k]; |
| avgRef[0] += avgRef[k]; |
| contrastTest[0] += contrastTest[k]; |
| contrastRef[0] += contrastRef[k]; |
| crossCorr[0] += crossCorr[k]; |
| } |
| |
| float tmp1 = (contrastTest[0] - avgTest[0]*avgTest[0]); |
| if (tmp1 < 0.0) tmp1 = 0.0; |
| contrastTest[0] = sqrt(tmp1); |
| float tmp2 = (contrastRef[0] - avgRef[0]*avgRef[0]); |
| if (tmp2 < 0.0) tmp2 = 0.0; |
| contrastRef[0] = sqrt(tmp2); |
| crossCorr[0] = crossCorr[0] - avgTest[0]*avgRef[0]; |
| |
| float ssimCorrCoeff = (crossCorr[0]+offset3)/(contrastTest[0]*contrastRef[0] + offset3); |
| float ssimLuminance = (2*avgTest[0]*avgRef[0]+offset1)/(avgTest[0]*avgTest[0] + avgRef[0]*avgRef[0] + offset1); |
| float ssimContrast = (2*contrastTest[0]*contrastRef[0]+offset2)/(contrastTest[0]*contrastTest[0] + contrastRef[0]*contrastRef[0] + offset2); |
| |
| float ssimPixel = ssimCorrCoeff * ssimLuminance * ssimContrast; |
| ssimFrame += ssimPixel; |
| } |
| ssimFrame = ssimFrame / (numPixels); |
| //printf("***SSIM for frame ***%f \n",ssimFrame); |
| ssimScene += ssimFrame; |
| // |
| //SSIM: done with SSIM computation |
| // |
| |
| frames++; |
| |
| refBytes = (int) fread(ref, 1, frameBytes, refFp); |
| testBytes = (int) fread(test, 1, frameBytes, testFp); |
| |
| } |
| |
| //SSIM: normalize/average for sequence |
| ssimScene = ssimScene / frames; |
| *SSIMptr = ssimScene; |
| |
| delete [] ref; |
| delete [] test; |
| |
| fclose(refFp); |
| fclose(testFp); |
| |
| return 0; |
| } |
| |
| bool |
| Test::SSIMthread(void *vctx) |
| { |
| SSIMcontext *ctx = (SSIMcontext *) vctx; |
| SSIMfromFiles(ctx->refFileName, ctx->testFileName, ctx->width, ctx->height, ctx->SSIMptr, ctx->startFrame, ctx->endFrame); |
| ctx->evnt->Set(); |
| return false; |
| } |
| |
| double Test::SSIMfromFilesMT(const int numThreads) |
| { |
| int numFrames = filesize(_inname.c_str()) / _lengthSourceFrame; |
| std::vector<int> nFramesVec(numThreads); |
| std::vector<double> ssimVec(numThreads); |
| int framesPerCore = (numFrames + numThreads - 1) / numThreads; // rounding up |
| int i = 0; |
| int nFrames; |
| for (nFrames = numFrames; nFrames >= framesPerCore; nFrames -= framesPerCore) |
| { |
| nFramesVec[i++] = framesPerCore; |
| } |
| if (nFrames > 0) |
| { |
| assert(i == numThreads - 1); |
| nFramesVec[i] = nFrames; // remainder |
| } |
| |
| int frameIx = 0; |
| std::vector<EventWrapper*> eventVec(numThreads); |
| std::vector<ThreadWrapper*> threadVec(numThreads); |
| std::vector<SSIMcontext> ctxVec(numThreads); |
| for (i = 0; i < numThreads; i++) |
| { |
| eventVec[i] = EventWrapper::Create(); |
| ctxVec[i] = SSIMcontext(_inname.c_str(), _outname.c_str(), _inst.width, _inst.height, &ssimVec[i], frameIx, frameIx + nFramesVec[i] - 1, eventVec[i]); |
| threadVec[i] = ThreadWrapper::CreateThread(SSIMthread, &(ctxVec[i]), kLowPriority); |
| unsigned int id; |
| threadVec[i]->Start(id); |
| frameIx += nFramesVec[i]; |
| } |
| |
| // wait for all events |
| for (i = 0; i < numThreads; i++) { |
| eventVec[i]->Wait(100000 /* ms*/); |
| threadVec[i]->Stop(); |
| delete threadVec[i]; |
| delete eventVec[i]; |
| } |
| |
| double avgSsim = 0; |
| for (i = 0; i < numThreads; i++) |
| { |
| avgSsim += (ssimVec[i] * nFramesVec[i]); |
| } |
| |
| avgSsim /= numFrames; |
| return avgSsim; |
| } |
| |
| |
| double Test::ActualBitRate(int nFrames) |
| { |
| return 8.0 * _sumEncBytes / (nFrames / _inst.maxFramerate); |
| } |
| |
| bool Test::PacketLoss(double lossRate) |
| { |
| return RandUniform() < lossRate; |
| } |
| |
| void |
| Test::VideoBufferToRawImage(TestVideoBuffer& videoBuffer, RawImage &image) |
| { |
| image._buffer = videoBuffer.GetBuffer(); |
| image._size = videoBuffer.GetSize(); |
| image._length = videoBuffer.GetLength(); |
| image._width = videoBuffer.GetWidth(); |
| image._height = videoBuffer.GetHeight(); |
| image._timeStamp = videoBuffer.GetTimeStamp(); |
| } |
| void |
| Test::VideoEncodedBufferToEncodedImage(TestVideoEncodedBuffer& videoBuffer, EncodedImage &image) |
| { |
| image._buffer = videoBuffer.GetBuffer(); |
| image._length = videoBuffer.GetLength(); |
| image._size = videoBuffer.GetSize(); |
| image._frameType = static_cast<VideoFrameType>(videoBuffer.GetFrameType()); |
| image._timeStamp = videoBuffer.GetTimeStamp(); |
| image._encodedWidth = videoBuffer.GetCaptureWidth(); |
| image._encodedHeight = videoBuffer.GetCaptureHeight(); |
| image._completeFrame = true; |
| } |
| |
| long filesize(const char *filename) |
| { |
| FILE *f = fopen(filename,"rb"); /* open the file in read only */ |
| |
| long size = 0; |
| if (fseek(f,0,SEEK_END)==0) /* seek was successful */ |
| size = ftell(f); |
| fclose(f); |
| return size; |
| } |