FFmpeg+SDL纯视频播放器

简介

这里实现了一个简单的纯视频播放器,其数据流图如下
在这里插入图片描述

图中已经表述的很清楚了,解复用、解码、分辨率调节都通过ffmpeg完成,而显示则由SDL完成。其中解复用、解码和分辨率调整部分可以参看ffmpeg给出的示例文件(解复用和解码可参考demuxing_decoding.c,分辨率调整可参考scaling_video.c)。

分辨率调节并不是必须的,但如果输入视频的分辨率是1080P的话,则最终的显示需要创建一个1920*1080大小的窗口,否则视频将显示不完全。


头文件

解复用需要用到libavformat库,解码需要用到libavcodec库,而分辨率调整需要用到libswscalelibavutil库,显示需要用到SDL库,关于ffmpeg+SDL工程的配置可以参考之前写过的一篇文章

由于这里创建了main.cpp源文件,而ffmpeg是用C写的,因此在包含头文件时要做些处理

#include <iostream>

#ifdef __cplusplus
extern "C" {
#endif

#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>

#ifdef __cplusplus
}
#endif

#include <SDL.h>
using namespace std;

源码

#include <iostream>

#ifdef __cplusplus
extern "C" {
#endif

#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>

#ifdef __cplusplus
}
#endif
#include <SDL.h>
using namespace std;

int InitCodec(AVFormatContext* fmt_ctx, AVMediaType type, AVCodecContext*& video_dec_ctx, AVStream*& videoStream, int& streamIdx)
{
	int ret = 0;
	const AVCodec* dec = NULL;
	//-- 查找指定媒体流信息
	streamIdx = av_find_best_stream(fmt_ctx, type, -1, -1, NULL, 0);
	if (streamIdx < 0)
	{
		cout << "find stream error!" << endl;
		return -1;
	}
	videoStream = fmt_ctx->streams[streamIdx];
	//-- 获取解码器
	dec = avcodec_find_decoder(videoStream->codecpar->codec_id);
	if (dec == NULL)
	{
		cout << "find decoder error!" << endl;
		return -1;
	}
	//-- 创建编码器
	video_dec_ctx = avcodec_alloc_context3(dec);
	if (video_dec_ctx == NULL)
	{
		cout << "create decoder error!" << endl;
		return -1;
	}
	//-- 配置编码器
	ret = avcodec_parameters_to_context(video_dec_ctx, videoStream->codecpar);
	if (ret < 0)
	{
		cout << "config decoder failure!" << endl;
		return -1;
	}
	//--- 打开编码器
	ret = avcodec_open2(video_dec_ctx, dec, NULL);
	if (ret != 0)
	{
		cout << "open decoder error!" << endl;
		return -1;
	}
	return 1;
	
}

int main(int argc, char** argv)
{
	const char* fileName;
	int ret = 0;
	//-- 解复用解码
	AVFormatContext* fmt_ctx = NULL;
	AVCodecContext* video_dec_ctx = NULL;
	AVFrame* frame = NULL;
	AVPacket* pkt = NULL;
	AVStream* videoStream = NULL;
	int videoStreamIdx = -1;
	//-- 分辨率调整
	SwsContext* sws_ctx = NULL;
	uint8_t* pYUVFrame_data[4];
	int pYUVFrame_linesize[4];
	int src_w, src_h;
	int dst_w, dst_h;
	//-- SDL相关
	SDL_Window* screen;
	SDL_Renderer* sdlRenderer;
	SDL_Texture* sdlTexture;
	SDL_Rect sdlRect;

	if (argc != 2)
	{
		cout << "The number if input parameter error" << endl;
		return 1;
	}
	fileName = argv[1];
	//-- 打开文件,并初始化fmt_ctx
	if ((ret = avformat_open_input(&fmt_ctx, fileName, 0, 0)) < 0)
	{
		cout << "Open file error!" << endl;
		return 1;
	}
	//-- 读取流信息
	if ((ret = avformat_find_stream_info(fmt_ctx, 0)) < 0)
	{
		cout << "Retrieve stream info error!" << endl;
		return 1;
	}
	//-- 打印流信息
	av_dump_format(fmt_ctx, 0, fileName, 0);
	//-- 初始化解码器
	ret = InitCodec(fmt_ctx, AVMEDIA_TYPE_VIDEO, video_dec_ctx, videoStream, videoStreamIdx);
	if(ret <0)
	{
		cout << "init codec error!" << endl;
		return -1;
	}

	//-- 获取视频长宽信息
	src_w = videoStream->codecpar->width;
	src_h = videoStream->codecpar->height;
	//-- 设置目标长宽信息
	dst_w = src_w / 2;
	dst_h = src_h / 2;
	//-- 创建缩放对象(注:像素格式根据视频的具体信息而定,不一定都是AV_PIX_FMT_YUV420P)
	sws_ctx = sws_getContext(
		src_w, src_h, AV_PIX_FMT_YUV420P,
		dst_w, dst_h, AV_PIX_FMT_YUV420P,
		SWS_BICUBIC, NULL, NULL, NULL);
	//-- 分配缩放后数据的存储空间
	av_image_alloc(pYUVFrame_data, pYUVFrame_linesize,
		dst_w, dst_h, AV_PIX_FMT_YUV420P, 16);

	//-- 初始化SDL
	SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER);
	//-- 创建SDL窗口
	screen = SDL_CreateWindow("MyWindow", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
		dst_w, dst_h, SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE);
	//-- 创建SDL渲染
	sdlRenderer = SDL_CreateRenderer(screen, 0, -1);
	//-- 创建SDL纹理
	sdlTexture = SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_YV12, SDL_TEXTUREACCESS_STREAMING, dst_w, dst_h);
	//-- 初始化显示区域
	sdlRect = { 0, 0, dst_w, dst_h };

	//-- 分配包空间和帧空间
	pkt = av_packet_alloc();
	frame = av_frame_alloc();
	
	//-- 主循环
	while (av_read_frame(fmt_ctx, pkt) >= 0)
	{
		//-- 筛选视频流包
		if (pkt->stream_index == videoStreamIdx)
		{
			//-- 将视频包发送到解码器
			ret = avcodec_send_packet(video_dec_ctx, pkt);
			if (ret < 0)
			{
				cout << "Send pkt error!" << endl;
				break;
			}
			while (ret >= 0)
			{
				//-- 读取解码帧
				ret = avcodec_receive_frame(video_dec_ctx, frame);
				if (ret < 0)
				{
					if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN))
					{
						break;
					}
					cout << "Receive frame error!" << endl;
					break;
				}
				//-- 分辨率调整
				sws_scale(sws_ctx,
					(const unsigned char* const*)frame->data, frame->linesize, 
					0, src_h, 
					pYUVFrame_data, pYUVFrame_linesize);
				//-- 更新到SDL纹理
				SDL_UpdateYUVTexture(sdlTexture, &sdlRect,
					pYUVFrame_data[0], pYUVFrame_linesize[0],
					pYUVFrame_data[1], pYUVFrame_linesize[1],
					pYUVFrame_data[2], pYUVFrame_linesize[2]);
				//-- 清除源有渲染
				SDL_RenderClear(sdlRenderer);
				//-- 更新渲染
				SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, &sdlRect);
				//-- 显示渲染
				SDL_RenderPresent(sdlRenderer);
				//-- 延时40ms
				SDL_Delay(40);
			}
		}
		//-- 复位内存空间数据
		av_frame_unref(frame);
		av_packet_unref(pkt);
	}
    av_frame_unref(frame);
	av_packet_unref(pkt);
	//-- 释放资源
	SDL_DestroyTexture(sdlTexture);
	SDL_DestroyRenderer(sdlRenderer);
	SDL_DestroyWindow(screen);

	av_freep(&pYUVFrame_data[0]);
	sws_freeContext(sws_ctx);

	avcodec_free_context(&video_dec_ctx);
	avformat_close_input(&fmt_ctx);
	av_frame_free(&frame);
	av_packet_free(&pkt);
	return 0;
}

输入参数配置

在这里插入图片描述


运行结果

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值