一、浏览器内嵌C++播放器
基本原理在浏览器⽹页中的指定播放窗口的位置和⼤⼩,实现⼀个内嵌到⽹页中显⽰的播放窗⼝,前端还必须可对这个内嵌播放窗⼝进⾏控制,⽽且播放窗⼝必须跟随浏览器窗⼝的移动和缩放、⽹页滚动、标签页切换、关闭等操作进⾏⾃动联动。播放器可以通过QT实现。这个播放窗⼝同时提供Web Socket的服务端和JSON打包命令的解析执⾏模块,前端就可以通过Web Socket连接后发送JSON打包的控制命令实现控制播放窗⼝。
二、浏览器内嵌播放器实现
实现功能,通过插件在网页端播放H264和H265视频流,并能实现抓图功能。
下面是部分JavaScript代码
/*实现显示*/
show: function () {
var that = this
this.setting.stopRefresh = false
this.setting.show = true
this.send({
method: 'window.show',
info: {
show: true
}
},
/**
* @method playReal 实时预览
* @param { String } option.devId 设备ID
* @param { Number } option.winIndex 选择的窗口号,从0开始
* @param { String } option.url rtsp地址
* @param { Number } option.decodeType 软/硬解码 0-软解 1-硬解
* @param { Number } option.connectType 连接方式 0-TCP 1-UDP
*/
playReal: function (option) {
this.stopVideo(option.winIndex)
this.send({
method: 'media.playReal',
info: {
devId: option.devId,
winIndex: option.winIndex,
url: option.url,
decodeType: option.decodeType,
connectType: option.connectType
}
})
},
C++部分通过调用ffmpeg来实现拉流,部分实现如下
MY_DEBUG << "ctFFmpeg::Init sUrl:" << sUrl << " start.";
//AVCodec *pvAVCodec = nullptr; // ffmpeg low than 5.1
const AVCodec *pvAVCodec = nullptr; //ffmpeg 5.1
m_bNeedToRecon = false;
m_bQuit = false;
if(sUrl.contains("rtsp://") || sUrl.contains("http://")) //实时流
m_nPlayWay = MEDIA_PLAY_STREAM;
else
m_nPlayWay = MEDIA_PLAY_FILE;
avformat_network_init();//初始化网络流
//参数设置
AVDictionary* pOptDict = NULL;
if(nProtocolType == MEDIA_PROTOCOL_UDP)
{
MY_DEBUG << "UDP";
av_dict_set(&pOptDict, "rtsp_transport", "udp", 0);
}
else
{
MY_DEBUG << "TCP";
av_dict_set(&pOptDict, "rtsp_transport", "tcp", 0);
}
m_nProtocolType = nProtocolType;
m_nDecodeType = nDecodeType;
m_nShowType = nShowType;
m_bEnablePush = bEnablePush;
m_pShareQueue = pQueue;
m_sRtmpServerAddr = sServerAddr;
av_dict_set(&pOptDict, "stimeout", "5000000", 0);
av_dict_set(&pOptDict, "buffer_size", "8192000", 0);
av_dict_set(&pOptDict, "recv_buffer_size", "4096000", 0); // 防止花屏, max 4M.
av_dict_set(&pOptDict, "tune", "stillimage,fastdecode,zerolatency", 0);//快速解码和低延时
if(nullptr == m_pAVFmtCxt)
m_pAVFmtCxt = avformat_alloc_context();
if(nullptr == m_pYuvFrame)
m_pYuvFrame = av_frame_alloc();
if(nullptr == m_pDstVideoFrame)
m_pDstVideoFrame = av_frame_alloc();
if(nullptr == m_pPcmFrame)
m_pPcmFrame = av_frame_alloc();
if(nullptr == m_pFilterFrame)
m_pFilterFrame = av_frame_alloc();
if(nullptr == m_pHwFrame)
m_pHwFrame = av_frame_alloc();
//加入中断处理
m_nLastReadPacktTime = av_gettime();
m_pAVFmtCxt->interrupt_callback.opaque = this;
m_pAVFmtCxt->interrupt_callback.callback = [](void* ctx)
{
ctFFmpeg* pThis = (ctFFmpeg*)ctx;
int nTimeout = 15;
if (av_gettime() - pThis->m_nLastReadPacktTime > nTimeout * 1000 * 1000)
{
return -1;
}
return 0;
};
//打开码流
MY_DEBUG << "Open Stream.";
int nRet;
if(m_nPlayWay == MEDIA_PLAY_FILE)
{
nRet = avformat_open_input(&m_pAVFmtCxt, sUrl.toStdString().c_str(), nullptr, nullptr);
if(m_pAVFmtCxt)
{
m_FileTotalTimes = m_pAVFmtCxt->duration/1000000.0;//获取总时长,转化为秒
MY_DEBUG << "m_FileTimes:" << m_FileTotalTimes;
emit sig_showTotalTime(static_cast<int>(m_FileTotalTimes));
}
}
else{
nRet = avformat_open_input(&m_pAVFmtCxt, sUrl.toStdString().c_str(), nullptr, &pOptDict);
if(nRet < 0)
{
MY_DEBUG << "avformat_open_input failed nRet:" << nRet;
av_dict_free(&pOptDict);
return nRet;
}
}
av_dict_free(&pOptDict);
//设置探测时间,获取码流信息
MY_DEBUG << "Get Stream Info.";
m_pAVFmtCxt->probesize = MAX_PROBE_SIZE * 15;
m_pAVFmtCxt->max_analyze_duration = MAX_ANALYZE_DURATION * 15;
nRet = avformat_find_stream_info(m_pAVFmtCxt, nullptr);
if(nRet < 0)
{
MY_DEBUG << "avformat_find_stream_info failed nRet:" << nRet;
return nRet;
}
//打印码流信息
av_dump_format(m_pAVFmtCxt, 0, sUrl.toStdString().c_str(), 0);
//查找码流
MY_DEBUG << "av_find_best_stream.";
m_nVideoIndex = av_find_best_stream(m_pAVFmtCxt, AVMEDIA_TYPE_VIDEO, -1, -1, &pvAVCodec, 0);
if(m_nVideoIndex < 0)
{
MY_DEBUG << "av not find best stream";
return -1;
}
m_nAudioIndex = av_find_best_stream(m_pAVFmtCxt, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
if(nullptr == pvAVCodec)
{
MY_DEBUG << "nullptr == pvAVCodec error.";
return -1;
}
//初始化视频
MY_DEBUG << "Init Video.";
if(InitVideo(pvAVCodec) < 0)
{
MY_DEBUG << "InitVideo() error";
return -1;
}
MY_DEBUG << "Init ffmpeg version"<<av_version_info();
#if FILTER_ENABLE
if(m_nPlayWay == MEDIA_PLAY_FILE)
{
m_bSupportFilter = InitOutFile();
if(!m_bSupportFilter)
{
MY_DEBUG << "InitOutFile() error.";
}
else
MY_DEBUG << "InitOutFile() success.";
}
#endif
//初始化音频
if(m_nAudioIndex >= 0)
{
if(InitAudio() < 0)
{
MY_DEBUG << "InitAudio() error";
m_bSupportAudioPlay = false;
}
else
m_bSupportAudioPlay = true;
}
m_nShowWidgetW = 0;
m_nShowWidgetH = 0;
if(m_nDecodeType == MEDIA_DECODE_HARD)
{
if(m_bSupportHw)
{
if(nullptr != m_pHwDevCtx)
av_buffer_unref(&m_pHwDevCtx);
}
}
else
{
m_bSupportPush = initPushStream();
}
MY_DEBUG << "ctFFmpeg::Init sUrl:" << sUrl << " success.";
return 0;
三、实现效果如下所示
1、可以实现多路视频播放
播放三路H265(2688*1520)所占用的cpu和内存如下
2、支持H264和H265的视频抓拍功能,图片保存在本地