继续看雷霄骅的 课程资料 - 基于FFmpeg+SDL的视频播放器的制作
参看:WIKI -- Simple DirectMedia Layer
参看:最简单的视音频播放示例9:SDL2播放PCM
参看:SDL介绍
SDL (Simple DirectMedia Layer)是一套开源代码的跨平台多媒体开发库,使用C语言写成。SDL提供了数种控制图像、声音、输出入的函数,让开发者只要用相同或是相似的代码就可以开发出跨多个平台(Linux、Windows、Mac OS等)的应用软件。目前 SDL 多用于开发游戏、模拟器、媒体播放器等多媒体应用领域。用下面这张图可以很明确地说明 SDL 的用途。
SDL 分为几个子系统:
Basics(基本)
初始化和关闭,配置变量,错误处理,日志处理
vedio(视频)
显示和窗口管理,表面功能,渲染加速等
Input Events (输入事件)
事件处理,支持键盘,鼠标,操纵杆和游戏控制器
Force Feedback (力反馈)
SDL_haptic.h支持“强制反馈”
Audio(音频)
SDL_audio.h实现音频设备管理,播放和录制
Threads(主题)
多线程:线程管理,线程同步原语,原子操作
Timers (计时器)
定时器支持
File Abstraction (文件抽象)
文件系统路径,文件I / O抽象
Shared Object Support (共享对象支持)
共享对象加载和功能查找
Platform and CPU Information (平台和CPU信息)
平台检测,CPU特征检测,字节顺序和字节交换,位操作
Power Mangement (能源管理)
电源管理状态
Additional (额外)
平台特定的功能
除了这个基本的低级支持之外,还有几个独立的官方图书馆提供了更多的功能。 这些包括“标准库”,并在官方网站上提供,并包含在官方文件中:
SDL_image - 支持多种图像格式[19]
SDL_mixer - 复合音频功能,主要用于混音[20]
SDL_net - 网络支持[21]
SDL_ttf - TrueType字体渲染支持[22]
SDL_rtf - 简单的富文本格式渲染[23]
这是第三遍了啊。。
// SDL.cpp : 定义控制台应用程序的入口点。
//
#include
#include "stdafx.h"
extern "C"
{
#include "SDL2/SDL.h"
}
int main()
{
if (SDL_Init(SDL_INIT_VIDEO)) {
printf("Could not initialize SDL - %s\n", SDL_GetError());
}
else {
printf("Success init SDL");
}
return 0;
}
1>------ 已启动生成: 项目: SDL, 配置: Debug Win32 ------
1>stdafx.cpp
1>SDL.cpp
1>MSVCRTD.lib(initializers.obj) : warning LNK4098: 默认库“msvcrt.lib”与其他库的使用冲突;请使用 /NODEFAULTLIB:library
1>SDL2main.lib(SDL_windows_main.obj) : error LNK2019: 无法解析的外部符号 __imp__fprintf,该符号在函数 _ShowError 中被引用
1>SDL2main.lib(SDL_windows_main.obj) : error LNK2019: 无法解析的外部符号 __imp____iob_func,该符号在函数 _ShowError 中被引用
1>D:\zslfchenjuke\work2017\SDL\SDL\Debug\SDL.exe : fatal error LNK1120: 2 个无法解析的外部命令
1>已完成生成项目“SDL.vcxproj”的操作 - 失败。
========== 生成: 成功 0 个,失败 1 个,最新 0 个,跳过 0 个 ==========
问题分析:因为我用的是 VS2017,而提供的lib实在 VS 2013上执行的。
SDL2源代码分析1:初始化(SDL_Init())
SDL2源代码分析2:窗口(SDL_Window)
SDL2源代码分析3:渲染器(SDL_Renderer)
SDL2源代码分析4:纹理(SDL_Texture)
SDL2源代码分析5:更新纹理(SDL_UpdateTexture())
SDL2源代码分析6:复制到渲染器(SDL_RenderCopy())
SDL2源代码分析7:显示(SDL_RenderPresent())
SDL2源代码分析8:视频显示总结
#include
#include "stdafx.h"
extern "C"
{
#include "SDL2/SDL.h"
}
const int bpp = 12;
//screan 为屏幕长宽,pixel为视频长宽
int screen_w = 640, screen_h = 272;
const int pixel_w = 640, pixel_h = 272;
unsigned char buffer[pixel_w*pixel_h*bpp / 8];
//Refresh Event
#define REFRESH_EVENT (SDL_USEREVENT + 1)
//Break
#define BREAK_EVENT (SDL_USEREVENT + 2)
int thread_exit = 0;
int refresh_video(void *opaque) {
thread_exit = 0;
while (thread_exit == 0) {
SDL_Event event;
event.type = REFRESH_EVENT;
SDL_PushEvent(&event); //发送一个事件
SDL_Delay(40); //工具函数,可用于调节播放速度
}
thread_exit = 0;
//Break
SDL_Event event;
event.type = BREAK_EVENT;
SDL_PushEvent(&event);
return 0;
}
int main(int argc, char* argv[])
{
//初始化 SDL 系统
if (SDL_Init(SDL_INIT_VIDEO)) {
printf("Could not initialize SDL - %s\n", SDL_GetError());
return -1;
}
SDL_Window *screen;
//SDL 2.0 Support for multiple windows
//创建窗口 SDL_Window
screen = SDL_CreateWindow("Simplest Video Play SDL2", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
screen_w, screen_h, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
if (!screen) {
printf("SDL: could not create window - exiting:%s\n", SDL_GetError());
return -1;
}
//创建渲染器SDL_Renderer
SDL_Renderer* sdlRenderer = SDL_CreateRenderer(screen, -1, 0);
Uint32 pixformat = 0;
//IYUV: Y + U + V (3 planes)
//YV12: Y + V + U (3 planes)
pixformat = SDL_PIXELFORMAT_IYUV;
//创建纹理 SDL_Texture
SDL_Texture* sdlTexture = SDL_CreateTexture(sdlRenderer, pixformat, SDL_TEXTUREACCESS_STREAMING, pixel_w, pixel_h);
FILE *fp = NULL;
//打开yuv文件
fp = fopen("output.yuv", "rb+");
if (fp == NULL) {
printf("cannot open this file\n");
return -1;
}
//SDL_Rect srcRect[4];
//SDL_Rect sdlRect[4];
SDL_Rect sdlRect;
//创建一个线程
SDL_Thread *refresh_thread = SDL_CreateThread(refresh_video, NULL, NULL);
SDL_Event event;
while (1) {
//等待一个事件
SDL_WaitEvent(&event);
if (event.type == REFRESH_EVENT) {
if (fread(buffer, 1, pixel_w*pixel_h*bpp / 8, fp) != pixel_w*pixel_h*bpp / 8) {
// Loop
fseek(fp, 0, SEEK_SET);
fread(buffer, 1, pixel_w*pixel_h*bpp / 8, fp);
}
//设置纹理的数据
SDL_UpdateTexture(sdlTexture, NULL, buffer, pixel_w);
#if 0 //分屏播放
srcRect[0].x =0;
srcRect[0].y = 0;
srcRect[0].w = 320;
srcRect[0].h = 176;
srcRect[1].x =320;
srcRect[1].y = 0;
srcRect[1].w = 320;
srcRect[1].h = 176;
srcRect[2].x =0;
srcRect[2].y = 176;
srcRect[2].w = 320;
srcRect[2].h = 176;
srcRect[3].x =320;
srcRect[3].y = 176;
srcRect[3].w = 320;
srcRect[3].h = 176;
//FIX: If window is resize
sdlRect[0].x = 0;
sdlRect[0].y = 0;
sdlRect[0].w = 320;
sdlRect[0].h = 176;
sdlRect[1].x = 330;
sdlRect[1].y = 0;
sdlRect[1].w = 320;
sdlRect[1].h = 176;
sdlRect[2].x = 0;
sdlRect[2].y = 186;
sdlRect[2].w = 320;
sdlRect[2].h = 176;
sdlRect[3].x = 330;
sdlRect[3].y = 186;
sdlRect[3].w = 320;
sdlRect[3].h = 176;
//清空纹理
SDL_RenderClear( sdlRenderer );
//将纹理的数据拷贝给渲染器
SDL_RenderCopy( sdlRenderer, sdlTexture,/*NULL*/ &srcRect[0], &sdlRect[0]);
SDL_RenderCopy(sdlRenderer, sdlTexture,/*NULL*/ &srcRect[1], &sdlRect[1]);
SDL_RenderCopy(sdlRenderer, sdlTexture,/*NULL*/ &srcRect[2], &sdlRect[2]);
SDL_RenderCopy(sdlRenderer, sdlTexture,/*NULL*/ &srcRect[3], &sdlRect[3]);
#endif
sdlRect.x = 0;
sdlRect.y = 0;
sdlRect.w = screen_w;
sdlRect.h = screen_h;
SDL_RenderClear(sdlRenderer);
SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, &sdlRect);
//显示
SDL_RenderPresent(sdlRenderer);
}
else if (event.type == SDL_WINDOWEVENT) {
//If Resize
SDL_GetWindowSize(screen, &screen_w, &screen_h);
}
else if (event.type == SDL_QUIT) {
thread_exit = 1;
}
else if (event.type == BREAK_EVENT) {
break;
}
}
//退出 SDL 系统
SDL_Quit();
return 0;
}
/**
* 最简单的基于FFmpeg的视频播放器2(SDL升级版)
* Simplest FFmpeg Player 2(SDL Update)
*
* 雷霄骅 Lei Xiaohua
* [email protected]
* 中国传媒大学/数字电视技术
* 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
#include "stdafx.h"
#define __STDC_CONSTANT_MACROS
#ifdef _WIN32
//Windows
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "SDL2/SDL.h"
};
#else
//Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include
#include
#include
#include
#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;
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;
AVCodec *pCodec;
AVFrame *pFrame, *pFrameYUV;
uint8_t *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_Thread *video_tid;
SDL_Event event;
struct SwsContext *img_convert_ctx;
//输入文件路径
char filepath[] = "Tai.mp4";
av_register_all(); //注册所有组件
avformat_network_init(); //初始化网络
pFormatCtx = avformat_alloc_context(); //初始化一个 AVFormatContext
if (avformat_open_input(&pFormatCtx, filepath, NULL, NULL) != 0) { //打开输入的视频文件
printf("Couldn't open input stream.\n");
return -1;
}
if (avformat_find_stream_info(pFormatCtx, NULL)<0) { //获取视频文件信息
printf("Couldn't find stream information.\n");
return -1;
}
videoindex = -1;
for (i = 0; inb_streams; i++)
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
videoindex = i;
break;
}
if (videoindex == -1) {
printf("Didn't find a video stream.\n");
return -1;
}
pCodecCtx = pFormatCtx->streams[videoindex]->codec;
pCodec = avcodec_find_decoder(pCodecCtx->codec_id); //查找解码器
if (pCodec == NULL) {
printf("Codec not found.\n");
return -1;
}
if (avcodec_open2(pCodecCtx, pCodec, NULL)<0) { //打开解码器
printf("Could not open codec.\n");
return -1;
}
pFrame = av_frame_alloc();
pFrameYUV = av_frame_alloc();
out_buffer = (uint8_t *)av_malloc(avpicture_get_size(PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height));
avpicture_fill((AVPicture *)pFrameYUV, out_buffer, PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);
packet = (AVPacket *)av_malloc(sizeof(AVPacket));
//Output Info-----------------------------
printf("---------------- File Information ---------------\n");
av_dump_format(pFormatCtx, 0, filepath, 0);
printf("-------------------------------------------------\n");
img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
pCodecCtx->width, pCodecCtx->height, 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;
//创建窗口
screen = SDL_CreateWindow("Simplest ffmpeg player's Window", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
screen_w, screen_h, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
if (!screen) {
printf("SDL: could not create window - exiting:%s\n", SDL_GetError());
return -1;
}
//创建渲染器
sdlRenderer = SDL_CreateRenderer(screen, -1, 0);
//IYUV: Y + U + V (3 planes)
//YV12: Y + V + U (3 planes)
//创建纹理
sdlTexture = SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, screen_w, screen_h);
//创建一个线程
video_tid = SDL_CreateThread(sfp_refresh_thread, NULL, NULL);
//------------SDL End------------
//Event Loop
for (;;) {
//Wait
SDL_WaitEvent(&event);
if (event.type == SFM_REFRESH_EVENT) {
//------------------------------
if (av_read_frame(pFormatCtx, packet) >= 0) { //读取一帧压缩数据
if (packet->stream_index == videoindex) {
ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet); //解码一帧压缩数据
if (ret < 0) {
printf("Decode Error.\n");
return -1;
}
if (got_picture) {
sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);
//SDL---------------------------
//设置纹理的数据
SDL_UpdateTexture(sdlTexture, NULL, pFrameYUV->data[0], pFrameYUV->linesize[0]);
sdlRect.x = 0;
sdlRect.y = 0;
sdlRect.w = screen_w;
sdlRect.h = screen_h;
//清空纹理
SDL_RenderClear(sdlRenderer);
//SDL_RenderCopy( sdlRenderer, sdlTexture, &sdlRect, &sdlRect );
//将纹理的数据拷贝给渲染器
SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, NULL);
//显示
SDL_RenderPresent(sdlRenderer);
//SDL End-----------------------
}
}
av_free_packet(packet);
}
else {
//Exit Thread
thread_exit = 1;
}
}
else if (event.type == SDL_WINDOWEVENT) {
//If Resize
SDL_GetWindowSize(screen, &screen_w, &screen_h);
}
else if (event.type == SDL_KEYDOWN) {
//Pause
if (event.key.keysym.sym == SDLK_SPACE)
thread_pause = !thread_pause;
}
else if (event.type == SDL_QUIT) {
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;
}
1>------ 已启动生成: 项目: SDL, 配置: Debug Win32 ------
1>SDL.obj : error LNK2019: 无法解析的外部符号 _av_malloc,该符号在函数 _SDL_main 中被引用
1>SDL.obj : error LNK2019: 无法解析的外部符号 _av_frame_alloc,该符号在函数 _SDL_main 中被引用
1>SDL.obj : error LNK2019: 无法解析的外部符号 _av_frame_free,该符号在函数 _SDL_main 中被引用
1>SDL.obj : error LNK2019: 无法解析的外部符号 _avcodec_open2,该符号在函数 _SDL_main 中被引用
原因是未将FFmpeg 的 dll文件,拷贝到相应的位置。
//创建窗口
screen = SDL_CreateWindow("Simplest ffmpeg player's Window", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
screen_w, screen_h, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
//输入文件路径
char filepath[] = "Tai.mp4";
char *filepath = argv[1];