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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

ffplay.c学习-3-音视频解码线程

發(fā)布時間:2024/4/11 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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解碼線程
    視頻videoqpictqvidcllkvideo_thread
    ?頻audioqsampqaudclkaudio_thread
    字幕subtitleqsubpq?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; // 數(shù)據(jù)包隊列AVCodecContext *avctx; // 解碼器上下文int pkt_serial; // 包序列int finished; // =0,解碼器處于工作狀態(tài);=非0,解碼器處于空閑狀態(tài)int packet_pending; // =0,解碼器處于異常狀態(tài),需要考慮重置解碼器;=1,解碼器處于正常狀態(tài)SDL_cond *empty_queue_cond; // 檢查到packet隊列空時發(fā)送 signal緩存read_thread讀取數(shù)據(jù)int64_t start_pts; // 初始化時是stream的start timeAVRational start_pts_tb; // 初始化時是stream的time_baseint64_t next_pts; // 記錄最近一次解碼后的frame的pts,當(dāng)解出來的部分幀沒有有效的pts時則使用next_pts進(jìn)行推算AVRational next_pts_tb; // next_pts的單位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)
  • 啟動解碼器
  • /*** 創(chuàng)建解碼線程, audio/video有獨(dú)立的線程*/ 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; // ptsdouble duration; // 幀持續(xù)時間int ret;//1 獲取stream timebaseAVRational tb = is->video_st->time_base; // 獲取stream timebase//2 獲取幀率,以便計算每幀picture的durationAVRational 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;#endifif (!frame)return AVERROR(ENOMEM);for (;;) { // 循環(huán)取出視頻解碼的幀數(shù)據(jù)// 3 獲取解碼后的視頻幀ret = get_video_frame(is, frame);if (ret < 0)goto the_end; //解碼結(jié)束, 什么時候會結(jié)束if (!ret) //沒有解碼得到畫面, 什么情況下會得不到解后的幀continue;// 4 計算幀持續(xù)時間和換算pts值為秒// 1/幀率 = duration 單位秒, 沒有幀率時則設(shè)置為0, 有幀率幀計算出幀間隔duration = (frame_rate.num && frame_rate.den ? av_q2d((AVRational) {frame_rate.den, frame_rate.num}) : 0);// 根據(jù)AVStream timebase計算出pts值, 單位為秒pts = (frame->pts == AV_NOPTS_VALUE) ? NAN : frame->pts * av_q2d(tb);// 5 將解碼后的視頻幀插入隊列ret = queue_picture(is, frame, pts, duration, frame->pkt_pos, is->viddec.pkt_serial);// 6 釋放frame對應(yīng)的數(shù)據(jù)av_frame_unref(frame); #if CONFIG_AVFILTERif (is->videoq.serial != is->viddec.pkt_serial)break;} #endifif (ret < 0) // 返回值小于0則退出線程goto the_end;}the_end: #if CONFIG_AVFILTERavfilter_graph_free(&graph); #endifav_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 簡化如下:
  • /*** @brief 獲取視頻幀* @param is* @param frame 指向獲取的視頻幀* @return*/ static int get_video_frame(VideoState *is, AVFrame *frame) {int got_picture;// 1. 獲取解碼后的視頻幀if ((got_picture = decoder_decode_frame(&is->viddec, frame, NULL)) < 0) {return -1; // 返回-1意味著要退出解碼線程, 所以要分析decoder_decode_frame什么情況下返回-1}if (got_picture) {// 2. 分析獲取到的該幀是否要drop掉, 該機(jī)制的目的是在放入幀隊列前先drop掉過時的視頻幀...}return got_picture; }
  • 主要流程:

  • 調(diào)? decoder_decode_frame 解碼并獲取解碼后的視頻幀;
  • 分析如果獲取到幀是否需要drop掉(邏輯就是如果剛解出來就落后主時鐘,那就沒有必要放?Frame隊列,再拿去播放,但是也是有?定的條件的,?下?分析)
  • 被簡化的部分主要是針對丟幀的?個處理

  • if (got_picture) {// 2. 分析獲取到的該幀是否要drop掉, 該機(jī)制的目的是在放入幀隊列前先drop掉過時的視頻幀double dpts = NAN;if (frame->pts != AV_NOPTS_VALUE)dpts = av_q2d(is->video_st->time_base) * frame->pts; //計算出秒為單位的ptsframe->sample_aspect_ratio = av_guess_sample_aspect_ratio(is->ic, is->video_st, frame);if (framedrop > 0 || // 允許drop幀(framedrop && get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER))//非視頻同步模式{if (frame->pts != AV_NOPTS_VALUE) { // pts值有效double diff = dpts - get_master_clock(is);if (!isnan(diff) && // 差值有效fabs(diff) < AV_NOSYNC_THRESHOLD && // 差值在可同步范圍呢diff - is->frame_last_filter_delay < 0 && // 和過濾器有關(guān)系is->viddec.pkt_serial == is->vidclk.serial && // 同一序列的包is->videoq.nb_packets) { // 幀隊列至少有1幀數(shù)據(jù)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 (;;) { // ?循環(huán)//1. 流連續(xù)情況下獲取解碼后的幀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));}//2. 獲取?個packet,如果播放序列不?致(數(shù)據(jù)不連續(xù))則過濾掉“過時”的packetdo {if (d->queue->nb_packets == 0)//如果沒有數(shù)據(jù)可讀則喚醒read_threadSDL_CondSignal(d->empty_queue_cond);if (packet_queue_get(d->queue, &pkt, 1, &d->pkt_serial) < 0)// 阻塞?式讀packetreturn -1;} while (d->queue->serial != d->pkt_serial); // 播放序列的判斷//3. 將packet送?解碼器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:
  • // 1. 流連續(xù)情況下獲取解碼后的幀if (d->queue->serial == d->pkt_serial) { // 1.1 先判斷是否是同一播放序列的數(shù)據(jù)do {if (d->queue->abort_request)return -1; // 是否請求退出// 1.2. 獲取解碼幀switch (d->avctx->codec_type) {case AVMEDIA_TYPE_VIDEO:ret = avcodec_receive_frame(d->avctx, frame);//printf("frame pts:%ld, dts:%ld\n", frame->pts, frame->pkt_dts);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;}// 1.3. 檢查解碼是否已經(jīng)結(jié)束,解碼結(jié)束返回0if (ret == AVERROR_EOF) {d->finished = d->pkt_serial;printf("avcodec_flush_buffers %s(%d)\n", __FUNCTION__, __LINE__);avcodec_flush_buffers(d->avctx);return 0;}// 1.4. 正常解碼返回1if (ret >= 0)return 1;} while (ret != AVERROR(EAGAIN)); // 1.5 沒幀可讀時ret返回EAGIN,需要繼續(xù)送packet}
  • 注意返回值:
    -1:請求退出解碼器線程
    0:解碼器已經(jīng)完全沖刷,沒有幀可讀,這?也說明對應(yīng)碼流播放結(jié)束
    1:正常解碼獲取到幀
  • 1. 重點分析decoder_reorder_pts
    ret = avcodec_receive_frame(d->avctx, frame);//printf("frame pts:%ld, dts:%ld\n", frame->pts, frame->pkt_dts);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
    // 2 獲取一個packet,如果播放序列不一致(數(shù)據(jù)不連續(xù))則過濾掉“過時”的packetdo {// 2.1 如果沒有數(shù)據(jù)可讀則喚醒read_thread, 實際是continue_read_thread SDL_condif (d->queue->nb_packets == 0) // 沒有數(shù)據(jù)可讀SDL_CondSignal(d->empty_queue_cond);// 通知read_thread放入packet// 2.2 如果還有pending的packet則使用它if (d->packet_pending) {av_packet_move_ref(&pkt, &d->pkt);d->packet_pending = 0;} else {// 2.3 阻塞式讀取packetif (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);// 如果不是同一播放序列(流不連續(xù))則繼續(xù)讀取
    1. 重點:如果還有pending的packet則使?它
    // 2.2 如果還有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); // fixed me? 釋放要過濾的packet }
    3. 將packet送?解碼器
    // 3 將packet送入解碼器if (pkt.data == flush_pkt.data) {//// when seeking or when switching to a different streamavcodec_flush_buffers(d->avctx); //清空里面的緩存幀d->finished = 0; // 重置為0d->next_pts = d->start_pts; // 主要用在了audiod->next_pts_tb = d->start_pts_tb;// 主要用在了audio} 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); // 一定要自己去釋放音視頻數(shù)據(jù)}}
    1. 重點:有針對 flush_pkt 的處理
    if (pkt.data == flush_pkt.data) {//// when seeking or when switching to a different streamavcodec_flush_buffers(d->avctx); //清空里面的緩存幀d->finished = 0; // 重置為0d->next_pts = d->start_pts; // 主要用在了audiod->next_pts_tb = d->start_pts_tb;// 主要用在了audio}
  • 了解過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:
  • // 4 計算幀持續(xù)時間和換算pts值為秒// 1/幀率 = duration 單位秒, 沒有幀率時則設(shè)置為0, 有幀率幀計算出幀間隔duration = (frame_rate.num && frame_rate.den ? av_q2d((AVRational) {frame_rate.den, frame_rate.num}) : 0);// 根據(jù)AVStream timebase計算出pts值, 單位為秒pts = (frame->pts == AV_NOPTS_VALUE) ? NAN : frame->pts * av_q2d(tb);// 5 將解碼后的視頻幀插入隊列ret = queue_picture(is, frame, pts, duration, frame->pkt_pos, is->viddec.pkt_serial);// 6 釋放frame對應(yīng)的數(shù)據(jù)av_frame_unref(frame); #if CONFIG_AVFILTERif (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); #endifif (!(vp = frame_queue_peek_writable(&is->pictq))) // 檢測隊列是否有可寫空間return -1; // Frame隊列滿了則返回-1// 執(zhí)行到這步說已經(jīng)獲取到了可寫入的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);av_frame_move_ref(vp->frame, src_frame); // 將src中所有數(shù)據(jù)拷貝到dst中,并復(fù)位src。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; // timebaseint ret = 0;if (!frame)return AVERROR(ENOMEM);do {// 1. 讀取解碼幀if ((got_frame = decoder_decode_frame(&is->auddec, frame, NULL)) < 0)goto the_end;if (got_frame) {tb = (AVRational){1, frame->sample_rate}; // 設(shè)置為sample_rate為timebase// 2. 獲取可寫Frameif (!(af = frame_queue_peek_writable(&is->sampq)))// 獲取可寫幀goto the_end;// 3. 設(shè)置Frame并放?FrameQueueaf->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); //轉(zhuǎn)移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; // 1. 流連續(xù)情況下獲取解碼后的幀if (d->queue->serial == d->pkt_serial) { // 1.1 先判斷是否是同?播放序列的數(shù)據(jù)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正常則先將其從pkt_timebase轉(zhuǎn)成{1, frame->sample_rate}// pkt_timebase實質(zhì)就是stream->time_baseframe->pts = av_rescale_q(frame->pts, d->avctx->pkt_timebase, tb);}else if (d->next_pts != AV_NOPTS_VALUE) {// 如果frame->pts不正常則使?上?幀更新的next_s和next_pts_tb// 轉(zhuǎn)成{1, frame->sample_rate}frame->pts = av_rescale_q(d->next_pts, d->next_pts_tb, tb);}if (frame->pts != AV_NOPTS_VALUE) {tb = (AVRational){1, frame->sample_rate}; // 設(shè)置為sample_rate為timebase18// 根據(jù)當(dāng)前幀的pts和nb_samples預(yù)估下?幀的ptsd->next_pts = frame->pts + frame->nb_samples;d->next_pts_tb = tb; // 設(shè)置timebase}}break;}....} while (ret != AVERROR(EAGAIN)); // 1.5 沒幀可讀時ret返回EAGIN,需要繼續(xù)送packet}....}
  • 從上可以看出來,將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)容還不錯,歡迎將生活随笔推薦給好友。