博客原文地址
https://blog.csdn.net/leixiaohua1020/article/details/38868499
//因旧的ffmpeg部分函数已弃用,修改后的版本
//运行环境vs2015 +sdl 2+ffmpeg20200426
//代码的注释某位网友的,博主很懒就不找了
/**
* 最简单的基于FFmpeg的视频播放器2(SDL升级版)
* Simplest FFmpeg Player 2(SDL Update)
*
* 雷霄骅 Lei Xiaohua
* leixiaohua1020@126.com
* 中国传媒大学/数字电视技术
* Communication University of China / Digital TV Technology
* http://blog.csdn.net/leixiaohua1020
*
* 第2版使用SDL2.0取代了第一版中的SDL1.2
* Version 2 use SDL 2.0 instead of SDL 1.2 in version 1.
*
* 本程序实现了视频文件的解码和显示(支持HEVC,H.264,MPEG2等)。
* 是最简单的FFmpeg视频解码方面的教程。
* 通过学习本例子可以了解FFmpeg的解码流程。
* 本版本中使用SDL消息机制刷新视频画面。
* This software is a simplest video player based on FFmpeg.
* Suitable for beginner of FFmpeg.
*
* 备注:
* 标准版在播放视频的时候,画面显示使用延时40ms的方式。这么做有两个后果:
* (1)SDL弹出的窗口无法移动,一直显示是忙碌状态
* (2)画面显示并不是严格的40ms一帧,因为还没有考虑解码的时间。
* SU(SDL Update)版在视频解码的过程中,不再使用延时40ms的方式,而是创建了
* 一个线程,每隔40ms发送一个自定义的消息,告知主函数进行解码显示。这样做之后:
* (1)SDL弹出的窗口可以移动了
* (2)画面显示是严格的40ms一帧
* Remark:
* Standard Version use's SDL_Delay() to control video's frame rate, it has 2
* disadvantages:
* (1)SDL's Screen can't be moved and always "Busy".
* (2)Frame rate can't be accurate because it doesn't consider the time consumed
* by avcodec_decode_video2()
* SU(SDL Update)Version solved 2 problems above. It create a thread to send SDL
* Event every 40ms to tell the main loop to decode and show video frames.
*/
#include <stdio.h>
#define __STDC_CONSTANT_MACROS
#ifdef _WIN32
//Windows
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavutil/imgutils.h"
#include "SDL.h"
};
#else
//Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#include <SDL2/SDL.h>
#ifdef __cplusplus
};
#endif
#endif
//Refresh Event
#define SFM_REFRESH_EVENT (SDL_USEREVENT + 1)
#define SFM_BREAK_EVENT (SDL_USEREVENT + 2)
int thread_exit = 0;
int thread_pause = 0;
/**
* 每隔40ms发送一次消息,eventType为SFM_REFRESH_EVENT
*
* thread_pause:暂停
* thread_exit:退出
*/
int sfp_refresh_thread(void *opaque) {
thread_exit = 0;
thread_pause = 0;
while (!thread_exit) {
if (!thread_pause) {
SDL_Event event;
event.type = SFM_REFRESH_EVENT;
SDL_PushEvent(&event);
}
SDL_Delay(40);
}
thread_exit = 0;
thread_pause = 0;
//Break
SDL_Event event;
event.type = SFM_BREAK_EVENT;
SDL_PushEvent(&event);
return 0;
}
int main(int argc, char* argv[])
{
// 封装格式上下文的结构体,也是统领全局的结构体,保存了视频文件封装格式的相关信息
AVFormatContext *pFormatCtx;
// 视频流在文件中的位置
int i, videoindex;
// 编码器上下文结构体,保存了视频(音频)编解码相关信息
AVCodecContext *pCodecCtx;
// 每种视频(音频)编解码器(例如H.264解码器)对应一个该结构体
AVCodec *pCodec;
// 存储一帧解码后像素(采样)数据
AVFrame *pFrame, *pFrameYUV;
// 存储图像数据
unsigned char *out_buffer;
// 存储一帧压缩编码数据
AVPacket *packet;
// 是否获取到数据的返回值
int ret, got_picture;
//------------SDL----------------
int screen_w, screen_h;
// 窗口
SDL_Window *screen;
// 渲染器
SDL_Renderer* sdlRenderer;
// 纹理
SDL_Texture* sdlTexture;
// 一个简单的矩形
SDL_Rect sdlRect;
SDL_Rect srcsdlRect;
SDL_Rect dstsdlRect;
// 线程
SDL_Thread *video_tid;
// 事件
SDL_Event event;
struct SwsContext *img_convert_ctx;
//char filepath[]="bigbuckbunny_480x272.h265";
char filepath[] = "d:/test.264";
// 注册复用器,编码器等(参考FFmpeg解码流程图)
//av_register_all();
// 进行网络组件的全局初始化(详细代码请参考第三篇文章中代码对应位置的描述)
avformat_network_init();
/**
* Allocate an AVFormatContext.
* avformat_free_context() can be used to free the context and everything
* allocated by the framework within it.
* 分配AVFormatContext。
* avformat_free_context()可用于释放上下文以及框架在其中分配的所有内容。
*/
pFormatCtx = avformat_alloc_context();
// 打开多媒体数据并且获得一些相关的信息(参考FFmpeg解码流程图)
if (avformat_open_input(&pFormatCtx, filepath, NULL, NULL) != 0) {
printf("Couldn't open input stream.\n");
return -1;
}
// 读取一部分视音频数据并且获得一些相关的信息(参考FFmpeg解码流程图)
if (avformat_find_stream_info(pFormatCtx, NULL)<0) {
printf("Couldn't find stream information.\n");
return -1;
}
// 每个视频文件中有多个流(视频流、音频流、字幕流等,而且可有多个),循环遍历找到视频流
// 判断方式:AVFormatContext->AVStream->AVCodecContext->codec_type是否为AVMEDIA_TYPE_VIDEO
videoindex = -1;
for (i = 0; i<pFormatCtx->nb_streams; i++)
if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
videoindex = i;
break;
}
// 如果没有视频流,返回
if (videoindex == -1) {
printf("Didn't find a video stream.\n");
return -1;
}
// 保存视频流中的AVCodecContext
//pCodecCtx = pFormatCtx->streams[videoindex]->codecpar;
pCodecCtx = avcodec_alloc_context3(NULL);
avcodec_parameters_to_context(pCodecCtx, pFormatCtx->streams[videoindex]->codecpar);
// 用于查找FFmpeg的解码器(参考FFmpeg解码流程图)
pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
if (pCodec == NULL) {
printf("Codec not found.\n");
return -1;
}
// 初始化一个视音频编解码器的AVCodecContext(参考FFmpeg解码流程图)
if (avcodec_open2(pCodecCtx, pCodec, NULL)<0) {
printf("Could not open codec.\n");
return -1;
}
// 创建AVFrame,用来存放解码后的一帧的数据
pFrame = av_frame_alloc();
pFrameYUV = av_frame_alloc();
// av_image_get_buffer_size:返回使用给定参数存储图像所需的数据量的字节大小
out_buffer = (unsigned char *)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1));
// 根据指定的图像参数和提供的数组设置数据指针和线条(data pointers and linesizes)
av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, out_buffer,
AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1);
//Output Info-----------------------------
printf("---------------- File Information ---------------\n");
av_dump_format(pFormatCtx, 0, filepath, 0);
printf("-------------------------------------------------\n");
// sws_getContext():初始化一个SwsContext
img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
// 初始化SDL系统
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
printf("Could not initialize SDL - %s\n", SDL_GetError());
return -1;
}
//SDL 2.0 Support for multiple windows
// 保存视频的实际宽高,用于下面代码中复用
screen_w = pCodecCtx->width;
screen_h = pCodecCtx->height;
// 创建窗口SDL_CreateWindow
//screen = SDL_CreateWindow("Simplest ffmpeg player's Window", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
// screen_w, screen_h, SDL_WINDOW_OPENGL);
screen = SDL_CreateWindow("Simplest ffmpeg player's Window", 10, 10,
screen_w/4, screen_h/4, SDL_WINDOW_OPENGL);
if (!screen) {
printf("SDL: could not create window - exiting:%s\n", SDL_GetError());
return -1;
}
// 创建渲染器SDL_Renderer
sdlRenderer = SDL_CreateRenderer(screen, -1, 0);
//IYUV: Y + U + V (3 planes)
//YV12: Y + V + U (3 planes)
// 创建纹理SDL_Texture
sdlTexture = SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, pCodecCtx->width, pCodecCtx->height);
//源rect
sdlRect.x = 0;
sdlRect.y = 0;
sdlRect.w = screen_w;
sdlRect.h = screen_h;
//sdlRect.w = screen_w/8;
//sdlRect.h = screen_h/8;
//显示rect
dstsdlRect.x = 0;
dstsdlRect.y = 0;
dstsdlRect.w = screen_w / 8;
dstsdlRect.h = screen_h / 8;
// 创建一个AVPacket,用来存放下面循环获取到的未解码帧
packet = (AVPacket *)av_malloc(sizeof(AVPacket));
// 创建线程sfp_refresh_thread,进行视频数据的读取
video_tid = SDL_CreateThread(sfp_refresh_thread, NULL, NULL);
//------------SDL End------------
//Event Loop
for (;;) {
//Wait
SDL_WaitEvent(&event);
if (event.type == SFM_REFRESH_EVENT) {
// sfp_refresh_thread中定义的事件,视频播放的事件
while (1) {
// 读取到的数据为空,则停止播放
if (av_read_frame(pFormatCtx, packet)<0)
thread_exit = 1;
// 循环找到视频流
if (packet->stream_index == videoindex)
break;
}
// 解码一帧视频数据:输入一个压缩编码的结构体AVPacket,输出一个解码后的结构体AVFrame
//ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);
ret = avcodec_send_packet(pCodecCtx, packet);
got_picture = avcodec_receive_frame(pCodecCtx, pFrame);
if (ret < 0) {
printf("Decode Error.\n");
return -1;
}
if (got_picture == 0) {
sws_scale(img_convert_ctx, (const unsigned char* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);
//SDL---------------------------
// 设置纹理数据
SDL_UpdateTexture(sdlTexture, NULL, pFrameYUV->data[0], pFrameYUV->linesize[0]);
// 清理渲染器
SDL_RenderClear(sdlRenderer);
// 将纹理数据copy到渲染器
//将sdlRect区域的图像显示到dstsdlRect区域
//SDL_RenderCopy( sdlRenderer, sdlTexture, &sdlRect, &sdlRect );
SDL_RenderCopy(sdlRenderer, sdlTexture, &sdlRect, &dstsdlRect);
//SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, NULL);
// 显示
SDL_RenderPresent(sdlRenderer);
//SDL End-----------------------
}
av_packet_unref(packet);
}
else if (event.type == SDL_KEYDOWN) {
//Pause
if (event.key.keysym.sym == SDLK_SPACE)
// 空格键
thread_pause = !thread_pause;
}
else if (event.type == SDL_QUIT) {
// 系统event,点击关闭时返回该event
// thread_exit置为1,退出监听事件的循环
thread_exit = 1;
}
else if (event.type == SFM_BREAK_EVENT) {
break;
}
}
sws_freeContext(img_convert_ctx);
SDL_Quit();
//--------------
av_frame_free(&pFrameYUV);
av_frame_free(&pFrame);
avcodec_close(pCodecCtx);
avformat_close_input(&pFormatCtx);
return 0;
}