超级详细的ffplay源码注释(一)

/*
基于ffmpeg实现的播放器
av_gettime_relative  获取时间,微秒为单位

音视频同步:
假如是以音频为基准,视频同步音频的方式,那么就是音频在每播放一帧的时候,就去将当前的时间同步到时间轴,视频参考时间轴做调整

时间基:
时间基就是最小的时间刻度,时间戳就是在此最小刻度的基础上记录的时间量

SDL_LockMutex  加锁

*/

#include "pch.h"
#include <iostream>
#include <Windows.h>

//增加的配置文件
#include "config.h"

//windows sdk自带的文件
#include <stdint.h>
#include <inttypes.h>
#include <math.h>
#include <limits.h>
#include <signal.h>


//ffmpeg的头文件
extern "C"
{
#include "libavutil/avstring.h"
#include "libavutil/eval.h"
#include "libavutil/mathematics.h"
#include "libavutil/pixdesc.h"
#include "libavutil/imgutils.h"
#include "libavutil/dict.h"
#include "libavutil/parseutils.h"
#include "libavutil/samplefmt.h"
#include "libavutil/avassert.h"
#include "libavutil/time.h"
#include "libavutil/display.h"
#include "libavformat/avformat.h"
#include "libavdevice/avdevice.h"
#include "libswscale/swscale.h"
#include "libavutil/opt.h"
#include "libavcodec/avfft.h"
#include "libswresample/swresample.h"
#if CONFIG_AVFILTER
# include "libavfilter/avfilter.h"
# include "libavfilter/buffersink.h"
# include "libavfilter/buffersrc.h"
#endif
}

//sdl的头文件
#include "SDL/SDL.h"
#include "SDL/SDL_thread.h"

//需要的库文件
#pragma comment(lib,"../FFMPEG-master/FFMPEG/msvc/lib/x86/libpostproc.lib")
#pragma comment(lib,"../FFMPEG-master/FFMPEG/msvc/lib/x86/libswresample.lib")
#pragma comment(lib,"../FFMPEG-master/FFMPEG/msvc/lib/x86/libavcodec.lib")  
#pragma comment(lib,"../FFMPEG-master/FFMPEG/msvc/lib/x86/libavformat.lib")  
#pragma comment(lib,"../FFMPEG-master/FFMPEG/msvc/lib/x86/libavfilter.lib")  
#pragma comment(lib,"../FFMPEG-master/FFMPEG/msvc/lib/x86/libavutil.lib")  
#pragma comment(lib,"../FFMPEG-master/FFMPEG/msvc/lib/x86/libswscale.lib") 
#pragma comment(lib,"../FFMPEG-master/FFMPEG/msvc/lib/x86/libavdevice.lib")
#pragma comment(lib,"../FFMPEG-master/FFMPEG/msvc/lib/x86/libsdl2.lib")
#pragma comment(lib,"Psapi.lib")

#define _CRT_SECURE_NO_WARNINGS

/* Minimum SDL audio buffer size, in samples. */
#define SDL_AUDIO_MIN_BUFFER_SIZE 512

/* Calculate actual buffer size keeping in mind not cause too frequent audio callbacks */
#define SDL_AUDIO_MAX_CALLBACKS_PER_SEC 30

/* no AV correction is done if too big error */
#define AV_NOSYNC_THRESHOLD 10.0
/* we use about AUDIO_DIFF_AVG_NB A-V differences to make the average */
#define AUDIO_DIFF_AVG_NB   20
/* maximum audio speed change to get correct sync */
#define SAMPLE_CORRECTION_PERCENT_MAX 10

#define FF_QUIT_EVENT    (SDL_USEREVENT + 2)

#define MAX_QUEUE_SIZE (15 * 1024 * 1024)

#define MIN_FRAMES 25

//音视频同步,默认以音频为基准
enum {
    AV_SYNC_AUDIO_MASTER, /* default choice */
    AV_SYNC_VIDEO_MASTER,
    AV_SYNC_EXTERNAL_CLOCK, /* synchronize to an external clock */
};

enum ShowMode {
    SHOW_MODE_NONE = -1,//显示窗口自适应
    SHOW_MODE_VIDEO = 0,//显示视频
    SHOW_MODE_WAVES,//显示音频图形
    SHOW_MODE_RDFT, //自适应滤波器
    SHOW_MODE_NB
};

//程序的名称
const char program_name[] = "MyPlayer";


//一个空的packet, 解码过程中碰到这种Pcket就会调用avcodec_flush_buffers(d->avctx)清空解码器,
//在seek之后,通过往PacketQueue队列种加这种Packet,来通知解码器,代码如下:
//packet_queue_flush(&is->audioq);
//packet_queue_put(&is->audioq, &flush_pkt)
//插入flush_pkt的同时会把serrial加一,可以知道一个新的播放序列开始了
static AVPacket flush_pkt;

static SDL_Window *window;
static SDL_Renderer *renderer;
static SDL_RendererInfo renderer_info = { 0 };
static SDL_AudioDeviceID audio_dev;//打开的音频设备的ID


#if CONFIG_AVFILTER
static const char **vfilters_list = NULL;
static int nb_vfilters = 0;
static char *afilters = NULL;
#endif

static int show_status = 1;//命令行窗口是否显示相关文件信息

/* options specified by the user */
static const char *input_filename;
static AVInputFormat *file_iformat;
static int startup_volume = 100;
static int av_sync_type = AV_SYNC_AUDIO_MASTER;
static int genpts = 0;//生成缺失的pts,即使需要解析未来帧才知道
static int find_stream_info = 0;//是否查找流中的信息
static int seek_by_bytes = -1;//不同视频格式快进方式不同,是否是按字节方式快进,正常都是0
static const char *window_title;
static int64_t start_time = AV_NOPTS_VALUE;// 10000000; 从视频的什么位置开始播放,单位微秒
static int64_t duration = AV_NOPTS_VALUE; // 10000000;播放多长时间,单位微秒
static const char* wanted_stream_spec[AVMEDIA_TYPE_NB] = { 0 };//添加想要的流类型,默认有五种流类型,全部设置为0,则默认只解析音频,视频和字幕,此参数可以去除

static int video_disable = 0;//禁止视频
static int audio_disable = 0;//禁止音频
static int borderless = 0;//窗口是否有边框
static int subtitle_disable = 0;
static int default_width = 640;//默认渲染图像宽度
static int default_height = 480;//默认渲染图像高度
static int screen_width = 320;//强制的窗口宽度
static int screen_height = 240;//强制的窗口高度
ShowMode show_mode = SHOW_MODE_NONE;//默认显示哪种信息,视频,音频
static int lowres = 0;//支持的低分辨率的最大值
static const char *audio_codec_name;//强制要使用的音频解码器的名字,默认不设置的话,使用的是文件流中一样的解码器
static const char *subtitle_codec_name;//强制要使用的字幕解码器的名字,默认不设置的话,使用的是文件流中一样的解码器
static const char *video_codec_name;//强制要使用的视频解码器的名字,默认不设置的话,使用的是文件流中一样的解码器
static int fast = 0;//允许一些非标准的加速编码方法

static int decoder_reorder_pts = -1;//解码后,视频时间戳的获取方式

static int framedrop = -1;//允许丢帧标志,当framedrop为0时不允许丢弃帧,否则都是允许丢弃帧
static int autorotate = 1;
static int infinite_buffer = -1;
static int loop = 1;//循环播放的次数
static int autoexit;

/* current context */
static int is_full_screen = 0;//设置播放窗口全屏
static int64_t audio_callback_time;


AVDictionary *sws_dict;
AVDictionary *swr_opts;
AVDictionary *format_opts, *codec_opts, *resample_opts;

//程序退出
static void sigterm_handler(int sig)
{
    exit(123);
}

//打印版权/配置/版本信息
static void print_all_info()
{
    av_log(NULL, AV_LOG_INFO, "\n");
    av_log(NULL, AV_LOG_INFO, "%sbuilt with %s\n", " ", CC_IDENT);
    av_log(NULL, AV_LOG_INFO, "%sconfiguration: " FFMPEG_CONFIGURATION "\n", " ");

    unsigned int version = avcodec_version();
    av_log(NULL, AV_LOG_INFO,
        "%slib%-11s %2d.%3d.%3d / %2d.%3d.%3d\n", 
        " ", "avcodec", 
        LIBAVCODEC_VERSION_MAJOR,
                   LIBAVCODEC_VERSION_MINOR,
                   LIBAVCODEC_VERSION_MICRO,
                   AV_VERSION_MAJOR(version), AV_VERSION_MINOR(version),
                   AV_VERSION_MICRO(version));       
}

//打印用法
static void show_usage(void)
{
    av_log(NULL, AV_LOG_INFO, "Simple media player\n");
    av_log(NULL, AV_LOG_INFO, "usage: %s input_file\n", program_name);
    av_log(NULL, AV_LOG_INFO, "\n");

    
}

#ifdef _WIN32
#undef main /* SDL中有预定义main,我们要用自己的main,所以取消SDL中的定义 */
#endif

//时钟,有三种时钟,视频时钟,音频时钟,外部时钟
typedef struct Clock {
    double pts;           /* clock base 时间基准*/
    double pts_drift;     /* clock base minus time at which we updated the clock 时间基减去更新时钟的时间*/
    double last_updated;// 上一次更新的时间
    double speed;// 速度
    int serial;           /* clock is based on a packet with this serial */// 时钟基于使用该序列的包 
    int paused; // 停止标志
    int *queue_serial;    //指向对应的PacketQueue中的serial,外部时钟指向自身时钟的serial/* pointer to the current packet queue serial, used for obsolete clock detection */// 指向当前数据包队列序列的指针,用于过时的时钟检测
    int clockType;//增加一个时钟类型,0视频时钟,1音频时钟,2外部时钟
} Clock;

/* Common struct for handling all types of decoded data and allocated render buffers. */
typedef struct Frame {
    AVFrame *frame;
    AVSubtitle sub;
    int serial;//序列号,快进,快退,循环从头播放都会导致序列号加一。即一次连续播放的编号,每开始一次连续的播放都有唯一的序列号
    double pts;           /* presentation timestamp for the frame */
    double duration;      /* estimated duration of the frame */
    int64_t pos;          /* byte position of the frame in the input file */
    int width;
    int height;
    int format;
    AVRational sar;
    int uploaded;
    int flip_v;
} Frame;

typedef struct MyAVPacketList {
    AVPacket pkt;
    struct MyAVPacketList *next;
    int serial;
} MyAVPacketList;

//循环队列,其中存放加入进来的Packet
typedef struct PacketQueue {
    MyAVPacketList *first_pkt, //头节点指针
        *last_pkt;//尾节点指针
    int nb_packets;//总共读取的Packet包数,不包含已经取出的
    int size;//当前队列中的Packet占用的空间大小,包含指向的空间和本身的空间,不包含已经取出的
    int64_t duration;//当前队列中的packet的播放总时间,不包含已经取出的
    int abort_request;//放弃请求
    int serial;//序列号,快进,快退,循环从头播放都会导致序列号加一。即一次连续播放的编号,每开始一次连续的播放都有唯一的序列号
    SDL_mutex *mutex;
    SDL_cond *cond;
} PacketQueue;

#define VIDEO_PICTURE_QUEUE_SIZE 3//视频FrameQueue中最大3个
#define SUBPICTURE_QUEUE_SIZE 16//字幕的FrameQueue中最大放置16个
#define SAMPLE_QUEUE_SIZE 9//音频的FrameQueue中最大放置9个
#define FRAME_QUEUE_SIZE FFMAX(SAMPLE_QUEUE_SIZE, FFMAX(VIDEO_PICTURE_QUEUE_SIZE, SUBPICTURE_QUEUE_SIZE))

/* NOTE: the size must be big enough to compensate the hardware audio buffersize size */
/* TODO: We assume that a decoded and resampled frame fits into this buffer */
#define SAMPLE_ARRAY_SIZE (8 * 65536)


static const struct TextureFormatEntry {
    enum AVPixelFormat format;
    int texture_fmt;
} sdl_texture_format_map[] = {
    { AV_PIX_FMT_RGB8,           SDL_PIXELFORMAT_RGB332 },
    { AV_PIX_FMT_RGB444,         SDL_PIXELFORMAT_RGB444 },
    { AV_PIX_FMT_RGB555,         SDL_PIXELFORMAT_RGB555 },
    { AV_PIX_FMT_BGR555,         SDL_PIXELFORMAT_BGR555 },
    { AV_PIX_FMT_RGB565,         SDL_PIXELFORMAT_RGB565 },
    { AV_PIX_FMT_BGR565,         SDL_PIXELFORMAT_BGR565 },
    { AV_PIX_FMT_RGB24,          SDL_PIXELFORMAT_RGB24 },
    { AV_PIX_FMT_BGR24,          SDL_PIXELFORMAT_BGR24 },
    { AV_PIX_FMT_0RGB32,         SDL_PIXELFORMAT_RGB888 },
    { AV_PIX_FMT_0BGR32,         SDL_PIXELFORMAT_BGR888 },
    { AV_PIX_FMT_NE(RGB0, 0BGR), SDL_PIXELFORMAT_RGBX8888 },
    { AV_PIX_FMT_NE(BGR0, 0RGB), SDL_PIXELFORMAT_BGRX8888 },
    { AV_PIX_FMT_RGB32,          SDL_PIXELFORMAT_ARGB8888 },
    { AV_PIX_FMT_RGB32_1,        SDL_PIXELFORMAT_RGBA8888 },
    { AV_PIX_FMT_BGR32,          SDL_PIXELFORMAT_ABGR8888 },
    { AV_PIX_FMT_BGR32_1,        SDL_PIXELFORMAT_BGRA8888 },
    { AV_PIX_FMT_YUV420P,        SDL_PIXELFORMAT_IYUV },
    { AV_PIX_FMT_YUYV422,        SDL_PIXELFORMAT_YUY2 },
    { AV_PIX_FMT_UYVY422,        SDL_PIXELFORMAT_UYVY },
    { AV_PIX_FMT_NONE,           SDL_PIXELFORMAT_UNKNOWN },
};


typedef struct AudioParams {
    int freq;//采样率
    int channels;//通道数
    int64_t channel_layout;
    enum AVSampleFormat fmt;
    int frame_size;//单个样本占用的大小,比如双通道16位占用4字节
    int bytes_per_sec;//每秒的数据量,采样率*单个样本的大小,比如48000*4=192000
} AudioParams;

typedef struct FrameQueue {
    Frame queue[FRAME_QUEUE_SIZE]; //是存储Frame的数组
    int rindex;//是读帧数据索引, 相当于是队列的队首
    int windex;//是写帧数据索引, 相当于是队列的队尾
    int size;//是存储在这个队列的Frame的数量
    int max_size;//是可以存储Frame的最大数量
    int keep_last;//这个变量的含义,据我分析, 是用来判断队列是否保留正在显示的帧(Frame)
    int rindex_shown;//表示当前是否有帧在显示
    SDL_mutex *mutex;
    SDL_cond *cond;
    PacketQueue *pktq;//指向各自数据包(ES包)的队列
} FrameQueue;

typedef struct Decoder {
    AVPacket pkt;
    PacketQueue *queue;
    AVCodecContext *avctx;
    int pkt_serial;//序列号,快进,快退,循环从头播放都会导致序列号加一。即一次连续播放的编号,每开始一次连续的播放都有唯一的序列号
    int finished;
    int packet_pending;
    SDL_cond *empty_queue_cond;
    int64_t start_pts;
    AVRational start_pts_tb;
    int64_t next_pts;
    AVRational next_pts_tb;
    SDL_Thread *decoder_tid;
} Decoder;

typedef struct VideoState {
    SDL_Thread *read_tid;
    AVInputFormat *iformat;
    int abort_request;//是否要中断文件读取
    int force_refresh;//需要进行强制刷新的标志
    int paused;//当前的暂停状态
    int last_paused;
    int queue_attachments_req;

    int seek_req;//是否要进行seek的请求标志,进行seek之前会设置为1,seek一次后会自动设置为0
    int seek_flags;//seek的方式,按字节或者按时间
    int64_t seek_pos;//要seek到的位置,单位微秒或者字节数(要看seek的方式)
    int64_t seek_rel;//当前位置和要seek到的位置的距离,单位微秒或者字节数(要看seek的方式)

    int read_pause_return;//暂停读取流的返回的错误码
    AVFormatContext *ic;//读取文件流的AVFormatContext上下文
    int realtime;//是否是实时流

    Clock audclk;//音频时钟
    Clock vidclk;//视频时钟
    Clock extclk;//外部时钟

    FrameQueue pictq;//视频的Frame队列,解码后的数据
    FrameQueue subpq;//字幕的Frame队列,解码后的数据
    FrameQueue sampq;//音频的Frame队列,解码后的数据

    PacketQueue audioq;//音频数据包
    PacketQueue subtitleq;//字幕数据包
    PacketQueue videoq;//视频数据包

    Decoder auddec;//音频解码器
    Decoder viddec;//视频解码器
    Decoder subdec;

    int audio_stream;//音频流的流序号
    AVStream *audio_st;//音频流

    int av_sync_type;//音视频的同步方式

    double audio_clock;
    int audio_clock_serial;
    double audio_diff_cum; /* used for AV difference average computation */
    double audio_diff_avg_coef;//初始化大约0.8
    double audio_diff_threshold;//初始化大约0.4
    int audio_diff_avg_count;//用于累加
    
    
    int audio_hw_buf_size;//打开音频设备成功后,返回的buffer大小
    uint8_t *audio_buf;
    uint8_t *audio_buf1;
    unsigned int audio_buf_size; /* 已经解码出的音频数据的数据量in bytes */
    unsigned int audio_buf1_size;
    int audio_buf_index; /* 已经放入播放设备缓存的数据量in bytes */
    int audio_write_buf_size;//解码出的数据尚未放入设备缓存的数据量,也即待写入的数据量
    int audio_volume;//设置音量
    int muted;//静音
    struct AudioParams audio_src;//源音频参数
#if CONFIG_AVFILTER
    struct AudioParams audio_filter_src;
#endif
    struct AudioParams audio_tgt;//目标音频参数
    struct SwrContext *swr_ctx;
    int frame_drops_early;
    int frame_drops_late;

    ShowMode show_mode;
    int16_t sample_array[SAMPLE_ARRAY_SIZE];
    int sample_array_index;
    int last_i_start;
    RDFTContext *rdft;
    int rdft_bits;
    FFTSample *rdft_data;
    int xpos;
    double last_vis_time;
    SDL_Texture *vis_texture;
    SDL_Texture *sub_texture;
    SDL_Texture *vid_texture;

    int subtitle_stream;//字幕流的流序号
    AVStream *subtitle_st;//字幕流
    

    double frame_timer;//记录当前帧实际播放到的时间
    double frame_last_returned_time;
    double frame_last_filter_delay;
    int video_stream;//视频流的流序号
    AVStream *video_st;//视频流

    double max_frame_duration;      // 最大帧显示时间 默认算出来是3600 maximum duration of a frame - above this, we consider the jump a timestamp discontinuity
    struct SwsContext *img_convert_ctx;
    struct SwsContext *sub_convert_ctx;
    int eof;//是否到文件结尾

    char *filename;
    int width, height, xleft, ytop;
    int step;//步进的标志,进行步进操作,按键盘S就是步进操作

#if CONFIG_AVFILTER
    int vfilter_idx;
    AVFilterContext *in_video_filter;   // the first filter in the video chain
    AVFilterContext *out_video_filter;  // the last filter in the video chain
    AVFilterContext *in_audio_filter;   // the first filter in the audio chain
    AVFilterContext *out_audio_filter;  // the last filter in the audio chain
    AVFilterGraph *agraph;              // audio filter graph
#endif

    int last_video_stream, last_audio_stream, last_subtitle_stream;

    SDL_cond *continue_read_thread;//读线程的条件变量
} VideoState;

//
//读取数据
static void packet_queue_abort(PacketQueue *q)
{
    SDL_LockMutex(q->mutex);

    q->abort_request = 1;

    SDL_CondSignal(q->cond);

    SDL_UnlockMutex(q->mutex);
}
static void frame_queue_signal(FrameQueue *f)
{
    SDL_LockMutex(f->mutex);
    SDL_CondSignal(f->cond);
    SDL_UnlockMutex(f->mutex);
}

//清空队列q中的所有数据
static void packet_queue_flush(PacketQueue *q)
{
    MyAVPacketList *pkt, *pkt1;

    SDL_LockMutex(q->mutex);
    for (pkt = q->first_pkt; pkt; pkt = pkt1) {
        pkt1 = pkt->next;

        //释放节点占用的所有资源
        av_packet_unref(&pkt->pkt);
        av_freep(&pkt);
    }
    q->last_pkt = NULL;
    q->first_pkt = NULL;
    q->nb_packets = 0;
    q->size = 0;
    q->duration = 0;
    SDL_UnlockMutex(q->mutex);
}

static void decoder_abort(Decoder *d, FrameQueue *fq)
{
    packet_queue_abort(d->queue);
    frame_queue_signal(fq);
    SDL_WaitThread(d->decoder_tid, NULL);
    d->decoder_tid = NULL;
    packet_queue_flush(d->queue);
}

static void decoder_destroy(Decoder *d) {
    av_packet_unref(&d->pkt);
    avcodec_free_context(&d->avctx);
}

static void stream_component_close(VideoState *is, int stream_index)
{
    AVFormatContext *ic = is->ic;
    AVCodecParameters *codecpar;

    if (stream_index < 0 || stream_index >= ic->nb_streams)
        return;
    codecpar = ic->streams[stream_index]->codecpar;

    switch (codecpar->codec_type) {
    case AVMEDIA_TYPE_AUDIO:
        decoder_abort(&is->auddec, &is->sampq);
        SDL_CloseAudioDevice(audio_dev);
        decoder_destroy(&is->auddec);
        swr_free(&is->swr_ctx);
        av_freep(&is->audio_buf1);
        is->audio_buf1_size = 0;
        is->audio_buf = NULL;

        if (is->rdft) {
            av_rdft_end(is->rdft);
            av_freep(&is->rdft_data);
            is->rdft = NULL;
            is->rdft_bits = 0;
        }
        break;
    case AVMEDIA_TYPE_VIDEO:
        decoder_abort(&is->viddec, &is->pictq);
        decoder_destroy(&is->viddec);
        break;
    case AVMEDIA_TYPE_SUBTITLE:
        decoder_abort(&is->subdec, &is->subpq);
        decoder_destroy(&is->subdec);
        break;
    default:
        break;
    }

    ic->streams[stream_index]->discard = AVDISCARD_ALL;
    switch (codecpar->codec_type) {
    case AVMEDIA_TYPE_AUDIO:
        is->audio_st = NULL;
        is->audio_stream = -1;
        break;
    case AVMEDIA_TYPE_VIDEO:
        is->video_st = NULL;
        is->video_stream = -1;
        break;
    case AVMEDIA_TYPE_SUBTITLE:
        is->subtitle_st = NULL;
        is->subtitle_stream = -1;
        break;
    default:
        break;
    }
}

static void packet_queue_destroy(PacketQueue *q)
{
    packet_queue_flush(q);
    SDL_DestroyMutex(q->mutex);
    SDL_DestroyCond(q->cond);
}

//取消引用帧引用的所有缓冲区并重置帧字段,释放给定字幕结构中的所有已分配数据
static void frame_queue_unref_item(Frame *vp)
{
    av_frame_unref(vp->frame);
    avsubtitle_free(&vp->sub);
}

#if CONFIG_AVFILTER
static int opt_add_vfilter(void *optctx, const char *opt, const char *arg)
{
    //GROW_ARRAY(vfilters_list, nb_vfilters);
    //vfilters_list[nb_vfilters - 1] = arg;
    return 0;
}
#endif


//释放Frame,释放互斥锁和互斥量
static void frame_queue_destory(FrameQueue *f)
{
    int i;
    for (i = 0; i < f->max_size; i++) {
        Frame *vp = &f->queue[i];
        frame_queue_unref_item(vp);
        av_frame_free(&vp->frame);
    }
    SDL_DestroyMutex(f->mutex);
    SDL_DestroyCond(f->cond);
}
 

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值