#ifdef __cplusplus
extern "C" {
#endif
#include <stdio.h>
#include <stdlib.h>
#include "gpac/isomedia.h"
#include "gpac/internal/media_dev.h"
#include "gpac/constants.h"
#include "gpac/version.h"
#ifdef __cplusplus
}
#endif
#include<QDebug>
#include "MP4Writer.h"
#include <QtGlobal>
#include <QDebug>
#define SWAP(x,y)   ((x)^=(y)^=(x)^=(y))

#define INIT_STATUS    0
#define CONFIG_STATUS  1
#define CONFIG_FINISH  2

#define LIB_PUBLIC __attribute__((visibility("default")))
#define LIB_LOCAL  __attribute__((visibility("hidden")))

typedef enum tag_AF_NALU_TYPE
{
    AF_NALU_VPS = 0,
    AF_NALU_SPS,
    AF_NALU_PPS,

    AF_NALU_MAX
}AF_NALU_TYPE;

const static int g_HEVC_NaluMap[AF_NALU_MAX] = {
    GF_HEVC_NALU_VID_PARAM,
    GF_HEVC_NALU_SEQ_PARAM,
    GF_HEVC_NALU_PIC_PARAM
};

const static unsigned long g_SamplingRatesList[] = {
	96000,
	88200,
	64000,
	48000,
	44100,
	32000,
	24000,
	22050,
	16000,
	12000,
	11025,
	8000,
	0
};

class AF_MP4Writer
{
public:
    AF_MP4Writer();
    virtual ~AF_MP4Writer();

    int Create(char *strName, AF_MP4_VIDEO_ENC_TYPE nEncType, int nWidth, int nHeight, int nFps);
    int Save();
    int Write(unsigned char *pData, int nSize, bool bKey, long nTimeStamp);
    int WriteAudio(unsigned char *pData, int nSize, long nTimeStamp, int nSampleRate, int nChannel, int nBitsPerSample);

private:
    int WriteH264Nalu(unsigned char **ppNaluData, int *pNaluLength);
    int WriteH265Nalu(unsigned char **ppNaluData, int *pNaluLength);

    int WriteFrame(unsigned char *pData, int nSize, bool bKey, long nTimeStamp);

    int WriteH264(unsigned char *pData, int nSize, bool bKey, long nTimeStamp);
    int WriteH265(unsigned char *pData, int nSize, bool bKey, long nTimeStamp);

    int WriteAAC(unsigned char *pData, int nSize, long nTimeStamp, int nSampleRate, int nChannel, int nBitsPerSample);
    int WriteAACInfo(unsigned char *pData, int nSize, int nSampleRate, int nChannel, int nBitsPerSample);
    int WriteAACFrame(unsigned char *pData, int nSize, long nTimeStamp);

    int ParseNalu(unsigned char *pData, int nSize, int *pStart, int *pEnd);

private:
    AF_MP4_VIDEO_ENC_TYPE m_enEncType;
    int m_nWidth;
    int m_nHeight;
    int m_nFps;

    long m_TimeStamp;
    long m_AudioTimeStamp;
    bool m_bReady;
    bool m_bAudioReady;

    GF_ISOFile *m_pFile;

    int m_nTrackID;
    int m_nAudioTrackID;
    unsigned int m_nStreamIndex;
    unsigned int m_nAudioStreamIndex;

    unsigned char *m_pNaluData[AF_NALU_MAX];
    int m_pNaluLength[AF_NALU_MAX];
};

AF_MP4Writer::AF_MP4Writer()
{
    m_enEncType = AF_MP4_VIDEO_H264;
    m_nWidth = 1920;
    m_nHeight = 1080;

    m_TimeStamp = -1;
    m_AudioTimeStamp = -1;

    m_bReady = false;
    m_bAudioReady = false;

    m_pFile = NULL;

    m_nTrackID = -1;
    m_nAudioTrackID = -1;
    m_nStreamIndex = 0;
    m_nAudioStreamIndex = 0;

    memset(m_pNaluData, 0, sizeof(m_pNaluData));
    memset(m_pNaluLength, 0, sizeof(m_pNaluLength));
}

AF_MP4Writer::~AF_MP4Writer()
{
}

int AF_MP4Writer::Create(char *strName, AF_MP4_VIDEO_ENC_TYPE nEncType, int nWidth, int nHeight, int nFps)
{
    if ((strName == NULL) || strlen(strName) == 0)
    {
        return -1;
    }

    m_pFile = gf_isom_open(strName, GF_ISOM_OPEN_WRITE, NULL);
    if (m_pFile == NULL)
    {
        return -1;
    }

    gf_isom_set_brand_info(m_pFile, GF_ISOM_BRAND_MP42, 0);

    m_enEncType = nEncType;
    m_nWidth = nWidth;
    m_nHeight = nHeight;
    m_nFps = nFps;
    return 0;
}

int AF_MP4Writer::Save()
{
    for (int i = 0; i < AF_NALU_MAX; i++)
    {
        if (m_pNaluData[i])
        {
            delete m_pNaluData[i];
            m_pNaluData[i] = NULL;
            m_pNaluLength[i] = 0;
        }
    }

    if (m_pFile)
    {
        gf_isom_close(m_pFile);
        m_pFile = NULL;
    }

    m_TimeStamp = -1;
    m_AudioTimeStamp = -1;

    m_bReady = false;
    m_bAudioReady = false;

    return 0;
}

int AF_MP4Writer::Write(unsigned char *pData, int nSize, bool bKey, long nTimeStamp)
{
    if (m_pFile)
    {
        if (m_enEncType == AF_MP4_VIDEO_H265)
        {
            return WriteH265(pData, nSize, bKey, nTimeStamp);
        }
        else if (m_enEncType == AF_MP4_VIDEO_H264)
        {
            return WriteH264(pData, nSize, bKey, nTimeStamp);
        }
    }

    return 0;
}

int AF_MP4Writer::WriteAudio(unsigned char *pData, int nSize, long nTimeStamp, int nSampleRate, int nChannel, int nBitsPerSample)
{
    return WriteAAC(pData, nSize, nTimeStamp, nSampleRate, nChannel, nBitsPerSample);
}

int AF_MP4Writer::WriteH264(unsigned char *pData, int nSize, bool bKey, long nTimeStamp)
{
    if ((nTimeStamp == 0) || (pData == NULL) || (nSize <= 0))
        return -1;

    int lenIn = nSize;
    int lenOut = 0;
    unsigned char *pIn = pData;
    unsigned char *pOut = NULL;

    int nBufSize = 0;
    unsigned char *pBuf = (unsigned char *)malloc(nSize + 4);
    memset(pBuf, 0, nSize + 4);

    do {
        int nNalStart = 0;
        int nNalEnd = 0;
        lenOut = ParseNalu(pIn, lenIn, &nNalStart, &nNalEnd);
        if (lenOut <= 0)
            break;
        pOut = pIn + nNalStart;
        if (pOut)
        {
            unsigned int nNalType = (pOut[0] & 0x1F);
            switch (nNalType)
            {
                case 0x07: // SPS
                    if (!m_bReady)
                    {
                        m_pNaluData[AF_NALU_SPS] = new unsigned char[lenOut];
                        memcpy(m_pNaluData[AF_NALU_SPS], pOut, lenOut);
                        m_pNaluLength[AF_NALU_SPS] = lenOut;
                    }
                    break;
                case 0x08: // PPS
                    if (!m_bReady)
                    {
                        m_pNaluData[AF_NALU_PPS] = new unsigned char[lenOut];
                        memcpy(m_pNaluData[AF_NALU_PPS], pOut, lenOut);
                        m_pNaluLength[AF_NALU_PPS] = lenOut;
                    }
                    break;
                case 0x06: // SEI
                    break;
                default :
                    memcpy(pBuf + nBufSize, &lenOut, 4);
                    SWAP(pBuf[nBufSize], pBuf[nBufSize + 3]);
                    SWAP(pBuf[nBufSize + 1], pBuf[nBufSize + 2]);
                    nBufSize += 4;
                    memcpy(pBuf + nBufSize, pOut, lenOut);
                    nBufSize += lenOut;
                    break;
            }

            lenIn -= (lenOut - (pOut - pIn));
            pIn = pOut + lenOut;
        }
    } while (1);

    if (!m_bReady && m_pNaluData[AF_NALU_SPS] && m_pNaluData[AF_NALU_PPS])
    {
        int nPPSLen = m_pNaluLength[AF_NALU_PPS];
        int nZeroCount = 0;
        for (int ni = nPPSLen - 1; ni >= 0; ni--)
        {
            if (m_pNaluData[AF_NALU_PPS][ni])
                break;
            nZeroCount++;
        }

        m_pNaluLength[AF_NALU_PPS] -= nZeroCount;
        WriteH264Nalu(m_pNaluData, m_pNaluLength);
        m_bReady = true;
    }

    if (m_bReady && (nBufSize > 0))
        WriteFrame(pBuf, nBufSize, bKey, nTimeStamp);

    if (pBuf)
    {
        free(pBuf);
        pBuf = NULL;
    }

    return 0;
}

int AF_MP4Writer::WriteH265(unsigned char *pData, int nSize, bool bKey, long nTimeStamp)
{
    if ((nTimeStamp == 0) || (pData == NULL) || (nSize <= 0))
        return -1;

    int lenIn = nSize;
    int lenOut = 0;
    unsigned char *pIn = pData;
    unsigned char *pOut = NULL;

    int nBufSize = 0;
    unsigned char *pBuf = (unsigned char *)malloc(nSize + 4);
    memset(pBuf, 0, nSize + 4);

    do {
        int nNalStart = 0;
        int nNalEnd = 0;
        lenOut = ParseNalu(pIn, lenIn, &nNalStart, &nNalEnd);
        if (lenOut <= 0)
            break;
        pOut = pIn + nNalStart;
        if (pOut)
        {
            unsigned int nNalType = ((pOut[0] >> 1) & 0x3F);
            switch (nNalType)
            {
                case 0x20: // VPS
                    if (!m_bReady)
                    {
                        m_pNaluData[AF_NALU_VPS] = new unsigned char[lenOut];
                        memcpy(m_pNaluData[AF_NALU_VPS], pOut, lenOut);
                        m_pNaluLength[AF_NALU_VPS] = lenOut;
                    }
                    break;
                case 0x21: // SPS
                    if (!m_bReady)
                    {
                        m_pNaluData[AF_NALU_SPS] = new unsigned char[lenOut];
                        memcpy(m_pNaluData[AF_NALU_SPS], pOut, lenOut);
                        m_pNaluLength[AF_NALU_SPS] = lenOut;
                    }
                    break;
                case 0x22: // PPS
                    if (!m_bReady)
                    {
                        m_pNaluData[AF_NALU_PPS] = new unsigned char[lenOut];
                        memcpy(m_pNaluData[AF_NALU_PPS], pOut, lenOut);
                        m_pNaluLength[AF_NALU_PPS] = lenOut;
                    }
                    break;
                case 0x27:
                case 0x28: // SEI
                    break;
                default :
                    //if ((nNalType >= 0x00 && nNalType <= 0x09) || (nNalType >= 0x10 && nNalType <= 0x15)) // I/P
                    if ((nNalType <= 0x09) || (nNalType >= 0x10 && nNalType <= 0x15)) // I/P
                    {
                        memcpy(pBuf + nBufSize, &lenOut, 4);
                        SWAP(pBuf[nBufSize], pBuf[nBufSize + 3]);
                        SWAP(pBuf[nBufSize + 1], pBuf[nBufSize + 2]);
                        nBufSize += 4;
                        memcpy(pBuf + nBufSize, pOut, lenOut);
                        nBufSize += lenOut;
                    }
                    break;
            }

            lenIn -= (lenOut - (pOut - pIn));
            pIn = pOut + lenOut;
        }
    } while (1);

    if (!m_bReady && m_pNaluData[AF_NALU_VPS] && m_pNaluData[AF_NALU_SPS] && m_pNaluData[AF_NALU_PPS])
    {
        int nPPSLen = m_pNaluLength[AF_NALU_PPS];
        int nZeroCount = 0;
        for (int ni = nPPSLen - 1; ni >= 0; ni--)
        {
            if (m_pNaluData[AF_NALU_PPS][ni])
                break;
            nZeroCount++;
        }

        m_pNaluLength[AF_NALU_PPS] -= nZeroCount;
        WriteH265Nalu(m_pNaluData, m_pNaluLength);
        m_bReady = true;
    }

    if (m_bReady && (nBufSize > 0))
        WriteFrame(pBuf, nBufSize, bKey, nTimeStamp);

    if (pBuf)
    {
        free(pBuf);
        pBuf = NULL;
    }

    return 0;
}

int AF_MP4Writer::WriteH264Nalu(unsigned char **ppNaluData, int *pNaluLength)
{
    m_nTrackID = gf_isom_new_track(m_pFile,0,GF_ISOM_MEDIA_VISUAL,1000);
    gf_isom_set_track_enabled(m_pFile,m_nTrackID,GF_TRUE);

    GF_AVCConfig *pstAVCConfig = gf_odf_avc_cfg_new();
    gf_isom_avc_config_new(m_pFile, m_nTrackID, pstAVCConfig, NULL, NULL, &m_nStreamIndex);
    gf_isom_set_visual_info(m_pFile, m_nTrackID, m_nStreamIndex, m_nWidth, m_nHeight);

    pstAVCConfig->configurationVersion = 1;
    pstAVCConfig->AVCProfileIndication = ppNaluData[AF_NALU_SPS][1];
    pstAVCConfig->profile_compatibility = ppNaluData[AF_NALU_SPS][2];
    pstAVCConfig->AVCLevelIndication = ppNaluData[AF_NALU_SPS][3];

    GF_AVCConfigSlot stAVCConfig[AF_NALU_MAX];
    memset(stAVCConfig, 0, sizeof(stAVCConfig));

    for (int i = 0; i < AF_NALU_MAX; i++)
    {
        if (i == AF_NALU_VPS)
            continue;

        stAVCConfig[i].size = pNaluLength[i];

#if (QT_VERSION_CHECK(GPAC_VERSION_MAJOR,GPAC_VERSION_MINOR,0) >= QT_VERSION_CHECK(10, 0, 0))
        stAVCConfig[i].data = (u8 *)ppNaluData[i];
#else
        stAVCConfig[i].data = (char *)ppNaluData[i];
#endif
        if (i == AF_NALU_SPS)
        {
            gf_list_add(pstAVCConfig->sequenceParameterSets, &stAVCConfig[i]);
        }
        else if (i == AF_NALU_PPS)
        {
            gf_list_add(pstAVCConfig->pictureParameterSets, &stAVCConfig[i]);
        }
    }
    gf_isom_avc_config_update(m_pFile, m_nTrackID, 1, pstAVCConfig);

    pstAVCConfig->pictureParameterSets = NULL;
    pstAVCConfig->sequenceParameterSets = NULL;
    gf_odf_avc_cfg_del(pstAVCConfig);
    pstAVCConfig = NULL;

    return 0;
}

int AF_MP4Writer::WriteH265Nalu(unsigned char **ppNaluData, int *pNaluLength)
{
    m_nTrackID = gf_isom_new_track(m_pFile, 0, GF_ISOM_MEDIA_VISUAL, 1000);
    gf_isom_set_track_enabled(m_pFile, m_nTrackID, GF_TRUE);

    GF_HEVCConfig *pstHEVCConfig = gf_odf_hevc_cfg_new();
    pstHEVCConfig->nal_unit_size = 4;

    gf_isom_hevc_config_new(m_pFile, m_nTrackID, pstHEVCConfig, NULL, NULL, &m_nStreamIndex);
    gf_isom_set_nalu_extract_mode(m_pFile, m_nTrackID, GF_ISOM_NALU_EXTRACT_INSPECT);
    gf_isom_set_cts_packing(m_pFile, m_nTrackID, GF_TRUE);

    pstHEVCConfig->configurationVersion = 1;

    HEVCState hevc;
    memset(&hevc, 0 ,sizeof(HEVCState));

    GF_AVCConfigSlot stAVCConfig[AF_NALU_MAX];
    memset(stAVCConfig, 0, sizeof(stAVCConfig));
    GF_HEVCParamArray stHEVCNalu[AF_NALU_MAX];
    memset(stHEVCNalu, 0, sizeof(stHEVCNalu));

    int idx = 0;
    for (int i = 0; i < AF_NALU_MAX; i++)


    {
        switch (i)
        {
            case AF_NALU_SPS:
#if (QT_VERSION_CHECK(GPAC_VERSION_MAJOR,GPAC_VERSION_MINOR,0) >= QT_VERSION_CHECK(12, 0, 0))
                idx = gf_hevc_read_sps((u8 *)ppNaluData[i], pNaluLength[i], &hevc);
                hevc.sps[idx].crc = gf_crc_32((u8 *)ppNaluData[i], pNaluLength[i]);
#elif (QT_VERSION_CHECK(GPAC_VERSION_MAJOR,GPAC_VERSION_MINOR,0) >= QT_VERSION_CHECK(10, 0, 0))
                idx = gf_media_hevc_read_sps((u8 *)ppNaluData[i], pNaluLength[i], &hevc);
                hevc.sps[idx].crc = gf_crc_32((u8 *)ppNaluData[i], pNaluLength[i]);
#else
                idx = gf_media_hevc_read_sps((char *)ppNaluData[i], pNaluLength[i], &hevc);
                hevc.sps[idx].crc = gf_crc_32((char *)ppNaluData[i], pNaluLength[i]);
#endif
                pstHEVCConfig->profile_space = hevc.sps[idx].ptl.profile_space;
                pstHEVCConfig->tier_flag = hevc.sps[idx].ptl.tier_flag;
                pstHEVCConfig->profile_idc = hevc.sps[idx].ptl.profile_idc;
                break;
            case AF_NALU_PPS:
#if (QT_VERSION_CHECK(GPAC_VERSION_MAJOR,GPAC_VERSION_MINOR,0) >= QT_VERSION_CHECK(12, 0, 0))
                idx = gf_hevc_read_pps((u8 *)ppNaluData[i], pNaluLength[i], &hevc);
                hevc.pps[idx].crc = gf_crc_32((u8 *)ppNaluData[i], pNaluLength[i]);
#elif (QT_VERSION_CHECK(GPAC_VERSION_MAJOR,GPAC_VERSION_MINOR,0) >= QT_VERSION_CHECK(10, 0, 0))
                idx = gf_media_hevc_read_pps((u8 *)ppNaluData[i], pNaluLength[i], &hevc);
                hevc.pps[idx].crc = gf_crc_32((u8 *)ppNaluData[i], pNaluLength[i]);
#else
                idx = gf_media_hevc_read_pps((char *)ppNaluData[i], pNaluLength[i], &hevc);
                hevc.pps[idx].crc = gf_crc_32((char *)ppNaluData[i], pNaluLength[i]);
#endif
                break;
            case AF_NALU_VPS:
#if (QT_VERSION_CHECK(GPAC_VERSION_MAJOR,GPAC_VERSION_MINOR,0) >= QT_VERSION_CHECK(12, 0, 0))
                idx = gf_hevc_read_vps((u8 *)ppNaluData[i], pNaluLength[i], &hevc);
                hevc.vps[idx].crc = gf_crc_32((u8 *)ppNaluData[i], pNaluLength[i]);
#elif (QT_VERSION_CHECK(GPAC_VERSION_MAJOR,GPAC_VERSION_MINOR,0) >= QT_VERSION_CHECK(10, 0, 0))
                idx = gf_media_hevc_read_vps((u8 *)ppNaluData[i], pNaluLength[i], &hevc);
                hevc.vps[idx].crc = gf_crc_32((u8 *)ppNaluData[i], pNaluLength[i]);
#else
                idx = gf_media_hevc_read_vps((char *)ppNaluData[i], pNaluLength[i], &hevc);
                hevc.vps[idx].crc = gf_crc_32((char *)ppNaluData[i], pNaluLength[i]);
#endif
#if (QT_VERSION_CHECK(GPAC_VERSION_MAJOR,GPAC_VERSION_MINOR,0) >= QT_VERSION_CHECK(12, 0, 0))
                pstHEVCConfig->constantFrameRate = hevc.vps[idx].rates[0].constant_pic_rate_idc;
#else
                pstHEVCConfig->constantFrameRate = hevc.vps[idx].rates[0].constand_pic_rate_idc;
#endif
                pstHEVCConfig->avgFrameRate = hevc.vps[idx].rates[0].avg_pic_rate;
                pstHEVCConfig->numTemporalLayers = hevc.vps[idx].max_sub_layers;
                pstHEVCConfig->temporalIdNested = hevc.vps[idx].temporal_id_nesting;
                break;
        }

        stHEVCNalu[i].nalus = gf_list_new();
        gf_list_add(pstHEVCConfig->param_array, &stHEVCNalu[i]);
        stHEVCNalu[i].array_completeness = 1;
        stHEVCNalu[i].type = g_HEVC_NaluMap[i];
        stAVCConfig[i].id = idx;
        stAVCConfig[i].size = pNaluLength[i];
#if (QT_VERSION_CHECK(GPAC_VERSION_MAJOR,GPAC_VERSION_MINOR,0) >= QT_VERSION_CHECK(10, 0, 0))
        stAVCConfig[i].data = (u8 *)ppNaluData[i];
#else
        stAVCConfig[i].data = (char *)ppNaluData[i];
#endif
        gf_list_add(stHEVCNalu[i].nalus, &stAVCConfig[i]);
    }

    gf_isom_set_visual_info(m_pFile, m_nTrackID, m_nStreamIndex, hevc.sps[idx].width, hevc.sps[idx].height);
    gf_isom_hevc_config_update(m_pFile, m_nTrackID, 1, pstHEVCConfig);

    for (int i = 0; i < AF_NALU_MAX; i++)


    {
        if (stHEVCNalu[i].nalus)
            gf_list_del(stHEVCNalu[i].nalus);
    }

    pstHEVCConfig->param_array = NULL;
    gf_odf_hevc_cfg_del(pstHEVCConfig);
    pstHEVCConfig = NULL;

    return 0;
}

int AF_MP4Writer::WriteFrame(unsigned char *pData, int nSize, bool bKey, long nTimeStamp)
{
    if (m_TimeStamp == -1 && bKey)
    {
        m_TimeStamp = nTimeStamp;
    }

    if (m_TimeStamp != -1)
    {
        GF_ISOSample *pISOSample = gf_isom_sample_new();
#if (QT_VERSION_CHECK(GPAC_VERSION_MAJOR,GPAC_VERSION_MINOR,0) >= QT_VERSION_CHECK(10, 0, 0))
        pISOSample->IsRAP = (GF_ISOSAPType)bKey;
        pISOSample->data = (u8 *)pData;
#else
        pISOSample->IsRAP = (SAPType)bKey;
        pISOSample->data = (char *)pData;
#endif
        pISOSample->dataLength = nSize;

        pISOSample->DTS = nTimeStamp - m_TimeStamp;
        pISOSample->CTS_Offset = 0;
        GF_Err gferr = gf_isom_add_sample(m_pFile, m_nTrackID, m_nStreamIndex, pISOSample);
        if (gferr == -1)
        {
            pISOSample->DTS = nTimeStamp - m_TimeStamp + 1000 / (m_nFps * 2);
            gf_isom_add_sample(m_pFile, m_nTrackID, m_nStreamIndex, pISOSample);
        }

        pISOSample->data = NULL;
        pISOSample->dataLength = 0;
        gf_isom_sample_del(&pISOSample);
        pISOSample = NULL;
    }

    return 0;
}

int AF_MP4Writer::WriteAAC(unsigned char *pData, int nSize, long nTimeStamp, int nSampleRate, int nChannel, int nBitsPerSample)
{
	if (!m_bAudioReady && nSampleRate > 0)
	{
		if (nChannel <= 0)
			nChannel = 2;
		if (nBitsPerSample <= 0)
			nBitsPerSample = 16;

		int i = 0;
        for (i = 0; i < (int)(sizeof(g_SamplingRatesList) / sizeof(g_SamplingRatesList[0])); i++)
		{
            if (g_SamplingRatesList[i] == (unsigned long)nSampleRate)
				break;
		}

		unsigned char ucDecInfoBuff[] = {0x12, 0x10};
		unsigned short nDecInfo = (1 << 12) | (i << 7) | (nChannel << 3);
		memcpy(ucDecInfoBuff, &nDecInfo, sizeof(unsigned short));
		SWAP(ucDecInfoBuff[0], ucDecInfoBuff[1]);

		WriteAACInfo(ucDecInfoBuff, sizeof(ucDecInfoBuff), nSampleRate, nChannel, nBitsPerSample);
		
		m_bAudioReady = true;
	}

	if (!m_bAudioReady)
		return 0;

	if (pData[0] == 0xFF && (pData[1] & 0xF0) == 0xF0)
		WriteAACFrame(pData + 7, nSize - 7, nTimeStamp);
	else
		WriteAACFrame(pData, nSize, nTimeStamp);
	
	return 0;
}

int AF_MP4Writer::WriteAACInfo(unsigned char *pData, int nSize, int nSampleRate, int nChannel, int nBitsPerSample)
{
    m_nAudioTrackID = gf_isom_new_track(m_pFile, 0, GF_ISOM_MEDIA_AUDIO, 1000);
    gf_isom_set_track_enabled(m_pFile, m_nAudioTrackID, GF_TRUE);

	GF_ESD *esd = gf_odf_desc_esd_new(0);
	esd->ESID = gf_isom_get_track_id(m_pFile, m_nAudioTrackID);
	esd->OCRESID = gf_isom_get_track_id(m_pFile, m_nAudioTrackID);
	esd->decoderConfig->streamType = 0x05;
	esd->decoderConfig->objectTypeIndication = 0x40;
    esd->slConfig->timestampResolution = 1000;
	esd->decoderConfig->decoderSpecificInfo = (GF_DefaultDescriptor *)gf_odf_desc_new(GF_ODF_DSI_TAG);
#if (QT_VERSION_CHECK(GPAC_VERSION_MAJOR,GPAC_VERSION_MINOR,0) >= QT_VERSION_CHECK(10, 0, 0))
    esd->decoderConfig->decoderSpecificInfo->data = (u8 *)pData;
#else
    esd->decoderConfig->decoderSpecificInfo->data = (char *)pData;
#endif
	esd->decoderConfig->decoderSpecificInfo->dataLength = nSize;
	
	GF_Err gferr = gf_isom_new_mpeg4_description(m_pFile, m_nAudioTrackID, esd, NULL, NULL, &m_nAudioStreamIndex);
    (void)gferr;
#if (QT_VERSION_CHECK(GPAC_VERSION_MAJOR,GPAC_VERSION_MINOR,0) >= QT_VERSION_CHECK(10, 0, 0))
    gferr = gf_isom_set_audio_info(m_pFile, m_nAudioTrackID, m_nAudioStreamIndex, nSampleRate, nChannel, nBitsPerSample, GF_IMPORT_AUDIO_SAMPLE_ENTRY_NOT_SET);
#else
    gferr = gf_isom_set_audio_info(m_pFile, m_nAudioTrackID, m_nAudioStreamIndex, nSampleRate, nChannel, nBitsPerSample);
#endif
	return 0;
}

int AF_MP4Writer::WriteAACFrame(unsigned char *pData, int nSize, long nTimeStamp)
{
	if (m_AudioTimeStamp == -1)
	{
		m_AudioTimeStamp = nTimeStamp;
	}
	
	if (m_AudioTimeStamp != -1)
	{
		GF_ISOSample *pISOSample = gf_isom_sample_new();
		pISOSample->dataLength = nSize;
#if (QT_VERSION_CHECK(GPAC_VERSION_MAJOR,GPAC_VERSION_MINOR,0) >= QT_VERSION_CHECK(10, 0, 0))
        pISOSample->IsRAP = (GF_ISOSAPType)1;
        pISOSample->data = (u8 *)pData;
#else
        pISOSample->IsRAP = (SAPType)1;
        pISOSample->data = (char *)pData;
#endif
		pISOSample->DTS = nTimeStamp - m_AudioTimeStamp;
		pISOSample->CTS_Offset = 0;	
		GF_Err gferr = gf_isom_add_sample(m_pFile, m_nAudioTrackID, m_nAudioStreamIndex, pISOSample);			
		if (gferr == -1)
		{
            pISOSample->DTS = nTimeStamp - m_AudioTimeStamp + 1000 / m_nFps;
			gf_isom_add_sample(m_pFile, m_nAudioTrackID, m_nAudioStreamIndex, pISOSample);
		}
		
		pISOSample->data = NULL;
		pISOSample->dataLength = 0;
		gf_isom_sample_del(&pISOSample);
		pISOSample = NULL;
	}
	
	return 0;
}

int AF_MP4Writer::ParseNalu(unsigned char *pData, int nSize, int *pStart, int *pEnd)
{
    int i = 0;
    *pStart = 0;
    *pEnd = 0;

    while ((pData[i] != 0x00 || pData[i + 1] != 0x00 || pData[i + 2] != 0x01)
        && (pData[i] != 0x00 || pData[i + 1] != 0x00 || pData[i + 2] != 0x00 || pData[i + 3] != 0x01))
    {
        i++;
        if (i + 4 >= nSize)
            return 0;
    }

    if (pData[i] != 0x00 || pData[i + 1] != 0x00 || pData[i + 2] != 0x01)
        i++;

    if (pData[i] != 0x00 || pData[i + 1] != 0x00 || pData[i + 2] != 0x01)
        return 0;

    i += 3;
    *pStart = i;

    while ((pData[i] != 0x00 || pData[i + 1] != 0x00 || pData[i + 2] != 0x00)
        && (pData[i] != 0x00 || pData[i + 1] != 0x00 || pData[i + 2] != 0x01))
    {
        i++;
        if (i + 3 >= nSize)
        {
            *pEnd = nSize;
             return (*pEnd - *pStart);
        }
    }

    *pEnd = i;
    return (*pEnd - *pStart);
}

LIB_LOCAL AFMP4Handle MP4WriterInit()
{
    AF_MP4Writer *pCMP4Writer = new AF_MP4Writer();
    return (AFMP4Handle)pCMP4Writer;
}

LIB_LOCAL int MP4WriterExit(AFMP4Handle hMP4File)
{
    AF_MP4Writer *pCMP4Writer = (AF_MP4Writer *)hMP4File;

    if (pCMP4Writer)
    {
        delete pCMP4Writer;
        pCMP4Writer = NULL;
    }

    return 0;
}

LIB_LOCAL int MP4WriterCreateFile(AFMP4Handle hMP4File, char *strName, AF_MP4_VIDEO_ENC_TYPE nEncType, int nWidth, int nHeight, int nFps)
{
    AF_MP4Writer *pCMP4Writer = (AF_MP4Writer *)hMP4File;
    return pCMP4Writer->Create(strName, nEncType, nWidth, nHeight, nFps);
}

LIB_LOCAL int MP4WriterSaveFile(AFMP4Handle hMP4File)
{
    AF_MP4Writer *pCMP4Writer = (AF_MP4Writer *)hMP4File;
    return pCMP4Writer->Save();
}

LIB_LOCAL int MP4WriterWriteFile(AFMP4Handle hMP4File, unsigned char *pData, int nSize, bool bKey, long nTimeStamp)
{
    AF_MP4Writer *pCMP4Writer = (AF_MP4Writer *)hMP4File;

    return pCMP4Writer->Write(pData, nSize, bKey, nTimeStamp);
}

LIB_LOCAL int MP4WriterWriteAudioFile(AFMP4Handle hMP4File, unsigned char *pData, int nSize, long nTimeStamp, int nSampleRate, int nChannel, int nBitsPerSample)
{
	AF_MP4Writer *pCMP4Writer = (AF_MP4Writer *)hMP4File;

	return pCMP4Writer->WriteAudio(pData, nSize, nTimeStamp, nSampleRate, nChannel, nBitsPerSample);
}
