ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

ffmpeg录屏

2022-03-21 19:03:14  阅读:187  来源: 互联网

标签:ffmpeg int ctx 录屏 enCodecCtx av qDebug include


qt + ffmpeg屏幕录制软件

完整工程链接:https://download.csdn.net/download/weixin_42538789/85013858
测试代码

#include <QtCore/QCoreApplication>
#include "screencapture.h"

#include <iostream>

using namespace std;

int main(int argc, char *argv[])
{
	QCoreApplication a(argc, argv);
	ScreenCapture* capture = new ScreenCapture();
	capture->start();
	while (true)
	{
		string str;
		cin >> str;
		if (str == "q")
		{
			capture->setStop(true);
			break;
		}
	}
	return a.exec();
}

头文件screencapture.h

#ifndef SCREEN_CAPTURE_H
#define SCREEN_CAPTURE_H

#include <QThread>
#include <QTcpSocket>

extern "C"
{
#include "libavformat/avformat.h"
#include "libavutil/ffversion.h"
#include "libavcodec/avcodec.h"
#include "libswscale/swscale.h"
#include "libavutil/imgutils.h"
#include "libavdevice/avdevice.h"
}

class ScreenCapture : public QThread
{
    Q_OBJECT
public:
    explicit ScreenCapture(QWidget *parent = 0, QString savefile = "record.mp4");

protected:
    void run();

private:
    AVFrame* avFrameRgb;                //解码帧对象RGB
    AVFrame* avFrameYuv;                //解码帧对象YUV
    AVStream* in_stream;                //输入视频流
    AVStream* out_stream;               //输出视频流
    AVCodecContext* deCodecCtx;         //解码器上下文
    AVCodecContext* enCodecCtx;         //解码器上下文
    SwsContext* swsContextYuvtoRgb;     //格式转换上下文(YuvtoRgb)
    SwsContext* swsContextRgbtoYuv;     //格式转换上下文(RgbtoYuv)
    AVFormatContext *ifmt_ctx;          //输入格式对象
    AVFormatContext *ofmt_ctx;          //输出格式对象
    AVOutputFormat *ofmt;               //输出格式
    AVPacket *avDePacket;               //包对象
    AVPacket* avEnPacket;               //包对象
    AVPixelFormat srcFormat;            //像素格式
    AVPixelFormat dstFormat;            //像素格式

    QString url;                        //视频流地址
    QString fileName;                   //保存文件名称
    bool stop;                          //线程停止标志
    int videoStreamIndex;               //视频流索引
    int videoWidth;                     //视频宽度
    int videoHeight;                    //视频高度
    int videoFps;                       //视频帧率
    int videoIndex;                     //视频索引
	uint64_t frameCount;				//帧计数

private:
    bool  intiDeContext();
    bool  initDecoder();
    bool  initEncoder();
    bool  initOutputStream();
    bool  initSwsContext();
    void  memoryAlloc();
    bool  initmodules();
	
signals:
	void initFinished();

public:
	void setStop(bool stop);
};

#endif // SCREEN_CAPTURE_H

源文件screencapture.cpp

#include <QDateTime>
#include <QThread>
#include <QTimer>
#include <QDebug>
#include "screencapture.h"

#define TIMEMS      qPrintable(QTime::currentTime().toString("HH:mm:ss zzz"))

ScreenCapture::ScreenCapture(QWidget* parent, QString savefile) : QThread()
{
	stop = false;
	fileName = savefile;
	frameCount = 0;
}

bool ScreenCapture::initmodules()
{
	/* 各个模块的初始化顺序不能变 */
	if (!intiDeContext())     return false;
	if (!initDecoder())       return false;
	if (!initEncoder())       return false;
	if (!initOutputStream())  return false;
	if (!initSwsContext())    return false;

	memoryAlloc();
	return true;
}

void ScreenCapture::run()
{
	int err = 0;
	if (!initmodules())
	{
		return;
	}
	qDebug() << TIMEMS << "start capture screen ...";
	while (!stop)
	{
		if ((av_read_frame(ifmt_ctx, avDePacket)) >= 0)
		{
			err = 0;
			int index = avDePacket->stream_index;
			in_stream = ifmt_ctx->streams[index];

			if (index == videoStreamIndex) 
			{
				int frameFinish = 0;
				avcodec_decode_video2(deCodecCtx, avFrameRgb, &frameFinish, avDePacket);
				sws_scale(swsContextRgbtoYuv, (const uint8_t* const*)avFrameRgb->data, avFrameRgb->linesize, \
					0, videoHeight, avFrameYuv->data, avFrameYuv->linesize);

				int base = 90000 / videoFps;
				avFrameYuv->pts = frameCount++;
				frameCount = frameCount + base;
				/* 这里设定每隔一秒钟打印一次帧号 */
				if (frameCount / base % videoFps == 0)
				{
					qDebug() << "framCount = " << frameCount / base;
				}

				int ret = avcodec_send_frame(enCodecCtx, avFrameYuv);
				if (ret != 0)
				{
					qDebug() << "avcodec_send_frame failed.";
					continue;
				}
				ret = avcodec_receive_packet(enCodecCtx, avEnPacket);
				if (ret != 0)
				{
					qDebug() << "avcodec_receive_packet failed.";
					continue;
				}
				ret = av_write_frame(ofmt_ctx, avEnPacket);
				if (ret < 0)
				{
					av_packet_unref(avEnPacket);
					av_freep(avEnPacket);
					qDebug() << "av_write_frame failed.";
					continue;
				}
			}
			av_packet_unref(avDePacket);
			av_freep(avDePacket);
		}
		else
		{
			qDebug() << "av_read_frame failed";
			av_packet_unref(avDePacket);
			av_freep(avDePacket);
			err++;
			/* 连续N次读取失败则跳出循环 */
			if (err > 3)
			{
				err = 0;
				break;
			}
		}
	}
	av_write_trailer(ofmt_ctx);
	/* close output */
	if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE))
	{
		avio_close(ofmt_ctx->pb);
	}
	avformat_close_input(&ifmt_ctx);
	avformat_free_context(ofmt_ctx);
	avformat_free_context(ifmt_ctx);

	qDebug() << TIMEMS << "stop ffmpeg thread";
}

bool ScreenCapture::intiDeContext()
{
	av_register_all();
	/* 注册编解码器 */
	avcodec_register_all();
	avdevice_register_all();
	AVDictionary* options = NULL;

	/* 指定录屏的分辨率 */
	av_dict_set(&options, "video_size", "1920x1080", 0);
	av_dict_set(&options, "pix_fmt", "yuv420p", 0);

	ifmt_ctx = avformat_alloc_context();
	if (!ifmt_ctx)
	{
		qDebug() << "avformat_alloc_context failed.";
		return false;
	}
	AVInputFormat* ifmt = av_find_input_format("gdigrab");
	if (avformat_open_input(&ifmt_ctx, "desktop", ifmt, &options) != 0) {
		qDebug() << TIMEMS << "open input error" << url;
		return false;
	}

	/* 释放设置参数 */
	if (options != NULL) {
		av_dict_free(&options);
	}
	/* 获取流信息 */
	bool result = avformat_find_stream_info(ifmt_ctx, NULL);
	if (result < 0) {
		qDebug() << TIMEMS << "find stream info error";
		return false;
	}

	AVCodec* videoDecoder = NULL;
	videoStreamIndex = -1;

	videoStreamIndex = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &videoDecoder, 0);
	if (videoStreamIndex < 0) {
		qDebug() << TIMEMS << "find video stream index error";
		return false;
	}
	/* 获取输入视频流 */
	in_stream = ifmt_ctx->streams[videoStreamIndex];
	if (!in_stream)
	{
		qDebug() << "Failed get input stream.";
		return false;
	}

	/* 获取分辨率大小 */
	videoWidth = in_stream->codec->width;
	videoHeight = in_stream->codec->height;

	/* 如果没有获取到宽高则返回 */
	if (videoWidth == 0 || videoHeight == 0) {
		qDebug() << TIMEMS << "find width height error";
		avformat_free_context(ofmt_ctx);
		avformat_free_context(ifmt_ctx);
		return false;
	}

	/* 获取视频流的帧率 fps,要对0进行过滤 */
	int num = in_stream->codec->framerate.num;
	int den = in_stream->codec->framerate.den;
	if (num != 0 && den != 0) {
		videoFps = num / den;
	}

	QString videoInfo = QString("保存视频流信息 -> 索引: %1  解码: %2  格式: %3  时长: %4 秒  fps: %5  分辨率: %6*%7  rtsp地址: %8")
		.arg(videoStreamIndex).arg(videoDecoder->name).arg(ifmt_ctx->iformat->name)
		.arg((ifmt_ctx->duration) / 1000000).arg(videoFps).arg(videoWidth).arg(videoHeight).arg(url);
	qDebug() << TIMEMS << videoInfo;
	return true;
}

bool ScreenCapture::initDecoder()
{
	AVCodec* deCodec = NULL;
	deCodecCtx = in_stream->codec;
	deCodec = avcodec_find_decoder(deCodecCtx->codec_id);

	int result = avcodec_open2(deCodecCtx, deCodec, NULL);
	if (result < 0) {
		qDebug() << TIMEMS << "open video codec error :" << result;
		return false;
	}

	return true;
}

void ScreenCapture::memoryAlloc()
{
	//分配AVFram及像素存储空间
	avFrameRgb = av_frame_alloc();
	avFrameRgb->format = srcFormat;
	avFrameRgb->width = videoWidth;
	avFrameRgb->height = videoHeight;
	av_frame_get_buffer(avFrameRgb, 32);

	avFrameYuv = av_frame_alloc();
	avFrameYuv->format = dstFormat;
	avFrameYuv->width = videoWidth;
	avFrameYuv->height = videoHeight;
	av_frame_get_buffer(avFrameYuv, 32);

	avEnPacket = av_packet_alloc();
	av_init_packet(avEnPacket);
	avDePacket = av_packet_alloc();
	av_init_packet(avDePacket);
}

bool ScreenCapture::initSwsContext()
{
	/* 定义像素格式 */
	srcFormat = AV_PIX_FMT_BGRA;
	dstFormat = AV_PIX_FMT_YUV420P;
	/* 默认最快速度的解码采用的SWS_FAST_BILINEAR参数 */
	int flags = SWS_FAST_BILINEAR;
	/* 格式转换上下文(RgbtoYuv) */
	swsContextRgbtoYuv = sws_getContext(videoWidth, videoHeight, srcFormat, \
		videoWidth, videoHeight, dstFormat, \
		flags, NULL, NULL, NULL);

	return true;
}

bool ScreenCapture::initOutputStream()
{
	/* 设置输出封装格式上下文 */
	avformat_alloc_output_context2(&ofmt_ctx, 0, "mpegts", fileName.toLatin1().data());
	ofmt = ofmt_ctx->oformat;
	/* 封装格式上下文中增加视频流信息 */
	out_stream = avformat_new_stream(ofmt_ctx, NULL);
	/* 设置封装器参数 */
	out_stream->id = 0;
	out_stream->codecpar->codec_tag = 0;
	avcodec_parameters_from_context(out_stream->codecpar, enCodecCtx);
	av_dump_format(ofmt_ctx, 0, fileName.toLatin1().data(), 1);

	/* 写MP4头 */
	avio_open(&ofmt_ctx->pb, fileName.toLatin1().data(), AVIO_FLAG_WRITE);
	int ret = avformat_write_header(ofmt_ctx, NULL);
	if (ret < 0)
	{
		qDebug() << "Failed to avformat_write_header.";
		return false;
	}

	return true;
}

bool ScreenCapture::initEncoder()
{
	/* 找到编码器对象 */
	AVCodec* encodec = avcodec_find_encoder(AV_CODEC_ID_H264);
	if (!encodec)
	{
		qDebug() << "Failed to find encoder.";
		return false;
	}
	/* 分配编码器上下文 */
	enCodecCtx = avcodec_alloc_context3(encodec);
	if (!enCodecCtx)
	{
		qDebug() << "Failed to alloc context3.";
		return false;
	}
	/* 设置编码器参数 */
	enCodecCtx->bit_rate = 400000;
	enCodecCtx->width = videoWidth;
	enCodecCtx->height = videoHeight;
	enCodecCtx->time_base = { 1,videoFps };
	enCodecCtx->framerate = { videoFps,1 };
	enCodecCtx->gop_size = 50;
	enCodecCtx->keyint_min = 20;
	enCodecCtx->max_b_frames = 0;
	enCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
	enCodecCtx->codec_id = AV_CODEC_ID_H264;
	enCodecCtx->flags |= AV_CODEC_FLAG2_LOCAL_HEADER;
	enCodecCtx->thread_count = 8;
	/* 量化因子,范围越大,画质越差,编码速度越快 */
	enCodecCtx->qmin = 20;
	enCodecCtx->qmax = 30;

	AVDictionary* param = 0;
	av_dict_set(&param, "preset", "superfast", 0);
	av_dict_set(&param, "tune", "zerolatency", 0);

	/* 打开编码器 */
	int ret = avcodec_open2(enCodecCtx, encodec, &param);
	if (ret < 0)
	{
		qDebug() << "Failed to open enCodecCtx.";
		return false;
	}

	qDebug() << TIMEMS;
	return true;
}

void ScreenCapture::setStop(bool stop)
{
	this->stop = stop;
}

标签:ffmpeg,int,ctx,录屏,enCodecCtx,av,qDebug,include
来源: https://blog.csdn.net/weixin_42538789/article/details/123642875

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有