項目:基于ffmpeg視頻質量安卓測試平臺.(計算PSNR與SSIM)

  • YUV420

  • H264

  • 硬解碼,軟解碼

  • 硬編碼仑性,軟編碼

  • PSNR

  • SSIM

以上關系大致上可以這么看:
mp4==>MediaMuxer解封==>H264==>解碼=>YUV==>有損編碼==>H264==>MediaMuxer封裝==>有損mp4

============================================================================================

  • YUV420

YUV是一種亮度信號Y和色度信號U、V是分離的色彩空間还惠,它主要用于優(yōu)化彩色視頻信號的傳輸,使其向后相容老式黑白電視。與RGB視頻信號傳輸相比遭商,它最大的優(yōu)點在于只需占用極少的頻寬(RGB要求三個獨立的視頻信號同時傳輸)。其中“Y”表示明亮度(Luminance或Luma)捅伤,也就是灰階值劫流;而“U”和“V”表示的則是色度(Chrominance或Chroma),作用是描述影像色彩及飽和度丛忆,用于指定像素的顏色祠汇。

YUV主流有三種格式格式。

YUV444,YUV422,YUV420.為什么分了三種呢蘸际? 因為人眼對亮度感受明顯,對色度感受不明顯徒扶。所以粮彤,這三種格式完全保留了亮度信息,而對色度信息有一定的取舍。
如何取舍导坟?

image.png

黑點表示采樣該像素點的Y分量屿良,以空心圓圈表示采用該像素點的UV分量。

  • YUV444:YUV444即表示Y惫周、U尘惧、V所占比為4:4:4,這種采樣方式的色度值UV不會較少采樣递递,Y喷橙、U、V分量各占一個字節(jié)登舞,連同Alpha通道一個字節(jié)贰逾,YUV444每個像素占4字節(jié),也就是說這個格式實質就是24bpp的RGB格式菠秒。采樣示例:
    如果原始數(shù)據(jù)四個像素是:A0Y0 U0 V0 ,A1 Y1 U1 V1,A2 Y2 U2 V2,A3 Y3 U3 V3(每個像素(YUV)占32Bits)
    經過4:4:4采樣后疙剑,數(shù)據(jù)仍為:A0Y0 U0 V0 ,A1 Y1 U1 V1,A2 Y2 U2 V2,A3 Y3 U3 V3
  • YUV422:YUV422即表示Y、U践叠、V所占比為4:2:2言缤,這種采樣方式的色度值UV分量采樣減半,比如第一個像素采樣為Y禁灼、U管挟,第二個像素采樣Y、V匾二,依此類推…YUV422每個像素占2個字節(jié)哮独。采樣示例:
    如果原始數(shù)據(jù)四個像素是:Y0U0 V0 ,Y1 U1 V1,Y2 U2 V2,Y3 U3 V3(每個像素占16bits)
    經過4:2:2采樣后,數(shù)據(jù)變成:Y0U0 ,Y1 V1 ,Y2 U2,Y3 V3
  • YUV420:YUV420采樣并不意味沒有V分量察藐,0的意思是U皮璧、V分量隔行才采樣一次,比如第一行采樣為4:2:0分飞,第二行采樣4:0:2悴务,依此類推…YUV采樣(每個像素)占用16bits或12bits∑┟ǎ總之除了4:4:4采樣讯檐,其余采樣后信號重新還原顯示后,會丟失部分UV數(shù)據(jù),只能用相臨的數(shù)據(jù)補齊染服,但人眼對UV不敏感别洪,因此總體感覺損失不大。
YUV420 的兩種幀格式

CameraPrevieCallback實時采集的視頻幀格式為YV12或者NV21柳刮,它們都屬于YUV420采樣格式挖垛。YUV格式的存放方式永遠是先排列完Y分量痒钝,再排序U或V分量,不同的采樣只是Y或V分量的排列格式和順序不同痢毒。送矩。NV21、NV12的區(qū)別在于Y值排序完全相同哪替,U和V交錯排序栋荸,不同在于UV順序:

  • NV12存儲方式:Y0Y1Y2Y3 U0V0

  • NV21存儲方式:Y0Y1Y2Y3 V0U0

image.png

所以可以分析出對于1920×1080的資源每幀的大小為:1920×1080×2/3。
轉換方式在網上很多凭舶,基本原理就是旋轉==》數(shù)值的交換晌块。
==================================================================================

  • H264(視頻編碼方式)

YUV文件非常非常大,測試中10m的mp4文件库快,解碼之后YUV420文件達500m摸袁。Camera采集的YUV圖像通常為YUV420,而在現(xiàn)實網絡中义屏,這么高的上行寬帶一般是很難達到的靠汁,因此,我們就必須在傳輸之前對采集的視頻數(shù)據(jù)(YUV文件)進行視頻壓縮編碼闽铐。所以我們需要H264了蝶怔。所謂視頻編碼方式就是指能夠對數(shù)字視頻進行壓縮或者解壓縮(視頻解碼)的程序或者設備。通常這種壓縮屬于有損數(shù)據(jù)壓縮兄墅。

H.264簡介:
H.264是MPEG-4的第十部分踢星,是由VCEG和MPEG聯(lián)合提出的高度壓縮數(shù)字視頻編碼器標準,目前在多媒體開發(fā)應用中非常廣泛隙咸。H.264具有低碼率沐悦、高壓縮、高質量的圖像五督、容錯能力強藏否、網絡適應性強等特點,它最大的優(yōu)勢擁有很高的數(shù)據(jù)壓縮比率充包,在同等圖像質量的條件下副签,H.264的壓縮比是MPEG-2的兩倍以上。
總之基矮,就是壓縮壓縮淆储,如何壓縮呢?

三種幀(I家浇,B本砰,P)的協(xié)作。

在H.264協(xié)議里定義了三種幀钢悲,完整編碼的幀叫I幀(關鍵幀)点额,參考之前的I幀生成的只包含差異部分編碼的幀叫P幀青团,還有一種參考前后的幀編碼的幀叫B幀。H.264編碼采用的核心算法是幀內壓縮幀間壓縮咖楣。

  • 幀內壓縮是生成I幀的算法,它的原理是當壓縮一幀圖像時芦昔,僅考慮本幀的數(shù)據(jù)而不用考慮相鄰幀之間的冗余信息诱贿,由于幀內壓縮是編碼一個完整的圖像,所以可以獨立的解碼顯示咕缎;
  • 幀間壓縮是生成P珠十、B幀的算法,它的原理是通過對比相鄰兩幀之間的數(shù)據(jù)進行壓縮凭豪,進一步提高壓縮量焙蹭,減少壓縮比。

通俗的來說嫂伞,H.264編碼的就是對于一段變化不大圖像畫面孔厉,我們可以先編碼出一個完整的圖像幀A,隨后的B幀就不編碼全部圖像帖努,只寫入與A幀的差別撰豺,這樣B幀的大小就只有完整幀的1/10或更小。B幀之后的C幀如果變化不大拼余,我們可以繼續(xù)以參考B的方式編碼C幀污桦,這樣循環(huán)下去。

三種幀如何協(xié)作呢匙监?

  • IDR(Instantaneous Decoding Refresh):即時解碼刷新凡橱。一個序列的第一個圖像叫做IDR 圖像(立即刷新圖像),IDR 圖像都是I 幀圖像(關鍵幀)亭姥。H.264引入 IDR 圖像是為了解碼的重同步稼钩,當解碼器解碼到IDR 圖像時,立即將參考幀隊列清空致份,將已解碼的數(shù)據(jù)全部輸出或拋棄变抽,重新查找參數(shù)集,開始一個新的序列氮块。這樣绍载,如果前一個序列出現(xiàn)重大錯誤,在這里可以獲得重新同步的機會滔蝉。IDR圖像之后的圖像永遠不會使用IDR之前的圖像的數(shù)據(jù)來解碼击儡。
  • P幀:前向預測編碼幀。P幀表示的是這一幀跟之前的一個關鍵幀(或P幀)的差別蝠引,它P幀是I幀后面相隔1~2幀的編碼幀阳谍,其沒有完整畫面數(shù)據(jù)蛀柴,只有與前一幀的、畫面差別的數(shù)據(jù)矫夯。
  • B幀:雙向預測內插編碼幀鸽疾。B幀記錄的是本幀與前后幀的差別,它是由前面的I或P幀和后面的P幀來進行預測的训貌。
    三種幀如何協(xié)作呢制肮?
    H.264編碼采用的核心算法是幀內壓縮幀間壓縮
  • 幀內壓縮是生成I幀的算法递沪,它的原理是當壓縮一幀圖像時豺鼻,僅考慮本幀的數(shù)據(jù)而不用考慮相鄰幀之間的冗余信息,由于幀內壓縮是編碼一個完整的圖像款慨,所以可以獨立的解碼顯示儒飒;
  • 幀間壓縮是生成P、B幀的算法檩奠,它的原理是通過對比相鄰兩幀之間的數(shù)據(jù)進行壓縮桩了,進一步提高壓縮量,減少壓縮比埠戳。

h264的壓縮方法:

1.分組:把幾幀圖像分為一組(GOP圣猎,也就是一個序列),為防止運動變化,幀數(shù)不宜取多。
2.定義幀:將每組內各幀圖像定義為三種類型,即I幀乞而、B幀和P幀;
3.預測幀:以I幀做為基礎幀,以I幀預測P幀,再由I幀和P幀預測B幀;
4.數(shù)據(jù)傳輸:最后將I幀數(shù)據(jù)與預測的差值信息進行存儲和傳輸送悔。

通俗的來說,H.264編碼的就是對于一段變化不大圖像畫面爪模,我們可以先編碼出一個完整的圖像幀A欠啤,隨后的B幀就不編碼全部圖像,只寫入與A幀的差別屋灌,這樣B幀的大小就只有完整幀的1/10或更小洁段。B幀之后的C幀如果變化不大,我們可以繼續(xù)以參考B的方式編碼C幀共郭,這樣循環(huán)下去祠丝。

在ffmpeg里,I幀間隔可以自己設置除嘹,用來測試不同I幀間隔對視頻質量有何影響写半。

==================================================================================

  • MP4(視頻文件格式)

容器mp4,rmvb,mkv,avi從形式上來說首先都是視頻文件的擴展名,其次它們也是視頻文件的封裝格式(即容器)mp4是MPEG-4標準的第14部分所制定的容器標準尉咕。所謂容器叠蝇,就是把編碼器生成的多媒體內容(視頻,音頻年缎,字幕悔捶,章節(jié)信息等)混合封裝在一起的標準铃慷。
容器使得不同多媒體內容同步播放變得很簡單,而容器的另一個作用就是為多媒體內容提供索引蜕该,也就是說如果沒有容器存在的話一部影片你只能從一開始看到最后犁柜,不能拖動進度條(當然這種情況下有的播放器會話比較長的時間臨時創(chuàng)建索引),而且如果你不自己去手動另外載入音頻就沒有聲音堂淡。
==================================================================================

  • 硬解碼赁温,軟解碼

硬件解碼是將原來全部交由CPU來處理的視頻數(shù)據(jù)的一部分交由GPU來做,而GPU的并行運算能力要遠遠高于CPU淤齐,這樣可以大大的降低對CPU的負載,CPU的占用率較低了之后就可以同時運行一些其他的程序了袜匿。在Android中使用硬件解碼直接使用MediaCodec就可以了更啄,雖然MediaPlayer也是硬件解碼,但是被封裝得太死了居灯,支持的協(xié)議很少祭务。而MediaCodec就很好拓展,我們可以根據(jù)流媒體的協(xié)議和設備硬件本身來自定義硬件解碼怪嫌,代表播放器就是Google的ExoPlayer义锥。
軟解碼:即通過軟件讓CPU來對視頻進行解碼處理,就是通過CPU來運行視頻編解碼代碼岩灭,我們最最常見的視頻軟解碼開源看就是FFmpeg.

==================================================================================

  • 硬編碼拌倍,軟編碼

消費者生產者模型:

image.png

編碼流程:
configure:首先要初始化硬件編碼器,配置要編碼的格式噪径、視頻文件的長寬柱恤、碼率、幀率找爱、關鍵幀間隔等等梗顺。
開啟編碼器,當前編碼器便是可用狀態(tài)车摄,隨時準備接收數(shù)據(jù)
running:在此過程中寺谤,需要維護兩個buffer隊列,InputBuffer 和OutputBuffer吮播,用戶需要不斷出隊InputBuffer (即dequeueInputBuffer)变屁,往里邊放入需要編碼的圖像數(shù)據(jù)之后再入隊等待處理,然后硬件編碼器開始異步處理意狠,一旦處理結束敞贡,他會將數(shù)據(jù)放在OutputBuffer中,并且通知用戶當前有輸出數(shù)據(jù)可用了摄职,那么用戶就可以出隊一個OutputBuffer誊役,將其中的數(shù)據(jù)拿走获列,然后釋放掉這個buffer。
end:結束條件在于end-of-stream這個flag標志位的設定蛔垢。在編碼結束后击孩,編碼器調用stop函數(shù)停止編碼,之后調用release函數(shù)將編碼器完全釋放掉鹏漆,整體流程結束巩梢。

編碼處理
byte[] input = data;
byte[] yuv420sp = new byte[width*height*3/2]; 
NV21ToNV12(input,yuv420sp,width,height); 
input = yuv420sp;
if (input != null) { try { ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();//拿到輸入緩沖區(qū),用于傳送數(shù)據(jù)進行編碼
ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers();//拿到輸出緩沖區(qū),用于取到編碼后的數(shù)據(jù)
int inputBufferIndex = mediaCodec.dequeueInputBuffer(-1);
 if (inputBufferIndex >= 0) {//當輸入緩沖區(qū)有效時,就是>=0
ByteBuffer inputBuffer = inputBuffers[inputBufferIndex]; inputBuffer.clear(); 
inputBuffer.put(input);//往輸入緩沖區(qū)寫入數(shù)據(jù), // //五個參數(shù),第一個是輸入緩沖區(qū)的索引艺玲,第二個數(shù)據(jù)是輸入緩沖區(qū)起始索引括蝠,第三個是放入的數(shù)據(jù)大小,第四個是時間戳饭聚,保證遞增就是 
mediaCodec.queueInputBuffer(inputBufferIndex, 0, input.length, System.nanoTime() / 1000, 0); } 
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); 
int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC);//拿到輸出緩沖區(qū)的索引 
while (outputBufferIndex >= 0) { ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
 byte[] outData = new byte[bufferInfo.size]; outputBuffer.get(outData); //outData就是輸出的h264數(shù)據(jù) 
outputStream.write(outData, 0, outData.length);//將輸出的h264數(shù)據(jù)保存為文件忌警,用vlc就可以播放 
mediaCodec.releaseOutputBuffer(outputBufferIndex, false); 
outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC); } } 
catch (Throwable t) 
{ t.printStackTrace(); } }

創(chuàng)建文件夾
private void createfile(){ File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/test.h264"); 
if(file.exists()){
    file.delete(); }
 try { outputStream = new BufferedOutputStream(new FileOutputStream(file)); } 
catch (Exception e){ e.printStackTrace(); } }

獲取軟編碼器

MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
 (codecInfo.getName().contains("OMX.google") || codecInfo.getName().contains("OMX.ffmeg")

獲取硬編碼器

!codecInfo.getName().contains("OMX.google") && !codecInfo.getName().contains("OMX.ffmeg")

==================================================================================

PSNR& SSIM

  • 全參考客觀視頻質量評價方法 SSIM

把原始參考視頻與失真視頻在每一個對應幀中的每一個對應像素之問進行比較。準確的講秒梳,這種方法得到的并不是真正的視頻質量法绵,而是失真視頻相對于原始視頻的相似程度或保真程度。

SSIM = Structural SIMilarity(結構相似性)酪碘,這是一種用來評測圖像質量的一種方法朋譬。由于人類視覺很容易從圖像中抽取出結構信息,因此計算兩幅圖像結構信息的相似性就可以用來作為一種檢測圖像質量的好壞.其取值范圍為[0,1],值越大越好兴垦;
結構相似性理論認為徙赢,圖像是高度結構化的,即像素間有很強的相關性探越,特別是空域中最接近的像素犀忱,這種相關性蘊含著視覺場景中物體結構的重要信息;作為結構相似性理論的實現(xiàn)扶关,結構相似度指數(shù)從圖像組成的角度將結構信息定義為獨立于亮度阴汇、對比度的,反映場景中物體結構的屬性节槐,并將失真建模為亮度搀庶、對比度和結構三個不同因素的組合。用均值作為亮度的估計铜异,標準差作為對比度的估計哥倔,協(xié)方差作為結構相似程度的估計,計算數(shù)學模型如下:亮度表示L;對比度表示C;結構相似性表示S;通常取C1=(K1L)^2,C2=(K2L)^2, C3=C2/2, K1=0.01, K2=0.03, L=255.

image.png

最后的SSIM指數(shù)為:


image.png

當我們設定C3=C2/2時,我們可以將公式改寫成更加簡單的形式:


image.png

SSIM相當于將數(shù)據(jù)進行歸一化后,分別計算圖像塊照明度(圖像塊的均值)尉尾,對比度(圖像塊的方差)和歸一化后的像素向量這三者相似度本缠,并將三者相乘沃测。
  • 峰值信噪比PSNR

PSNR是最普遍和使用最為廣泛的一種圖像客觀評價指標缭黔,然而它是基于對應像素點間的誤差,即基于誤差敏感的圖像質量評價蒂破。由于并未考慮到人眼的視覺特性(人眼對空間頻率較低的對比差異敏感度較高馏谨,人眼對亮度對比差異的敏感度較色度高,人眼對一個區(qū)域的感知結果會受到其周圍鄰近區(qū)域的影響等)附迷,因而經常出現(xiàn)評價結果與人的主觀感覺不一致的情況惧互。

PSNR定義與計算

PSNR本質上與MSE相同,是MSE的對數(shù)表示喇伯。

對于大多數(shù)常見的8bit/彩色視頻圖像喊儡,
PSNR完全由MSE確定。PSNR較MSE更常用稻据,因為人們想把圖像質量與某個范圍的PSNR相聯(lián)系艾猜。

根據(jù)實際經驗,對于亮度像素分量:
PSNR高于40dB說明圖像質量極好(即非常接近原始圖像)攀甚,
在30—40dB通常表示圖像質量是好的(即失真可以察覺但可以接受),
在20—30dB說明圖像質量差岗喉;
最后秋度,PSNR低于20dB圖像不可接受

獲取每幀PSNR與SSIM的代碼:

這里用到了滑動窗口的算法:4×4的窗口2格2格的滑動 ;
isRD是用來畫制RD曲線圖,當沒有必要畫就去掉相應代碼即可钱床。

public class Psnr {
    private int width;
    private int height;
    private int YuvFormat;
    private int F;
    private String yuvSource;
    private String dstSource;
    private String filename;
    private boolean isSsim;
    private boolean isRD = true;
    private Handler handler;
    private float ave_psnr;
    private float ave_ssim;


    public Psnr(int width, int height, int yuvFormat, String y, String des, String filename, Handler handler) {
        this.width = width;
        this.height = height;
        this.YuvFormat = yuvFormat;
        this.yuvSource = y;
        this.dstSource = des;
        this.filename = filename;
        this.handler = handler;
        switch (YuvFormat) {
            case 400:
                F = this.width * this.height;
                break;
            case 422:
                F = this.width * this.height * 2;
                break;
            case 444:
                F = this.width * this.height * 3;
                break;
            default:
            case 420:
                F = this.width * this.height * 3 / 2;
                break;
        }
    }

    public float getSsim(byte[] data1, byte[] data2, int framecount) {
        if (!isSsim) {
            return -1;
        }
        try {

            Message message = new Message();
            if (framecount == 0) {
                message.what = -2;
                message.arg1 = 1;
                handler.sendMessage(message);
            }

            float tem_ssim;
            tem_ssim = x264_pixel_ssim_wxh(data1, width, data2, width, width, height);

            Message message1 = new Message();
            message1.what = 1;
            message1.arg1 = framecount;
            handler.sendMessage(message1);

            Log.i("SSIM", "tem_ssim" + tem_ssim + "_" + framecount);

            return tem_ssim;

        } catch (Exception e) {
            e.printStackTrace();
        }
        return -1;

    }
    /*
     * 功能:計算SSIM
     * s1: 一幀受損數(shù)據(jù)
     * s2: 一幀原始數(shù)據(jù)
     * i_width: 圖像寬
     * i_height: 圖像高
     */

    private float x264_pixel_ssim_wxh(byte[] s1, int stride1, byte[] s2, int stride2, int width, int height) {
        int x, y, z;
        float ssim = 0;
        //按照4x4的塊對像素進行處理的荚斯。使用sum1保存上一行塊的“信息”,sum0保存當前一行塊的“信息”
        ArrayList<int[]> sum0 = new ArrayList<>(width);
        /*
         * sum0是一個數(shù)組指針查牌,其中存儲了一個4元素數(shù)組的地址
         * 換句話說事期,sum0中每一個元素對應一個4x4塊的信息(該信息包含4個元素)。
         *
         * 4個元素中:
         * [0]原始像素之和
         * [1]受損像素之和
         * [2]原始像素平方之和+受損像素平方之和
         * [3]原始像素*受損像素的值的和
         *
         */
        ArrayList<int[]> sum1 = new ArrayList<>(width);
        width >>= 2;
        //除以4
        height >>= 2;
        z = 0;

        for (y = 1; y < height; y++) {
            //下面這個循環(huán)纸颜,只有在第一次執(zhí)行的時候執(zhí)行2次兽泣,處理第1行和第2行的塊
            //后面的都只會執(zhí)行一次
            for (; z <= y; z++) {
                //執(zhí)行完XCHG()之后,sum1[]存儲上1行塊的值(在上面)胁孙,而sum0[]等待ssim_4x4x2_core()計算當前行的值(在下面)
                ArrayList<int[]> temp = new ArrayList<>(width);

                //System.arraycopy(sum0,0,temp,width-1,width);
                if (sum0.size() > 0) {
                    temp.addAll(sum0);
                    sum0 = new ArrayList<>(sum1);
                    sum1 = new ArrayList<>(temp);
                }
                //獲取4x4塊的信息(4個值存于長度為4的一維數(shù)組)(這里并沒有代入公式計算SSIM結果)
                //結果存儲在sum0中唠倦。從左到右每個4x4的塊依次存儲在sum0.get[0],sum0.get[1]涮较,sum0.get[2]...
                //每次x前進2個塊
                /*
                 * ssim_4x4x2_core():計算2個4x4塊,兩個4×4有一半重疊部分
                 * +----+----+
                 * |    |    |
                 * +----+----+
                 */
                for (x = 0; x < width; x += 2) {
                    sum0.add(ssim_4x4x2_core(4 * (x + z * stride1), 4 * (x + z * stride2), s1, stride1, s2, stride2));
                    sum0.add(ssim_4x4x2_core(4 * (x + z * stride1) + 4, 4 * (x + z * stride2) + 4, s1, stride1, s2, stride2));
                    if (sum0.isEmpty() || sum0.get(x) == null || sum0.get(x + 1) == null || sum0.get(x).length != 4 || sum0.get(x).length != 4) {
                        return -1;
                    }
                }
            }
            for (x = 0; x < width; x += 4) {
                //sum1是儲存上一行的信息稠鼻,sum0是儲存本行的信息,ssim_end4是進行2(line)×4×4×2 2行每行2個4×4的塊的單元進行處理
                ssim += ssim_end4(x, sum0, sum1, Math.min(4, width - x - 1));
            }
            sum1.clear();
        }
        return ssim / ((width - 1) * (height - 1));
    }

    private int[] ssim_4x4x2_core(int shift1, int shift2, byte[] source1, int stride1, byte[] source2, int stride2) {

        int x, y, z;
        //“信息”包含4個元素:
        //
        //s1:原始像素之和狂票;
        //
        //s2:受損像素之和候齿;
        //
        //ss:原始像素平方之和+受損像素平方之和;
        //
        //s12:原始像素*受損像素的值的和。
        //每次計算兩個4×4的方格的信息

        int[] sum = new int[4];

        int s1 = 0;
        int s2 = 0;
        int ss = 0;
        int s12 = 0;
        for (y = 0; y < 4; y++) {
            for (x = 0; x < 4; x++) {
                int a = source1[x + y * stride1 + shift1] & 0xFF;
                int b = source2[x + y * stride2 + shift2] & 0xFF;
                s1 += a;
                s2 += b;
                ss += a * a;
                ss += b * b;
                s12 += a * b;
            }
        }
        sum[0] = s1;
        sum[1] = s2;
        sum[2] = ss;
        sum[3] = s12;

        return sum;
    }

    private double ssim_end4(int shift, ArrayList<int[]> sum0, ArrayList<int[]> sum1, int width) {
        double ssim = 0.0;
        for (int i = 0; i < width; i++) {

            ssim += ssim_end1(sum0.get(shift + i)[0] + sum0.get(shift + i + 1)[0] + sum1.get(shift + i)[0] + sum1.get(shift + i + 1)[0],
                    sum0.get(shift + i)[1] + sum0.get(shift + i + 1)[1] + sum1.get(shift + i)[1] + sum1.get(shift + i + 1)[1],
                    sum0.get(shift + i)[2] + sum0.get(shift + i + 1)[2] + sum1.get(shift + i)[2] + sum1.get(shift + i + 1)[2],
                    sum0.get(shift + i)[3] + sum0.get(shift + i + 1)[3] + sum1.get(shift + i)[3] + sum1.get(shift + i + 1)[3]);
        }
        return ssim;
    }

    private double ssim_end1(int s1, int s2, int ss, int s12) {
        int ssim_c1 = (int) (.01 * .01 * 255 * 255 * 64 + .5);
        int ssim_c2 = (int) (.03 * .03 * 255 * 255 * 64 * 63 + .5);
        int vars = ss * 64 - s1 * s1 - s2 * s2;
        int covar = s12 * 64 - s1 * s2;
        return (float) (2 * s1 * s2 + ssim_c1) * (float) (2 * covar + ssim_c2) / ((float) (s1 * s1 + s2 * s2 + ssim_c1) * (float) (vars + ssim_c2));
    }


    public float getPsnr(byte[] frame1, byte[] frame2, int framecount) {
        if (width == 0 || height == 0 || yuvSource.isEmpty() || dstSource.isEmpty()) {//YuvFormat????
            return -1;
        }

        Message message = new Message();
        if (framecount == 0) {
            message.what = -2;
            message.arg1 = 2;
            handler.sendMessage(message);
        }

        float mse = 0;
        float diff;
        float tem_psnr;
        int num = width * height;

        for (int n = 0; n < num; n++) {
            diff = ((frame1[n] & 0XFF) - (frame2[n] & 0xFF));
            mse += diff * diff;
        }

        mse = mse / (float) num;
        tem_psnr = (float) (10 * StrictMath.log10((255.0 * 255.0) / mse));

        Message message1 = new Message();
        message1.what = 2;
        message1.arg1 = framecount;
        handler.sendMessage(message1);
        return tem_psnr;

    }

    public int start() {
        return get2ImgArrayByFrame(new File(yuvSource), new File(dstSource));
    }

    private int get2ImgArrayByFrame(File imgSrcYuv, File imgDstFile) {
        if (width == 0 || height == 0 || yuvSource.isEmpty() || dstSource.isEmpty()) {//YuvFormat????
            return 0;
        }
        byte[] pictureArray1;
        byte[] pictureArray2;

        pictureArray1 = new byte[F];
        pictureArray2 = new byte[F];

        int framCount = 0;
        try {
            FileInputStream inp1 = new FileInputStream(imgSrcYuv);
            FileInputStream inp2= new FileInputStream(imgDstFile);

            if (isSsim) {
                File ssim = new File(filename + "_ssim.txt"); // 相對路徑慌盯,如果沒有則要建立一個新的output周霉。txt文件
                ssim.createNewFile(); // 創(chuàng)建新文件
                BufferedWriter out2 = new BufferedWriter(new FileWriter(ssim));

                for (framCount = 0; ; ) {
                    if (inp1.read(pictureArray1) == -1 || inp2.read(pictureArray2) == -1) {
                        if (isRD) {
                            out2.write("AVE_SSIM is " + ave_ssim / framCount + "\n");
                        }
                        out2.flush();
                        out2.close();
                        inp1.close();
                        inp2.close();


                        Message message = new Message();
                        message.what = 0;
                        message.arg1 = 1;
                        handler.sendMessage(message);

                        break;
                    } else {
                        double tem_ssim = getSsim(pictureArray1, pictureArray2, framCount);
                        ave_ssim += tem_ssim;
                        out2.write(tem_ssim + "\n");
                        framCount++;
                    }
                }
            } else {
                File psnr = new File(filename + "_psnr.txt"); // 相對路徑,如果沒有則要建立一個新的output润匙。txt文件
                psnr.createNewFile(); // 創(chuàng)建新文件
                BufferedWriter out1 = new BufferedWriter(new FileWriter(psnr));

                for (framCount = 0; ; ) {
                    if (inp1.read(pictureArray1) == -1 || inp2.read(pictureArray2) == -1) {
                        if (isRD) {
                            out1.write("AVE_PSNR is " + ave_psnr / framCount + "\n");
                        }
                        out1.flush();
                        out1.close();
                        inp1.close();
                        inp2.close();

                        if (isRD) {
                            isSsim = true;
                            start();
                        } else {
                            Message message = new Message();
                            message.what = 0;
                            message.arg1 = 2;
                            handler.sendMessage(message);
                        }

                        break;
                    } else {
                        float tem_psnr = getPsnr(pictureArray1, pictureArray2, framCount);
                        ave_psnr += tem_psnr;
                        out1.write(tem_psnr + "\n");
                        framCount++;
                    }

                }

            }

        } catch (Exception e) {
            Log.i("getImgArray", "Exception" + e);
            return 0;
            //JOptionPane.showMessageDialog(null,"Loading Image Data, Click to continue....","PSNR Calculation",JOptionPane.INYuvFormatION_MESSAGE);
        }
        return framCount;
    }

    public boolean getIssSim() {
        return isSsim;
    }

    public void setIssSim(boolean bo) {
        isSsim = bo;
    }

    public void setRD(boolean bo) {
        isRD = bo;
    }


}

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末诗眨,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子孕讳,更是在濱河造成了極大的恐慌匠楚,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件厂财,死亡現(xiàn)場離奇詭異芋簿,居然都是意外死亡,警方通過查閱死者的電腦和手機璃饱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門与斤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人荚恶,你說我怎么就攤上這事撩穿。” “怎么了谒撼?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵食寡,是天一觀的道長。 經常有香客問我廓潜,道長抵皱,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任辩蛋,我火速辦了婚禮呻畸,結果婚禮上,老公的妹妹穿的比我還像新娘悼院。我一直安慰自己伤为,他們只是感情好,可當我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布据途。 她就那樣靜靜地躺著钮呀,像睡著了一般。 火紅的嫁衣襯著肌膚如雪昨凡。 梳的紋絲不亂的頭發(fā)上爽醋,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天,我揣著相機與錄音便脊,去河邊找鬼蚂四。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的遂赠。 我是一名探鬼主播久妆,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼跷睦!你這毒婦竟也來了筷弦?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤抑诸,失蹤者是張志新(化名)和其女友劉穎烂琴,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蜕乡,經...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡奸绷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了层玲。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片号醉。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖辛块,靈堂內的尸體忽然破棺而出畔派,到底是詐尸還是另有隱情,我是刑警寧澤润绵,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布线椰,位于F島的核電站,受9級特大地震影響授药,放射性物質發(fā)生泄漏士嚎。R本人自食惡果不足惜呜魄,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一悔叽、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧爵嗅,春花似錦娇澎、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至伪很,卻和暖如春戚啥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背锉试。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工猫十, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓拖云,卻偏偏與公主長得像贷笛,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子宙项,可洞房花燭夜當晚...
    茶點故事閱讀 45,435評論 2 359