zoukankan      html  css  js  c++  java
  • FFMPEG

    FFmpeg是一个开源,免费,跨平台的视频和音频流方案,它提供了一套完整的录制、转换以及流化音视频的解决方案。而ffplay是有ffmpeg官方提供的一个基于ffmpeg的简单播放器。学习ffplay对于播放器流程、ffmpeg的调用等等是一个非常好的例子。

    1.例子

    这里的说明使用如下的例子:

    ./ffplay avm.mp4

    2. read_thread()

    线程read_thread负责demux,它的流程如下图:

    • avformat_alloc_context分配AVFormatContext。这是demux的上下文;
    • avformat_open_input()解析文件,确定文件的封装格式(即mux类型);
    • aformat_find_stream_info()继续解析AVFormatContext中包含的stream,根据stream类型确定其decoder,并创建AVCodecContext,这是decode的上下文;
    • 如果指定了播放位置,avformat_seek_file()将播放位置移动到指定位置;
    • av_find_best_stream()查找指定stream类型的最佳质量的stream。
    • stream_component_open()创建新的线程video_thread。Video_thread负责decode。
    • 最后,read_thread在循环中,调用av_read_frame()读packet,并调用packet_queue_put(),放入queue中,供video_thread读取。

    2.1 avformat_alloc_context()

    avformat_alloc_context调用avformat_get_context_defaults()给AVFormatContext设置缺省参数,其中包括AVFormtContext::io_open()和AVFormatContext::io_close()。

    2.2 avformat_open_input() - input_input() - io_open_default()

    avformat_open_input()调用av_probe_input_format2()确定文件的封装格式,这点后面再提。这里先看io_open_default()如何打开要播放的链接。有两个层次的Context:AVIOContext和URLContext。ffio_xxx()负责IO层,ffurl_xxx()负责URL层。

    • url_find_protocol()首先提取播放链接的前缀。如http://.../a.mp4,前缀就是http。这里只有文件名avm.mp4,没有前缀,则默认为“file”,所以avm.mp4与file:avm.mp4是等同的。
    • ffurl_get_protocols得到已注册的URL protocol列表。url_find_protocol()根据前缀在这个列表中查找。前缀”file”对应的是ff_file_protocol。
    • ffurl_alloc()调用url_alloc_for_protocol()分配相应的URLContext。URLContext保存真正的文件句柄fd。
    • ffurl_connect()调用file_open()打开文件,调用file_seek()将开始位置调整到0。
    • ffio_fdopen()调用avio_alloc_context()分配IOContext,然后初始化它。

    2.3 avformat_open_input() - input_input() - av_probe_input_buffer2()

    这里的重要函数是av_probe_input_format3()。它根据文件名及文件内容确定文件封装格式。它有两种工作模式,根据参数is_opened值决定,is_opened表示文件是否已经打开。

    AVInputFormat *av_probe_input_format3 (AVProbeData *pd, int is_opened,  int* score_ret);
    

    如果is_opened为false,则av_probe_input_format3()只根据文件名查找AVInputFormat,否则,av_probe_input_format3()调用AVInputFormat::read_probe(),根据文件内容的头部判断。对于mp4,其read_probe()是mov_probe()。
    av_probe_input_format3()的第一个参数pd保存了文件名和文件头部的内容。 avInputFormat可能有多种选择,它们的优先级不同,用score表示。av_probe_input_format3()返回优先级最高的,优先级保存在第3个参数score_ret中。

    init_input()第一次调用av_probe_input_format2()。这时文件没打开,根据文件名字找查找封装格式,没找到。av_probe_input_buffer2()先调用avio_read()读文件内容头部分,再第二次调用av_probe_input_format2(),这次mov_probe()返回的AVInputFormat是ff_mov_demuxer。

    avio_read()调用IO层的io_read_packet(),和URL层的file_read()读取指定大小的文件内容。

    如下是以上过程涉及的类:

    2.4 avformat_open_input() - AVInputFormat::read_header()

    avformat_open_input()先调用avio_skip()跳过指定字节,这里是0字节。接着调用AVInputFormat::reader_header()分析文件头部。这里调用的是mov_read_header(),它会创建MOV层的上下文MovContext。

    2.5 avformat_open_input() - avformat_find_stream_info()

    avformat_find_stream_info()调用av_parser_init(),为每个Stream找到AVCodecParserContext,这是用来从连续的stream数据中分割frame的。
    avcodec_parameters_to_context()将Codec上下文从内部使用的结构AVCodecParameters,复制到作为对外接口的AVCodecContext中。
    find_probe_decoder()根据codec_id查找解码器AVCodec,这里调用avcodec_find_decoder_by_name(),根据名字在已注册的Codec列表中找到ff_h264_decoder。
    avcodec_open2()调用AVCodec::init()初始Codec的上下文。这里是h264_decode_init()创建H264Context并初始化。

    以上过程涉及到的类如下:

    2.6 avformat_open_input() - avformat_seek_file()

    avformat_seek_file()调整播放位置。av_rescale()将对外使用的时间单位转换成内部使用的。位置调整之后,之前的frame就不需要了。ff_read_frame_flush()用于清除之前的frame。最后调用mov_read_seek()调整位置。

    2.7 avformat_open_input() - av_find_best_stream()

    可能包括多个同类型的stream,av_find_best_stream()为每个类型选出最好的一个stream。

    2.8 avformat_open_input() - stream_component_open()

    stream_component_open()首先创建avcodec_alloc_context3()创建AVCodecContext并初始化,调用avcodec_parameters_to_context()复制内部参数,调用avcodec_find_decoder()找到decoder,调用avcodec_open2()初始化AVCodecContext。

    最后是应用层面的数据结构VideoState,用decoder_init()初始化。在decoder_start()中,用packet_queue_start()启动packet queue,用SDL_CreateThread()启动新线程video_thread。

    2.9 avformat_open_input() - av_read_frame()

    read_thread在一个循环中调用av_read_frame()读packet,调用packet_queue_put()写入packet queue。
    av_read_frame调用read_frame_internal(),最终调用mov_read_packet()读取packet。它可能将packet返回,也可能暂时保存在AVFormatContext::AVFormatInternal::packet_buffer中。在后一种情况下,下一次调用av_read_frame()会返回暂存的packet。

    涉及到的类如下图:

    3.video_thread()

    线程video_thread负责decode,它在循环中调用get_video_frame()从packet queue中读packet,decode为frame,然后调用queue_picture()将frame推入frame queue。流程如下图:

    3.1 get_video_frame()

    decoder_decoder_frame()负责decode frame。packet_queue_get()从packet queue中取出packet。
    在avcodec_send_packet()中,实际负责的是decode_receive_frame_internal(),它最终调用h264_decode_frame()。decode后的frame可能返回,也可能暂存在AVCodecInternal::buffer_frame中。在后一种情况下,下一次调用decode_receive_frame_internal()会直接返回暂存的frame。

    3.2 queue_picture()

    queue_picture()将decode得到的frame写入frame queue。frame_queue_peek_writable()得到可写的位置,frame_queue_push()将写位置推前一步。

    涉及的类如下图:

    4. main thread / display thread

    main thread首先做初始化工作,然后调用event_loop()进入display阶段。

    初始化工作包括:

    • av_register_all()注册ffmpeg的各种库,如demux,decode等。
    • parse_options()解析命令行
    • SDL_Init()初始化SDL,SDL_CreateWindow()创建SDL窗口,SDL_CreateRenderer()创建SDL渲染对象。SDL_GetRenderInfo()得到渲染对象的信息。
    • stream_open()调用frame_queue_init()初始化packet queue,调用frame_queue_init()初始化frame queue,调用init_clock()初始化同步时钟。最后调用SDL_CreateThread()创建和启动新的线程read_thread。

    4.1 event_loop()

    在refresh_loop_wait_event()中,再循环中最多0.01秒调用一次video_refresh()。Video_refresh()读取frame并刷新显示。
    frame_queue_nb_remaining()得到frame_queue中的frame数目;
    frame_queue_peek_last()和frame_queue_peek()分别得到frame queue中的最后两个frame。Frame_queue_next()将读位置推进一步。
    video_display()显示frame。
    调用SDL_PumpEvents()和SDL_PeepEvents()得到SDL event。这是SDL的要求,不然SDL会以为用户无动作,所以将SDL窗口变暗。SDL Event返回给refresh_loop_wait_event(),在这里event,如用户输入,会得到处理,如调整播放位置,改变音量等等。

    4.2 event_loop() - video_display()

    video_display()几乎只与SDL有关。

      • video_open()设置SDL窗口。
      • SDL_SetRenderDrawColor()和SDL_SetRenderClear()清理渲染器的背景。
      • video_image_display()调用frame_queue_peek_last()从frame queue中得到最后一个frame,调用calculate_display_rect()计算显示范围,调用upload_texture()创建纹理并将frame中的YUV分量,渲染到纹理中,调用SDL_RenderCopyEx()将纹理复制到渲染器。
      • SDL_RenderPresent()显示渲染器中的内容。
  • 相关阅读:
    html JS 开发备忘
    C++学习备忘(一)
    博客开通备忘
    自己制作的代码生成工具AutoCoder
    C# 小技巧
    突破list存为模板为10M限制
    开博
    OpenEuler中C语言中的函数调用测试
    socket测试3
    电子公文传输系统验收4开发基础
  • 原文地址:https://www.cnblogs.com/schips/p/11525418.html
Copyright © 2011-2022 走看看