版權聲明:本文由張坤原創文章,轉載請註明出處:
文章原文連接:https://www.qcloud.com/community/article/535574001486630869架構
來源:騰雲閣 https://www.qcloud.com/communityide
視頻播放器原理其實大抵相同,都是對音視頻幀序列的控制。只是一些播放器在音視頻同步上可能作了更爲複雜的幀預測技術,來保證音頻和視頻有更好的同步性。函數
ffplay是FFMpeg自帶的播放器,使用了 ffmpeg解碼庫和用於視頻渲染顯示的sdl 庫,也是業界播放器最初參考的設計標準。本文對ffplay源碼進行分析,試圖用更基礎而系統的方法,來嘗試解開播放器的音視頻同步,以及播放/暫停、快進/後退的控制原理。ui
因爲FFMpeg自己的跨平臺特性,相比在移動端看音視頻代碼,在PC端利用VS查看和調試代碼,分析播放器原理,要高效迅速不少。編碼
因爲FFMpeg官方提供的ffmplay在console中進行使用不夠直觀,本文直接分析CSDN上將ffplay移植到VC的代碼(ffplay for MFC)進行分析。spa
文章目錄:
1、初探mp4文件
2、以最簡單播放器開始:FFmpeg解碼 + SDL顯示
3、先拋五個問題
4、ffplay代碼整體結構
5、視頻播放器的操做控制
5.1 ffplay所定義的關鍵結構體VideoState
5.2 補充基礎知識——PTS和DTS
5.2 如何控制音視頻同步
5.4 如何控制視頻的播放和暫停?
5.5 逐幀播放是如何作的?
5.6 快進和後退
6、 此次分析ffplay代碼的檢討總結.net
爲了讓你們對視頻文件有一個初步認識,首先來看對一個MP4文件的簡單分析,如圖1。線程
圖1 對MP4文件解參設計
從圖一咱們知道,每一個視頻文件都會有特定的封裝格式、比特率、時長等信息。視頻解複用以後,就劃分爲video_stream和audio_stream,分別對應視頻流和音頻流。3d
解複用以後的音視頻有本身獨立的參數,視頻參數包括編碼方式、採樣率、畫面大小等,音頻參數包括採樣率、編碼方式和聲道數等。
對解複用以後的音頻和視頻Packet進行解碼以後,就變成原始的音頻(PWM)和視頻(YUV/RGB)數據,才能夠在進行顯示和播放。
其實這已經差很少涉及到了,視頻解碼播放的大部分流程,整個視頻播放的流程如圖2所示。
圖2 視頻播放流程(圖摘自http://blog.csdn.net/leixiaohua1020/article/details/50534150)
爲將問題簡單化,先不考慮播放音頻,只播放視頻,代碼流程圖如圖3所示:
圖3 播放器流程圖(圖源見水印)
流程圖說明以下:
1.FFmpeg初始化的代碼比較固定,主要目的就是爲了設置AVFormatContext實例中相關成員變量的值,調用av_register_all、avformat_open_input av_find_stream_info和avcodec_find_decoder等函數。
如圖4所示,初始化以後的AVFormatContext實例裏面具體的值,調用av_find_stream_info就是找到文件中的音視頻流數據,對其中的streams(包含音頻、視頻流)變量進行初始化。
圖4 AVFormatContext初始化實例
2.av_read_frame不斷讀取stream中的下一幀,對其進行解複用獲得視頻的AVPacket,隨後調用avcodec_decode_video2是視頻幀AVPacket進行解碼,獲得圖像幀AVFrame。
3.獲得AVFrame以後,接下來就是放到SDL中進行渲染顯示了,也很簡單,流程見下面代碼註釋:
SDL_Overlay *bmp;
//將解析獲得的AVFrame的數據拷貝到SDL_Overlay實例當中 SDL_LockYUVOverlay(bmp); bmp->pixels[0]=pFrameYUV->data[0]; bmp->pixels[2]=pFrameYUV->data[1]; bmp->pixels[1]=pFrameYUV->data[2]; bmp->pitches[0]=pFrameYUV->linesize[0]; bmp->pitches[2]=pFrameYUV->linesize[1]; bmp->pitches[1]=pFrameYUV->linesize[2]; SDL_UnlockYUVOverlay(bmp); //設置SDL_Rect,由於涉及到起始點和顯示大小,用rect進行表示。 SDL_Rect rect; rect.x = 0; rect.y = 0; rect.w = pCodecCtx->width; rect.h = pCodecCtx->height; //將SDL_Overlay數據顯示到SDL_Surface當中。 SDL_DisplayYUVOverlay(bmp, &rect); //延時40ms,留足ffmpeg取到下一幀並解碼該幀的時間,隨後繼續讀取下一幀 SDL_Delay(40);
由上面的原理可知,從幀流中獲取到AVPacket,而且解碼獲得AVFrame,渲染到SDL窗口中。
圖5 視頻播放狀態圖
對視頻播放的流程總結一下就是:讀取下一幀——>解碼——>播放——>不斷往復,狀態圖如圖5所示。
本文仍是以問題拋問題的思路,以逐步對每一個問題進行原理性分析,加深對音視頻解碼和播放的認識。如下這些問題也是每個播放器所須要面對的基礎問題和原理:
1.咱們在觀看電影時發現,電影能夠更換不一樣字幕,甚至不一樣音頻,好比中英文字幕和配音,最後在同一個畫面中進行顯示,視頻關於畫面、字幕和聲音是如何組合的?
其實每個視頻文件,讀取出來以後發現,都會被區分不一樣的流。爲了讓你們有更具體的理解,以FFMpeg中的代碼爲例,AVMediaType定義了具體的流類型:
enum AVMediaType { AVMEDIA_TYPE_VIDEO, //視頻流 AVMEDIA_TYPE_AUDIO, //音頻流 AVMEDIA_TYPE_SUBTITLE, //字幕流 };
利用av_read_frame讀取出音視頻幀以後,隨後就利用avcodec_decode_video2對視頻捷星解碼,或者調用avcodec_decode_audio4對音頻進行解碼,獲得能夠供渲染和顯示的音視頻原始數據。
圖像和字幕都將會以Surface或者texture的形式,就像Android中的SurfaceFlinger,將畫面不一樣模塊的顯示進行組合,生成一幅新的圖像,顯示在視頻畫面中。
2.既然視頻有幀率的概念,音頻有采樣率的概念,是否直接利用幀率就能夠控制音視頻的同步了呢?
每個視頻幀和音頻幀在時域上都對應於一個時間點,按道理來講只要控制每個音視頻幀的播放時間,就能夠實現同步。
但實際上,對每一幀顯示的時間上的精確控制是很難的,更況且音頻和視頻的解碼所需時間不一樣,極容易引發音視頻在時間上的不一樣步。
因此,播放器具體是如何作音視頻同步的呢?
3.視頻的音頻流、視頻流和字幕流,他們在時間上是連續的仍是離散的?不一樣流的幀數相同嗎?
因爲計算機只能數字模擬離散的世界,因此在時間上確定是離散的。那既然是離散的,他們的幀數是否相同呢?
視頻能夠理解爲諸多音頻幀、視頻幀和字幕幀在時間上的序列,他們在時間上的時長,跟視頻總時長是相同的,可是因爲每一個幀解碼時間不一樣,必然會致使他們在每幀的時間間隔不相同。
音頻原始數據自己就是採樣數據,因此是有固定時鐘週期。可是視頻假如想跟音頻進行同步的話,可能會出現跳幀的狀況,每一個視頻幀播放時間差,都會起伏不定,不是恆定週期。
因此結論是,三者在視頻總時長上播放的幀數確定是不同的。
4.視頻播放就是一系列的連續幀不停渲染。對視頻的控制操做包括:暫停和播放、快進和後退。那有沒有想過,每次快進/後退的幅度,以時間爲量度好,仍是以每次跳躍的幀數,就是每次快進是前進多長時間,仍是前進多少幀。 時間 VS 幀數?
由上面問題分析,咱們知道,視頻是以音頻流、視頻流和字幕流進行分流的,假如以幀數爲基礎,因爲不一樣流的幀數量不必定相同,以幀數爲單位,很容易致使三個流播放的不一致。
所以以時間爲量度,相對更好,直接搜尋mp4文件流,當前播放時間的前進或後退時長的seek時間點,隨後從新對文件流進行分流解析,就能夠達到快進和後退以後的音視頻同步效果。
咱們能夠看到絕大部分播放器,快進/倒退都是以時長爲步進的,咱們能夠看看ffplay是怎麼樣的,以及是如何實現的。
5.上一節中,實現的簡單播放器,解碼和播放都是在同一個線程中,解碼速度直接影響播放速度,從而將直接形成播放不流暢的問題。那如何在解碼可能出現速度不均勻的狀況下,進行流暢的視頻播放呢?
很容易想到,引入緩衝隊列,將視頻圖像渲染顯示和視頻解碼做爲兩個線程,視頻解碼線程往隊列中寫數據,視頻渲染線程從隊列中讀取數據進行顯示,這樣就能夠保證視頻是能夠流程播放的。
所以須要採用音頻幀、視頻幀和字幕幀的三個緩衝隊列,那如何保證音視頻播放的同步呢?
PTS是視頻幀或者音頻幀的顯示時間戳,到底是如何利用起來的,從而控制視頻幀、音頻幀以及字幕幀的顯示時刻呢?
那咱們就能夠探尋ffplay,到底是如何去作緩衝隊列控制的。
全部以上五個問題,咱們都將在對ffplay源代碼的探尋中,逐步找到更具體的解答。
圖6 ffplay代碼整體流程
網上有人作了ffplay的整體流程圖,如圖6。有了這幅圖,代碼看起來,就會輕鬆了不少。流程中具體包含的細節以下:
1.啓動定時器Timer,計時器40ms刷新一次,利用SDL事件機制,觸發從圖像幀隊列中讀取數據,進行渲染顯示;
2.stream_componet_open函數中,av_read_frame()讀取到AVPacket,隨後放入到音頻、視頻或字幕Packet隊列中;
3.video_thread,從視頻packet隊列中獲取AVPacket並進行解碼,獲得AVFrame圖像幀,放到VideoPicture隊列中。
4..audio_thread線程,同video_thread,對音頻Packet進行解碼;
5.subtitle_thread線程,同video_thread,對字幕Packet進行解碼。
視頻播放器的操做包括播放/暫停、快進/倒退、逐幀播放等,這些操做的實現原理是什麼呢,下面對其從代碼層面逐個進行分析。
與FFmpeg解碼相似,定義了一個AVFormatContext結構體,用於存儲文件名、音視頻流、解碼器等字段,供全局進行訪問。
ffplay也定義了一個結構體VideoState,經過對VideoState的分析,就能夠大致知道播放器基本實現原理。
typedef struct VideoState { // Demux解複用線程,讀視頻文件stream線程,獲得AVPacket,並對packet入棧 SDL_Thread *read_tid; //視頻解碼線程,讀取AVPacket,decode 爬出能夠成AVFrame併入隊 SDL_Thread *video_tid; //視頻播放刷新線程,定時播放下一幀 SDL_Thread *refresh_tid; int paused; //控制視頻暫停或播放標誌位 int seek_req; //進度控制標誌 int seek_flags; AVStream *audio_st; //音頻流 PacketQueue audioq; //音頻packet隊列 double audio_current_pts; //當前音頻幀顯示時間 AVStream *subtitle_st; //字幕流 PacketQueue subtitleq;//字幕packet隊列 AVStream *video_st; //視頻流 PacketQueue videoq;//視頻packet隊列 double video_current_pts; ///當前視頻幀pts double video_current_pts_drift; VideoPicture pictq[VIDEO_PICTURE_QUEUE_SIZE]; //解碼後的圖像幀隊列 }
從VideoState結構體中能夠看出:
1.解複用、視頻解碼和視頻刷新播放,分屬三個線程中,並行控制;
2.音頻流、視頻流、字幕流,都有本身的緩衝隊列,供不一樣線程讀寫,而且有本身的當前幀的PTS;
3.解碼後的圖像幀單獨放在pictq隊列當中,SDL利用其進行顯示。
其中PTS是什麼呢,這在音視頻中是一個很重要的概念,直接決定視頻幀或音頻幀的顯示時間,下面具體介紹一下。
圖7 音視頻解碼分析
圖7爲輸出的音頻幀和視頻幀序列,每一幀都有PTS和DTS標籤,這兩個標籤到底是什麼意思呢?
DTS(Decode Time Stamp)和PTS(Presentation Time Stamp)都是時間戳,前者是解碼時間,後者是顯示時間,都是爲視頻幀、音頻幀打上的時間標籤,以更有效地支持上層應用的同步機制。
也就是說,視頻幀或者音頻在解碼時,會記錄其解碼時間,視頻幀的播放時間依賴於PTS。
對於聲音來講 ,這兩個時間標籤是相同的;但對於某些視頻編碼格式,因爲採用了雙向預測技術,DTS會設置必定的超時或延時,保證音視頻的同步,會形成DTS和PTS的不一致。
咱們已經知道,視頻幀的播放時間其實依賴pts字段的,音頻和視頻都有本身單獨的pts。但pts到底是如何生成的呢,假如音視頻不一樣步時,pts是否須要動態調整,以保證音視頻的同步?
下面先來分析,如何控制視頻幀的顯示時間的:
static void video_refresh(void *opaque){ //根據索引獲取當前須要顯示的VideoPicture VideoPicture *vp = &is->pictq[is->pictq_rindex]; if (is->paused) goto display; //只有在paused的狀況下,才播放圖像 // 將當前幀的pts減去上一幀的pts,獲得中間時間差 last_duration = vp->pts - is->frame_last_pts; //檢查差值是否在合理範圍內,由於兩個連續幀pts的時間差,不該該太大或過小 if (last_duration > 0 && last_duration < 10.0) { /* if duration of the last frame was sane, update last_duration in video state */ is->frame_last_duration = last_duration; } //既然要音視頻同步,確定要以視頻或音頻爲參考標準,而後控制延時來保證音視頻的同步, //這個函數就作這個事情了,下面會有分析,具體是如何作到的。 delay = compute_target_delay(is->frame_last_duration, is); //獲取當前時間 time= av_gettime()/1000000.0; //假如當前時間小於frame_timer + delay,也就是這幀改顯示的時間超前,還沒到,就直接返回 if (time < is->frame_timer + delay) return; //根據音頻時鐘,只要須要延時,即delay大於0,就須要更新累加到frame_timer當中。 if (delay > 0) /更新frame_timer,frame_time是delay的累加值 is->frame_timer += delay * FFMAX(1, floor((time-is->frame_timer) / delay)); SDL_LockMutex(is->pictq_mutex); //更新is當中當前幀的pts,好比video_current_pts、video_current_pos 等變量 update_video_pts(is, vp->pts, vp->pos); SDL_UnlockMutex(is->pictq_mutex); display: /* display picture */ if (!display_disable) video_display(is); }
函數compute_target_delay根據音頻的時鐘信號,從新計算了延時,從而達到了根據音頻來調整視頻的顯示時間,從而實現音視頻同步的效果。
static double compute_target_delay(double delay, VideoState *is) { double sync_threshold, diff; //由於音頻是採樣數據,有固定的採用週期而且依賴於主系統時鐘,要調整音頻的延時播放較難控制。因此實際場合中視頻同步音頻相比音頻同步視頻實現起來更容易。 if (((is->av_sync_type == AV_SYNC_AUDIO_MASTER && is->audio_st) || is->av_sync_type == AV_SYNC_EXTERNAL_CLOCK)) { //獲取當前視頻幀播放的時間,與系統主時鐘時間相減獲得差值 diff = get_video_clock(is) - get_master_clock(is); sync_threshold = FFMAX(AV_SYNC_THRESHOLD, delay); //假如當前幀的播放時間,也就是pts,滯後於主時鐘 if (fabs(diff) < AV_NOSYNC_THRESHOLD) { if (diff <= -sync_threshold) delay = 0; //假如當前幀的播放時間,也就是pts,超前於主時鐘,那就須要加大延時 else if (diff >= sync_threshold) delay = 2 * delay; } } return delay; }
圖8 音視頻幀顯示序列
因此這裏的流程就很簡單了,圖8簡單畫了一個音視頻幀序列,想表達的意思是,音頻幀數量和視頻幀數量不必定對等,另外每一個音頻幀的顯示時間在時間上幾乎對等,每一個視頻幀的顯示時間,會根據具體狀況有延時顯示,這個延時就是有上面的compute_target_delay函數計算出來的。
計算延遲後,更新pts的代碼以下:
static void update_video_pts(VideoState *is, double pts, int64_t pos) { double time = av_gettime() / 1000000.0; /* update current video pts */ is->video_current_pts = pts; is->video_current_pts_drift = is->video_current_pts - time; is->video_current_pos = pos; is->frame_last_pts = pts; }
整個流程能夠歸納爲:
顯示第一幀視頻圖像;
根據音頻信號,計算出第二幀的delay時間,更新該幀的pts。
當pts到達後,顯示第二幀視頻圖像。
重複以上步驟,到最後一幀
也許在這裏仍然會讓人很困惑,爲何單單根據主時鐘,就能夠播放下一幀所須要的延時呢?
其實視頻是具有必定長度的播放流,具體能夠分爲音頻流、視頻流和字幕流,三者同時在一塊兒播放造成了視頻,固然他們總的播放時間是跟視頻文件的播放時長是同樣的。
因爲音頻流自己是pwm採樣數據,以固定的頻率播放,這個頻率是跟主時鐘相同或是它的分頻,從時間的角度來看,每一個音頻幀是天然均勻流逝。
因此音頻的話,直接按照主時鐘或其分頻走就能夠了。
視頻,要根據本身的顯示時間即pts,跟主時鐘當前的時間進行對比,肯定是超前仍是滯後於系統時鐘,從而肯定延時,隨後進行準確的播放,這樣就能夠保證音視頻的同步了。
那接下來,還有一個問題,計算出延時以後,難道須要sleep一下作延遲顯示嗎?
其實並非如此,上面分析咱們知道delay會更新到當前須要更新視頻幀的pts (video_current_pts),對當前AVFrame進行顯示前,先檢測其pts時間,假如還沒到,就不進行顯示了,直接return。直到下一次刷新,從新進行檢測(ffplay採用的40ms定時刷新)。
代碼以下,未到更新後的pts時間( is->frame_timer + dela),直接return:
if (av_gettime()/1000000.0 < is->frame_timer + delay) return;
那接下來就是分析如何播放視頻幀,就很簡單了,只是這裏多加了一個字幕流的處理:
static void video_image_display(VideoState *is) { VideoPicture *vp; SubPicture *sp; AVPicture pict; SDL_Rect rect; int i; vp = &is->pictq[is->pictq_rindex]; if (vp->bmp) { //字幕處理 if (is->subtitle_st) {} } //計算圖像的顯示區域 calculate_display_rect(&rect, is->xleft, is->ytop, is->width, is->height, vp); //顯示圖像 SDL_DisplayYUVOverlay(vp->bmp, &rect); //將pic隊列的指針向前移動一個位置 pictq_next_picture(is); }
VIDEO_PICTURE_QUEUE_SIZE 只設置爲4,很快就會用完了。數據滿了如何從新更新呢?
一旦檢測到超出隊列大小限制,就處於等待狀態,直到pictq被取出消費,從而避免開啓播放器,就把整個文件所有解碼完,這樣會代碼會很吃內存。
static int queue_picture(VideoState *is, AVFrame *src_frame, double pts1, int64_t pos){ /* keep the last already displayed picture in the queue */ while (is->pictq_size >= VIDEO_PICTURE_QUEUE_SIZE - 2 && !is->videoq.abort_request) { SDL_CondWait(is->pictq_cond, is->pictq_mutex); } SDL_UnlockMutex(is->pictq_mutex); }
static void stream_toggle_pause(VideoState *is) { if (is->paused) { //因爲frame_timer記下來視頻從開始播放到當前幀播放的時間,因此暫停後,必需要將暫停的時間( is->video_current_pts_drift - is->video_current_pts)一塊兒累加起來,並加上drift時間。 is->frame_timer += av_gettime() / 1000000.0 + is->video_current_pts_drift - is->video_current_pts; if (is->read_pause_return != AVERROR(ENOSYS)) { //並更新video_current_pts is->video_current_pts = is->video_current_pts_drift + av_gettime() / 1000000.0; } //drift其實就是當前幀的pts和當前時間的時間差 is->video_current_pts_drift = is->video_current_pts - av_gettime() / 1000000.0; } //paused取反,paused標誌位也會控制到圖像幀的展現,按一次空格鍵實現暫停,再按一次就實現播放了。 is->paused = !is->paused; }
特別說明:paused標誌位控制着視頻是否播放,當須要繼續播放的時候,必定要從新更新當前所須要播放幀的pts時間,由於這裏面要加上已經暫停的時間。
在視頻解碼線程中,不斷經過stream_toggle_paused,控制對視頻的暫停和顯示,從而實現逐幀播放:
static void step_to_next_frame(VideoState *is) { //逐幀播放時,必定要先繼續播放,而後再設置step變量,控制逐幀播放 if (is->paused) stream_toggle_pause(is);//會不斷將paused進行取反 is->step = 1; }
其原理就是不斷的播放,而後暫停,從而實現逐幀播放:
static int video_thread(void *arg) { if (is->step) stream_toggle_pause(is); …………………… if (is->paused) goto display;//顯示視頻 } }
關於快進/後退,首先拋出兩個問題:
1. 快進以時間爲維度仍是以幀數爲維度來對播放進度進行控制呢?
2.一旦進度發生了變化,那麼當前幀,以及AVFrame隊列是否須要清零,整個對stream的流是否須要從新來進行控制呢?
ffplay中採用以時間爲維度的控制方法。對於快進和後退的控制,都是經過設置VideoState的seek_req、seek_pos等變量進行控制
do_seek:
//其實是計算is->audio_current_pts_drift + av_gettime() / 1000000.0,肯定當前須要播放幀的時間值
pos = get_master_clock(cur_stream);
pos += incr; //incr爲每次快進的步進值,相加便可獲得快進後的時間點
stream_seek(cur_stream, (int64_t)(pos AV_TIME_BASE), (int64_t)(incr AV_TIME_BASE), 0);
關於stream_seek的代碼以下,其實就是設置VideoState的相關變量,以控制read_tread中的快進或後退的流程:
/* seek in the stream */ static void stream_seek(VideoState *is, int64_t pos, int64_t rel, int seek_by_bytes) { if (!is->seek_req) { is->seek_pos = pos; is->seek_rel = rel; is->seek_flags &= ~AVSEEK_FLAG_BYTE; if (seek_by_bytes) is->seek_flags |= AVSEEK_FLAG_BYTE; is->seek_req = 1; } }
stream_seek中設置了seek_req標誌,就直接進入前進/後退控制流程了,其原理是調用avformat_seek_file函數,根據時間戳控制索引點,從而控制須要顯示的下一幀:
static int read_thread(void *arg){ //當調整播放進度之後 if (is->seek_req) { int64_t seek_target = is->seek_pos; int64_t seek_min = is->seek_rel > 0 ? seek_target - is->seek_rel + 2: INT64_MIN; int64_t seek_max = is->seek_rel < 0 ? seek_target - is->seek_rel - 2: INT64_MAX; //根據時間抽查找索引點位置,定位到索引點以後,下一幀的讀取直接從這裏開始,就實現了快進/後退操做 ret = avformat_seek_file(is->ic, -1, seek_min, seek_target, seek_max, is->seek_flags); if (ret < 0) { fprintf(stderr, "s: error while seeking\n", is->ic->filename); } else { //查找成功以後,就須要清空當前的PAcket隊列,包括音頻、視頻和字幕 if (is->audio_stream >= 0) { packet_queue_flush(&is->audioq); packet_queue_put(&is->audioq, &flush_pkt); } if (is->subtitle_stream >= 0) {//處理字幕stream packet_queue_flush(&is->subtitleq); packet_queue_put(&is->subtitleq, &flush_pkt); } if (is->video_stream >= 0) { packet_queue_flush(&is->videoq); packet_queue_put(&is->videoq, &flush_pkt); } } is->seek_req = 0; eof = 0; } }
另外從上面代碼中發現,每次快進後退以後都會對audioq、videoq和subtitleq進行flush清零,也是至關於從新開始,保證緩衝隊列中的數據的正確性。
對於音頻,開始仍然有些困惑,由於在暫停的時候,沒有看到對音頻的控制,是如何控制的呢?
後來發現,其實暫停的時候設置了is->paused變量,解複用和音頻解碼和播放都依賴於is->paused變量,因此音頻和視頻播放都隨之中止了。
1.基礎概念和原理積累,最開始接觸FFmpeg,由於其涉及的概念不少,看起來有種無從下手的感受。這時候必須從基本模塊入手,逐步理解更多,必定的量積累,就會產生一些質變,更好的理解視頻編解碼機制;
2.必定要首先看懂代碼整體架構和流程,隨後針對每一個細節點進行深刻分析,會極大提升看代碼效率。會畫一些框圖是很是重要的,好比下面這張,因此簡要的流程圖要比注重細節的uml圖要方便得多;
3.看FFmpeg代碼,在PC端上調試,會快捷不少。假如要在Android上,調用jni來看代碼,效率就會很低。
參考文章:
基於ffmpeg的跨平臺播放器實現
https://www.qcloud.com/community/article/309889001486708756
雷神的文章(多媒體入門開發必看):
http://blog.csdn.net/leixiaohua1020/article/details/15811977