FFMpeg-6、Libavdevice+SDL捕获显示摄像头、录屏

一、使用命令进行摄像头 录屏相关测试实现

1、使用dshow的时候找不到对应设备

根据网上步骤使用dshow命令查找设备,发现视频设备只有一个就是摄像头 音频信号也就有一个,没有屏幕设备和对应的音响设备。
在这里插入图片描述
发现需要下载第三方软件注册录屏dshow滤镜如screen-capture-recorder,网上下载太慢 已附上百度云链接。
链接: https://pan.baidu.com/s/1Y_idLtI-mzSyLW_Wk3wCYQ 提取码: atmv
安装下载后就可以显示出四个设备了

命令;ffmpeg -list_devices true -f dshow -i dummy
[dshow @ 000002a18fcb5140] DirectShow video devices (some may be both video and audio devices)
[dshow @ 000002a18fcb5140]  "Integrated Camera"
[dshow @ 000002a18fcb5140]     Alternative name "@device_pnp_\\?\usb#vid_13d3&pid_5419&mi_00#7&17d116b8&1&0000#{65e8773d-8f56-11d0-a3b9-00a0c9223196}\global"
[dshow @ 000002a18fcb5140]  "screen-capture-recorder"
[dshow @ 000002a18fcb5140]     Alternative name "@device_sw_{860BB310-5D01-11D0-BD3B-00A0C911CE86}\{4EA69364-2C8A-4AE6-A561-56E4B5044439}"
[dshow @ 000002a18fcb5140] DirectShow audio devices
[dshow @ 000002a18fcb5140]  "麦克风阵列 (Realtek(R) Audio)"
[dshow @ 000002a18fcb5140]     Alternative name "@device_cm_{33D9A762-90C8-11D0-BD43-00A0C911CE86}\wave_{6B542DD3-5F68-47CD-AAB4-1A1610ABF554}"
[dshow @ 000002a18fcb5140]  "virtual-audio-capturer"
[dshow @ 000002a18fcb5140]     Alternative name "@device_sw_{33D9A762-90C8-11D0-BD43-00A0C911CE86}\{8E146464-DB61-4309-AFA1-3578E927E935}"

其中视频的就是
"Integrated Camera"  摄像头  ffplay -f dshow -i video="Integrated Camera"直接测试打开摄像头
"screen-capture-recorder"  屏幕
//如果存在多显示器这里会出现多个设备的

音频相关的设备
"麦克风阵列 (Realtek(R) Audio)"  摄像头相关的
"virtual-audio-capturer"  屏幕相关的音响

具体可借鉴博客
FFmpeg获取DirectShow设备数据(摄像头,录屏)

2、捕获摄像头和录屏等命令

捕获摄像头
从摄像头读取数据并编码为H.264,最后保存成mycamera.mkv。
-f 表示输出格式转换   -i输入  -vcodec指定编码  mycamera.mkv输出文件  测试成功
ffmpeg -f dshow -i video="Integrated Camera" -vcodec libx264 mycamera.mkv

ffplay可以直接播放摄像头的数据  测试成功
ffplay -f dshow -i video="Integrated Camera"

录屏相关
Linux下使用FFmpeg进行屏幕录制相对比较方便,可以使用x11grab
-r fps 设置帧频 缺省25 
-s size 设置帧大小 格式为WXH 缺省160X128
编码效率和视频质量的取舍(preset, crf)
ffmpeg -f x11grab -r 30 -i :0.0 -f alsa -i hw:0,0 -acodec flac -vcodec ffvhuff out.mkv


windows下就需要安装screen capture recorder 注册屏幕捕获设备 (验证失败 网上提示改大缓冲还是不行 -rtbufsize 160M)
ffmpeg -f dshow -i video="screen-capture-recorder" -r 5 -vcodec libx264 mycamera.mkv
录屏也可以使用**gdigrab** 测试成功
ffmpeg -f gdigrab -i desktop -preset ultrafast -crf 10 a.mkv

//录屏录音 测试成功 只是比较模糊
ffmpeg -f dshow -i audio="麦克风阵列 (Realtek(R) Audio)" \
-f dshow -i audio="virtual-audio-capturer" \
-filter_complex amix=inputs=2:duration=first:dropout_transition=2 \
-f dshow -i video="screen-capture-recorder" -y av-out.flv

‘-y (global)’ 
  覆盖输出文件而不询问

注意;
本程序在Windows下可以使用2种方式录制屏幕:
1.gdigrab: Win32下的基于GDI的屏幕录制设备。 抓取桌面的时候,输入URL为“desktop”。
2.dshow:使用Directshow。注意需要安装额外的软件screen-capture-recorder
在Linux下可以使用x11grab录制屏幕。
在MacOS下可以使用avfoundation录制屏幕

ffmpeg命令详细可查看ffmpeg命令介绍

二、使用工程代码实现

FFmpeg中有一个和多媒体设备交互的类库:Libavdevice。使用这个库可以读取电脑(或者其他设备上)的多媒体设备的数据,或者输出数据到指定的多媒体设备上。现在实现录屏和获取摄像头数据都是基于Libavdevice库的,详情可以查看雷神博客https://blog.csdn.net/leixiaohua1020/article/details/39702113

1、遇到的问题汇总

1.1、记录vs2015编译错误

注意工程配置就是SDL+FFMpeg的那些头文件 lib 放置并在属性里面进行配置即可。

LNK1295 “/OPT:NOREF”与“/LTCG:incremental”规范不兼容;链接时不使用“/LTCG:incremental”
解决方法;
1、项目属性-》链接器-》优化-》引用-》是
2、项目属性-》链接器-》命令行进行设置/SAFESEH:NO
参考博客;https://blog.csdn.net/hejjunlin/article/details/68921811

1.2、对屏幕视频流解码后需要转换成YUV

首先是需要使用sws_getCachedContext、sws_scale的ffmpeg里面的视频转换库函数 转换指定像素格式。
第二需要注意转换过来的存放格式需要定义 如果自定义的话需要计算 如YUV格式则data需要定义三维指针数组 并且每一维数据行的大小要定义好否则就会提示bad dst image pointers,坏帧的情况。
解决方法

//定义转换的存放区以及每行大小 
AVFrame	*pFrameYUV = NULL;
pFrameYUV = av_frame_alloc();
//这一步十分重要 之前知道
//后面使用sws_scale 需要内存和指定每行大小存放 使用avpicture_fill就可以不用自定义计算了
	pFrame = av_frame_alloc();
	uint8_t *out_buffer = (uint8_t *)av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height));
//avpicture_fill((AVPicture *)pFrameYUV, out_buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);  新版本被遗弃
//存储一帧像素数据缓冲区
av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, out_buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1);

//使用swr视频尺寸像素转换
//这一步十分重要 之前知道  解码出来的进行SDL显示 像素格式需要转换,否则就是绿屏
struct SwsContext *img_convert_ctx = NULL;
img_convert_ctx = sws_getCachedContext(img_convert_ctx, pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);

1.3、未解决问题

1)显示的SDL大小问题及windows缩放比存在问题导致显示的时候很卡或者不全面。
2)使用dshow的时候提示错误
real real-time buffer [screen-capture-recorder] [video input] too full or near too full (340% of size: 3041280 [rtbufsize parameter])! frame dropped!
3) 在里面进行读取avformat_find_stream_info(acformatCon, 0);奔溃

2、录屏+SDL显示

2.1、整体流程

1、首先ffmpeg的gdigrab/dshow解封装器对desktop进行解码获取对应的视频流
 1.1、注册设备
 1.2、获取gdigrab的desktop的封装器上下文或者dshow的screen-capture-recorder上下文
 1.3、得到视频流的ID
 1.4、获取打开对应视频流的解码器和解码器上下文
 2、SDL显示相关初始化
 2.1、初始化init
 2.2、创建窗口
 2.3、创建渲染器
 2.4、创建纹理
 3、读取编解码数据并SDL显示
 3.1、创建SDL管理线程 进行事件发送
 3.2、播放
 3.2.1、定义AVPacket、AVFrame,设置rect纹理显示的位置,宽高
 3.2.2、定义存储转换后一帧像素数据缓冲区
 3.2.3、sws进行视频尺寸像素转换定义
 3.2.4、循环播放
	SDL_WaitEvent等待事件
	if (event.type == REFRESH_EVENT)//刷新事件进入
	{
		//预处理 读完或者读到视频帧才下一步
		//解码发送packet接收帧frame
		//进行sws_scale转换输出YUV类型
		SDL_UpdateYUVTexture 填充纹理
		//进行SDL刷新显示
		SDL_RenderClear(renderer);
		SDL_RenderCopy(renderer, texture, NULL, &rect);
		SDL_RenderPresent(renderer);
	}
 4、释放内存

2.2、使用ffmpeg中gdigrab或dshow+SDL进行录屏工程代码

#include <iostream>
#include <thread>

using namespace std;

#ifdef __cplusplus
extern "C"
{
#endif // !__cplusplus
	
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"
#include "libswscale/swscale.h"
#include "libswresample/swresample.h"//包含头文件
#include "libavdevice/avdevice.h"
#include "libavutil/imgutils.h"
	
#include "SDL.h"
#include "SDL_thread.h"

#ifdef __cplusplus
}
#endif // !__cplusplus

#pragma comment(lib,"avformat.lib")//添加库文件,也可以在属性处添加
#pragma comment(lib,"avutil.lib")
#pragma comment(lib,"avcodec.lib")
#pragma comment(lib,"swresample.lib")
#pragma comment(lib,"swscale.lib")
#pragma comment(lib,"avdevice.lib")

#pragma comment(lib,"SDL2.lib")
#pragma comment(lib,"SDL2main.lib")
#pragma comment(lib,"SDL2test.lib")

//SDL的事件定义
#define REFRESH_EVENT  (SDL_USEREVENT + 1) //刷新事件
#define BREAK_EVENT  (SDL_USEREVENT + 2) // 退出事件
int thread_exit = 0;//相应变量定义
int thread_pause = 0;

//解决vs2015链接错误
#pragma comment(lib,"legacy_stdio_definitions.lib")
extern "C" { FILE __iob_func[3] = { *stdin,*stdout,*stderr }; }

//SDL线程函数
int video_refresh_thread(void *data) {
	thread_exit = 0;
	thread_pause = 0;

	while (!thread_exit) {
		if (!thread_pause) {
			SDL_Event event;
			event.type = REFRESH_EVENT;
			SDL_PushEvent(&event);// 发送刷新事件
		}
		SDL_Delay(40);
	}
	thread_exit = 0;
	thread_pause = 0;
	//Break
	SDL_Event event;
	event.type = BREAK_EVENT;
	SDL_PushEvent(&event);

	return 0;
}
int main(int argc, char *argv[])
{
	AVFormatContext	*pFormatCtx;
	int				i, videoindex;
	AVCodecContext	*pCodecCtx;
	AVCodec			*pCodec;
	int ret;

	//SDL相关变量
	int w_width = 640;	//默认窗口大小
	int w_height = 480;
	SDL_Rect rect;
	Uint32 pixformat;
	SDL_Window *win = NULL;
	SDL_Renderer *renderer = NULL;
	SDL_Texture *texture = NULL;

	//1、首先ffmpeg的gdigrab解封装器对desktop进行解码获取对应的视频流
	//1.1、注册设备
	av_register_all();
	avformat_network_init();
	avdevice_register_all();//添加一个avdevice设备注册
	//1.2、获取gdigrab的desktop的封装器上下文
	pFormatCtx = avformat_alloc_context();
	/*
	//使用gdigrab进行录屏
	AVDictionary* options = NULL;
	//Set some options
	//grabbing frame rate
	//av_dict_set(&options,"framerate","5",0);
	//The distance from the left edge of the screen or desktop
	//av_dict_set(&options,"offset_x","20",0);
	//The distance from the top edge of the screen or desktop
	//av_dict_set(&options,"offset_y","40",0);
	//Video frame size. The default is to capture the full screen
	//av_dict_set(&options,"video_size","640x480",0);
	AVInputFormat *ifmt = av_find_input_format("gdigrab");
	if (avformat_open_input(&pFormatCtx, "desktop", ifmt, &options) != 0) {
		printf("Couldn't open input stream.(无法打开输入流)\n");
		return -1;
	}
	*/
	//使用dshow进行录屏
	AVInputFormat *ifmt = av_find_input_format("dshow");
	if (avformat_open_input(&pFormatCtx, "video=screen-capture-recorder", ifmt, NULL) != 0) {
		printf("Couldn't open input stream.(无法打开输入流)\n");
		return -1;
	}

	//1.3、得到视频流的ID
	if (avformat_find_stream_info(pFormatCtx, NULL)<0)//有时候要调用一下 不然一些数据可能未读取到
	{
		printf("Couldn't find stream information.(无法获取流信息)\n");
		return -1;
	}
	videoindex = -1;
	videoindex = av_find_best_stream(pFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);//直接获取 未使用循环
	if (videoindex == -1)
	{
		printf("Didn't find a video stream.(没有找到视频流)\n");
		return -1;
	}
	//1.4、获取打开对应视频流的解码器和解码器上下文
	AVCodec *vcodec = avcodec_find_decoder(pFormatCtx->streams[videoindex]->codecpar->codec_id);
	if (!vcodec)
	{
		cout << "Codec not found.(没有找到解码器" << endl;
		return -1;
	}
	pCodecCtx = avcodec_alloc_context3(vcodec);
	avcodec_parameters_to_context(pCodecCtx, pFormatCtx->streams[videoindex]->codecpar);
	pCodecCtx->thread_count = 8;
	ret = avcodec_open2(pCodecCtx, NULL, 0);
	if (ret != 0)
	{
		cout << "Could not open codec.(无法打开解码器" << endl;
		return -1;
	}
	//avformat_find_stream_info(pFormatCtx, NULL);
	//2、SDL显示相关初始化
	//2.1、初始化init
	if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
		SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Could not initialize SDL - %s\n", SDL_GetError());
		return ret;
	}

	//2.2、创建窗口
	w_width = pFormatCtx->streams[videoindex]->codecpar->width;//关于屏幕这里还不知道怎么设置屏幕大小
	w_height = pFormatCtx->streams[videoindex]->codecpar->height;
	win = SDL_CreateWindow("Media Player",
		SDL_WINDOWPOS_UNDEFINED,
		SDL_WINDOWPOS_UNDEFINED,
		w_width, w_height,
		SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
	if (!win) {
		SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create window by SDL");
		//要释放ffmpeg的相关内存
		return -1;
	}

	//2.3、创建渲染器
	renderer = SDL_CreateRenderer(win, -1, 0);
	if (!renderer) {
		SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create Renderer by SDL");
		//要释放ffmpeg的相关内存
		return -1;
	}
	//2.4、创建纹理
	pixformat = SDL_PIXELFORMAT_IYUV;//YUV格式			 
	texture = SDL_CreateTexture(renderer,
		pixformat,
		SDL_TEXTUREACCESS_STREAMING,
		w_width,
		w_height);
	
	//3、读取编解码数据并SDL显示
	//3.1、创建SDL管理线程
	SDL_Event event;
	SDL_CreateThread(video_refresh_thread, "Video Thread", NULL);
	//3.2、播放
	//3.2.1、定义AVPacket、AVFrame设置纹理显示的位置,宽高
	AVPacket *packet = av_packet_alloc();
	AVFrame *pFrame = NULL;
	AVFrame	*pFrameYUV = NULL;
	pFrameYUV = av_frame_alloc();

	//3.2.2、定义存储转换后一帧像素数据缓冲区
	//这一步十分重要 之前知道
	//后面使用sws_scale 需要内存和指定每行大小存放 使用avpicture_fill就可以不用自定义计算了
	pFrame = av_frame_alloc();
	uint8_t *out_buffer = (uint8_t *)av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height));
	//avpicture_fill((AVPicture *)pFrameYUV, out_buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);  新版本被遗弃2
	//存储一帧像素数据缓冲区
	av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, out_buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1);

	rect.x = 0;
	rect.y = 0;
	rect.w = w_width;
	rect.h = w_height;
	//3.2.3、sws进行视频尺寸像素转换定义
	//这一步十分重要 之前知道  解码出来的进行SDL显示 像素格式需要转换,否则就是绿屏
	struct SwsContext *img_convert_ctx = NULL;
	img_convert_ctx = sws_getCachedContext(img_convert_ctx, pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);

	//写入文件中
	//FILE *fp_yuv=fopen("output.yuv","wb+");

	//3.2.4、循环播放
	for (;;) 
	{
		SDL_WaitEvent(&event);//使用时间驱动,每40ms执行一次 等SDL_PushEvent
		 //是刷新事件REFRESH_EVENT才进入
		if (event.type == REFRESH_EVENT)
		{
			//预处理一下
			while (1) {
				if (av_read_frame(pFormatCtx, packet) < 0)//读完了
					thread_exit = 1;

				if (packet->stream_index == videoindex)
					break;
			}
			//接收的包是视频包则进入SDL显示
			if (packet->stream_index == videoindex) {
				avcodec_send_packet(pCodecCtx, packet);//发送packet到解码线程 返回多帧
				while (avcodec_receive_frame(pCodecCtx, pFrame) == 0) {//循环读取解码返回的帧

					sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);

					/*
					//写入文件中
						int y_size=pCodecCtx->width*pCodecCtx->height;    
						fwrite(pFrameYUV->data[0],1,y_size,fp_yuv);    //Y   
						fwrite(pFrameYUV->data[1],1,y_size/4,fp_yuv);  //U  
						fwrite(pFrameYUV->data[2],1,y_size/4,fp_yuv);  //V  
					*/
				
					//将返回的数据更新到SDL纹理当中
					SDL_UpdateYUVTexture(texture, NULL,
						pFrameYUV->data[0], pFrameYUV->linesize[0],
						pFrameYUV->data[1], pFrameYUV->linesize[1],
						pFrameYUV->data[2], pFrameYUV->linesize[2]);

					//进行SDL刷新显示
					SDL_RenderClear(renderer);
					SDL_RenderCopy(renderer, texture, NULL, &rect);
					SDL_RenderPresent(renderer);
				}
				av_packet_unref(packet);
			}
		}
		else if (event.type == SDL_KEYDOWN) 
		{
			if (event.key.keysym.sym == SDLK_SPACE) { //空格键暂停
				thread_pause = !thread_pause;
			}
			if (event.key.keysym.sym == SDLK_ESCAPE) { // ESC键退出
				thread_exit = 1;
			}
		}
		else if (event.type == SDL_QUIT) 
		{
			thread_exit = 1;
		}
		else if (event.type == BREAK_EVENT) 
		{
			break;
		}
	}
	//4、释放内存
	if (pFrame)
	{
		av_frame_free(&pFrame);
	}
	if (pFrameYUV)
	{
		av_frame_free(&pFrameYUV);
	}
	if (packet)
	{
		av_packet_free(&packet);
	}
	// Close the codec
	if (pCodecCtx)
	{
		avcodec_close(pCodecCtx);
	}
	// Close the video file
	if (pFormatCtx) 
	{
		avformat_close_input(&pFormatCtx);
	}
	if (win) 
	{
		SDL_DestroyWindow(win);
	}
	if (renderer) 
	{
		SDL_DestroyRenderer(renderer);
	}
	if (texture) 
	{
		SDL_DestroyTexture(texture);
	}

	SDL_Quit();

	return 0;
}

3、摄像头数据+SDL显示

在Windows下可以使用2种方式读取摄像头数据;
1.VFW: Video for Windows 屏幕捕捉设备。注意输入URL是设备的序号, 从0至9。
2.dshow: 使用Directshow。注意作者机器上的摄像头设备名称是 “Integrated Camera”,使用的时候需要改成自己电脑上摄像头设备的名称。
命令行中使用在上面已经描述了但是注意在代码中进行获取的时候要先进行代码识别,再作为解封装器传入。

获取摄像头与录屏基本都是一致的,只是摄像头这边多出需要提前show一下,但是如果提前在命令行中运行过则是可以找到不需要提前show的,然后就是注册打开解封装器上下文那里传入的数据不一致
只有这一部分不一样 其余编码显示流程都可以不变可以直接显示摄像头的简单demo
在这里插入图片描述
工程代码

#include <iostream>
#include <thread>

using namespace std;

#ifdef __cplusplus
extern "C"
{
#endif // !__cplusplus
	
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"
#include "libswscale/swscale.h"
#include "libswresample/swresample.h"//包含头文件
#include "libavdevice/avdevice.h"
#include "libavutil/imgutils.h"
	
#include "SDL.h"
#include "SDL_thread.h"

#ifdef __cplusplus
}
#endif // !__cplusplus

#pragma comment(lib,"avformat.lib")//添加库文件,也可以在属性处添加
#pragma comment(lib,"avutil.lib")
#pragma comment(lib,"avcodec.lib")
#pragma comment(lib,"swresample.lib")
#pragma comment(lib,"swscale.lib")
#pragma comment(lib,"avdevice.lib")

#pragma comment(lib,"SDL2.lib")
#pragma comment(lib,"SDL2main.lib")
#pragma comment(lib,"SDL2test.lib")

//SDL的事件定义
#define REFRESH_EVENT  (SDL_USEREVENT + 1) //刷新事件
#define BREAK_EVENT  (SDL_USEREVENT + 2) // 退出事件
int thread_exit = 0;//相应变量定义
int thread_pause = 0;

//解决vs2015链接错误
#pragma comment(lib,"legacy_stdio_definitions.lib")
extern "C" { FILE __iob_func[3] = { *stdin,*stdout,*stderr }; }

#define USE_DSHOW 1
//Show Device
void show_dshow_device() {
	AVFormatContext *pFormatCtx = avformat_alloc_context();
	AVDictionary* options = NULL;
	av_dict_set(&options, "list_devices", "true", 0);
	AVInputFormat *iformat = av_find_input_format("dshow");
	printf("Device Info=============\n");
	avformat_open_input(&pFormatCtx, "video=dummy", iformat, &options);
	printf("========================\n");
}

//Show Device Option
void show_dshow_device_option() {
	AVFormatContext *pFormatCtx = avformat_alloc_context();
	AVDictionary* options = NULL;
	av_dict_set(&options, "list_options", "true", 0);
	AVInputFormat *iformat = av_find_input_format("dshow");
	printf("Device Option Info======\n");
	avformat_open_input(&pFormatCtx, "video=Integrated Camera", iformat, &options);
	printf("========================\n");
}

//Show VFW Device
void show_vfw_device() {
	AVFormatContext *pFormatCtx = avformat_alloc_context();
	AVInputFormat *iformat = av_find_input_format("vfwcap");
	printf("VFW Device Info======\n");
	avformat_open_input(&pFormatCtx, "list", iformat, NULL);
	printf("=====================\n");
}



//SDL线程函数
int video_refresh_thread(void *data) {
	thread_exit = 0;
	thread_pause = 0;

	while (!thread_exit) {
		if (!thread_pause) {
			SDL_Event event;
			event.type = REFRESH_EVENT;
			SDL_PushEvent(&event);// 发送刷新事件
		}
		SDL_Delay(40);
	}
	thread_exit = 0;
	thread_pause = 0;
	//Break
	SDL_Event event;
	event.type = BREAK_EVENT;
	SDL_PushEvent(&event);

	return 0;
}

int main(int argc, char *argv[])
{
	AVFormatContext	*pFormatCtx;
	int				i, videoindex;
	AVCodecContext	*pCodecCtx;
	AVCodec			*pCodec;
	int ret;

	//SDL相关变量
	int w_width = 640;	//默认窗口大小
	int w_height = 480;
	SDL_Rect rect;
	Uint32 pixformat;
	SDL_Window *win = NULL;
	SDL_Renderer *renderer = NULL;
	SDL_Texture *texture = NULL;

	//1、首先ffmpeg的gdigrab解封装器对desktop进行解码获取对应的视频流
	//1.1、注册设备
	av_register_all();
	avformat_network_init();
	avdevice_register_all();//添加一个avdevice设备注册

	//如果命令行运行过则不需要执行也没有问题
	Show Dshow Device
	//show_dshow_device();
	Show Device Options
	//show_dshow_device_option();
	Show VFW Options
	//show_vfw_device();

	//1.2、获取gdigrab的desktop的封装器上下文
	pFormatCtx = avformat_alloc_context();

#if USE_DSHOW
	AVInputFormat *ifmt = av_find_input_format("dshow");
	//Set own video device's name
	if (avformat_open_input(&pFormatCtx, "video=Integrated Camera", ifmt, NULL) != 0) {
		printf("Couldn't open input stream.(无法打开输入流)\n");
		return -1;
	}
#else
	AVInputFormat *ifmt = av_find_input_format("vfwcap");
	if (avformat_open_input(&pFormatCtx, "0", ifmt, NULL) != 0) {
		printf("Couldn't open input stream.(无法打开输入流)\n");
		return -1;
	}
#endif

	//1.3、得到视频流的ID
	if (avformat_find_stream_info(pFormatCtx, NULL)<0)//有时候要调用一下 不然一些数据可能未读取到
	{
		printf("Couldn't find stream information.(无法获取流信息)\n");
		return -1;
	}
	videoindex = -1;
	videoindex = av_find_best_stream(pFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);//直接获取 未使用循环
	if (videoindex == -1)
	{
		printf("Didn't find a video stream.(没有找到视频流)\n");
		return -1;
	}
	//1.4、获取打开对应视频流的解码器和解码器上下文
	AVCodec *vcodec = avcodec_find_decoder(pFormatCtx->streams[videoindex]->codecpar->codec_id);
	if (!vcodec)
	{
		cout << "Codec not found.(没有找到解码器" << endl;
		return -1;
	}
	pCodecCtx = avcodec_alloc_context3(vcodec);
	avcodec_parameters_to_context(pCodecCtx, pFormatCtx->streams[videoindex]->codecpar);
	pCodecCtx->thread_count = 8;
	ret = avcodec_open2(pCodecCtx, NULL, 0);
	if (ret != 0)
	{
		cout << "Could not open codec.(无法打开解码器" << endl;
		return -1;
	}
	//avformat_find_stream_info(pFormatCtx, NULL);
	//2、SDL显示相关初始化
	//2.1、初始化init
	if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
		SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Could not initialize SDL - %s\n", SDL_GetError());
		return ret;
	}

	//2.2、创建窗口
	w_width = pFormatCtx->streams[videoindex]->codecpar->width;//关于屏幕这里还不知道怎么设置屏幕大小
	w_height = pFormatCtx->streams[videoindex]->codecpar->height;
	win = SDL_CreateWindow("Media Player",
		SDL_WINDOWPOS_UNDEFINED,
		SDL_WINDOWPOS_UNDEFINED,
		w_width, w_height,
		SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
	if (!win) {
		SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create window by SDL");
		//要释放ffmpeg的相关内存
		return -1;
	}

	//2.3、创建渲染器
	renderer = SDL_CreateRenderer(win, -1, 0);
	if (!renderer) {
		SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create Renderer by SDL");
		//要释放ffmpeg的相关内存
		return -1;
	}
	//2.4、创建纹理
	pixformat = SDL_PIXELFORMAT_IYUV;//YUV格式			 
	texture = SDL_CreateTexture(renderer,
		pixformat,
		SDL_TEXTUREACCESS_STREAMING,
		w_width,
		w_height);
	
	//3、读取编解码数据并SDL显示
	//3.1、创建SDL管理线程
	SDL_Event event;
	SDL_CreateThread(video_refresh_thread, "Video Thread", NULL);
	//3.2、播放
	//3.2.1、定义AVPacket、AVFrame设置纹理显示的位置,宽高
	AVPacket *packet = av_packet_alloc();
	AVFrame *pFrame = NULL;
	AVFrame	*pFrameYUV = NULL;
	pFrameYUV = av_frame_alloc();

	//3.2.2、定义存储转换后一帧像素数据缓冲区
	//这一步十分重要 之前知道
	//后面使用sws_scale 需要内存和指定每行大小存放 使用avpicture_fill就可以不用自定义计算了
	pFrame = av_frame_alloc();
	uint8_t *out_buffer = (uint8_t *)av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height));
	//avpicture_fill((AVPicture *)pFrameYUV, out_buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);  新版本被遗弃2
	//存储一帧像素数据缓冲区
	av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, out_buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1);

	rect.x = 0;
	rect.y = 0;
	rect.w = w_width;
	rect.h = w_height;
	//3.2.3、sws进行视频尺寸像素转换定义
	//这一步十分重要 之前知道  解码出来的进行SDL显示 像素格式需要转换,否则就是绿屏
	struct SwsContext *img_convert_ctx = NULL;
	img_convert_ctx = sws_getCachedContext(img_convert_ctx, pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);

	//写入文件中
	//FILE *fp_yuv=fopen("output.yuv","wb+");

	//3.2.4、循环播放
	for (;;) 
	{
		SDL_WaitEvent(&event);//使用时间驱动,每40ms执行一次 等SDL_PushEvent
		 //是刷新事件REFRESH_EVENT才进入
		if (event.type == REFRESH_EVENT)
		{
			//预处理一下
			while (1) {
				if (av_read_frame(pFormatCtx, packet) < 0)//读完了
					thread_exit = 1;

				if (packet->stream_index == videoindex)
					break;
			}
			//接收的包是视频包则进入SDL显示
			if (packet->stream_index == videoindex) {
				avcodec_send_packet(pCodecCtx, packet);//发送packet到解码线程 返回多帧
				while (avcodec_receive_frame(pCodecCtx, pFrame) == 0) {//循环读取解码返回的帧

					sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);

					/*
					//写入文件中
						int y_size=pCodecCtx->width*pCodecCtx->height;    
						fwrite(pFrameYUV->data[0],1,y_size,fp_yuv);    //Y   
						fwrite(pFrameYUV->data[1],1,y_size/4,fp_yuv);  //U  
						fwrite(pFrameYUV->data[2],1,y_size/4,fp_yuv);  //V  
					*/
				
					//将返回的数据更新到SDL纹理当中
					SDL_UpdateYUVTexture(texture, NULL,
						pFrameYUV->data[0], pFrameYUV->linesize[0],
						pFrameYUV->data[1], pFrameYUV->linesize[1],
						pFrameYUV->data[2], pFrameYUV->linesize[2]);

					//进行SDL刷新显示
					SDL_RenderClear(renderer);
					SDL_RenderCopy(renderer, texture, NULL, &rect);
					SDL_RenderPresent(renderer);
				}
				av_packet_unref(packet);
			}
		}
		else if (event.type == SDL_KEYDOWN) 
		{
			if (event.key.keysym.sym == SDLK_SPACE) { //空格键暂停
				thread_pause = !thread_pause;
			}
			if (event.key.keysym.sym == SDLK_ESCAPE) { // ESC键退出
				thread_exit = 1;
			}
		}
		else if (event.type == SDL_QUIT) 
		{
			thread_exit = 1;
		}
		else if (event.type == BREAK_EVENT) 
		{
			break;
		}
	}
	//4、释放内存
	if (pFrame)
	{
		av_frame_free(&pFrame);
	}
	if (pFrameYUV)
	{
		av_frame_free(&pFrameYUV);
	}
	if (packet)
	{
		av_packet_free(&packet);
	}
	// Close the codec
	if (pCodecCtx)
	{
		avcodec_close(pCodecCtx);
	}
	// Close the video file
	if (pFormatCtx) 
	{
		avformat_close_input(&pFormatCtx);
	}
	if (win) 
	{
		SDL_DestroyWindow(win);
	}
	if (renderer) 
	{
		SDL_DestroyRenderer(renderer);
	}
	if (texture) 
	{
		SDL_DestroyTexture(texture);
	}

	SDL_Quit();

	return 0;
}

工程链接地址GIT地址

雷神的工程代码另存
链接:https://pan.baidu.com/s/16p7_aDmRK69eZi0NfzXryg
提取码:t096
复制这段内容后打开百度网盘手机App,操作更方便哦

参与评论 您还未登录,请先 登录 后发表或查看评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页

打赏作者

卖酒的小码农

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值