03-流媒体-ffplay.c源码分析

ffplay.c是一个使用ffmpeg库的参考代码,实现了一个播放器的功能,源代码的的目录是:
ffmpeg-4.2.2/fftools/ffplay.c
ffplay.c可以作为入门来了解ffmpeg,我们也可以学习其编程方法实践到我们以后的开发中。本节和下一节对ffplay.c的源代码进行一个简单的描述,可以帮助读者来快速了解ffplay工具。另外ffplay.c的代码量还比较多,对于初学者而言,先了解其重要点,后面在做项目时慢慢深入即可。

假如本文媒体文件是由视频h264格式,音频aac格式,字幕封装而成。
1.我们从主函数开始分析,发现主要的功能函数如下:

int main(int argc, char **argv)
{
    ......
    avdevice_register_all();   
   ......
    parse_options(NULL, argc, argv, options, opt_input_file);
    ......
    if (SDL_Init (flags)) {
    ......
    is = stream_open(input_filename, file_iformat);
   ......
    event_loop(is);
    return 0;
}

(1)注册编码解码器:avdevice_register_all();
(2)解析输入命令:parse_options();
用于解析用客输入的运行指令,了解用户意途
(3)初始化SDL,用于显示:SDL_Init ()
ffplay工具是用SDL控件来播放音视频
(4)打开媒体文件:stream_open()
这个函数是ffplay中最重要的函数,其作用是读一个媒体文件,然后将文件进行解封装,再然后进行解码。
(5)事件刷新(包括视频刷新): event_loop()
这个函数最主要的作用是将stream_open()得到的音视频帧进行播放。

至此,我们可以知道,播放器主要由两个函数完成工作,stream_open和event_loop

2.分析两个主要函数的工作内容:
2.1读文件函数stream_open():
(1)音视频媒体文件->解封装->视频编码文件(如h264)、音频(如aac)、字幕。
后面的处理视频、音频、字幕从原理上差不多,本节以视频为例子进行说明
(2)h264帧->存入packet队列。并创建一个用于解码h264帧的线程。
(3)从packet队列,取出一帧h264帧,用ffmpeg的api解码函数进行解码。h264->YUV数据。
(4)YUV帧->存入frame队列
2.2事件刷新函数event_loop():
(1).从frame中读取一个YUV帧
(2).SDL播放
(3)在播放过程中有音视频同步的策略协调

3.一步一步分析读文件函数
3.1 打开文件函数 stream_open()内容如下

static VideoState *stream_open(const char *filename, AVInputFormat *iformat)
{
    ......
    if (frame_queue_init(&is->pictq, &is->videoq, VIDEO_PICTURE_QUEUE_SIZE, 1) < 0)
   ......
    if (frame_queue_init(&is->subpq, &is->subtitleq, SUBPICTURE_QUEUE_SIZE, 0) < 0)
    ......
    if (frame_queue_init(&is->sampq, &is->audioq, SAMPLE_QUEUE_SIZE, 1) < 0)
    ......
    if (packet_queue_init(&is->videoq) < 0 ||
        packet_queue_init(&is->audioq) < 0 ||
        packet_queue_init(&is->subtitleq) < 0)
    ......
    init_clock(&is->vidclk, &is->videoq.serial);
    init_clock(&is->audclk, &is->audioq.serial);
    init_clock(&is->extclk, &is->extclk.serial);
   ......
    is->read_tid     = SDL_CreateThread(read_thread, "read_thread", is);
   ......
}

(1)用于缓存功能的初始化
frame_queue_init()
packet_queue_init()
此两个函数,分别对应frame队列和packet队列,frame队列用于存放YUV数据,而packet队列用于存放如h264之类的数据

(2)同步时钟初始化
init_clock()
(3)读文件线程
read_thread
创建一个线程,专门用于读取媒体文件,并将媒体文件所包含的h264、aac,和字幕放在不同的packet队列,这步的过程叫解封装。

3.2read_thread内容

static int read_thread(void *arg)
{
  .....
    err = avformat_open_input(&ic, is->filename, is->iformat, &format_opts);
   ......
        av_dump_format(ic, 0, is->filename, 0);
   ......
        ret = stream_component_open(is, st_index[AVMEDIA_TYPE_VIDEO]);
   ......
        ret = av_read_frame(ic, pkt);
    ......
            packet_queue_put(&is->videoq, pkt);
 ......
}

(1)//打开媒体
avformat_open_input();
(2)//输出媒体信息到控制台
av_dump_format();
(3)//打开视频/音频/字幕解码线程
stream_component_open();

(4)//获取一帧压缩编码的数据
av_read_frame();
(5)//将压缩编码的数据放入队列
packet_queue_put();

3.3 stream_component_open()内容

static int stream_component_open(VideoState *is, int stream_index)
{
    ......
    codec = avcodec_find_decoder(avctx->codec_id);
  ......
        if ((ret = audio_open(is, channel_layout, nb_channels, sample_rate, &is->audio_tgt)) < 0)
       ......
        decoder_init(&is->auddec, avctx, &is->audioq, is->continue_read_thread);
       ......
        if ((ret = decoder_start(&is->auddec, audio_thread, "audio_decoder", is)) < 0)
         ......
        SDL_PauseAudioDevice(audio_dev, 0);
       ......
        decoder_init(&is->viddec, avctx, &is->videoq, is->continue_read_thread);
        if ((ret = decoder_start(&is->viddec, video_thread, "video_decoder", is)) < 0)
         ......
    ......
        decoder_init(&is->subdec, avctx, &is->subtitleq, is->continue_read_thread);
        if ((ret = decoder_start(&is->subdec, subtitle_thread, "subtitle_decoder", is)) < 0)
       ......
}

(1)//获取解码器
avcodec_find_decoder();
(2)//开启音频、视频、字幕的解码线程
decoder_start()开启视频解码线程

3.4video_thread解码线程

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);
 ......
}

主要函数get_video_frame里面包含decoder_decode_frame函数
get_video_frame()->decoder_decode_frame()

3.5decoder_decode_frame内容

static int decoder_decode_frame(Decoder *d, AVFrame *frame, AVSubtitle *sub) {
    int ret = AVERROR(EAGAIN);
    for (;;) {
       ......
                        ret = avcodec_receive_frame(d->avctx, frame);
                       ......
                if (packet_queue_get(d->queue, &pkt, 1, &d->pkt_serial) < 0)
                    return -1;
    ......
           ......
                if (avcodec_send_packet(d->avctx, &pkt) == AVERROR(EAGAIN)) {
          ......
}

avcodec_receive_frame 将成功的解码队列中取出1个frame
packet_queue_get获取队列数据
avcodec_send_packet发送数据到ffmpeg.放到解码队列中

至此,解码完成

3.6 queue_picture();
将3.4中get_video_frame函数所获得的YUV数据放入队列
frame_queue_push(&is->pictq);

4.事件刷新函数
4.1 event_loop()内容

static void event_loop(VideoState *cur_stream)
{
 ......
    for (;;) {
       .......
        refresh_loop_wait_event(cur_stream, &event);
       ......
}

4.2 refresh_loop_wait_event()->video_refresh()->video_display()
其中video_refresh中包含video_display和音视频同步的逻辑
video_display内容如下

static void video_display(VideoState *is)
{
   .......
        video_open(is);

    ......
        video_audio_display(is);
    .......
        video_image_display(is);
   .......
}

//初始化
video_open();
//
video_audio_display();
//显示视频画面
video_image_display();

4.3 video_image_display的内容是从frame队列弹出一个YUV帧,并播放。内容如下

static void video_image_display(VideoState *is)
{
......
    vp = frame_queue_peek_last(&is->pictq);
 ......
            sp = frame_queue_peek(&is->subpq);
        显示图像至SDL
      ......
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值