/*
 * Copyright (C) 2014 The Android Open Source Project
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in
 * the documentation and/or other materials provided with the
 * distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <stdio.h>
#include <stdint.h>
#include <assert.h>
#include <stdlib.h>

#include "mp4enc_api.h"

// Constants.
enum {
    kMaxWidth         = 720,
    kMaxHeight        = 480,
    kMaxFrameRate     = 30,
    kMaxBitrate       = 2048, // in kbps.
    kOutputBufferSize = 250 * 1024,
    kIDRFrameRefreshIntervalInSec = 1, // in seconds.
};

int main(int argc, char *argv[]) {

    if (argc < 8) {
        fprintf(stderr, "Usage %s <input yuv> <output file> <mode> <width> "
                        "<height> <frame rate> <bitrate in kbps>\n", argv[0]);
        fprintf(stderr, "mode : h263 or mpeg4\n");
        fprintf(stderr, "Max width %d\n", kMaxWidth);
        fprintf(stderr, "Max height %d\n", kMaxHeight);
        fprintf(stderr, "Max framerate %d\n", kMaxFrameRate);
        fprintf(stderr, "Max bitrate %d kbps\n", kMaxBitrate);
        return EXIT_FAILURE;
    }

    // Read mode.
    bool isH263mode;
    if (strcmp(argv[3], "mpeg4") == 0) {
        isH263mode = false;
    } else if (strcmp(argv[3], "h263") == 0) {
        isH263mode = true;
    } else {
        fprintf(stderr, "Unsupported mode %s\n", argv[3]);
        return EXIT_FAILURE;
    }

    // Read height and width.
    int32_t width;
    int32_t height;
    width = atoi(argv[4]);
    height = atoi(argv[5]);
    if (width > kMaxWidth || height > kMaxHeight || width <= 0 || height <= 0) {
        fprintf(stderr, "Unsupported dimensions %dx%d\n", width, height);
        return EXIT_FAILURE;
    }

    if (width % 16 != 0 || height % 16 != 0) {
        fprintf(stderr, "Video frame size %dx%d must be a multiple of 16\n",
            width, height);
        return EXIT_FAILURE;
    }

    // Read frame rate.
    int32_t frameRate;
    frameRate = atoi(argv[6]);
    if (frameRate > kMaxFrameRate || frameRate <= 0) {
        fprintf(stderr, "Unsupported frame rate %d\n", frameRate);
        return EXIT_FAILURE;
    }

    // Read bitrate.
    int32_t bitrate;
    bitrate = atoi(argv[7]);
    if (bitrate > kMaxBitrate || bitrate <= 0) {
        fprintf(stderr, "Unsupported bitrate %d\n", bitrate);
        return EXIT_FAILURE;
    }

    // Allocate input buffer.
    uint8_t *inputBuf = (uint8_t *)malloc((width * height * 3) / 2);
    assert(inputBuf != NULL);

    // Allocate output buffer.
    uint8_t *outputBuf = (uint8_t *)malloc(kOutputBufferSize);
    assert(outputBuf != NULL);

    // Open the input file.
    FILE *fpInput = fopen(argv[1], "rb");
    if (fpInput == NULL) {
        fprintf(stderr, "Could not open %s\n", argv[1]);
        free(inputBuf);
        free(outputBuf);
        return EXIT_FAILURE;
    }

    // Open the output file.
    FILE *fpOutput = fopen(argv[2], "wb");
    if (fpOutput == NULL) {
        fprintf(stderr, "Could not open %s\n", argv[2]);
        free(inputBuf);
        free(outputBuf);
        fclose(fpInput);
        return EXIT_FAILURE;
    }

    // Initialize the encoder parameters.
    tagvideoEncOptions encParams;
    memset(&encParams, 0, sizeof(tagvideoEncOptions));
    if (!PVGetDefaultEncOption(&encParams, 0)) {
        fprintf(stderr, "Failed to get default encoding parameters\n");
        free(inputBuf);
        free(outputBuf);
        fclose(fpInput);
        fclose(fpOutput);
        return EXIT_FAILURE;
    }

    if (isH263mode == false) {
        encParams.encMode = COMBINE_MODE_WITH_ERR_RES;
    } else {
        encParams.encMode = H263_MODE;
    }
    encParams.encWidth[0] = width;
    encParams.encHeight[0] = height;
    encParams.encFrameRate[0] = frameRate;
    encParams.rcType = VBR_1;
    encParams.vbvDelay = 5.0f;
    encParams.profile_level = CORE_PROFILE_LEVEL2;
    encParams.packetSize = 32;
    encParams.rvlcEnable = PV_OFF;
    encParams.numLayers = 1;
    encParams.timeIncRes = 1000;
    encParams.tickPerSrc = encParams.timeIncRes / frameRate;

    encParams.bitRate[0] = bitrate * 1024;
    encParams.iQuant[0] = 15;
    encParams.pQuant[0] = 12;
    encParams.quantType[0] = 0;
    encParams.noFrameSkipped = PV_OFF;

    int32_t  IDRFrameRefreshIntervalInSec = kIDRFrameRefreshIntervalInSec;
    if (IDRFrameRefreshIntervalInSec == 0) {
        encParams.intraPeriod = 1;  // All I frames.
    } else {
        encParams.intraPeriod = (IDRFrameRefreshIntervalInSec * frameRate);
    }

    encParams.numIntraMB = 0;
    encParams.sceneDetect = PV_ON;
    encParams.searchRange = 16;
    encParams.mv8x8Enable = PV_OFF;
    encParams.gobHeaderInterval = 0;
    encParams.useACPred = PV_ON;
    encParams.intraDCVlcTh = 0;

    // Initialize the handle.
    tagvideoEncControls handle;
    memset(&handle, 0, sizeof(tagvideoEncControls));

    // Initialize the encoder.
    if (!PVInitVideoEncoder(&handle, &encParams)) {
        fprintf(stderr, "Failed to initialize the encoder\n");
        return EXIT_FAILURE;
    }

    // Generate the header.
    int32_t headerLength = kOutputBufferSize;
    if (!PVGetVolHeader(&handle, outputBuf, &headerLength, 0)) {
        fprintf(stderr, "Failed to get VOL header\n");
        return EXIT_FAILURE;
    }
    fwrite(outputBuf, 1, headerLength, fpOutput);

    // Core loop.
    int32_t retVal = EXIT_SUCCESS;
    int32_t frameSize = (width * height * 3) / 2;
    int32_t numFramesEncoded = 0;

    while (1) {
        // Read the input frame.
        int32_t bytesRead;
        bytesRead = fread(inputBuf, 1, frameSize, fpInput);
        if (bytesRead != frameSize) {
            break; // End of file.
        }

        // Encode the input frame.
        VideoEncFrameIO vin, vout;
        memset(&vin, 0, sizeof(vin));
        memset(&vout, 0, sizeof(vout));
        vin.height = height; // height is multiple of 16.
        vin.pitch = width; // width is multiple of 16.
        vin.timestamp = (numFramesEncoded * 1000) / frameRate;  // in ms.
        vin.yChan = inputBuf;
        vin.uChan = vin.yChan + vin.height * vin.pitch;
        vin.vChan = vin.uChan + ((vin.height * vin.pitch) >> 2);

        uint32_t modTimeMs = 0;
        int32_t nLayer = 0;
        MP4HintTrack hintTrack;
        int32_t dataLength = kOutputBufferSize;
        if (!PVEncodeVideoFrame(&handle, &vin, &vout,
                &modTimeMs, outputBuf, &dataLength, &nLayer) ||
            !PVGetHintTrack(&handle, &hintTrack)) {
            fprintf(stderr, "Failed to encode frame or get hink track at "
                    " frame %d\n", numFramesEncoded);
            retVal = EXIT_FAILURE;
            break;
        }
        PVGetOverrunBuffer(&handle);
        numFramesEncoded++;

        // Write the output.
        fwrite(outputBuf, 1, dataLength, fpOutput);
    }

    // Close input and output file.
    fclose(fpInput);
    fclose(fpOutput);

    // Free allocated memory.
    free(inputBuf);
    free(outputBuf);

    // Close encoder instance.
    PVCleanUpVideoEncoder(&handle);
    return retVal;
}
