2015-05-14 10:46:28 +08:00
|
|
|
/*
|
2015-06-06 09:40:08 +08:00
|
|
|
Copyright © 2015 by The qTox Project
|
|
|
|
|
2015-05-14 10:46:28 +08:00
|
|
|
This file is part of qTox, a Qt-based graphical interface for Tox.
|
|
|
|
|
2015-06-06 09:40:08 +08:00
|
|
|
qTox is libre software: you can redistribute it and/or modify
|
2015-05-14 10:46:28 +08:00
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
2015-06-06 09:40:08 +08:00
|
|
|
|
|
|
|
qTox is distributed in the hope that it will be useful,
|
2015-05-14 10:46:28 +08:00
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
2015-06-06 09:40:08 +08:00
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
2015-05-14 10:46:28 +08:00
|
|
|
|
2015-06-06 09:40:08 +08:00
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with qTox. If not, see <http://www.gnu.org/licenses/>.
|
2015-05-14 10:46:28 +08:00
|
|
|
*/
|
|
|
|
|
|
|
|
extern "C" {
|
|
|
|
#include <libavcodec/avcodec.h>
|
|
|
|
#include <libavdevice/avdevice.h>
|
|
|
|
#include <libavformat/avformat.h>
|
|
|
|
#include <libswscale/swscale.h>
|
|
|
|
}
|
|
|
|
#include <QMutexLocker>
|
|
|
|
#include <QDebug>
|
|
|
|
#include <QtConcurrent/QtConcurrentRun>
|
|
|
|
#include <memory>
|
|
|
|
#include <functional>
|
|
|
|
#include "camerasource.h"
|
|
|
|
#include "cameradevice.h"
|
|
|
|
#include "videoframe.h"
|
|
|
|
|
2015-06-26 23:38:53 +08:00
|
|
|
CameraSource* CameraSource::instance{nullptr};
|
|
|
|
|
2015-05-14 10:46:28 +08:00
|
|
|
CameraSource::CameraSource()
|
2016-01-27 03:14:58 +08:00
|
|
|
: deviceName{"none"}, device{nullptr}, mode(VideoMode{0,0,0,0}),
|
2015-06-26 23:38:53 +08:00
|
|
|
cctx{nullptr}, cctxOrig{nullptr}, videoStreamIndex{-1},
|
2015-10-24 11:19:23 +08:00
|
|
|
_isOpen{false}, streamBlocker{false}, subscriptions{0}
|
2015-05-14 10:46:28 +08:00
|
|
|
{
|
2015-10-11 14:38:44 +08:00
|
|
|
subscriptions = 0;
|
2015-06-26 23:38:53 +08:00
|
|
|
av_register_all();
|
|
|
|
avdevice_register_all();
|
2015-05-14 10:46:28 +08:00
|
|
|
}
|
|
|
|
|
2015-06-26 23:38:53 +08:00
|
|
|
CameraSource& CameraSource::getInstance()
|
2015-05-16 10:01:38 +08:00
|
|
|
{
|
2015-06-26 23:38:53 +08:00
|
|
|
if (!instance)
|
|
|
|
instance = new CameraSource();
|
|
|
|
return *instance;
|
2015-05-16 10:01:38 +08:00
|
|
|
}
|
|
|
|
|
2015-06-27 01:04:53 +08:00
|
|
|
void CameraSource::destroyInstance()
|
|
|
|
{
|
|
|
|
if (instance)
|
|
|
|
{
|
|
|
|
delete instance;
|
|
|
|
instance = nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-26 23:38:53 +08:00
|
|
|
void CameraSource::open()
|
2015-05-14 10:46:28 +08:00
|
|
|
{
|
2015-06-26 23:38:53 +08:00
|
|
|
open(CameraDevice::getDefaultDeviceName());
|
|
|
|
}
|
|
|
|
|
|
|
|
void CameraSource::open(const QString deviceName)
|
|
|
|
{
|
2016-01-27 03:14:58 +08:00
|
|
|
open(deviceName, VideoMode{0,0,0,0});
|
2015-06-26 23:38:53 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void CameraSource::open(const QString DeviceName, VideoMode Mode)
|
|
|
|
{
|
2015-10-24 11:19:23 +08:00
|
|
|
streamBlocker = true;
|
2015-10-10 07:24:04 +08:00
|
|
|
QMutexLocker l{&biglock};
|
2015-06-26 23:38:53 +08:00
|
|
|
|
|
|
|
if (DeviceName == deviceName && Mode == mode)
|
2015-10-24 11:19:23 +08:00
|
|
|
{
|
|
|
|
streamBlocker = false;
|
2015-06-26 23:38:53 +08:00
|
|
|
return;
|
2015-10-24 11:19:23 +08:00
|
|
|
}
|
2015-06-26 23:38:53 +08:00
|
|
|
|
|
|
|
if (subscriptions)
|
|
|
|
closeDevice();
|
|
|
|
|
|
|
|
deviceName = DeviceName;
|
|
|
|
mode = Mode;
|
2015-09-28 07:04:39 +08:00
|
|
|
_isOpen = (deviceName != "none");
|
2015-06-26 23:38:53 +08:00
|
|
|
|
2015-09-28 07:04:39 +08:00
|
|
|
if (subscriptions && _isOpen)
|
2015-06-26 23:38:53 +08:00
|
|
|
openDevice();
|
2015-10-24 11:19:23 +08:00
|
|
|
|
|
|
|
streamBlocker = false;
|
2015-06-26 23:38:53 +08:00
|
|
|
}
|
2015-06-22 20:59:55 +08:00
|
|
|
|
2015-06-26 23:38:53 +08:00
|
|
|
void CameraSource::close()
|
|
|
|
{
|
|
|
|
open("none");
|
2015-05-14 10:46:28 +08:00
|
|
|
}
|
|
|
|
|
2015-09-28 07:04:39 +08:00
|
|
|
bool CameraSource::isOpen()
|
|
|
|
{
|
|
|
|
return _isOpen;
|
|
|
|
}
|
|
|
|
|
2015-05-14 10:46:28 +08:00
|
|
|
CameraSource::~CameraSource()
|
|
|
|
{
|
2015-10-10 07:24:04 +08:00
|
|
|
QMutexLocker l{&biglock};
|
2015-05-14 10:46:28 +08:00
|
|
|
|
2015-09-28 07:04:39 +08:00
|
|
|
if (!_isOpen)
|
2015-06-22 20:59:55 +08:00
|
|
|
return;
|
|
|
|
|
2015-05-16 10:01:38 +08:00
|
|
|
// Free all remaining VideoFrame
|
|
|
|
// Locking must be done precisely this way to avoid races
|
2015-10-11 14:38:44 +08:00
|
|
|
for (int i = 0; i < freelist.size(); i++)
|
2015-05-16 10:01:38 +08:00
|
|
|
{
|
|
|
|
std::shared_ptr<VideoFrame> vframe = freelist[i].lock();
|
|
|
|
if (!vframe)
|
|
|
|
continue;
|
|
|
|
vframe->releaseFrame();
|
|
|
|
}
|
|
|
|
|
2015-05-14 10:46:28 +08:00
|
|
|
if (cctx)
|
|
|
|
avcodec_free_context(&cctx);
|
2015-05-16 10:01:38 +08:00
|
|
|
if (cctxOrig)
|
|
|
|
avcodec_close(cctxOrig);
|
2015-05-14 10:46:28 +08:00
|
|
|
|
2015-10-24 02:30:22 +08:00
|
|
|
if (device)
|
|
|
|
{
|
|
|
|
for(int i = 0; i < subscriptions; i++)
|
|
|
|
device->close();
|
|
|
|
device = nullptr;
|
|
|
|
}
|
2015-10-11 14:38:44 +08:00
|
|
|
|
2015-06-26 23:38:53 +08:00
|
|
|
// Memfence so the stream thread sees a nullptr device
|
|
|
|
std::atomic_thread_fence(std::memory_order_release);
|
2015-10-10 07:24:04 +08:00
|
|
|
l.unlock();
|
2015-05-14 10:46:28 +08:00
|
|
|
|
|
|
|
// Synchronize with our stream thread
|
|
|
|
while (streamFuture.isRunning())
|
|
|
|
QThread::yieldCurrentThread();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CameraSource::subscribe()
|
|
|
|
{
|
2015-10-10 07:24:04 +08:00
|
|
|
QMutexLocker l{&biglock};
|
2015-05-14 10:46:28 +08:00
|
|
|
|
2015-09-28 07:04:39 +08:00
|
|
|
if (!_isOpen)
|
2015-06-22 20:59:55 +08:00
|
|
|
{
|
2015-06-26 23:38:53 +08:00
|
|
|
++subscriptions;
|
2015-06-22 20:59:55 +08:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2015-06-26 23:38:53 +08:00
|
|
|
if (openDevice())
|
2015-05-14 10:46:28 +08:00
|
|
|
{
|
|
|
|
++subscriptions;
|
|
|
|
return true;
|
|
|
|
}
|
2015-06-26 23:38:53 +08:00
|
|
|
else
|
|
|
|
{
|
|
|
|
while (device && !device->close()) {}
|
|
|
|
device = nullptr;
|
|
|
|
cctx = cctxOrig = nullptr;
|
|
|
|
videoStreamIndex = -1;
|
|
|
|
// Memfence so the stream thread sees a nullptr device
|
|
|
|
std::atomic_thread_fence(std::memory_order_release);
|
|
|
|
return false;
|
|
|
|
}
|
2015-10-11 14:38:44 +08:00
|
|
|
|
2015-06-26 23:38:53 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void CameraSource::unsubscribe()
|
|
|
|
{
|
2015-11-08 01:30:01 +08:00
|
|
|
streamBlocker = true;
|
2015-10-10 07:24:04 +08:00
|
|
|
QMutexLocker l{&biglock};
|
2015-11-08 01:30:01 +08:00
|
|
|
streamBlocker = false;
|
2015-06-26 23:38:53 +08:00
|
|
|
|
2015-09-28 07:04:39 +08:00
|
|
|
if (!_isOpen)
|
2015-06-26 23:38:53 +08:00
|
|
|
{
|
2015-10-10 07:24:04 +08:00
|
|
|
--subscriptions;
|
2015-06-26 23:38:53 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!device)
|
|
|
|
{
|
2015-10-15 01:53:46 +08:00
|
|
|
qWarning() << "Unsubscribing with zero subscriber";
|
2015-06-26 23:38:53 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-10-11 14:38:44 +08:00
|
|
|
if (subscriptions - 1 == 0)
|
2015-06-26 23:38:53 +08:00
|
|
|
{
|
|
|
|
closeDevice();
|
2015-10-10 07:24:04 +08:00
|
|
|
l.unlock();
|
2015-06-26 23:38:53 +08:00
|
|
|
|
|
|
|
// Synchronize with our stream thread
|
|
|
|
while (streamFuture.isRunning())
|
|
|
|
QThread::yieldCurrentThread();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
device->close();
|
|
|
|
}
|
2015-10-11 14:38:44 +08:00
|
|
|
subscriptions--;
|
2015-06-26 23:38:53 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
bool CameraSource::openDevice()
|
|
|
|
{
|
2015-10-10 07:24:04 +08:00
|
|
|
qDebug() << "Opening device "<<deviceName;
|
|
|
|
|
2015-06-26 23:38:53 +08:00
|
|
|
if (device)
|
|
|
|
{
|
|
|
|
device->open();
|
|
|
|
return true;
|
|
|
|
}
|
2015-05-14 10:46:28 +08:00
|
|
|
|
|
|
|
// We need to create a new CameraDevice
|
|
|
|
AVCodec* codec;
|
2015-05-16 10:01:38 +08:00
|
|
|
if (mode)
|
|
|
|
device = CameraDevice::open(deviceName, mode);
|
|
|
|
else
|
|
|
|
device = CameraDevice::open(deviceName);
|
2015-05-14 10:46:28 +08:00
|
|
|
if (!device)
|
|
|
|
{
|
2015-06-22 20:59:55 +08:00
|
|
|
qWarning() << "Failed to open device!";
|
2015-05-14 10:46:28 +08:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-06-26 23:38:53 +08:00
|
|
|
// We need to open the device as many time as we already have subscribers,
|
|
|
|
// otherwise the device could get closed while we still have subscribers
|
2015-10-11 14:38:44 +08:00
|
|
|
for (int i = 0; i < subscriptions; i++)
|
2015-06-26 23:38:53 +08:00
|
|
|
device->open();
|
|
|
|
|
2015-05-14 10:46:28 +08:00
|
|
|
// Find the first video stream
|
2015-10-11 14:38:44 +08:00
|
|
|
for (unsigned i = 0; i < device->context->nb_streams; i++)
|
2015-05-14 10:46:28 +08:00
|
|
|
{
|
2015-10-11 14:38:44 +08:00
|
|
|
if(device->context->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
|
2015-05-14 10:46:28 +08:00
|
|
|
{
|
2015-10-11 14:38:44 +08:00
|
|
|
videoStreamIndex = i;
|
2015-05-14 10:46:28 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (videoStreamIndex == -1)
|
2015-06-26 23:38:53 +08:00
|
|
|
return false;
|
2015-05-14 10:46:28 +08:00
|
|
|
|
|
|
|
// Get a pointer to the codec context for the video stream
|
2015-10-11 14:38:44 +08:00
|
|
|
cctxOrig = device->context->streams[videoStreamIndex]->codec;
|
|
|
|
codec = avcodec_find_decoder(cctxOrig->codec_id);
|
2015-05-14 10:46:28 +08:00
|
|
|
if(!codec)
|
2015-06-26 23:38:53 +08:00
|
|
|
return false;
|
2015-05-14 10:46:28 +08:00
|
|
|
|
|
|
|
// Copy context, since we apparently aren't allowed to use the original
|
|
|
|
cctx = avcodec_alloc_context3(codec);
|
|
|
|
if(avcodec_copy_context(cctx, cctxOrig) != 0)
|
2015-06-26 23:38:53 +08:00
|
|
|
return false;
|
2015-10-11 14:38:44 +08:00
|
|
|
|
2015-05-14 10:46:28 +08:00
|
|
|
cctx->refcounted_frames = 1;
|
|
|
|
|
|
|
|
// Open codec
|
|
|
|
if(avcodec_open2(cctx, codec, nullptr)<0)
|
|
|
|
{
|
|
|
|
avcodec_free_context(&cctx);
|
2015-06-26 23:38:53 +08:00
|
|
|
return false;
|
2015-05-14 10:46:28 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (streamFuture.isRunning())
|
2015-06-26 23:38:53 +08:00
|
|
|
qDebug() << "The stream thread is already running! Keeping the current one open.";
|
2015-05-14 10:46:28 +08:00
|
|
|
else
|
|
|
|
streamFuture = QtConcurrent::run(std::bind(&CameraSource::stream, this));
|
|
|
|
|
|
|
|
// Synchronize with our stream thread
|
|
|
|
while (!streamFuture.isRunning())
|
|
|
|
QThread::yieldCurrentThread();
|
|
|
|
|
2015-07-22 02:38:43 +08:00
|
|
|
emit deviceOpened();
|
|
|
|
|
2015-05-14 10:46:28 +08:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2015-06-26 23:38:53 +08:00
|
|
|
void CameraSource::closeDevice()
|
2015-05-14 10:46:28 +08:00
|
|
|
{
|
2015-10-10 07:24:04 +08:00
|
|
|
qDebug() << "Closing device "<<deviceName;
|
|
|
|
|
2015-06-26 23:38:53 +08:00
|
|
|
// Free all remaining VideoFrame
|
|
|
|
// Locking must be done precisely this way to avoid races
|
2015-10-11 14:38:44 +08:00
|
|
|
for (int i = 0; i < freelist.size(); i++)
|
2015-05-14 10:46:28 +08:00
|
|
|
{
|
2015-06-26 23:38:53 +08:00
|
|
|
std::shared_ptr<VideoFrame> vframe = freelist[i].lock();
|
|
|
|
if (!vframe)
|
|
|
|
continue;
|
|
|
|
vframe->releaseFrame();
|
2015-05-14 10:46:28 +08:00
|
|
|
}
|
2015-06-27 01:04:53 +08:00
|
|
|
freelist.clear();
|
|
|
|
freelist.squeeze();
|
2015-05-14 10:46:28 +08:00
|
|
|
|
2015-06-26 23:38:53 +08:00
|
|
|
// Free our resources and close the device
|
|
|
|
videoStreamIndex = -1;
|
|
|
|
avcodec_free_context(&cctx);
|
|
|
|
avcodec_close(cctxOrig);
|
|
|
|
cctxOrig = nullptr;
|
|
|
|
while (device && !device->close()) {}
|
|
|
|
device = nullptr;
|
|
|
|
// Memfence so the stream thread sees a nullptr device
|
|
|
|
std::atomic_thread_fence(std::memory_order_release);
|
2015-05-14 10:46:28 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void CameraSource::stream()
|
|
|
|
{
|
|
|
|
auto streamLoop = [=]()
|
|
|
|
{
|
|
|
|
AVFrame* frame = av_frame_alloc();
|
|
|
|
if (!frame)
|
|
|
|
return;
|
|
|
|
frame->opaque = nullptr;
|
|
|
|
|
|
|
|
AVPacket packet;
|
|
|
|
if (av_read_frame(device->context, &packet)<0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Only keep packets from the right stream;
|
|
|
|
if (packet.stream_index==videoStreamIndex)
|
|
|
|
{
|
|
|
|
// Decode video frame
|
|
|
|
int frameFinished;
|
|
|
|
avcodec_decode_video2(cctx, frame, &frameFinished, &packet);
|
|
|
|
if (!frameFinished)
|
|
|
|
return;
|
|
|
|
|
2015-10-10 07:24:04 +08:00
|
|
|
freelistLock.lock();
|
2015-05-15 00:40:12 +08:00
|
|
|
|
2015-05-14 10:46:28 +08:00
|
|
|
int freeFreelistSlot = getFreelistSlotLockless();
|
|
|
|
auto frameFreeCb = std::bind(&CameraSource::freelistCallback, this, freeFreelistSlot);
|
|
|
|
std::shared_ptr<VideoFrame> vframe = std::make_shared<VideoFrame>(frame, frameFreeCb);
|
|
|
|
freelist.append(vframe);
|
2015-10-10 07:24:04 +08:00
|
|
|
freelistLock.unlock();
|
2015-05-14 10:46:28 +08:00
|
|
|
emit frameAvailable(vframe);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Free the packet that was allocated by av_read_frame
|
|
|
|
av_free_packet(&packet);
|
|
|
|
};
|
|
|
|
|
|
|
|
forever {
|
2015-10-10 07:24:04 +08:00
|
|
|
biglock.lock();
|
2015-05-14 10:46:28 +08:00
|
|
|
|
2015-06-26 23:38:53 +08:00
|
|
|
// When a thread makes device null, it releases it, so we acquire here
|
|
|
|
std::atomic_thread_fence(std::memory_order_acquire);
|
2015-05-14 10:46:28 +08:00
|
|
|
if (!device)
|
|
|
|
{
|
2015-10-10 07:24:04 +08:00
|
|
|
biglock.unlock();
|
2015-05-14 10:46:28 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
streamLoop();
|
|
|
|
|
|
|
|
// Give a chance to other functions to pick up the lock if needed
|
2015-10-10 07:24:04 +08:00
|
|
|
biglock.unlock();
|
2015-10-24 11:19:23 +08:00
|
|
|
while (streamBlocker)
|
|
|
|
QThread::yieldCurrentThread();
|
2015-05-14 10:46:28 +08:00
|
|
|
QThread::yieldCurrentThread();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CameraSource::freelistCallback(int freelistIndex)
|
|
|
|
{
|
2015-10-10 07:24:04 +08:00
|
|
|
QMutexLocker l{&freelistLock};
|
2015-05-14 10:46:28 +08:00
|
|
|
freelist[freelistIndex].reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
int CameraSource::getFreelistSlotLockless()
|
|
|
|
{
|
|
|
|
int size = freelist.size();
|
2015-10-11 14:38:44 +08:00
|
|
|
for (int i = 0; i < size; ++i)
|
2015-05-14 10:46:28 +08:00
|
|
|
if (freelist[i].expired())
|
|
|
|
return i;
|
2015-10-11 14:38:44 +08:00
|
|
|
|
|
|
|
freelist.resize(size + (size>>1) + 4); // Arbitrary growth strategy, should work well
|
2015-05-14 10:46:28 +08:00
|
|
|
return size;
|
|
|
|
}
|