写点什么

FFmpeg-ffplay 播放器分析(1).md

作者:Changing Lin
  • 2022-11-01
    广东
  • 本文字数:3700 字

    阅读完需:约 12 分钟

1.背景:

本文主要分析 ffplay 播放器的基础框架,从而理解代码结构和工作原理,同时,理解时间戳和音视频同步等知识点,掌握 ffmpeg 基础开发能力。

2.正文

1.main 方法


/* Called from the main */int main(int argc, char **argv){    avformat_network_init(); // 初始化ffmpeg框架    SDL_Init (flags);    SDL_CreateWindow    SDL_CreateRenderer    SDL_GetRendererInfo    is = stream_open(input_filename, file_iformat);    event_loop(is);}
复制代码


2.stream_open 方法


static VideoState *stream_open(const char *filename, AVInputFormat *iformat){    frame_queue_init    packet_queue_init    init_clock    SDL_CreateThread(read_thread, "read_thread", is); // 新建一个名字叫read_thread的子线程}
复制代码


3.event_loop 方法


static void event_loop(VideoState *cur_stream)for (;;) {    refresh_loop_wait_event(cur_stream, &event); // 监听各种输入事件,移动进度,快进快退,退出,鼠标事件等    switch (event.type) {        case SDL_KEYDOWN:            switch (event.key.keysym.sym) {            case SDLK_f:                break;            case SDLK_p:            case SDLK_SPACE:                break;            case SDLK_m:                break;            case SDLK_KP_MULTIPLY:            case SDLK_0:                break;            case SDLK_KP_DIVIDE:            case SDLK_9:                break;            case SDLK_s: // S: Step to next frame                break;            case SDLK_a:                break;            case SDLK_v:                break;            case SDLK_c:                break;            case SDLK_t:                break;            case SDLK_w:                break;            case SDLK_PAGEUP:                break;            case SDLK_PAGEDOWN:                break;            case SDLK_LEFT:                goto do_seek;            case SDLK_RIGHT:                goto do_seek;            case SDLK_UP:                goto do_seek;            case SDLK_DOWN:            do_seek:                break;            default:                break;            }            break;        case SDL_MOUSEBUTTONDOWN:                    case SDL_MOUSEMOTION:                        break;        case SDL_WINDOWEVENT:            break;        case SDL_QUIT:        case FF_QUIT_EVENT:            break;        default:            break;        }}}
复制代码


4.read_thread 子线程


static int read_thread(void *arg){   AVFormatContext * ic = avformat_alloc_context();    avformat_open_input(&ic, is->filename, is->iformat, &format_opts);    av_format_inject_global_side_data(ic);    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 (;;) {        ret = av_read_frame(ic, pkt);        if (ret < 0) {            packet_queue_put_nullpacket(&is->videoq, is->video_stream);            or packet_queue_put_nullpacket(&is->audioq, is->audio_stream);            or packet_queue_put_nullpacket(&is->subtitleq, is->subtitle_stream);            continue;        }        packet_queue_put(&is->audioq, pkt);        or packet_queue_put(&is->videoq, pkt);        or packet_queue_put(&is->subtitleq, pkt);    }}
复制代码


5.stream_component_open 方法


static int stream_component_open(VideoState *is, int stream_index){    avctx = avcodec_alloc_context3(NULL);    codec = avcodec_find_decoder(avctx->codec_id);    ret = avcodec_open2(avctx, codec, &opts)    switch (avctx->codec_type) {        case AVMEDIA_TYPE_AUDIO:            decoder_init(&is->auddec, avctx, &is->audioq, is->continue_read_thread);            ret = decoder_start(&is->auddec, audio_thread, "audio_decoder", is)            break;        case AVMEDIA_TYPE_VIDEO:            decoder_init(&is->viddec, avctx, &is->videoq, is->continue_read_thread);            ret = decoder_start(&is->viddec, video_thread, "video_decoder", is)            break;        case AVMEDIA_TYPE_SUBTITLE:            break;    }}
复制代码


6.decoder_init/decoder_start 方法


    d->decoder_tid = SDL_CreateThread(fn, thread_name, arg); // 启动子线程
复制代码


7.video_decoder 子线程


static int video_thread(void *arg){    for (;;) {        ret = get_video_frame(is, frame);        ret = queue_picture(is, frame, pts, duration, frame->pkt_pos, is->viddec.pkt_serial);    }}
复制代码


8.queue_picture 方法


static int queue_picture(VideoState *is, AVFrame *src_frame, double pts, double duration, int64_t pos, int serial){    av_frame_move_ref(vp->frame, src_frame);    frame_queue_push(&is->pictq)}
复制代码


9.get_video_frame 方法


static int get_video_frame(VideoState *is, AVFrame *frame){    got_picture = decoder_decode_frame(&is->viddec, frame, NULL);    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++; // 计算是否丢帧,会不会是追帧策略?                    av_frame_unref(frame);                    got_picture = 0;                }            }        }}
复制代码


10.decoder_decode_frame 方法


static int decoder_decode_frame(Decoder *d, AVFrame *frame, AVSubtitle *sub) {    for (;;) {        ret = avcodec_receive_frame(d->avctx, frame);        packet_queue_get(d->queue, &pkt, 1, &d->pkt_serial)        avcodec_send_packet(d->avctx, &pkt) == AVERROR(EAGAIN)    }}
复制代码


11.refresh_loop_wait_event 方法


static void refresh_loop_wait_event(VideoState *is, SDL_Event *event) {    double remaining_time = 0.0;    SDL_PumpEvents();    while (!SDL_PeepEvents(event, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT)) {        if (!cursor_hidden && av_gettime_relative() - cursor_last_shown > CURSOR_HIDE_DELAY) {            SDL_ShowCursor(0);            cursor_hidden = 1;        }        if (remaining_time > 0.0)            av_usleep((int64_t)(remaining_time * 1000000.0));        remaining_time = REFRESH_RATE;        if (is->show_mode != SHOW_MODE_NONE && (!is->paused || is->force_refresh))            video_refresh(is, &remaining_time);        SDL_PumpEvents();    }}
复制代码


12.video_refresh->video_display->video_image_display 方法


static void video_refresh(void *opaque, double *remaining_time){    video_display(is);}static void video_display(VideoState *is){    if (!is->width)        video_open(is);    if (is->audio_st && is->show_mode != SHOW_MODE_VIDEO)        video_audio_display(is);    else if (is->video_st)        video_image_display(is);    SDL_RenderPresent(renderer);}static void video_image_display(VideoState *is){    vp = frame_queue_peek_last(&is->pictq);    upload_texture    SDL_RenderCopyEx    SDL_RenderCopy}
复制代码


3.总结

3.1 多线程

  • 通过梳理,ffplay 播放器总共开了 5 个线程

  • 主线程:循环监听输入事件,从 FrameQueue 中获取每一帧 Frame,渲染画面或播放音频

  • read_thread:启动下面 3 个子线程,循环从 Stream 中读取每一个 AVPacket,并且放到 PacketQueue 里面

  • video_thread:循环从 PacketQueue 里面获取一个 AVPacket,并且解码成一个 Frame,计算是否丢帧策略,放到 FrameQueue 里面

  • audio_thread:如上,只不过 AVPacket 的类型是 audio,也就是音频包、音频帧

  • subtitle_thread:如上,只不过 AVPacket 的类型是 subtitle,也就是字幕包、字幕帧

3.2 时间戳:

  • 音视频同步

  • 快进、快退、从指定位置开始播放

发布于: 刚刚阅读数: 4
用户头像

Changing Lin

关注

获得机遇的手段远超于固有常规之上~ 2020-04-29 加入

我能做的,就是调整好自己的精神状态,以最佳的面貌去面对那些未曾经历过得事情,对生活充满热情和希望。

评论

发布
暂无评论
FFmpeg-ffplay播放器分析(1).md_音视频_Changing Lin_InfoQ写作社区