/*
基於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);
}