100行代码实现最简单的基于FFMPEG+SDL的视频播放器(SDL1.x)

时间:2021-04-19 12:04:05

转自:

http://blog.csdn.net/leixiaohua1020/article/details/8652605

机器环境:

  • vs2010
  • SDL-1.2.15
  • ffmpeg-20160628-c0cb53c-win32-dev

代码:(代码略有修改, vs2010调试通过)

/**
* 最简单的基于FFmpeg的视频播放器
* Simplest FFmpeg Player
*
* 雷霄骅 Lei Xiaohua
* leixiaohua1020@126.com
* 中国传媒大学/数字电视技术
* Communication University of China / Digital TV Technology
* http://blog.csdn.net/leixiaohua1020
*
* 本程序实现了视频文件的解码和显示(支持HEVC,H.264,MPEG2等)。
* 是最简单的FFmpeg视频解码方面的教程。
* 通过学习本例子可以了解FFmpeg的解码流程。
* This software is a simplest video player based on FFmpeg.
* Suitable for beginner of FFmpeg.
*/



#include <stdio.h>

//C99整数常量宏. [纯C程序可以不用, 而C++程序必须定义该宏.]
#define __STDC_CONSTANT_MACROS

// Windows平台
#ifdef _WIN32
// C++中包含C
extern "C"
{
#include "libavcodec/avcodec.h" // ffmpeg编解码模块
#include "libavformat/avformat.h" // ffmpeg视频格式模块
#include "libswscale/swscale.h" // ffmpeg各种图像像素格式的转换,以及图像大小缩放
#include "SDL.h" // Simple DirectMedia Layer
};
#else
//Linux...
#ifdef __cplusplus // 将C++代码以标准C形式输出
extern "C"
{
#endif
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <SDL/SDL.h>
#ifdef __cplusplus
};
#endif
#endif


//Full Screen
#define SHOW_FULLSCREEN 0
//Output YUV420P
#define OUTPUT_YUV420P 0


int main(int argc, char* argv[])
{
//FFmpeg
AVFormatContext *pFormatCtx; // 解封装功能结构体
int i, videoindex;
AVCodecContext *pCodecCtx; // 解码功能结构体
AVCodec *pCodec; // 解码器
AVFrame *pFrame,*pFrameYUV; // 解码后数据/帧
AVPacket *packet; // 解码前数据
struct SwsContext *img_convert_ctx; // 图像转换结构体
//SDL
int screen_w,screen_h; // 屏幕的宽&高
SDL_Surface *screen; // Graphical Surface Structure
SDL_VideoInfo *vi; // Video Target information
SDL_Overlay *bmp; // YUV video overlay
SDL_Rect rect; // Defines a rectangular area

FILE *fp_yuv; // 定义YUV文件
int ret, got_picture;
char filepath[]="some.mp4"; // 定义文件路径

av_register_all(); // ffmpeg注册复用器&编码器
avformat_network_init(); // ffmpeg网络流初始化

pFormatCtx = avformat_alloc_context(); // 初始化解封装功能结构体AVFormatContext

// 输入输出结构体AVIOContext的初始化
if(avformat_open_input(&pFormatCtx,filepath,NULL,NULL)!=0){
printf("Couldn't open input stream.\n");
return -1;
}

// 给每个媒体流(音频/视频)的AVStream结构体赋值
if(avformat_find_stream_info(pFormatCtx,NULL)<0){
printf("Couldn't find stream information.\n");
return -1;
}

// 获取所有视频流
videoindex=-1;
for(i=0; i<pFormatCtx->nb_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;

// 通过code ID查找一个已经注册的音视频解码器
// 查找解码器之前,必须先调用av_register_all注册所有支持的解码器
pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
if(pCodec==NULL){
printf("Codec not found.\n");
return -1;
}

// 初始化一个视音频编解码器的AVCodecContext
if(avcodec_open2(pCodecCtx, pCodec,NULL)<0){
printf("Could not open codec.\n");
return -1;
}

// 初始化解码后数据AVFrame
pFrame=av_frame_alloc();
pFrameYUV=av_frame_alloc();

// SDL初如化(视频|音频|定时器)
if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
printf( "Could not initialize SDL - %s\n", SDL_GetError());
return -1;
}

// 是否全屏
#if SHOW_FULLSCREEN
vi = SDL_GetVideoInfo();
screen_w = vi->current_w;
screen_h = vi->current_h;
screen = SDL_SetVideoMode(screen_w, screen_h, 0,SDL_FULLSCREEN);
#else
screen_w = pCodecCtx->width;
screen_h = pCodecCtx->height;
// 创建一个窗口
screen = SDL_SetVideoMode(screen_w, screen_h, 0,0);
#endif

if(!screen) {
printf("SDL: could not set video mode - exiting:%s\n",SDL_GetError());
return -1;
}

// 创建YUV图像
bmp = SDL_CreateYUVOverlay(pCodecCtx->width, pCodecCtx->height,SDL_YV12_OVERLAY, screen);

rect.x = 0;
rect.y = 0;
rect.w = screen_w;
rect.h = screen_h;
//SDL End------------------------


packet=(AVPacket *)av_malloc(sizeof(AVPacket));
//Output Information-----------------------------
printf("------------- File Information ------------------\n");

// 解封装结构体赋值
av_dump_format(pFormatCtx,0,filepath,0);
printf("-------------------------------------------------\n");

#if OUTPUT_YUV420P
fp_yuv=fopen("output.yuv","wb+");
#endif
// 设置窗口标题
SDL_WM_SetCaption("Simplest FFmpeg Player",NULL);

// 初始化一个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);

//-----------------------------------------------------------
// av_read_frame读取码流中的音频若干帧或者视频一帧
// 输入的AVFormatContext,输出的AVPacket
while(av_read_frame(pFormatCtx, packet)>=0){
if(packet->stream_index==videoindex){
// 解码
// 变量的定义:int ret, got_picture;
// 解码一帧视频数据。输入一个解码前AVCodecContext,输出一个解码后的结构体AVFrame
ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);
if(ret < 0){
printf("Decode Error.\n");
return -1;
}

// got_picture为零表示获取失败,非零获取成功
if(got_picture){
// Lock an overlay
SDL_LockYUVOverlay(bmp);

// SDL_Overlay *bmp;
// AVFrame *pFrame,*pFrameYUV;
// YUV420P中data[0]存Y,data[1]存U,data[2]存V
// 这里为啥交换1,2顺序???
pFrameYUV->data[0]=bmp->pixels[0];
pFrameYUV->data[1]=bmp->pixels[2];
pFrameYUV->data[2]=bmp->pixels[1];

pFrameYUV->linesize[0]=bmp->pitches[0];
pFrameYUV->linesize[1]=bmp->pitches[2];
pFrameYUV->linesize[2]=bmp->pitches[1];

// struct SwsContext *img_convert_ctx;
// sws_scale主要用于在2个AVFrame之间进行转换。
sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0,
pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);
#if OUTPUT_YUV420P
int y_size=pCodecCtx->width*pCodecCtx->height;
// FILE *fp_yuv;
// 将转换后的原始数据存成文件
// 保存YUV420P格式的数据
fwrite(pFrameYUV->data[0],1,y_size,fp_yuv); //Y
fwrite(pFrameYUV->data[1],1,y_size/4,fp_yuv); //U
fwrite(pFrameYUV->data[2],1,y_size/4,fp_yuv); //V
#endif
// UnLock an overlay
SDL_UnlockYUVOverlay(bmp);

// Blit the overlay to the display
SDL_DisplayYUVOverlay(bmp, &rect);

// 设置延迟,最小10ms.
// Delay 40ms
SDL_Delay(40);
}
}

// 释放AVPacket对象
av_free_packet(packet);
}

//FIX: Flush Frames remained in Codec
while (1) {
// 解码一帧视频数据
ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);
if (ret < 0)
break;
if (!got_picture)
break;

sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);

SDL_LockYUVOverlay(bmp);
pFrameYUV->data[0]=bmp->pixels[0];
pFrameYUV->data[1]=bmp->pixels[2];
pFrameYUV->data[2]=bmp->pixels[1];

pFrameYUV->linesize[0]=bmp->pitches[0];
pFrameYUV->linesize[1]=bmp->pitches[2];
pFrameYUV->linesize[2]=bmp->pitches[1];
#if OUTPUT_YUV420P
int y_size=pCodecCtx->width*pCodecCtx->height;
fwrite(pFrameYUV->data[0],1,y_size,fp_yuv);//Y
fwrite(pFrameYUV->data[1],1,y_size/4,fp_yuv); //U
fwrite(pFrameYUV->data[2],1,y_size/4,fp_yuv); //V
#endif

SDL_UnlockYUVOverlay(bmp);
SDL_DisplayYUVOverlay(bmp, &rect);
//Delay 40ms
SDL_Delay(40);
}

sws_freeContext(img_convert_ctx);

#if OUTPUT_YUV420P
fclose(fp_yuv);
#endif

SDL_Quit();

//av_free(out_buffer);
av_free(pFrameYUV);
avcodec_close(pCodecCtx);
avformat_close_input(&pFormatCtx);

return 0;
}