/*
基于ffmpeg实现的播放器
av_gettime_relative 获取时间,微秒为单位
音视频同步:
假如是以音频为基准,视频同步音频的方式,那么就是音频在每播放一帧的时候,就去将当前的时间同步到时间轴,视频参考时间轴做调整
时间基:
时间基就是最小的时间刻度,时间戳就是在此最小刻度的基础上记录的时间量
SDL_LockMutex 加锁
*/
#include "pch.h"
#include
#include
//增加的配置文件
#include "config.h"
//windows sdk自带的文件
#include
#include
#include
#include
#include
//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);
}