参考链接:《基于 FFmpeg + SDL 的视频播放器的制作》课程的视频_雷霄骅(leixiaohua1020)的专栏-CSDN博客_雷霄骅ffmpeg视频教程
雷神的基于 FFmpeg + SDL 的视频播放器的制作到这里快进入尾声,下面还有一节添加图形界面的课程,不得不说,雷神的录播课讲的非常详细,令人钦佩。
关于雷神的基于 FFmpeg + SDL 的视频播放器的制作的源代码,我发现在他在界面.h265,h.264的文件时,视频的播放速度还算是正常,但是在播放flv,mp4等封装格式的文件时,速度会比较慢,不是正常的速度,这个他在视频中有讲过修改方法,不过在课件的源代码中给出的不是修改过后的,这里给出正确的修改代码(具体修改方式,雷神在http://www.iqiyi.com/w_19rruoxurx.html视频教程中有所提及)
#include "stdafx.h"
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
//SDL
#include "sdl/SDL.h"
#include "sdl/SDL_thread.h"
};
//Refresh Event
#define SFM_REFRESH_EVENT (SDL_USEREVENT + 1)
int thread_exit = 0; //定义一个线程结束标志
int sfp_refresh_thread(void* opaque)
{
while (thread_exit == 0) {
SDL_Event event;
event.type = SFM_REFRESH_EVENT;
SDL_PushEvent(&event);
SDL_Delay(40);
}
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
AVFormatContext* pFormatCtx;
int i, videoindex;
AVCodecContext* pCodecCtx; //保存了视频(音频)编解码相关信息。
AVCodec* pCodec;
char filepath[] = "屌丝男士.mov";
int frame_cnt = 0;
//注册组件+初始化
av_register_all();
avformat_network_init();
//函数用来申请AVFormatContext类型变量并初始化默认参数,分配内存空间
pFormatCtx = avformat_alloc_context();
//打开视频流文件
if (avformat_open_input(&pFormatCtx, filepath, NULL, NULL) != 0) {
printf("Couldn't open input stream.(无法打开输入流)\n");
return -1;
}
//获取视频文件信息
if (av_find_stream_info(pFormatCtx) < 0)
{
printf("Couldn't find stream information.(无法获取流信息)\n");
return -1;
}
//设置一个视频帧索引
videoindex = -1;
for (i = 0; i < pFormatCtx->nb_streams; i++) //nb_streams:输入视频的AVStream 个数
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) //streams:输入视频的AVStream []数组
{
videoindex = i; //判断文件是视频流还是音频流,把其中的视频流获取出来
break;
}
if (videoindex == -1)
{
printf("Didn't find a video stream.(没有找到视频流)\n");
return -1;
}
pCodecCtx = pFormatCtx->streams[videoindex]->codec; //codec:该流对应的AVCodecContext
pCodec = avcodec_find_decoder(pCodecCtx->codec_id); //avcodec_find_decoder():查找解码器; pCodecCtx->codec_id 编解码器ID
if (pCodec == NULL)
{
printf("Codec not found.(没有找到解码器)\n");
return -1;
}
//打开解码器,参数为AVCodecContext和对应的解码器ID
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0)
{
printf("Could not open codec.(无法打开解码器)\n");
return -1;
}
AVFrame* pFrame, * pFrameYUV; //存储一帧解码后像素(采样)数据
pFrame = avcodec_alloc_frame(); //为解码帧分配内存
pFrameYUV = avcodec_alloc_frame();
//av_malloc()内存分配,uint8_t = unsigned char无符号字符型
//avpicture_get_size()计算这个格式的图片,需要多少字节来存储
uint8_t* out_buffer = (uint8_t*)av_malloc(avpicture_get_size(PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height));
/*
pFrameYUV和out_buffer都是已经申请到的一段内存, 但是pFrameYUV只是申请了一段结构体内存,
结构体里面的值是空的, 我们需要使用avpicture_fill()函数来使得pFrameYUV和out_buffer关联起来,
pFrameYUV里面使用的是out_buffer所指向的内存空间.
*/
avpicture_fill((AVPicture*)pFrameYUV, out_buffer, PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);
//------------SDL----------------
//初始化
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
printf("Could not initialize SDL - %s\n", SDL_GetError());
return -1;
}
int screen_w = 0, screen_h = 0;
SDL_Window* screen;
//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);
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);
SDL_Rect sdlRect; //创建一个矩形结构体,分别给左上角的坐标以及矩形框的宽和高
sdlRect.x = 0;
sdlRect.y = 0;
sdlRect.w = screen_w;
sdlRect.h = screen_h;
int ret, got_picture;
//分配一帧压缩编码数据的内存
AVPacket* packet = (AVPacket*)av_malloc(sizeof(AVPacket));
//Output Info-----------------------------
printf("File Information(文件信息)---------------------\n");
//av_dump_format()打印关于输入或输出格式的详细信息
av_dump_format(pFormatCtx, 0, filepath, 0);
printf("-------------------------------------------------\n");
//详解:https://blog.csdn.net/MACMACip/article/details/105450185
struct SwsContext* img_convert_ctx;
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_Thread* video_tid = SDL_CreateThread(sfp_refresh_thread, NULL, NULL);
//
//Event Loop
SDL_Event event;
for (;;) {
//Wait
SDL_WaitEvent(&event);
if (event.type == SFM_REFRESH_EVENT) {
//------------------------------
//在这里判断是不是视频帧,如果是就直接跳出循环
while (1) {
if (av_read_frame(pFormatCtx, packet) < 0) {
thread_exit = 1;
}
if (packet->stream_index == videoindex) {
break;
}
}
//解码一帧压缩数据
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);
//printf("Decoded frame index: %d\n", frame_cnt);
//frame_cnt++;
//SDL---------------------------
SDL_UpdateTexture(sdlTexture, &sdlRect, pFrameYUV->data[0], pFrameYUV->linesize[0]);
SDL_RenderClear(sdlRenderer);
SDL_RenderCopy(sdlRenderer, sdlTexture, &sdlRect, &sdlRect);
SDL_RenderPresent(sdlRenderer);
//SDL End-----------------------
//av_free_packet(packet);
}
av_free_packet(packet); //释放
}
}
sws_freeContext(img_convert_ctx);
SDL_Quit(); //退出线程
//--------------
av_free(out_buffer);
av_free(pFrameYUV);
avcodec_close(pCodecCtx);
avformat_close_input(&pFormatCtx);
return 0;
}