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

700 lines
13 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 "video_capture_linux.h"
#include "media_format.h"
#include "media_codec.h"
#include "lock.h"
#include "v4l2.h"
#include "v4l2_comm.h"
#include <linux/videodev2.h>
/***************************************************************************************/
static void * videoCaptureThread(void * argv)
{
CLVideoCapture *capture = (CLVideoCapture *)argv;
capture->captureThread();
return NULL;
}
CLVideoCapture::CLVideoCapture(void) : CVideoCapture()
{
m_fd = 0;
m_nBuffers = 0;
m_pBufStart = NULL;
m_pBufLen = NULL;
}
CLVideoCapture::~CLVideoCapture(void)
{
stopCapture();
}
CVideoCapture * CLVideoCapture::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 CLVideoCapture;
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 CLVideoCapture::getDeviceNums()
{
int fd;
int count = 0;
DIR *dir;
struct dirent *entry;
char filename[512] = {'\0'};
dir = opendir("/dev");
if (!dir)
{
log_print(HT_LOG_ERR, "%s, opendir failed\r\n", __FUNCTION__);
return 0;
}
while ((entry = readdir(dir)))
{
if (strncmp(entry->d_name, "video", 5))
{
continue;
}
snprintf(filename, sizeof(filename), "/dev/%s", entry->d_name);
if ((fd = v4l2_open_device(filename)) < 0)
{
continue;
}
close(fd);
if (++count > MAX_VIDEO_DEV_NUMS)
{
break;
}
}
closedir(dir);
return count > MAX_VIDEO_DEV_NUMS ? MAX_VIDEO_DEV_NUMS : count;
}
void CLVideoCapture::listDevice()
{
int fd;
int count = 0;
DIR *dir;
struct dirent *entry;
char filename[512] = {'\0'};
struct v4l2_capability cap;
dir = opendir("/dev");
if (!dir)
{
log_print(HT_LOG_ERR, "%s, opendir failed\r\n", __FUNCTION__);
return;
}
printf("\r\nAvailable video capture device : \r\n\r\n");
while ((entry = readdir(dir)))
{
if (strncmp(entry->d_name, "video", 5))
{
continue;
}
snprintf(filename, sizeof(filename), "/dev/%s", entry->d_name);
if ((fd = v4l2_open_device(filename)) < 0)
{
continue;
}
ioctl(fd, VIDIOC_QUERYCAP, &cap);
close(fd);
printf("index : %d, name : %s\r\n", count, cap.card);
if (++count > MAX_VIDEO_DEV_NUMS)
{
break;
}
}
closedir(dir);
}
int CLVideoCapture::getDeviceIndex(const char * name)
{
int fd;
int index = 0;
int count = 0;
DIR *dir;
struct dirent *entry;
char filename[512] = {'\0'};
struct v4l2_capability cap;
dir = opendir("/dev");
if (!dir)
{
log_print(HT_LOG_ERR, "%s, opendir failed\r\n", __FUNCTION__);
return 0;
}
while ((entry = readdir(dir)))
{
if (strncmp(entry->d_name, "video", 5))
{
continue;
}
snprintf(filename, sizeof(filename), "/dev/%s", entry->d_name);
if ((fd = v4l2_open_device(filename)) < 0)
{
continue;
}
ioctl(fd, VIDIOC_QUERYCAP, &cap);
close(fd);
if (strcasecmp((char *)cap.card, name) == 0)
{
index = count;
break;
}
if (++count > MAX_VIDEO_DEV_NUMS)
{
break;
}
}
closedir(dir);
return index;
}
BOOL CLVideoCapture::getDeviceName(int index, char * name, int len)
{
BOOL ret = FALSE;
int count = 0;
DIR *dir;
struct dirent *entry;
char filename[512] = {'\0'};
int fd;
dir = opendir("/dev");
if (!dir)
{
log_print(HT_LOG_ERR, "%s, opendir failed\r\n", __FUNCTION__);
return FALSE;
}
while ((entry = readdir(dir)))
{
if (strncmp(entry->d_name, "video", 5))
{
continue;
}
snprintf(filename, sizeof(filename), "/dev/%s", entry->d_name);
if ((fd = v4l2_open_device(filename)) < 0)
{
continue;
}
close(fd);
if (count == index)
{
ret = TRUE;
strncpy(name, filename, len);
break;
}
count++;
}
closedir(dir);
return ret;
}
BOOL CLVideoCapture::initCapture(int codec, int width, int height, double framerate, int bitrate)
{
int ret;
char filename[512] = {'\0'};
uint32 v4l2_format;
CLock lock(m_pMutex);
if (m_bInited)
{
return TRUE;
}
m_nFramerate = framerate;
m_nBitrate = bitrate;
if (!getDeviceName(m_nDevIndex, filename, sizeof(filename)))
{
return FALSE;
}
m_fd = v4l2_open_device(filename);
if (m_fd < 0)
{
return FALSE;
}
if (!width || !height)
{
struct v4l2_format fmt;
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
log_print(HT_LOG_DBG, "Querying the device for the current frame size\r\n");
if (ioctl(m_fd, VIDIOC_G_FMT, &fmt) < 0)
{
log_print(HT_LOG_ERR, "ioctl(VIDIOC_G_FMT) failed\r\n");
return FALSE;
}
m_nWidth = fmt.fmt.pix.width;
m_nHeight = fmt.fmt.pix.height;
log_print(HT_LOG_DBG, "Setting frame size to %dx%d\r\n", m_nWidth, m_nHeight);
}
else
{
m_nWidth = width;
m_nHeight = height;
}
ret = initDevice(&m_nWidth, &m_nHeight, &v4l2_format);
if (ret < 0)
{
return FALSE;
}
m_nVideoFormat = v4l2_fmt_to_ht_fmt(v4l2_format);
if (VIDEO_FMT_NONE == m_nVideoFormat)
{
return FALSE;
}
ret = mmapInit();
if (ret < 0)
{
return FALSE;
}
setCodecFlag(codec);
if (m_bDecode)
{
if (!m_decoder.init(getVideoCodec(m_nVideoFormat), NULL, 0, HW_DECODING_AUTO))
{
return FALSE;
}
m_decoder.setCallback(VideoDecodeCb, this);
}
if (m_bEncode)
{
VideoEncoderParam params;
memset(&params, 0, sizeof(params));
params.SrcWidth = m_nWidth;
params.SrcHeight = m_nHeight;
params.SrcPixFmt = to_avpixelformat(m_nVideoFormat);
params.DstCodec = codec;
params.DstWidth = width ? width : m_nWidth;
params.DstHeight = height ? height : m_nHeight;
params.DstFramerate = framerate;
params.DstBitrate = bitrate;
if (m_encoder.init(&params) == FALSE)
{
return FALSE;
}
}
m_bInited = TRUE;
return TRUE;
}
BOOL CLVideoCapture::capture()
{
BOOL res = TRUE;
int ret;
struct v4l2_buffer buf;
if (!m_bInited)
{
return FALSE;
}
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
ret = ioctl(m_fd, VIDIOC_DQBUF, &buf);
if (ret < 0)
{
log_print(HT_LOG_ERR, "VIDIOC_DQBUF failed (%d)\r\n", ret);
return FALSE;
}
if (buf.index >= m_nBuffers)
{
log_print(HT_LOG_ERR, "Invalid buffer index received.\r\n");
return FALSE;
}
if (m_bDecode)
{
res = m_decoder.decode((uint8 *)m_pBufStart[buf.index], buf.bytesused, 0);
}
else if (m_bEncode)
{
res = m_encoder.encode((uint8 *)m_pBufStart[buf.index], buf.bytesused);
}
else
{
m_encoder.procData((uint8 *)m_pBufStart[buf.index], buf.bytesused);
}
// re-queen buffer
ret = ioctl(m_fd, VIDIOC_QBUF, &buf);
if (ret < 0)
{
log_print(HT_LOG_ERR, "VIDIOC_QBUF failed (%d)\r\n", ret);
return FALSE;
}
return res;
}
BOOL CLVideoCapture::startCapture()
{
CLock lock(m_pMutex);
if (m_hCapture)
{
return TRUE;
}
m_bCapture = TRUE;
m_hCapture = sys_os_create_thread((void *)videoCaptureThread, this);
return (m_hCapture ? TRUE : FALSE);
}
void CLVideoCapture::stopCapture()
{
m_bCapture = FALSE;
// wait for capture thread exit
while (m_hCapture)
{
usleep(10*1000);
}
mmapUninit();
uninitDevice();
m_bInited = FALSE;
}
void CLVideoCapture::captureThread()
{
int64 cur_delay = 0;
int64 pre_delay = 0;
int timeout = 1000000.0 / m_nFramerate;
uint32 cur_time = 0;
uint32 pre_time = 0;
while (m_bCapture)
{
if (capture())
{
cur_time = sys_os_get_ms();
cur_delay = timeout;
if (pre_time > 0)
{
cur_delay += pre_delay - (cur_time - pre_time) * 1000;
if (cur_delay < 1000)
{
cur_delay = 0;
}
}
pre_time = cur_time;
pre_delay = cur_delay;
if (cur_delay > 0)
{
usleep(cur_delay);
}
}
else
{
usleep(10*1000);
}
}
m_hCapture = 0;
}
/****************************************************************************/
int CLVideoCapture::initDevice(int *width, int *height, uint32 *desired_format)
{
int ret = -1, i;
int w, h;
for (i = 0; v4l2_fmt_table[i].ht_fmt != VIDEO_FMT_NONE; i++)
{
w = *width;
h = *height;
*desired_format = v4l2_fmt_table[i].v4l2_fmt;
ret = v4l2_init_device(m_fd, &w, &h, desired_format);
if (ret >= 0 && v4l2_fmt_is_support(*desired_format))
{
*width = w;
*height = h;
break;
}
*desired_format = 0;
}
if (*desired_format == 0)
{
log_print(HT_LOG_ERR, "Cannot find a proper format\r\n");
ret = -1;
}
return ret;
}
void CLVideoCapture::uninitDevice()
{
close(m_fd);
m_fd = 0;
}
int CLVideoCapture::mmapInit()
{
int ret;
uint32 i;
struct v4l2_requestbuffers reqbuf;
reqbuf.count = QBUF_COUNT;
reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
reqbuf.memory = V4L2_MEMORY_MMAP;
ret = ioctl(m_fd , VIDIOC_REQBUFS, &reqbuf);
if (ret < 0)
{
log_print(HT_LOG_ERR, "ioctl(VIDIOC_REQBUFS) failed %d\r\n", ret);
return ret;
}
if (reqbuf.count < 2)
{
log_print(HT_LOG_ERR, "Insufficient buffer memory\r\n", ret);
return -1;
}
m_nBuffers = reqbuf.count;
m_pBufStart = (void **) malloc(m_nBuffers * sizeof(void *));
if (NULL == m_pBufStart)
{
log_print(HT_LOG_ERR, "malloc failed\r\n", ret);
return -1;
}
m_pBufLen = (uint32 *) malloc(m_nBuffers * sizeof(uint32));
if (NULL == m_pBufLen)
{
log_print(HT_LOG_ERR, "malloc failed\r\n", ret);
return -1;
}
struct v4l2_buffer buf;
for (i = 0; i < reqbuf.count; i++)
{
buf.index = i;
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
ret = ioctl(m_fd, VIDIOC_QUERYBUF, &buf);
if (ret < 0)
{
log_print(HT_LOG_ERR, "ioctl(VIDIOC_QUERYBUF failed) %d\r\n", ret);
return ret;
}
// mmap buffer
m_pBufLen[i] = buf.length;
m_pBufStart[i] = (void *) mmap(NULL, buf.length, PROT_READ|PROT_WRITE, MAP_SHARED, m_fd, buf.m.offset);
if (m_pBufStart[i] == MAP_FAILED)
{
log_print(HT_LOG_ERR, "mmap (%d) failed: %s\r\n", i, strerror(errno));
return -1;
}
// queen buffer
ret = ioctl(m_fd , VIDIOC_QBUF, &buf);
if (ret < 0)
{
log_print(HT_LOG_ERR, "VIDIOC_QBUF (%d) failed (%d)\r\n", i, ret);
return ret;
}
log_print(HT_LOG_INFO, "buffer %d: addr=0x%ul, len=%d\r\n", i, (unsigned long)m_pBufStart[i], m_pBufLen[i]);
}
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(m_fd, VIDIOC_STREAMON, &type);
if (ret < 0)
{
log_print(HT_LOG_ERR, "VIDIOC_STREAMON failed (%d)\r\n", ret);
return ret;
}
return 0;
}
void CLVideoCapture::mmapUninit()
{
uint32 i;
enum v4l2_buf_type type;
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
/* We do not check for the result, because we could
* not do anything about it anyway...
*/
ioctl(m_fd, VIDIOC_STREAMOFF, &type);
for (i = 0; i < m_nBuffers; i++)
{
munmap(m_pBufStart[i], m_pBufLen[i]);
}
if (m_pBufStart)
{
free(m_pBufStart);
}
if (m_pBufLen)
{
free(m_pBufLen);
}
m_pBufStart = NULL;
m_pBufLen = NULL;
}
int CLVideoCapture::toLocalVideoFormat(int v4l2_pixfmt)
{
int pixfmt = VIDEO_FMT_NONE;
switch (v4l2_pixfmt)
{
case V4L2_PIX_FMT_YUYV:
pixfmt = VIDEO_FMT_YUYV422;
break;
case V4L2_PIX_FMT_YUV420:
case V4L2_PIX_FMT_YVU420:
pixfmt = VIDEO_FMT_YUV420P;
break;
case V4L2_PIX_FMT_YUV422P:
pixfmt = VIDEO_FMT_YUV422P;
break;
case V4L2_PIX_FMT_UYVY:
pixfmt = VIDEO_FMT_UYVY422;
break;
case V4L2_PIX_FMT_BGR24:
pixfmt = VIDEO_FMT_BGR24;
break;
case V4L2_PIX_FMT_RGB24:
pixfmt = VIDEO_FMT_RGB24;
break;
case V4L2_PIX_FMT_BGR32:
pixfmt = VIDEO_FMT_BGR32;
break;
case V4L2_PIX_FMT_RGB32:
pixfmt = VIDEO_FMT_RGB32;
break;
case V4L2_PIX_FMT_NV12:
pixfmt = VIDEO_FMT_NV12;
break;
}
return pixfmt;
}