项目:基于ffmpeg视频质量安卓测试平台.(计算PSNR与SSIM)

  • YUV420

  • H264

  • 硬解码,软解码

  • 硬编码,软编码

  • PSNR

  • SSIM

以上关系大致上可以这么看:
mp4==>MediaMuxer解封==>H264==>解码=>YUV==>有损编码==>H264==>MediaMuxer封装==>有损mp4

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

  • YUV420

YUV是一种亮度信号Y和色度信号U、V是分离的色彩空间,它主要用于优化彩色视频信号的传输,使其向后相容老式黑白电视。与RGB视频信号传输相比,它最大的优点在于只需占用极少的频宽(RGB要求三个独立的视频信号同时传输)。其中“Y”表示明亮度(Luminance或Luma),也就是灰阶值;而“U”和“V”表示的则是色度(Chrominance或Chroma),作用是描述影像色彩及饱和度,用于指定像素的颜色。

YUV主流有三种格式格式。

YUV444,YUV422,YUV420.为什么分了三种呢? 因为人眼对亮度感受明显,对色度感受不明显。所以,这三种格式完全保留了亮度信息,而对色度信息有一定的取舍。
如何取舍?

项目:基于ffmpeg视频质量安卓测试平台.(计算PSNR与SSIM)_第1张图片
image.png

黑点表示采样该像素点的Y分量,以空心圆圈表示采用该像素点的UV分量。

  • YUV444:YUV444即表示Y、U、V所占比为4:4:4,这种采样方式的色度值UV不会较少采样,Y、U、V分量各占一个字节,连同Alpha通道一个字节,YUV444每个像素占4字节,也就是说这个格式实质就是24bpp的RGB格式。采样示例:
    如果原始数据四个像素是:A0Y0 U0 V0 ,A1 Y1 U1 V1,A2 Y2 U2 V2,A3 Y3 U3 V3(每个像素(YUV)占32Bits)
    经过4:4:4采样后,数据仍为: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个字节。采样示例:
    如果原始数据四个像素是:Y0U0 V0 ,Y1 U1 V1,Y2 U2 V2,Y3 U3 V3(每个像素占16bits)
    经过4:2:2采样后,数据变成: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数据,只能用相临的数据补齐,但人眼对UV不敏感,因此总体感觉损失不大。
YUV420 的两种帧格式

CameraPrevieCallback实时采集的视频帧格式为YV12或者NV21,它们都属于YUV420采样格式。YUV格式的存放方式永远是先排列完Y分量,再排序U或V分量,不同的采样只是Y或V分量的排列格式和顺序不同。。NV21、NV12的区别在于Y值排序完全相同,U和V交错排序,不同在于UV顺序:

  • NV12存储方式:Y0Y1Y2Y3 U0V0

  • NV21存储方式:Y0Y1Y2Y3 V0U0

项目:基于ffmpeg视频质量安卓测试平台.(计算PSNR与SSIM)_第2张图片
image.png

所以可以分析出对于1920×1080的资源每帧的大小为:1920×1080×2/3。
转换方式在网上很多,基本原理就是旋转==》数值的交换。
==================================================================================

  • H264(视频编码方式)

YUV文件非常非常大,测试中10m的mp4文件,解码之后YUV420文件达500m。Camera采集的YUV图像通常为YUV420,而在现实网络中,这么高的上行宽带一般是很难达到的,因此,我们就必须在传输之前对采集的视频数据(YUV文件)进行视频压缩编码。所以我们需要H264了。所谓视频编码方式就是指能够对数字视频进行压缩或者解压缩(视频解码)的程序或者设备。通常这种压缩属于有损数据压缩。

H.264简介:
H.264是MPEG-4的第十部分,是由VCEG和MPEG联合提出的高度压缩数字视频编码器标准,目前在多媒体开发应用中非常广泛。H.264具有低码率、高压缩、高质量的图像、容错能力强、网络适应性强等特点,它最大的优势拥有很高的数据压缩比率,在同等图像质量的条件下,H.264的压缩比是MPEG-2的两倍以上。
总之,就是压缩压缩,如何压缩呢?

三种帧(I,B,P)的协作。

在H.264协议里定义了三种帧,完整编码的帧叫I帧(关键帧),参考之前的I帧生成的只包含差异部分编码的帧叫P帧,还有一种参考前后的帧编码的帧叫B帧。H.264编码采用的核心算法是帧内压缩帧间压缩

  • 帧内压缩是生成I帧的算法,它的原理是当压缩一帧图像时,仅考虑本帧的数据而不用考虑相邻帧之间的冗余信息,由于帧内压缩是编码一个完整的图像,所以可以独立的解码显示;
  • 帧间压缩是生成P、B帧的算法,它的原理是通过对比相邻两帧之间的数据进行压缩,进一步提高压缩量,减少压缩比。

通俗的来说,H.264编码的就是对于一段变化不大图像画面,我们可以先编码出一个完整的图像帧A,随后的B帧就不编码全部图像,只写入与A帧的差别,这样B帧的大小就只有完整帧的1/10或更小。B帧之后的C帧如果变化不大,我们可以继续以参考B的方式编码C帧,这样循环下去。

三种帧如何协作呢?

  • IDR(Instantaneous Decoding Refresh):即时解码刷新。一个序列的第一个图像叫做IDR 图像(立即刷新图像),IDR 图像都是I 帧图像(关键帧)。H.264引入 IDR 图像是为了解码的重同步,当解码器解码到IDR 图像时,立即将参考帧队列清空,将已解码的数据全部输出或抛弃,重新查找参数集,开始一个新的序列。这样,如果前一个序列出现重大错误,在这里可以获得重新同步的机会。IDR图像之后的图像永远不会使用IDR之前的图像的数据来解码。
  • P帧:前向预测编码帧。P帧表示的是这一帧跟之前的一个关键帧(或P帧)的差别,它P帧是I帧后面相隔1~2帧的编码帧,其没有完整画面数据,只有与前一帧的、画面差别的数据。
  • B帧:双向预测内插编码帧。B帧记录的是本帧与前后帧的差别,它是由前面的I或P帧和后面的P帧来进行预测的。
    三种帧如何协作呢?
    H.264编码采用的核心算法是帧内压缩帧间压缩
  • 帧内压缩是生成I帧的算法,它的原理是当压缩一帧图像时,仅考虑本帧的数据而不用考虑相邻帧之间的冗余信息,由于帧内压缩是编码一个完整的图像,所以可以独立的解码显示;
  • 帧间压缩是生成P、B帧的算法,它的原理是通过对比相邻两帧之间的数据进行压缩,进一步提高压缩量,减少压缩比。

h264的压缩方法:

1.分组:把几帧图像分为一组(GOP,也就是一个序列),为防止运动变化,帧数不宜取多。
2.定义帧:将每组内各帧图像定义为三种类型,即I帧、B帧和P帧;
3.预测帧:以I帧做为基础帧,以I帧预测P帧,再由I帧和P帧预测B帧;
4.数据传输:最后将I帧数据与预测的差值信息进行存储和传输。

通俗的来说,H.264编码的就是对于一段变化不大图像画面,我们可以先编码出一个完整的图像帧A,随后的B帧就不编码全部图像,只写入与A帧的差别,这样B帧的大小就只有完整帧的1/10或更小。B帧之后的C帧如果变化不大,我们可以继续以参考B的方式编码C帧,这样循环下去。

在ffmpeg里,I帧间隔可以自己设置,用来测试不同I帧间隔对视频质量有何影响。

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

  • MP4(视频文件格式)

容器mp4,rmvb,mkv,avi从形式上来说首先都是视频文件的扩展名,其次它们也是视频文件的封装格式(即容器)mp4是MPEG-4标准的第14部分所制定的容器标准。所谓容器,就是把编码器生成的多媒体内容(视频,音频,字幕,章节信息等)混合封装在一起的标准。
容器使得不同多媒体内容同步播放变得很简单,而容器的另一个作用就是为多媒体内容提供索引,也就是说如果没有容器存在的话一部影片你只能从一开始看到最后,不能拖动进度条(当然这种情况下有的播放器会话比较长的时间临时创建索引),而且如果你不自己去手动另外载入音频就没有声音。
==================================================================================

  • 硬解码,软解码

硬件解码是将原来全部交由CPU来处理的视频数据的一部分交由GPU来做,而GPU的并行运算能力要远远高于CPU,这样可以大大的降低对CPU的负载,CPU的占用率较低了之后就可以同时运行一些其他的程序了。在Android中使用硬件解码直接使用MediaCodec就可以了,虽然MediaPlayer也是硬件解码,但是被封装得太死了,支持的协议很少。而MediaCodec就很好拓展,我们可以根据流媒体的协议和设备硬件本身来自定义硬件解码,代表播放器就是Google的ExoPlayer。
软解码:即通过软件让CPU来对视频进行解码处理,就是通过CPU来运行视频编解码代码,我们最最常见的视频软解码开源看就是FFmpeg.

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

  • 硬编码,软编码

消费者生产者模型:

项目:基于ffmpeg视频质量安卓测试平台.(计算PSNR与SSIM)_第3张图片
image.png

编码流程:
configure:首先要初始化硬件编码器,配置要编码的格式、视频文件的长宽、码率、帧率、关键帧间隔等等。
开启编码器,当前编码器便是可用状态,随时准备接收数据
running:在此过程中,需要维护两个buffer队列,InputBuffer 和OutputBuffer,用户需要不断出队InputBuffer (即dequeueInputBuffer),往里边放入需要编码的图像数据之后再入队等待处理,然后硬件编码器开始异步处理,一旦处理结束,他会将数据放在OutputBuffer中,并且通知用户当前有输出数据可用了,那么用户就可以出队一个OutputBuffer,将其中的数据拿走,然后释放掉这个buffer。
end:结束条件在于end-of-stream这个flag标志位的设定。在编码结束后,编码器调用stop函数停止编码,之后调用release函数将编码器完全释放掉,整体流程结束。

编码处理
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();//拿到输入缓冲区,用于传送数据进行编码
ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers();//拿到输出缓冲区,用于取到编码后的数据
int inputBufferIndex = mediaCodec.dequeueInputBuffer(-1);
 if (inputBufferIndex >= 0) {//当输入缓冲区有效时,就是>=0
ByteBuffer inputBuffer = inputBuffers[inputBufferIndex]; inputBuffer.clear(); 
inputBuffer.put(input);//往输入缓冲区写入数据, // //五个参数,第一个是输入缓冲区的索引,第二个数据是输入缓冲区起始索引,第三个是放入的数据大小,第四个是时间戳,保证递增就是 
mediaCodec.queueInputBuffer(inputBufferIndex, 0, input.length, System.nanoTime() / 1000, 0); } 
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); 
int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC);//拿到输出缓冲区的索引 
while (outputBufferIndex >= 0) { ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
 byte[] outData = new byte[bufferInfo.size]; outputBuffer.get(outData); //outData就是输出的h264数据 
outputStream.write(outData, 0, outData.length);//将输出的h264数据保存为文件,用vlc就可以播放 
mediaCodec.releaseOutputBuffer(outputBufferIndex, false); 
outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC); } } 
catch (Throwable t) 
{ t.printStackTrace(); } }

创建文件夹
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],值越大越好;
结构相似性理论认为,图像是高度结构化的,即像素间有很强的相关性,特别是空域中最接近的像素,这种相关性蕴含着视觉场景中物体结构的重要信息;作为结构相似性理论的实现,结构相似度指数从图像组成的角度将结构信息定义为独立于亮度、对比度的,反映场景中物体结构的属性,并将失真建模为亮度、对比度和结构三个不同因素的组合。用均值作为亮度的估计,标准差作为对比度的估计,协方差作为结构相似程度的估计,计算数学模型如下:亮度表示L;对比度表示C;结构相似性表示S;通常取C1=(K1L)^2,C2=(K2L)^2, C3=C2/2, K1=0.01, K2=0.03, L=255.

项目:基于ffmpeg视频质量安卓测试平台.(计算PSNR与SSIM)_第4张图片
image.png

最后的SSIM指数为:


image.png

当我们设定C3=C2/2时,我们可以将公式改写成更加简单的形式:


项目:基于ffmpeg视频质量安卓测试平台.(计算PSNR与SSIM)_第5张图片
image.png

SSIM相当于将数据进行归一化后,分别计算图像块照明度(图像块的均值),对比度(图像块的方差)和归一化后的像素向量这三者相似度,并将三者相乘。
  • 峰值信噪比PSNR

PSNR是最普遍和使用最为广泛的一种图像客观评价指标,然而它是基于对应像素点间的误差,即基于误差敏感的图像质量评价。由于并未考虑到人眼的视觉特性(人眼对空间频率较低的对比差异敏感度较高,人眼对亮度对比差异的敏感度较色度高,人眼对一个区域的感知结果会受到其周围邻近区域的影响等),因而经常出现评价结果与人的主观感觉不一致的情况。

PSNR定义与计算

PSNR本质上与MSE相同,是MSE的对数表示。

对于大多数常见的8bit/彩色视频图像,
PSNR完全由MSE确定。PSNR较MSE更常用,因为人们想把图像质量与某个范围的PSNR相联系。

根据实际经验,对于亮度像素分量:
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: 一帧受损数据
     * s2: 一帧原始数据
     * 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 sum0 = new ArrayList<>(width);
        /*
         * sum0是一个数组指针,其中存储了一个4元素数组的地址
         * 换句话说,sum0中每一个元素对应一个4x4块的信息(该信息包含4个元素)。
         *
         * 4个元素中:
         * [0]原始像素之和
         * [1]受损像素之和
         * [2]原始像素平方之和+受损像素平方之和
         * [3]原始像素*受损像素的值的和
         *
         */
        ArrayList sum1 = new ArrayList<>(width);
        width >>= 2;
        //除以4
        height >>= 2;
        z = 0;

        for (y = 1; y < height; y++) {
            //下面这个循环,只有在第一次执行的时候执行2次,处理第1行和第2行的块
            //后面的都只会执行一次
            for (; z <= y; z++) {
                //执行完XCHG()之后,sum1[]存储上1行块的值(在上面),而sum0[]等待ssim_4x4x2_core()计算当前行的值(在下面)
                ArrayList 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的一维数组)(这里并没有代入公式计算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 sum0, ArrayList 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(); // 创建新文件
                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(); // 创建新文件
                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;
    }


}

你可能感兴趣的:(项目:基于ffmpeg视频质量安卓测试平台.(计算PSNR与SSIM))