【FFMPEG源码分析】从ffplay源码摸清ffmpeg框架(一)

ffplay入口

ffmpeg\fftools\ffplay.c
int main(int argc, char **argv)
{
    /*******************start 动态库加载/网络初始化等**************/
    int flags;
    VideoState *is;
    init_dynload();
    av_log_set_flags(AV_LOG_SKIP_REPEATED);
    parse_loglevel(argc, argv, options);
    /* register all codecs, demux and protocols */
#if CONFIG_AVDEVICE
    avdevice_register_all();
#endif
    avformat_network_init();
    signal(SIGINT , sigterm_handler); /* Interrupt (ANSI).    */
    signal(SIGTERM, sigterm_handler); /* Termination (ANSI).  */
    show_banner(argc, argv, options);
    parse_options(NULL, argc, argv, options, opt_input_file);
    if (!input_filename) {
        show_usage();
        av_log(NULL, AV_LOG_FATAL, "An input file must be specified\n");
        av_log(NULL, AV_LOG_FATAL,
               "Use -h to get full help or, even better, run 'man %s'\n", program_name);
        exit(1);
    }
    /*******************end动态库加载/网络初始化等**************/
    /*******************start 视频帧显示窗口初始化*******************/
    if (display_disable) {
        video_disable = 1;
    }
    flags = SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER;
    if (audio_disable)
        flags &= ~SDL_INIT_AUDIO;
    else {
        /* Try to work around an occasional ALSA buffer underflow issue when the
         * period size is NPOT due to ALSA resampling by forcing the buffer size. */
        if (!SDL_getenv("SDL_AUDIO_ALSA_SET_BUFFER_SIZE"))
            SDL_setenv("SDL_AUDIO_ALSA_SET_BUFFER_SIZE","1", 1);
    }
    if (display_disable)
        flags &= ~SDL_INIT_VIDEO;
    if (SDL_Init (flags)) {
        av_log(NULL, AV_LOG_FATAL, "Could not initialize SDL - %s\n", SDL_GetError());
        av_log(NULL, AV_LOG_FATAL, "(Did you set the DISPLAY variable?)\n");
        exit(1);
    }
    SDL_EventState(SDL_SYSWMEVENT, SDL_IGNORE);
    SDL_EventState(SDL_USEREVENT, SDL_IGNORE);
    if (!display_disable) {
        int flags = SDL_WINDOW_HIDDEN;
        if (alwaysontop)
#if SDL_VERSION_ATLEAST(2,0,5)
            flags |= SDL_WINDOW_ALWAYS_ON_TOP;
#else
            av_log(NULL, AV_LOG_WARNING, "Your SDL version doesn't support SDL_WINDOW_ALWAYS_ON_TOP. Feature will be inactive.\n");
#endif
       if (borderless)
            flags |= SDL_WINDOW_BORDERLESS;
        else
            flags |= SDL_WINDOW_RESIZABLE;

#ifdef SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR
        SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0");
#endif
        window = SDL_CreateWindow(program_name, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, default_width, default_height, flags);
        SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
        if (window) {
            renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
            if (!renderer) {
                av_log(NULL, AV_LOG_WARNING, "Failed to initialize a hardware accelerated renderer: %s\n", SDL_GetError());
                renderer = SDL_CreateRenderer(window, -1, 0);
            }
            if (renderer) {
                if (!SDL_GetRendererInfo(renderer, &renderer_info))
                    av_log(NULL, AV_LOG_VERBOSE, "Initialized %s renderer.\n", renderer_info.name);
            }
        }
        if (!window || !renderer || !renderer_info.num_texture_formats) {
            av_log(NULL, AV_LOG_FATAL, "Failed to create window or renderer: %s", SDL_GetError());
            do_exit(NULL);
        }
    }
    /*******************end 视频帧显示窗口初始化*******************/
    
    /******************* 下载数据/demux/decoder*******************/
    is = stream_open(input_filename, file_iformat);
    if (!is) {
        av_log(NULL, AV_LOG_FATAL, "Failed to initialize VideoState!\n");
        do_exit(NULL);
    }
    event_loop(is);
    return 0;
}

从上面main函数入口来看,main中大部分是一些init工作,而ffmpeg的核心工作demux/decoder的工作都封装在stream_open中了。
想要摸清ffmpeg的框架,学习思路是追踪音视频数据的流动过程。

  1. 原始数据通过什么模块获取?
  2. 如何通过原始数据匹配到对应的demuxer ,如: ts, mp4, mov, avi等?
  3. 如何通过demuxer后的audio/video数据匹配到对应的decoder, 如:h264, vp9, aac等?
  4. ffmpeg中数据流转的驱动模型是什么样的?也就是数据是从什么地方驱动开始下载/demux/decode ?
static VideoState *stream_open(const char *filename,
                               const AVInputFormat *iformat)
{
    VideoState *is = av_mallocz(sizeof(VideoState));
    is->last_video_stream = is->video_stream = -1;
    is->last_audio_stream = is->audio_stream = -1;
    is->last_subtitle_stream = is->subtitle_stream = -1;
    is->filename = av_strdup(filename);
    is->iformat = iformat;
    is->ytop    = 0;
    is->xleft   = 0;
    is->continue_read_thread = SDL_CreateCond();
    /* start video display */
   frame_queue_init(&is->pictq, &is->videoq, VIDEO_PICTURE_QUEUE_SIZE, 1);
   frame_queue_init(&is->subpq, &is->subtitleq, SUBPICTURE_QUEUE_SIZE, 0);
   frame_queue_init(&is->sampq, &is->audioq, SAMPLE_QUEUE_SIZE, 1);
   packet_queue_init(&is->videoq);
   packet_queue_init(&is->audioq);
   packet_queue_init(&is->subtitleq);
   
   init_clock(&is->vidclk, &is->videoq.serial);
   init_clock(&is->audclk, &is->audioq.serial);
   init_clock(&is->extclk, &is->extclk.serial);
   is->audio_clock_serial = -1;
   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;
   is->read_tid     = SDL_CreateThread(read_thread, "read_thread", is);
   if (!is->read_tid) {
        av_log(NULL, AV_LOG_FATAL, "SDL_CreateThread(): %s\n", SDL_GetError());
fail:
        stream_close(is);
        return NULL;
    return is;
}

从上面的代码看,stream_open主要是用于初始化VideoState这个结构体,初始化audio/video/subtitile解码后数据的buffer queque。结构体的具体细节先暂不深入。
另外在stream_open中创建了一个thread: read_thread, 看来干活的是这个了。。。

static int read_thread(void *arg)
{
    VideoState *is = arg;
    AVPacket *pkt = av_packet_alloc();
    AVFormatContext *ic = avformat_alloc_context();
    //启动数据下载模块,demux模块
    avformat_open_input(&ic, is->filename, is->iformat, &format_opts);
    is->ic = ic;
    av_format_inject_global_side_data(ic);
    if (find_stream_info) {
        AVDictionary **opts = setup_find_stream_info_opts(ic, codec_opts);
        avformat_find_stream_info(ic, opts);
    }
    if (!video_disable)
        st_index[AVMEDIA_TYPE_VIDEO] =
            av_find_best_stream(ic, AVMEDIA_TYPE_VIDEO,
                                st_index[AVMEDIA_TYPE_VIDEO], -1, NULL, 0);
    if (!audio_disable)
        st_index[AVMEDIA_TYPE_AUDIO] =
            av_find_best_stream(ic, AVMEDIA_TYPE_AUDIO,
                                st_index[AVMEDIA_TYPE_AUDIO],
                                st_index[AVMEDIA_TYPE_VIDEO],
                                NULL, 0);
    if (!video_disable && !subtitle_disable)
        st_index[AVMEDIA_TYPE_SUBTITLE] =
            av_find_best_stream(ic, AVMEDIA_TYPE_SUBTITLE,
                                st_index[AVMEDIA_TYPE_SUBTITLE],
                                (st_index[AVMEDIA_TYPE_AUDIO] >= 0 ?
                                 st_index[AVMEDIA_TYPE_AUDIO] :
                                 st_index[AVMEDIA_TYPE_VIDEO]),
                                NULL, 0);
    // 启动decoder thread
    stream_component_open(is, st_index[AVMEDIA_TYPE_AUDIO]);
    stream_component_open(is, st_index[AVMEDIA_TYPE_VIDEO]);
    stream_component_open(is, st_index[AVMEDIA_TYPE_SUBTITLE]);

    for (;;) {
        if (is->abort_request)
            break;
        if (is->paused != is->last_paused) {
            is->last_paused = is->paused;
            if (is->paused)
                is->read_pause_return = av_read_pause(ic);
            else
                av_read_play(ic);
        }
        //读取demux后的数据,并存入audio/video/subtitle的buffer queue中
        av_read_frame(ic, pkt);
        /* check if packet is in play range specified by user, then queue, otherwise discard */
        stream_start_time = ic->streams[pkt->stream_index]->start_time;
        pkt_ts = pkt->pts == AV_NOPTS_VALUE ? pkt->dts : pkt->pts;
        pkt_in_play_range = duration == AV_NOPTS_VALUE ||
                (pkt_ts - (stream_start_time != AV_NOPTS_VALUE ? stream_start_time : 0)) *
                av_q2d(ic->streams[pkt->stream_index]->time_base) -
                (double)(start_time != AV_NOPTS_VALUE ? start_time : 0) / 1000000
                <= ((double)duration / 1000000);
        if (pkt->stream_index == is->audio_stream && pkt_in_play_range) {
            packet_queue_put(&is->audioq, pkt);
        } else if (pkt->stream_index == is->video_stream && pkt_in_play_range
                   && !(is->video_st->disposition & AV_DISPOSITION_ATTACHED_PIC)) {
            packet_queue_put(&is->videoq, pkt);
        } else if (pkt->stream_index == is->subtitle_stream && pkt_in_play_range) {
            packet_queue_put(&is->subtitleq, pkt);
        } else {
            av_packet_unref(pkt);
        }
    }
    ..............;
    return 0;
}

从上面read_thread代码看,主要是通过avformat_open_input(…)启动数据下载和demux,再通过stream_component_open(…) 启动audio/video/subtitle的decoder。最后启动死循环通过av_read_frame(…) 读取demux出来的audio/video/subtitle存入对应的buffer queue供decoder使用。
下面再看看decoder是如何消耗数据的?以及decoder完成的数据缓存在什么地方?

static int stream_component_open(VideoState *is, int stream_index)
{
    AVFormatContext *ic = is->ic;
    const AVCodec *codec;
    const char *forced_codec_name = NULL;
    AVDictionary *opts = NULL;
    const AVDictionaryEntry *t = NULL;
    int sample_rate;
    AVChannelLayout ch_layout = { 0 };
    int ret = 0;
    int stream_lowres = lowres;

    AVCodecContext *avctx = avcodec_alloc_context3(NULL);
    avcodec_parameters_to_context(avctx, ic->streams[stream_index]->codecpar);
    const AVCodec * codec = avcodec_find_decoder(avctx->codec_id);
    avctx->codec_id = codec->id;
    AVDictionary *opts = filter_codec_opts(codec_opts, avctx->codec_id, ic, ic->streams[stream_index], codec);
    avcodec_open2(avctx, codec, &opts));
   
    is->eof = 0;
    ic->streams[stream_index]->discard = AVDISCARD_DEFAULT;
    switch (avctx->codec_type) {
    case AVMEDIA_TYPE_AUDIO:
        /* prepare audio output */
        audio_open(is, &ch_layout, sample_rate, &is->audio_tgt))decoder_init(&is->auddec, avctx, &is->audioq, is->continue_read_thread))decoder_start(&is->auddec, audio_thread, "audio_decoder", is))static SDL_AudioDeviceID audio_dev;
        SDL_PauseAudioDevice(audio_dev, 0);
        break;
    case AVMEDIA_TYPE_VIDEO:
        is->video_stream = stream_index;
        is->video_st = ic->streams[stream_index];
        decoder_init(&is->viddec, avctx, &is->videoq, is->continue_read_thread))decoder_start(&is->viddec, video_thread, "video_decoder", is));
        is->queue_attachments_req = 1;
        break;
    case AVMEDIA_TYPE_SUBTITLE:
        is->subtitle_stream = stream_index;
        is->subtitle_st = ic->streams[stream_index];
        decoder_init(&is->subdec, avctx, &is->subtitleq, is->continue_read_thread))decoder_start(&is->subdec, subtitle_thread, "subtitle_decoder", is))
        break;
    default:
        break;
    }
    ....................;
    return ret;
}

ffplay内部结构图

通过stream_component_open函数的代码可以看出,先初始化decoder,再创建thread。
至此, ffplay的大致脉络已经摸清楚了,具体看下图:
ffplay_thread
结构关系图:
ff_state
后面的文章继续深入分析demuxer和decoder。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值