/*************************************************************************************** * * 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 "sys_inc.h" #include "video_player.h" #include "utils.h" #include "media_util.h" #include "media_parse.h" #include "media_codec.h" #include "h264.h" #include "h265.h" extern "C" { #include #include #include #include #include #include #include #include } #if __WINDOWS_OS__ #include "video_render_d3d.h" #include "video_render_gdi.h" #include "audio_play_win.h" #elif defined(IOS) #include "video_render_sdl.h" #include "audio_play_mac.h" #include #elif __LINUX_OS__ #include "video_render_sdl.h" #include "audio_play_qt.h" #endif void VideoDecoderCallback(AVFrame * frame, void * userdata) { CVideoPlayer * pPlayer = (CVideoPlayer *) userdata; pPlayer->onVideoFrame(frame); } void AudioDecoderCallback(AVFrame * frame, void * userdata) { CVideoPlayer * pPlayer = (CVideoPlayer *) userdata; pPlayer->onAudioFrame(frame); } void * AudioPlayThread(void * argv) { CVideoPlayer * pPlayer = (CVideoPlayer *) argv; pPlayer->audioPlayThread(); return NULL; } void * VideoPlayThread(void * argv) { CVideoPlayer * pPlayer = (CVideoPlayer *) argv; pPlayer->videoPlayThread(); return NULL; } CVideoPlayer::CVideoPlayer(QObject * parent) : QObject(parent) , m_bVideoInited(FALSE) , m_bAudioInited(FALSE) , m_pVideoDecoder(NULL) , m_pAudioDecoder(NULL) , m_pVideoRender(NULL) , m_pAudioPlay(NULL) , m_bPlaying(FALSE) , m_bPaused(FALSE) , m_nVideoWnd(0) , m_bSizeChanged(FALSE) , m_nRenderMode(RENDER_MODE_KEEP) , m_nHWDecoding(HW_DECODING_AUTO) , m_nDstVideoFmt(AV_PIX_FMT_YUV420P) , m_bUpdown(FALSE) , m_bSnapshot(FALSE) , m_nSnapVideoFmt(VIDEO_FMT_BGR24) , m_nVideoCodec(VIDEO_CODEC_NONE) , m_nAudioCodec(AUDIO_CODEC_NONE) , m_nSampleRate(0) , m_nChannel(0) , m_nBitPerSample(0) , m_pSnapFrame(NULL) , m_pRenderFrame(NULL) , m_bRecording(FALSE) , m_bNalFlag(FALSE) , m_pAviCtx(NULL) , m_pAudioListMutex(NULL) , m_audioPlayFlag(FALSE) , m_audioPlayThread(0) , m_pVideoListMutex(NULL) , m_videoPlayFlag(FALSE) , m_videoPlayThread(0) , m_nLastAudioPts(AV_NOPTS_VALUE) , m_lastAudioTS(0) { m_pRecordMutex = sys_os_create_mutex(); memset(&m_h26XParamSets, 0, sizeof(H26XParamSets)); memset(&m_audioClock, 0, sizeof(HTCLOCK)); memset(&m_videoClock, 0, sizeof(HTCLOCK)); } CVideoPlayer::~CVideoPlayer() { close(); } BOOL CVideoPlayer::open(QString fileName, WId hWnd) { m_sFileName = fileName; m_nVideoWnd = hWnd; return TRUE; } void CVideoPlayer::close() { closeVideo(); closeAudio(); if (m_pSnapFrame) { av_frame_free(&m_pSnapFrame); } if (m_pRenderFrame) { av_frame_free(&m_pRenderFrame); } stopRecord(); sys_os_destroy_sig_mutex(m_pRecordMutex); m_pRecordMutex = NULL; } void CVideoPlayer::setVolume(int volume) { if (m_pAudioPlay) { m_pAudioPlay->setVolume(volume); } } void CVideoPlayer::snapshot(int videofmt) { m_bSnapshot = TRUE; m_nSnapVideoFmt = videofmt; } BOOL CVideoPlayer::record(QString baseName) { if (m_bRecording) { return TRUE; } QString path = getRecordPath(); QString file = path + "/" + getTempFile(baseName, ".avi"); m_sBaseName = baseName; m_pAviCtx = avi_write_open(file.toLocal8Bit().toStdString().c_str()); if (NULL == m_pAviCtx) { log_print(HT_LOG_ERR, "%s, avi_write_open failed. %s\r\n", __FUNCTION__, file.toLocal8Bit().toStdString().c_str()); return FALSE; } if (!onRecord()) { avi_write_close(m_pAviCtx); m_pAviCtx = NULL; return FALSE; } m_bRecording = TRUE; return m_bRecording; } void CVideoPlayer::stopRecord() { sys_os_mutex_enter(m_pRecordMutex); m_bRecording = FALSE; m_bNalFlag = FALSE; memset(&m_h26XParamSets, 0, sizeof(H26XParamSets)); if (m_pAviCtx) { avi_write_close(m_pAviCtx); m_pAviCtx = NULL; } sys_os_mutex_leave(m_pRecordMutex); } void CVideoPlayer::recordVideo(uint8 * data, int len, uint32 ts, uint16 seq) { int codec = VIDEO_CODEC_NONE; if (!memcmp(m_pAviCtx->v_fcc, "H264", 4)) { codec = VIDEO_CODEC_H264; } else if (!memcmp(m_pAviCtx->v_fcc, "H265", 4)) { codec = VIDEO_CODEC_H265; } if ((VIDEO_CODEC_H264 == codec || VIDEO_CODEC_H265 == codec) && !m_bNalFlag) { if (avc_get_h26x_paramsets(data, len, codec, &m_h26XParamSets)) { avi_write_nalu(m_pAviCtx, m_h26XParamSets.vps, m_h26XParamSets.vps_size, m_h26XParamSets.sps, m_h26XParamSets.sps_size, m_h26XParamSets.pps, m_h26XParamSets.pps_size); m_bNalFlag = 1; } } recordVideoEx(data, len, ts, seq); if (recordSwitchCheck()) { recordFileSwitch(); } } void CVideoPlayer::recordVideoEx(uint8 * data, int len, uint32 ts, uint16 seq) { AVICTX * p_avictx = m_pAviCtx; if (p_avictx->v_width == 0 || p_avictx->v_height == 0) { int codec = VIDEO_CODEC_NONE; if (memcmp(p_avictx->v_fcc, "H264", 4) == 0) { codec = VIDEO_CODEC_H264; } else if (memcmp(p_avictx->v_fcc, "H265", 4) == 0) { codec = VIDEO_CODEC_H265; } else if (memcmp(p_avictx->v_fcc, "JPEG", 4) == 0) { codec = VIDEO_CODEC_JPEG; } else if (memcmp(p_avictx->v_fcc, "MP4V", 4) == 0) { codec = VIDEO_CODEC_MP4; } avc_parse_video_size(codec, data, len, &p_avictx->v_width, &p_avictx->v_height); if (p_avictx->v_width && p_avictx->v_height) { avi_update_header(p_avictx); } } int key = 0; if (memcmp(p_avictx->v_fcc, "H264", 4) == 0) { uint8 nalu_t = (data[4] & 0x1F); key = (nalu_t == 5 || nalu_t == 7 || nalu_t == 8); } else if (memcmp(p_avictx->v_fcc, "H265", 4) == 0) { uint8 nalu_t = (data[4] >> 1) & 0x3F; key = ((nalu_t >= 16 && nalu_t <= 21) || nalu_t == 32 || nalu_t == 33 || nalu_t == 34); } else if (memcmp(p_avictx->v_fcc, "MP4V", 4) == 0) { key = 1; } else if (memcmp(p_avictx->v_fcc, "JPEG", 4) == 0) { key = 1; } avi_write_video(p_avictx, data, len, ts, key); } void CVideoPlayer::recordAudio(uint8 * data, int len, uint32 ts, uint16 seq) { AVICTX * p_avictx = m_pAviCtx; avi_write_audio(p_avictx, data, len, ts); if (recordSwitchCheck()) { recordFileSwitch(); } } BOOL CVideoPlayer::recordSwitchCheck() { uint64 tlen = avi_get_file_length(m_pAviCtx); uint32 mtime = avi_get_media_time(m_pAviCtx); uint32 recordSize = getRecordSize(); if (recordSize == 0) { recordSize = 1048576; // max 1G file size } // Switch according to the recording size if (tlen > recordSize * 1024) { return TRUE; } uint32 recordTime = getRecordTime(); // Switch according to the recording duration if (recordTime > 0 && mtime > recordTime * 1000) { return TRUE; } return FALSE; } void CVideoPlayer::recordFileSwitch() { AVICTX * p_ctx; AVICTX * p_oldctx = m_pAviCtx; QString path = getRecordPath(); QString file = path + "/" + getTempFile(m_sBaseName, ".avi"); p_ctx = avi_write_open(file.toLocal8Bit().toStdString().c_str()); if (NULL == p_ctx) { return; } p_ctx->ctxf_video = p_oldctx->ctxf_video; p_ctx->ctxf_audio = p_oldctx->ctxf_audio; if (p_ctx->ctxf_video) { avi_calc_fps(p_oldctx); avi_set_video_info(p_ctx, p_oldctx->v_fps, p_oldctx->v_width, p_oldctx->v_height, p_oldctx->v_fcc); avi_set_video_extra_info(p_ctx, p_oldctx->v_extra, p_oldctx->v_extra_len); } if (p_ctx->ctxf_audio) { avi_set_audio_info(p_ctx, p_oldctx->a_chns, p_oldctx->a_rate, p_oldctx->a_fmt); avi_set_audio_extra_info(p_ctx, p_oldctx->a_extra, p_oldctx->a_extra_len); } avi_write_close(p_oldctx); avi_update_header(p_ctx); m_pAviCtx = p_ctx; if (m_h26XParamSets.vps_size > 0 || m_h26XParamSets.sps_size > 0 || m_h26XParamSets.pps_size > 0) { avi_write_nalu(m_pAviCtx, m_h26XParamSets.vps, m_h26XParamSets.vps_size, m_h26XParamSets.sps, m_h26XParamSets.sps_size, m_h26XParamSets.pps, m_h26XParamSets.pps_size); } } BOOL CVideoPlayer::openVideo(enum AVCodecID codec, uint8 * extradata, int extradata_size) { if (m_bVideoInited) { return TRUE; } m_pVideoDecoder = new CVideoDecoder(); if (m_pVideoDecoder) { m_bVideoInited = m_pVideoDecoder->init(codec, extradata, extradata_size, m_nHWDecoding, m_nPreferredGpu); } if (m_bVideoInited) { m_pVideoDecoder->setCallback(VideoDecoderCallback, this); m_pVideoListMutex = sys_os_create_mutex(); m_videoPlayFlag = TRUE; m_videoPlayThread = sys_os_create_thread((void *)VideoPlayThread, this); } m_nVideoCodec = to_video_codec(codec); return m_bVideoInited; } BOOL CVideoPlayer::openVideo(int codec, uint8 * extradata, int extradata_size) { return openVideo(to_video_avcodecid(codec), extradata, extradata_size); } void CVideoPlayer::closeVideo() { m_videoPlayFlag = FALSE; // Wait for the video playback thread to exit while (m_videoPlayThread) { usleep(10*1000); } if (m_pVideoDecoder) { delete m_pVideoDecoder; m_pVideoDecoder = NULL; } if (m_pVideoRender) { delete m_pVideoRender; m_pVideoRender = NULL; } // Released video frame while (m_videoFrameList.size() > 0) { AVFrame * pFrame = m_videoFrameList.front(); m_videoFrameList.pop_front(); av_frame_free(&pFrame); } if (m_pVideoListMutex) { sys_os_destroy_sig_mutex(m_pVideoListMutex); m_pVideoListMutex = NULL; } m_bVideoInited = FALSE; } BOOL CVideoPlayer::openAudio(enum AVCodecID codec, int samplerate, int channels, int bitpersample) { if (m_bAudioInited) { return TRUE; } m_pAudioDecoder = new CAudioDecoder(); if (m_pAudioDecoder) { m_bAudioInited = m_pAudioDecoder->init(codec, samplerate, channels, bitpersample); } if (m_bAudioInited) { m_pAudioDecoder->setCallback(AudioDecoderCallback, this); #if __WINDOWS_OS__ m_pAudioPlay = new CWAudioPlay(); #elif defined(IOS) m_pAudioPlay = new CMAudioPlay(); #elif __LINUX_OS__ m_pAudioPlay = new CQAudioPlay(); #endif if (m_pAudioPlay) { m_pAudioPlay->startPlay(samplerate, channels); } m_pAudioListMutex = sys_os_create_mutex(); m_audioPlayFlag = TRUE; m_audioPlayThread = sys_os_create_thread((void *)AudioPlayThread, this); } m_nAudioCodec = to_audio_codec(codec); m_nSampleRate = samplerate; m_nChannel = channels; m_nBitPerSample = bitpersample; return m_bAudioInited; } BOOL CVideoPlayer::openAudio(int codec, int samplerate, int channels, int bitpersample) { return openAudio(to_audio_avcodecid(codec), samplerate, channels, bitpersample); } void CVideoPlayer::closeAudio() { m_audioPlayFlag = FALSE; // Wait for the audio playback thread to exit while (m_audioPlayThread) { usleep(10*1000); } if (m_pAudioDecoder) { delete m_pAudioDecoder; m_pAudioDecoder = NULL; } if (m_pAudioPlay) { delete m_pAudioPlay; m_pAudioPlay = NULL; } // Released audio frame while (m_audioFrameList.size() > 0) { AVFrame * pFrame = m_audioFrameList.front(); m_audioFrameList.pop_front(); av_frame_free(&pFrame); } if (m_pAudioListMutex) { sys_os_destroy_sig_mutex(m_pAudioListMutex); m_pAudioListMutex = NULL; } m_bAudioInited = FALSE; } void CVideoPlayer::setWindowSize(QSize size) { m_bSizeChanged = TRUE; m_size = size; } int CVideoPlayer::getVideoWidth() { if (m_pVideoDecoder) { return m_pVideoDecoder->getWidth(); } return 0; } int CVideoPlayer::getVideoHeight() { if (m_pVideoDecoder) { return m_pVideoDecoder->getHeight(); } return 0; } double CVideoPlayer::getFrameRate() { if (m_pVideoDecoder) { return m_pVideoDecoder->getFrameRate(); } return 0; } void CVideoPlayer::playVideo(uint8 * data, int len, uint32 ts, uint16 seq) { if (m_bRecording) { sys_os_mutex_enter(m_pRecordMutex); recordVideo(data, len, ts, seq); sys_os_mutex_leave(m_pRecordMutex); } updateClock(&m_videoClock, ts, getVideoClock()); if (m_bVideoInited) { m_pVideoDecoder->decode(data, len, m_videoClock.SyncTime.tv_sec * 1000000 + m_videoClock.SyncTime.tv_usec); } } void CVideoPlayer::playAudio(uint8 * data, int len, uint32 ts, uint16 seq) { if (m_bRecording) { sys_os_mutex_enter(m_pRecordMutex); recordAudio(data, len, ts, seq); sys_os_mutex_leave(m_pRecordMutex); } updateClock(&m_audioClock, ts, getAudioClock()); if (m_bAudioInited) { m_pAudioDecoder->decode(data, len, m_audioClock.SyncTime.tv_sec * 1000000 + m_audioClock.SyncTime.tv_usec); } } void CVideoPlayer::updateClock(HTCLOCK * clock, uint32 ts, int frequency) { if (ts == 0) { return; } if (clock->SyncTime.tv_sec == 0 && clock->SyncTime.tv_usec == 0) { clock->SyncTimestamp = ts; gettimeofday(&clock->SyncTime, NULL); } int timestampDiff = ts - clock->SyncTimestamp; // Divide this by the timestamp frequency to get real time: double timeDiff = timestampDiff / (double)frequency; uint32 const million = 1000000; uint32 seconds, uSeconds; if (timeDiff >= 0.0) { seconds = clock->SyncTime.tv_sec + (uint32)(timeDiff); uSeconds = clock->SyncTime.tv_usec + (uint32)((timeDiff - (uint32)timeDiff)*million); if (uSeconds >= million) { uSeconds -= million; ++seconds; } } else { timeDiff = -timeDiff; seconds = clock->SyncTime.tv_sec - (uint32)(timeDiff); uSeconds = clock->SyncTime.tv_usec - (uint32)((timeDiff - (uint32)timeDiff)*million); if ((int)uSeconds < 0) { uSeconds += million; --seconds; } } // Save these as the new synchronization timestamp & time: clock->SyncTimestamp = ts; clock->SyncTime.tv_sec = seconds; clock->SyncTime.tv_usec = uSeconds; } BOOL CVideoPlayer::initFrame(AVFrame *& frame, int width, int height, AVPixelFormat pixfmt) { if (width == 0 || height == 0 || pixfmt == AV_PIX_FMT_NONE) { return FALSE; } if (NULL == frame || frame->width != width || frame->height != height || frame->format != pixfmt) { if (frame) { av_frame_free(&frame); } frame = av_frame_alloc(); if (NULL == frame) { return FALSE; } frame->format = pixfmt; frame->width = width; frame->height = height; if (0 != av_frame_get_buffer(frame, 0)) { av_frame_free(&frame); return FALSE; } av_frame_make_writable(frame); } return TRUE; } BOOL CVideoPlayer::initVideoRender(int width, int height) { BOOL init = FALSE; #if __WINDOWS_OS__ m_pVideoRender = new CD3DVideoRender(); #elif __LINUX_OS__ m_pVideoRender = new CSDLVideoRender(); #endif if (m_pVideoRender) { init = m_pVideoRender->init(m_nVideoWnd, width, height, VIDEO_FMT_YUV420P); #ifdef IOS if (init) { m_pVideoRender->setWindowTitle(m_sFileName.toStdString().c_str()); } #endif } #if __WINDOWS_OS__ if (!init) { if (m_pVideoRender) { delete m_pVideoRender; } m_pVideoRender = new CGDIVideoRender(); if (m_pVideoRender) { init = m_pVideoRender->init(m_nVideoWnd, width, height, VIDEO_FMT_BGR24); if (init) { m_bUpdown = TRUE; m_nDstVideoFmt = to_avpixelformat(VIDEO_FMT_BGR24); log_print(HT_LOG_INFO, "%s, use GDI to render video\r\n", __FUNCTION__); } else { delete m_pVideoRender; m_pVideoRender = NULL; } } } else { log_print(HT_LOG_INFO, "%s, use D3D to render video\r\n", __FUNCTION__); } #endif return init; } BOOL CVideoPlayer::doSnapshot(AVFrame * frame) { if (!initFrame(m_pSnapFrame, frame->width, frame->height, to_avpixelformat(m_nSnapVideoFmt))) { return FALSE; } if (NULL == convertFrame(frame, m_pSnapFrame, FALSE)) { return FALSE; } emit snapshoted(m_pSnapFrame); return TRUE; } AVFrame * CVideoPlayer::convertFrame(AVFrame * srcframe, AVFrame * dstframe, BOOL updown) { if (NULL == srcframe || NULL == dstframe) { return NULL; } SwsContext * swsctx = sws_getContext(srcframe->width, srcframe->height, (enum AVPixelFormat)srcframe->format, srcframe->width, srcframe->height, (enum AVPixelFormat)dstframe->format, SWS_BICUBIC, NULL, NULL, NULL); if (swsctx) { if (updown) { srcframe->data[0] += srcframe->linesize[0] * (srcframe->height - 1); srcframe->linesize[0] *= -1; srcframe->data[1] += srcframe->linesize[1] * (srcframe->height / 2 - 1); srcframe->linesize[1] *= -1; srcframe->data[2] += srcframe->linesize[2] * (srcframe->height / 2 - 1); srcframe->linesize[2] *= -1; } int ret = sws_scale(swsctx, srcframe->data, srcframe->linesize, 0, srcframe->height, dstframe->data, dstframe->linesize); if (ret > 0) { dstframe->pts = srcframe->pts; dstframe->pkt_dts = srcframe->pkt_dts; sws_freeContext(swsctx); return dstframe; } else { log_print(HT_LOG_ERR, "%s, sws_scale failed\r\n", __FUNCTION__); sws_freeContext(swsctx); return NULL; } } return NULL; } void CVideoPlayer::onVideoFrame(AVFrame * frame) { // Perform snapshot request if (m_bSnapshot) { if (doSnapshot(frame)) { m_bSnapshot = FALSE; } } AVFrame * dst = NULL; if (m_nDstVideoFmt != frame->format) { if (initFrame(m_pRenderFrame, frame->width, frame->height, m_nDstVideoFmt)) { if (NULL != convertFrame(frame, m_pRenderFrame, m_bUpdown)) { dst = av_frame_clone(m_pRenderFrame); } } } else { dst = av_frame_clone(frame); } if (dst) { sys_os_mutex_enter(m_pVideoListMutex); if (m_videoFrameList.size() >= 10) { AVFrame * frame = m_videoFrameList.front(); if (frame) { av_frame_free(&frame); } m_videoFrameList.pop_front(); } m_videoFrameList.push_back(dst); sys_os_mutex_leave(m_pVideoListMutex); } } void CVideoPlayer::onAudioFrame(AVFrame * frame) { AVFrame * dst = av_frame_clone(frame); if (dst) { while (m_audioPlayFlag && m_audioFrameList.size() >= 10) { usleep(10*1000); } sys_os_mutex_enter(m_pAudioListMutex); m_audioFrameList.push_back(dst); sys_os_mutex_leave(m_pAudioListMutex); } } /* Audio play thread */ void CVideoPlayer::audioPlayThread() { while (m_audioPlayFlag) { AVFrame * pFrame = NULL; sys_os_mutex_enter(m_pAudioListMutex); if (m_audioFrameList.size() > 0) { pFrame = m_audioFrameList.front(); m_audioFrameList.pop_front(); } sys_os_mutex_leave(m_pAudioListMutex); if (pFrame) { // Save audio PTS and play time stamp m_lastAudioTS = sys_os_get_ms(); m_nLastAudioPts = pFrame->pts; if (m_pAudioPlay) { // Will wait for the playback to complete before returning m_pAudioPlay->playAudio(pFrame->data[0], pFrame->nb_samples * pFrame->channels * 2); } else { usleep(1000000 * pFrame->nb_samples / m_nSampleRate); } av_frame_free(&pFrame); } else { // Fill the silence data uint8 buff[1024] = {0}; m_pAudioPlay->playAudio(buff, 1024); } } m_audioPlayThread = 0; } /* Video rendering thread */ void CVideoPlayer::videoPlayThread() { int64 cur_delay = 0; int64 pre_delay = 0; uint32 cur_time = 0; uint32 pre_time = 0; AVFrame * pLastFrame = NULL; while (m_videoPlayFlag) { int size, fast = 0, slow = 0; AVFrame * pFrame = NULL; sys_os_mutex_enter(m_pVideoListMutex); size = m_videoFrameList.size(); if (size > 0) { pFrame = m_videoFrameList.front(); m_videoFrameList.pop_front(); } else { pFrame = pLastFrame; } sys_os_mutex_leave(m_pVideoListMutex); if (pFrame) { // The Mac platform needs to initialize the SDL window on the UI main thread if (NULL == m_pVideoRender) { dispatch_sync(dispatch_get_main_queue(), ^{ initVideoRender(pFrame->width, pFrame->height); }); } if (pFrame->format != m_nDstVideoFmt) { av_frame_free(&pFrame); continue; } // If there is audio, synchronize with audio if (m_nLastAudioPts != (uint64) AV_NOPTS_VALUE) { int diff = sys_os_get_ms() - m_lastAudioTS; uint64 vpts = pFrame->pts; uint64 epts = m_nLastAudioPts + diff * 1000; int ptsdiff = vpts - epts; // Video ahead of audio 200ms // If the video is 3 minutes ahead of the audio, the RTP timestamp may be incorrect, so skip it if (ptsdiff >= 200 * 1000 && ptsdiff <= 180 * 1000 * 1000) { fast = 1; } // Video is behind audio 200ms // If the video is 3 minutes behind the audio, the RTP timestamp may be inaccurate, so skip it else if (ptsdiff <= -200 * 1000 && ptsdiff >= -180 * 1000 *1000) { slow = 1; } } if (m_pVideoRender) { if (m_bSizeChanged) { dispatch_sync(dispatch_get_main_queue(), ^{ m_pVideoRender->setWindowSize(m_size); m_bSizeChanged = FALSE; }); } m_pVideoRender->render(pFrame, m_nRenderMode); } // Keep the last frame if (size > 1) { av_frame_free(&pFrame); av_frame_free(&pLastFrame); pLastFrame = NULL; } else if (pLastFrame != pFrame) { av_frame_free(&pLastFrame); pLastFrame = pFrame; } if (fast) { cur_delay = 1000/10.0 * 1000; // The rendering fps is 10 } else if (slow) { // don't sleep cur_delay = 0; } else { float fps = 30.0; if (m_pVideoDecoder) { fps = m_pVideoDecoder->getFrameRate(); if (fps == 0) { fps = 30.0; } } cur_delay = 1000/fps * 1000; // The default rendering fps } cur_time = sys_os_get_ms(); 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); } } else { usleep(1000); } } if (pLastFrame) { av_frame_free(&pLastFrame); } m_videoPlayThread = 0; }