生活随笔
收集整理的這篇文章主要介紹了
ffplay.c学习-3-音视频解码线程
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
ffplay.c學(xué)習(xí)-3-音視頻解碼線程
目錄
解碼線程 視頻解碼線程 video_thread() get_video_frame() 同?播放序列流連續(xù)的情況下,不斷調(diào)?avcodec_receive_frame獲取解碼后的frame。 獲取?個packet,如果播放序列不?致(數(shù)據(jù)不連續(xù))則過濾掉“過時”的packet 將packet送?解碼器 queue_picture() 音頻解碼線程
類型PacketQueueFrameQueuevidck解碼線程 視頻 videoq pictq vidcllk video_thread ?頻 audioq sampq audclk audio_thread 字幕 subtitleq subpq ? subtitle_thread
其中PacketQueue?于存放從read_thread取到的各?播放時間內(nèi)的AVPacket。FrameQueue?于存放各?解碼后的AVFrame。Clock?于同步?視頻。解碼線程負(fù)責(zé)將PacketQueue數(shù)據(jù)解碼為AVFrame,并存?FrameQueue。 對于不同流,其解碼過程?同?異。
typedef
struct Decoder
{ AVPacket pkt
; PacketQueue
* queue
; AVCodecContext
* avctx
; int pkt_serial
; int finished
; int packet_pending
; SDL_cond
* empty_queue_cond
; int64_t start_pts
; AVRational start_pts_tb
; int64_t next_pts
; AVRational next_pts_tb
; SDL_Thread
* decoder_tid
;
} Decoder
;
1. 解碼器相關(guān)的函數(shù)(decoder是ffplay?定義,重新封裝的。 avcodec才是ffmpeg的提供的)
初始化解碼器
static void
decoder_init ( Decoder
* d
, AVCodecContext
* avctx
, PacketQueue
* queue
, SDL_cond
* empty_queue_cond
)
啟動解碼器
static
int decoder_start ( Decoder
* d
, int ( * fn
) ( void
* ) , const char
* thread_name
, void
* arg
)
解幀
static
int decoder_decode_frame ( Decoder
* d
, AVFrame
* frame
, AVSubtitle
* sub
)
終?解碼器
void
decoder_abort ( Decoder
* d
, FrameQueue
* fq
) ;
銷毀解碼器
void
decoder_destroy ( Decoder
* d
) ;
2. 使??法
啟動解碼線程 decoder_init() decoder_start() 解碼線程具體流程 decoder_decode_frame() 退出解碼線程 decoder_abort() decoder_destroy()
1. 解碼線程
ffplay的解碼線程獨(dú)?于數(shù)據(jù)讀線程,并且每種類型的流(AVStream)都有其各?的解碼線程,如: video_thread?于解碼video stream; audio_thread?于解碼audio stream; subtitle_thread?于解碼subtitle stream。 為?便閱讀,先列?張表格,梳理各個變量、函數(shù)名稱。
2. 視頻解碼線程
數(shù)據(jù)來源:從read_thread線程?來 數(shù)據(jù)處理:在video_thread進(jìn)?解碼,具體調(diào)?get_video_frame 數(shù)據(jù)出?:在video_refresh讀取frame進(jìn)?顯示
1. video_thread()
我們先看video_thead,對于濾鏡部分(CONFIG_AVFILTER定義部分),這?不做分析 ,簡化后的代碼如下:
static
int video_thread ( void
* arg
) { VideoState
* is
= arg
; AVFrame
* frame
= av_frame_alloc ( ) ; double pts
; double duration
; int ret
; AVRational tb
= is
- > video_st
- > time_base
; AVRational frame_rate
= av_guess_frame_rate ( is
- > ic
, is
- > video_st
, NULL
) ; #
if CONFIG_AVFILTERAVFilterGraph
* graph
= NULL
; AVFilterContext
* filt_out
= NULL
, * filt_in
= NULL
; int last_w
= 0 ; int last_h
= 0 ; enum AVPixelFormat last_format
= - 2 ; int last_serial
= - 1 ; int last_vfilter_idx
= 0 ; #endif
if ( ! frame
) return AVERROR ( ENOMEM
) ; for ( ; ; ) { ret
= get_video_frame ( is
, frame
) ; if ( ret
< 0 ) goto the_end
; if ( ! ret
) continue ; duration
= ( frame_rate
. num
&& frame_rate
. den ?
av_q2d ( ( AVRational
) { frame_rate
. den
, frame_rate
. num
} ) : 0 ) ; pts
= ( frame
- > pts
== AV_NOPTS_VALUE
) ? NAN
: frame
- > pts
* av_q2d ( tb
) ; ret
= queue_picture ( is
, frame
, pts
, duration
, frame
- > pkt_pos
, is
- > viddec
. pkt_serial
) ; av_frame_unref ( frame
) ;
#
if CONFIG_AVFILTER
if ( is
- > videoq
. serial
!= is
- > viddec
. pkt_serial
) break ; }
#endif
if ( ret
< 0 ) goto the_end
; } the_end
:
#
if CONFIG_AVFILTER
avfilter_graph_free ( & graph
) ;
#endif
av_frame_free ( & frame
) ; return 0 ;
}
在該流程中,當(dāng)調(diào)?函數(shù)返回值?于<0時則退出線程。 線程的總體流程很清晰: 獲取stream timebase,以便將frame的pts轉(zhuǎn)成秒為單位 獲取幀率,以便計算每幀picture的duration 獲取解碼后的視頻幀,具體調(diào)?get_video_frame()實現(xiàn) 計算幀持續(xù)時間和換算pts值為秒 將解碼后的視頻幀插?隊列,具體調(diào)?queue_picture()實現(xiàn) 釋放frame對應(yīng)的數(shù)據(jù) 重點是get_video_frame()和queue_picture()
2. get_video_frame()
get_video_frame 簡化如下:
static
int get_video_frame ( VideoState
* is
, AVFrame
* frame
) { int got_picture
; if ( ( got_picture
= decoder_decode_frame ( & is
- > viddec
, frame
, NULL
) ) < 0 ) { return - 1 ; } if ( got_picture
) { ... } return got_picture
;
}
主要流程:
調(diào)? decoder_decode_frame 解碼并獲取解碼后的視頻幀; 分析如果獲取到幀是否需要drop掉(邏輯就是如果剛解出來就落后主時鐘,那就沒有必要放?Frame隊列,再拿去播放,但是也是有?定的條件的,?下?分析) 被簡化的部分主要是針對丟幀的?個處理
if ( got_picture
) { double dpts
= NAN
; if ( frame
- > pts
!= AV_NOPTS_VALUE
) dpts
= av_q2d ( is
- > video_st
- > time_base
) * frame
- > pts
; frame
- > sample_aspect_ratio
= av_guess_sample_aspect_ratio ( is
- > ic
, is
- > video_st
, frame
) ; if ( framedrop
> 0 || ( framedrop
&& get_master_sync_type ( is
) != AV_SYNC_VIDEO_MASTER
) ) { if ( frame
- > pts
!= AV_NOPTS_VALUE
) { double diff
= dpts
- get_master_clock ( is
) ; if ( ! isnan ( diff
) && fabs ( diff
) < AV_NOSYNC_THRESHOLD
&& diff
- is
- > frame_last_filter_delay
< 0 && is
- > viddec
. pkt_serial
== is
- > vidclk
. serial
&& is
- > videoq
. nb_packets
) { is
- > frame_drops_early
++ ; printf ( "%s(%d) diff:%lfs, drop frame, drops:%d\n" , __FUNCTION__
, __LINE__
, diff
, is
- > frame_drops_early
) ; av_frame_unref ( frame
) ; got_picture
= 0 ; } } } }
先確定進(jìn)?丟幀檢測流程,控制是否進(jìn)?丟幀檢測有3種情況
控制是否丟幀的開關(guān)變量是 framedrop ,為1,則始終判斷是否丟幀; framedrop 為0,則始終不丟幀; framedrop 為-1(默認(rèn)值),則在主時鐘不是video的時候,判斷是否丟幀。 如果進(jìn)?丟幀檢測流程,drop幀需要下列因素都成?:
!isnan(diff):當(dāng)前pts和主時鐘的差值是有效值; fabs(diff) < AV_NOSYNC_THRESHOLD:差值在可同步范圍內(nèi),這?設(shè)置的是10秒,意思是如果差值太?這?就不管了了,可能流本身錄制的時候就有問題,這?不能隨便把幀都drop掉; diff - is->frame_last_filter_delay < 0:和過濾器有關(guān)系,不設(shè)置過濾器時簡化為 diff < 0; is->viddec.pkt_serial == is->vidclk.serial:解碼器的serial和時鐘的serial相同,即是?少顯示了?幀圖像,因為只有顯示的時候才調(diào)?update_video_pts()設(shè)置到video clk的serial; is->videoq.nb_packets:?少packetqueue有1個包。 接下來看下真正解碼的過程—— decoder_decode_frame ,這個函數(shù)也包含了對audio和subtitle的解碼,其返回值: -1:請求退出解碼器線程 0:解碼器已經(jīng)完全沖刷,沒有幀可讀,這?也說明對應(yīng)碼流播放結(jié)束 1:正常解碼獲取到幀
先看簡化后的主?代碼(注意for(;;)這個?循環(huán)):
static
int decoder_decode_frame ( Decoder
* d
, AVFrame
* frame
, AVSubtitle
* sub
) { for ( ; ; ) { if ( d
- > queue
- > serial
== d
- > pkt_serial
) { do
{ if ( d
- > queue
- > abort_request
) return - 1 ; ret
= avcodec_receive_frame ( d
- > avctx
, frame
) ; if ( ret
== AVERROR_EOF
) { return 0 ; } if ( ret
>= 0 ) return 1 ; } while
( ret
!= AVERROR ( EAGAIN
) ) ; } do
{ if ( d
- > queue
- > nb_packets
== 0 ) SDL_CondSignal ( d
- > empty_queue_cond
) ; if ( packet_queue_get ( d
- > queue
, & pkt
, 1 , & d
- > pkt_serial
) < 0 ) return - 1 ; } while
( d
- > queue
- > serial
!= d
- > pkt_serial
) ; avcodec_send_packet ( d
- > avctx
, & pkt
) ; } }
decoder_decode_frame 的主?代碼是?個循環(huán),要拿到?幀解碼數(shù)據(jù),或解碼出錯、?件結(jié)束,才會返回。 循環(huán)內(nèi)可以分解為3個步驟: 同?播放序列流連續(xù)的情況下,不斷調(diào)?avcodec_receive_frame獲取解碼后的frame。 a. d->queue 就是video PacketQueue(videoq) b. d->pkt_serial 是最近?次取的packet的序列號。在判斷完 d->queue->serial == d->pkt_serial 確保流連續(xù)后,循環(huán)調(diào)?avcodec_receive_frame ,有取到幀就返回。(即使還沒送?新的Packet,這是為了兼容?個Packet可以解出多個Frame的情況) 獲取?個packet,如果播放序列不?致(數(shù)據(jù)不連續(xù))則過濾掉“過時”的packet。主要阻塞調(diào)?packet_queue_get ()。另外,會在PacketQueue為空時,發(fā)送 empty_queue_cond 條件信號,通知讀線程繼續(xù)讀數(shù)據(jù)。( empty_queue_cond 就是 continue_read_thread ,可以參考read線程的分析,查看讀線程何時會等待該條件量。 將packet送?解碼器。
1. 同?播放序列流連續(xù)的情況下,不斷調(diào)?avcodec_receive_frame獲取解碼后的frame。
我們先看avcodec_receive_frame的具體流程,這?先省略Audio的case:
if ( d
- > queue
- > serial
== d
- > pkt_serial
) { do
{ if ( d
- > queue
- > abort_request
) return - 1 ; switch ( d
- > avctx
- > codec_type
) { case AVMEDIA_TYPE_VIDEO
: ret
= avcodec_receive_frame ( d
- > avctx
, frame
) ; if ( ret
>= 0 ) { if ( decoder_reorder_pts
== - 1 ) { frame
- > pts
= frame
- > best_effort_timestamp
; } else if ( ! decoder_reorder_pts
) { frame
- > pts
= frame
- > pkt_dts
; } } break ; case AVMEDIA_TYPE_AUDIO
: ret
= avcodec_receive_frame ( d
- > avctx
, frame
) ; if ( ret
>= 0 ) { AVRational tb
= ( AVRational
) { 1 , frame
- > sample_rate
} ; if ( frame
- > pts
!= AV_NOPTS_VALUE
) frame
- > pts
= av_rescale_q ( frame
- > pts
, d
- > avctx
- > pkt_timebase
, tb
) ; else if ( d
- > next_pts
!= AV_NOPTS_VALUE
) frame
- > pts
= av_rescale_q ( d
- > next_pts
, d
- > next_pts_tb
, tb
) ; if ( frame
- > pts
!= AV_NOPTS_VALUE
) { d
- > next_pts
= frame
- > pts
+ frame
- > nb_samples
; d
- > next_pts_tb
= tb
; } } break ; } if ( ret
== AVERROR_EOF
) { d
- > finished
= d
- > pkt_serial
; printf ( "avcodec_flush_buffers %s(%d)\n" , __FUNCTION__
, __LINE__
) ; avcodec_flush_buffers ( d
- > avctx
) ; return 0 ; } if ( ret
>= 0 ) return 1 ; } while
( ret
!= AVERROR ( EAGAIN
) ) ; }
注意返回值: -1:請求退出解碼器線程 0:解碼器已經(jīng)完全沖刷,沒有幀可讀,這?也說明對應(yīng)碼流播放結(jié)束 1:正常解碼獲取到幀
1. 重點分析decoder_reorder_pts
ret
= avcodec_receive_frame ( d
- > avctx
, frame
) ; if ( ret
>= 0 ) { if ( decoder_reorder_pts
== - 1 ) { frame
- > pts
= frame
- > best_effort_timestamp
; } else if ( ! decoder_reorder_pts
) { frame
- > pts
= frame
- > pkt_dts
; } }
decoder_reorder_pts:讓ffmpeg排序pts 0=off 1=on -1=auto,默認(rèn)為-1 (ffplay配置 -drp value進(jìn)?設(shè)置) 0:frame的pts使?pkt_dts,這種情況基本不會出現(xiàn) 1:frame保留??的pts -1:frame的pts使?frame->best_effort_timestamp,best_effort_timestamp是經(jīng)過算法計算出來的值,主要是“嘗試為可能有錯誤的時間戳猜測出適當(dāng)單調(diào)的時間戳”,?部分情況下還是frame->pts,或者就是frame->pkt_dts。
2. 重點分析avcodec_flush_buffers
使?“空包”沖刷解碼器后,如果要再次解碼則需要調(diào)?avcodec_flush_buffers(),之所以在這個節(jié)點調(diào)?avcodec_flush_buffers(),主要是讓我們在循環(huán)播放碼流的時候可以繼續(xù)正常解碼。
2. 獲取?個packet,如果播放序列不?致(數(shù)據(jù)不連續(xù))則過濾掉“過時”的packet
do
{ if ( d
- > queue
- > nb_packets
== 0 ) SDL_CondSignal ( d
- > empty_queue_cond
) ; if ( d
- > packet_pending
) { av_packet_move_ref ( & pkt
, & d
- > pkt
) ; d
- > packet_pending
= 0 ; } else { if ( packet_queue_get ( d
- > queue
, & pkt
, 1 , & d
- > pkt_serial
) < 0 ) return - 1 ; } if ( d
- > queue
- > serial
!= d
- > pkt_serial
) { printf ( "%s(%d) discontinue:queue->serial:%d,pkt_serial:%d\n" , __FUNCTION__
, __LINE__
, d
- > queue
- > serial
, d
- > pkt_serial
) ; } } while
( d
- > queue
- > serial
!= d
- > pkt_serial
) ;
1. 重點:如果還有pending的packet則使?它
if ( d
- > packet_pending
) { av_packet_move_ref ( & pkt
, & d
- > pkt
) ; d
- > packet_pending
= 0 ; }
pending包packet和 packet_pending 的概念的來源,來?send失敗時重新發(fā)送 if ( avcodec_send_packet ( d- > avctx, & pkt) == AVERROR ( EAGAIN) ) { av_log ( d- > avctx, AV_LOG_ERROR, "Receive_frame and send_packet both returned EAGAIN, which is an API violation.\n" ) ; d- > packet_pending = 1 ; av_packet_move_ref ( & d- > pkt, & pkt) ; }
如果 avcodec_send_packet 返回 EAGAIN ,則把當(dāng)前 pkt 存? d->pkt ,然后置標(biāo)志位packet_pending 為1
2. 重點:do {} while (d->queue->serial != d->pkt_serial);// 如果不是同?播放序列(流不連續(xù))則繼續(xù)讀取
d->queue->serial是最新的播放序列,當(dāng)讀取出來的packet的serial和最新的serial不同時則過濾掉,繼續(xù)讀取packet,但檢測到不是同?serial,是不是應(yīng)該釋放掉packet的數(shù)據(jù)??如下列代碼
if ( d
- > queue
- > serial
!= d
- > pkt_serial
) { printf ( "%s(%d) discontinue:queue->serial:%d,pkt_serial:%d\n" , __FUNCTION__
, __LINE__
, d
- > queue
- > serial
, d
- > pkt_serial
) ; av_packet_unref ( & pkt
) ;
}
3. 將packet送?解碼器
if ( pkt
. data
== flush_pkt
. data
) { avcodec_flush_buffers ( d
- > avctx
) ; d
- > finished
= 0 ; d
- > next_pts
= d
- > start_pts
; d
- > next_pts_tb
= d
- > start_pts_tb
; } else { if ( d
- > avctx
- > codec_type
== AVMEDIA_TYPE_SUBTITLE
) { int got_frame
= 0 ; ret
= avcodec_decode_subtitle2 ( d
- > avctx
, sub
, & got_frame
, & pkt
) ; if ( ret
< 0 ) { ret
= AVERROR ( EAGAIN
) ; } else { if ( got_frame
&& ! pkt
. data
) { d
- > packet_pending
= 1 ; av_packet_move_ref ( & d
- > pkt
, & pkt
) ; } ret
= got_frame ?
0 : ( pkt
. data ?
AVERROR ( EAGAIN
) : AVERROR_EOF
) ; } } else { if ( avcodec_send_packet ( d
- > avctx
, & pkt
) == AVERROR ( EAGAIN
) ) { av_log ( d
- > avctx
, AV_LOG_ERROR
, "Receive_frame and send_packet both returned EAGAIN, which is an API violation.\n" ) ; d
- > packet_pending
= 1 ; av_packet_move_ref ( & d
- > pkt
, & pkt
) ; } } av_packet_unref ( & pkt
) ; } }
1. 重點:有針對 flush_pkt 的處理
if ( pkt
. data
== flush_pkt
. data
) { avcodec_flush_buffers ( d
- > avctx
) ; d
- > finished
= 0 ; d
- > next_pts
= d
- > start_pts
; d
- > next_pts_tb
= d
- > start_pts_tb
; }
了解過PacketQueue的代碼,我們知道在往PacketQueue送??個flush_pkt后,PacketQueue的serial值會加1,?送?的flush_pkt和PacketQueue的新serial值保持?致。所以如果有“過時(舊serial)”Packet,過濾后,取到新的播放序列第?個pkt將是flush_pkt。 根據(jù)api要求,此時需要調(diào)? avcodec_flush_buffers 也要注意d->finished = 0; 的重置。
2. 重點:avcodec_send_packet后出現(xiàn)AVERROR(EAGAIN),則說明我們要繼續(xù)調(diào)?avcodec_receive_frame()將frame讀取,再調(diào)?avcodec_send_packet發(fā)packet。
由于出現(xiàn)AVERROR(EAGAIN)返回值解碼器內(nèi)部沒有接收傳?的packet,但?沒法放回PacketQueue,所以我們就緩存到了?封裝的Decoder的pkt(即是d->pkt),并將 d->packet_pending = 1,以備下次繼續(xù)使?該packet
if ( avcodec_send_packet ( d
- > avctx
, & pkt
) == AVERROR ( EAGAIN
) ) { av_log ( d
- > avctx
, AV_LOG_ERROR
, "Receive_frame and send_packet both returned EAGAIN, which is an API violation.\n" ) ; d
- > packet_pending
= 1 ; av_packet_move_ref ( & d
- > pkt
, & pkt
) ; }
3. queue_picture()
上?,我們就分析完video_thread中關(guān)鍵的 get_video_frame 函數(shù),根據(jù)所分析的代碼,已經(jīng)可以取到正確解碼后的?幀數(shù)據(jù)。接下來就要把這?幀放?FrameQueue:
duration
= ( frame_rate
. num
&& frame_rate
. den ?
av_q2d ( ( AVRational
) { frame_rate
. den
, frame_rate
. num
} ) : 0 ) ; pts
= ( frame
- > pts
== AV_NOPTS_VALUE
) ? NAN
: frame
- > pts
* av_q2d ( tb
) ; ret
= queue_picture ( is
, frame
, pts
, duration
, frame
- > pkt_pos
, is
- > viddec
. pkt_serial
) ; av_frame_unref ( frame
) ;
#
if CONFIG_AVFILTER
if ( is
- > videoq
. serial
!= is
- > viddec
. pkt_serial
) break ; }
主要調(diào)? queue_picture :
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
) ;
#endif
if ( ! ( vp
= frame_queue_peek_writable ( & is
- > pictq
) ) ) return - 1 ; vp
- > 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
) ; av_frame_move_ref ( vp
- > frame
, src_frame
) ; frame_queue_push ( & is
- > pictq
) ; return 0 ;
}
queue_picture 的代碼很直觀: ?先 frame_queue_peek_writable 取FrameQueue的當(dāng)前寫節(jié)點; 然后把該拷?的拷?給節(jié)點(struct Frame)保存再 frame_queue_push ,“push”節(jié)點到隊列中。唯?需要關(guān)注的是,AVFrame的拷?是通過av_frame_move_ref 實現(xiàn)的,所以拷?后 src_frame 就是?效的了
3. 音頻解碼線程
數(shù)據(jù)來源:從read_thread()線程?來 數(shù)據(jù)處理:在audio_thread()進(jìn)?解碼,具體調(diào)?decoder_decode_frame() 數(shù)據(jù)出?:在sdl_audio_callback()->audio_decode_frame()讀取frame進(jìn)?播放
1. audio_thread()
static
int audio_thread ( void
* arg
) { VideoState
* is
= arg
; AVFrame
* frame
= av_frame_alloc ( ) ; Frame
* af
; int got_frame
= 0 ; AVRational tb
; int ret
= 0 ; if ( ! frame
) return AVERROR ( ENOMEM
) ; do
{ if ( ( got_frame
= decoder_decode_frame ( & is
- > auddec
, frame
, NULL
) ) < 0 ) goto the_end
; if ( got_frame
) { tb
= ( AVRational
) { 1 , frame
- > sample_rate
} ; if ( ! ( af
= frame_queue_peek_writable ( & is
- > sampq
) ) ) goto the_end
; af
- > pts
= ( frame
- > pts
== AV_NOPTS_VALUE
) ? NAN
: frame
- > pts
* av_q2d ( tb
) ; af
- > pos
= frame
- > pkt_pos
; af
- > serial
= is
- > auddec
. pkt_serial
; af
- > duration
= av_q2d ( ( AVRational
) { frame
- > nb_samples
, frame
- > sample_rate
} ) ; av_frame_move_ref ( af
- > frame
, frame
) ; frame_queue_push ( & is
- > sampq
) ; } } while
( ret
>= 0 || ret
== AVERROR ( EAGAIN
) || ret
== AVERROR_EOF
) ; the_end
: av_frame_free ( & frame
) ; return ret
; }
從簡化后的代碼來看,邏輯和video_thread()基本是類似的且更簡單,這?主要重點講解為什么video_thread()是tb是采?了stream->base_base,這?卻不是,這個時候就要回到decoder_decode_frame()函數(shù),我們主要是重點看audio部分
static
int decoder_decode_frame ( Decoder
* d
, AVFrame
* frame
, AVSubtitle
* sub
) { ... for ( ; ; ) { AVPacket pkt
;
if ( d
- > queue
- > serial
== d
- > pkt_serial
) { do
{ ... ... ... switch ( d
- > avctx
- > codec_type
) { case AVMEDIA_TYPE_VIDEO
: ... . break ; case AVMEDIA_TYPE_AUDIO
: ret
= avcodec_receive_frame ( d
- > avctx
, frame
) ; if ( ret
>= 0 ) { AVRational tb
= ( AVRational
) { 1 , frame
- > sample_rate
} ; if ( frame
- > pts
!= AV_NOPTS_VALUE
) { frame
- > pts
= av_rescale_q ( frame
- > pts
, d
- > avctx
- > pkt_timebase
, tb
) ; } else if ( d
- > next_pts
!= AV_NOPTS_VALUE
) { s和next_pts_tbframe
- > pts
= av_rescale_q ( d
- > next_pts
, d
- > next_pts_tb
, tb
) ; } if ( frame
- > pts
!= AV_NOPTS_VALUE
) { tb
= ( AVRational
) { 1 , frame
- > sample_rate
} ; d
- > next_pts
= frame
- > pts
+ frame
- > nb_samples
; d
- > next_pts_tb
= tb
; } } break ; } ... . } while
( ret
!= AVERROR ( EAGAIN
) ) ; } ... . }
從上可以看出來,將audio frame從decoder_decode_frame取出來后,已由stream->time_base轉(zhuǎn)成了{(lán)1, frame->sample_rate}作為time_base。
總結(jié)
以上是生活随笔 為你收集整理的ffplay.c学习-3-音视频解码线程 的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔 推薦給好友。