Commit 30462aa3 authored by Guillaume Roguez's avatar Guillaume Roguez

#28512: Video mixer implementation and related.

- Implement VideoSource and VideoGenerator classes to help VideoMixer class creation
- VideoMixe class started (not functionnal yet).
- VideoSendThread uses VideoSource API.
- VideDecoder implements VideoGenerator.
- VideoPreview and VideoMixer implement VideoSource.
parent abb371ec
......@@ -11,6 +11,7 @@ libvideo_la_SOURCES = \
video_scaler.cpp video_scaler.h \
video_decoder.cpp video_decoder.h \
video_encoder.cpp video_encoder.h \
video_mixer.cpp video_mixer.h \
socket_pair.cpp socket_pair.h \
shm_sink.cpp shm_sink.h \
video_preview.cpp video_preview.h \
......
......@@ -35,6 +35,8 @@
namespace sfl_video {
/*=== VideoPacket ===========================================================*/
VideoPacket::VideoPacket() : packet_(0)
{
packet_ = static_cast<AVPacket *>(av_malloc(sizeof(AVPacket)));
......@@ -42,6 +44,8 @@ VideoPacket::VideoPacket() : packet_(0)
VideoPacket::~VideoPacket() { av_free_packet(packet_); }
/*=== VideoIOHandle =========================================================*/
VideoIOHandle::VideoIOHandle(ssize_t buffer_size,
bool writeable,
io_readcallback read_cb,
......@@ -65,10 +69,17 @@ void VideoCodec::setOption(const char *name, const char *value)
av_dict_set(&options_, name, value, 0);
}
/*=== VideoFrame =============================================================*/
VideoFrame::VideoFrame() : frame_(avcodec_alloc_frame()) {}
VideoFrame::~VideoFrame() { avcodec_free_frame(&frame_); }
void VideoFrame::setdefaults()
{
avcodec_get_frame_defaults(frame_);
}
void VideoFrame::setGeometry(int width, int height, int pix_fmt)
{
frame_->format = libav_utils::libav_pixel_format(pix_fmt);
......@@ -90,4 +101,85 @@ size_t VideoFrame::getSize()
frame_->height);
}
/*=== VideoGenerator ============================================================*/
VideoGenerator::VideoGenerator() :
VideoSource::VideoSource()
, mutex_()
, condition_()
, writableFrame_()
, writtenFrame_()
, readList_()
{
pthread_mutex_init(&mutex_, NULL);
pthread_cond_init(&condition_, NULL);
}
VideoGenerator::~VideoGenerator()
{
pthread_cond_destroy(&condition_);
pthread_mutex_destroy(&mutex_);
}
void VideoGenerator::publishFrame()
{
pthread_mutex_lock(&mutex_);
{
writtenFrame_ = std::move(writableFrame_); // we owns it now
pthread_cond_signal(&condition_);
}
pthread_mutex_unlock(&mutex_);
}
std::shared_ptr<VideoFrame> VideoGenerator::waitNewFrame()
{
pthread_mutex_lock(&mutex_);
{
if (writtenFrame_)
pthread_cond_wait(&condition_, &mutex_);
}
pthread_mutex_unlock(&mutex_);
return obtainLastFrame();
}
std::shared_ptr<VideoFrame> VideoGenerator::obtainLastFrame()
{
std::shared_ptr<VideoFrame> frame;
pthread_mutex_lock(&mutex_);
{
if (writtenFrame_) {
frame = std::move(writtenFrame_);
readList_.push_front(frame);
} else if (!readList_.empty())
frame = readList_.front();
else
frame = nullptr;
}
pthread_mutex_unlock(&mutex_);
return frame;
}
VideoFrame& VideoGenerator::getNewFrame()
{
VideoFrame* frame;
pthread_mutex_lock(&mutex_);
{
if (writableFrame_) {
frame = writableFrame_.get();
frame->setdefaults();
} else {
frame = new VideoFrame();
writableFrame_.reset(frame);
}
}
pthread_mutex_unlock(&mutex_);
return *frame;
}
}
......@@ -29,13 +29,15 @@
* as that of the covered work.
*/
#ifndef _VIDEO_FRAME_H_
#define _VIDEO_FRAME_H_
#ifndef __VIDEO_FRAME_H__
#define __VIDEO_FRAME_H__
#include "noncopyable.h"
#include <cstdlib>
#include <cstdint>
#include <memory>
#include <forward_list>
class AVFrame;
......@@ -51,67 +53,110 @@ enum VideoPixelFormat {
};
namespace sfl_video {
typedef int(*io_readcallback)(void *opaque, uint8_t *buf, int buf_size);
typedef int(*io_writecallback)(void *opaque, uint8_t *buf, int buf_size);
typedef int64_t(*io_seekcallback)(void *opaque, int64_t offset, int whence);
class VideoPacket {
public:
VideoPacket();
~VideoPacket();
AVPacket* get() { return packet_; };
private:
NON_COPYABLE(VideoPacket);
AVPacket *packet_;
};
class VideoIOHandle {
public:
VideoIOHandle(ssize_t buffer_size,
bool writeable,
io_readcallback read_cb,
io_writecallback write_cb,
io_seekcallback seek_cb,
void *opaque);
~VideoIOHandle();
AVIOContext* get() { return ctx_; }
private:
NON_COPYABLE(VideoIOHandle);
AVIOContext *ctx_;
unsigned char *buf_;
};
class VideoCodec {
public:
VideoCodec();
virtual ~VideoCodec() {};
void setOption(const char *name, const char *value);
private:
NON_COPYABLE(VideoCodec);
protected:
AVDictionary *options_;
};
class VideoFrame {
public:
VideoFrame();
~VideoFrame();
AVFrame* get() { return frame_; };
void setGeometry(int width, int height, int pix_fmt);
void setDestination(void *data);
size_t getSize();
private:
NON_COPYABLE(VideoFrame);
AVFrame *frame_;
};
typedef int(*io_readcallback)(void *opaque, uint8_t *buf, int buf_size);
typedef int(*io_writecallback)(void *opaque, uint8_t *buf, int buf_size);
typedef int64_t(*io_seekcallback)(void *opaque, int64_t offset, int whence);
/*=== VideoPacket ===========================================================*/
class VideoPacket {
public:
VideoPacket();
~VideoPacket();
AVPacket* get() { return packet_; };
private:
NON_COPYABLE(VideoPacket);
AVPacket *packet_;
};
/*=== VideoIOHandle =========================================================*/
class VideoIOHandle {
public:
VideoIOHandle(ssize_t buffer_size,
bool writeable,
io_readcallback read_cb,
io_writecallback write_cb,
io_seekcallback seek_cb,
void *opaque);
~VideoIOHandle();
AVIOContext* get() { return ctx_; }
private:
NON_COPYABLE(VideoIOHandle);
AVIOContext *ctx_;
unsigned char *buf_;
};
class VideoCodec {
public:
VideoCodec();
virtual ~VideoCodec() {}
void setOption(const char *name, const char *value);
private:
NON_COPYABLE(VideoCodec);
protected:
AVDictionary *options_;
};
/*=== VideoFrame =============================================================*/
class VideoFrame {
public:
VideoFrame();
~VideoFrame();
AVFrame* get() { return frame_; };
void setGeometry(int width, int height, int pix_fmt);
void setDestination(void *data);
size_t getSize();
void setdefaults();
private:
NON_COPYABLE(VideoFrame);
AVFrame *frame_;
};
/*=== VideoSource ============================================================*/
class VideoSource {
public:
virtual ~VideoSource() {}
virtual std::shared_ptr<VideoFrame> waitNewFrame() = 0;
virtual std::shared_ptr<VideoFrame> obtainLastFrame() = 0;
virtual int getWidth() const = 0;
virtual int getHeight() const = 0;
};
/*=== VideoGenerator ============================================================*/
class VideoGenerator : public VideoSource {
public:
VideoGenerator();
virtual ~VideoGenerator();
std::shared_ptr<VideoFrame> waitNewFrame();
std::shared_ptr<VideoFrame> obtainLastFrame();
protected:
void publishFrame();
VideoFrame& getNewFrame();
private:
pthread_mutex_t mutex_;
pthread_cond_t condition_;
std::unique_ptr<VideoFrame> writableFrame_;
std::unique_ptr<VideoFrame> writtenFrame_;
std::forward_list<std::shared_ptr<VideoFrame>> readList_;
};
}
#endif // _VIDEO_BASE_H_
#endif // __VIDEO_BASE_H__
......@@ -41,20 +41,15 @@ namespace sfl_video {
using std::string;
VideoDecoder::VideoDecoder() :
inputDecoder_(0),
decoderCtx_(0),
rawFrames_(),
lockedFrame_(-1),
lockedFrameCnt_(0),
lastFrame_(-1),
inputCtx_(avformat_alloc_context()),
scaledPicture_(),
accessMutex_(),
streamIndex_(-1),
dstWidth_(0),
dstHeight_(0)
VideoGenerator::VideoGenerator()
, inputDecoder_(0)
, decoderCtx_(0)
, inputCtx_(avformat_alloc_context())
, scaledPicture_()
, streamIndex_(-1)
, dstWidth_(0)
, dstHeight_(0)
{
pthread_mutex_init(&accessMutex_, NULL);
}
VideoDecoder::~VideoDecoder()
......@@ -69,8 +64,6 @@ VideoDecoder::~VideoDecoder()
avformat_close_input(&inputCtx_);
#endif
}
pthread_mutex_destroy(&accessMutex_);
}
int VideoDecoder::openInput(const std::string &source_str,
......@@ -180,122 +173,64 @@ int VideoDecoder::setupFromVideoData()
int VideoDecoder::decode()
{
int ret = 0;
// Guarantee that we free the packet every iteration
VideoPacket video_packet;
AVPacket *inpacket = video_packet.get();
ret = av_read_frame(inputCtx_, inpacket);
int ret = av_read_frame(inputCtx_, inpacket);
if (ret == AVERROR(EAGAIN)) {
DEBUG("Again");
return 0;
} else if (ret < 0) {
ERROR("Couldn't read frame: %s\n", strerror(ret));
return -1;
}
int idx;
pthread_mutex_lock(&accessMutex_);
if (lockedFrame_ >= 0)
idx = !lockedFrame_;
else if (lastFrame_ >= 0)
idx = !lastFrame_;
else
idx = 0;
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;
VideoFrame &frame = getNewFrame();
int frameFinished = 0;
const int len = avcodec_decode_video2(decoderCtx_, frame,
&frameFinished, inpacket);
int len = avcodec_decode_video2(decoderCtx_, frame.get(),
&frameFinished, inpacket);
if (len <= 0)
return -2;
if (frameFinished) {
pthread_mutex_lock(&accessMutex_);
lastFrame_ = idx;
pthread_mutex_unlock(&accessMutex_);
publishFrame();
return 1;
}
return frameFinished;
return 0;
}
int VideoDecoder::flush()
{
AVPacket inpacket;
av_init_packet(&inpacket);
inpacket.data = NULL;
inpacket.size = 0;
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);
VideoFrame &frame = getNewFrame();
int frameFinished = 0;
const int len = avcodec_decode_video2(decoderCtx_, frame,
&frameFinished, &inpacket);
int len = avcodec_decode_video2(decoderCtx_, frame.get(),
&frameFinished, &inpacket);
if (len <= 0)
return -2;
if (frameFinished) {
pthread_mutex_lock(&accessMutex_);
lastFrame_ = idx;
pthread_mutex_unlock(&accessMutex_);
publishFrame();
return 1;
}
return frameFinished;
return 0;
}
void VideoDecoder::scale(VideoScaler &scaler, VideoFrame &output)
{
VideoFrame *frame = lockFrame();
VideoFrame* frame = obtainLastFrame().get();
if (frame) {
if (frame)
scaler.scale(*frame, output);
unlockFrame();
}
}
VideoFrame *VideoDecoder::lockFrame()
{
VideoFrame *frame = NULL;
pthread_mutex_lock(&accessMutex_);
if (lockedFrame_ >= 0) {
lockedFrameCnt_++;
} else {
lockedFrame_ = lastFrame_;
lockedFrameCnt_ = 0;
}
if (lockedFrame_ >= 0)
frame = &rawFrames_[lockedFrame_];
pthread_mutex_unlock(&accessMutex_);
return frame;
}
void VideoDecoder::unlockFrame()
{
pthread_mutex_lock(&accessMutex_);
if (lockedFrameCnt_ > 0)
lockedFrameCnt_--;
else
lockedFrame_ = -1;
pthread_mutex_unlock(&accessMutex_);
}
}
......@@ -29,8 +29,8 @@
* as that of the covered work.
*/
#ifndef _VIDEO_DECODER_H_
#define _VIDEO_DECODER_H_
#ifndef __VIDEO_DECODER_H__
#define __VIDEO_DECODER_H__
#include "video_base.h"
#include "video_scaler.h"
......@@ -46,7 +46,7 @@ class AVCodec;
namespace sfl_video {
class VideoDecoder : public VideoCodec {
class VideoDecoder : public VideoCodec, public VideoGenerator {
public:
VideoDecoder();
~VideoDecoder();
......@@ -59,8 +59,6 @@ namespace sfl_video {
int decode();
int flush();
void scale(VideoScaler &ctx, VideoFrame &output);
VideoFrame *lockFrame();
void unlockFrame();
int getWidth() const { return dstWidth_; }
int getHeight() const { return dstHeight_; }
......@@ -70,13 +68,8 @@ namespace sfl_video {
AVCodec *inputDecoder_;
AVCodecContext *decoderCtx_;
VideoFrame rawFrames_[2];
int lockedFrame_;
int lockedFrameCnt_;
int lastFrame_;
AVFormatContext *inputCtx_;
VideoFrame scaledPicture_;
pthread_mutex_t accessMutex_;
int streamIndex_;
int dstWidth_;
......@@ -84,4 +77,4 @@ namespace sfl_video {
};
}
#endif // _VIDEO_DECODER_H_
#endif // __VIDEO_DECODER_H__
......@@ -260,7 +260,7 @@ int VideoEncoder::flush()
opkt.size = encoderBufferSize_;
ret = avcodec_encode_video2(encoderCtx_, &opkt, NULL, &got_packet);
if (ret < 0) {
ERROR("avcodec_encode_video failed");
ERROR("avcodec_encode_video2 failed");
return -1;
}
......
......@@ -29,8 +29,8 @@
* as that of the covered work.
*/
#ifndef _VIDEO_ENCODER_H_
#define _VIDEO_ENCODER_H_
#ifndef __VIDEO_ENCODER_H__
#define __VIDEO_ENCODER_H__
#include "video_base.h"
#include "video_scaler.h"
......@@ -86,4 +86,4 @@ namespace sfl_video {
};
}
#endif // _VIDEO_ENCODER_H_
#endif // __VIDEO_ENCODER_H__
/*
* Copyright (C) 2013 Savoir-Faire Linux Inc.
*
* Author: Guillaume Roguez <Guillaume.Roguez@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Additional permission under GNU GPL version 3 section 7:
*
* If you modify this program, or any covered work, by linking or
* combining it with the OpenSSL project's OpenSSL library (or a
* modified version of that library), containing parts covered by the
* terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
* grants you additional permission to convey the resulting work.
* Corresponding Source for a non-source form of such a combination
* shall include the source code for the parts of OpenSSL used as well
* as that of the covered work.
*/
#include "video_mixer.h"
#include "check.h"
namespace sfl_video {
VideoMixer::VideoMixer() :
VideoSource::VideoSource()
, thread_()
, threadRunning_(false)
, updateMutex_()
, updateCondition_()
, updated_(false)
, sourceList_()
, width(0)
, height(0)
{
pthread_mutex_init(&updateMutex_, NULL);
pthread_cond_init(&updateCondition_, NULL);
pthread_create(&thread_, NULL, &runCallback, this);
}
VideoMixer::~VideoMixer()