/*************************************************************************************** * * IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. * * By downloading, copying, installing or using the software you agree to this license. * If you do not agree to this license, do not download, install, * copy or use the software. * * Copyright (C) 2014-2024, Happytimesoft Corporation, all rights reserved. * * Redistribution and use in binary forms, with or without modification, are permitted. * * Unless required by applicable law or agreed to in writing, software distributed * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific * language governing permissions and limitations under the License. * ****************************************************************************************/ #include "sys_inc.h" #include "VideoWidget.h" #include "utils.h" #include "InstMsgDialog.h" #include "rtsp_player.h" #include "rtmp_player.h" #include "http_flv_player.h" #include "http_mjpeg_player.h" #include "srt_player.h" #include "file_player.h" #include "http_test.h" #include #include #include #include #include #include #include #include #include #include #if defined(ANDROID) #include #endif /*********************************************************************************************/ #define VERTEXIN 0 #define TEXTUREIN 1 /*********************************************************************************************/ VideoWidget::VideoWidget(QWidget * parent, Qt::WindowFlags f) : QOpenGLWidget(parent, f) , m_pPlayer(NULL) , m_bMute(FALSE) , m_bRecording(FALSE) , m_pRenderFrame(NULL) #ifdef BACKCHANNEL , m_nBackChannelFlag(0) #endif { setAttribute(Qt::WA_OpaquePaintEvent); m_timerReconn.setSingleShot(true); connect(&m_timerReconn, SIGNAL(timeout()), this, SLOT(slotReconn())); connect(this, SIGNAL(imageReady()), this, SLOT(update()), Qt::QueuedConnection); } VideoWidget::~VideoWidget() { closeVideo(); makeCurrent(); vbo.destroy(); textureY->destroy(); textureU->destroy(); textureV->destroy(); doneCurrent(); } void VideoWidget::play(QString url, QString acct, QString pass) { if (m_url == url && m_acct == acct && m_pass == pass) { return; } closeVideo(); m_url = url; m_acct = acct; m_pass = pass; m_base = getBaseName(url); if (!m_url.isEmpty()) { makeCall(); } } void VideoWidget::pause() { if (m_pPlayer) { m_pPlayer->pause(); } } void VideoWidget::stop() { closeVideo(); } void VideoWidget::closePlayer() { m_timerReconn.stop(); if (m_pPlayer) { delete m_pPlayer; m_pPlayer = NULL; } } void VideoWidget::closeVideo() { closePlayer(); m_url = ""; m_acct = ""; m_pass = ""; m_bRecording = FALSE; QMutexLocker locker(&m_mutex); if (m_pRenderFrame) { av_frame_free(&m_pRenderFrame); } update(); } BOOL VideoWidget::isRecording() { if (m_pPlayer) { return m_pPlayer->isRecording(); } return FALSE; } void VideoWidget::makeCall() { BOOL isFile = 0; BOOL isRtsp = FALSE; if (isRtspUrl(m_url)) { isRtsp = TRUE; m_pPlayer = new CRtspPlayer(this); } else if (isRtmpUrl(m_url)) { m_pPlayer = new CRtmpPlayer(this); } else if (isHttpUrl(m_url)) { HTTPCTT ctt; if (http_test(m_url.toStdString().c_str(), m_acct.toStdString().c_str(), m_pass.toStdString().c_str(), &ctt, 2*1000)) { if (CTT_RTSP_TUNNELLED == ctt) { isRtsp = TRUE; m_pPlayer = new CRtspPlayer(this); } else if (CTT_FLV == ctt) { m_pPlayer = new CHttpFlvPlayer(this); } else if (CTT_MULTIPART == ctt) { m_pPlayer = new CHttpMjpegPlayer(this); } } else { m_pPlayer = new CHttpFlvPlayer(this); } } else if (isSrtUrl(m_url)) { m_pPlayer = new CSrtPlayer(this); } else { isFile = 1; m_pPlayer = new CFilePlayer(this); } if (NULL == m_pPlayer) { return; } connect(m_pPlayer, SIGNAL(notify(int)), this, SLOT(slotPlayerNotify(int)), Qt::QueuedConnection); connect(m_pPlayer, SIGNAL(snapshoted(AVFrame*)), this, SLOT(slotSnapshoted(AVFrame*)), Qt::QueuedConnection); connect(m_pPlayer, SIGNAL(imageReady(AVFrame*)), this, SLOT(slotImageReady(AVFrame*)), Qt::QueuedConnection); connect(m_pPlayer, SIGNAL(updateStatistics(int)), this, SIGNAL(updateStatistics(int))); if (m_pPlayer->open(m_url, 0)) { m_pPlayer->setAuthInfo(m_acct, m_pass); m_pPlayer->setHWDecoding(getHWDecoding()); if (isRtsp) { m_pPlayer->setRtpOverUdp(getRtpOverUdp()); m_pPlayer->setRtpMulticast(getRtpMulticast()); #ifdef OVER_HTTP m_pPlayer->setRtspOverHttp(getRtspOverHttp(), getRtspOverHttpPort()); #endif #ifdef OVER_WEBSOCKET m_pPlayer->setRtspOverWs(getRtspOverWs(), getRtspOverWsPort()); #endif #ifdef BACKCHANNEL m_pPlayer->setBCFlag(m_nBackChannelFlag); #endif } else if (isFile) { setMute(m_bMute); } m_pPlayer->play(); } else { closePlayer(); } } void VideoWidget::mousePressEvent(QMouseEvent * event) { emit widgetSelecting(this); } BOOL VideoWidget::micphone() { #ifdef BACKCHANNEL if (NULL == m_pPlayer) { return FALSE; } #if defined(ANDROID) QJniObject str = QJniObject::fromString("android.permission.RECORD_AUDIO"); QJniObject::callStaticMethod("org/happytimesoft/util/HtUtil", "requestPermission", "(Landroid/content/Context;Ljava/lang/String;)I", QNativeInterface::QAndroidApplication::context(), str.object()); #endif closePlayer(); if (m_nBackChannelFlag) { m_nBackChannelFlag = 0; } else { m_nBackChannelFlag = 1; } makeCall(); if (m_pPlayer) { m_pPlayer->setBCDataFlag(m_nBackChannelFlag); return m_pPlayer->getBCFlag(); } #endif return FALSE; } void VideoWidget::setMute(BOOL flag) { m_bMute = flag; if (m_pPlayer) { m_pPlayer->setVolume(flag ? HTVOLUME_MIN : HTVOLUME_MAX); } } BOOL VideoWidget::isPlaying() { if (m_pPlayer) { return m_pPlayer->isPlaying() || m_pPlayer->isPaused(); } return FALSE; } void VideoWidget::snapshot() { if (m_pPlayer) { m_pPlayer->snapshot(VIDEO_FMT_RGB24); } } void VideoWidget::slotSnapshoted(AVFrame * frame) { QImage image = QImage(frame->data[0], frame->width, frame->height, frame->linesize[0], QImage::Format_RGB888); QString file = getSnapshotPath() + "/" + getTempFile(m_base, ".jpg"); if (!image.save(file, "JPG")) { emit snapshotResult(false); } else { emit snapshotResult(true); } } BOOL VideoWidget::record() { if (NULL == m_pPlayer) { return FALSE; } if (m_pPlayer->isRecording()) { stopRecord(); } else { startRecord(); } m_bRecording = m_pPlayer->isRecording(); return m_bRecording; } void VideoWidget::startRecord() { if (NULL == m_pPlayer) { return; } if (m_pPlayer->isRecording()) { return; } QString file = getRecordPath() + "/" + getTempFile(m_base, ".avi"); m_pPlayer->record(file); } void VideoWidget::stopRecord() { if (NULL == m_pPlayer) { return; } if (m_pPlayer->isRecording()) { m_pPlayer->stopRecord(); emit recordResult(true); } } QRect VideoWidget::getVideoRenderRect(int videoW, int videoH) { QRect rect = this->rect(); qreal ratio = QGuiApplication::primaryScreen()->devicePixelRatio(); int w = rect.width() * ratio; int h = rect.height() * ratio; if (getVideoRenderMode() == RENDER_MODE_KEEP) // keep the original aspect ratio { int iw = videoW; int ih = videoH; int nw, nh; double vratio = iw / (double)ih; double wratio = w / (double)h; if (vratio > wratio) { nw = w; nh = w * ih / iw; } else { nw = h * iw / ih; nh = h; } rect.setLeft((w - nw) / 2); rect.setTop((h - nh) / 2); rect.setRight(rect.left() + nw); rect.setBottom(rect.top() + nh); } else // fill the whole window { rect.setLeft(0); rect.setTop(0); rect.setRight(w); rect.setBottom(h); } return rect; } void VideoWidget::slotImageReady(AVFrame * frame) { QMutexLocker locker(&m_mutex); if (m_pRenderFrame) { av_frame_free(&m_pRenderFrame); } if (m_pPlayer) { m_pRenderFrame = frame; emit imageReady(); } else { av_frame_free(&frame); } } void VideoWidget::slotPlayerNotify(int event) { if (event == RTSP_EVE_CONNFAIL || event == RTMP_EVE_CONNFAIL || event == HTTP_FLV_EVE_CONNFAIL || event == MJPEG_EVE_CONNFAIL || event == SRT_EVE_CONNFAIL) { m_timerReconn.start(5 * 1000); } else if (event == RTSP_EVE_CONNSUCC || event == MJPEG_EVE_CONNSUCC) { setMute(m_bMute); // Re-record after reconnect if (m_bRecording) { startRecord(); } } else if (event == RTMP_EVE_VIDEOREADY || event == HTTP_FLV_EVE_VIDEOREADY || event == SRT_EVE_VIDEOREADY) { // Re-record after reconnect if (m_bRecording) { startRecord(); } } else if (event == RTMP_EVE_AUDIOREADY || event == HTTP_FLV_EVE_AUDIOREADY || event == SRT_EVE_AUDIOREADY) { setMute(m_bMute); } else if (event == RTSP_EVE_NOSIGNAL || event == RTMP_EVE_NOSIGNAL || event == HTTP_FLV_EVE_NOSIGNAL || event == MJPEG_EVE_NOSIGNAL || event == SRT_EVE_NOSIGNAL) { m_timerReconn.start(5 * 1000); } else if (event == RTSP_EVE_NODATA || event == RTMP_EVE_NODATA || event == HTTP_FLV_EVE_NODATA || event == MJPEG_EVE_NODATA || event == SRT_EVE_NODATA) { m_timerReconn.start(5 * 1000); } else if (event == RTSP_EVE_RESUME || event == RTMP_EVE_RESUME || event == HTTP_FLV_EVE_RESUME || event == MJPEG_EVE_RESUME || event == SRT_EVE_RESUME) { m_timerReconn.stop(); } else if (event == RTSP_EVE_STOPPED || event == RTMP_EVE_STOPPED || event == HTTP_FLV_EVE_STOPPED || event == MJPEG_EVE_STOPPED || event == SRT_EVE_STOPPED) { m_timerReconn.start(5 * 1000); } emit callState(this, event); } void VideoWidget::slotReconn() { closePlayer(); makeCall(); } QString VideoWidget::getBaseName(QString &url) { if (isUrl(url)) { char host[100] = {'\0'}; url_split(url.toStdString().c_str(), NULL, 0, NULL, 0, NULL, 0, host, sizeof(host), NULL, NULL, 0); return QString(host); } else { QFileInfo fileInfo(url); return fileInfo.baseName(); } } void VideoWidget::initializeGL() { initializeOpenGLFunctions(); glEnable(GL_DEPTH_TEST); glEnable(GL_TEXTURE_2D); static const GLfloat vertices[] { -1.0f, -1.0f, -1.0f, +1.0f, +1.0f, +1.0f, +1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, }; if (!vbo.create()) { log_print(HT_LOG_ERR, "%s, vbo.create failed\r\n", __FUNCTION__); } if (!vbo.bind()) { log_print(HT_LOG_ERR, "%s, vbo.bind failed\r\n", __FUNCTION__); } vbo.allocate(vertices, sizeof(vertices)); QOpenGLShader * vshader = new QOpenGLShader(QOpenGLShader::Vertex, this); const char * vsrc = "attribute vec4 vertexIn; \n" "attribute vec2 textureIn; \n" "varying highp vec2 textureOut; \n" "void main(void) \n" "{ \n" " gl_Position = vertexIn; \n" " textureOut = textureIn; \n" "}"; if (!vshader->compileSourceCode(vsrc)) { log_print(HT_LOG_ERR, "%s, compile vertex source failed\r\n", __FUNCTION__); } QOpenGLShader * fshader = new QOpenGLShader(QOpenGLShader::Fragment, this); const char * fsrc = "varying highp vec2 textureOut; \n" "uniform sampler2D tex_y; \n" "uniform sampler2D tex_u; \n" "uniform sampler2D tex_v; \n" "void main(void) \n" "{ \n" " lowp vec3 yuv; \n" " lowp vec3 rgb; \n" " yuv.x = texture2D(tex_y, textureOut).r; \n" " yuv.y = texture2D(tex_u, textureOut).r - 0.5; \n" " yuv.z = texture2D(tex_v, textureOut).r - 0.5; \n" " rgb = mat3( 1, 1, 1, \n" " 0, -0.39465, 2.03211, \n" " 1.13983, -0.58060, 0) * yuv; \n" " gl_FragColor = vec4(rgb, 1); \n" "}"; if (!fshader->compileSourceCode(fsrc)) { log_print(HT_LOG_ERR, "%s, compile fragment source failed\r\n", __FUNCTION__); } program = new QOpenGLShaderProgram(this); if (!program->addShader(vshader)) { log_print(HT_LOG_ERR, "%s, add vertex shader failed\r\n", __FUNCTION__); } if (!program->addShader(fshader)) { log_print(HT_LOG_ERR, "%s, add fragment shader failed\r\n", __FUNCTION__); } program->bindAttributeLocation("vertexIn", VERTEXIN); program->bindAttributeLocation("textureIn", TEXTUREIN); if (!program->link()) { log_print(HT_LOG_ERR, "%s, link failed. %s\r\n", __FUNCTION__, program->log().toStdString().c_str()); } if (!program->bind()) { log_print(HT_LOG_ERR, "%s, program bind failed\r\n", __FUNCTION__); } program->enableAttributeArray(VERTEXIN); program->enableAttributeArray(TEXTUREIN); program->setAttributeBuffer(VERTEXIN, GL_FLOAT, 0, 2, 2*sizeof(GLfloat)); program->setAttributeBuffer(TEXTUREIN, GL_FLOAT, 8*sizeof(GLfloat), 2, 2*sizeof(GLfloat)); textureUniformY = program->uniformLocation("tex_y"); textureUniformU = program->uniformLocation("tex_u"); textureUniformV = program->uniformLocation("tex_v"); textureY = new QOpenGLTexture(QOpenGLTexture::Target2D); textureU = new QOpenGLTexture(QOpenGLTexture::Target2D); textureV = new QOpenGLTexture(QOpenGLTexture::Target2D); if (!textureY->create()) { log_print(HT_LOG_ERR, "%s, textureY create failed\r\n", __FUNCTION__); } if (!textureU->create()) { log_print(HT_LOG_ERR, "%s, textureU create failed\r\n", __FUNCTION__); } if (!textureV->create()) { log_print(HT_LOG_ERR, "%s, textureV create failed\r\n", __FUNCTION__); } idY = textureY->textureId(); idU = textureU->textureId(); idV = textureV->textureId(); glClearColor(0.0, 0.0, 0.0, 1.0f); } void VideoWidget::paintGL() { glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); QMutexLocker locker(&m_mutex); if (NULL == m_pRenderFrame) { return; } int videoW = m_pRenderFrame->width; int videoH = m_pRenderFrame->height; QRect rect = getVideoRenderRect(videoW, videoH); glViewport(rect.left(), rect.top(), rect.width(), rect.height()); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, idY); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, videoW, videoH, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, m_pRenderFrame->data[0]); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, idU); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, videoW >> 1, videoH >> 1, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, m_pRenderFrame->data[1]); glActiveTexture(GL_TEXTURE2); glBindTexture(GL_TEXTURE_2D, idV); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, videoW >> 1, videoH >> 1, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, m_pRenderFrame->data[2]); glUniform1i(textureUniformY, 0); glUniform1i(textureUniformU, 1); glUniform1i(textureUniformV, 2); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); }