摘要:在的過程中,不僅會啟動,而且會啟動。獲取隊列最后一幀,如果幀中的圖像正常,繼續走渲染畫面。最后通過消息通知開始渲染。這個返回的偏差值就是后面進行是否拋幀或的判斷依據。
在prepare的stream_open過程中,不僅會啟動read_thread,而且會啟動video_refresh_thread。今天就來看看這個video_refresh_thread干了什么。
static int video_refresh_thread(void *arg) { FFPlayer *ffp = arg; VideoState *is = ffp->is; double remaining_time = 0.0; while (!is->abort_request) { if (remaining_time > 0.0) av_usleep((int)(int64_t)(remaining_time * 1000000.0)); remaining_time = REFRESH_RATE; if (is->show_mode != SHOW_MODE_NONE && (!is->paused || is->force_refresh)) video_refresh(ffp, &remaining_time); } return 0; }
非暫停或強制刷新的時候,循環調用video_refresh。
static void video_refresh(FFPlayer *opaque, double *remaining_time) { ...... if (!ffp->display_disable && is->show_mode != SHOW_MODE_VIDEO && is->audio_st) { time = av_gettime_relative() / 1000000.0; if (is->force_refresh || is->last_vis_time + ffp->rdftspeed < time) { video_display2(ffp); is->last_vis_time = time; } *remaining_time = FFMIN(*remaining_time, is->last_vis_time + ffp->rdftspeed - time); } ...... }
video_display2的調用較為關鍵,之前有外部時鐘同步和一些時間檢測等。video_display2里直接調用了video_image_display2。
static void video_image_display2(FFPlayer *ffp) { VideoState *is = ffp->is; Frame *vp; Frame *sp = NULL; vp = frame_queue_peek_last(&is->pictq); int latest_seek_load_serial = __atomic_exchange_n(&(is->latest_seek_load_serial), -1, memory_order_seq_cst); if (latest_seek_load_serial == vp->serial) ffp->stat.latest_seek_load_duration = (av_gettime() - is->latest_seek_load_start_at) / 1000; if (vp->bmp) { if (is->subtitle_st) { if (frame_queue_nb_remaining(&is->subpq) > 0) { sp = frame_queue_peek(&is->subpq); if (vp->pts >= sp->pts + ((float) sp->sub.start_display_time / 1000)) { if (!sp->uploaded) { if (sp->sub.num_rects > 0) { char buffered_text[4096]; if (sp->sub.rects[0]->text) { strncpy(buffered_text, sp->sub.rects[0]->text, 4096); } else if (sp->sub.rects[0]->ass) { parse_ass_subtitle(sp->sub.rects[0]->ass, buffered_text); } ffp_notify_msg4(ffp, FFP_MSG_TIMED_TEXT, 0, 0, buffered_text, sizeof(buffered_text)); } sp->uploaded = 1; } } } } SDL_VoutDisplayYUVOverlay(ffp->vout, vp->bmp); ffp->stat.vfps = SDL_SpeedSamplerAdd(&ffp->vfps_sampler, FFP_SHOW_VFPS_FFPLAY, "vfps[ffplay]"); if (!ffp->first_video_frame_rendered) { ffp->first_video_frame_rendered = 1; ffp_notify_msg1(ffp, FFP_MSG_VIDEO_RENDERING_START); } } }
frame_queue_peek_last獲取隊列最后一幀,如果幀中的圖像正常,繼續走sdl渲染yup畫面SDL_VoutDisplayYUVOverlay。最后通過消息通知開始渲染ffp_notify_msg1。
下面我們回到video_refresh,看看音畫同步的問題是如何處理的:
double last_duration, duration, delay; Frame *vp, *lastvp; /* dequeue the picture */ lastvp = frame_queue_peek_last(&is->pictq); vp = frame_queue_peek(&is->pictq); if (vp->serial != is->videoq.serial) { frame_queue_next(&is->pictq); goto retry; } if (lastvp->serial != vp->serial) is->frame_timer = av_gettime_relative() / 1000000.0; if (is->paused) goto display; /* compute nominal last_duration */ last_duration = vp_duration(is, lastvp, vp); delay = compute_target_delay(ffp, last_duration, is); time= av_gettime_relative()/1000000.0; if (isnan(is->frame_timer) || time < is->frame_timer) is->frame_timer = time; if (time < is->frame_timer + delay) { *remaining_time = FFMIN(is->frame_timer + delay - time, *remaining_time); goto display; } is->frame_timer += delay; if (delay > 0 && time - is->frame_timer > AV_SYNC_THRESHOLD_MAX) is->frame_timer = time; SDL_LockMutex(is->pictq.mutex); if (!isnan(vp->pts)) update_video_pts(is, vp->pts, vp->pos, vp->serial); SDL_UnlockMutex(is->pictq.mutex); if (frame_queue_nb_remaining(&is->pictq) > 1) { Frame *nextvp = frame_queue_peek_next(&is->pictq); duration = vp_duration(is, vp, nextvp); if(!is->step && (ffp->framedrop > 0 || (ffp->framedrop && get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER)) && time > is->frame_timer + duration) { frame_queue_next(&is->pictq); goto retry; } }
首先取出上一幀(lastvp)和當前幀(vp),然后有個判斷是看從video的隊列中的序列是否與當前的幀是相同的,如果不是挨個查找下幀,然后跳轉到retry,再次執行。我理解的是從FrameQueue隊列中找到播放序列相同的這一幀,然后進行后續的操作。下面就是vp_duration了,這里做了一個減法,計算出了這一幀持續的時間。那么后面的delay = compute_target_delay(ffp, last_duration, is);有什么作用呢?
static double compute_target_delay(FFPlayer *ffp, double delay, VideoState *is) { double sync_threshold, diff = 0; /* update delay to follow master synchronisation source */ if (get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER) { /* if video is slave, we try to correct big delays by duplicating or deleting a frame */ diff = get_clock(&is->vidclk) - get_master_clock(is); /* skip or repeat frame. We take into account the delay to compute the threshold. I still don"t know if it is the best guess */ sync_threshold = FFMAX(AV_SYNC_THRESHOLD_MIN, FFMIN(AV_SYNC_THRESHOLD_MAX, delay)); /* -- by bbcallen: replace is->max_frame_duration with AV_NOSYNC_THRESHOLD */ if (!isnan(diff) && fabs(diff) < AV_NOSYNC_THRESHOLD) { if (diff <= -sync_threshold) delay = FFMAX(0, delay + diff); else if (diff >= sync_threshold && delay > AV_SYNC_FRAMEDUP_THRESHOLD) delay = delay + diff; else if (diff >= sync_threshold) delay = 2 * delay; } } if (ffp) { ffp->stat.avdelay = delay; ffp->stat.avdiff = diff; } #ifdef FFP_SHOW_AUDIO_DELAY av_log(NULL, AV_LOG_TRACE, "video: delay=%0.3f A-V=%f ", delay, -diff); #endif return delay; }
首先是一個AV_SYNC_VIDEO_MASTER的判斷,如果發現主時鐘不是video,那么計算當前視頻時鐘與主時鐘的差值,diff = get_clock(&is->vidclk) - get_master_clock(is);計算視頻時鐘與當前的音頻時鐘之間的差值。然后根據根據偏差的范圍進行了調整延遲時間(視頻比音頻快,加大下一幀的渲染時間,否則縮短時間),這里有3種情況判斷:如果當前視頻時間落后于主時鐘,需要減小下一幀畫面的等待時間;如果視頻幀超前了,并且顯示時間大于一個閾值(AV_SYNC_FRAMEDUP_THRESHOLD),則顯示下一幀的時間為超前的時間差加上上一幀的顯示時間;如果視頻超前了,并且上一幀的顯示時間小于這個閾值,則加倍延時。之后設置到了ffp->stat中,并返回。這個返回的偏差值就是后面進行是否拋幀或sleep的判斷依據。
回到video_refresh,is->frame_timer = av_gettime_relative() / 1000000.0;這里的frame_time實際上就是上一幀顯示的時間,is->frame_timer + delay其實就是當前這一幀顯示的時間。那么看這個判斷:
if (time < is->frame_timer + delay) { *remaining_time = FFMIN(is->frame_timer + delay - time, *remaining_time); goto display; }
如果播放的時間還沒有到達當前這一幀的顯示時間,那么直接跳到display,在display中將is->force_refresh變量為0,不顯示當前幀,
這里的意思大約是還沒到播放的時間,那么跳轉出去到display,然后函數就返回了。那么這個函數的調用者video_refresh_thread里面可是循環調用的,所以后面會經過sleep然后再次調用到這個里面。好吧,這里我就簡單的理解為如果沒有到達顯示的時間點,就sleep。后面是如果time - is->frame_timer + delay超過了AV_SYNC_THRESHOLD_MAX,就將is->frame_timer設置為當前時間。這里的目的還不是特別清楚,不過可以感覺到,是為了后面要做判斷,估計是要對一些延遲比較高的幀考慮拋幀處理吧。
后面進行update_video_pts更新pts。再下面的這里
if (frame_queue_nb_remaining(&is->pictq) > 1) { Frame *nextvp = frame_queue_peek_next(&is->pictq); duration = vp_duration(is, vp, nextvp); if(!is->step && (ffp->framedrop > 0 || (ffp->framedrop && get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER)) && time > is->frame_timer + duration) { frame_queue_next(&is->pictq); goto retry; } }
如果視頻還有下一幀,則拿出下一幀來,計算差值。下面要注意:time > is->frame_timer + duration的判斷,如果下一幀的時間點比當前的系統時間慢,也就是說不僅當前的慢了,下一幀的也慢了,慢了2幀,那么久觸發丟幀,丟掉當前幀。通過frame_queue_next將緩存游標挪到下一幀,然后goto到retry,重新進行上面的渲染判斷。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/66715.html
摘要:我們下面先從讀取線程入手。無論這個循環前后干了什么,都是要走這一步,讀取數據幀。從開始,我理解的是計算出當前數據幀的時間戳后再計算出播放的起始時間到當前時間,然后看這個時間戳是否在此范圍內。 ijkplayer現在比較流行,因為工作關系,接觸了他,現在做個簡單的分析記錄吧。我這里直接跳過java層代碼,進入c層,因為大多數的工作都是通過jni調用到c層來完成的,java層的內容并不是主...
摘要:下面是,讀取頭信息頭信息。猜測網絡部分至少在一開始就應當初始化好的,因此在的過程里面找,在中找到了。就先暫時分析到此吧。 這章要簡單分析下ijkplayer是如何從文件或網絡讀取數據源的。還是read_thread函數中的關鍵點avformat_open_input函數: int avformat_open_input(AVFormatContext **ps, const char ...
摘要:分別為音頻視頻和字母進行相關處理。向下跟蹤兩層,會發現,核心函數是。至此解碼算完了。整個過程真是粗略分析啊,對自己也很抱歉,暫時先這樣吧。 上文中說到在read_thread線程中有個關鍵函數:avformat_open_input(utils.c),應當是讀取視頻文件的,這個函數屬于ffmpeg層。這回進入到其中去看下: int avformat_open_input(AVForma...
摘要:管協議和編解碼,管渲染顯示,管理播放器。然后是,這個最后會走到基本上以的初始化內容居多,開頭的應該都是。然后是,的網絡初始化。這下子與基礎協議對應的各項操作算是找到了。終于分析完了,總結起來就是各種初始化,協議的解碼器的網絡的回調上層的。 本來這個過程我是不大想寫初始化的過程,覺得網上已經有不少文章來分析了。但是在前面的整個分析過程中,暴露了自己對一些問題理解還不夠透徹,因此有必要做一...
閱讀 3473·2023-04-26 02:48
閱讀 1468·2021-10-11 10:57
閱讀 2493·2021-09-23 11:35
閱讀 1201·2021-09-06 15:02
閱讀 3300·2019-08-30 15:54
閱讀 1617·2019-08-30 15:44
閱讀 883·2019-08-30 15:44
閱讀 992·2019-08-30 12:52