日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

1.框架分析

FFmpeg的FFplay框架分析

 

ffplay.c是FFmpeg源碼?帶的播放器,調(diào)?FFmpeg和SDL API實(shí)現(xiàn)?個(gè)?常有?的播放器。例如嗶哩嗶哩著名開源項(xiàng)?ijkplayer也是基于ffplay.c進(jìn)??次開發(fā)。ffplay實(shí)現(xiàn)了播放器的主體功能,掌握其原理對(duì)于我們獨(dú)?開發(fā)播放器?常有幫助。看看整體的框架,如下圖:

從整體上,分這幾個(gè)大的模塊,數(shù)據(jù)讀取,AVpacket緩存隊(duì)列,音頻解碼,視頻解碼,AVframe緩存隊(duì)列,聲音和視頻輸出,音視頻同步等模塊組成。

FFmpeg的FFplay框架分析

 

首先進(jìn)入stream_open,stream_open主要的工作是創(chuàng)建音視頻解碼前和后的數(shù)據(jù)緩存隊(duì)列,初始化時(shí)鐘,包括音頻,視頻,外部時(shí)鐘等,初始化數(shù)據(jù)讀取線程read_thread。源碼如下:

static VideoState *stream_open(const char *filename, AVInputFormat *iformat)
{
    VideoState *is;
    is = av_mallocz(sizeof(VideoState));
    if (!is)
        return NULL;
    is->filename = av_strdup(filename);
    if (!is->filename)
        goto fail;
    is->iformat = iformat;
    is->ytop    = 0;
    is->xleft   = 0;
    /* start video display */
  //初始化幀隊(duì)列
    if (frame_queue_init(&is->pictq, &is->videoq, VIDEO_PICTURE_QUEUE_SIZE, 1) < 0)
        goto fail;
    if (frame_queue_init(&is->subpq, &is->subtitleq, SUBPICTURE_QUEUE_SIZE, 0) < 0)
        goto fail;
    if (frame_queue_init(&is->sampq, &is->audioq, SAMPLE_QUEUE_SIZE, 1) < 0)
        goto fail;
//初始化packet隊(duì)列
    if (packet_queue_init(&is->videoq) < 0 ||
        packet_queue_init(&is->audioq) < 0 ||
        packet_queue_init(&is->subtitleq) < 0)
        goto fail;
    if (!(is->continue_read_thread = SDL_CreateCond())) {
        av_log(NULL, AV_LOG_FATAL, "SDL_CreateCond(): %sn", SDL_GetError());
        goto fail;
    }
//初始化視頻時(shí)鐘
    init_clock(&is->vidclk, &is->videoq.serial);
  //初始化音頻時(shí)鐘
    init_clock(&is->audclk, &is->audioq.serial);
  //初始化外部時(shí)鐘
    init_clock(&is->extclk, &is->extclk.serial);
    is->audio_clock_serial = -1;
    if (startup_volume < 0)
        av_log(NULL, AV_LOG_WARNING, "-volume=%d < 0, setting to 0n", startup_volume);
    if (startup_volume > 100)
        av_log(NULL, AV_LOG_WARNING, "-volume=%d > 100, setting to 100n", startup_volume);
    startup_volume = av_clip(startup_volume, 0, 100);
    startup_volume = av_clip(SDL_MIX_MAXVOLUME * startup_volume / 100, 0, SDL_MIX_MAXVOLUME);
    is->audio_volume = startup_volume;
    is->muted = 0;
    is->av_sync_type = av_sync_type;
  //初始化數(shù)據(jù)讀取線程
    is->read_tid     = SDL_CreateThread(read_thread, "read_thread", is);
    if (!is->read_tid) {
        av_log(NULL, AV_LOG_FATAL, "SDL_CreateThread(): %sn", SDL_GetError());
fail:
        stream_close(is);
        return NULL;
    }
    return is;
}

 

看看數(shù)據(jù)讀取線程read_thread做了什么?

大致就是一個(gè)解封轉(zhuǎn)過程,然后把解封裝的packet放到packet隊(duì)列。分別調(diào)用avformat_alloc_context(分配解封裝上下文),avforamt_open_input(打開文件或網(wǎng)絡(luò)流,內(nèi)存數(shù)據(jù)),avformat_find_stream_info(找到相關(guān)流信息),stream_component_open(打開音視頻流),av_read_frame(解封轉(zhuǎn)),packet_queue_put(放入到幀隊(duì)列)。源碼如下,詳細(xì)請(qǐng)看注釋:

typedef struct MyAVPacketList {
    AVPacket pkt;    struct MyAVPacketList *next;
    int serial;
} MyAVPacketList;typedef struct PacketQueue {
  //鏈表
    MyAVPacketList *first_pkt, *last_pkt;
  //多少個(gè)packet  
  int nb_packets;
  //每個(gè)packet大小
    int size;
  //每個(gè)packet時(shí)長
    int64_t duration;
  //請(qǐng)求標(biāo)志
    int abort_request;
    int serial;
  //鎖
    SDL_mutex *mutex;
  //條件變量  
  SDL_cond *cond;
} PacketQueue;

 

然后把解封裝的數(shù)據(jù),放到packet隊(duì)列。看看packet的封裝結(jié)構(gòu),實(shí)際就是一個(gè)鏈表。詳細(xì)源碼如下:

typedef struct MyAVPacketList {
    AVPacket pkt;    struct MyAVPacketList *next;
    int serial;
} MyAVPacketList;typedef struct PacketQueue {
  //鏈表
    MyAVPacketList *first_pkt, *last_pkt;
  //多少個(gè)packet  
  int nb_packets;
  //每個(gè)packet大小
    int size;
  //每個(gè)packet時(shí)長
    int64_t duration;
  //請(qǐng)求標(biāo)志
    int abort_request;
    int serial;
  //鎖
    SDL_mutex *mutex;
  //條件變量  
  SDL_cond *cond;
} PacketQueue;

視頻解碼線程,然后就從video的packet 隊(duì)列取數(shù)據(jù),看看做了什么?大致就是使用packet_queue_get去取packet,然后發(fā)送解碼,avcode_send_packet,avcode_receive_frame,并刷新解碼后的frame隊(duì)列。詳細(xì)看看源碼,如下:

可以看出ffplay.c還支持過濾器功能,功能還是很全。

FFmpeg的FFplay框架分析

 


FFmpeg的FFplay框架分析

 

取數(shù)據(jù)。

FFmpeg的FFplay框架分析

 

可以看出音視頻和subtitle的所調(diào)用的函數(shù)是不一樣。

FFmpeg的FFplay框架分析

 

解碼:

FFmpeg的FFplay框架分析

 

解碼完后的數(shù)據(jù),插入frame隊(duì)列。注意,視頻和音頻,都各自有自己frame隊(duì)列。

FFmpeg的FFplay框架分析

 


FFmpeg的FFplay框架分析

 

涉及到線程安全,就要加鎖。

FFmpeg的FFplay框架分析

 

音頻基本上也是走的這個(gè)流程。就不再敘述了。

最后就是顯示模塊了,取出數(shù)據(jù),然后根據(jù)當(dāng)前能支持的播放格式和尺寸,看看是否要格式轉(zhuǎn)換,也就是是否要調(diào)用sws_scale。

static void video_image_display(VideoState *is)
{
    Frame *vp;
    Frame *sp = NULL;
    SDL_Rect rect;
//從frame隊(duì)列取數(shù)據(jù)
    vp = frame_queue_peek_last(&is->pictq);
    if (is->subtitle_st) {
        if (frame_queue_nb_remaining(&is->subpq) > 0) {
            sp = frame_queue_peek(&is->subpq);
            if (vp->pts >= sp->pts + ((float) sp->sub.start_display_time / 1000)) {
                if (!sp->uploaded) {
                    uint8_t* pixels[4];
                    int pitch[4];
                    int i;
                    if (!sp->width || !sp->height) {
                        sp->width = vp->width;
                        sp->height = vp->height;
                    }
                    if (realloc_texture(&is->sub_texture, SDL_PIXELFORMAT_ARGB8888, sp->width, sp->height, SDL_BLENDMODE_BLEND, 1) < 0)
                        return;
                    for (i = 0; i < sp->sub.num_rects; i++) {
                        AVSubtitleRect *sub_rect = sp->sub.rects[i];
                        sub_rect->x = av_clip(sub_rect->x, 0, sp->width );
                        sub_rect->y = av_clip(sub_rect->y, 0, sp->height);
                        sub_rect->w = av_clip(sub_rect->w, 0, sp->width  - sub_rect->x);
                        sub_rect->h = av_clip(sub_rect->h, 0, sp->height - sub_rect->y);
                        is->sub_convert_ctx = sws_getCachedContext(is->sub_convert_ctx,
                            sub_rect->w, sub_rect->h, AV_PIX_FMT_PAL8,
                            sub_rect->w, sub_rect->h, AV_PIX_FMT_BGRA,
                            0, NULL, NULL, NULL);
                        if (!is->sub_convert_ctx) {
                            av_log(NULL, AV_LOG_FATAL, "Cannot initialize the conversion contextn");
                            return;
                        }
                        if (!SDL_LockTexture(is->sub_texture, (SDL_Rect *)sub_rect, (void **)pixels, pitch)) {
                         //格式轉(zhuǎn)換
                          sws_scale(is->sub_convert_ctx, (const uint8_t * const *)sub_rect->data, sub_rect->linesize,
                                      0, sub_rect->h, pixels, pitch);
                            SDL_UnlockTexture(is->sub_texture);
                        }
                    }
                    sp->uploaded = 1;
                }
            } else
                sp = NULL;
        }
    }
//計(jì)算顯示區(qū)域
    calculate_display_rect(&rect, is->xleft, is->ytop, is->width, is->height, vp->width, vp->height, vp->sar);
    if (!vp->uploaded) {
        if (upload_texture(&is->vid_texture, vp->frame, &is->img_convert_ctx) < 0)
            return;
        vp->uploaded = 1;
        vp->flip_v = vp->frame->linesize[0] < 0;
    }
    set_sdl_yuv_conversion_mode(vp->frame);
    SDL_RenderCopyEx(renderer, is->vid_texture, NULL, &rect, 0, NULL, vp->flip_v ? SDL_FLIP_VERTICAL : 0);
    set_sdl_yuv_conversion_mode(NULL);
    if (sp) {
#if USE_ONEPASS_SUBTITLE_RENDER
        SDL_RenderCopy(renderer, is->sub_texture, NULL, &rect);
#else
        int i;
        double xratio = (double)rect.w / (double)sp->width;
        double yratio = (double)rect.h / (double)sp->height;
        for (i = 0; i < sp->sub.num_rects; i++) {
            SDL_Rect *sub_rect = (SDL_Rect*)sp->sub.rects[i];
            SDL_Rect target = {.x = rect.x + sub_rect->x * xratio,
                               .y = rect.y + sub_rect->y * yratio,
                               .w = sub_rect->w * xratio,
                               .h = sub_rect->h * yratio};
            SDL_RenderCopy(renderer, is->sub_texture, sub_rect, &target);
        }
#endif
    }
}
static int audio_decode_frame(VideoState *is)
{    int data_size, resampled_data_size;    int64_t dec_channel_layout;    av_unused double audio_clock0;    int wanted_nb_samples;    Frame *af;    if (is->paused)
        return -1;
    do {
#if defined(_WIN32)
        while (frame_queue_nb_remaining(&is->sampq) == 0) {
            if ((av_gettime_relative() - audio_callback_time) > 1000000LL * is->audio_hw_buf_size / is->audio_tgt.bytes_per_sec / 2)
                return -1;
            av_usleep (1000);
        }#endif        if (!(af = frame_queue_peek_readable(&is->sampq)))
            return -1;
        frame_queue_next(&is->sampq);    } while (af->serial != is->audioq.serial);
    data_size = av_samples_get_buffer_size(NULL, af->frame->channels,                                           af->frame->nb_samples,                                           af->frame->format, 1);
    dec_channel_layout =        (af->frame->channel_layout && af->frame->channels == av_get_channel_layout_nb_channels(af->frame->channel_layout)) ?        af->frame->channel_layout : av_get_default_channel_layout(af->frame->channels);    wanted_nb_samples = synchronize_audio(is, af->frame->nb_samples);    if (af->frame->format        != is->audio_src.fmt            ||
        dec_channel_layout       != is->audio_src.channel_layout ||        af->frame->sample_rate   != is->audio_src.freq           ||        (wanted_nb_samples       != af->frame->nb_samples && !is->swr_ctx)) {        swr_free(&is->swr_ctx);        is->swr_ctx = swr_alloc_set_opts(NULL,                                         is->audio_tgt.channel_layout, is->audio_tgt.fmt, is->audio_tgt.freq,                                         dec_channel_layout,           af->frame->format, af->frame->sample_rate,                                         0, NULL);
        if (!is->swr_ctx || swr_init(is->swr_ctx) < 0) {
            av_log(NULL, AV_LOG_ERROR,                   "Cannot create sample rate converter for conversion of %d Hz %s %d channels to %d Hz %s %d channels!n",
                    af->frame->sample_rate, av_get_sample_fmt_name(af->frame->format), af->frame->channels,                    is->audio_tgt.freq, av_get_sample_fmt_name(is->audio_tgt.fmt), is->audio_tgt.channels);            swr_free(&is->swr_ctx);            return -1;
        }        is->audio_src.channel_layout = dec_channel_layout;        is->audio_src.channels       = af->frame->channels;        is->audio_src.freq = af->frame->sample_rate;        is->audio_src.fmt = af->frame->format;    }    if (is->swr_ctx) {
        const uint8_t **in = (const uint8_t **)af->frame->extended_data;
        uint8_t **out = &is->audio_buf1;        int out_count = (int64_t)wanted_nb_samples * is->audio_tgt.freq / af->frame->sample_rate + 256;
        int out_size  = av_samples_get_buffer_size(NULL, is->audio_tgt.channels, out_count, is->audio_tgt.fmt, 0);
        int len2;        if (out_size < 0) {
            av_log(NULL, AV_LOG_ERROR, "av_samples_get_buffer_size() failedn");
            return -1;
        }        if (wanted_nb_samples != af->frame->nb_samples) {
            if (swr_set_compensation(is->swr_ctx, (wanted_nb_samples - af->frame->nb_samples) * is->audio_tgt.freq / af->frame->sample_rate,
                                        wanted_nb_samples * is->audio_tgt.freq / af->frame->sample_rate) < 0) {
                av_log(NULL, AV_LOG_ERROR, "swr_set_compensation() failedn");
                return -1;
            }        }        av_fast_malloc(&is->audio_buf1, &is->audio_buf1_size, out_size);        if (!is->audio_buf1)
            return AVERROR(ENOMEM);
        len2 = swr_convert(is->swr_ctx, out, out_count, in, af->frame->nb_samples);
        if (len2 < 0) {
            av_log(NULL, AV_LOG_ERROR, "swr_convert() failedn");
            return -1;
        }        if (len2 == out_count) {
            av_log(NULL, AV_LOG_WARNING, "audio buffer is probably too smalln");
            if (swr_init(is->swr_ctx) < 0)
                swr_free(&is->swr_ctx);        }        is->audio_buf = is->audio_buf1;        resampled_data_size = len2 * is->audio_tgt.channels * av_get_bytes_per_sample(is->audio_tgt.fmt);    } else {
        is->audio_buf = af->frame->data[0];
        resampled_data_size = data_size;    }    audio_clock0 = is->audio_clock;    /* update the audio clock with the pts */
    if (!isnan(af->pts))
        is->audio_clock = af->pts + (double) af->frame->nb_samples / af->frame->sample_rate;    else
        is->audio_clock = NAN;    is->audio_clock_serial = af->serial;#ifdef DEBUG    {        static double last_clock;
        printf("audio: delay=%0.3f clock=%0.3f clock0=%0.3fn",
               is->audio_clock - last_clock,               is->audio_clock, audio_clock0);        last_clock = is->audio_clock;    }#endif    return resampled_data_size;
}

音頻也是類似的情況,調(diào)用SDL去播放聲音。

 

總體上總結(jié)下:

(1)數(shù)據(jù)讀取線程主要的工作:

打開媒體?件。

打開對(duì)應(yīng)碼流的decoder以及初始化對(duì)應(yīng)的audio、video、subtitle輸出。

創(chuàng)建decoder線程,audio、video和subtitle的解碼線程獨(dú)?。

調(diào)?av_read_frame讀取packet,并根據(jù)steam_index放?不同stream對(duì)應(yīng)的packet隊(duì)列。

(2)?頻解碼

從packet queue讀取packet,解出frame后放?frame queue。

(3)視頻解碼

從packet queue讀取packet,解出frame后放?frame queue。

(4)字幕解碼

從packet queue讀取packet,解出frame后放?frame queue。

(5)?頻播放(或者回調(diào)函數(shù))

從frame queue讀取frame進(jìn)?播放。

(6)視頻播放,ffplay?前是在main主線程進(jìn)?視頻播放。我個(gè)人覺得放到子線程比較好。

從frame queue讀取frame進(jìn)?播放。

(7)字幕播放,ffplay?前是在main主線程進(jìn)?字幕播放。我個(gè)人覺得放到子線程比較好。

從frame queue讀取frame進(jìn)?播放。

(8)控制響應(yīng)(播放/暫停/快進(jìn)/快退等)

ffplay?前是在main主線程進(jìn)?播放控制。

(9)packet隊(duì)列的設(shè)計(jì)

線程安全,?持互斥、等待、喚醒。

緩存數(shù)據(jù)??。

緩存包數(shù)。

隊(duì)列播放可持續(xù)時(shí)間。

進(jìn)隊(duì)列/出隊(duì)列等。

(10)frame隊(duì)列的設(shè)計(jì)

線程安全,?持互斥、等待、喚醒。

緩存幀數(shù)。

?持讀取數(shù)據(jù)?不出隊(duì)列。

進(jìn)隊(duì)列/出隊(duì)列等。

(11)?視頻同步

?頻同步。

視頻同步。

外部時(shí)鐘同步。

(12)?頻處理

?量調(diào)節(jié)。

靜?。

重采樣。

(12)視頻處理

圖像格式轉(zhuǎn)換RGB->YUV等。

圖像縮放1280*720->800*480等。

(13)播放器控制

播放。

暫停。

停?。

快進(jìn)/快退。

逐幀。

靜?。

本篇文章分析到這里,后面還會(huì)繼續(xù)分析,歡迎關(guān)注,點(diǎn)贊,轉(zhuǎn)發(fā),收藏。

分享到:
標(biāo)簽:FFmpeg
用戶無頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績?cè)u(píng)定