/*
* Copyright (C) 2014 Intel Corporation. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <sys/eventfd.h>
#include <poll.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <linux/videodev2.h>
#include <sys/prctl.h>
#include "v4l2_encode.h"
#include "v4l2_decode.h"
#include "common/log.h"
#include "common/common_def.h"
#include "vaapi/vaapidisplay.h"
#include <algorithm>
#if defined(__ENABLE_WAYLAND__)
#include <va/va_wayland.h>
#endif
typedef SharedPtr < V4l2CodecBase > V4l2CodecPtr;
#define THREAD_NAME(thread) (thread == INPUT ? "V4L2-INPUT" : "V4L2-OUTPUT")
#define DEBUG_FRAME_LIST(list, listType, maxSize) do { \
std::listType<int>::iterator it = list.begin(); \
char buf[256]; \
int i=0; \
\
buf[0] = '\0'; \
while (it != list.end()) { \
sprintf(&buf[i], "<%02d>", *it++); \
i += 4; \
} \
DEBUG("%s size=%d, elements: %s", \
#list, static_cast<int>(list.size()), buf); \
ASSERT(list.size() <= maxSize); \
} while (0)
V4l2CodecPtr V4l2CodecBase::createCodec(const char* name, int32_t flags)
{
V4l2CodecPtr codec;
if (!strcmp(name, "encoder"))
codec.reset(new V4l2Encoder());
else if (!strcmp(name, "decoder"))
codec.reset(new V4l2Decoder());
ASSERT(codec);
codec->open(name, flags);
return codec;
}
V4l2CodecBase::V4l2CodecBase()
: m_memoryType(VIDEO_DATA_MEMORY_TYPE_RAW_COPY)
, m_started(false)
, m_svct(false)
, m_isThumbnailMode(false)
, m_hasEvent(false)
, m_inputThreadCond(m_frameLock[INPUT])
, m_outputThreadCond(m_frameLock[OUTPUT])
, m_eosState(EosStateNormal)
{
m_maxBufferCount[INPUT] = 0;
m_maxBufferCount[OUTPUT] = 0;
m_bufferPlaneCount[INPUT] = 0;
m_bufferPlaneCount[OUTPUT] = 0;
m_memoryMode[INPUT] = 0;
m_memoryMode[OUTPUT] = 0;
m_pixelFormat[INPUT] = 0;
m_pixelFormat[OUTPUT] = 0;
m_streamOn[INPUT] = false;
m_streamOn[OUTPUT] = false;
m_threadOn[INPUT] = false;
m_threadOn[OUTPUT] = false;
m_threadCond[INPUT] = &m_inputThreadCond;
m_threadCond[OUTPUT] = &m_outputThreadCond;
m_reqBufState[INPUT] = RBS_Normal;
m_reqBufState[OUTPUT] = RBS_Normal;
m_fd[0] = -1;
m_fd[1] = -1;
#ifdef __ENABLE_DEBUG__
m_frameCount[INPUT] = 0;
m_frameCount[OUTPUT] = 0;
#endif
memset(&m_nativeDisplay, 0, sizeof(m_nativeDisplay));
}
V4l2CodecBase::~V4l2CodecBase()
{
ASSERT(!m_streamOn[INPUT]);
ASSERT(!m_streamOn[OUTPUT]);
ASSERT(!m_threadOn[INPUT]);
ASSERT(!m_threadOn[OUTPUT]);
INFO("codec is released, m_fd[0]: %d", m_fd[0]);
}
void V4l2CodecBase::setEosState(EosState eosState)
{
AutoLock locker(m_codecLock);
m_eosState = eosState;
}
bool V4l2CodecBase::open(const char* name, int32_t flags)
{
m_fd[0] = eventfd(0, EFD_SEMAPHORE | EFD_CLOEXEC); // event for codec library, block on read(), one event per read()
m_fd[1] = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC); // for interrupt (escape from poll)
return true;
}
bool V4l2CodecBase::close()
{
bool ret = true;
int timeOut = 10;
INFO("m_streamOn[INPUT]: %d, m_streamOn[OUTPUT]: %d, m_threadOn[INPUT]: %d, m_threadOn[OUTPUT]: %d",
m_streamOn[INPUT], m_streamOn[OUTPUT], m_threadOn[INPUT], m_threadOn[OUTPUT]);
// wait until input/output thread terminate
while ((m_threadOn[INPUT] || m_threadOn[OUTPUT]) && timeOut) {
usleep(10000);
timeOut--;
}
for (uint32_t i=0; i<m_maxBufferCount[OUTPUT]; i++)
recycleOutputBuffer(i);
ASSERT(!m_threadOn[INPUT]);
ASSERT(!m_threadOn[OUTPUT]);
ret = stop();
ASSERT(ret);
::close(m_fd[0]);
::close(m_fd[1]);
return true;
}
void V4l2CodecBase::workerThread()
{
bool ret = true;
int thread = -1;
uint32_t index = 0xffffffff;
{
AutoLock locker(m_codecLock);
if (m_streamOn[INPUT] && !m_threadOn[INPUT])
thread = INPUT;
else if (m_streamOn[OUTPUT] && !m_threadOn[OUTPUT])
thread = OUTPUT;
else {
ERROR("fail to create v4l2 work thread, unexpected status");
return;
}
INFO("create work thread for %s", THREAD_NAME(thread));
prctl(PR_SET_NAME, THREAD_NAME(thread)) ;
m_threadOn[thread] = true;
}
while (m_streamOn[thread]) {
{
AutoLock locker(m_frameLock[thread]);
//ack i/o thread that it is safe to release the buffer queue
if (m_reqBufState[thread] == RBS_Request) {
m_reqBufState[thread] = RBS_Acknowledge;
m_threadCond[thread]->signal();
}
// wait until i/o thead has released the buffer queue
while (m_reqBufState[thread] == RBS_Acknowledge) {
m_threadCond[thread]->wait();
}
}
{
AutoLock locker(m_frameLock[thread]);
if (m_framesTodo[thread].empty()) {
DEBUG("%s thread wait because m_framesTodo is empty", THREAD_NAME(thread));
m_threadCond[thread]->wait(); // wait if no todo frame is available
continue;
}
index = (uint32_t)(m_framesTodo[thread].front());
}
// for decode, outputPulse may update index
ret = (thread == INPUT) ? inputPulse(index) : outputPulse(index);
{
AutoLock locker(m_frameLock[thread]);
// wait until EOS is processed on OUTPUT port
if (thread == INPUT && m_eosState == EosStateInput) {
m_threadCond[!thread]->signal();
while (m_eosState == EosStateInput) {
m_threadCond[thread]->wait();
}
DEBUG("flush-debug flush done, INPUT thread continue");
setEosState(EosStateNormal);
}
if (ret) {
if (thread == OUTPUT) {
// decoder output is in random order
// encoder output is FIFO for now since we does additional copy in v4l2_encode; it can be random order if we use a pool for coded buffer.
std::list<int>::iterator itList = std::find(m_framesTodo[OUTPUT].begin(), m_framesTodo[OUTPUT].end(), index);
ASSERT(itList != m_framesTodo[OUTPUT].end());
m_framesTodo[OUTPUT].erase(itList);
} else
m_framesTodo[thread].pop_front();
m_framesDone[thread].push_back(index);
setDeviceEvent(0);
#ifdef __ENABLE_DEBUG__
m_frameCount[thread]++;
DEBUG("m_frameCount[%s]: %d", THREAD_NAME(thread), m_frameCount[thread]);
#endif
DEBUG("%s thread wake up %s thread after process one frame", THREAD_NAME(thread), THREAD_NAME(!thread));
m_threadCond[!thread]->signal(); // encode/getOutput one frame success, wakeup the other thread
} else {
if (thread == OUTPUT && m_eosState == EosStateOutput) {
m_threadCond[!thread]->signal();
DEBUG("flush-debug, wakeup INPUT thread out of EOS waiting");
}
DEBUG("%s thread wait because operation on yami fails", THREAD_NAME(thread));
m_threadCond[thread]->wait(); // wait if encode/getOutput fail (encode hw is busy or no available output)
}
}//protected by mLock
DEBUG("fd: %d", m_fd[0]);
}
// VDA flush goes here, clear frames
{
AutoLock locker(m_frameLock[thread]);
m_framesTodo[thread].clear();
m_framesDone[thread].clear();
if (thread == INPUT) {
flush();
}
DEBUG("%s worker thread exit", THREAD_NAME(thread));
}
m_threadOn[thread] = false;
}
static void* _workerThread(void *arg)
{
V4l2CodecBase *v4l2Codec = static_cast<V4l2CodecBase*>(arg);
pthread_detach(pthread_self());
v4l2Codec->workerThread();
return NULL;
}
#if defined(__ENABLE_DEBUG__)
const char* V4l2CodecBase::IoctlCommandString(int command)
{
static const char* unknown = "Unkonwn command";
#define IOCTL_COMMAND_STRING_MAP(cmd) {cmd, #cmd}
static struct IoctlCommanMap{
long unsigned int command;
const char* cmdStr;
} ioctlCommandMap[] = {
IOCTL_COMMAND_STRING_MAP(VIDIOC_QUERYCAP),
IOCTL_COMMAND_STRING_MAP(VIDIOC_STREAMON),
IOCTL_COMMAND_STRING_MAP(VIDIOC_STREAMOFF),
IOCTL_COMMAND_STRING_MAP(VIDIOC_QUERYBUF),
IOCTL_COMMAND_STRING_MAP(VIDIOC_REQBUFS),
IOCTL_COMMAND_STRING_MAP(VIDIOC_QBUF),
IOCTL_COMMAND_STRING_MAP(VIDIOC_DQBUF),
IOCTL_COMMAND_STRING_MAP(VIDIOC_S_EXT_CTRLS),
IOCTL_COMMAND_STRING_MAP(VIDIOC_S_PARM),
IOCTL_COMMAND_STRING_MAP(VIDIOC_S_FMT),
IOCTL_COMMAND_STRING_MAP(VIDIOC_S_CROP),
IOCTL_COMMAND_STRING_MAP(VIDIOC_SUBSCRIBE_EVENT),
IOCTL_COMMAND_STRING_MAP(VIDIOC_DQEVENT),
IOCTL_COMMAND_STRING_MAP(VIDIOC_G_FMT),
IOCTL_COMMAND_STRING_MAP(VIDIOC_G_CTRL)
};
// int --> long unsigned is different from int-->uint-->long unsigned
unsigned int u_cmd = (unsigned int)command;
long unsigned int lu_cmd = (long unsigned int) u_cmd;
size_t i;
for (i=0; i<sizeof(ioctlCommandMap)/sizeof(IoctlCommanMap); i++)
if (ioctlCommandMap[i].command == lu_cmd)
return ioctlCommandMap[i].cmdStr;
return unknown;
}
#endif
int32_t V4l2CodecBase::ioctl(int command, void* arg)
{
int32_t ret = 0;
bool boolRet = true;
int port = -1;
switch (command) {
case VIDIOC_QUERYCAP: {
// ::Initialize
struct v4l2_capability *caps = static_cast<struct v4l2_capability *>(arg);
caps->capabilities = V4L2_CAP_VIDEO_CAPTURE_MPLANE | V4L2_CAP_VIDEO_OUTPUT_MPLANE | V4L2_CAP_STREAMING;
}
break;
case VIDIOC_STREAMON: {
// ::Enqueue
__u32 type = * ((__u32*)arg);
GET_PORT_INDEX(port, type, ret);
ASSERT(ret != -1);
if (port == INPUT) {
DEBUG("start decoding/encoding");
boolRet = start();
ASSERT(boolRet);
DEBUG("INPUT port got STREAMON, escape from flushing state");
releaseCodecLock(true);
}
m_streamOn[port] = true;
if (pthread_create(&m_worker[port], NULL, _workerThread, this) != 0) {
ret = -1;
ERROR("fail to create input worker thread");
}
}
break;
case VIDIOC_STREAMOFF: {
// ::StopDevicePoll
__u32 type = * ((__u32*)arg);
GET_PORT_INDEX(port, type, ret);
ASSERT(ret != -1);
m_streamOn[port] = false;
// wait until the worker thread exit, some cleanup happend there
while (m_threadOn[port]) {
if (port == INPUT) {
DEBUG("INPUT port got STREAMOFF, release internal lock");
setEosState(EosStateNormal);
releaseCodecLock(false);
}
DEBUG("%s port got STREAMOFF, wait until the worker thread exit/cleanup", THREAD_NAME(port));
m_threadCond[port]->broadcast();
usleep(5000);
}
}
break;
case VIDIOC_REQBUFS: {
struct v4l2_requestbuffers *reqbufs = static_cast<struct v4l2_requestbuffers *>(arg);
if (reqbufs->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
port = INPUT;
} else if (reqbufs->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
port = OUTPUT;
} else {
ret = -1;
ERROR("unknown request buffer type: %d", reqbufs->type);
break;
}
// ASSERT(port == INPUT || reqbufs->memory == m_memoryMode[port]);
m_memoryMode[port] = reqbufs->memory;
{
AutoLock locker(m_frameLock[port]);
// initial status of buffers are at client side
if (reqbufs->count > 0) {
// ::CreateInputBuffers()/CreateOutputBuffers()
ASSERT(reqbufs->count <= m_maxBufferCount[port]);
reqbufs->count = m_maxBufferCount[port];
} else {
// ::DestroyInputBuffers()/:DestroyOutputBuffers()
}
m_framesDone[port].clear(); //it is safe to clear m_framesDone, but race condition for clearing m_framesTodo
if (m_framesTodo[port].empty())
break;
// info work thread that we want to release the buffer queue
m_reqBufState[port] = RBS_Request;
// in case work thread is locked by yami codec
releaseCodecLock(false);
//try to wakeup workthread (workthread may not be active, after EOS for example)
m_threadCond[port]->signal();
// wait until work thread doesn't work on current buffer queue
while (m_reqBufState[port] != RBS_Acknowledge) {
m_threadCond[port]->wait();
}
// bring yami codec back to normal
releaseCodecLock(true);
DEBUG("m_framesTodo[%d].size %zu, m_framesDone[%d].size %zu\n",
port, m_framesTodo[port].size(), port, m_framesDone[port].size());
m_framesTodo[port].clear();
m_reqBufState[port] = (reqbufs->count > 0) ? RBS_FormatChanged : RBS_Released;
m_threadCond[port]->signal();
}
}
break;
case VIDIOC_QBUF: {
struct v4l2_buffer *qbuf = static_cast<struct v4l2_buffer *>(arg);
GET_PORT_INDEX(port, qbuf->type, ret);
ASSERT(ret != -1);
// ::EnqueueInputRecord/EnqueueOutputRecord
#ifndef __ENABLE_WAYLAND__
ASSERT(qbuf->memory == m_memoryMode[port]);
ASSERT (qbuf->length == m_bufferPlaneCount[port]);
#endif
if (port == INPUT) {
bool _ret = acceptInputBuffer(qbuf);
ASSERT(_ret);
} else {
bool _ret = recycleOutputBuffer(qbuf->index);
ASSERT(_ret);
}
{
AutoLock locker(m_frameLock[port]);
if (m_reqBufState[port] == RBS_Normal ||
m_reqBufState[port] == RBS_FormatChanged ){
m_framesTodo[port].push_back(qbuf->index);
m_threadCond[port]->signal();
} else {
ret = EAGAIN;
}
}
}
break;
case VIDIOC_DQBUF: {
// ::Dequeue
struct v4l2_buffer *dqbuf = static_cast<struct v4l2_buffer *>(arg);
GET_PORT_INDEX(port, dqbuf->type, ret);
ASSERT(ret != -1);
ASSERT(dqbuf->memory == m_memoryMode[port]);
DEBUG("m_framesDone[%d].size(): %zu, m_reqBufState[port]: %d",
port, m_framesDone[port].size(), m_reqBufState[port]);
{
AutoLock locker (m_frameLock[port]);
if (m_framesDone[port].empty() ||
((m_reqBufState[port] != RBS_Normal &&
m_reqBufState[port] != RBS_FormatChanged))) {
ret = -1;
errno = EAGAIN;
break;
}
}
ASSERT(dqbuf->length == m_bufferPlaneCount[port]);
dqbuf->index = m_framesDone[port].front();
ASSERT(dqbuf->index < m_maxBufferCount[port]);
bool _ret = true;
if (port == OUTPUT) {
_ret = giveOutputBuffer(dqbuf);
} else {
_ret = recycleInputBuffer(dqbuf);
}
ASSERT(_ret);
{
AutoLock locker (m_frameLock[port]);
m_framesDone[port].pop_front();
DEBUG("%s port dqbuf->index: %d", THREAD_NAME(port), dqbuf->index);
}
}
break;
default:
ERROR("unknown command type");
ret = -1;
break;
}
return ret;
}
int32_t V4l2CodecBase::setDeviceEvent(int index)
{
uint64_t buf = 1;
int32_t ret = -1;
ASSERT(index == 0 || index == 1);
if (index == 1)
DEBUG("SetDevicePollInterrupt to unblock pool from client");
ret = write(m_fd[index], &buf, sizeof(buf));
if (ret != sizeof(uint64_t)) {
ERROR ("device %s setDeviceEvent(): write() failed", index ? "interrupt" : "event");
return -1;
}
if (index == 1)
DEBUG("SetDevicePollInterrupt OK to unblock pool from client");
return 0;
}
int32_t V4l2CodecBase::clearDeviceEvent(int index)
{
uint64_t buf;
int ret = -1;
ASSERT(index == 0 || index == 1);
if (index == 1)
DEBUG("ClearDevicePollInterrupt to block pool from client except there is event");
ret = read(m_fd[index], &buf, sizeof(buf));
if (ret == -1) {
if (errno == EAGAIN) {
// No interrupt flag set, and we're reading nonblocking. Not an error.
return 0;
} else {
ERROR("device %s clearDeviceEvent(): read() failed", index ? "interrupt" : "event");
return -1;
}
}
return 0;
}
int32_t V4l2CodecBase::poll(bool poll_device, bool* event_pending)
{
struct pollfd pollfds[2];
nfds_t nfds;
pollfds[0].fd = m_fd[1];
pollfds[0].events = POLLIN | POLLERR;
nfds = 1;
if (poll_device) {
DEBUG("Poll(): adding device fd to poll() set");
pollfds[nfds].fd = m_fd[0];
pollfds[nfds].events = POLLIN | POLLERR;
nfds++;
}
if (::poll(pollfds, nfds, -1) == -1) {
ERROR("poll() failed");
return -1;
}
{
YamiMediaCodec::AutoLock locker(m_codecLock);
*event_pending = m_hasEvent;
}
// clear event
if (pollfds[1].revents & POLLIN)
clearDeviceEvent(0);
return 0;
}
bool V4l2CodecBase::hasCodecEvent()
{
YamiMediaCodec::AutoLock locker(m_codecLock);
return m_hasEvent;
}
void V4l2CodecBase::setCodecEvent()
{
{
YamiMediaCodec::AutoLock locker(m_codecLock);
m_hasEvent = true;
}
//notify user about this
setDeviceEvent(0);
}
void V4l2CodecBase::clearCodecEvent()
{
YamiMediaCodec::AutoLock locker(m_codecLock);
m_hasEvent = false;
}
struct FormatEntry {
uint32_t format;
const char* mime;
};
static const FormatEntry FormatEntrys[] = {
{V4L2_PIX_FMT_H264, YAMI_MIME_H264},
{V4L2_PIX_FMT_VP8, YAMI_MIME_VP8},
{V4L2_PIX_FMT_VP9, YAMI_MIME_VP9},
{V4L2_PIX_FMT_MJPEG, YAMI_MIME_JPEG},
{V4L2_PIX_FMT_MPEG2, YAMI_MIME_MPEG2},
{V4L2_PIX_FMT_VC1, YAMI_MIME_VC1}
};
uint32_t v4l2PixelFormatFromMime(const char* mime)
{
uint32_t format = 0;
for (uint32_t i = 0; i < N_ELEMENTS(FormatEntrys); i++) {
const FormatEntry* entry = FormatEntrys + i;
if (strcmp(mime, entry->mime) == 0) {
format = entry->format;
break;
}
}
return format;
}
const char* mimeFromV4l2PixelFormat(uint32_t pixelFormat)
{
const char* mime = NULL;
for (size_t i = 0; i < N_ELEMENTS(FormatEntrys); i++) {
const FormatEntry* entry = FormatEntrys + i;
if (entry->format == pixelFormat) {
mime = entry->mime;
break;
}
}
return mime;
}
#if defined(__ENABLE_WAYLAND__)
bool V4l2CodecBase::setWaylandDisplay(struct wl_display* wlDisplay)
{
m_nativeDisplay.type = NATIVE_DISPLAY_WAYLAND;
m_nativeDisplay.handle = (intptr_t)wlDisplay;
return true;
};
#endif //__ENABLE_WAYLAND__
#if defined(__ENABLE_X11__)
bool V4l2CodecBase::setXDisplay(Display* x11Display)
{
m_nativeDisplay.type = NATIVE_DISPLAY_X11;
m_nativeDisplay.handle = (intptr_t)x11Display;
return true;
};
#endif //__ENABLE_X11__
bool V4l2CodecBase::setDrmFd(int fd)
{
m_nativeDisplay.type = NATIVE_DISPLAY_DRM;
m_nativeDisplay.handle = (intptr_t)fd;
return true;
};