ffplay.c学习-1-框架及数据结构
生活随笔
收集整理的這篇文章主要介紹了
ffplay.c学习-1-框架及数据结构
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
ffplay.c學習-1-框架及數據結構
目錄
1. ffplay.c的意義
2. FFplay框架分析
1. 播放器初始化
2. 線程的劃分
3. packet隊列的設計
4. frame隊列的設計
5. ?視頻同步
6. ?頻處理
7. 視頻處理
8. 播放器控制
3. 數據結構分析
1. struct VideoState 播放器封裝
typedef struct VideoState {SDL_Thread *read_tid; // 讀線程句柄AVInputFormat *iformat; // 指向demuxerint abort_request; // =1時請求退出播放int force_refresh; // =1時需要刷新畫面,請求立即刷新畫面的意思int paused; // =1時暫停,=0時播放int last_paused; // 暫存“暫停”/“播放”狀態int queue_attachments_req; // 隊列附件,用于mp3等專輯封面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 masterdouble audio_clock; // 當前音頻幀的PTS+當前幀Durationint audio_clock_serial; // 播放序列,seek可改變此值// 以下4個參數 非audio master同步方式使用double audio_diff_cum; // used for AV difference average computationdouble audio_diff_avg_coef;double audio_diff_threshold;int audio_diff_avg_count;// endAVStream *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; // 待播放的一幀音頻數據(audio_buf指向)的大小unsigned int audio_buf1_size; // 申請到的音頻緩沖區audio_buf1的實際尺寸int audio_buf_index; // 更新拷貝位置 當前音頻幀中已拷入SDL音頻緩沖區// 的位置索引(指向第一個待拷貝字節)// 當前音頻幀中尚未拷入SDL音頻緩沖區的數據量:// audio_buf_size = audio_buf_index + audio_write_buf_sizeint audio_write_buf_size;int audio_volume; // 音量int muted; // =1靜音,=0則正常struct AudioParams audio_src; // 音頻frame的參數 #if CONFIG_AVFILTERstruct AudioParams audio_filter_src; #endifstruct AudioParams audio_tgt; // SDL支持的音頻參數,重采樣轉換:audio_src->audio_tgtstruct SwrContext *swr_ctx; // 音頻重采樣contextint frame_drops_early; // 丟棄視頻packet計數int frame_drops_late; // 丟棄視頻frame計數enum ShowMode {SHOW_MODE_NONE = -1, // 無顯示SHOW_MODE_VIDEO = 0, // 顯示視頻SHOW_MODE_WAVES, // 顯示波浪,音頻SHOW_MODE_RDFT, // 自適應濾波器SHOW_MODE_NB} show_mode;// 音頻波形顯示使用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; // 音頻TextureSDL_Texture *sub_texture; // 字幕顯示SDL_Texture *vid_texture; // 視頻顯示int subtitle_stream; // 字幕流索引AVStream *subtitle_st; // 字幕流PacketQueue subtitleq; // 字幕packet隊列double frame_timer; // 記錄最后一幀播放的時刻double frame_last_returned_time; // 上一次返回時間double frame_last_filter_delay; // 上一個過濾器延時int video_stream; // 視頻流索引AVStream *video_st; // 視頻流PacketQueue videoq; // 視頻隊列double max_frame_duration; // 一幀最大間隔. above this, we consider the jump a timestamp discontinuitystruct SwsContext *img_convert_ctx; // 視頻尺寸格式變換struct SwsContext *sub_convert_ctx; // 字幕尺寸格式變換int eof; // 是否讀取結束char *filename; // 文件名int width, height, xleft, ytop; // 寬、高,x起始坐標,y起始坐標int step; // =1 步進播放模式, =0 其他模式#if CONFIG_AVFILTERint vfilter_idx;AVFilterContext *in_video_filter; // the first filter in the video chainAVFilterContext *out_video_filter; // the last filter in the video chainAVFilterContext *in_audio_filter; // the first filter in the audio chainAVFilterContext *out_audio_filter; // the last filter in the audio chainAVFilterGraph *agraph; // audio filter graph #endif// 保留最近的相應audio、video、subtitle流的steam indexint last_video_stream, last_audio_stream, last_subtitle_stream;SDL_cond *continue_read_thread; // 當讀取數據隊列滿了后進入休眠時,可以通過該condition喚醒讀線程 } VideoState;2. struct Clock 時鐘封裝
// 這里講的系統時鐘 是通過av_gettime_relative()獲取到的時鐘,單位為微妙 typedef struct Clock {double pts; // 時鐘基礎, 當前幀(待播放)顯示時間戳,播放后,當前幀變成上一幀// 當前pts與當前系統時鐘的差值, audio、video對于該值是獨立的double pts_drift; // clock base minus time at which we updated the clock// 當前時鐘(如視頻時鐘)最后一次更新時間,也可稱當前時鐘時間double last_updated; // 最后一次更新的系統時鐘double speed; // 時鐘速度控制,用于控制播放速度// 播放序列,所謂播放序列就是一段連續的播放動作,一個seek操作會啟動一段新的播放序列int serial; // clock is based on a packet with this serialint paused; // = 1 說明是暫停狀態// 指向packet_serialint *queue_serial; /* pointer to the current packet queue serial, used for obsolete clock detection */ } Clock;3. struct MyAVPacketList和PacketQueue隊列
1. packet_queue_init()
2. packet_queue_destroy()
3. packet_queue_start()
4. packet_queue_abort()
5. packet_queue_put()
6. packet_queue_get()
7. packet_queue_put_nullpacket()
8. packet_queue_flush()
9. PacketQueue總結
1. PacketQueue的內存管理
2. serial的變化過程:
3. PacketQueue設計思路:
4. struct Frame 和 FrameQueue隊列
1. Frame
/* Common struct for handling all types of decoded data and allocated render buffers. */ // 用于緩存解碼后的數據 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; // 圖像的寬高比(16:9,4:3...),如果未知或未指定則為0/1int uploaded; // 用來記錄該幀是否已經顯示過?int flip_v; // =1則旋轉180, = 0則正常播放 } Frame;2. FrameQueue
/* 這是一個循環隊列,windex是指其中的首元素,rindex是指其中的尾部元素. */ 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;1. frame_queue_init() 初始化
/* 初始化FrameQueue,視頻和音頻keep_last設置為1,字幕設置為0 */ 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; }2. 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); }3. 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]; }4. 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); // 當_readable在等待時則可以喚醒SDL_UnlockMutex(f->mutex); }5. 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]; }6. frame_queue_peek_readable() 獲取可讀Fram
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]; }7. frame_queue_next()出隊列
8. 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; // 注意這里為什么要減去f->rindex_shown }主要步驟:
frame_queue_peek_readable()的具體實現
9. 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]; }10. frame_queue_peek_next()獲取下?幀
/* 獲取當前Frame的下一Frame, 此時要確保queue里面至少有2個Frame */ // 不管你什么時候調用,返回來肯定不是 NULL static Frame *frame_queue_peek_next(FrameQueue *f) {return &f->queue[(f->rindex + f->rindex_shown + 1) % f->max_size]; }11. 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]; // 這時候才有意義 }5. struct AudioParams ?頻參數
typedef struct AudioParams {int freq; // 采樣率int channels; // 通道數int64_t channel_layout; // 通道布局,比如2.1聲道,5.1聲道等enum AVSampleFormat fmt; // 音頻采樣格式,比如AV_SAMPLE_FMT_S16表示為有符號16bit深度,交錯排列模式。int frame_size; // 一個采樣單元占用的字節數(比如2通道時,則左右通道各采樣一次合成一個采樣單元)int bytes_per_sec; // 一秒時間的字節數,比如采樣率48Khz,2 channel,16bit,則一秒48000*2*16/8=192000 } AudioParams;6. 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.c学习-1-框架及数据结构的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: FFmpeg Filter基本使用
- 下一篇: ffplay.c学习-2-数据读取线程