/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.mediaframeworktest.functional.mediarecorder;

import com.android.mediaframeworktest.MediaFrameworkTest;
import com.android.mediaframeworktest.MediaNames;

import java.io.*;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.hardware.Camera;
import android.media.MediaCodec;
import android.media.MediaMetadataRetriever;
import android.media.MediaPlayer;
import android.media.MediaRecorder;
import android.media.EncoderCapabilities;
import android.media.EncoderCapabilities.VideoEncoderCap;
import android.media.EncoderCapabilities.AudioEncoderCap;
import android.test.ActivityInstrumentationTestCase2;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import com.android.mediaframeworktest.MediaProfileReader;
import com.android.mediaframeworktest.MediaFrameworkTestRunner;

import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.Suppress;
import java.util.List;


/**
 * Junit / Instrumentation test case for the media recorder api
 */
public class MediaRecorderTest extends ActivityInstrumentationTestCase2<MediaFrameworkTest> {
    private String TAG = "MediaRecorderTest";
    private int mOutputDuration =0;
    private int mOutputVideoWidth = 0;
    private int mOutputVideoHeight= 0 ;

    private SurfaceHolder mSurfaceHolder = null;
    private MediaRecorder mRecorder;

    private int MIN_VIDEO_FPS = 5;
    private int HIGH_SPEED_FPS = 120;

    private static final int CAMERA_ID = 0;

    Context mContext;
    Camera mCamera;

    public MediaRecorderTest() {
        super(MediaFrameworkTest.class);

    }

    protected void setUp() throws Exception {
        getActivity();
        mRecorder = new MediaRecorder();
        super.setUp();
    }

    private void recordVideo(int frameRate, int width, int height,
            int videoFormat, int outFormat, String outFile, boolean videoOnly) {
        Log.v(TAG,"startPreviewAndPrepareRecording");
        try {
            if (!videoOnly) {
                Log.v(TAG, "setAudioSource");
                mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
            }
            mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
            mRecorder.setOutputFormat(outFormat);
            Log.v(TAG, "output format " + outFormat);
            mRecorder.setOutputFile(outFile);
            mRecorder.setVideoFrameRate(frameRate);
            mRecorder.setVideoSize(width, height);
            Log.v(TAG, "setEncoder");
            mRecorder.setVideoEncoder(videoFormat);
            if (!videoOnly) {
                mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
            }
            mSurfaceHolder = MediaFrameworkTest.mSurfaceView.getHolder();
            Log.v(TAG, "setPreview");
            mRecorder.setPreviewDisplay(mSurfaceHolder.getSurface());
            Log.v(TAG, "prepare");
            mRecorder.prepare();
            Log.v(TAG, "start");
            mRecorder.start();
            Thread.sleep(MediaNames.RECORDED_TIME);
            Log.v(TAG, "stop");
            mRecorder.stop();
            mRecorder.release();
        } catch (Exception e) {
            Log.v("record video failed ", e.toString());
            mRecorder.release();
        }
    }

    private boolean validateGetSurface(boolean useSurface) {
        Log.v(TAG,"validateGetSurface, useSurface=" + useSurface);
        MediaRecorder recorder = new MediaRecorder();
        Surface surface;
        boolean success = true;
        try {
            /* initialization */
            if (!useSurface) {
                mCamera = Camera.open(CAMERA_ID);
                Camera.Parameters parameters = mCamera.getParameters();
                parameters.setPreviewSize(352, 288);
                parameters.set("orientation", "portrait");
                mCamera.setParameters(parameters);
                mCamera.unlock();
                recorder.setCamera(mCamera);
                mSurfaceHolder = MediaFrameworkTest.mSurfaceView.getHolder();
                recorder.setPreviewDisplay(mSurfaceHolder.getSurface());
            }

            recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
            int videoSource = useSurface ?
                    MediaRecorder.VideoSource.SURFACE :
                    MediaRecorder.VideoSource.CAMERA;
            recorder.setVideoSource(videoSource);
            recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
            recorder.setOutputFile(MediaNames.RECORDED_SURFACE_3GP);
            recorder.setVideoFrameRate(30);
            recorder.setVideoSize(352, 288);
            recorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
            recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);

            /* Test: getSurface() before prepare()
             * should throw IllegalStateException
             */
            try {
                surface = recorder.getSurface();
                throw new Exception("getSurface failed to throw IllegalStateException");
            } catch (IllegalStateException e) {
                // OK
            }

            recorder.prepare();

            /* Test: getSurface() after prepare()
             * should succeed for surface source
             * should fail for camera source
             */
            try {
                surface = recorder.getSurface();
                if (!useSurface) {
                    throw new Exception("getSurface failed to throw IllegalStateException");
                }
            } catch (IllegalStateException e) {
                if (useSurface) {
                    throw new Exception("getSurface failed to throw IllegalStateException");
                }
            }

            recorder.start();

            /* Test: getSurface() after start()
             * should succeed for surface source
             * should fail for camera source
             */
            try {
                surface = recorder.getSurface();
                if (!useSurface) {
                    throw new Exception("getSurface failed to throw IllegalStateException");
                }
            } catch (IllegalStateException e) {
                if (useSurface) {
                    throw new Exception("getSurface failed to throw IllegalStateException");
                }
            }

            try {
                recorder.stop();
            } catch (Exception e) {
                // stop() could fail if the recording is empty, as we didn't render anything.
                // ignore any failure in stop, we just want it stopped.
            }

            /* Test: getSurface() after stop()
             * should throw IllegalStateException
             */
            try {
                surface = recorder.getSurface();
                throw new Exception("getSurface failed to throw IllegalStateException");
            } catch (IllegalStateException e) {
                // OK
            }
        } catch (Exception e) {
            // fail
            success = false;
        }

        try {
            if (mCamera != null) {
                mCamera.lock();
                mCamera.release();
                mCamera = null;
            }
            recorder.release();
        } catch (Exception e) {
            success = false;
        }

        return success;
    }

    private boolean recordVideoFromSurface(
            int frameRate, int captureRate, int width, int height,
            int videoFormat, int outFormat, String outFile, boolean videoOnly,
            Surface persistentSurface) {
        Log.v(TAG,"recordVideoFromSurface");
        MediaRecorder recorder = new MediaRecorder();
        int sleepTime = 33; // normal capture at 33ms / frame
        Surface surface = null;
        try {
            if (!videoOnly) {
                recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
            }
            recorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
            recorder.setOutputFormat(outFormat);
            recorder.setOutputFile(outFile);
            recorder.setVideoFrameRate(frameRate);
            if (captureRate > 0) {
                recorder.setCaptureRate(captureRate);
                sleepTime = 1000 / captureRate;
            }
            recorder.setVideoSize(width, height);
            recorder.setVideoEncoder(videoFormat);
            if (!videoOnly) {
                recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
            }
            if (persistentSurface != null) {
                Log.v(TAG, "using persistent surface");
                surface = persistentSurface;
                recorder.setInputSurface(surface);
            }
            recorder.prepare();
            if (persistentSurface == null) {
                surface = recorder.getSurface();
            }

            Paint paint = new Paint();
            paint.setTextSize(16);
            paint.setColor(Color.RED);
            int i;

            /* Test: draw 10 frames at 30fps before start
             * these should be dropped and not causing malformed stream.
             */
            for(i = 0; i < 10; i++) {
                Canvas canvas = surface.lockCanvas(null);
                int background = (i * 255 / 99);
                canvas.drawARGB(255, background, background, background);
                String text = "Frame #" + i;
                canvas.drawText(text, 100, 100, paint);
                surface.unlockCanvasAndPost(canvas);
                Thread.sleep(sleepTime);
            }

            Log.v(TAG, "start");
            recorder.start();

            /* Test: draw another 90 frames at 30fps after start */
            for(i = 10; i < 100; i++) {
                Canvas canvas = surface.lockCanvas(null);
                int background = (i * 255 / 99);
                canvas.drawARGB(255, background, background, background);
                String text = "Frame #" + i;
                canvas.drawText(text, 100, 100, paint);
                surface.unlockCanvasAndPost(canvas);
                Thread.sleep(sleepTime);
            }

            Log.v(TAG, "stop");
            recorder.stop();
        } catch (Exception e) {
            Log.v(TAG, "record video failed: " + e.toString());
            return false;
        } finally {
            recorder.release();
            // release surface if not using persistent surface
            if (persistentSurface == null && surface != null) {
                surface.release();
            }
        }
        return true;
    }

    private boolean recordVideoWithPara(VideoEncoderCap videoCap, AudioEncoderCap audioCap, boolean highQuality){
        boolean recordSuccess = false;
        int videoEncoder = videoCap.mCodec;
        int audioEncoder = audioCap.mCodec;
        int videoWidth = highQuality? videoCap.mMaxFrameWidth: videoCap.mMinFrameWidth;
        int videoHeight = highQuality? videoCap.mMaxFrameHeight: videoCap.mMinFrameHeight;
        int videoFps = highQuality? videoCap.mMaxFrameRate: videoCap.mMinFrameRate;
        int videoBitrate = highQuality? videoCap.mMaxBitRate: videoCap.mMinBitRate;
        int audioBitrate = highQuality? audioCap.mMaxBitRate: audioCap.mMinBitRate;
        int audioChannels = highQuality? audioCap.mMaxChannels: audioCap.mMinChannels ;
        int audioSamplingRate = highQuality? audioCap.mMaxSampleRate: audioCap.mMinSampleRate;

        //Overide the fps if the min_camera_fps is set
        if (MediaFrameworkTestRunner.mMinCameraFps != 0 &&
            MediaFrameworkTestRunner.mMinCameraFps > videoFps){
            videoFps = MediaFrameworkTestRunner.mMinCameraFps;
        }

        if (videoFps < MIN_VIDEO_FPS) {
            videoFps = MIN_VIDEO_FPS;
        }

        mSurfaceHolder = MediaFrameworkTest.mSurfaceView.getHolder();
        String filename = ("/sdcard/" + videoEncoder + "_" + audioEncoder + "_" + highQuality + ".3gp");
        try {
            Log.v(TAG, "video encoder : " + videoEncoder);
            Log.v(TAG, "audio encoder : " + audioEncoder);
            Log.v(TAG, "quality : " + (highQuality?"high": "low"));
            Log.v(TAG, "encoder : " + MediaProfileReader.getVideoCodecName(videoEncoder));
            Log.v(TAG, "audio : " + MediaProfileReader.getAudioCodecName(audioEncoder));
            Log.v(TAG, "videoWidth : " + videoWidth);
            Log.v(TAG, "videoHeight : " + videoHeight);
            Log.v(TAG, "videoFPS : " + videoFps);
            Log.v(TAG, "videobitrate : " + videoBitrate);
            Log.v(TAG, "audioBitrate : " + audioBitrate);
            Log.v(TAG, "audioChannel : " + audioChannels);
            Log.v(TAG, "AudioSampleRate : " + audioSamplingRate);

            MediaRecorder mMediaRecorder = new MediaRecorder();
            mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
            mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
            mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
            mMediaRecorder.setOutputFile(filename);
            mMediaRecorder.setVideoFrameRate(videoFps);
            mMediaRecorder.setVideoSize(videoWidth, videoHeight);
            mMediaRecorder.setVideoEncodingBitRate(videoBitrate);
            mMediaRecorder.setAudioEncodingBitRate(audioBitrate);
            mMediaRecorder.setAudioChannels(audioChannels);
            mMediaRecorder.setAudioSamplingRate(audioSamplingRate);
            mMediaRecorder.setVideoEncoder(videoEncoder);
            mMediaRecorder.setAudioEncoder(audioEncoder);
            mMediaRecorder.setPreviewDisplay(mSurfaceHolder.getSurface());
            mMediaRecorder.prepare();
            mMediaRecorder.start();
            Thread.sleep(MediaNames.RECORDED_TIME);
            mMediaRecorder.stop();
            mMediaRecorder.release();
            recordSuccess = validateVideo(filename, videoWidth, videoHeight);
        } catch (Exception e) {
            Log.v(TAG, e.toString());
            return false;
        }
        return recordSuccess;
    }

    private boolean invalidRecordSetting(int frameRate, int width, int height,
            int videoFormat, int outFormat, String outFile, boolean videoOnly) {
        try {
            if (!videoOnly) {
                Log.v(TAG, "setAudioSource");
                mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
            }
            mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
            mRecorder.setOutputFormat(outFormat);
            Log.v(TAG, "output format " + outFormat);
            mRecorder.setOutputFile(outFile);
            mRecorder.setVideoFrameRate(frameRate);
            mRecorder.setVideoSize(width, height);
            Log.v(TAG, "setEncoder");
            mRecorder.setVideoEncoder(videoFormat);
            if (!videoOnly) {
                mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
            }
            mSurfaceHolder = MediaFrameworkTest.mSurfaceView.getHolder();
            Log.v(TAG, "setPreview");
            mRecorder.setPreviewDisplay(mSurfaceHolder.getSurface());
            Log.v(TAG, "prepare");
            mRecorder.prepare();
            Log.v(TAG, "start");
            mRecorder.start();
            Thread.sleep(MediaNames.RECORDED_TIME);
            Log.v(TAG, "stop");
            mRecorder.stop();
            mRecorder.release();
        } catch (Exception e) {
            Log.v("record video failed ", e.toString());
            mRecorder.release();
            Log.v(TAG, "reset and release");
            return true;
        }
        return false;
    }

    private void getOutputVideoProperty(String outputFilePath) {
        MediaPlayer mediaPlayer = new MediaPlayer();
        try {
            mediaPlayer.setDataSource(outputFilePath);
            Log.v(TAG, "file Path = " + outputFilePath);
            mediaPlayer.setDisplay(MediaFrameworkTest.mSurfaceView.getHolder());
            Log.v(TAG, "before player prepare");
            mediaPlayer.prepare();
            Log.v(TAG, "before getduration");
            mOutputDuration = mediaPlayer.getDuration();
            Log.v(TAG, "get video dimension");
            Thread.sleep(1000);
            mOutputVideoHeight = mediaPlayer.getVideoHeight();
            mOutputVideoWidth = mediaPlayer.getVideoWidth();
            mediaPlayer.release();
        } catch (Exception e) {
            Log.v(TAG, e.toString());
            mediaPlayer.release();
        }
    }

    private boolean validateVideo(String filePath, int width, int height) {
        boolean validVideo = false;
        getOutputVideoProperty(filePath);
        if (mOutputVideoWidth == width && mOutputVideoHeight == height &&
                mOutputDuration > MediaNames.VALID_VIDEO_DURATION ) {
            validVideo = true;
        }
        Log.v(TAG, "width = " + mOutputVideoWidth + " height = " + mOutputVideoHeight + " Duration = " + mOutputDuration);
        return validVideo;
    }

    private boolean validateMetadata(String filePath, int captureRate) {
        MediaMetadataRetriever retriever = new MediaMetadataRetriever();

        retriever.setDataSource(filePath);

        // verify capture rate meta key is present and correct
        String captureFps = retriever.extractMetadata(
                MediaMetadataRetriever.METADATA_KEY_CAPTURE_FRAMERATE);

        if (captureFps == null) {
            Log.d(TAG, "METADATA_KEY_CAPTURE_FRAMERATE is missing");
            return false;
        }

        if (Math.abs(Float.parseFloat(captureFps) - captureRate) > 0.001) {
            Log.d(TAG, "METADATA_KEY_CAPTURE_FRAMERATE is incorrect: "
                    + captureFps + "vs. " + captureRate);
            return false;
        }

        // verify other meta keys here if necessary
        return true;
    }
    @LargeTest
    /*
     * This test case set the camera in portrait mode.
     * Verification: validate the video dimension and the duration.
     */
    public void testPortraitH263() throws Exception {
        boolean videoRecordedResult = false;
        try {
            mCamera = Camera.open(CAMERA_ID);
            Camera.Parameters parameters = mCamera.getParameters();
            parameters.setPreviewSize(352, 288);
            parameters.set("orientation", "portrait");
            mCamera.setParameters(parameters);
            mCamera.unlock();
            mRecorder.setCamera(mCamera);
            Thread.sleep(1000);
            int codec = MediaRecorder.VideoEncoder.H263;
            int frameRate = MediaProfileReader.getMaxFrameRateForCodec(codec);
            recordVideo(frameRate, 352, 288, codec,
                    MediaRecorder.OutputFormat.THREE_GPP,
                    MediaNames.RECORDED_PORTRAIT_H263, true);
            mCamera.lock();
            mCamera.release();
            videoRecordedResult =
                validateVideo(MediaNames.RECORDED_PORTRAIT_H263, 352, 288);
        } catch (Exception e) {
            Log.v(TAG, e.toString());
        }
        assertTrue("PortraitH263", videoRecordedResult);
    }

    @LargeTest
    public void testInvalidVideoPath() throws Exception {
        boolean isTestInvalidVideoPathSuccessful = false;
        isTestInvalidVideoPathSuccessful = invalidRecordSetting(15, 176, 144, MediaRecorder.VideoEncoder.H263,
               MediaRecorder.OutputFormat.THREE_GPP, MediaNames.INVALD_VIDEO_PATH, false);
        assertTrue("Invalid outputFile Path", isTestInvalidVideoPathSuccessful);
    }

    @LargeTest
    //test cases for the new codec
    public void testDeviceSpecificCodec() throws Exception {
        int noOfFailure = 0;
        boolean recordSuccess = false;
        String deviceType = MediaProfileReader.getDeviceType();
        Log.v(TAG, "deviceType = " + deviceType);
        List<VideoEncoderCap> videoEncoders = MediaProfileReader.getVideoEncoders();
        List<AudioEncoderCap> audioEncoders = MediaProfileReader.getAudioEncoders();
        for (int k = 0; k < 2; k++) {
            for (VideoEncoderCap videoEncoder: videoEncoders) {
                for (AudioEncoderCap audioEncoder: audioEncoders) {
                    if (k == 0) {
                        recordSuccess = recordVideoWithPara(videoEncoder, audioEncoder, true);
                    } else {
                        recordSuccess = recordVideoWithPara(videoEncoder, audioEncoder, false);
                    }
                    if (!recordSuccess) {
                        Log.v(TAG, "testDeviceSpecificCodec failed");
                        Log.v(TAG, "Encoder = " + videoEncoder.mCodec + "Audio Encoder = " + audioEncoder.mCodec);
                        noOfFailure++;
                    }
                }
            }
        }
        if (noOfFailure != 0) {
            assertTrue("testDeviceSpecificCodec", false);
        }
    }

    // Test MediaRecorder.getSurface() api with surface or camera source
    public void testGetSurfaceApi() {
        boolean success = false;
        int noOfFailure = 0;
        try {
            for (int k = 0; k < 2; k++) {
                success = validateGetSurface(
                        k == 0 ? true : false /* useSurface */);
                if (!success) {
                    noOfFailure++;
                }
            }
        } catch (Exception e) {
            Log.v(TAG, e.toString());
        }
        assertTrue("testGetSurfaceApi", noOfFailure == 0);
    }

    // Test recording from surface source with/without audio
    public void testSurfaceRecording() {
        boolean success = false;
        int noOfFailure = 0;
        try {
            int codec = MediaRecorder.VideoEncoder.H264;
            int frameRate = MediaProfileReader.getMaxFrameRateForCodec(codec);
            for (int k = 0; k < 2; k++) {
                String filename = "/sdcard/surface_" +
                            (k==0?"video_only":"with_audio") + ".3gp";

                success = recordVideoFromSurface(frameRate, 0, 352, 288, codec,
                        MediaRecorder.OutputFormat.THREE_GPP, filename,
                        k == 0 ? true : false /* videoOnly */, null);
                if (success) {
                    success = validateVideo(filename, 352, 288);
                }
                if (!success) {
                    noOfFailure++;
                }
            }
        } catch (Exception e) {
            Log.v(TAG, e.toString());
        }
        assertTrue("testSurfaceRecording", noOfFailure == 0);
    }

    public void testPersistentSurfaceRecording() {
        boolean success = false;
        int noOfFailure = 0;
        Surface surface = null;
        try {
            int codec = MediaRecorder.VideoEncoder.H264;
            int frameRate = MediaProfileReader.getMaxFrameRateForCodec(codec);
            surface = MediaCodec.createPersistentInputSurface();
            for (int k = 0; k < 2; k++) {
                String filename = "/sdcard/surface_persistent" + k + ".3gp";

                Log.v(TAG, "test persistent surface - round " + k);
                success = recordVideoFromSurface(frameRate, 0, 352, 288, codec,
                        MediaRecorder.OutputFormat.THREE_GPP, filename,
                        true /* videoOnly */, surface);
                if (success) {
                    success = validateVideo(filename, 352, 288);
                }
                if (!success) {
                    noOfFailure++;
                }
            }
        } catch (Exception e) {
            Log.v(TAG, e.toString());
        } finally {
            if (surface != null) {
                Log.v(TAG, "releasing persistent surface");
                surface.release();
                surface = null;
            }
        }
        assertTrue("testPersistentSurfaceRecording", noOfFailure == 0);
    }

    // Test recording from surface source with/without audio
    public void testSurfaceRecordingTimeLapse() {
        boolean success = false;
        int noOfFailure = 0;
        try {
            int codec = MediaRecorder.VideoEncoder.H264;
            int frameRate = MediaProfileReader.getMaxFrameRateForCodec(codec);
            for (int k = 0; k < 2; k++) {
                // k==0: time lapse test, set capture rate to MIN_VIDEO_FPS
                // k==1: slow motion test, set capture rate to HIGH_SPEED_FPS
                String filename = "/sdcard/surface_" +
                            (k==0 ? "time_lapse" : "slow_motion") + ".3gp";

                // always set videoOnly=false, MediaRecorder should disable
                // audio automatically with time lapse/slow motion
                int captureRate = k==0 ? MIN_VIDEO_FPS : HIGH_SPEED_FPS;
                success = recordVideoFromSurface(
                        frameRate, captureRate, 352, 288, codec,
                        MediaRecorder.OutputFormat.THREE_GPP,
                        filename, false /* videoOnly */, null);
                if (success) {
                    success = validateVideo(filename, 352, 288);
                    if (success) {
                        success = validateMetadata(filename, captureRate);
                    }
                }
                if (!success) {
                    noOfFailure++;
                }
            }
        } catch (Exception e) {
            Log.v(TAG, e.toString());
            noOfFailure++;
        }
        assertTrue("testSurfaceRecordingTimeLapse", noOfFailure == 0);
    }

}
