日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

ffplay播放器

發布時間:2023/12/14 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 ffplay播放器 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

1 ffplay.c的意義

ffplay.c是FFmpeg源碼?帶的播放器,調?FFmpeg和SDL API實現?個?常有?的播放器。 例如嗶哩嗶哩著名開源項?ijkplayer也是基于ffplay.c進??次開發。

ffplay實現了播放器的主體功能,掌握其原理對于我們獨?開發播放器?常有幫助。

2 FFplay框架分析


播放器初始化

  • 初始化packet queue
  • 初始化frame queue
  • 初始化clock
  • 創建數據讀取線程

線程的劃分

  • 數據讀取線程
    • 打開媒體?件
    • 打開對應碼流的decoder以及初始化對應的audio、video、subtitle輸出
    • 創建decoder線程,audio、video和subtitle的解碼線程獨?
    • 調?av_read_frame讀取packet,并根據steam_index放?不同stream對應的packet隊列
  • ?頻解碼
    • 從packet queue讀取packet,解出frame后放?frame queue
  • 視頻解碼
    • 從packet queue讀取packet,解出frame后放?frame queue
  • 字幕解碼
    • 從packet queue讀取packet,解出frame后放?frame queue
  • ?頻播放(或者回調函數)
    • 從frame queue讀取frame進?播放
  • 視頻播放(ffplay?前是在main主線程進?視頻播放)
    • 從frame queue讀取frame進?播放 字幕播放(ffplay?前是在main主線程進?字幕播放) 從frame queue讀取frame進?播放
  • 控制響應(播放/暫停/快進/快退等)(ffplay?前是在main主線程進?播放控制)

packet隊列的設計

  • 線程安全,?持互斥、等待、喚醒
  • 緩存數據??
  • 緩存包數
  • 隊列播放可持續時間
  • 進隊列/出隊列等

frame隊列的設計

  • 線程安全,?持互斥、等待、喚醒
  • 緩存幀數
  • ?持讀取數據?不出隊列
  • 進隊列/出隊列等

?視頻同步

  • ?量調節
  • 靜?
  • 重采樣

視頻處理

  • 圖像格式轉換YUV->RGB等
  • 圖像縮放1280720->800480等

播放器控制

  • 播放
  • 暫停
  • 停?
  • 快進/快退
  • 逐幀靜?

3數據結構分析

struct VideoState 播放器封裝

typedef struct VideoState { SDL_Thread *read_tid; // 讀線程句柄 AVInputFormat *iformat; // 指向demuxer int abort_request; // =1時請求退出播放 int force_refresh; // =1時需要刷新畫?,請求?即刷新畫?的意思 int paused; // =1時暫停,=0時播放 int last_paused; // 暫存“暫停”/“播放”狀態 int queue_attachments_req; int seek_req; // 標識?次seek請求 int seek_flags; // seek標志,諸如AVSEEK_FLAG_BYTE等 int64_t seek_pos; // 請求seek的?標位置(當前位置+增量) int64_t seek_rel; // 本次seek的位置增量 int read_pause_return; AVFormatContext *ic; // iformat的上下? int realtime; // =1為實時流 Clock audclk; // ?頻時鐘 Clock vidclk; // 視頻時鐘 Clock extclk; // 外部時鐘 FrameQueue pictq; // 視頻Frame隊列 FrameQueue subpq; // 字幕Frame隊列 FrameQueue sampq; // 采樣Frame隊列 Decoder auddec; // ?頻解碼器 Decoder viddec; // 視頻解碼器 Decoder subdec; // 字幕解碼器 int audio_stream ; // ?頻流索引 int av_sync_type; // ?視頻同步類型, 默認audio master double audio_clock; // 當前?頻幀的PTS+當前幀Du ration int audio_clock_serial; // 播放序列,seek可改變此值 // 以下4個參數 ?audio master同步?式使? double audio_diff_cum; // used for AV differen ce average computation double audio_diff_avg_coef; double audio_diff_threshold; int audio_diff_avg_count; // end 4142 AVStream *audio_st; // ?頻流 PacketQueue audioq; // ?頻packet隊列 int audio_hw_buf_size; // SDL?頻緩沖區的??(字節 為單位) // 指向待播放的?幀?頻數據,指向的數據區將被拷?SDL?頻緩沖區。若經過重采樣 則指向audio_buf1, // 否則指向frame中的?頻 uint8_t *audio_buf; // 指向需要重采樣的數據 uint8_t *audio_buf1; // 指向重采樣后的數據 unsigned int audio_buf_size; // 待播放的?幀?頻數據(aud io_buf指向)的?? unsigned int audio_buf1_size; // 申請到的?頻緩沖區audio_ buf1的實際尺? int audio_buf_index; // 更新拷?位置 當前?頻幀中 已拷?SDL?頻緩沖區 // 的位置索引(指向第?個待拷 ?字節) // 當前?頻幀中尚未拷?SDL?頻緩沖區的數據量: // audio_buf_size = audio_buf_index + audio_write_buf_size int audio_write_buf_size; int audio_volume; // ?量 int muted; // =1靜?,=0則正常 struct AudioParams audio_src; // ?頻frame的參數#if CONFIG_AVFILTER struct AudioParams audio_filter_src; #endif struct AudioParams audio_tgt; // SDL?持的?頻參數,重采樣轉 換:audio_src->audio_tgt struct SwrContext *swr_ctx; // ?頻重采樣context int frame_drops_early; // 丟棄視頻packet計數 int frame_drops_late; // 丟棄視頻frame計數 enum ShowMode { 68 SHOW_MODE_NONE = -1, SHOW_MODE_VIDEO = 0, SHOW_MODE_WAVES, SHOW_MODE_RDFT, SHOW_MODE_NB 69 } show_mode; 7071 // ?頻波形顯示使? int16_t sample_array[SAMPLE_ARRAY_SIZE]; int sample_array_index; int last_i_start; RDFTContext *rdft; int rdft_bits; FFTSample *rdft_data; int xpos; double last_vis_time; SDL_Texture *vis_texture; SDL_Texture *sub_texture; // 字幕顯示 SDL_Texture *vid_texture; // 視頻顯示 int subtitle_stream; // 字幕流索引 AVStream *subtitle_st; // 字幕流 PacketQueue subtitleq; // 字幕packet隊列 double frame_timer; // 記錄最后?幀播放的時刻 double frame_last_returned_time; 92 double frame_last_filter_delay; 9394 int video_stream; // 視頻流索引 AVStream *video_st; // 視頻流 PacketQueue videoq; // 視頻隊列 double max_frame_duration; // ?幀最?間隔. above this, we co nsider the jump a timestamp discontinuity 98 struct SwsContext *img_convert_ctx; // 視頻尺?格式變換 struct SwsContext *sub_convert_ctx; // 字幕尺?格式變換 100 int eof; // 是否讀取結束 char *filename; // ?件名 int width, height, xleft, ytop; // 寬、?,x起始坐標,y起始坐標 104 int step; // =1 步進播放模式, =0 其他模式 #if CONFIG_AVFILTER int vfilter_idx; AVFilterContext *in_video_filter; // the first filter in the video chain AVFilterContext *out_video_filter; // the last filter in the v ideo chain AVFilterContext *in_audio_filter; // the first filter in the audio chain AVFilterContext *out_audio_filter; // the last filter in the a udio chain AVFilterGraph *agraph; // audio filter graph 113 #endif // 保留最近的相應audio、video、subtitle流的steam index int last_video_stream, last_audio_stream, last_subtitle_stream; SDL_cond *continue_read_thread; // 當讀取數據隊列滿了后進?休眠時,可 以通過該condition喚醒讀線程 } VideoState;

struct Clock 時鐘封裝

typedef struct Clock { double pts; // 時鐘基礎, 當前幀(待播放)顯示時間戳,播放后, 當前幀變成上?幀 // 當前pts與當前系統時鐘的差值, audio、video對于該值是獨?的 double pts_drift; // clock base minus time at which we upd ated the clock // 當前時鐘(如視頻時鐘)最后?次更新時間,也可稱當前時鐘時間 double last_updated; // 最后?次更新的系統時鐘 double speed; // 時鐘速度控制,?于控制播放速度 // 播放序列,所謂播放序列就是?段連續的播放動作,?個seek操作會啟動?段新的播 放序列 int serial; // clock is based on a packet with this serial int paused; // = 1 說明是暫停狀態 // 指向packet_serial int *queue_serial; /* pointer to the current packet queue s erial, used for obsolete clock detection */ } Clock;

struct MyAVPacketList和PacketQueue隊列

ffplay?PacketQueue保存解封裝后的數據,即保存AVPacket。
ffplay?先定義了?個結構體 MyAVPacketList :

typedef struct MyAVPacketList { AVPacket pkt; //解封裝后的數據 struct MyAVPacketList *next; //下?個節點 int serial; //播放序列 } MyAVPacketList;

可以理解為是隊列的?個節點。可以通過其 next 字段訪問下?個節點。

serial字段主要?于標記當前節點的播放序列號,ffplay中多處?到serial的概念,主要?來區分是否連續 數據,每做?次seek,該serial都會做+1的遞增,以區分不同的播放序列。serial字段在我們ffplay的分析 中應??常?泛,謹記他是?來區分數據否連續先。

接著定義另?個結構體PacketQueue:

typedef struct PacketQueue { MyAVPacketList *first_pkt, *last_pkt; // 隊?,隊尾指針 int nb_packets; // 包數量,也就是隊列元素數量 int size; // 隊列所有元素的數據??總和 int64_t duration; // 隊列所有元素的數據播放持續時間 int abort_request; // ?戶退出請求標志 int serial; // 播放序列號,和MyAVPacketList的serial作?相 同 SDL_mutex *mutex; // ?于維持PacketQueue的多線程安全(SDL_mutex 可以按pthread_mutex_t理解) SDL_cond *cond; // ?于讀、寫線程相互通知(SDL_cond可以按pthrea d_cond_t理解) } PacketQueue;

該結構體內定義了“隊列”?身的屬性。上?的注釋對每個字段作了簡單的介紹,這?也看到了serial字段, **MyAVPacketList的serial字段的賦值來?PacketQueue的serial,**每個PacketQueue的serial是獨?的。

?頻、視頻、字幕流都有??獨?的PacketQueue

接下來我們也從隊列的操作函數具體分析各個字段的含義。

PacketQueue 操作提供以下?法:

  • packet_queue_init:初始化
  • packet_queue_destroy:銷毀
  • packet_queue_start:啟?
  • packet_queue_abort:中?
  • packet_queue_get:獲取?個節點
  • packet_queue_put:存??個節點
  • packet_queue_put_nullpacket:存??個空節點
  • packet_queue_flush:清除隊列內所有的節點

packet_queue_init()

初始化?于初始各個字段的值,并創建mutex和cond:

/* packet queue handling */ static int packet_queue_init(PacketQueue *q) {memset(q, 0, sizeof(PacketQueue));q->mutex = SDL_CreateMutex();if (!q->mutex) {av_log(NULL, AV_LOG_FATAL, "SDL_CreateMutex(): %s\n", SDL_GetError());return AVERROR(ENOMEM);}q->cond = SDL_CreateCond();if (!q->cond) {av_log(NULL, AV_LOG_FATAL, "SDL_CreateCond(): %s\n", SDL_GetError());return AVERROR(ENOMEM);}q->abort_request = 1;return 0; }

packet_queue_destroy()

相應的,packet_queue_destroy()銷毀過程負責清理mutex和cond:

static void packet_queue_destroy(PacketQueue *q) {packet_queue_flush(q);//先清除所有的節點SDL_DestroyMutex(q->mutex);SDL_DestroyCond(q->cond); }

packet_queue_start()

啟動隊列

static void packet_queue_start(PacketQueue *q) {SDL_LockMutex(q->mutex);q->abort_request = 0;packet_queue_put_private(q, &flush_pkt);//這?放?了?個flush_pkt, 問題:?的是什么SDL_UnlockMutex(q->mutex); }

flush_pkt定義是 static AVPacket flush_pkt; ,是?個特殊的packet,主要?來作為?連續的兩 端數據的“分界”標記:

  • 插? flush_pkt 觸發PacketQueue其對應的serial,加1操作
  • 觸發解碼器清空?身緩存 avcodec_flush_buffers(),以備新序列的數據進?新解碼

packet_queue_abort()

中?隊列:

static void packet_queue_abort(PacketQueue *q) {SDL_LockMutex(q->mutex);q->abort_request = 1;// 請求退出SDL_CondSignal(q->cond);//釋放?個條件信號SDL_UnlockMutex(q->mutex); }

這?SDL_CondSignal的作?在于確保當前等待該條件的線程能被激活并繼續執?退出流程,并喚醒者會 檢測abort_request標志確定??的退出流程

packet_queue_put()

讀、寫是PacketQueue的主要?法。 先看寫——往隊列中放??個節點:

static int packet_queue_put(PacketQueue *q, AVPacket *pkt) {int ret;SDL_LockMutex(q->mutex);ret = packet_queue_put_private(q, pkt);//主要實現SDL_UnlockMutex(q->mutex);if (pkt != &flush_pkt && ret < 0)av_packet_unref(pkt);//放?失敗,釋放AVPacketreturn ret; }

主要實現在函數 packet_queue_put_private ,這?需要注意的是如果插?失敗,則需要釋放 AVPacket

我們再分析packet_queue_put_private:

static int packet_queue_put_private(PacketQueue *q, AVPacket *pkt) {MyAVPacketList *pkt1;if (q->abort_request)//如果已中?,則放?失敗return -1;pkt1 = av_malloc(sizeof(MyAVPacketList));if (!pkt1)//內存不?,則放?失敗return -1;// 沒有做引?計數,那這?也說明av_read_frame不會釋放替?戶釋放buffer。pkt1->pkt = *pkt; //拷?AVPacket(淺拷?,AVPacket.data等內存并沒有拷?)pkt1->next = NULL;if (pkt == &flush_pkt)//如果放?的是flush_pkt,需要增加隊列的播放序列 號,以區分不連續的兩段數據q->serial++;pkt1->serial = q->serial;//?隊列序列號標記節點/* 隊列操作:如果last_pkt為空,說明隊列是空的,新增節點為隊頭; * 否則,隊列有數據,則讓原隊尾的next為新增節點。 最后將隊尾指向新增節點 */if (!q->last_pkt)q->first_pkt = pkt1;elseq->last_pkt->next = pkt1;q->last_pkt = pkt1;//隊列屬性操作:增加節點數、cache??、cache總時?, ?來控制隊列的??q->nb_packets++;q->size += pkt1->pkt.size + sizeof(*pkt1);q->duration += pkt1->pkt.duration;/* XXX: should duplicate packet data in DV case *///發出信號,表明當前隊列中有數據了,通知等待中的讀線程可以取數據了SDL_CondSignal(q->cond);return 0; }

對于packet_queue_put_private主要完成3件事

  • 計算serial。serial標記了這個節點內的數據是何時的。?般情況下新增節點與上?個節點的serial是? 樣的,但當隊列中加??個flush_pkt后,后續節點的serial會?之前?1,?來區別不同播放序列的 packet.
  • 節點?隊列操作。
  • 隊列屬性操作。更新隊列中節點的數?、占?字節數(含AVPacket.data的??)及其時?。主要?來 控制Packet隊列的??,我們PacketQueue鏈表式的隊列,在內存充?的條件下我們可以?限put? packet,如果我們要控制隊列??,則需要通過其變量size、duration、nb_packets三者單?或者綜 合去約束隊列的節點的數量,具體在read_thread進?分析。

packet_queue_get()

從隊列中取?個節點:

/** * @brief packet_queue_get * @param q 隊列 * @param pkt 輸出參數,即MyAVPacketList.pkt * @param block 調?者是否需要在沒節點可取的情況下阻塞等待 * @param serial 輸出參數,即MyAVPacketList.serial * @return <0: aborted; =0: no packet; >0: has packet *//* return < 0 if aborted, 0 if no packet and > 0 if packet. */ static int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block, int *serial) {MyAVPacketList *pkt1;int ret;SDL_LockMutex(q->mutex);// 加鎖for (;;) {if (q->abort_request) {ret = -1;break;}pkt1 = q->first_pkt;//MyAVPacketList *pkt1; 從隊頭拿數據if (pkt1) {//隊列中有數據q->first_pkt = pkt1->next;//隊頭移到第?個節點if (!q->first_pkt)q->last_pkt = NULL;q->nb_packets--;//節點數減1q->size -= pkt1->pkt.size + sizeof(*pkt1);//cache??扣 除?個節點q->duration -= pkt1->pkt.duration;//返回AVPacket,這?發??次AVPacket結構體拷?,AVPacket的data 只拷?了指針*pkt = pkt1->pkt;if (serial) //如果需要輸出serial,把serial輸出*serial = pkt1->serial;av_free(pkt1);//釋放節點內存,只是釋放節點,?不是釋放AVPa cketret = 1;break;} else if (!block) {//隊列中沒有數據,且?阻塞調?ret = 0;break;} else {//隊列中沒有數據,且阻塞調?//這?沒有break。for循環的另?個作?是在條件變量滿?后重復上述代碼取出節點SDL_CondWait(q->cond, q->mutex);}}SDL_UnlockMutex(q->mutex);// 釋放鎖return ret; }

該函數整體流程:

  • 加鎖
  • 進?for循環,如果需要退出for循環,則break;當沒有數據可讀且block為1時則等待
    • ret = -1 終?獲取packet
    • ret = 0 沒有讀取到packet
    • ret = 1 獲取到了packet
  • 釋放鎖

如果有取到數據,主要分3個步驟:

  • 隊列操作:出隊列操作; nb_packets相應-1; duration 的也相應減少, size也相應占?的字節 ??(pkt1->pkt.size + sizeof(*pkt1))
  • 給輸出參數賦值:就是MyAVPacketList的成員傳遞給輸出參數pkt和serial
  • 釋放節點內存:釋放放?隊列時申請的節點內存(注意是節點內存?不是AVPacket的數據的內存)
  • packet_queue_put_nullpacket()

    放?“空包”(nullpacket)。放?空包意味著流的結束,?般在媒體數據讀取完成的時候放?空包。放? 空包,?的是為了沖刷解碼器,將編碼器??所有frame都讀取出來:

    static int packet_queue_put_nullpacket(PacketQueue *q, int stream_index) {AVPacket pkt1, *pkt = &pkt1;av_init_packet(pkt);pkt->data = NULL;pkt->size = 0;pkt->stream_index = stream_index;return packet_queue_put(q, pkt); }

    ?件數據讀取完畢后刷?空包。

    packet_queue_flush()

    packet_queue_flush?于將packet隊列中的所有節點清除,包括節點對應的AVPacket。?如?于退出播 放和seek播放:

    • 退出播放,則要清空packet queue的節點
    • seek播放,要清空seek之前緩存的節點數據,以便插?新節點數據
    static void packet_queue_flush(PacketQueue *q) {MyAVPacketList *pkt, *pkt1;SDL_LockMutex(q->mutex);for (pkt = q->first_pkt; pkt; pkt = pkt1) {pkt1 = pkt->next;av_packet_unref(&pkt->pkt);// 釋放AVPacket的數據av_freep(&pkt);}q->last_pkt = NULL;q->first_pkt = NULL;q->nb_packets = 0;q->size = 0;q->duration = 0;SDL_UnlockMutex(q->mutex); }

    函數主體的for循環是隊列遍歷,遍歷過程釋放節點和AVPacket(AVpacket對應的數據也被釋放掉)。最后 將PacketQueue的屬性恢復為空隊列狀態。

    PacketQueue總結

    前?我們分析了PacketQueue的實現和主要的操作?法,現在總結下兩個關鍵的點:
    第?,PacketQueue的內存管理

    MyAVPacketList的內存是完全由PacketQueue維護的,在put的時候malloc,在get的時候free。 AVPacket分兩塊:

    • ?部分是AVPacket結構體的內存,這部分從MyAVPacketList的定義可以看出是和MyAVPacketList 共存亡的。
    • 另?部分是AVPacket字段指向的內存,這部分?般通過 av_packet_unref 函數釋放。?般情況 下,是在get后由調?者負責? av_packet_unref 函數釋放。特殊的情況是當碰到 packet_queue_flush 或put失敗時,這時需要隊列??處理。

    第?,serial的變化過程:

    如上圖所示,左邊是隊頭,右邊是隊尾,從左往右標注了4個節點的serial,以及放?對應節點時queue的 serial。
    可以看到放?flush_pkt的時候后,serial增加了1.
    假設,現在要從隊頭取出?個節點,那么取出的節點是serial 1,?PacketQueue?身的queue已經增?到 了2。

    PacketQueue設計思路:

  • 設計?個多線程安全的隊列,保存AVPacket,同時統計隊列內已緩存的數據??。(這個統計數據會 ?來后續設置要緩存的數據量)
  • 引?serial的概念,區別前后數據包是否連續,主要應?于seek操作。
  • 設計了兩類特殊的packet——flush_pkt和nullpkt(類似?于多線程編程的事件模型——往隊列中放? flush事件、放?null事件),我們在?頻輸出、視頻輸出、播放控制等模塊時也會繼續對flush_pkt和 nullpkt的作?展開分析。
  • struct Frame 和 FrameQueue隊列

    1 Frame

    typedef struct Frame { AVFrame *frame; // 指向數據幀 AVSubtitle sub; // ?于字幕 int serial; // 播放序列,在seek的操作時serial會變化 double pts; // 時間戳,單位為秒 double duration; // 該幀持續時間,單位為秒 int64_t pos; // 該幀在輸??件中的字節位置 int width; // 圖像寬度 int height; // 圖像?讀 int format; // 對于圖像為(enum AVPixelFormat), // 對于聲?則為(enum AVSampleFormat) AVRational sar; // 圖像的寬??,如果未知或未指定則為0/1 int uploaded; // ?來記錄該幀是否已經顯示過? int flip_v; // =1則旋轉180, = 0則正常播放 } Frame;

    真正存儲解碼后?視頻數據的結構體為AVFrame ,存儲字幕則使?AVSubtitle,該Frame的設計是為了? 頻、視頻、字幕幀通?,所以Frame結構體的設計類似AVFrame,部分成員變量只對不同類型有作?,? 如sar只對視頻有作?。

    ??也包含了serial播放序列(每次seek時都切換serial),sar(圖像的寬??(16:9,4:3…),該值來 ?AVFrame結構體的sample_aspect_ratio變量)。

    2 FrameQueue

    typedef struct FrameQueue { Frame queue[FRAME_QUEUE_SIZE]; // FRAME_QUEUE_SIZE 最? size, 數字太?時會占??量的內存,需要注意該值的設置 int rindex; // 讀索引。待播放時讀取此幀進?播 放,播放后此幀成為上?幀 int windex; // 寫索引 int size; // 當前總幀數 int max_size; // 可存儲最?幀數 int keep_last; // = 1說明要在隊列??保持最后? 幀的數據不釋放,只在銷毀隊列的時候才將其真正釋放 int rindex_shown; // 初始化為0,配合keep_last=1 使? SDL_mutex *mutex; // 互斥量 SDL_cond *cond; // 條件變量 PacketQueue *pktq; // 數據包緩沖隊列 } FrameQueue;

    FrameQueue是?個環形緩沖區(ring buffer),是?數組實現的?個FIFO。數組?式的環形緩沖區適合于 事先明確了緩沖區的最?容量的情形。

    ffplay中創建了三個frame_queue:?頻frame_queue,視頻frame_queue,字幕frame_queue。每? 個frame_queue?個寫端?個讀端,寫端位于解碼線程,讀端位于播放線程。

    FrameQueue的設計?如PacketQueue復雜,引?了讀取節點但節點不出隊列的操作、讀取下?節點也不 出隊列等等的操作,FrameQueue操作提供以下?法:

    • frame_queue_unref_item:釋放Frame??的AVFrame和 AVSubtitle
    • frame_queue_init:初始化隊列
    • frame_queue_destory:銷毀隊列
    • frame_queue_signal:發送喚醒信號
    • frame_queue_peek:獲取當前Frame,調?之前先調?
    • frame_queue_nb_remaining確保有frame可 讀
    • frame_queue_peek_next:獲取當前Frame的下?Frame,調?之前先調?
    • frame_queue_nb_remaining確保?少有2 Frame在隊列
    • frame_queue_peek_last:獲取上?Frame
    • frame_queue_peek_writable:獲取?個可寫Frame,可以以阻塞或?阻塞?式進?
    • frame_queue_peek_readable:獲取?個可讀Frame,可以以阻塞或?阻塞?式進?
    • frame_queue_push:更新寫索引,此時Frame才真正?隊列,隊列節點Frame個數加1
    • frame_queue_next:更新讀索引,此時Frame才真正出隊列,隊列節點Frame個數減1,內部調?
    • frame_queue_unref_item是否對應的AVFrame和AVSubtitle
    • frame_queue_nb_remaining:獲取隊列Frame節點個數
    • frame_queue_last_pos:獲取最近播放Frame對應數據在媒體?件的位置,主要在seek時使?

    frame_queue_init() 初始化

    static int frame_queue_init(FrameQueue *f, PacketQueue *pktq, int max_size, int keep_last) {int i;memset(f, 0, sizeof(FrameQueue));if (!(f->mutex = SDL_CreateMutex())) {av_log(NULL, AV_LOG_FATAL, "SDL_CreateMutex(): %s\n", SDL_GetError());return AVERROR(ENOMEM);}if (!(f->cond = SDL_CreateCond())) {av_log(NULL, AV_LOG_FATAL, "SDL_CreateCond(): %s\n", SDL_GetError());return AVERROR(ENOMEM);}f->pktq = pktq;f->max_size = FFMIN(max_size, FRAME_QUEUE_SIZE);f->keep_last = !!keep_last;for (i = 0; i < f->max_size; i++)if (!(f->queue[i].frame = av_frame_alloc()))// 分配AVFrame結 構體return AVERROR(ENOMEM);return 0; }

    隊列初始化函數確定了隊列??,將為隊列中每?個節點的frame( f->queue[i].frame )分配內 存,注意只是分配Frame對象本身,?不關注Frame中的數據緩沖區。Frame中的數據緩沖區是 AVBuffer,使?引?計數機制。
    f->max_size 是隊列的??,此處值為16(由FRAME_QUEUE_SIZE定義),實際分配的時候視 頻為3,?頻為9,字幕為16,因為這?存儲的是解碼后的數據,不宜設置過?,?如視頻當為 1080p時,如果為YUV420p格式,?幀就有3110400字節。

    • #define VIDEO_PICTURE_QUEUE_SIZE 3 // 圖像幀緩存數量
    • #define SUBPICTURE_QUEUE_SIZE 16 // 字幕幀緩存數量
    • #define SAMPLE_QUEUE_SIZE 9 // 采樣幀緩存數量
    • #define FRAME_QUEUE_SIZE FFMAX(SAMPLE_QUEUE_SIZE,
    • FFMAX(VIDEO_PICTURE_QUEUE_SIZE, SUBPICTURE_QUEUE_SIZE))

    f->keep_last 是隊列中是否保留最后?次播放的幀的標志。
    f->keep_last = !!keep_last 是將int取值的keep_last轉換為boot取值(0或1)。

    frame_queue_destory()銷毀

    static void frame_queue_destory(FrameQueue *f) {int i;for (i = 0; i < f->max_size; i++) {Frame *vp = &f->queue[i];// 釋放對vp->frame中的數據緩沖區的引?,注意不是釋放frame對象本身frame_queue_unref_item(vp);// 釋放vp->frame對象av_frame_free(&vp->frame);}SDL_DestroyMutex(f->mutex);SDL_DestroyCond(f->cond); }

    隊列銷毀函數對隊列中的每個節點作了如下處理:

  • frame_queue_unref_item(vp) 釋放本隊列對vp->frame中AVBuffer的引?
  • av_frame_free(&vp->frame) 釋放vp->frame對象本身
  • frame_queue_peek_writable()獲取可寫

    Frame frame_queue_push()?隊列

    FrameQueue寫隊列的步驟和PacketQueue不同,分了3步進?:

  • 調?frame_queue_peek_writable獲取可寫的Frame,如果隊列已滿則等待
  • 獲取到Frame后,設置Frame的成員變量
  • 再調?frame_queue_push更新隊列的寫索引,真正將Frame?隊列
  • Frame *frame_queue_peek_writable(FrameQueue *f); // 獲取可寫幀 void frame_queue_push(FrameQueue *f); // 更新寫索引

    通過實例看?下寫隊列的?法:

    // video AVFrame ?隊列 static int queue_picture(VideoState *is, AVFrame *src_frame, double pts, double duration, int64_t pos, int serial) {Frame *vp;#if defined(DEBUG_SYNC)printf("frame_type=%c pts=%0.3f\n",av_get_picture_type_char(src_frame->pict_type), pts); #endifif (!(vp = frame_queue_peek_writable(&is->pictq)))// 檢測隊列是否 有可寫空間return -1;// Frame隊列滿了則返回-1// 執?到這步說已經獲取到了可寫?的Framevp->sar = src_frame->sample_aspect_ratio;vp->uploaded = 0;vp->width = src_frame->width;vp->height = src_frame->height;vp->format = src_frame->format;vp->pts = pts;vp->duration = duration;vp->pos = pos;vp->serial = serial;set_default_window_size(vp->width, vp->height, vp->sar);// 將src中所有數據拷?到dst 中,并復位srcav_frame_move_ref(vp->frame, src_frame);frame_queue_push(&is->pictq);// 更新寫索引位置return 0; }

    上??段代碼是視頻解碼線程向視頻frame_queue中寫??幀的代碼,步驟如下:

  • frame_queue_peek_writable(&is->pictq) 向隊列尾部申請?個可寫的幀空間,若隊列已滿 ?空間可寫,則等待(由SDL_cond *cond控制,由frame_queue_next或frame_queue_signal觸發喚 醒)
  • av_frame_move_ref(vp->frame, src_frame) 將src_frame中所有數據拷?到vp-> frame并復位src_frame,vp-> frame中AVBuffer使?引?計數機制,不會執?AVBuffer的拷?動作,僅是修改指針指向值。為避免內存泄漏,在 av_frame_move_ref(dst, src) 之前應先調? av_frame_unref(dst) ,這? 沒有調?,是因為frame_queue在刪除?個節點時,已經釋放了frame及frame中的AVBuffer。
  • frame_queue_push(&is->pictq) 此步僅將frame_queue中的寫索引加1,實際的數據寫?在此 步之前已經完成。
  • frame_queue_peek_writable獲取可寫Frame指針

    // 獲取可寫指針 static Frame *frame_queue_peek_writable(FrameQueue *f) {/* wait until we have space to put a new frame */SDL_LockMutex(f->mutex);while (f->size >= f->max_size &&!f->pktq->abort_request) {/* 檢查是否需要退出 */SDL_CondWait(f->cond, f->mutex);}SDL_UnlockMutex(f->mutex);if (f->pktq->abort_request)/* 檢查是不是要退出 */return NULL;return &f->queue[f->windex]; }

    向隊列尾部申請?個可寫的幀空間,若?空間可寫,則等待。
    這?最需要體會到的是abort_request的使?,在等待時如果播放器需要退出則將abort_request = 1,那 frame_queue_peek_writable函數可以知道是正常frame可寫喚醒,還是其他喚醒。

    frame_queue_push()

    // 更新寫索引 static void frame_queue_push(FrameQueue *f) {if (++f->windex == f->max_size)f->windex = 0;SDL_LockMutex(f->mutex);f->size++;SDL_CondSignal(f->cond);// 當frame_queue_peek_readable在等待時 則可以喚醒SDL_UnlockMutex(f->mutex); }

    向隊列尾部壓??幀,只更新計數與寫指針,因此調?此函數前應將幀數據寫?隊列相應位 置。SDL_CondSignal(f->cond);可以喚醒讀frame_queue_peek_readable。

    frame_queue_peek_readable() 獲取可讀

    Frame frame_queue_next()出隊列

    寫隊列中,應?程序寫??個新幀后通常總是將寫索引加1。?讀隊列中,“讀取”和“更新讀索引(同時刪除 舊幀)”?者是獨?的,可以只讀取?不更新讀索引,也可以只更新讀索引(只刪除)?不讀取(只有更新讀索 引的時候才真正釋放對應的Frame數據)。?且讀隊列引?了是否保留已顯示的最后?幀的機制,導致讀 隊列?寫隊列要復雜很多。
    讀隊列和寫隊列步驟是類似的,基本步驟如下:

  • 調?frame_queue_peek_readable獲取可讀Frame;
  • 如果需要更新讀索引(出隊列該節點)則調?frame_queue_peek_next;
  • 讀隊列涉及如下函數:

    Frame *frame_queue_peek_readable(FrameQueue *f); // 獲取可讀Frame指 針(若讀空則等待) Frame *frame_queue_peek(FrameQueue *f); // 獲取當前Frame指針 Frame *frame_queue_peek_next(FrameQueue *f); // 獲取下?Frame指針 Frame *frame_queue_peek_last(FrameQueue *f); // 獲取上?Frame指針 void frame_queue_next(FrameQueue *f); // 更新讀索引(同時刪除 舊frame)

    通過實例看?下讀隊列的?法:

    static void video_refresh(void *opaque, double *remaining_time) {...... if (frame_queue_nb_remaining(&is->pictq) == 0{ // 所有幀已顯示 // nothing to do, no picture to display in the queue } else { Frame *vp, *lastvp;lastvp = frame_queue_peek_last(&is->pictq); // 上?幀:上次 已顯示的幀 vp = frame_queue_peek(&is->pictq); // 當前幀:當前 待顯示的幀 frame_queue_next(&is->pictq); // 出隊列,并更 新rindex video_display(is)-->video_image_display()-->frame_queue_peek _last(); } ...... }

    上??段代碼是視頻播放線程從視頻frame_queue中讀取視頻幀進?顯示的基本步驟,其他 代碼已省略,只保留了讀隊列部分。 記lastvp為上?次已播放的幀,vp為本次待播放的幀,下圖中?框中的數字表示顯示序列中幀 的序號:

    在啟?keep_last機制后,rindex_shown值總是為1,rindex_shown確保了最后播放的?幀總保留在隊列 中。
    假設某次進? video_refresh() 的時刻為T0,下次進?的時刻為T1。在T0時刻,讀隊列的步驟如下:

  • rindex表示上?次播放的幀lastvp,本次調? video_refresh() 中,lastvp會被刪除,rindex會加 1,即是當調?frame_queue_next刪除的是lastvp,?不是當前的vp,當前的vp轉為lastvp。
  • rindex+rindex_shown表示本次待播放的幀vp,本次調? video_refresh() 中,vp會被讀出播放 圖中已播放的幀是灰??框,本次待播放的幀是紅??框,其他未播放的幀是綠??框,隊列中空位置為???框。
  • rindex+rindex_shown+1表示下?幀nextvp
  • frame_queue_nb_remaining()獲取隊列的size

    /* return the number of undisplayed frames in the queue */ static int frame_queue_nb_remaining(FrameQueue *f) { return f->size - f->rindex_shown; }

    rindex_shown為1時,隊列中總是保留了最后?幀lastvp(灰??框)。需要注意的時候rindex_shown的值 就是0或1,不存在變為2,3等的可能。在計算隊列當前Frame數量是不包含lastvp。

    rindex_shown的引?增加了讀隊列操作的理解難度。?多數讀操作函數都會?到這個變量。 通過 FrameQueue.keep_last 和 FrameQueue.rindex_shown 兩個變量實現了保留最后?次播放幀 的機制。

    是否啟?keep_last機制是由全局變量 keep_last 值決定的,在隊列初始化函數 frame_queue_init() 中有 f->keep_last = !!keep_last; ,?在更新讀指針函數 frame_queue_next() 中如果啟?keep_last機制,則 f->rindex_shown 值為1。

    我們具體分析下 frame_queue_next() 函數:

    /* 釋放當前frame,并更新讀索引rindex,* 當keep_last為1, rindex_show為0時不去更新rindex,也不釋放當前frame */ static void frame_queue_next(FrameQueue *f) {if (f->keep_last && !f->rindex_shown) {f->rindex_shown = 1;// 第?次調?置為1return;}frame_queue_unref_item(&f->queue[f->rindex]);if (++f->rindex == f->max_size)f->rindex = 0;SDL_LockMutex(f->mutex);f->size--;SDL_CondSignal(f->cond);SDL_UnlockMutex(f->mutex); }

    主要步驟:

    • 在啟?keeplast時,如果rindex_shown為0則將其設置為1,并返回。此時并不會更新讀索引。也就是 說keeplast機制實質上也會占?著隊列Frame的size,當調?frame_queue_nb_remaining()獲取size 時并不能將其計算?size;
    • 釋放Frame對應的數據(?如AVFrame的數據),但不釋放Frame本身
    • 更新讀索引
    • 釋放喚醒信號,以喚醒正在等待寫?的線程。

    frame_queue_peek_readable()的具體實現

    static Frame *frame_queue_peek_readable(FrameQueue *f) {/* wait until we have a readable a new frame */SDL_LockMutex(f->mutex);while (f->size - f->rindex_shown <= 0 &&!f->pktq->abort_request) {SDL_CondWait(f->cond, f->mutex);}SDL_UnlockMutex(f->mutex);if (f->pktq->abort_request)return NULL;return &f->queue[(f->rindex + f->rindex_shown) % f->max_size]; }

    從隊列頭部讀取?幀(vp),只讀取不刪除,若?幀可讀則等待。這個函數和 frame_queue_peek() 的區 別僅僅是多了不可讀時等待的操作。

    frame_queue_peek()獲取當前幀

    /* 獲取隊列當前Frame, 在調?該函數前先調?frame_queue_nb_remaining確保有frame 可讀 */ static Frame *frame_queue_peek(FrameQueue *f) {return &f->queue[(f->rindex + f->rindex_shown) % f->max_size]; }

    frame_queue_peek_next()獲取下?幀

    /* 獲取當前Frame的下?Frame, 此時要確保queue???少有2個Frame */ static Frame *frame_queue_peek_next(FrameQueue *f) {return &f->queue[(f->rindex + f->rindex_shown + 1) % f->max_size]; }

    frame_queue_peek_last()獲取上?幀

    /* 獲取last Frame: * 當rindex_shown=0時,和frame_queue_peek效果?樣 * 當rindex_shown=1時,讀取的是已經顯示過的frame */ static Frame *frame_queue_peek_last(FrameQueue *f) {return &f->queue[f->rindex]; }

    struct AudioParams ?頻參數

    typedef struct AudioParams {int freq;// 采樣率int channels;// 通道數int64_t channel_layout;// 通道布局,?如2.1聲道,5.1聲道等enum AVSampleFormat fmt;// ?頻采樣格式,?如AV_SAMPLE_FM T_S16表示為有符號16bit深度,交錯排列模式int frame_size;// ?個采樣單元占?的字節數(?如2通道時,則左右通道各采樣?次合成?個采樣單元)int bytes_per_sec;// ?秒時間的字節數,?如采樣率48Khz,2 channel,16bit,則?秒48000*2*16/8=192000 } AudioParams;

    struct Decoder解碼器封裝

    typedef struct Decoder {AVPacket pkt;PacketQueue *queue;// 數據包隊列AVCodecContext *avctx;// 解碼器上下?int pkt_serial;// 包序列int finished;// =0,解碼器處于?作狀態;=?0,解碼器處于 空閑狀態 int packet_pending;// =0,解碼器處于異常狀態,需要考慮重置解碼 器;=1,解碼器處于正常狀態SDL_cond *empty_queue_cond;// 檢查到packet隊列空時發送 signal緩 存read_thread讀取數據 int64_t start_pts;// 初始化時是stream的start timeAVRational start_pts_tb;// 初始化時是stream的time_baseint64_t next_pts;// 記錄最近?次解碼后的frame的pts,當 解出來的部分幀沒有有效的pts時則使?next_pts進?推算AVRational next_pts_tb;// next_pts的單位SDL_Thread *decoder_tid;// 線程句柄 } Decoder;

    總結

    以上是生活随笔為你收集整理的ffplay播放器的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    主站蜘蛛池模板: 国产成人无遮挡在线视频 | 69视频免费| 日本三区在线 | 波多野结衣mp4| 在线亚洲精品 | 欧美人与禽zozzo性之恋的特点 | 久久婷婷综合色 | 欧美黄色片网站 | 精品人妻少妇一区二区 | 一区二区欧美精品 | 国产福利在线观看 | 欧美精品久久久久久久久久 | 人人艹在线| 成人毛片网站 | 欲色综合 | 日韩美女少妇 | 欧美一级片a| 琪琪电影午夜理论片八戒八戒 | 28一20岁女人一级 | 肉性天堂 | 朋友人妻少妇精品系列 | 911美女片黄在线观看游戏 | 中国黄色录像 | 农村妇女毛片 | 小镇姑娘国语版在线观看免费 | 久久久久99精品成人片毛片 | 精品无码久久久久久国产 | 四虎色网 | 欧美1页 | 成年人在线观看视频免费 | 大陆农村乡下av | 蜜桃视频一区二区 | 亚洲视频一二区 | 伊人色综合久久天天 | 亚洲一区二区三区四区在线观看 | 中文字幕在线看高清电影 | 亚洲一二三av | 97在线精品视频 | 操操操操操操操操操操 | 久久综合久久久 | 神马午夜嘿嘿 | 又粗又大又硬毛片免费看 | 欧美一级片在线看 | 肉体粗喘娇吟国产91 | 午夜久久久久久久久 | 老牛影视少妇在线观看 | 国产第一页在线播放 | 中文字幕精品国产 | 日韩视频免费播放 | 色屁屁www| 毛片av网址 | 337p色噜噜 | 韩国一级淫一片免费放 | 少妇视频网 | 国产在线一区二区三区四区 | 中文日韩字幕 | 99视频 | 欧美一级淫片bbb一84 | 日韩人妻无码一区二区三区99 | 天天草影院 | 黄网站欧美内射 | 中文字幕亚洲精品在线 | 天天看天天摸 | 玖玖国产精品视频 | 狼人色综合 | 国产素人自拍 | 欧美性猛交xxxx久久久 | 欧美在线黄 | 亚洲熟女乱色综合亚洲av | 人操人 | 国产又爽又黄无码无遮挡在线观看 | 日韩女优中文字幕 | 欧美日日操 | 国产三区精品 | 日本wwwxx | 精品午夜一区二区 | 久久久久亚洲av成人人电影 | 亚洲天堂一 | 夜福利视频| 精品1区2区| 日韩欧美二区三区 | 欧美日韩精品久久 | 亚洲天堂色图 | 神马影院一区二区三区 | 天天躁日日躁狠狠躁喷水 | 欧美一级黄色片网站 | 未满十八岁勿进 | 欧美特一级| 亚洲欧美www | 亚洲国产精品成人综合久久久 | av在线免| 亚洲熟妇av一区二区三区漫画 | 女同亚洲精品一区二区三 | 日本三级456 | 日韩av三级在线观看 | 国产卡一卡二 | 久久久久久久中文字幕 | 男生和女生差差的视频 | 丁香色欲久久久久久综合网 |