本博客是摘自雷霄骅大神的课程《基于 FFmpeg + SDL 的视频播放器的制作》课程 里的内容,非常适合音视频小白入门,在这里感谢雷神的指导!
整合方式
• FFmpeg解码器实现了:视频文件->YUV
• SDL视频显示实现了:YUV->屏幕
• FFmpeg+SDL整合之后实现了:视频文件->YUV->屏幕
/**
* 最简单的基于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
*
* 本程序实现了视频文件的解码和显示(支持HEVC,H.264,MPEG2等)。
* 是最简单的FFmpeg视频解码方面的教程。
* 通过学习本例子可以了解FFmpeg的解码流程。
* 本版本中使用SDL消息机制刷新视频画面。
* This software is a simplest video player based on FFmpeg.
* Suitable for beginner of FFmpeg.
*
*/
#include
#define __STDC_CONSTANT_MACROS
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavutil/imgutils.h"
#include "SDL.h"
};
//Refresh Event
#define SFM_REFRESH_EVENT (SDL_USEREVENT + 1)
#define SFM_BREAK_EVENT (SDL_USEREVENT + 2)
int thread_exit = 0;
int sfp_refresh_thread(void* opaque) {
thread_exit = 0;
while (!thread_exit) {
SDL_Event event;
event.type = SFM_REFRESH_EVENT;
SDL_PushEvent(&event);
SDL_Delay(40);
}
thread_exit = 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[] = "屌丝男士.mov";
//av_register_all();
avformat_network_init();
pFormatCtx = avformat_alloc_context();
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; 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;
}
AVCodecParameters* codecParameters = pFormatCtx->streams[videoindex]->codecpar;
pCodecCtx = avcodec_alloc_context3(nullptr);
avcodec_parameters_to_context(pCodecCtx, codecParameters);
//pCodecCtx = pFormatCtx->streams[videoindex]->codec;
pCodec = (AVCodec*)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;
}
packet = av_packet_alloc();
pFrame = av_frame_alloc();
pFrameYUV = av_frame_alloc();
out_buffer = (uint8_t*)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1));
av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, out_buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1);
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);
if (SDL_Init(SDL_INIT_VIDEO | 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);
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, pCodecCtx->width, pCodecCtx->height);
sdlRect.x = 0;
sdlRect.y = 0;
sdlRect.w = screen_w;
sdlRect.h = screen_h;
packet = (AVPacket*)av_malloc(sizeof(AVPacket));
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) {
if (avcodec_send_packet(pCodecCtx, packet) < 0) {
printf("avcodec_send_packet failed!.\n");
continue;
}
while (1) {
ret = avcodec_receive_frame(pCodecCtx, pFrame);
if (ret != 0)break;
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]);
SDL_RenderClear(sdlRenderer);
//SDL_RenderCopy( sdlRenderer, sdlTexture, &sdlRect, &sdlRect );
SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, NULL);
SDL_RenderPresent(sdlRenderer);
//SDL End-----------------------
}
}
av_packet_unref(packet);
}
else {
//Exit Thread
thread_exit = 1;
}
}
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;
}
因为VS会生成可执行文件(在Debug文件夹中),利用argv参数进行视频文件的选择播放
main()函数的参数
• argc argv:全称为ARGument Counter 和 ARGument Vector。其中argv存储了来自于命令行的参数;而argc存储了参数的个数。
• 例如在命令行中输入“ffmpeg -i test.mkv test.ts ”,则argc取值为4, 而argv[]数组取值如下:
argv[0]=“ffmpeg”
argv[1]=“-i”
argv[2]=“test.mkv”
argv[3]=“test.ts”
• 动态链接库(*.dll)
动态链接库不能被编译进应用程序。因而使用应用程序的时候必须在相同目录下保存用到的动态链接库文件。