1. 前言
在研究一个基于FFmpeg + UDP的流媒体服务器期间,偶然发现对于UDP高清流,有些播放器很容易花屏,于是我使用SDL播放器,做了一个简易的FFmpeg+SDL播放器。
感谢雷神,看他的博客提供的思路。
雷神走好!!!!!
2. 播放器
首先,定义一个FFmpegVideo类,用来打开udp流,读取帧数据,然后刷新SDL。
#pragma once
#ifndef FFmpegVideo_H
#define FFmpegVideo_H
#pragma warning(disable:4996)
#include <iostream>
#include <string.h>
#include <time.h>
#include <atlstr.h>
#include <WINSOCK.H>
#include <windows.h>
#include <Iphlpapi.h>
#include <vector>
#include <stdio.h>
#include "tlhelp32.h"
#include <regex>//标准
#include <process.h>
#include <Psapi.h>
#include <mutex>
#include <vector>
#include <thread> // std::thread
//引入头文件
extern "C"
{
#include "../lib/ffmpeg-20200126-5e62100-win32/include/libavutil/avassert.h"
#include "../lib/ffmpeg-20200126-5e62100-win32/include/libavutil/error.h"
//引入时间
#include "../lib/ffmpeg-20200126-5e62100-win32/include/libavformat/avformat.h"
#include "../lib/ffmpeg-20200126-5e62100-win32/include/libavutil/time.h"
#include "../lib/ffmpeg-20200126-5e62100-win32/include/libavutil/opt.h"
#include "../lib/ffmpeg-20200126-5e62100-win32/include/libswscale/swscale.h"
#include "../lib/ffmpeg-20200126-5e62100-win32/include/libavutil/imgutils.h"
#include "SDL.h"
}
//引入库
#pragma comment(lib,"../lib/ffmpeg-20200126-5e62100-win32/lib/swscale.lib")
//工具库,包括获取错误信息等
#pragma comment(lib,"../lib/ffmpeg-20200126-5e62100-win32/lib/avcodec.lib")
//编解码的库
#pragma comment(lib,"../lib/ffmpeg-20200126-5e62100-win32/lib/avutil.lib")
//引入库
#pragma comment(lib,"../lib/ffmpeg-20200126-5e62100-win32/lib/avformat.lib")
//工具库,包括获取错误信息等
#pragma comment(lib,"../lib/ffmpeg-20200126-5e62100-win32/lib/avutil.lib")
//编解码的库
#pragma comment(lib,"../lib/ffmpeg-20200126-5e62100-win32/lib/avcodec.lib")
class FFmpegVideo
{
public:
FFmpegVideo(char*, SDL_Renderer*, int x0, int y0, int w0, int h0);
bool Init();
void Run();
bool Close();
bool ReadFrame();
bool Play();
void lock();
void unlock();
void SetPosition(int x,int y,int w,int h);
~FFmpegVideo();
public:
std::mutex mut; //线程锁,用来锁定
char src[300]{ 0 };
AVFormatContext* pFormatCtx;
int i, videoindex;
AVCodecContext* pCodecCtx;
AVCodec* pCodec;
AVFrame* pFrame, * pFrameYUV;
unsigned char* out_buffer;
struct SwsContext* img_convert_ctx;
AVDictionary* opts = NULL;
int ret, got_picture;
AVPacket* packet = (AVPacket*)av_malloc(sizeof(AVPacket));;
bool ThreadRunFlag = true;
bool isSuccess = false;
bool init_rander = false;
SDL_Renderer* Randerer;
SDL_Rect rect;
SDL_Texture* text;
};
#endif
上面是类定义,类代码如下:
#include "FFmpegVideo.h"
FFmpegVideo::FFmpegVideo(char* src, SDL_Renderer* r,int x0, int y0, int w0, int h0) {
memcpy_s(this->src, 300, src, strlen(src));
this->Randerer = r;
this->rect.x = x0;
this->rect.y = y0;
this->rect.w = w0;
this->rect.h = h0;
};
FFmpegVideo::~FFmpegVideo() {
};
bool FFmpegVideo::Close() {
av_frame_free(&this->pFrameYUV);
av_frame_free(&this->pFrame);
avcodec_close(this->pCodecCtx);
avformat_close_input(&this->pFormatCtx);
}
bool FFmpegVideo::Init() {
this->isSuccess = false;
av_register_all();
avformat_network_init();
this->pFormatCtx = avformat_alloc_context();
av_dict_set(&this->opts, "buffer_size", "8192000", 0);
av_dict_set(&this->opts, "max_interleave_delta", "40000", 0);
if (avformat_open_input(&this->pFormatCtx, this->src, NULL, &this->opts) != 0) {
printf("Couldn't open input stream.\n");
return 0;
}
if (avformat_find_stream_info(this->pFormatCtx, NULL) < 0) {
printf("Couldn't find stream information.\n");
return 0;
}
this->videoindex = -1;
for (i = 0; i < this->pFormatCtx->nb_streams; i++)
if (this->pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
this->videoindex = i;
break;
}
if (this->videoindex == -1) {
printf("Didn't find a video stream.\n");
return 0;
}
this->pCodecCtx = this->pFormatCtx->streams[this->videoindex]->codec;
this->pCodec = avcodec_find_decoder(this->pCodecCtx->codec_id);
if (this->pCodec == NULL) {
printf("Codec not found.\n");
return 0;
}
if (avcodec_open2(this->pCodecCtx, this->pCodec, NULL) < 0) {
printf("Could not open codec.\n");
return 0;
}
this->pFrame = av_frame_alloc();
this->pFrameYUV = av_frame_alloc();
this->out_buffer = (unsigned char*)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);
av_dump_format(pFormatCtx, 0, this->src, 0);
img_convert_ctx = sws_getContext(this->pCodecCtx->width, this->pCodecCtx->height, this->pCodecCtx->pix_fmt,
pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
if (init_rander == false) {
init_rander = true;
this->text = SDL_CreateTexture(this->Randerer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, pCodecCtx->width, pCodecCtx->height);
}
this->isSuccess = true;
return true;
};
void FFmpegVideo::Run() {
while (this->ThreadRunFlag)
{
if (isSuccess ==false) {
this->Init();
}
if (isSuccess == true) {
this->ReadFrame();
}
Sleep(40);
}
}
bool FFmpegVideo::ReadFrame() {
if (av_read_frame(pFormatCtx, packet) < 0) {
av_free_packet(packet);
return 0;
}
this->Play();
av_free_packet(packet);
return 1;
}
void FFmpegVideo::lock() {
int fal = mut.try_lock();
while (fal == false)
{
//printf("锁定失败\n");
fal = mut.try_lock();
Sleep(0);
};
}
void FFmpegVideo::unlock() {
this->mut.unlock();
}
bool FFmpegVideo::Play() {
ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);
if (ret < 0) {
printf("Decode Error.\n");
return 0;
}
if (got_picture) {
sws_scale(img_convert_ctx, (const unsigned char* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);
SDL_UpdateTexture(this->text, NULL, pFrameYUV->data[0], pFrameYUV->linesize[0]);
SDL_RenderClear(this->Randerer);
this->lock();
SDL_RenderCopy(this->Randerer, this->text, NULL, &this->rect);// &this->rect
this->unlock();
SDL_RenderPresent(this->Randerer);
return 1;
}
return 0;
}
void FFmpegVideo::SetPosition(int x, int y, int w, int h) {
this->lock();
this->rect.x = x;
this->rect.y = y;
this->rect.w = w;
this->rect.h = h;
this->unlock();
};
然后来个主函数
#include <stdio.h>
#include "FFmpegVideo.h"
#pragma warning(disable:4996)
extern "C"
{
#include "SDL.h"
}
//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;
}
//启动超时推送线程
static void GetStreamThread(PVOID param) {
FFmpegVideo* vv = (FFmpegVideo*)param;
vv->Run();
};
int main(int argc, char* argv[])
{
//------------SDL----------------
SDL_Window* screen;
SDL_Renderer* sdlRenderer;
SDL_Thread* video_tid;
SDL_Event event;
//char filepath[]="bigbuckbunny_480x272.h265";
char filepath[] = "udp://127.0.0.1:6666";
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 = SDL_CreateWindow(filepath, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
1280, 720, 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);
video_tid = SDL_CreateThread(sfp_refresh_thread, NULL, NULL);
//创建一个播放源,并指定它的位置
FFmpegVideo V1(filepath, sdlRenderer,0,0,600,400);
V1.Init();
int width, height;
HANDLE h = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)GetStreamThread, (PVOID)&V1, 1, 0); //创建子线程
ResumeThread(h); //启动子线程
for (;;) {
//Wait
SDL_WaitEvent(&event);
if (event.type == SFM_REFRESH_EVENT) {
//
//V1.Play();
/*if (V1.ReadFrame()) {
V1.Play();
};*/
}
else if (event.type == SDL_WINDOWEVENT) {
//If Resize
SDL_GetWindowSize(screen, &width, &height);
V1.SetPosition(0, 0, width, height);
SDL_Log("resize to %d,%d\n", width, height);
}
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;
}
}
SDL_Quit();
//--------------
return 0;
}
3. 运行效果
实测,花屏跟两方面有关系,1是读取UDP流的时候,要一直读,不要有间歇,2是打开流的时候,要设置两个参数,如下:
av_dict_set(&this->opts, "buffer_size", "8192000", 0);
av_dict_set(&this->opts, "max_interleave_delta", "40000", 0);