Commit 0c59c449 authored by Guillaume Roguez's avatar Guillaume Roguez

#26845: move local video camera capture from SipCall to preview.

Make video preview a standalone feature, currently start/stoppable from Preference UI.
parent 53a75bc9
......@@ -180,6 +180,12 @@ VideoControls::stopPreview()
}
}
sfl_video::VideoPreview *
VideoControls::getVideoPreview()
{
return preview_.get();
}
bool
VideoControls::hasPreviewStarted()
{
......
......@@ -126,6 +126,7 @@ class VideoControls : public org::sflphone::SFLphone::VideoControls_adaptor,
void startPreview();
void stopPreview();
bool hasPreviewStarted();
sfl_video::VideoPreview *getVideoPreview();
};
#endif // VIDEO_CONTROLS_H_
......@@ -94,7 +94,8 @@
ManagerImpl::ManagerImpl() :
preferences(), voipPreferences(),
hookPreference(), audioPreference(), shortcutPreferences(),
hasTriedToRegister_(false), audioCodecFactory(), client_(), config_(),
hasTriedToRegister_(false), audioCodecFactory(), client_(),
config_(),
currentCallId_(), currentCallMutex_(), audiodriver_(0), dtmfKey_(),
toneMutex_(), telephoneTone_(), audiofile_(), audioLayerMutex_(),
waitingCalls_(), waitingCallsMutex_(), path_(),
......
......@@ -1869,7 +1869,6 @@ void sdp_media_update_cb(pjsip_inv_session *inv, pj_status_t status)
call->getAudioRtp().setDtmfPayloadType(sdpSession->getTelephoneEventType());
#ifdef SFL_VIDEO
Manager::instance().getVideoControls()->stopPreview();
call->getVideoRtp().updateSDP(*call->getLocalSDP());
call->getVideoRtp().updateDestination(call->getLocalSDP()->getRemoteIP(), call->getLocalSDP()->getRemoteVideoPort());
call->getVideoRtp().start(call->getLocalSDP()->getLocalVideoPort());
......
......@@ -14,7 +14,7 @@ libvideo_la_SOURCES = \
shm_sink.cpp shm_sink.h video_provider.h \
socket_pair.cpp socket_pair.h \
video_decoder.cpp video_decoder.h \
video_encoder.cpp video_encoder.h
video_encoder.cpp video_encoder.h \
video_codec.h
libvideo_la_LIBADD = @LIBAVCODEC_LIBS@ @LIBAVFORMAT_LIBS@ @LIBAVDEVICE_LIBS@ @LIBSWSCALE_LIBS@ @LIBAVUTIL_LIBS@ @UDEV_LIBS@
......
......@@ -35,6 +35,7 @@
// cast to void to avoid compiler warnings about unused return values
#define set_false_atomic(x) static_cast<void>(__sync_fetch_and_and(x, false))
#define set_true_atomic(x) static_cast<void>(__sync_fetch_and_or(x, true))
#define atomic_increment(x) static_cast<void>(__sync_fetch_and_add(x, 1))
#define atomic_decrement(x) static_cast<void>(__sync_fetch_and_sub(x, 1))
......
......@@ -54,16 +54,22 @@ namespace sfl_video {
VideoDecoder::VideoDecoder() :
inputDecoder_(0),
decoderCtx_(0),
rawFrame_(0),
rawFrames_(),
lockedFrame_(-1),
lockedFrameCnt_(0),
lastFrame_(1),
inputCtx_(avformat_alloc_context()),
imgConvertCtx_(0),
interruptCb_(),
scalerCtx_(0),
scaledPicture_(),
accessMutex_(),
streamIndex_(-1),
dstWidth_(0),
dstHeight_(0)
{ }
{
pthread_mutex_init(&accessMutex_, NULL);
}
VideoDecoder::~VideoDecoder()
{
......@@ -79,7 +85,8 @@ namespace sfl_video {
}
sws_freeContext(scalerCtx_);
avcodec_free_frame(&rawFrame_);
pthread_mutex_destroy(&accessMutex_);
}
int VideoDecoder::openInput(const std::string &source_str,
......@@ -92,12 +99,6 @@ namespace sfl_video {
return -1;
}
rawFrame_ = avcodec_alloc_frame();
if (!rawFrame_){
ERROR("avcodec_alloc_frame failed");
return -1;
}
int ret = avformat_open_input(&inputCtx_, source_str.c_str(), iformat,
NULL);
if (ret)
......@@ -198,7 +199,6 @@ namespace sfl_video {
// Guarantee that we free the packet every iteration
VideoPacket video_packet;
AVPacket *inpacket = video_packet.get();
ret = av_read_frame(inputCtx_, inpacket);
if (ret == AVERROR(EAGAIN))
return 0;
......@@ -207,18 +207,33 @@ namespace sfl_video {
return -1;
}
avcodec_get_frame_defaults(rawFrame_);
int idx;
pthread_mutex_lock(&accessMutex_);
if (lockedFrame_ >= 0)
idx = !lockedFrame_;
else
idx = !lastFrame_;
pthread_mutex_unlock(&accessMutex_);
AVFrame *frame = rawFrames_[idx].get();
avcodec_get_frame_defaults(frame);
// is this a packet from the video stream?
if (inpacket->stream_index != streamIndex_)
return 0;
int frameFinished = 0;
const int len = avcodec_decode_video2(decoderCtx_, rawFrame_,
const int len = avcodec_decode_video2(decoderCtx_, frame,
&frameFinished, inpacket);
if (len <= 0)
return -2;
if (frameFinished) {
pthread_mutex_lock(&accessMutex_);
lastFrame_ = idx;
pthread_mutex_unlock(&accessMutex_);
}
return frameFinished;
}
......@@ -230,14 +245,29 @@ namespace sfl_video {
inpacket.data = NULL;
inpacket.size = 0;
avcodec_get_frame_defaults(rawFrame_);
int idx;
pthread_mutex_lock(&accessMutex_);
if (lockedFrame_ >= 0)
idx = !lockedFrame_;
else
idx = !lastFrame_;
pthread_mutex_unlock(&accessMutex_);
AVFrame *frame = rawFrames_[idx].get();
avcodec_get_frame_defaults(frame);
int frameFinished = 0;
const int len = avcodec_decode_video2(decoderCtx_, rawFrame_,
const int len = avcodec_decode_video2(decoderCtx_, frame,
&frameFinished, &inpacket);
if (len <= 0)
return -2;
if (frameFinished) {
pthread_mutex_lock(&accessMutex_);
lastFrame_ = idx;
pthread_mutex_unlock(&accessMutex_);
}
return frameFinished;
}
......@@ -252,26 +282,60 @@ namespace sfl_video {
output_frame->height = height;
}
void VideoDecoder::scale(int flags)
void* VideoDecoder::scale(SwsContext *ctx, int flags)
{
AVFrame *output_frame = scaledPicture_.get();
scalerCtx_ = sws_getCachedContext(scalerCtx_,
decoderCtx_->width,
decoderCtx_->height,
decoderCtx_->pix_fmt,
output_frame->width,
output_frame->height,
(PixelFormat) output_frame->format,
SWS_BICUBIC, /* FIXME: option? */
NULL, NULL, NULL);
if (!scalerCtx_) {
ctx = sws_getCachedContext(ctx,
decoderCtx_->width,
decoderCtx_->height,
decoderCtx_->pix_fmt,
output_frame->width,
output_frame->height,
(PixelFormat) output_frame->format,
SWS_BICUBIC, /* FIXME: option? */
NULL, NULL, NULL);
if (ctx) {
VideoFrame *frame = lockFrame();
if (frame) {
AVFrame *frame_ = frame->get();
sws_scale(ctx, frame_->data, frame_->linesize, 0,
decoderCtx_->height, output_frame->data,
output_frame->linesize);
unlockFrame();
}
} else {
ERROR("Unable to create a scaler context");
return;
}
sws_scale(scalerCtx_,
rawFrame_->data, rawFrame_->linesize, 0, decoderCtx_->height,
output_frame->data, output_frame->linesize);
return ctx;
}
VideoFrame *VideoDecoder::lockFrame()
{
VideoFrame *frame;
pthread_mutex_lock(&accessMutex_);
if (lockedFrame_ >= 0) {
lockedFrameCnt_++;
} else {
lockedFrame_ = lastFrame_;
lockedFrameCnt_ = 0;
}
pthread_mutex_unlock(&accessMutex_);
if (lockedFrame_ >= 0)
return &rawFrames_[lockedFrame_];
return NULL;
}
void VideoDecoder::unlockFrame()
{
pthread_mutex_lock(&accessMutex_);
if (lockedFrameCnt_ > 0)
lockedFrameCnt_--;
else
lockedFrame_ = -1;
pthread_mutex_unlock(&accessMutex_);
}
}
......@@ -35,6 +35,7 @@
#include "video_base.h"
#include "noncopyable.h"
#include <pthread.h>
#include <string>
class SwsContext;
......@@ -60,9 +61,10 @@ namespace sfl_video {
int decode();
int flush();
void setScaleDest(void *data, int width, int height, int pix_fmt);
void scale(int flags);
void *scale(SwsContext *ctx, int flags);
VideoFrame *lockFrame();
void unlockFrame();
AVFrame *getDecodedFrame() { return rawFrame_; }
int getWidth() const { return dstWidth_; }
int getHeight() const { return dstHeight_; }
......@@ -70,13 +72,17 @@ namespace sfl_video {
NON_COPYABLE(VideoDecoder);
AVCodec *inputDecoder_;
AVCodecContext *decoderCtx_;
AVFrame *rawFrame_;
AVFormatContext *inputCtx_;
SwsContext *imgConvertCtx_;
AVCodecContext *decoderCtx_;
VideoFrame rawFrames_[2];
int lockedFrame_;
int lockedFrameCnt_;
int lastFrame_;
AVFormatContext *inputCtx_;
SwsContext *imgConvertCtx_;
AVIOInterruptCB interruptCb_;
SwsContext *scalerCtx_;
VideoFrame scaledPicture_;
pthread_mutex_t accessMutex_;
int streamIndex_;
int dstWidth_;
......
......@@ -312,8 +312,10 @@ namespace sfl_video {
scaledPicture_->height = height;
}
void VideoEncoder::scale(AVFrame *src_frame, int flags)
void VideoEncoder::scale(VideoFrame *frame_, int flags)
{
AVFrame *src_frame = frame_->get();
scalerCtx_ = sws_getCachedContext(scalerCtx_,
src_frame->width,
src_frame->height,
......
......@@ -59,7 +59,7 @@ namespace sfl_video {
int startIO();
int encode(bool is_keyframe, int frame_number);
int flush();
void scale(AVFrame *src_frame, int flags);
void scale(VideoFrame *src_frame, int flags);
void print_sdp(std::string &sdp_);
/* getWidth and getHeight return size of the encoded frame.
......
......@@ -29,27 +29,167 @@
*/
#include "video_preview.h"
#include "logger.h"
#include "video_decoder.h"
#include "check.h"
#include "manager.h"
#include "client/video_controls.h"
#include <map>
#include <string>
#include "video_receive_thread.h"
class VideoControls;
namespace sfl_video {
VideoPreview::VideoPreview(const std::map<std::string, std::string> &args) :
args_(args), receiveThread_()
{
const char * const LOCAL_ID = "local";
receiveThread_.reset(new VideoReceiveThread(LOCAL_ID, args_));
receiveThread_->start();
}
VideoPreview::~VideoPreview()
{
// explicitly destroy the thread object
receiveThread_.reset();
}
using std::string;
AVIOInterruptCB interruptCb_;
VideoPreview::VideoPreview(const std::map<std::string, std::string> &args) :
id_("local"),
args_(args),
decoder_(0),
threadRunning_(false),
thread_(0),
accessMutex_(),
sink_(),
bufferSize_(0),
previewWidth_(0),
previewHeight_(0)
{
pthread_mutex_init(&accessMutex_, NULL);
interruptCb_.callback = interruptCb;
interruptCb_.opaque = this;
pthread_create(&thread_, NULL, &runCallback, this);
}
VideoPreview::~VideoPreview()
{
set_false_atomic(&threadRunning_);
string name = sink_.openedName();
Manager::instance().getVideoControls()->stoppedDecoding(id_, name);
if (thread_)
pthread_join(thread_, NULL);
pthread_mutex_destroy(&accessMutex_);
}
int VideoPreview::interruptCb(void *ctx)
{
VideoPreview *context = static_cast<VideoPreview*>(ctx);
return not context->threadRunning_;
}
void *VideoPreview::runCallback(void *data)
{
VideoPreview *context = static_cast<VideoPreview*>(data);
context->run();
return NULL;
}
void VideoPreview::run()
{
set_true_atomic(&threadRunning_);
decoder_ = new VideoDecoder();
setup();
while (threadRunning_) {
if (captureFrame())
renderFrame();
}
delete decoder_;
}
void VideoPreview::setup()
{
// it's a v4l device if starting with /dev/video
static const char * const V4L_PATH = "/dev/video";
string format_str;
string input = args_["input"];
if (args_["input"].find(V4L_PATH) != std::string::npos) {
DEBUG("Using v4l2 format");
format_str = "video4linux2";
}
if (!args_["framerate"].empty())
decoder_->setOption("framerate", args_["framerate"].c_str());
if (!args_["video_size"].empty())
decoder_->setOption("video_size", args_["video_size"].c_str());
if (!args_["channel"].empty())
decoder_->setOption("channel", args_["channel"].c_str());
decoder_->setInterruptCallback(interruptCb, this);
EXIT_IF_FAIL(decoder_->openInput(input, format_str) >= 0,
"Could not open input \"%s\"", input.c_str());
/* Data available, finish the decoding */
EXIT_IF_FAIL(!decoder_->setupFromVideoData(),
"decoder IO startup failed");
/* Preview frame size? (defaults from decoder) */
if (!args_["width"].empty())
previewWidth_ = atoi(args_["width"].c_str());
else
previewWidth_ = decoder_->getWidth();
if (!args_["height"].empty())
previewHeight_ = atoi(args_["height"].c_str());
else
previewHeight_ = decoder_->getHeight();
/* Previewing setup */
EXIT_IF_FAIL(sink_.start(), "Cannot start shared memory sink");
bufferSize_ = VideoDecoder::getBufferSize(PIX_FMT_BGRA, previewWidth_,
previewHeight_);
EXIT_IF_FAIL(bufferSize_ > 0, "Incorrect buffer size for decoding");
string name = sink_.openedName();
Manager::instance().getVideoControls()->startedDecoding(id_, name,
previewWidth_,
previewHeight_);
DEBUG("TX: shm sink started with size %d, width %d and height %d",
bufferSize_, previewWidth_, previewHeight_);
}
bool VideoPreview::captureFrame()
{
int ret = decoder_->decode();
if (ret <= 0) {
if (ret < 0)
threadRunning_ = false;
return false;
}
return true;
}
void VideoPreview::renderFrame()
{
// we want our rendering code to be called by the shm_sink,
// because it manages the shared memory synchronization
sink_.render_callback(*this, bufferSize_);
}
// This function is called by sink
void VideoPreview::fillBuffer(void *data)
{
fill(data, previewWidth_, previewHeight_);
}
void VideoPreview::fill(void *data, int width, int height)
{
pthread_mutex_lock(&accessMutex_);
decoder_->setScaleDest(data, width, height, PIX_FMT_BGRA);
decoder_->scale(NULL, 0);
pthread_mutex_unlock(&accessMutex_);
}
VideoFrame *VideoPreview::lockFrame() { return decoder_->lockFrame(); }
void VideoPreview::unlockFrame() { decoder_->unlockFrame(); }
int VideoPreview::getWidth() const { return decoder_->getWidth(); }
int VideoPreview::getHeight() const { return decoder_->getHeight(); }
} // end namspace sfl_video
......@@ -31,23 +31,54 @@
#ifndef __VIDEO_PREVIEW_H__
#define __VIDEO_PREVIEW_H__
#include "noncopyable.h"
#include "shm_sink.h"
#include "video_provider.h"
#include <tr1/memory>
#include <pthread.h>
#include <string>
#include <map>
namespace sfl_video {
using std::string;
class VideoDecoder;
class VideoFrame;
class VideoPreview : public VideoProvider
{
public:
VideoPreview(const std::map<string, string> &args);
~VideoPreview();
int getWidth() const;
int getHeight() const;
void fill(void *data, int width, int height);
VideoFrame *lockFrame();
void unlockFrame();
class VideoReceiveThread;
private:
NON_COPYABLE(VideoPreview);
class VideoPreview {
public:
VideoPreview(const std::map<std::string, std::string> &args);
~VideoPreview();
std::string id_;
std::map<string, string> args_;
VideoDecoder *decoder_;
bool threadRunning_;
pthread_t thread_;
pthread_mutex_t accessMutex_;
SHMSink sink_;
size_t bufferSize_;
int previewWidth_;
int previewHeight_;
private:
std::map<std::string, std::string> args_;
std::tr1::shared_ptr<VideoReceiveThread> receiveThread_;
};
static int interruptCb(void *ctx);
static void *runCallback(void *);
void fillBuffer(void *data);
void run();
bool captureFrame();
void setup();
void renderFrame();
};
}
#endif // __VIDEO_PREVIEW_H__
......@@ -192,7 +192,7 @@ namespace sfl_video {
void VideoReceiveThread::fillBuffer(void *data)
{
videoDecoder_->setScaleDest(data, dstWidth_, dstHeight_, PIX_FMT_BGRA);
videoDecoder_->scale(0);
videoDecoder_->scale(NULL, 0);
}
void VideoReceiveThread::run()
......
......@@ -126,6 +126,14 @@ void VideoRtpSession::updateDestination(const string &destination,
void VideoRtpSession::start(int localPort)
{
std::string curcid = Manager::instance().getCurrentCallId();
ERROR("CallID = %s", callID_.c_str());
DEBUG("current? %u", Manager::instance().isCurrentCall(callID_));
DEBUG("conf? %d", Manager::instance().isConference(callID_));
DEBUG("conf_part? %d", Manager::instance().isConferenceParticipant(callID_));
DEBUG("current is conf? %d", Manager::instance().isConference(curcid));
if (not sending_ and not receiving_)
return;
......@@ -136,17 +144,6 @@ void VideoRtpSession::start(int localPort)
return;
}
if (sending_) {
if (sendThread_.get())
WARN("Restarting video sender");
sendThread_.reset(new VideoSendThread("local", txArgs_));
sendThread_->addIOContext(*socketPair_);
sendThread_->start();
} else {
DEBUG("Video sending disabled");
sendThread_.reset();