整体框架:
屏幕录制、声音录制、音视频合成分别在不同的子线程中运行,由主程序控制录制的开始和结束。控制流程如下图所示:

主线程:点击开始按钮à打开音频设备、视频设备、输出文件、启动子线程à发送开始录制信号;
子线程:收到开始信号à开始录制;
主线程:点击结束按钮à发送结束信号;
子线程:接收到结束信号à发送结束信号;
主线程:已经接收到了所有子线程发来的结束信号à处理结束事项并清理资源。
主要代码:
主线程:screenrecord.h
/**
* 录屏主控类
*/
#ifndef SCREENRECORD_H
#define SCREENRECORD_H
/**QT header**/
#include <QFont>
#include <QLabel>
#include <QMutex>
#include <QDebug>
#include <QWidget>
#include <QThread>
#include <QLineEdit>
#include <QFileInfo>
#include <QPaintEvent>
#include <QPushButton>
#include <QCameraInfo>
#include <QMessageBox>
#include <QStandardPaths>
#include <QAudioDeviceInfo>
#include "myglobals.h"
#include "muxerprocess.h"
#include "screencaptureprocess.h"
#include "audiocaptureprocess.h"
class ScreenRecord : public QWidget
{
Q_OBJECT
public:
explicit ScreenRecord(QWidget *parent = nullptr);
~ScreenRecord();
public:
QMutex mutex;
/*状态*/
bool screenCapturing;
bool audioCapturing;
bool muxing;
/*合并音视频的线程及程序*/
QThread muxerThread;
MuxerProcess *muxerProcess;
/*抓取屏幕的线程及程序*/
QThread screenCaptureThread;
ScreenCaptureProcess *screenCaptureProcess;
/*录制声音的线程及程序*/
QThread audioCaptureThread;
AudioCaptureProcess *audioCaptureProcess;
/*开始及结束录屏的按钮*/
QPushButton *startButton;
/*转换后的视频帧尺寸(由用户设置,宽度必须是4的整数倍)*/
int outputWidth;
int outputHeight;
/*视频输出路径/文件名称/文件类型*/
QString outputDirectory;
QString outputFilename;
QString outputFileType;
/*********控件***********/
//视频输出路径
QLabel *outputDirLabel;
QLineEdit *outputDirEdit;
QPushButton *outputDirButton;
/*********控件***********/
public slots:
void replyButtonPressed();
void replyScreenCapturingFinished();
void replyAudioCapturingFinished();
void replyMuxingFinished();
signals:
void startRecord(); //开始录屏信号
void stopRecord(); //结束录屏信号
public:
/**
* @brief 打开屏幕抓取设备(gdigrab)
* @return
* 0 -- success,else error code
*
*/
int openScreenCaptureDevice();
/**
* @brief 打开声音抓取设备(dshow)
* @return
* 0 -- success,else error code
*
*/
int openAudioCaptureDevice();
/*打开输出视频文件*/
int openOutput();
/*获取电脑内置的声音设备*/
QString getSpeakerDeviceName();
/*释放全局资源*/
void releaseGlobals();
/*结束所有子线程*/
void stopChildThreads();
/*开始所有的子线程*/
void startChildThreads();
protected:
void paintEvent(QPaintEvent *event);
};
#endif // SCREENRECORD_H
主线程:screenrecord.cpp
#include "screencaptureprocess.h"
ScreenCaptureProcess::ScreenCaptureProcess(QObject *parent)
: QObject{parent}
{
capturing = false;
videoFrame = nullptr;
videoYUVFrame = nullptr;
videoYUVBuffer = nullptr;
}
void ScreenCaptureProcess::start()
{
qDebug()<<"录屏子线程开始";
capturing = true;
captureScreen();
}
void ScreenCaptureProcess::stop()
{
qDebug()<<"ScreenCapture收到停止信号";
capturing = false;
}
void ScreenCaptureProcess::release()
{
if (videoFrame != nullptr)
av_frame_free(&videoFrame);
if (videoYUVFrame != nullptr)
av_frame_free(&videoYUVFrame);
if (videoYUVBuffer != nullptr)
av_freep(&videoYUVBuffer);
}
void ScreenCaptureProcess::captureScreen()
{
int ret = 0;
int number = 0;
int _lineSize = 0;
if (videoFrame == nullptr)
videoFrame = av_frame_alloc();
if (videoYUVFrame == nullptr)
videoYUVFrame = av_frame_alloc();
if (videoYUVBuffer == nullptr)
videoYUVBuffer = (uint8_t *)av_malloc(videoFrameSize);
//帧尺寸输出直接使用输入的尺寸
av_image_fill_arrays(videoYUVFrame->data,videoYUVFrame->linesize,videoYUVBuffer,AV_PIX_FMT_YUV420P, sourceWidth, sourceHeight,1);
_lineSize = sourceWidth * sourceHeight;
AVPacket packet,outPacket;
av_init_packet(&packet);
av_init_packet(&outPacket);
while (1){
/*接收到主程序的停止信号,如果全局recording还是true,那么发送录屏结束信号,
* 主程序接收到此信号后才能清理分配的资源
*/
if (!capturing){
if (recording)
emit(capturingfinished());
break;
}
av_packet_unref(&packet);
av_packet_unref(&outPacket);
if (av_read_frame(videoSourceFormatContext,&packet) < 0)
continue;
ret = avcodec_send_packet(videoSourceCodecContext,&packet);
if (ret >= 0){
ret = avcodec_receive_frame(videoSourceCodecContext,videoFrame);
if (ret == EAGAIN)
continue;
else if (ret == AVERROR_EOF)
break;
else if (ret < 0)
qDebug()<<"Error during decoding";
}
/*数据格式转化为AV_PIX_FMT_YUV420P,尺寸与源尺寸一致*/
sws_scale(videoSwsContext, (const uint8_t* const*)videoFrame->data, videoFrame->linesize, 0, sourceHeight,videoYUVFrame->data,videoYUVFrame->linesize);
mutex.lock();
/*写入帧缓存*/
if (av_fifo_space(videoFifoBuffer) > videoFrameSize){
av_fifo_generic_write(videoFifoBuffer, videoYUVFrame->data[0], _lineSize, NULL);
av_fifo_generic_write(videoFifoBuffer, videoYUVFrame->data[1], _lineSize / 4, NULL);
av_fifo_generic_write(videoFifoBuffer, videoYUVFrame->data[2], _lineSize / 4, NULL);
}
mutex.unlock();
number ++;
QCoreApplication::processEvents();
}
}
声音录制函数:void captureAudio();
void AudioCaptureProcess::captureAudio()
{
int ret = -1;
//编码器的frame size
int _sampleNumber = audioEncodeFrameSize;
if (_sampleNumber == 0)
_sampleNumber = 1024;
int dstNbSamples, maxDstNbSamples;
maxDstNbSamples = dstNbSamples = av_rescale_rnd(_sampleNumber,
audioEncodeCodecContext->sample_rate,
audioSourceCodecContext->sample_rate,
AV_ROUND_UP);
AVPacket packet;
av_init_packet(&packet);
AVFrame *_rawFrame = av_frame_alloc();
AVFrame *_newFrame = AllocAudioFrame(audioEncodeCodecContext, _sampleNumber);
while (1){
if (av_read_frame(audioSourceFormatContext,&packet) < 0){
av_packet_unref(&packet);
qDebug()<<"不能读取音频信号:av_read_frame";
continue;
}
if (packet.stream_index != 0){
av_packet_unref(&packet);
qDebug()<<"读取的音频信号不是正确的:av_read_frame";
continue;
}
ret = avcodec_send_packet(audioSourceCodecContext,&packet);
if (ret != 0){
av_packet_unref(&packet);
continue;
}
ret = avcodec_receive_frame(audioSourceCodecContext,_rawFrame);
if (ret != 0){
av_packet_unref(&packet);
continue;
}
dstNbSamples = av_rescale_rnd(swr_get_delay(audioSwrContext, audioSourceCodecContext->sample_rate) + _rawFrame->nb_samples,
audioEncodeCodecContext->sample_rate, audioSourceCodecContext->sample_rate, AV_ROUND_UP);
if (dstNbSamples > maxDstNbSamples)
{
av_freep(&_newFrame->data[0]);
//nb_samples*nb_channels*Bytes_sample_fmt
ret = av_samples_alloc(_newFrame->data, _newFrame->linesize, audioEncodeCodecContext->channels,
dstNbSamples, audioEncodeCodecContext->sample_fmt, 1);
if (ret < 0){
qDebug() << "av_samples_alloc failed";
return;
}
maxDstNbSamples = dstNbSamples;
audioEncodeCodecContext->frame_size = dstNbSamples;
audioEncodeFrameSize = _newFrame->nb_samples;
}
//为音频分配内存(音频还未转换)
if (audioFifoBuffer == nullptr)
audioFifoBuffer = av_audio_fifo_alloc(audioEncodeCodecContext->sample_fmt,
audioEncodeCodecContext->channels,
30 * _sampleNumber);
_newFrame->nb_samples = swr_convert(audioSwrContext, _newFrame->data, dstNbSamples,
(const uint8_t **)_rawFrame->data,
_rawFrame->nb_samples);
if (_newFrame->nb_samples < 0){
qDebug() << "audio swr_convert failed";
return;
}
int _space = av_audio_fifo_space(audioFifoBuffer);
if (_space >= _newFrame->nb_samples){
ret = av_audio_fifo_write(audioFifoBuffer, (void **)_newFrame->data, _newFrame->nb_samples);
if (ret < _newFrame->nb_samples){
qDebug() << "av_audio_fifo_write error!";
return;
}
}
av_packet_unref(&packet);
QCoreApplication::processEvents();
}
av_frame_free(&_rawFrame);
av_frame_free(&_newFrame);
}
屏幕抓取函数:void captureScreen();
void captureScreen()
{
int ret = 0;
int number = 0;
int _lineSize = 0;
if (videoFrame == nullptr)
videoFrame = av_frame_alloc();
if (videoYUVFrame == nullptr)
videoYUVFrame = av_frame_alloc();
if (videoYUVBuffer == nullptr)
videoYUVBuffer = (uint8_t *)av_malloc(videoFrameSize);
//帧尺寸输出直接使用输入的尺寸
av_image_fill_arrays(videoYUVFrame->data,videoYUVFrame->linesize,videoYUVBuffer,AV_PIX_FMT_YUV420P, sourceWidth, sourceHeight,1);
_lineSize = sourceWidth * sourceHeight;
AVPacket packet,outPacket;
av_init_packet(&packet);
av_init_packet(&outPacket);
while (1){
av_packet_unref(&packet);
av_packet_unref(&outPacket);
if (av_read_frame(videoSourceFormatContext,&packet) < 0)
continue;
ret = avcodec_send_packet(videoSourceCodecContext,&packet);
if (ret >= 0){
ret = avcodec_receive_frame(videoSourceCodecContext,videoFrame);
if (ret == EAGAIN)
continue;
else if (ret == AVERROR_EOF)
break;
else if (ret < 0)
qDebug()<<"Error during decoding";
}
/*数据格式转化为AV_PIX_FMT_YUV420P,尺寸与源尺寸一致*/
sws_scale(videoSwsContext, (const uint8_t* const*)videoFrame->data, videoFrame->linesize, 0, sourceHeight,videoYUVFrame->data,videoYUVFrame->linesize);
/*写入帧缓存*/
if (av_fifo_space(videoFifoBuffer) > videoFrameSize){
av_fifo_generic_write(videoFifoBuffer, videoYUVFrame->data[0], _lineSize, NULL);
av_fifo_generic_write(videoFifoBuffer, videoYUVFrame->data[1], _lineSize / 4, NULL);
av_fifo_generic_write(videoFifoBuffer, videoYUVFrame->data[2], _lineSize / 4, NULL);
}
number ++;
}
}
音视频合并函数:void mux();
void MuxerProcess::mux()
{
int _cts = -1;
AVFrame *_yuvFrame = av_frame_alloc();
uint8_t *_yuvBuffer = (uint8_t *)av_malloc(videoFrameSize);
av_image_fill_arrays(_yuvFrame->data, _yuvFrame->linesize, _yuvBuffer, AV_PIX_FMT_YUV420P, sourceWidth, sourceHeight, 1);
AVPacket packet;
av_init_packet(&packet);
while(1){
/*音频缓存是在抓取开始后才创建*/
if (audioFifoBuffer == nullptr || videoFifoBuffer == nullptr){
qDebug()<<"音频或者视频缓存还没分配:"<<audioFifoBuffer<<videoFifoBuffer;
continue;
}
//比较音频和视频流的播放时间
_cts = av_compare_ts(videoCurrentPTS,outputFormatContext->streams[videoOutputStreamIndex]->time_base,
audioCurrentPTS,outputFormatContext->streams[audioOutputStreamIndex]->time_base);
/*如果视频流的播放时间小于音频流播放时间*/
if (_cts <= 0){
if (av_fifo_size(videoFifoBuffer) >= videoFrameSize){
mutex.lock();
//从缓存中读出数据
av_fifo_generic_read(videoFifoBuffer, _yuvBuffer, videoFrameSize, NULL);
mutex.unlock();
//计算时间
packet.pts = videoFrameIndex;
packet.dts = videoFrameIndex;
av_packet_rescale_ts(&packet, videoSourceCodecContext->time_base, outputFormatContext->streams[0]->time_base);
//设置frame参数
_yuvFrame->pts = packet.pts;
_yuvFrame->pkt_dts = packet.pts;
_yuvFrame->width = sourceWidth;
_yuvFrame->height = sourceHeight;
_yuvFrame->format = AV_PIX_FMT_YUV420P;
videoCurrentPTS = packet.pts;
//释放packet
av_packet_unref(&packet);
avcodec_send_frame(videoEncodeCodecContext,_yuvFrame);
avcodec_receive_packet(videoEncodeCodecContext, &packet);
av_interleaved_write_frame(outputFormatContext, &packet);
avio_flush(outputFormatContext->pb);
videoFrameIndex ++;
}
}
else{
if (av_audio_fifo_size(audioFifoBuffer) >= audioEncodeFrameSize ){
AVFrame *frame_mic = av_frame_alloc();
frame_mic->nb_samples = audioEncodeFrameSize;
frame_mic->channel_layout = audioEncodeCodecContext->channel_layout;
frame_mic->format = audioEncodeCodecContext->sample_fmt;
frame_mic->sample_rate = audioEncodeCodecContext->sample_rate;
frame_mic->pts = audioFrameIndex * audioEncodeFrameSize;
audioFrameIndex ++;
int ret = av_frame_get_buffer(frame_mic, 0);
if (ret < 0){
qDebug()<<"av_frame_get_buffer错误!"<<getErrorMessage(ret);
break;
}
mutex.lock();
ret = av_audio_fifo_read(audioFifoBuffer, (void **)frame_mic->data,audioEncodeFrameSize);
mutex.unlock();
if (ret < 0){
qDebug()<<"不能从音频流读取数据:"<<getErrorMessage(ret)<<ret;
break;
}
AVPacket pkt_out_mic;
av_init_packet(&pkt_out_mic);
ret = avcodec_send_frame(audioEncodeCodecContext, frame_mic);
if (ret != 0){
av_frame_free(&frame_mic);
av_packet_unref(&pkt_out_mic);
continue;
}
ret = avcodec_receive_packet(audioEncodeCodecContext, &pkt_out_mic);
if (ret != 0 ){
av_frame_free(&frame_mic);
av_packet_unref(&pkt_out_mic);
continue;
}
pkt_out_mic.stream_index = audioOutputStreamIndex;
audioCurrentPTS = pkt_out_mic.pts;
ret = av_interleaved_write_frame(outputFormatContext, &pkt_out_mic);
if (ret < 0){
qDebug()<<"音频编码错误!av_interleaved_write_frame";
continue;
}
av_frame_free(&frame_mic);
av_packet_unref(&pkt_out_mic);
}
}
}
av_frame_free(&_yuvFrame);
}
完整代码下载:
为了将Cucumber用于命令行脚本,我按照提供的说明安装了arubagem。它在我的Gemfile中,我可以验证是否安装了正确的版本并且我已经包含了require'aruba/cucumber'在'features/env.rb'中为了确保它能正常工作,我写了以下场景:@announceScenario:Testingcucumber/arubaGivenablankslateThentheoutputfrom"ls-la"shouldcontain"drw"假设事情应该失败。它确实失败了,但失败的原因是错误的:@announceScenario:Testingcucumber/ar
我有一个包含多个键的散列和一个字符串,该字符串不包含散列中的任何键或包含一个键。h={"k1"=>"v1","k2"=>"v2","k3"=>"v3"}s="thisisanexamplestringthatmightoccurwithakeysomewhereinthestringk1(withspecialcharacterslike(^&*$#@!^&&*))"检查s是否包含h中的任何键的最佳方法是什么,如果包含,则返回它包含的键的值?例如,对于上面的h和s的例子,输出应该是v1。编辑:只有字符串是用户定义的。哈希将始终相同。 最佳答案
相信很多人在录制视频的时候都会遇到各种各样的问题,比如录制的视频没有声音。屏幕录制为什么没声音?今天小编就和大家分享一下如何录制音画同步视频的具体操作方法。如果你有录制的视频没有声音,你可以试试这个方法。 一、检查是否打开电脑系统声音相信很多小伙伴在录制视频后会发现录制的视频没有声音,屏幕录制为什么没声音?如果当时没有打开音频录制,则录制好的视频是没有声音的。因此,建议在录制前进行检查。屏幕上没有声音,很可能是因为你的电脑系统的声音被禁止了。您只需打开电脑系统的声音,即可录制音频和图画同步视频。操作方法:步骤1:点击电脑屏幕右下侧的“小喇叭”图案,在上方的选项中,选择“声音”。 步骤2:在“声
在前面两节的例子中,主界面窗口的尺寸和标签控件显示的矩形区域等,都是用C++代码编写的。窗口和控件的尺寸都是预估的,控件如果多起来,那就不好估计每个控件合适的位置和大小了。用C++代码编写图形界面的问题就是不直观,因此Qt项目开发了专门的可视化图形界面编辑器——QtDesigner(Qt设计师)。通过QtDesigner就可以很方便地创建图形界面文件*.ui,然后将ui文件应用到源代码里面,做到“所见即所得”,大大方便了图形界面的设计。本节就演示一下QtDesigner的简单使用,学习拖拽控件和设置控件属性,并将ui文件应用到Qt程序代码里。使用QtDesigner设计界面在开始菜单中找到「Q
动漫制作技巧是很多新人想了解的问题,今天小编就来解答与大家分享一下动漫制作流程,为了帮助有兴趣的同学理解,大多数人会选择动漫培训机构,那么今天小编就带大家来看看动漫制作要掌握哪些技巧?一、动漫作品首先完成草图设计和原型制作。设计草图要有目的、有对象、有步骤、要形象、要简单、符合实际。设计图要一致性,以保证制作的顺利进行。二、原型制作是根据设计图纸和制作材料,可以是手绘也可以是3d软件创建。在此步骤中,要注意的问题是色彩和平面布局。三、动漫制作制作完成后,加工成型。完成不同的表现形式后,就要对设计稿进行加工处理,使加工的难易度降低,并得到一些基本准确的概念,以便于后续的大样、准确的尺寸制定。四、
2022/8/4更新支持加入水印水印必须包含透明图像,并且水印图像大小要等于原图像的大小pythonconvert_image_to_video.py-f30-mwatermark.pngim_dirout.mkv2022/6/21更新让命令行参数更加易用新的命令行使用方法pythonconvert_image_to_video.py-f30im_dirout.mkvFFMPEG命令行转换一组JPG图像到视频时,是将这组图像视为MJPG流。我需要转换一组PNG图像到视频,FFMPEG就不认了。pyav内置了ffmpeg库,不需要系统带有ffmpeg工具因此我使用ffmpeg的python包装p
Transformers开始在视频识别领域的“猪突猛进”,各种改进和魔改层出不穷。由此作者将开启VideoTransformer系列的讲解,本篇主要介绍了FBAI团队的TimeSformer,这也是第一篇使用纯Transformer结构在视频识别上的文章。如果觉得有用,就请点赞、收藏、关注!paper:https://arxiv.org/abs/2102.05095code(offical):https://github.com/facebookresearch/TimeSformeraccept:ICML2021author:FacebookAI一、前言Transformers(VIT)在图
我的Gallery模型中有以下查询:media_items.includes(:photo,:video).rank(:position_in_gallery)我的图库模型有_许多媒体项,每个都有一个照片或视频关联。到目前为止,一切正常。它返回所有media_items包括它们的photo或video关联,由media_item的position_in_gallery属性排序。但是我现在需要将此查询返回的照片限制为仅具有is_processing属性的照片,即nil。是否可以进行相同的查询,但条件是返回的照片等同于:.where(photo:'photo.is_processingIS
我正在尝试使用以下代码通过将ffmpeg实用程序作为子进程运行并获取其输出并解析它来确定视频分辨率:IO.popen'ffmpeg-i'+path_to_filedo|ffmpegIO|#myparsegoeshereend...但是ffmpeg输出仍然连接到标准输出并且ffmepgIO.readlines是空的。ffmpeg实用程序是否需要一些特殊处理?或者还有其他方法可以获得ffmpeg输出吗?我在WinXP和FedoraLinux下测试了这段代码-结果是一样的。 最佳答案 要跟进mouviciel的评论,您需要使用类似pope
-if!request.path_info.include?'A'%{:id=>'A'}"Text"-else"Text"“文本”写了两次。我怎样才能只写一次并同时检查path_info是否包含“A”? 最佳答案 有两种方法可以做到这一点。使用部分,或使用content_forblock:如果“文本”较长,或者是一个重要的子树,您可以将其提取到一个部分。这会使您的代码变干一点。在给出的示例中,这似乎有点矫枉过正。在这种情况下更好的方法是使用content_forblock,如下所示:-if!request.path_info.inc