#include "VideoEncoder.h"
#include "VideoDecoder.h"
#include "CCycleBuffer.h"
//#include "cameraLog.h"
#include "kylinlogger_cpp.h"

#include <QDebug>
#include <QDateTime>
#include "MP4Writer.h"
#include <sys/time.h>
#include <faac.h>

extern "C" {
#include "x264_enc.h"
#include "vaapi_h264.h"
#include "kylingpu-probe.h"
}

VideoEncoder::VideoEncoder(CCycleBuffer *video_buf, CCycleBuffer *audio_buf, int width, int height, int fps)
{
    m_encoder_buf = video_buf;
    m_audio_buf = audio_buf;
    m_ctx = NULL;
    m_va_ctx = NULL;
    m_enc_ctx = NULL;
    m_nv12_surf = NULL;
    audio_encoder = NULL;

    m_width = width;
    m_height = height;
    m_buf_size = width*height*3/2;
    m_buf = (uint8_t *)new char[m_buf_size];
    m_fps = fps;
    m_mp4_file = (unsigned long)NULL;
    m_stopped = true;
    m_started = false;

    m_time = 0;
    m_pause_time = 0;
    m_record_time = 0;
    m_start_time = 0;
    m_enc_level = -1;
}

int VideoEncoder::encoder_init(int width, int height, int fps)
{
    m_width = width;
    m_height = height;
    m_buf_size = width*height*3/2;
    m_buf = (uint8_t *)new char[m_buf_size];
    m_fps = fps;
    return 0;
}

int VideoEncoder::encoder_deinit()
{
    if(m_buf)
        delete []m_buf;

    m_encoder_buf->Empty();
    m_audio_buf->Empty();

    return 0;
}


void VideoEncoder::encode_fix(int width , int height)
{
    LOG(CAMERA_LOG , NOTICE) <<"fix format width="<<m_width<<"-"<<width<<" height="<<m_height<<"-"<<height;
    m_width = width;
    m_height = height;
    m_buf_size = width*height*3/2;
    if (m_buf)
        delete[] m_buf;
    m_buf = (uint8_t *)new char[m_buf_size];

}

int VideoEncoder::encoder_audio_init(unsigned long rate, unsigned int channel, unsigned int size)
{

    faacEncConfigurationPtr config;
    int ret = 0;
    (void)ret;
    m_bit_size = size;
    m_rate = rate;
    m_channels = channel;
    audio_encoder = faacEncOpen(rate, channel, &m_input_size, &m_output_size);
     if(audio_encoder == NULL) {
         LOG(CAMERA_LOG,ERROR) << "FAAC open failed";
     }
     /*one audio frame size*/
     m_pcm_size = m_input_size * m_bit_size / 8;

     m_pcm_buf = new char[m_pcm_size];               //raw pcm data
     m_aac_buf = new unsigned char[m_output_size];		   //encoder aac data

     /*get faac encoder config*/
     audio_config = faacEncGetCurrentConfiguration(audio_encoder);
     config = (faacEncConfigurationPtr)audio_config;
     /*原始（Raw）的AAC音频不能直接不放，需要解码。加了ADTS头的AAC音频可以直接播放，也可解码播放*/
     config->outputFormat = 1;                  //输出数据的类型  0=原始AAC,1=加ADTS头的AAC

     config->inputFormat = FAAC_INPUT_16BIT;    //输入音频数据的采样位数（要与采集设置参数一致）

     config->useTns = 1;                        //瞬时噪声定形滤波器
     config->allowMidside = 0;                  //mid/side coding
     config->aacObjectType=LOW;                 //MAIN=1,LOW=2,SSR=3,LTP=4

     ret = faacEncSetConfiguration((faacEncHandle)audio_encoder,config);

     //m_fp = fopen("record.aac", "wb");
     m_stopped = false;
     return 1;
}

VideoEncoder::~VideoEncoder()
{
    m_mutex.unlock();
    requestInterruption();
    quit();
    wait();

    if(m_buf)
        delete []m_buf;

    if(m_hwenc_support == true) {
        if(m_nv12_surf)
            vaapi_destroy_yuv(m_va_ctx, m_nv12_surf);

        if(m_enc_ctx)
            hwenc_context_destroy(m_enc_ctx);

        if(m_va_ctx)
            vaapi_context_destroy(m_va_ctx);
    } else {
        if(m_ctx)
            x264_context_destroy(m_ctx);
    }

    if(m_mp4_file)
        MP4WriterExit(m_mp4_file);

    if(audio_encoder)
        faacEncClose(audio_encoder);
}

int VideoEncoder::recordStart(char *videoname, bool hwenc, int nsamplerate, int nchannels, int nsamplesize,
                              int enclevel)
{
    int ret = true;
    m_enc_level = -1;

    if (xf86PciProbe() == 0)
        m_hwenc_support = encoder_hwenc_init();
    else
        m_hwenc_support = false;
    m_hwenc_support = m_hwenc_support & hwenc;
    m_enc_level = enclevel;
    LOG(CAMERA_LOG, NOTICE) << "m_hwenc_support:" << m_hwenc_support << " m_enc_level:" << m_enc_level;
    if (m_hwenc_support == false)
        m_ctx = x264_context_create();
    if (!m_ctx)
        return false;

    m_mp4_file = MP4WriterInit();
    if (!m_mp4_file)
        return false;
    ret = MP4WriterCreateFile(m_mp4_file, videoname, AF_MP4_VIDEO_H264, m_width, m_height, m_fps);
    if (ret == -1)
    {
        MP4WriterExit(m_mp4_file);
        m_mp4_file = NULL;
        return false;
    }

    ret = encoder_audio_init(nsamplerate, nchannels, nsamplesize);
    if (ret != 1)
    {
        LOG(CAMERA_LOG, WARN) << "encoder audio init err";
    }

    // clear
    m_encoder_buf->Empty();
    m_audio_buf->Empty();

    m_time = 0;
    m_pause_time = 0;
    m_record_time = 0;
    m_start_time = 0;


    return true;
}

void VideoEncoder::encode_last_data()
{
    int len;
    int num;
    uint8_t *buf;
    long long nTimeStamp;
    int i= 0;
    struct timeval timeNow;

    //encode the rest of frame in buffer
    gettimeofday(&timeNow, NULL);
    nTimeStamp = timeNow.tv_sec * 1000 + timeNow.tv_usec / 1000;
    while(1) {
        len = kylin_x264_encoder_delayed_frames(m_ctx, &buf, &num);
        if(num == 0)
            break;

        if(m_pause_time)
	    m_record_time = m_pause_time  - m_time + m_real_time * i;
        else
            m_record_time = nTimeStamp  - m_start_time - m_time + 1000/m_fps * i;

        if(m_mp4_file != (unsigned long)NULL)
            MP4WriterWriteFile(m_mp4_file, buf, len, 1, m_record_time);

        i++;
        usleep(1000/m_fps * 1000);
    }
}

void VideoEncoder::recordStop()
{
    if(m_mp4_file) {
        MP4WriterSaveFile(m_mp4_file);
        MP4WriterExit(m_mp4_file);
        m_mp4_file = NULL;
    }
    if(m_hwenc_support == true) {
        if(m_nv12_surf)
            vaapi_destroy_yuv(m_va_ctx, m_nv12_surf);

        if(m_enc_ctx)
            hwenc_context_destroy(m_enc_ctx);

        if(m_va_ctx)
        vaapi_context_destroy(m_va_ctx);
    } else {
        if(m_ctx)
            x264_context_destroy(m_ctx);
    }

    m_ctx = NULL;
    m_va_ctx = NULL;
    m_enc_ctx = NULL;
    m_nv12_surf = NULL;
    m_mp4_file = (unsigned long)NULL;
    m_time = 0;
}


void VideoEncoder::run()
{
    int len,lv;
    uint8_t *buf;
    struct timeval timeNow;
    long long nTimeStamp;
    int err, ret;
    while(!isInterruptionRequested())
    {
        m_mutex.lock();
        //LOG(CAMERA_LOG,NOTICE) << "encode frame size " << m_encoder_buf->GetLength();
        //LOG(CAMERA_LOG,NOTICE) << "encode audio size " << m_audio_buf->GetLength();
	
	gettimeofday(&timeNow, NULL);
	nTimeStamp = timeNow.tv_sec * 1000 + timeNow.tv_usec / 1000;
	if(!m_start_time)
	    m_start_time = nTimeStamp;
        m_record_time = nTimeStamp - m_start_time - m_time;

        if(m_encoder_buf->isEmpty() == false) {
            m_encoder_buf->Read(m_buf, m_buf_size);
            if(m_hwenc_support == true) {
                err = hwenc_upload_surface(m_va_ctx, m_nv12_surf, m_buf);
                if (err < 0) {
                    LOG(CAMERA_LOG,FATAL) << "encoder upload error";
                    break;
                }

                err = hwenc_import_yuv(m_enc_ctx, m_nv12_surf);
                if (err < 0) {
                    LOG(CAMERA_LOG,FATAL) << "encoder input error";
                    break;
                }

                if (m_enc_level != -1)
                    lv = m_enc_level;
                err = hwenc_params_level(m_enc_ctx, 0);
                if (err < 0) {
                    LOG(CAMERA_LOG,FATAL) << "encoder level error";
                    break;
                }

                len = hwenc_do_encode(m_enc_ctx);
                if (err < 0) {
                    LOG(CAMERA_LOG,FATAL) << "encoder encoding error";
                    break;
                }

                len = hwenc_export_h264(m_enc_ctx, &buf);
            } else {
                err = x264_import_yuv(m_ctx, (uint8_t *)m_buf, m_width, m_height);
                if(err < 0) {
                    LOG(CAMERA_LOG,NOTICE) << "x264_import_yuv error";
                    break;
                }

                lv = 2 - m_height/1081;
                if (m_enc_level != -1)
                    lv = m_enc_level;
                x264_params_level(m_ctx, lv);
                len = x264_do_encode(m_ctx, &buf);
            }


            if(len == 0){
                m_mutex.unlock();
                continue;
            }
            uint8_t value;
            value = buf[4] & 0x1f;
            switch (value) {
            case 0x05:
                LOG(CAMERA_LOG,NOTICE) << "IDR ";
                break;
            case 0x06:
                LOG(CAMERA_LOG,NOTICE) << "SEI ";
                break;
            case 0x07:
                LOG(CAMERA_LOG,NOTICE) << "SPS ";
                break;
            case 0x08:
                LOG(CAMERA_LOG,NOTICE) << "PPS ";
                break;
            default:
                break;
            }
            if(m_mp4_file != (unsigned long)NULL)
                MP4WriterWriteFile(m_mp4_file, buf, len, 1, m_record_time);
            //int w_len;
            //w_len = fwrite(buf, 1, len, m_fp);
            //if(w_len != len)
            //    LOG(CAMERA_LOG,NOTICE) << "w_len != len";
            //LOG(CAMERA_LOG,NOTICE) << "2 " << QDateTime::currentDateTime().toString("hh:MM:ss:zzz");

        }
#if 1
        if(m_audio_buf->isEmpty() == false && m_audio_buf->GetLength() >= m_pcm_size) {
            memset(m_pcm_buf, 0, m_pcm_size);
            //LOG(CAMERA_LOG,NOTICE) << "audio_buf " << m_audio_buf->GetLength();
            m_audio_buf->Read((uint8_t *)m_pcm_buf, m_pcm_size);
            m_input_size = m_pcm_size / (m_bit_size/8);//一帧样本大小通过该计算得到

            /*Encode*/
            /*传pbPCMBuffer和长度nPCMBufferSize,编码后的数据会存到pbAACBuffer,编码后的数据大小为nRect*/
            ret = faacEncEncode(audio_encoder, (int32_t*)m_pcm_buf, m_input_size,
                                m_aac_buf,m_output_size);

            //gettimeofday(&timeNow, NULL);
            //nTimeStamp = timeNow.tv_sec * 1000 + timeNow.tv_usec / 1000 - m_time;
            if(m_mp4_file != (unsigned long)NULL)
                err = MP4WriterWriteAudioFile(m_mp4_file, m_aac_buf, ret, m_record_time, m_rate, m_channels, m_bit_size);
            //LOG(CAMERA_LOG,NOTICE) << "audio ret " << err;
            //int w_len;
            //if(i++ < 200)
            //    w_len = fwrite(m_aac_buf, 1, ret, m_fp);
            //else if (i == 200)
            //    fclose(m_fp);

        }
#endif
        if(m_encoder_buf->GetLength() != 0 || m_stopped == false)
            m_mutex.unlock();
        else if(m_stopped == true && m_started == true) {
            encode_last_data();
            recordStop();
            m_started = false;
        }
        usleep(1000);
    }
}

bool VideoEncoder::encoder_hwenc_init()
{
    int err;
    int profile = 0;

    m_va_ctx = vaapi_context_create(0);
    if (!m_va_ctx) {
        logger_deinit();
        vaapi_context_destroy(m_va_ctx);
        return false;
    }

    m_enc_ctx = hwenc_context_create(m_va_ctx, profile);
    if(!m_enc_ctx) {
        logger_deinit();
        vaapi_context_destroy(m_va_ctx);
        hwenc_context_destroy(m_enc_ctx);
        return false;
    }

    m_nv12_surf = vaapi_create_yuv(m_va_ctx, m_width, m_height);
    if (m_nv12_surf == NULL) {
        logger_deinit();
        vaapi_context_destroy(m_va_ctx);
        hwenc_context_destroy(m_enc_ctx);
        return false;
    }

    return true;
}
