/***************************************************************************************
 *
 *  IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
 *
 *  By downloading, copying, installing or using the software you agree to this license.
 *  If you do not agree to this license, do not download, install, 
 *  copy or use the software.
 *
 *  Copyright (C) 2014-2024, Happytimesoft Corporation, all rights reserved.
 *
 *  Redistribution and use in binary forms, with or without modification, are permitted.
 *
 *  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.
 *
****************************************************************************************/

#include "file_player.h"
#include "utils.h"
#include "media_codec.h"


void * readThread(void * argv)
{
    CFilePlayer * pPlayer = (CFilePlayer *) argv;

    pPlayer->readThread();
    
    return NULL;
}

void * videoThread(void * argv)
{
    CFilePlayer * pPlayer = (CFilePlayer *) argv;

    pPlayer->videoThread();
    
    return NULL;
}

void * audioThread(void * argv)
{
    CFilePlayer * pPlayer = (CFilePlayer *) argv;

    pPlayer->audioThread();
    
    return NULL;
}


CFilePlayer::CFilePlayer()
: CVideoPlayer()
, m_nAudioIndex(-1)
, m_nVideoIndex(-1)
, m_nDuration(0)
, m_nCurPos(0)
, m_bSeek(0)
, m_dSeekPos(0)
, m_nNalLength(0)
, m_pFormatContext(NULL)
, m_hReadThread(0)
, m_hVideoThread(0)
, m_hAudioThread(0)
, m_pVideoQueue(NULL)
, m_pAudioQueue(NULL)
{
    
}

CFilePlayer::~CFilePlayer()
{
    close();
}

BOOL CFilePlayer::openFile(const char * filename)
{
    if (avformat_open_input(&m_pFormatContext, filename, NULL, NULL) != 0)
	{
		log_print(HT_LOG_ERR, "avformat_open_input failed, %s\r\n", filename);
		return FALSE;
	}

	avformat_find_stream_info(m_pFormatContext, NULL);

    if (m_pFormatContext->duration != AV_NOPTS_VALUE)
    {
        m_nDuration = m_pFormatContext->duration; 
    }
    
	// find audio & video stream index
	for (uint32 i=0; i < m_pFormatContext->nb_streams; i++)
	{
		if (m_pFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
		{
			m_nVideoIndex = i;

			if (m_nDuration < m_pFormatContext->streams[i]->duration) 
            {
                m_nDuration = m_pFormatContext->streams[i]->duration;
            }
		}
		else if (m_pFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
		{
			m_nAudioIndex = i;

			if (m_nDuration < m_pFormatContext->streams[i]->duration) 
            {
                m_nDuration = m_pFormatContext->streams[i]->duration;
            }
		}
	}

    m_nDuration /= 1000; // to millisecond
    
	// has video stream
	if (m_nVideoIndex != -1)
	{
	    AVCodecParameters * codecpar = m_pFormatContext->streams[m_nVideoIndex]->codecpar;
	    
		if (codecpar->codec_id == AV_CODEC_ID_H264)
    	{
    		if (codecpar->extradata && codecpar->extradata_size > 8)
    		{
    		    if (codecpar->extradata[0] == 1)
    		    {
    		        // Store right nal length size that will be used to parse all other nals
                    m_nNalLength = (codecpar->extradata[4] & 0x03) + 1;
    		    }
    		}

    		const AVBitStreamFilter * bsfc = av_bsf_get_by_name("h264_mp4toannexb");
    		if (bsfc) 
    		{
    			int ret;
    			
    			AVBSFContext *bsf;
    			av_bsf_alloc(bsfc, &bsf);

    			ret = avcodec_parameters_copy(bsf->par_in, codecpar);
    	        if (ret < 0)
    	        {
    	           return FALSE;
    			}
    			
    	        ret = av_bsf_init(bsf);
    	        if (ret < 0)
    	        {
    	            return FALSE;
    			}
    			
    	        ret = avcodec_parameters_copy(codecpar, bsf->par_out);
    	        if (ret < 0)
    	        {
    	            return FALSE;
    			}
    			
    			av_bsf_free(&bsf);	            
    		}
    	}
    	else if (codecpar->codec_id == AV_CODEC_ID_HEVC)
    	{
    	    if (codecpar->extradata && codecpar->extradata_size > 8)
    		{
    		    if (codecpar->extradata[0] || codecpar->extradata[1] || codecpar->extradata[2] > 1)
    		    {
                    m_nNalLength = 4;
    		    }
    		}

    		const AVBitStreamFilter * bsfc = av_bsf_get_by_name("hevc_mp4toannexb");
    		if (bsfc) 
    		{
    			int ret;

    			AVBSFContext *bsf;
    			av_bsf_alloc(bsfc, &bsf);

    			ret = avcodec_parameters_copy(bsf->par_in, codecpar);
    	        if (ret < 0)
    	        {
    	           return FALSE;
    			}
    			
    	        ret = av_bsf_init(bsf);
    	        if (ret < 0)
    	        {
    	            return FALSE;
    			}
    			
    	        ret = avcodec_parameters_copy(codecpar, bsf->par_out);
    	        if (ret < 0)
    	        {
    	            return FALSE;
    			}
    			
    			av_bsf_free(&bsf);	            
    		}
    	}
	}
    
	return TRUE;
}

BOOL CFilePlayer::open(std::string fileName)
{
    BOOL ret = FALSE;
    close();
    CVideoPlayer::open(fileName);    
    ret = openFile(fileName.c_str());
    if (ret)
    {
        if (m_nVideoIndex >= 0)
        {
            AVCodecParameters * codecpar = m_pFormatContext->streams[m_nVideoIndex]->codecpar;
            
            openVideo(codecpar->codec_id, codecpar->extradata, codecpar->extradata_size);
        }

        if (m_nAudioIndex >= 0)
        {
            AVCodecParameters * codecpar = m_pFormatContext->streams[m_nAudioIndex]->codecpar;
            
            openAudio(codecpar->codec_id, codecpar->sample_rate, codecpar->channels, codecpar->bits_per_coded_sample);
        }
    }
    return ret;
}

void CFilePlayer::close()
{
    m_bPlaying = FALSE;

    HTPACKET packet;
	memset(&packet, 0, sizeof(packet));

    clearQueue(m_pAudioQueue);
    clearQueue(m_pVideoQueue);
    
	hqBufPut(m_pAudioQueue, (char *)&packet);
	hqBufPut(m_pVideoQueue, (char *)&packet);

	while (m_hAudioThread)
	{
	    usleep(100*1000);
	}

	while (m_hVideoThread)
	{
	    usleep(100*1000);
	}
	
    while (m_hReadThread)
    {
        usleep(100*1000);
    }

    clearQueue(m_pAudioQueue);
    clearQueue(m_pVideoQueue);

    hqDelete(m_pAudioQueue);
    m_pAudioQueue = NULL;

    hqDelete(m_pVideoQueue);
    m_pVideoQueue = NULL;
    
    if (m_pFormatContext)	
	{
		avformat_close_input(&m_pFormatContext);
	}

	CVideoPlayer::close();
}

BOOL CFilePlayer::play()
{
    m_bPaused = FALSE;
    if (m_bPlaying)
    {
        return TRUE;
    }
    
    m_bPlaying = TRUE;

    if (m_nVideoIndex >= 0)
    {
        m_pVideoQueue = hqCreate(30, sizeof(HTPACKET), HQ_PUT_WAIT | HQ_GET_WAIT);

        m_hVideoThread = sys_os_create_thread((void *)::videoThread, this);

        AVCodecParameters * codecpar = m_pFormatContext->streams[m_nVideoIndex]->codecpar;

        if (codecpar->codec_id == AV_CODEC_ID_H264 || codecpar->codec_id == AV_CODEC_ID_HEVC)
    	{
    		if (codecpar->extradata && codecpar->extradata_size > 8)
    		{
    		    playVideo(codecpar->extradata, codecpar->extradata_size, 0, 0);
    		}
    	}	
    }

    if (m_nAudioIndex >= 0)
    {
        m_pAudioQueue = hqCreate(30, sizeof(HTPACKET), HQ_PUT_WAIT | HQ_GET_WAIT);

        m_hAudioThread = sys_os_create_thread((void *)::audioThread, this);
    }

    m_hReadThread = sys_os_create_thread((void *)::readThread, this);

    // Start the video decoder thread — required for getImage() to return frames.
    // Without this, the decoder stays stopped and g_frameQueue remains empty.
    // (Matches CRtspPlayer::play() which calls StartVideoDecoder() after rtsp_start().)
    CVideoPlayer::StartVideoDecoder();

    return TRUE;
}

void CFilePlayer::stop()
{
    close();
}

BOOL CFilePlayer::pause()
{
 
    m_bPaused = TRUE;
    return m_bPaused;
}

BOOL CFilePlayer::seek(int pos)
{
    if (pos < 0 || pos > 100)
    {
        return FALSE;
    }

    m_bSeek = 1;
    m_dSeekPos = m_nDuration / 100 * pos;

    return TRUE;
}
void  CFilePlayer::setBbox(cv::Rect bbox) {
    CVideoPlayer::setBbox(bbox);
}
void  CFilePlayer::setCrop(bool crop) {
    CVideoPlayer::setCrop(crop);
}

int CFilePlayer::getVideoCodec()
{
    int codec = VIDEO_CODEC_NONE;
    
    if (m_nVideoIndex >= 0 && m_pFormatContext)
    {
        AVCodecParameters * codecpar = m_pFormatContext->streams[m_nVideoIndex]->codecpar;
	    
		codec = to_video_codec(codecpar->codec_id);
    }
    
    return codec;
}

int CFilePlayer::getAudioCodec()
{
    int codec = AUDIO_CODEC_NONE;
    
    if (m_nAudioIndex >= 0 && m_pFormatContext)
    {
        AVCodecParameters * codecpar = m_pFormatContext->streams[m_nAudioIndex]->codecpar;
	    
		codec = to_audio_codec(codecpar->codec_id);
    }
    
    return codec;
}

void CFilePlayer::videoData(uint8 * data, int size, int64 pts, int waitnext)
{
    HTPACKET packet;

    packet.data = (uint8 *) malloc(size);
    if (packet.data)
    {
        memcpy(packet.data, data, size);
        packet.size = size;
        packet.ts = pts;
        packet.waitnext = waitnext;

        if (!hqBufPut(m_pVideoQueue, (char *)&packet))
        {
            free(packet.data);
        }
    }
}

void CFilePlayer::audioData(uint8 * data, int size, int64 pts)
{
    HTPACKET packet;

    packet.data = (uint8 *) malloc(size);
    if (packet.data)
    {
        memcpy(packet.data, data, size);
        packet.size = size;
        packet.ts = pts;
        packet.waitnext = 0;

        if (!hqBufPut(m_pAudioQueue, (char *)&packet))
        {
            free(packet.data);
        }
    }
}

BOOL CFilePlayer::readFrame()
{
    int rret = 0;
	AVPacket pkt;

    if (NULL == m_pFormatContext)
    {
        return FALSE;
    }
	
	av_init_packet(&pkt);
	pkt.data = 0;
	pkt.size = 0;

	rret = av_read_frame(m_pFormatContext, &pkt);
    if (AVERROR_EOF == rret)
    {
		rret = av_seek_frame(m_pFormatContext, 0, m_pFormatContext->streams[0]->start_time, 0);
        if (rret < 0)
        {
            rret = av_seek_frame(m_pFormatContext, 0, 0, AVSEEK_FLAG_BYTE | AVSEEK_FLAG_BACKWARD);
            if (rret < 0)
            {
                return FALSE;
            }
        }
        
        if (av_read_frame(m_pFormatContext, &pkt) != 0)
        {
            return FALSE;
        }
    }	
	else if (0 != rret)
	{
		return FALSE;
	}

    int64 pts = AV_NOPTS_VALUE;
    
	if (pkt.pts != AV_NOPTS_VALUE)
	{
		pts = pkt.pts;

		if (m_pFormatContext->start_time != AV_NOPTS_VALUE)
        {
            pts -= m_pFormatContext->start_time;
        }
	}
	else if (pkt.dts != AV_NOPTS_VALUE)
	{
	    pts = pkt.dts;

		if (m_pFormatContext->start_time != AV_NOPTS_VALUE)
        {
            pts -= m_pFormatContext->start_time;
        }
    }
		
	if (pkt.stream_index == m_nVideoIndex)
	{
	    AVCodecParameters * codecpar = m_pFormatContext->streams[m_nVideoIndex]->codecpar;
	    
		if (codecpar->codec_id == AV_CODEC_ID_H264 || codecpar->codec_id == AV_CODEC_ID_HEVC)
		{
            if (m_nNalLength)
            {
                uint8 * data = pkt.data;
                int size = pkt.size;
                
                while (pkt.size >= m_nNalLength)
				{
					int len = 0;
					int nal_length = m_nNalLength;
					uint8 * pdata = pkt.data;
					
					while (nal_length--)
					{
                        len = (len << 8) | *pdata++;
    				}
    				
					if (len > pkt.size - m_nNalLength || len <= 0)
					{
					    log_print(HT_LOG_DBG, "len=%d, pkt.size=%d\r\n", len, pkt.size);
						break;
					}

					nal_length = m_nNalLength;
					pkt.data[nal_length-1] = 1;
					nal_length--;
					while (nal_length--)
					{
					    pkt.data[nal_length] = 0;
					}
                    
					pkt.data += len + m_nNalLength;
					pkt.size -= len + m_nNalLength;
				}	

				videoData(data, size, pts, 1);
            }                
			else if (pkt.data[0] == 0 && pkt.data[1] == 0 && pkt.data[2] == 0 && pkt.data[3] == 1)
			{
			    videoData(pkt.data, pkt.size, pts, 1);
			}
			else if (pkt.data[0] == 0 && pkt.data[1] == 0 && pkt.data[2] == 1)
			{
			    videoData(pkt.data, pkt.size, pts, 1);
			}
			else 
			{
				log_print(HT_LOG_ERR, "%s, unknown format\r\n", __FUNCTION__);
			}
		}
		else
		{
		    videoData(pkt.data, pkt.size, pts, 1);
		}
	}
	else if (pkt.stream_index == m_nAudioIndex)
	{
        audioData(pkt.data, pkt.size, pts);
	}

	av_packet_unref(&pkt);

	return TRUE;
}

void CFilePlayer::readThread()
{
    while (m_bPlaying)
    {
        if (m_bPaused)
        {
            usleep(100*1000);
            continue;
        }

        if (m_bSeek)
        {
            if (seekStream(m_dSeekPos))
            {
                clearQueue(m_pVideoQueue);
                clearQueue(m_pAudioQueue);
            }
            
            m_bSeek = 0;
        }

        if (!readFrame())
        {
            break;
        }
    }

    log_print(HT_LOG_INFO, "%s, exit!\r\n", __FUNCTION__);

    m_hReadThread = 0;
}

void CFilePlayer::videoThread()
{
    HTPACKET packet;
    int64 pts = 0;
    int64 cur_delay = 0;
	int64 pre_delay = 0;
	uint32 cur_time = 0;
	uint32 pre_time = 0;
	int timeout = 1000000.0 / getFramerate(); 
    while (m_bPlaying)
    {
        if (m_bPaused)
        {
            pre_time = 0;
            usleep(100*1000);
            continue;
        }

        if (hqBufGet(m_pVideoQueue, (char *)&packet))
        {
            if (NULL == packet.data || 0 == packet.size)
            {
                break;
            }

            if (packet.ts != AV_NOPTS_VALUE)
    		{
    		    AVRational q = {1, AV_TIME_BASE};
    			pts = av_rescale_q (packet.ts, m_pFormatContext->streams[m_nVideoIndex]->time_base, q);
                pts /= 1000;

                m_nCurPos = pts;
    		}
    		
            playVideo(packet.data, packet.size, pts, 0);

            free(packet.data);

            if (packet.waitnext)
            {
                cur_time = sys_os_get_ms();
    		    cur_delay = timeout;

    		    if (pre_time > 0)
    		    {
    		        cur_delay += pre_delay - (cur_time - pre_time) * 1000;
    		        if (cur_delay < 1000)
    		        {
    		            cur_delay = 0;
    		        }
    		    }

    		    pre_time = cur_time;
    		    pre_delay = cur_delay;
    		    
    		    if (cur_delay > 0)
    		    {
    			    usleep(cur_delay);
    			}
			}
        }
    }
    log_print(HT_LOG_INFO, "%s, exit!\r\n", __FUNCTION__);
    m_hVideoThread = 0;
}

void CFilePlayer::audioThread()
{
    HTPACKET packet;
    int64 pts = 0;
    
    while (m_bPlaying)
    {
        if (m_bPaused)
        {
            usleep(100*1000);
            continue;
        }

        if (hqBufGet(m_pAudioQueue, (char *)&packet))
        {
            if (NULL == packet.data || 0 == packet.size)
            {
                break;
            }
            
            if (packet.ts != AV_NOPTS_VALUE)
    		{
    		    AVRational q = {1, AV_TIME_BASE};
    			pts = av_rescale_q (packet.ts, m_pFormatContext->streams[m_nAudioIndex]->time_base, q);
                pts /= 1000;

                m_nCurPos = pts;
    		}
		
            playAudio(packet.data, packet.size, pts, 0);

            free(packet.data);
        }
    }

    log_print(HT_LOG_INFO, "%s, exit!\r\n", __FUNCTION__);

    m_hAudioThread = 0;
}

void CFilePlayer::clearQueue(HQUEUE * queue)
{
	HTPACKET packet;
	
	while (!hqBufIsEmpty(queue))
	{
		if (hqBufGet(queue, (char *)&packet))
		{
			if (packet.data != NULL && packet.size != 0)
			{
				free(packet.data);
			}
		}
		else
		{
		    // should be not to here
		    log_print(HT_LOG_ERR, "%s, hqBufGet failed\r\n", __FUNCTION__);
		    break;
		}
	}
}

BOOL CFilePlayer::seekStream(double pos)
{
    if (pos < 0)
    {
        return FALSE;
    }

    if (pos == m_nCurPos)
    {
    	return TRUE;
    }

    int stream = -1;
    int64 seekpos = pos * 1000;

    if (m_nAudioIndex >= 0)
    {
        stream = m_nAudioIndex;
    }
    else if (m_nVideoIndex >= 0)
    {
        stream = m_nVideoIndex;
    }
    
    if (m_pFormatContext->start_time != AV_NOPTS_VALUE)
    {
        seekpos += m_pFormatContext->start_time;
    }

    if (stream >= 0)
    {
        AVRational q = {1, AV_TIME_BASE}; 
        
        seekpos = av_rescale_q(seekpos, q, m_pFormatContext->streams[stream]->time_base);
    }

    if (av_seek_frame(m_pFormatContext, stream, seekpos, AVSEEK_FLAG_BACKWARD) < 0)
    { 
        return FALSE;
    }

    // Accurate seek to the specified position
    AVPacket pkt;
	
	av_init_packet(&pkt);
	pkt.data = 0;
	pkt.size = 0;
	
    while (av_read_frame(m_pFormatContext, &pkt) == 0)
    {
		if (pkt.stream_index != stream)
		{
			av_packet_unref(&pkt);
			continue;
		}
		
        if (pkt.pts != AV_NOPTS_VALUE)
		{
			if (pkt.pts < seekpos)
			{
				av_packet_unref(&pkt);
				continue;
			}
			else
			{
			    break;
			}
		}
		else if (pkt.dts != AV_NOPTS_VALUE)
		{
			if (pkt.dts < seekpos)
			{
				av_packet_unref(&pkt);
				continue;
			}
			else
			{
			    break;
			}
		}
		else
		{
			break;
		}
        
        av_packet_unref(&pkt);
    }

	av_packet_unref(&pkt);
	
    m_nCurPos = pos;

    return TRUE;
}

double CFilePlayer::getFramerate() 
{
	double framerate = 25;
	
	if (m_nVideoIndex != -1)
	{
		if (m_pFormatContext->streams[m_nVideoIndex]->r_frame_rate.den > 0)
		{
			framerate = m_pFormatContext->streams[m_nVideoIndex]->r_frame_rate.num / 
						(double)m_pFormatContext->streams[m_nVideoIndex]->r_frame_rate.den;
		}

		if ((framerate < 1 || framerate > 60) && 
		    m_pFormatContext->streams[m_nVideoIndex]->avg_frame_rate.den > 0)
		{
		    framerate = m_pFormatContext->streams[m_nVideoIndex]->avg_frame_rate.num / 
						(double)m_pFormatContext->streams[m_nVideoIndex]->avg_frame_rate.den;
		}

		if (framerate < 1 || framerate > 60)
		{
		    framerate = 25;
		}
	}

	return framerate;
}

BOOL CFilePlayer::onRecord()
{
    AVICTX * p_avictx = m_pAviCtx;
    
    int vcodec = getVideoCodec();	
    int v_extra_len = 0;
    uint8 * v_extra = NULL;
    
    if (VIDEO_CODEC_H264 == vcodec)
    {
        AVCodecParameters * codecpar = m_pFormatContext->streams[m_nVideoIndex]->codecpar;

        v_extra = codecpar->extradata;
        v_extra_len = codecpar->extradata_size;

        avi_set_video_info(p_avictx, 0, 0, 0, "H264");

	    avi_set_video_extra_info(p_avictx, codecpar->extradata, codecpar->extradata_size);
    }
    else if (VIDEO_CODEC_H265 == vcodec)
    {
        AVCodecParameters * codecpar = m_pFormatContext->streams[m_nVideoIndex]->codecpar;

        v_extra = codecpar->extradata;
        v_extra_len = codecpar->extradata_size;

        avi_set_video_info(p_avictx, 0, 0, 0, "H265");

	    avi_set_video_extra_info(p_avictx, codecpar->extradata, codecpar->extradata_size);
    }
    else if (VIDEO_CODEC_JPEG == vcodec)
    {
        avi_set_video_info(p_avictx, 0, 0, 0, "JPEG");
    }
    else if (VIDEO_CODEC_MP4 == vcodec)
    {
        avi_set_video_info(p_avictx, 0, 0, 0, "MP4V");
    }

    int acodec = getAudioCodec();
    int sr = 0;
    int ch = 0;

    if (m_nAudioIndex >= 0 && m_pFormatContext)
    {
        AVCodecParameters * codecpar = m_pFormatContext->streams[m_nAudioIndex]->codecpar;
	    
		sr = codecpar->sample_rate;
		ch = codecpar->channels;
    }
    
    if (AUDIO_CODEC_G711A == acodec)
    {
        avi_set_audio_info(p_avictx, ch, sr, AUDIO_FORMAT_ALAW);
    }
    else if (AUDIO_CODEC_G711U == acodec)
    {
        avi_set_audio_info(p_avictx, ch, sr, AUDIO_FORMAT_MULAW);
    }
    else if (AUDIO_CODEC_G726 == acodec)
    {
        avi_set_audio_info(p_avictx, ch, sr, AUDIO_FORMAT_G726);
    }
    else if (AUDIO_CODEC_G722 == acodec)
    {
        avi_set_audio_info(p_avictx, ch, sr, AUDIO_FORMAT_G722);
    }
    else if (AUDIO_CODEC_AAC == acodec)
    {
        AVCodecParameters * codecpar = m_pFormatContext->streams[m_nAudioIndex]->codecpar;
        
        avi_set_audio_info(p_avictx, ch, sr, AUDIO_FORMAT_AAC);
        avi_set_audio_extra_info(p_avictx, codecpar->extradata, codecpar->extradata_size);
    }

    avi_update_header(p_avictx);

    if (p_avictx->ctxf_video)
    {
        if (v_extra && v_extra_len > 0)
        {
            recordVideo(v_extra, v_extra_len, 0, 0);
        }
    }
    
	return TRUE;
}



