ffplay視頻輸出和尺寸變換

視頻輸出模塊

視頻輸出初始化的主要流程

我們開始分析視頻(圖像)的顯示。

因為使?了SDL,?video的顯示也依賴SDL的窗?顯示系統,所以先從main函數的SDL初始化看起(節選):

int main(int argc, char **argv)
{// SDL初始化flags = SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER;if (audio_disable)flags &= ~SDL_INIT_AUDIO;else {/* Try to work around an occasional ALSA buffer underflow issue when the* period size is NPOT due to ALSA resampling by forcing the buffer size. */if (!SDL_getenv("SDL_AUDIO_ALSA_SET_BUFFER_SIZE"))SDL_setenv("SDL_AUDIO_ALSA_SET_BUFFER_SIZE","1", 1);}if (display_disable)flags &= ~SDL_INIT_VIDEO;if (SDL_Init (flags)) {av_log(NULL, AV_LOG_FATAL, "Could not initialize SDL - %s\n", SDL_GetError());av_log(NULL, AV_LOG_FATAL, "(Did you set the DISPLAY variable?)\n");exit(1);}SDL_EventState(SDL_SYSWMEVENT, SDL_IGNORE);SDL_EventState(SDL_USEREVENT, SDL_IGNORE);av_init_packet(&flush_pkt);flush_pkt.data = (uint8_t *)&flush_pkt;if (!display_disable) {int flags = SDL_WINDOW_HIDDEN;if (alwaysontop)
#if SDL_VERSION_ATLEAST(2,0,5)flags |= SDL_WINDOW_ALWAYS_ON_TOP;
#elseav_log(NULL, AV_LOG_WARNING, "Your SDL version doesn't support SDL_WINDOW_ALWAYS_ON_TOP. Feature will be inactive.\n");
#endifif (borderless)flags |= SDL_WINDOW_BORDERLESS;elseflags |= SDL_WINDOW_RESIZABLE;// 創建窗口window = SDL_CreateWindow(program_name, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, default_width, default_height, flags);SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");if (window) {// 創建rendererrenderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);if (!renderer) {av_log(NULL, AV_LOG_WARNING, "Failed to initialize a hardware accelerated renderer: %s\n", SDL_GetError());renderer = SDL_CreateRenderer(window, -1, 0);}if (renderer) {if (!SDL_GetRendererInfo(renderer, &renderer_info))av_log(NULL, AV_LOG_VERBOSE, "Initialized %s renderer.\n", renderer_info.name);}}if (!window || !renderer || !renderer_info.num_texture_formats) {av_log(NULL, AV_LOG_FATAL, "Failed to create window or renderer: %s", SDL_GetError());do_exit(NULL);}}// 通過stream_open函數,開啟read_thread讀取線程is = stream_open(input_filename, file_iformat);if (!is) {av_log(NULL, AV_LOG_FATAL, "Failed to initialize VideoState!\n");do_exit(NULL);}// 事件響應event_loop(is);/* never returns */return 0;
}

main函數主要步驟如下:

  1. SDL_Init,主要是SDL_INIT_VIDEO的?持
  2. SDL_CreateWindow,創建主窗?
  3. SDL_CreateRender,基于主窗?創建renderer,?于渲染輸出。
  4. stream_open
  5. event_loop,播放控制事件響應循環,但也負責了video顯示輸出。

我們之前在講read_thread線程時,講到了:

// 從待處理流中獲取相關參數,設置顯示窗?的寬度、?度及寬??
if (st_index[AVMEDIA_TYPE_VIDEO] >= 0) {AVStream *st = ic->streams[st_index[AVMEDIA_TYPE_VIDEO]];AVCodecParameters *codecpar = st->codecpar;// 根據流和幀寬??猜測幀的樣本寬??。該值只是?個參考AVRational sar = av_guess_sample_aspect_ratio(ic, st, NULL);if (codecpar->width)// 設置顯示窗口的大小和寬高比set_default_window_size(codecpar->width, codecpar->height, sar);
}

這?我們重點分析set_default_window_size的原理,該函數主要獲取窗?的寬?,以及視頻渲染的區域:

static void set_default_window_size(int width, int height, AVRational sar)
{SDL_Rect rect;// 確定是否指定窗口最大寬度int max_width  = screen_width  ? screen_width  : INT_MAX;// 確定是否指定窗口最大高度int max_height = screen_height ? screen_height : INT_MAX;if (max_width == INT_MAX && max_height == INT_MAX)max_height = height; // 沒有制定最大高度時則使用視頻高度calculate_display_rect(&rect, 0, 0, max_width, max_height, width, height, sar);default_width  = rect.w;default_height = rect.h;
}

screen_width和screen_height可以在ffplay啟動時設置 -x screen_width -y screen_height獲取指定的寬?,如果沒有指定,則max_height = height,即是視頻幀的?度。

重點在calculate_display_rect()函數。

初始化窗口顯示大小

calculate_display_rect根據傳入的參數 (int scr_xleft, int scr_ytop, int scr_width, int scr_height, int pic_width, int pic_height, AVRational pic_sar)獲取顯示區域的起始坐標和大小(rect)。

static void calculate_display_rect(SDL_Rect *rect,int scr_xleft, int scr_ytop, int scr_width, int scr_height,int pic_width, int pic_height, AVRational pic_sar)
{AVRational aspect_ratio = pic_sar; // 比例int64_t width, height, x, y;if (av_cmp_q(aspect_ratio, av_make_q(0, 1)) <= 0)aspect_ratio = av_make_q(1, 1); // 如果aspect_ratio是負數或者為0,設置為1:1// 轉成真正的播放比例aspect_ratio = av_mul_q(aspect_ratio, av_make_q(pic_width, pic_height));/* XXX: we suppose the screen has a 1.0 pixel ratio */// 計算顯示視頻幀區域的寬高// 先以高度為基準height = scr_height;// &~1,取偶數寬度width = av_rescale(height, aspect_ratio.num, aspect_ratio.den) & ~1;if (width > scr_width) {// 當以高度為基準,發現計算出來的需要的窗口寬度不足時調整為以窗口寬度為基準width = scr_width;height = av_rescale(width, aspect_ratio.den, aspect_ratio.num) & ~1;}// 計算顯示視頻幀區域的起始坐標(在顯示窗?內部的區域)x = (scr_width - width) / 2;y = (scr_height - height) / 2;rect->x = scr_xleft + x;rect->y = scr_ytop  + y;rect->w = FFMAX((int)width,  1);rect->h = FFMAX((int)height, 1);
}

注意視頻顯示尺?的計算

<font style="color:rgb(38,38,38);">aspect_ratio = av_mul_q(aspect_ratio, av_make_q(pic_width, pic_height));</font>計算出真正顯示時需要的比例。

視頻輸出邏輯

main() -->
event_loop() -->
video_refresh() -->
video_display() -->
video_image_display() -->
upload_texture()

event_loop開始處理 SDL 事件:

static void event_loop(VideoState *cur_stream)
{SDL_Event event;double incr, pos, frac;for (;;) {double x;refresh_loop_wait_event(cur_stream, &event);switch (event.type) {......case SDLK_SPACE:toggle_pause(cur_stream);break;case SDLK_m:toggle_mute(cur_stream);break;case SDLK_KP_MULTIPLY:case SDLK_0:update_volume(cur_stream, 1, SDL_VOLUME_STEP);break;case SDLK_KP_DIVIDE:case SDLK_9:update_volume(cur_stream, -1, SDL_VOLUME_STEP);break;case SDLK_s: // S: Step to next framestep_to_next_frame(cur_stream);break;case SDLK_a:stream_cycle_channel(cur_stream, AVMEDIA_TYPE_AUDIO);break;case SDLK_v:stream_cycle_channel(cur_stream, AVMEDIA_TYPE_VIDEO);break;case SDLK_c:stream_cycle_channel(cur_stream, AVMEDIA_TYPE_VIDEO);stream_cycle_channel(cur_stream, AVMEDIA_TYPE_AUDIO);stream_cycle_channel(cur_stream, AVMEDIA_TYPE_SUBTITLE);break;case SDLK_t:stream_cycle_channel(cur_stream, AVMEDIA_TYPE_SUBTITLE);break;case SDLK_w:
#if CONFIG_AVFILTERif (cur_stream->show_mode == SHOW_MODE_VIDEO && cur_stream->vfilter_idx < nb_vfilters - 1) {if (++cur_stream->vfilter_idx >= nb_vfilters)cur_stream->vfilter_idx = 0;} else {cur_stream->vfilter_idx = 0;toggle_audio_display(cur_stream);}
#elsetoggle_audio_display(cur_stream);
#endifbreak;case SDLK_PAGEUP:if (cur_stream->ic->nb_chapters <= 1) {incr = 600.0;goto do_seek;}seek_chapter(cur_stream, 1);break;case SDLK_PAGEDOWN:if (cur_stream->ic->nb_chapters <= 1) {incr = -600.0;goto do_seek;}seek_chapter(cur_stream, -1);break;case SDLK_LEFT:incr = seek_interval ? -seek_interval : -10.0;goto do_seek;case SDLK_RIGHT:incr = seek_interval ? seek_interval : 10.0;goto do_seek;case SDLK_UP:incr = 60.0;goto do_seek;case SDLK_DOWN:incr = -60.0;do_seek:if (seek_by_bytes) {pos = -1;if (pos < 0 && cur_stream->video_stream >= 0)pos = frame_queue_last_pos(&cur_stream->pictq);if (pos < 0 && cur_stream->audio_stream >= 0)pos = frame_queue_last_pos(&cur_stream->sampq);if (pos < 0)pos = avio_tell(cur_stream->ic->pb);if (cur_stream->ic->bit_rate)incr *= cur_stream->ic->bit_rate / 8.0;elseincr *= 180000.0;pos += incr;stream_seek(cur_stream, pos, incr, 1);} else {pos = get_master_clock(cur_stream);if (isnan(pos))pos = (double)cur_stream->seek_pos / AV_TIME_BASE;pos += incr;if (cur_stream->ic->start_time != AV_NOPTS_VALUE && pos < cur_stream->ic->start_time / (double)AV_TIME_BASE)pos = cur_stream->ic->start_time / (double)AV_TIME_BASE;stream_seek(cur_stream, (int64_t)(pos * AV_TIME_BASE), (int64_t)(incr * AV_TIME_BASE), 0);}break;default:break;}break;case SDL_MOUSEBUTTONDOWN:if (exit_on_mousedown) {do_exit(cur_stream);break;}if (event.button.button == SDL_BUTTON_LEFT) {static int64_t last_mouse_left_click = 0;if (av_gettime_relative() - last_mouse_left_click <= 500000) {toggle_full_screen(cur_stream);cur_stream->force_refresh = 1;last_mouse_left_click = 0;} else {last_mouse_left_click = av_gettime_relative();}}case SDL_MOUSEMOTION:if (cursor_hidden) {SDL_ShowCursor(1);cursor_hidden = 0;}cursor_last_shown = av_gettime_relative();if (event.type == SDL_MOUSEBUTTONDOWN) {if (event.button.button != SDL_BUTTON_RIGHT)break;x = event.button.x;} else {if (!(event.motion.state & SDL_BUTTON_RMASK))break;x = event.motion.x;}if (seek_by_bytes || cur_stream->ic->duration <= 0) {uint64_t size =  avio_size(cur_stream->ic->pb);stream_seek(cur_stream, size*x/cur_stream->width, 0, 1);} else {int64_t ts;int ns, hh, mm, ss;int tns, thh, tmm, tss;tns  = cur_stream->ic->duration / 1000000LL;thh  = tns / 3600;tmm  = (tns % 3600) / 60;tss  = (tns % 60);frac = x / cur_stream->width;ns   = frac * tns;hh   = ns / 3600;mm   = (ns % 3600) / 60;ss   = (ns % 60);av_log(NULL, AV_LOG_INFO,"Seek to %2.0f%% (%2d:%02d:%02d) of total duration (%2d:%02d:%02d)       \n", frac*100,hh, mm, ss, thh, tmm, tss);ts = frac * cur_stream->ic->duration;if (cur_stream->ic->start_time != AV_NOPTS_VALUE)ts += cur_stream->ic->start_time;stream_seek(cur_stream, ts, 0, 0);}break;case SDL_WINDOWEVENT:switch (event.window.event) {case SDL_WINDOWEVENT_SIZE_CHANGED:screen_width  = cur_stream->width  = event.window.data1;screen_height = cur_stream->height = event.window.data2;if (cur_stream->vis_texture) {SDL_DestroyTexture(cur_stream->vis_texture);cur_stream->vis_texture = NULL;}case SDL_WINDOWEVENT_EXPOSED:cur_stream->force_refresh = 1;}break;case SDL_QUIT:case FF_QUIT_EVENT:do_exit(cur_stream);break;default:break;}}
}

event_loop的主要代碼是一個主循環,主循環內執行:

  1. refresh_loop_wait_event
  2. 處理SDL事件隊列中的事件。?如按空格鍵可以觸發暫停/恢復,關閉窗?可以觸發do_exit銷毀播放現場。

video 顯示主要在 <font style="color:rgb(38,38,38);">refresh_loop_wait_event</font>

static void refresh_loop_wait_event(VideoState *is, SDL_Event *event) {// 休眠等待,remaining_time的計算在video_refresh中double remaining_time = 0.0;// 調?SDL_PeepEvents前先調?SDL_PumpEvents,將輸?設備的事件抽到事件隊列中SDL_PumpEvents();// SDL_PeepEvents check是否事件,?如?標移?顯示區等// 從事件隊列中拿?個事件,放到event中,如果沒有事件,則進?循環中// SDL_PeekEvents?于讀取事件,在調?該函數之前,必須調?SDL_PumpEvents搜集鍵盤等事件while (!SDL_PeepEvents(event, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT)) {if (!cursor_hidden && av_gettime_relative() - cursor_last_shown > CURSOR_HIDE_DELAY) {SDL_ShowCursor(0);cursor_hidden = 1;}/** remaining_time就是?來進??視頻同步的。* 在video_refresh函數中,根據當前幀顯示時刻(display time)和實際時刻(actual time)* 計算需要sleep的時間,保證幀按時顯示*/if (remaining_time > 0.0) //sleep控制畫?輸出的時機av_usleep((int64_t)(remaining_time * 1000000.0));remaining_time = REFRESH_RATE;if (is->show_mode != SHOW_MODE_NONE && (!is->paused || // ?暫停狀態is->force_refresh)) // ?強制刷新狀態video_refresh(is, &remaining_time);SDL_PumpEvents();}
}

參考資料:https://github.com/0voice

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/97770.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/97770.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/97770.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

協議_https協議

http http協議是將數據以明文的形式在網絡上傳輸。若是傳輸的數據中包含一些敏感信息比如銀行卡信息等可能會被有心人攻擊造成信息泄露或被篡改。 總結&#xff1a;http協議進行數據傳輸難以保證數據的隱私性以及數據完整性&#xff0c;為了保證數據的準確定引入了https這一協…

阿里云 騰訊云 API 自動化查詢指南

文章目錄一、核心思路與架構建議二、經驗與核心建議三、技術方案選型建議四、API使用詳解4.1 阿里云4.2 騰訊云五、進階&#xff1a;與內部系統聯動免費個人運維知識庫&#xff0c;歡迎您的訂閱&#xff1a;literator_ray.flowus.cn 一、核心思路與架構建議 自動化流程可以概括…

【Unity 性能優化之路——概述(0)】

Unity性能優化概述性能優化不是某個環節的極致壓榨&#xff0c;而是所有模塊的協同共進。本文將為你建立完整的Unity性能優化知識體系。很多Unity開發者一提到性能優化&#xff0c;首先想到的就是Draw Call、Batches這些渲染指標。這沒錯&#xff0c;但它們只是性能優化中的一部…

靈碼產品演示:軟件工程架構分析

作者&#xff1a;了哥 演示目的演示靈碼對于整個復雜軟件工程項目的架構分析能力&#xff0c;輸出項目的軟件系統架構圖。演示文檔接口生成能力。演示準備 克隆工程地址到本地&#xff08;需提前安裝好 git 工具&#xff0c; 建議本地配置 brew&#xff09;&#xff1a; git cl…

銀河麒麟部署mysql8.0并連接應用

?客戶需在國產化銀河麒麟系統中部署軟件應用&#xff0c;使用mysql8.0數據庫。機器放置了兩三年&#xff0c;里面命令工具和依賴都不太全。而且客戶環境不聯網&#xff0c;只能采用離線部署的方式。部署過程中踩了很多坑&#xff0c;也用到很多資源&#xff0c;記錄一下。 過…

GitAgent-面壁智能聯合清華大學發布的大模型智能體應用框架

本文轉載自&#xff1a;https://www.hello123.com/gitagent ** 一、&#x1f50d; GitAgent 框架&#xff1a;大模型智能體的工具箱革命 GitAgent 是由面壁智能與清華大學自然語言處理實驗室聯合研發的創新型框架&#xff0c;旨在解決大模型智能體在復雜任務中的工具擴展瓶頸…

靈碼產品演示:Maven 示例工程生成

作者&#xff1a;輕眉 演示主題&#xff1a;由 AI 自動生成 0 到 1 的電商訂單 Java 項目 演示目的 面向 Java 零基礎的用戶&#xff0c;通過靈碼的產品能力&#xff08;如提示詞、編碼智能體、項目 Rules 和 SQLite MCP 服務、單元測試&#xff09;自動生成 0 到 1 的電商訂單…

AI編程從0-1開發一個小程序

小伙伴們&#xff0c;今天我們利用AI實現從0到1開發一個小程序&#xff01;需求交給AI&#xff1a; 我們只要說出自己的開發思路&#xff0c;具體需求交給AI完成&#xff01;輸入提示詞&#xff1a;個人開發的小程序 能開發哪些好備案&#xff0c;用戶喜歡使用的 AI給出…

DDoS高防IP是什么? DDoS攻擊會暴露IP嗎?

DDoS高防IP是什么&#xff1f;高防IP是指一種網絡安全服務&#xff0c;主要用于防御DDoS攻擊。隨著技術的發展&#xff0c;黑客進行網絡攻擊的強度也在加大&#xff0c;所以我們要做好網絡防護&#xff0c;及時預防DDoS攻擊。DDoS高防IP是什么&#xff1f;DDoS高防IP是指基于IP…

k8s事件驅動運維利器 shell operator

Shell-Operator 概述 Shell-Operator 是 Kubernetes 的一個工具&#xff0c;用于通過 shell 腳本擴展集群功能。它允許用戶編寫簡單的腳本&#xff08;Bash、Python 等&#xff09;來響應 Kubernetes 事件&#xff08;如資源變更、定時任務&#xff09;&#xff0c;無需編譯復…

(二)文件管理-文件權限-chmod命令的使用

文章目錄1. 命令格式2. 基本用法2.1 符號模式2.2 八進制數字模式3. 高級用法3.1 遞歸操作3.2 參考權限3.3 特殊權限位(Setuid, Setgid, Sticky Bit)3.4 X 特殊執行權限4. 注意事項4.1權限與所有權4.2 Root 權限4.3 安全風險4.4 -R 的風險4.5 目錄的執行權限1. 命令格式 chmod …

醫院預約掛號腳本

醫院預約掛號腳本 功能介紹 本腳本是一個用 Python 編寫的醫院預約掛號程序&#xff0c;支持以下功能&#xff1a; 自動預約&#xff1a;通過api交互選擇醫院、科室、醫生和時間段。自動監控&#xff1a;持續檢查指定醫生的號源狀態&#xff0c;發現可預約時段時自動嘗試預約。…

.NET駕馭Word之力:理解Word對象模型核心 (Application, Document, Range)

在使用MudTools.OfficeInterop.Word庫進行Word文檔自動化處理時&#xff0c;深入理解Word對象模型的核心組件是至關重要的。Word對象模型提供了一套層次化的結構&#xff0c;使開發者能夠通過編程方式控制Word應用程序、文檔以及文檔內容。本章將詳細介紹Word對象模型中最核心的…

Kotlin在醫療大健康域的應用實例探究與編程剖析(上)

一、引言 1.1 研究背景與意義 在當今數字化時代,醫療行業正經歷著深刻的變革。隨著信息技術的飛速發展,尤其是人工智能、大數據、物聯網等新興技術的廣泛應用,醫療行業數字化轉型已成為必然趨勢。這種轉型旨在提升醫療服務的效率和質量,優化醫療資源配置,為患者提供更加…

AI智能體的應用前景

AI智能體的應用前景正從技術探索邁向規模化落地的關鍵階段,其發展動力源于大模型能力的突破、行業需求的深化以及商業化模式的創新。以下是基于最新技術動態和行業實踐的深度解析: 一、技術突破:從「有腦無手」到「知行合一」 大模型的進化顯著提升了智能體的多模態交互與…

高系分四:網絡分布式

目錄一、我的導圖和思考二、大模型對我導圖的評價優點可優化之處三、大模型對這章節的建議一、網絡知識范疇&#xff08;一&#xff09;網絡基礎理論&#xff08;二&#xff09;局域網與廣域網&#xff08;三&#xff09;網絡安全&#xff08;四&#xff09;網絡性能優化&#…

Day24_【深度學習(1)—概念】

一、AI、ML、DL基本關系 機器學習是實現人工智能的途徑&#xff0c;深度學習是機器學習的一種方法。人工智能 (AI)↓ 機器學習 (ML) —— 讓機器從數據中學習規律↓ 深度學習 (DL) —— 使用深層神經網絡的機器學習方法二、深度學習與機器學習概念深度學習&#xff08;Deep Lea…

VTK基礎(01):VTK中的基本概念

VTK中的基本概念 1.三維場景中的基本要素 三維場景的基本要素包含&#xff1a;燈光、相機、顏色和紋理映射 (1)燈光vtkLight 光的本質是特定頻段的電磁波&#xff0c;所以燈光的本質是特定頻段&#xff08;可見光頻段&#xff09;的電磁波發射器&#xff1b;依據發射可見光頻段…

LeetCode 2348.全0子數組的數目

給你一個整數數組 nums &#xff0c;返回全部為 0 的 子數組 數目。 子數組 是一個數組中一段連續非空元素組成的序列。 示例 1&#xff1a; 輸入&#xff1a;nums [1,3,0,0,2,0,0,4] 輸出&#xff1a;6 解釋&#xff1a; 子數組 [0] 出現了 4 次。 子數組 [0,0] 出現了 2 次。…

【wpf】從 DataContext 到依賴屬性:WPF 自定義控件 ImageView 的優化之路

從 DataContext 到依賴屬性&#xff1a;WPF 自定義控件 ImageView 的優化之路 最近我在做一個 WPF 項目&#xff0c;需要封裝一個 ImageView 控件&#xff0c;用來顯示圖像并處理鼠標交互。 在實際開發中&#xff0c;我遇到了一系列和 數據綁定 有關的問題&#xff1a; 控件需要…