video_receive_thread.cpp 7.81 KB
Newer Older
1
/*
2
 *  Copyright (C) 2004-2013 Savoir-Faire Linux Inc.
Rafaël Carré's avatar
Rafaël Carré committed
3
 *
4
 *  Author: Tristan Matthews <tristan.matthews@savoirfairelinux.com>
5
 *  Author: Guillaume Roguez <Guillaume.Roguez@savoirfairelinux.com>
6 7 8 9 10 11 12 13 14 15 16 17 18
 *
 *  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
19
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
20 21 22 23 24 25 26 27 28 29 30 31 32
 *
 *  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.
 */

33
#include "libav_deps.h"
34

35
#include "video_receive_thread.h"
36
#include "socket_pair.h"
37
#include "manager.h"
38
#include "client/video_controls.h"
39
#include "check.h"
40

41
#include <unistd.h>
42
#include <map>
Rafaël Carré's avatar
Rafaël Carré committed
43

44 45
namespace sfl_video {

Guillaume Roguez's avatar
Guillaume Roguez committed
46 47 48
using std::string;
const int SDP_BUFFER_SIZE = 8192;

49
VideoReceiveThread::VideoReceiveThread(const std::string& id,
50
                                       const std::map<string, string>& args) :
51 52 53
    VideoGenerator::VideoGenerator()
    , args_(args)
    , videoDecoder_()
54 55 56 57 58
    , dstWidth_(0)
    , dstHeight_(0)
    , id_(id)
    , stream_(args_["receiving_sdp"])
    , sdpContext_(SDP_BUFFER_SIZE, false, &readFunction, 0, 0, this)
59
    , demuxContext_()
60
    , sink_(id+"_RX")
61
    , requestKeyFrameCallback_(0)
62
{}
Guillaume Roguez's avatar
Guillaume Roguez committed
63 64 65

VideoReceiveThread::~VideoReceiveThread()
{
66 67
    stop();
    join();
Guillaume Roguez's avatar
Guillaume Roguez committed
68
}
69

70 71
// We do this setup here instead of the constructor because we don't want the
// main thread to block while this executes, so it happens in the video thread.
72
bool VideoReceiveThread::setup()
Guillaume Roguez's avatar
Guillaume Roguez committed
73
{
74
    videoDecoder_ = new VideoDecoder();
Guillaume Roguez's avatar
Guillaume Roguez committed
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94

    dstWidth_ = atoi(args_["width"].c_str());
    dstHeight_ = atoi(args_["height"].c_str());

    const std::string SDP_FILENAME = "dummyFilename";
    std::string format_str;
    std::string input;

    if (args_["input"].empty()) {
        format_str = "sdp";
        input = SDP_FILENAME;
    } else if (args_["input"].substr(0, strlen("/dev/video")) == "/dev/video") {
        // it's a v4l device if starting with /dev/video
        // FIXME: This is not a robust way of checking if we mean to use a
        // v4l2 device
        format_str = "video4linux2";
        input = args_["input"];
    }

    if (!args_["framerate"].empty())
95
        videoDecoder_->setOption("framerate", args_["framerate"].c_str());
Guillaume Roguez's avatar
Guillaume Roguez committed
96
    if (!args_["video_size"].empty())
97
        videoDecoder_->setOption("video_size", args_["video_size"].c_str());
Guillaume Roguez's avatar
Guillaume Roguez committed
98
    if (!args_["channel"].empty())
99
        videoDecoder_->setOption("channel", args_["channel"].c_str());
Guillaume Roguez's avatar
Guillaume Roguez committed
100

101
    videoDecoder_->setInterruptCallback(interruptCb, this);
Guillaume Roguez's avatar
Guillaume Roguez committed
102 103

    if (input == SDP_FILENAME) {
104
#if HAVE_SDP_CUSTOM_IO
Guillaume Roguez's avatar
Guillaume Roguez committed
105
        // custom_io so the SDP demuxer will not open any UDP connections
106
        videoDecoder_->setOption("sdp_flags", "custom_io");
107
#else
Guillaume Roguez's avatar
Guillaume Roguez committed
108
        WARN("libavformat too old for custom SDP demuxing");
109 110
#endif

Guillaume Roguez's avatar
Guillaume Roguez committed
111
        EXIT_IF_FAIL(not stream_.str().empty(), "No SDP loaded");
112
        videoDecoder_->setIOContext(&sdpContext_);
Guillaume Roguez's avatar
Guillaume Roguez committed
113
    }
114

115
    EXIT_IF_FAIL(!videoDecoder_->openInput(input, format_str),
Guillaume Roguez's avatar
Guillaume Roguez committed
116
                 "Could not open input \"%s\"", input.c_str());
117

Guillaume Roguez's avatar
Guillaume Roguez committed
118
    if (input == SDP_FILENAME) {
119
#if HAVE_SDP_CUSTOM_IO
Guillaume Roguez's avatar
Guillaume Roguez committed
120 121
        // Now replace our custom AVIOContext with one that will read
        // packets
122
        videoDecoder_->setIOContext(demuxContext_);
123
#endif
Guillaume Roguez's avatar
Guillaume Roguez committed
124 125 126 127 128 129 130 131 132
    }

    // FIXME: this is a hack because our peer sends us RTP before
    // we're ready for it, and we miss the SPS/PPS. We should be
    // ready earlier.
    sleep(1);
    if (requestKeyFrameCallback_)
        requestKeyFrameCallback_(id_);

133
    EXIT_IF_FAIL(!videoDecoder_->setupFromVideoData(),
Guillaume Roguez's avatar
Guillaume Roguez committed
134 135 136 137
                 "decoder IO startup failed");

    // Default size from input video
    if (dstWidth_ == 0 and dstHeight_ == 0) {
138 139
        dstWidth_ = videoDecoder_->getWidth();
        dstHeight_ = videoDecoder_->getHeight();
Guillaume Roguez's avatar
Guillaume Roguez committed
140 141
    }

142 143
    EXIT_IF_FAIL(sink_.start(), "RX: sink startup failed");

144 145 146
    auto conf = Manager::instance().getConferenceFromCallID(id_);
    if (!conf)
        exitConference();
147 148 149 150 151

    return true;
}

void VideoReceiveThread::process()
152
{ decodeFrame(); }
153 154 155

void VideoReceiveThread::cleanup()
{
156 157
    if (detach(&sink_))
        Manager::instance().getVideoControls()->stoppedDecoding(id_+"RX", sink_.openedName());
158
    sink_.stop();
159 160 161 162 163 164

    if (videoDecoder_)
        delete videoDecoder_;

    if (demuxContext_)
        delete demuxContext_;
Guillaume Roguez's avatar
Guillaume Roguez committed
165
}
166

167
// This callback is used by libav internally to break out of blocking calls
168
int VideoReceiveThread::interruptCb(void *data)
Guillaume Roguez's avatar
Guillaume Roguez committed
169
{
170 171
    VideoReceiveThread *context = static_cast<VideoReceiveThread*>(data);
    return not context->isRunning();
Guillaume Roguez's avatar
Guillaume Roguez committed
172 173
}

174
int VideoReceiveThread::readFunction(void *opaque, uint8_t *buf, int buf_size)
Guillaume Roguez's avatar
Guillaume Roguez committed
175 176 177 178 179 180 181 182
{
    std::istream &is = static_cast<VideoReceiveThread*>(opaque)->stream_;
    is.read(reinterpret_cast<char*>(buf), buf_size);
    return is.gcount();
}

void VideoReceiveThread::addIOContext(SocketPair &socketPair)
{
183
#if HAVE_SDP_CUSTOM_IO
Guillaume Roguez's avatar
Guillaume Roguez committed
184
    demuxContext_ = socketPair.getIOContext();
185
#endif
Guillaume Roguez's avatar
Guillaume Roguez committed
186
}
187

Guillaume Roguez's avatar
Guillaume Roguez committed
188 189
bool VideoReceiveThread::decodeFrame()
{
190
    int ret = videoDecoder_->decode(getNewFrame());
Guillaume Roguez's avatar
Guillaume Roguez committed
191

192 193 194
    if (ret > 0) {
        publishFrame();
        return true;
Guillaume Roguez's avatar
Guillaume Roguez committed
195 196 197 198
    }

    // decoding error?
    if (ret == -2 and requestKeyFrameCallback_) {
199
        WARN("VideoDecoder error, restarting it...");
200
        EXIT_IF_FAIL(!videoDecoder_->setupFromVideoData(), "Setup failed");
Guillaume Roguez's avatar
Guillaume Roguez committed
201
        requestKeyFrameCallback_(id_);
202 203 204
    } else if (ret < 0) {
        ERROR("VideoDecoder fatal error, stopping it...");
        stop();
Guillaume Roguez's avatar
Guillaume Roguez committed
205 206
    }

207
    return false;
Guillaume Roguez's avatar
Guillaume Roguez committed
208 209
}

210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
void VideoReceiveThread::addReceivingDetails(
    std::map<std::string, std::string> &details)
{
    if (isRunning() and dstWidth_ > 0 and dstHeight_ > 0) {
        details["VIDEO_SHM_PATH"] = sink_.openedName();
        std::ostringstream os;
        os << dstWidth_;
        details["VIDEO_WIDTH"] = os.str();
        os.str("");
        os << dstHeight_;
        details["VIDEO_HEIGHT"] = os.str();
    }
}

void VideoReceiveThread::enterConference()
{
    if (!isRunning())
        return;

229 230
    if (detach(&sink_)) {
        Manager::instance().getVideoControls()->stoppedDecoding(id_+"RX", sink_.openedName());
231
        DEBUG("RX: shm sink <%s> detached", sink_.openedName().c_str());
232
    }
233 234 235
}

void VideoReceiveThread::exitConference()
Guillaume Roguez's avatar
Guillaume Roguez committed
236
{
237 238 239
    if (!isRunning())
        return;

240
    if (dstWidth_ > 0 && dstHeight_ > 0) {
241 242 243 244 245
        if (attach(&sink_)) {
            Manager::instance().getVideoControls()->startedDecoding(id_+"RX", sink_.openedName(), dstWidth_, dstHeight_);
            DEBUG("RX: shm sink <%s> started: size = %dx%d",
                  sink_.openedName().c_str(), dstWidth_, dstHeight_);
        }
246
    }
Guillaume Roguez's avatar
Guillaume Roguez committed
247 248
}

249 250 251 252
void VideoReceiveThread::setRequestKeyFrameCallback(
    void (*cb)(const std::string &))
{ requestKeyFrameCallback_ = cb; }

253 254 255 256 257 258 259 260
int VideoReceiveThread::getWidth() const
{ return dstWidth_; }

int VideoReceiveThread::getHeight() const
{ return dstHeight_; }

int VideoReceiveThread::getPixelFormat() const
{ return videoDecoder_->getPixelFormat(); }
261

262
} // end namespace sfl_video