前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >结合ffmpeg用SDL播放YUV实现简易播放器

结合ffmpeg用SDL播放YUV实现简易播放器

作者头像
曾大稳
发布2018-09-11 10:34:46
1.2K0
发布2018-09-11 10:34:46
举报

通过解码之后得到的yuv视频数据我们直接可以进行播放,本篇使用SDL来实现视频播放。

SDL环境配置:https://blog.csdn.net/furzoom/article/details/53992124

SDL视频播放基础教程: https://blog.csdn.net/xuyankuanrong/article/details/77574152

有了上一篇文章的解码的基础,代码看着就很轻松了。

代码上写的很详细了,具体看代码



#include "stdafx.h"
#include <iostream>

// LNK2019	无法解析的外部符号 SDL_main,该符号在函数 main_utf8 中被引用
#define SDL_MAIN_HANDLED 

//Refresh Event
#define SFM_REFRESH_EVENT     (SDL_USEREVENT + 1)
#define SFM_BREAK_EVENT       (SDL_USEREVENT + 2)

extern "C" {
	#include <libavformat/avformat.h>   
	#include <libavcodec/avcodec.h>   
	#include <libavutil/imgutils.h>
	#include <libswscale/swscale.h>
	#include <libsdl/SDL.h>
}



void log_s(const char * msg, int d = -1123) {
	if (d == -1123) {
		printf_s("%s\n", msg);
	}
	else {
		printf_s("%s  %d \n", msg, d);
	}
}


int thread_exit = 0;
int thread_pause = 0;

int sfp_refresh_thread(void *opaque) {
	while (!thread_exit) {
		if (!thread_pause) {
			SDL_Event event;
			event.type = SFM_REFRESH_EVENT;
			SDL_PushEvent(&event);
		}
		SDL_Delay(10);
	}
	thread_exit = 0;
	thread_pause = 0;
	//Break
	SDL_Event event;
	event.type = SFM_BREAK_EVENT;
	SDL_PushEvent(&event);

	return 0;
}

int play(const char*  filePath) {
	AVFormatContext * pFmtCtx = NULL;
	AVCodecContext *pCodecCtx = NULL;
	AVFrame *pFrame = NULL;
	AVFrame *pFrameYUV = NULL;
	uint8_t *outBuffer = NULL;
	AVPacket *pPacket = NULL;
	SwsContext *pSwsCtx = NULL;

	//SDL
	int screen_w, screen_h;
	SDL_Window *screen=NULL;
	SDL_Renderer* sdlRenderer = NULL;
	SDL_Texture* sdlTexture = NULL;
	SDL_Rect sdlRect;
	SDL_Thread *video_tid = NULL;
	SDL_Event event;


	//1. 初始化
	av_register_all();
	avformat_network_init();
	//2. AVFormatContext获取
	pFmtCtx = avformat_alloc_context();
	//3. 打开文件
	if (avformat_open_input(&pFmtCtx, filePath, NULL, NULL) != 0) {
		log_s("Couldn't open input stream.\n");
		return -1;
	}
	//4. 获取文件信息
	if (avformat_find_stream_info(pFmtCtx, NULL)<0) {
		log_s("Couldn't find stream information.");
		return -1;
	}
	//5. 获取视频的index
	int i = 0, videoIndex = -1;
	for (; i < pFmtCtx->nb_streams; i++) {
		if (pFmtCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
			videoIndex = i;
			break;
		}
	}

	if (videoIndex == -1) {
		log_s("Didn't find a video stream.");
		return -1;
	}
	//6. 获取解码器并打开
	pCodecCtx = avcodec_alloc_context3(NULL);
	if (avcodec_parameters_to_context(pCodecCtx, pFmtCtx->streams[videoIndex]->codecpar) < 0) {
		log_s("Didn't parameters to contex.");
		return -1;
	}
	AVCodec *pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
	if (pCodec == NULL) {
		log_s("Codec not found.");
		return -1;
	}
	if (avcodec_open2(pCodecCtx, pCodec, NULL)<0) {//打开解码器
		log_s("Could not open codec.");
		return -1;
	}
	//7. 解码播放开始准备工作
	pFrame = av_frame_alloc();
	pFrameYUV = av_frame_alloc();

	//根据需要解码的类型,获取需要的buffer,不要忘记free
	outBuffer = (uint8_t *)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1) * sizeof(uint8_t));
	//根据指定的图像参数和提供的数组设置数据指针和行数 ,数据填充到对应的pFrameYUV里面
	av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, outBuffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1);

	pPacket = av_packet_alloc();
	log_s("--------------- File Information ----------------");
	av_dump_format(pFmtCtx, 0, filePath, 0);
	log_s("-------------------------------------------------");
	//获取SwsContext
	pSwsCtx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
		pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, NULL, NULL, NULL, NULL);
	//----------------------------------------------------------------------------------------------------------------
	// 8. SDL相关初始化
	if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
		log_s("Could not initialize SDL - ");
		log_s(SDL_GetError());
		return -1;
	}
	screen_w = pCodecCtx->width;
	screen_h = pCodecCtx->height;
	screen = SDL_CreateWindow("WS ffmpeg player", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
		pCodecCtx->width/2, pCodecCtx->height/2, SDL_WINDOW_OPENGL);
	if (!screen) {
		log_s("SDL: could not create window - exiting");
		log_s(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;

	video_tid = SDL_CreateThread(sfp_refresh_thread, NULL, NULL);
	//----------------------------------------------------------------------------------------------------------------


	int count = 0;

	//9.读取数据播放
	for (;;) {
		//Wait
		SDL_WaitEvent(&event);
		if (event.type == SFM_REFRESH_EVENT) {
			if (av_read_frame(pFmtCtx, pPacket) == 0) {
				if (pPacket->stream_index == videoIndex) {
					if (avcodec_send_packet(pCodecCtx, pPacket) != 0) {//解码一帧压缩数据
						log_s("Decode end or Error.");
						break;
					}
					else {//处理解码数据
						avcodec_receive_frame(pCodecCtx, pFrame);

						if (sws_scale(pSwsCtx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height,
							pFrameYUV->data, pFrameYUV->linesize) == 0) {
							continue;
						}

						count++;

						//SDL播放---------------------------
						SDL_UpdateTexture(sdlTexture, NULL, pFrameYUV->data[0], pFrameYUV->linesize[0]);
						SDL_RenderClear(sdlRenderer);
						//SDL_RenderCopy( sdlRenderer, sdlTexture, &sdlRect, &sdlRect);  
						SDL_RenderCopy(sdlRenderer, sdlTexture, &sdlRect, NULL);
						SDL_RenderPresent(sdlRenderer);
						//SDL End-----------------------

						log_s("Succeed to play frame!", count);
					}
				}
			}else {
				//退出线程
				thread_exit = 1;
				av_packet_unref(pPacket);
			}
		}
		else if (event.type == SDL_KEYDOWN) {
			log_s("Pause");
			//Pause
			if (event.key.keysym.sym == SDLK_SPACE)
				thread_pause = !thread_pause;
		}
		else if (event.type == SDL_QUIT) {
			log_s("quit");
			thread_exit = 1;
			break;
		}
		else if (event.type == SFM_BREAK_EVENT) {
			log_s("break");
			break;
		}
	}
	//sdl退出
	SDL_Quit();

	//回收
	if (pSwsCtx != NULL) {
		sws_freeContext(pSwsCtx);
	}
	if (outBuffer != NULL) {
		av_free(outBuffer);
	}
	if (pFrameYUV != NULL) {
		av_frame_free(&pFrameYUV);
	}
	if (pFrame != NULL) {
		av_frame_free(&pFrame);
	}
	if (pCodecCtx != NULL) {
		avcodec_close(pCodecCtx);
	}
	if (pFmtCtx != NULL) {
		avformat_close_input(&pFmtCtx);
	}
}


int main()
{
	play("F:/视频资源/gxsp.mp4");
	getchar();
	return 0;
}

参考链接:https://blog.csdn.net/king1425/article/details/71171142

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018-06-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档