FFmpeg:SDL播放器(1)

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);

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值