/*************************************************************************************** * * 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; }