一、前言

随着直播的兴起,采集本地摄像头和麦克风进行直播推流,也是一个刚需,最简单的做法是直接用ffmpeg命令行采集并推流,这种方式简单粗暴,但是不能实时预览画面,而且不方便加上一些特殊要求。之前就已经打通了音视频文件和视频流的采集,那是不是可以简单点的方式就能直接加入到原有的框架中呢,答案是可以的,经过一段时间的摸索,发现只要新增少量十几行的代码就行,一个是设置avformat_open_input的第三个参数,指定采集本地设备,一个是设置本地设备的文件名,桌面是gdigrab,本地摄像头是dshow,在linux上桌面是x11grab,本地摄像头是v4l2,本地麦克风是alsa。网上类似的代码也很多,这里只讲两点。

第一点是最初在同时打开本地摄像头和麦克风的时候,发现声音总是不正常,尽管深知这种思路肯定是对的,还总在转换声音那边下手,死活都不行。就在即将放弃此方案的时候,找遍了网络找相关资料,发现要通过av_dict_set(options, "audio_buffer_size", "40", 0);设置一个叫audio_buffer_size的参数值,默认这个值很大,导致声音延迟和缓存很大,所以声音画面不同步,把这个值设置小一点,性能相当好,简直完美。

第二点是在使用采集摄像头和麦肯风同步保存到MP4文件或者推流的时候,发现一个现象,随着时间的推移,音视频慢慢的不同步,音频延迟越来越大,一个是的几分钟内还是正常的,时间到了比如十分钟以后,慢慢的延迟越大,后面发现,在调用av_interleaved_write_frame函数写入数据的时候,这个函数默认会缓存,缓存av_write_frame直接立即写入,就再也没有发生过这个问题,连续测试几天稳得一逼。为了尽量提高实时性,目前的方案是直接采集到就解码并编码立即推流,音视频都是,先不做同步,通过audio_buffer_size参数的设置以及改成av_write_frame来写入,能够将实时性做到极致。如果使用ffmpeg命令行,无论如何设置参数,都做不到这个实时性。

公众号:Qt实战,各种开源作品、经验整理、项目实战技巧,专注Qt/C++软件开发,视频监控、物联网、工业控制、嵌入式软件、国产化系统应用软件开发。

公众号:Qt入门和进阶,专门介绍Qt/C++相关知识点学习,帮助Qt开发者更好的深入学习Qt。多位Qt元婴期大神,一步步带你从入门到进阶。

二、效果图

三、体验地址

  1. 国内站点:https://gitee.com/feiyangqingyun
  2. 国际站点:https://github.com/feiyangqingyun
  3. 个人作品:https://blog.csdn.net/feiyangqingyun/article/details/97565652
  4. 体验地址:https://pan.baidu.com/s/1d7TH_GEYl5nOecuNlWJJ7g 提取码:01jf 文件名:bin_video_push。
  5. 视频主页:https://space.bilibili.com/687803542

四、功能特点

  1. 支持各种本地音视频文件和网络音视频文件,格式包括mp3、aac、wav、wma、mp4、mkv、rmvb、wmv、mpg、flv、asf等。
  2. 支持各种网络音视频流,网络摄像头,协议包括rtsp、rtmp、http等。
  3. 支持本地摄像头设备推流,可指定分辨率、帧率、格式等。
  4. 支持本地桌面采集推流,可指定屏幕索引、采集区域、起始坐标、帧率等,也支持指定窗口标题进行采集。
  5. 可实时切换预览视频文件,可切换音视频文件播放进度,切换到哪里就推流到哪里。预览过程中可以切换静音状态和暂停推流。
  6. 可指定重新编码推流,任意源头格式可选强转264或265格式。
  7. 可转换分辨率推流,设置等比例缩放或者指定分辨率进行转换。
  8. 推流的清晰度、质量、码率都可调,可以节约网络带宽和拉流端的压力。
  9. 音视频文件自动循环不间断推流。
  10. 音视频流有自动掉线重连机制,重连成功自动继续推流。
  11. 支持各种流媒体服务程序,包括但不限于mediamtx、ZLMediaKit、srs、LiveQing、nginx-rtmp、EasyDarwin、ABLMediaServer。
  12. 通过配置文件自动加载对应流媒体程序的协议和端口,自动生成推流地址和各种协议的拉流地址。可以通过配置文件自己增加流媒体程序。
  13. 可选rtmp、rtmp格式推流,推流成功后,支持多种格式拉流,包括但不限于rtsp、rtmp、hls、flv、ws-flv、webrtc等。
  14. 在软件上推流成功后,可以直接单击网页预览,实时预览推流后拉流的画面,多画面网页展示。
  15. 软件界面上可单击对应按钮,动态添加文件和目录,可手动输入地址。
  16. 推拉流实时性极高,延迟极低,延迟时间大概在100ms左右。
  17. 极低CPU资源占用,4路主码流推流只需要占用0.2%CPU。理论上常规普通PC机器推100路毫无压力,主要性能瓶颈在网络。
  18. 可以推流到外网服务器,然后通过手机、电脑、平板等设备播放对应的视频流。
  19. 每路推流都可以手动指定唯一标识符(方便拉流/用户无需记忆复杂的地址),没有指定则按照策略随机生成hash值。也支持自动按照指定标识后面加数字的方式递增命名。比如设置标识为字母v,策略为标识递增,则每添加一个对应的推流码命名依次是v1、v2、v3等。
  20. 根据推流协议自动转码格式,默认策略按照选择的推流协议,比如rtsp支持265而rtmp不支持,如果是265的文件而选择rtmp推流,则自动转码成264格式再推流。
  21. 音视频同步推流,在拉流和采集的时候就会自动处理好同步,同步后的数据再推流。
  22. 表格中实时显示每一路推流的分辨率和音视频数据状态,灰色表示没有输入流,黑色表示没有输出流,绿色表示原数据推流,红色表示转码后的数据推流。
  23. 自动重连视频源,自动重连流媒体服务器,保证启动后,推流地址和打开地址都实时重连,只要恢复后立即连上继续采集和推流。
  24. 根据不同的流媒体服务器类型,自动生成对应的rtsp、rtmp、hls、flv、ws-flv、webrtc拉流地址,用户可以直接复制该地址到播放器或者网页中预览查看。
  25. 添加的推流地址等信息自动存储到文件,可以手动打开进行修改,默认启动后自动加载历史记录。
  26. 可以指定生成的网页文件保存位置,方便作为网站网页发布,可以直接在浏览器中输入网址进行访问,发布后可以直接在局域网其他设备比如手机或者电脑打开对应网址访问。
  27. 可选是否开机启动、后台运行等。网络推流添加的rtsp地址可勾选是否隐藏地址中的用户信息。
  28. 自带设备推流模块,自动识别本地设备,包括本地的摄像头和桌面,可以手动选择不同的是视频和音频采集设备进行推流。
  29. 自带文件点播模块,添加文件后用户可以拉取地址点播,用户端可以任意切换播放进度。支持各种浏览器(谷歌chromium、微软edge、火狐firefox等)、各种播放器(vlc、mpv、ffplay、potplayer、mpchc等)打开请求。
  30. 文件点播模块实时统计显示每个文件对应的访问数量、总访问数量、不同IP地址访问数量。
  31. 文件点播模块采用纯QTcpSocket通信,不依赖流媒体服务程序,核心源码不到500行,注释详细,功能完整。
  32. 支持任意Qt版本(Qt4、Qt5、Qt6),支持任意系统(windows、linux、macos、android、嵌入式linux等)。

五、相关代码

void FFmpegHelper::initOption(AVDictionary **options, const QString &mediaUrl)
{
//设置音频采集选项
if (mediaUrl.contains("audio=") && !mediaUrl.contains("audio=virtual-audio-capturer")) {
av_dict_set(options, "sample_size", "16", 0);
av_dict_set(options, "channels", "2", 0);
av_dict_set(options, "sample_rate", "44100", 0);
av_dict_set(options, "audio_buffer_size", "40", 0);
}
} void FFmpegThread::initOption()
{
//增加rtp/sdp支持/貌似网络地址带.sdp结尾的这种可以不用加
if (mediaType == MediaType_FileLocal && mediaUrl.endsWith(".sdp")) {
av_dict_set(&options, "protocol_whitelist", "file,rtp,udp", 0);
} //设置秘钥相关
FFmpegHelper::initDecryption(&options, this->property("cryptoKey").toByteArray());
//设置缓存大小/通信协议
FFmpegHelper::initOption(&options, caching, transport); //设置分辨率/帧率/桌面采集偏移值等参数
if (mediaType == MediaType_Device || mediaType == MediaType_Screen) {
FFmpegHelper::initOption(&options, mediaUrl);
FFmpegHelper::initOption(&options, bufferSize, frameRate);
if (mediaType == MediaType_Screen) {
int offsetX = this->property("offsetX").toInt();
int offsetY = this->property("offsetY").toInt();
FFmpegHelper::initOption(&options, offsetX, offsetY, mediaUrl);
}
}
} bool FFmpegThread::initInput()
{
//本地摄像头/桌面录屏/linux系统可以打开cheese程序查看本地摄像头(如果是在虚拟机中需要设置usb选项3.1)
if (mediaType == MediaType_Device) {
ifmt = av_find_input_format(mediaUrl.startsWith("audio=") ? Device_Audio : Device_Video);
} else if (mediaType == MediaType_Screen) {
ifmt = av_find_input_format(Device_Screen);
} //实例化格式处理上下文
formatCtx = avformat_alloc_context();
//设置超时回调(有些不存在的地址或者网络不好的情况下要卡很久)
formatCtx->interrupt_callback.callback = FFmpegThreadHelper::openAndReadCallBack;
formatCtx->interrupt_callback.opaque = this; //可以强制指定本地摄像头采集解码为mjpeg或者h264/找遍了网络原来是这样设置才起作用
if (mediaType == MediaType_Device) {
audioPlayer->setProperty("microphone", true);
FFmpegHelper::setVideoCodecName(formatCtx, videoCodecName);
} //取出最终的播放地址
QString url = VideoHelper::getPlayUrl(VideoCore_FFmpeg, mediaType, mediaUrl);
//桌面采集指定窗口标题需要转换才能支持中文/ffmpeg5开始才修复了这个问题
QByteArray urlData = VideoHelper::getUrlData(mediaType, url, FFMPEG_VERSION_MAJOR < 5); //打开输入(通过标志位控制回调那边做超时判断)
tryOpen = true;
int result = -1;
if (mediaType == MediaType_WebSocket) {
formatCtx->pb = webObj->getCtx();
if (!formatCtx->pb) {
return false;
} //打开失败则重新连接
result = avformat_open_input(&formatCtx, NULL, NULL, &options);
if (result < 0) {
QMetaObject::invokeMethod(webObj, "reopen");
}
} else {
result = avformat_open_input(&formatCtx, urlData.constData(), ifmt, &options);
} tryOpen = false;
if (result < 0) {
debug(result, "打开地址", "");
return false;
} //根据自己项目需要开启下面部分代码加快视频流打开速度
//开启后由于值太小可能会出现部分视频流获取不到分辨率
if (decodeType == DecodeType_Fastest && mediaType == MediaType_Rtsp) {
FFmpegHelper::initRtspFast(formatCtx);
} //获取流信息
result = avformat_find_stream_info(formatCtx, NULL);
if (result < 0) {
debug(result, "找流失败", "");
return false;
} //封装格式
formatName = formatCtx->iformat->name;
//校验硬解码
FFmpegThreadHelper::checkHardware(formatName, hardware); //获取文件时长(这里获取到的是秒)
double length = (double)formatCtx->duration / AV_TIME_BASE;
//如果是本地文件而且没有时长则用最原始方法读取时长
//有部分设备导出的视频文件读取出来时长不对也可以用此方法读取
if (mediaType == MediaType_FileLocal && duration <= 0) {
if (this->property("getDurationByFrame").toBool()) {
length = FFmpegUtil::getDuration(mediaUrl);
}
} duration = length * 1000;
this->checkMediaType(); //发送文件时长信号
if (getIsFile()) {
emit receiveDuration(duration > 0 ? duration : 0);
} //获取音视频轨道信息(一般有一个音频或者一个视频/ts节目文件可能有多个)
FFmpegHelper::getTracks(formatCtx, audioTracks, videoTracks);
emit receiveTrack(audioTracks, videoTracks);
QString msg = QString("格式: %1 时长: %2 秒 加速: %3").arg(formatName).arg(duration / 1000).arg(hardware);
debug(0, "媒体信息", msg);
return true;
} bool FFmpegThread::initVideo()
{
//找到视频流索引
videoIndex = av_find_best_stream(formatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
if (videoIndex < 0) {
//有些没有视频流所以这里不用返回
videoIndex = -1;
debug(0, "无视频流", "");
} else {
//如果手动指定了轨道则取指定的(节目流有多个轨道可以指定某个)
if (videoTrack >= 0 && videoTracks.contains(videoTrack)) {
videoIndex = videoTrack;
} //取出流获取对应的信息创建解码器
int result = -1;
AVStream *videoStream = formatCtx->streams[videoIndex]; //如果主动设置过旋转角度则将旋转信息设置到流信息中以便保存那边也应用(不需要保存也旋转可以注释)
if (rotate != -1) {
FFmpegHelper::setRotate(videoStream, rotate);
} //先获取旋转角度(如果有旋转角度则不能用硬件加速)
this->getRotate();
if (rotate != 0) {
hardware = "none";
} //查找视频解码器(如果上面av_find_best_stream第五个参数传了则这里不需要)
AVCodecID codecId = FFmpegHelper::getCodecId(videoStream);
if (codecId == AV_CODEC_ID_NONE) {
debug(result, "无视解码", "");
return false;
} //初始化解码器
FFmpegThreadHelper::initVideoCodec(&videoCodec, codecId, videoCodecName, hardware); //创建视频解码器上下文
videoCodecCtx = avcodec_alloc_context3(NULL);
if (!videoCodecCtx) {
debug(result, "创建视解", "");
return false;
} //将视频流的参数拷贝给视频解码器上下文/以便能够按照对应流参数进行解码
result = FFmpegHelper::copyContext(videoCodecCtx, videoStream, false);
if (result < 0) {
debug(result, "视频参数", "");
return false;
} //初始化硬件加速(也可以叫硬解码/如果当前格式不支持硬解则立即切换到软解码)
if (hardware != "none" && !FFmpegThreadHelper::initHardware(this, videoCodec, videoCodecCtx, hardware)) {
hardware = "none";
videoCodec = avcodec_find_decoder(codecId);
} if (!videoCodec) {
return false;
} //设置低延迟和加速解码等参数(设置max_lowres的话很可能画面采用最小的分辨率)
if (!getIsFile()) {
//videoCodecCtx->lowres = videoCodec->max_lowres;
videoCodecCtx->flags |= AV_CODEC_FLAG_LOW_DELAY;
videoCodecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
videoCodecCtx->flags2 |= AV_CODEC_FLAG2_FAST;
} //打开视频解码器
result = avcodec_open2(videoCodecCtx, videoCodec, NULL);
if (result < 0) {
debug(result, "打开视解", "");
return false;
} if (videoCodecCtx->pix_fmt == AV_PIX_FMT_NONE) {
debug(0, "格式为空", "");
return false;
} //获取分辨率大小
FFmpegHelper::getResolution(videoStream, videoWidth, videoHeight);
//如果没有获取到宽高则返回
if (videoWidth == 0 || videoHeight == 0) {
debug(0, "无分辨率", "");
return false;
} //记录首帧开始时间和解码器名称
videoFirstPts = videoStream->start_time;
videoCodecName = videoCodec->name;
frameRate = FFmpegHelper::getFrameRate(videoStream, formatName);
QString msg = QString("索引: %1 解码: %2 帧率: %3 宽高: %4x%5 角度: %6").arg(videoIndex).arg(videoCodecName).arg(frameRate).arg(videoWidth).arg(videoHeight).arg(rotate);
debug(0, "视频信息", msg);
//FFmpegUtil::getExtraData(videoCodecCtx);
} return true;
}

Qt/C++音视频开发81-采集本地麦克风/本地摄像头带麦克风/桌面采集和麦克风/本地设备和桌面推流的更多相关文章

  1. Android 音视频开发(一):PCM 格式音频的播放与采集

    什么是 PCM 格式 声音从模拟信号转化为数字信号的技术,经过采样.量化.编码三个过程将模拟信号数字化. 采样 顾名思义,对模拟信号采集样本,该过程是从时间上对信号进行数字化,例如每秒采集 44100 ...

  2. Android 音视频开发学习思路

    Android 音视频开发这块目前的确没有比较系统的教程或者书籍,网上的博客文章也都是比较零散的.只能通过一点点的学习和积累把这块的知识串联积累起来. 初级入门篇: Android 音视频开发(一) ...

  3. Android 音视频开发(一) : 通过三种方式绘制图片

    版权声明:转载请说明出处:http://www.cnblogs.com/renhui/p/7456956.html 在 Android 音视频开发学习思路 里面,我们写到了,想要逐步入门音视频开发,就 ...

  4. Android 音视频开发(七): 音视频录制流程总结

    在前面我们学习和使用了AudioRecord.AudioTrack.Camera.MediaExtractor.MediaMuxer API.MediaCodec. 学习和使用了上述的API之后,相信 ...

  5. Android开发 音视频开发需要了解的专业术语知识

    前言 在摸索一段时间的音视频开发后,越来越发现这个坑的深度真是特别的深. 除了了解Android自带的音视频处理API以外,还得了解一些视频与音频方面的知识.这篇博客就是主要讲解这方面的专业术语.内容 ...

  6. 音视频开发-FFmpeg

    音视频开发是个非常复杂的,庞大的开发话题,初涉其中,先看一下结合 OEIP(开源项目) 新增例子. 可以打开flv,mp4类型文件,以及rtmp协议音视频数据,声音的播放使用SDL. 把采集的麦/声卡 ...

  7. Python音视频开发:消除抖音短视频Logo的图形化工具实现

    ☞ ░ 前往老猿Python博文目录 ░ 一.引言 在<Python音视频开发:消除抖音短视频Logo和去电视台标的实现详解>节介绍了怎么通过Python+Moviepy+OpenCV实现 ...

  8. Moviepy音视频开发:视频转gif动画或jpg图片exe图形化工具开发案例

    ☞ ░ 前往老猿Python博文目录 ░ 一.引言 老猿之所以学习和研究Moviepy的使用,是因为需要一个将视频转成动画的工具,当时在网上到处搜索查找免费使用工具,结果找了很多自称免费的工具,但转完 ...

  9. 【秒懂音视频开发】02_Windows开发环境搭建

    音视频开发库的选择 每个主流平台基本都有自己的音视频开发库(API),用以处理音视频数据,比如: iOS:AVFoundation.AudioUnit等 Android:MediaPlayer.Med ...

  10. Android音视频开发(1):H264 基本原理

    前言 H264 视频压缩算法现在无疑是所有视频压缩技术中使用最广泛,最流行的.随着 x264/openh264 以及 ffmpeg 等开源库的推出,大多数使用者无需再对H264的细节做过多的研究,这大 ...

随机推荐

  1. 77.const声明对象修改对象里面的值会触发报错吗

    不会,因为对象是复杂类型数据 :对象的地址保存在栈内存中,对象的数据保存在堆内存中 : 只要对象的地址不发生改变,无论堆内存的对象数据如何改变,对象的值就不会改变 :

  2. Blazor Hybrid 实战体验:那些你可能没预料到的坑没预料到的坑

    前言 昨天写了一篇介绍 Blazor Hybrid 技术的文章,但限于篇幅,一些问题未能深入探讨.今天,我想继续记录使用 Blazor Hybrid 过程中遇到的几个问题,以及这个技术目前的一些局限性 ...

  3. [Java/日志] 日志框架打印应用程序日志代码的执行情况

    0 引言 我常以为 INFO 日志级别的 应用程序日志代码,不会被执行(比如,实验1中的printTestLog函数).但今天线上的问题,证实了这个思路是错的. 1 验证实验 版本信息 jdk : 1 ...

  4. KubeSphere v4 安装指南

    日前,KubeSphere v4 发布,相较于之前的版本,新版本在架构上有了颠覆性的变化.为了让社区的各位小伙伴能够丝滑的从旧版本过渡到新版本,我们特别推出本篇安装指南文章,以供参考. 关于 Kube ...

  5. LeetCode题目练习记录 _数组和链表03 _20211011

    LeetCode题目练习记录 _数组和链表03 _20211011 206. 反转链表 难度简单2015收藏分享切换为英文接收动态反馈 给你单链表的头节点 head ,请你反转链表,并返回反转后的链表 ...

  6. leetcode 740 删除并获得点数

    740 删除并获得点数 题意 给你一个整数数组 nums ,你可以对它进行一些操作. 每次操作中,选择任意一个 nums[i] ,删除它并获得 nums[i] 的点数.之后,你必须删除 所有 等于 n ...

  7. php日志分割

    为了方便查看php错误日志信息,将php的日志按照时间进行分割,器脚本如下 phpPid='/usr/local/webserver/php-5.3.27/var/run/php-fpm.pid' p ...

  8. 强化学习训练过程中的过度拟合(overfitting)

    相关: A.I. Learns to Drive From Scratch in Trackmania 本文讨论的是强化学习中的过度拟合问题,要知道强化学习中的过拟合和其他的监督.无监督学习的过拟合不 ...

  9. Help document of CAD Plus Mobile

    Help document for Mac 中文使用帮助 If you have any questions, please send email to 3167292926@qq.com 1. Pe ...

  10. html代码新手教学

    HTML 是超文本标记语言(HyperText Markup Language)的缩写,是用来描述网页结构的标记语言.在这篇教学中,我们将介绍一些 HTML 基础知识,帮助新手快速学习并掌握如何编写简 ...