Files
gb28181/GB28181Device/media/video_capture_qt.cpp
2024-12-15 20:42:32 +08:00

618 lines
15 KiB
C++

/***************************************************************************************
*
* 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-2022, 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 "sys_log.h"
#include "media_format.h"
#include "lock.h"
#include "video_capture_qt.h"
#include <QCameraInfo>
#include <QAndroidJniObject>
#include <QAndroidJniEnvironment>
/***************************************************************************************/
static const char HtCameraListenerClassName[] = "org/happytimesoft/gb28181device/HtCameraListener";
typedef QHash<int, CQVideoCapture *> CameraMap;
void * g_cameras_mutex = sys_os_create_mutex();
CameraMap g_cameras;
/***************************************************************************************/
static void notifyAutoFocusComplete(JNIEnv* , jobject, int id, jboolean success)
{
}
static void notifyPictureExposed(JNIEnv* , jobject, int id)
{
}
static void notifyPictureCaptured(JNIEnv *env, jobject, int id, jbyteArray data)
{
}
static void notifyNewPreviewFrame(JNIEnv *env, jobject, int id, jbyteArray data, int width, int height, int format, int bpl)
{
CLock lock(g_cameras_mutex);
const auto it = g_cameras.constFind(id);
if (Q_UNLIKELY(it == g_cameras.cend()))
{
return;
}
const int arrayLength = env->GetArrayLength(data);
if (arrayLength == 0)
{
return;
}
QByteArray bytes(arrayLength, Qt::Uninitialized);
env->GetByteArrayRegion(data, 0, arrayLength, (jbyte*)bytes.data());
(*it)->processFrame((uint8*)bytes.data(), arrayLength);
}
static void notifyFrameAvailable(JNIEnv *, jobject, int id)
{
log_print(HT_LOG_DBG, "%s, enter...\r\n", __FUNCTION__);
}
static void notifyError(JNIEnv *, jobject, int id, int error)
{
CLock lock(g_cameras_mutex);
const auto it = g_cameras.constFind(id);
if (Q_UNLIKELY(it == g_cameras.cend()))
{
return;
}
(*it)->processError(error);
}
void * checkThread(void * argv)
{
CQVideoCapture * pthis = (CQVideoCapture *)argv;
pthis->checkError();
return NULL;
}
CQVideoCapture::CQVideoCapture(QObject * parent) : QObject(parent), CVideoCapture()
{
m_nCodec = VIDEO_CODEC_NONE;
m_nDstWidth = 0;
m_nDstHeight = 0;
m_nFrameCount = 0;
m_bError = FALSE;
m_bCheck = TRUE;
m_pCheckThread = sys_os_create_thread((void *) checkThread, this);
}
CQVideoCapture::~CQVideoCapture(void)
{
stopCapture();
m_bCheck = FALSE;
while (m_pCheckThread)
{
usleep(1000);
}
}
CVideoCapture * CQVideoCapture::getInstance(int devid)
{
if (devid < 0 || devid >= MAX_VIDEO_DEV_NUMS)
{
return NULL;
}
sys_os_mutex_enter(m_pInstMutex);
if (NULL == m_pInstance[devid])
{
m_pInstance[devid] = (CVideoCapture *)new CQVideoCapture;
if (m_pInstance[devid])
{
m_pInstance[devid]->m_nRefCnt++;
m_pInstance[devid]->m_nDevIndex = devid;
}
}
else
{
m_pInstance[devid]->m_nRefCnt++;
}
sys_os_mutex_leave(m_pInstMutex);
return m_pInstance[devid];
}
int CQVideoCapture::getDeviceNums()
{
QList<QCameraInfo> info = QCameraInfo::availableCameras();
log_print(HT_LOG_INFO, "%s, count=%d\r\n", __FUNCTION__, info.count());
return info.count() > MAX_VIDEO_DEV_NUMS ? MAX_VIDEO_DEV_NUMS : info.count();
}
void CQVideoCapture::listDevice()
{
}
int CQVideoCapture::getDeviceIndex(const char * name)
{
return 0;
}
BOOL CQVideoCapture::startCamera()
{
QAndroidJniEnvironment env;
m_camera = QAndroidJniObject::callStaticObjectMethod("android/hardware/Camera",
"open",
"(I)Landroid/hardware/Camera;",
m_nDevIndex);
if (env->ExceptionCheck())
{
log_print(HT_LOG_ERR, "%s, get camera objectd failed 1\r\n", __FUNCTION__);
env->ExceptionClear();
return FALSE;
}
if (!m_camera.isValid())
{
log_print(HT_LOG_ERR, "%s, get camera objectd failed 2\r\n", __FUNCTION__);
return FALSE;
}
m_cameraListener = QAndroidJniObject(HtCameraListenerClassName, "(I)V", m_nDevIndex);
if (env->ExceptionCheck())
{
env->ExceptionClear();
}
QAndroidJniObject m_parameters = m_camera.callObjectMethod("getParameters",
"()Landroid/hardware/Camera$Parameters;");
if (env->ExceptionCheck())
{
env->ExceptionClear();
}
if (m_parameters.isValid())
{
int i;
int count;
QAndroidJniObject formatList = m_parameters.callObjectMethod("getSupportedPreviewFormats",
"()Ljava/util/List;");
if (env->ExceptionCheck())
{
env->ExceptionClear();
}
count = formatList.callMethod<jint>("size");
for (i = 0; i < count; ++i)
{
QAndroidJniObject format = formatList.callObjectMethod("get",
"(I)Ljava/lang/Object;",
i);
if (env->ExceptionCheck())
{
env->ExceptionClear();
}
int fmt = format.callMethod<jint>("intValue");
if (fmt == 17 || fmt == 842094169) // NV21 or YV12
{
m_parameters.callMethod<void>("setPreviewFormat", "(I)V", jint(fmt));
if (env->ExceptionCheck())
{
env->ExceptionClear();
}
break;
}
}
QAndroidJniObject sizeList = m_parameters.callObjectMethod("getSupportedPreviewSizes",
"()Ljava/util/List;");
if (env->ExceptionCheck())
{
env->ExceptionClear();
}
count = sizeList.callMethod<jint>("size");
for (i = 0; i < count; ++i)
{
QAndroidJniObject size = sizeList.callObjectMethod("get",
"(I)Ljava/lang/Object;",
i);
if (env->ExceptionCheck())
{
env->ExceptionClear();
}
int w = size.getField<jint>("width");
int h = size.getField<jint>("height");
if (w == m_nDstWidth && h == m_nDstHeight)
{
m_parameters.callMethod<void>("setPreviewSize", "(II)V", w, h);
m_parameters.callMethod<void>("setPictureSize", "(II)V", w, h);
break;
}
else if ((m_nDstWidth == 0 || m_nDstHeight == 0) && w == 1280 && h == 720)
{
m_parameters.callMethod<void>("setPreviewSize", "(II)V", w, h);
m_parameters.callMethod<void>("setPictureSize", "(II)V", w, h);
break;
}
if (env->ExceptionCheck())
{
env->ExceptionClear();
}
}
}
// m_parameters.callMethod<void>("setRotation", "(I)V", 180);
m_camera.callMethod<void>("setParameters",
"(Landroid/hardware/Camera$Parameters;)V",
m_parameters.object());
if (env->ExceptionCheck())
{
env->ExceptionClear();
}
// m_camera.callMethod<void>("setDisplayOrientation", "(I)V", 270);
m_parameters = m_camera.callObjectMethod("getParameters",
"()Landroid/hardware/Camera$Parameters;");
if (env->ExceptionCheck())
{
env->ExceptionClear();
}
QAndroidJniObject size = m_parameters.callObjectMethod("getPreviewSize",
"()Landroid/hardware/Camera$Size;");
if (env->ExceptionCheck())
{
env->ExceptionClear();
}
m_nWidth = size.getField<jint>("width");
m_nHeight = size.getField<jint>("height");
int format = m_parameters.callMethod<jint>("getPreviewFormat");
if (env->ExceptionCheck())
{
env->ExceptionClear();
}
if (format == 17)
{
m_nVideoFormat = VIDEO_FMT_NV21;
}
else if (format == 842094169)
{
m_nVideoFormat = VIDEO_FMT_YV12;
}
else
{
log_print(HT_LOG_ERR, "%s, unsupport pixel format!!! %d\r\n", __FUNCTION__, format);
return FALSE;
}
m_surfaceTexture = QAndroidJniObject("android/graphics/SurfaceTexture", "(I)V", jint(m_nDevIndex));
if (env->ExceptionCheck())
{
env->ExceptionClear();
}
if (!m_surfaceTexture.isValid())
{
log_print(HT_LOG_ERR, "m_surfaceTexture is null\r\n");
return FALSE;
}
m_camera.callMethod<void>("setPreviewTexture",
"(Landroid/graphics/SurfaceTexture;)V",
m_surfaceTexture.object());
if (env->ExceptionCheck())
{
env->ExceptionClear();
return FALSE;
}
m_cameraListener.callMethod<void>("setupPreviewCallback", "(Landroid/hardware/Camera;)V", m_camera.object());
if (env->ExceptionCheck())
{
env->ExceptionClear();
}
m_cameraListener.callMethod<void>("notifyNewFrames", "(Z)V", true);
if (env->ExceptionCheck())
{
env->ExceptionClear();
}
log_print(HT_LOG_INFO, "%s, w=%d, h=%d, fmt=%d\r\n", __FUNCTION__, m_nWidth, m_nHeight, m_nVideoFormat);
VideoEncoderParam params;
memset(&params, 0, sizeof(params));
params.SrcWidth = m_nWidth;
params.SrcHeight = m_nHeight;
params.SrcPixFmt = (AVPixelFormat)m_nVideoFormat;
params.DstCodec = m_nCodec;
params.DstWidth = m_nWidth;
params.DstHeight = m_nHeight;
params.DstFramerate = m_nFramerate;
params.DstBitrate = m_nBitrate;
if (m_encoder.init(&params) == FALSE)
{
return FALSE;
}
return TRUE;
}
void CQVideoCapture::stopCamera()
{
log_print(HT_LOG_DBG, "%s, enter...\r\n", __FUNCTION__);
QAndroidJniEnvironment env;
m_parameters = QAndroidJniObject();
if (m_camera.isValid())
{
m_camera.callMethod<void>("release");
if (env->ExceptionCheck())
{
env->ExceptionClear();
}
}
if (m_surfaceTexture.isValid())
{
m_surfaceTexture.callMethod<void>("release");
if (env->ExceptionCheck())
{
env->ExceptionClear();
}
}
m_nFrameCount = 0;
m_encoder.uninit();
CLock lock(g_cameras_mutex);
g_cameras.remove(m_nDevIndex);
log_print(HT_LOG_DBG, "%s, exit\r\n", __FUNCTION__);
}
BOOL CQVideoCapture::initCapture(int codec, int width, int height, double framerate, int bitrate)
{
log_print(HT_LOG_INFO, "%s, enter...\r\n", __FUNCTION__);
CLock lock(m_pMutex);
if (m_bInited)
{
return TRUE;
}
m_nCodec = codec;
m_nDstWidth = width;
m_nDstHeight = height;
m_nFramerate = framerate;
m_nBitrate = bitrate;
if (!startCamera())
{
return FALSE;
}
CLock lock1(g_cameras_mutex);
g_cameras.insert(m_nDevIndex, this);
m_bInited = TRUE;
return TRUE;
}
BOOL CQVideoCapture::startCapture()
{
log_print(HT_LOG_DBG, "%s\r\n", __FUNCTION__);
CLock lock(m_pMutex);
if (!m_bInited)
{
return FALSE;
}
if (m_bCapture)
{
return TRUE;
}
m_camera.callMethod<void>("startPreview");
QAndroidJniEnvironment env;
if (env->ExceptionCheck())
{
env->ExceptionClear();
m_bCapture = FALSE;
}
else
{
m_bCapture = TRUE;
}
return m_bCapture;
}
void CQVideoCapture::stopCapture()
{
log_print(HT_LOG_DBG, "%s, enter...\r\n", __FUNCTION__);
// wait for capture thread exit
m_bCapture = FALSE;
while (m_hCapture)
{
usleep(10*1000);
}
stopCamera();
m_bInited = FALSE;
}
void CQVideoCapture::processFrame(uint8 * data, int size)
{
if (!m_bInited)
{
return;
}
m_nFrameCount++;
m_encoder.encode(data, size);
}
void CQVideoCapture::processError(int error)
{
log_print(HT_LOG_ERR, "%s, error=%d\r\n", __FUNCTION__, error);
m_bError = TRUE;
}
void CQVideoCapture::checkError()
{
int count = 0;
BOOL reset = FALSE;
while (m_bCheck)
{
usleep(100*1000);
reset = FALSE;
if (m_bError)
{
m_bError = FALSE;
reset = TRUE;
}
else if (m_bCapture)
{
if (count++ > 10*4) // 4 seconds
{
count = 0;
if (m_nFrameCount == 0)
{
reset = TRUE;
}
else
{
m_nFrameCount = 0;
}
}
}
if (reset)
{
log_print(HT_LOG_DBG, "%s, reset camera start\r\n", __FUNCTION__);
stopCapture();
initCapture(m_nCodec, m_nDstWidth, m_nDstHeight, m_nFramerate, m_nBitrate);
startCapture();
log_print(HT_LOG_DBG, "%s, reset camera end\r\n", __FUNCTION__);
}
}
m_pCheckThread = 0;
}
void CQVideoCapture::initJNI(JNIEnv *env)
{
jclass clazz = env->FindClass(HtCameraListenerClassName);
static const JNINativeMethod methods[] = {
{"notifyAutoFocusComplete", "(IZ)V", (void *)notifyAutoFocusComplete},
{"notifyPictureExposed", "(I)V", (void *)notifyPictureExposed},
{"notifyPictureCaptured", "(I[B)V", (void *)notifyPictureCaptured},
{"notifyNewPreviewFrame", "(I[BIIII)V", (void *)notifyNewPreviewFrame},
{"notifyFrameAvailable", "(I)V", (void *)notifyFrameAvailable},
{"notifyError", "(II)V", (void *)notifyError}
};
if (clazz)
{
if (env->RegisterNatives(clazz,
methods,
sizeof(methods) / sizeof(methods[0])) != JNI_OK)
{
log_print(HT_LOG_INFO, "RegisterNatives failed!\r\n");
}
else
{
log_print(HT_LOG_INFO, "RegisterNatives succ!\r\n");
}
}
else
{
log_print(HT_LOG_ERR, "clazz is null\r\n");
}
}