#include "HardDecode.h"

using namespace std;

HardDecode::HardDecode()
{
	;
}

HardDecode::~HardDecode()
{
	;
}

int HardDecode::hardDecoderInit(unsigned int uiWidth, unsigned int uiHeight, unsigned int uiFrameRate, AVCodecParameters *pCodecParameters)
{
	uiWidth_ = uiWidth; uiHeight_ = uiHeight;
	uiFrameRate_ = uiFrameRate;
    this->pCodecParameters_ = pCodecParameters;

	av_log_set_level(AV_LOG_ERROR);
	
	// AVCodecID codec_id = AV_CODEC_ID_H264;	//解码H264
	// pCodec_ = avcodec_find_decoder(codec_id);	//获取解码器 

	pCodec_ = avcodec_find_decoder(this->pCodecParameters_->codec_id);

    if (pCodec_ == nullptr) {
        LogError << "cannot find decoder !";
        return 0;
    }

	//创建上下文
    pCodecCtx_ = avcodec_alloc_context3(pCodec_);	
    if (!pCodecCtx_){
        LogError << "Could not allocate video codec context";
        return 0;
    }

    // 将AVCodecParameters结构体中码流参数拷贝到AVCodecContext结构体中,format, width, height, codec_type等
    avcodec_parameters_to_context(pCodecCtx_, this->pCodecParameters_);
    if (!pCodecCtx_) {
        LogError << "Cannot alloc context.";
        return 0;
    }

	//打开解码器
    int iRet = 1;
    if ((iRet = avcodec_open2(pCodecCtx_, pCodec_, nullptr)) < 0) {
        LogError << "cannot open decoder, Ret:" << iRet;
        return 0;
    }

    //分配packet
    pPacket_ = av_packet_alloc();
    if (!pPacket_){
        LogError << "Could not allocate video packet";
        return 0;
    }
    // av_init_packet(pPacket_);

	//分配frame
    pSrcFrame_ = av_frame_alloc();
    if (!pSrcFrame_) {
        LogError << "Could not allocate video src pFrame";
        return 0;
    }

    pDstFrame_ = av_frame_alloc();
    if (!pDstFrame_) {
        LogError << "Could not allocate video dst pFrame";
        return 0;
    }

//    printf("after align down video_width: %d, video_height: %d\n", uiWidth_, uiHeight_);

    int bufferSize = av_image_get_buffer_size(AV_PIX_FMT_YUV420P,
                                              pCodecCtx_->width,
                                              pCodecCtx_->height, 1);
    outBuffer_ = (unsigned char *) av_malloc(bufferSize);
    av_image_fill_arrays(pDstFrame_->data,
                         pDstFrame_->linesize,
                         outBuffer_,
                         AV_PIX_FMT_YUV420P,
                         pCodecCtx_->width,
                         pCodecCtx_->height, 1);

    pSwsContext_ = sws_getContext(pCodecCtx_->width, pCodecCtx_->height, pCodecCtx_->pix_fmt,
                                     pCodecCtx_->width, pCodecCtx_->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, nullptr, nullptr, nullptr);
	return 1;
}

int HardDecode::hardDecoderDeInit()
{
    if (pCodecParameters_){
        av_free(pCodecParameters_);
        pCodecParameters_ = nullptr;
    }
	if(outBuffer_){
        av_free(outBuffer_);
        outBuffer_ = nullptr;
    }
	if(pSrcFrame_){
		av_frame_free(&pSrcFrame_);
		pSrcFrame_ = nullptr;
	}
	if(pDstFrame_){
		av_frame_free(&pDstFrame_);
		pDstFrame_ = nullptr;
	}
	if(pPacket_){
        av_packet_free(&pPacket_);  
        pPacket_ = nullptr;
    }
	if(pCodecParserCtx_){
		av_parser_close(pCodecParserCtx_);
		pCodecParserCtx_ = nullptr;
	}
	if(pCodecCtx_){
		avcodec_close(pCodecCtx_);
		av_free(pCodecCtx_);
		pCodecCtx_ = nullptr;
	}
	if(pSwsContext_){
		sws_freeContext(pSwsContext_);
		pSwsContext_ = nullptr;
	}
}

int HardDecode::hardDecoder(void* pOutputData,unsigned int* puiOutputDataSize)
{
	int ret;

    ret = avcodec_send_packet(this->pCodecCtx_, this->pPacket_);    //接收packet解码
    if (ret < 0) {
        LogError << "Error sending a packet for decoding";
        return 0;
    }

    while (ret >= 0) {
        ret = avcodec_receive_frame(this->pCodecCtx_, this->pSrcFrame_);    //解码
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF){
            LogWarn << "During decoding eof";
            return 0;
        }
        else if (ret < 0) {
            LogError << "Error during decoding";
            return 0;
        }

        sws_scale(this->pSwsContext_,
                  (const uint8_t *const *)this->pSrcFrame_->data,
                  this->pSrcFrame_->linesize,
                  0,
                  this->pCodecCtx_->height,
                  this->pDstFrame_->data,
                  this->pDstFrame_->linesize);

        fflush(stdout);

		int iSize = this->pCodecCtx_->width * this->pCodecCtx_->height;

		memcpy(pOutputData, this->pDstFrame_->data[0], iSize); 	//Y
		memcpy(pOutputData+iSize, this->pDstFrame_->data[1], iSize/4);    //U
		memcpy(pOutputData+iSize+iSize/4, this->pDstFrame_->data[2], iSize/4);   //V
		*puiOutputDataSize = iSize*3/2;
		return iSize*3/2;
	}
    return 0;
}