618 lines
15 KiB
C++
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(¶ms, 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(¶ms) == 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");
|
|
}
|
|
}
|
|
|
|
|
|
|