video_receive_thread.cpp 7.78 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_()
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 144
    auto conf = Manager::instance().getConferenceFromCallID(id_);
    if (!conf)
        exitConference();
145 146 147 148 149

    return true;
}

void VideoReceiveThread::process()
150
{ decodeFrame(); }
151 152 153

void VideoReceiveThread::cleanup()
{
154
    Manager::instance().getVideoControls()->stoppedDecoding(id_, sink_.openedName());
155 156 157 158 159 160 161

    if (videoDecoder_)
        delete videoDecoder_;

    if (demuxContext_)
        delete demuxContext_;

Guillaume Roguez's avatar
Guillaume Roguez committed
162
}
163

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

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

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

Guillaume Roguez's avatar
Guillaume Roguez committed
185 186
bool VideoReceiveThread::decodeFrame()
{
187
    int ret = videoDecoder_->decode(getNewFrame());
Guillaume Roguez's avatar
Guillaume Roguez committed
188

189 190 191
    if (ret > 0) {
        publishFrame();
        return true;
Guillaume Roguez's avatar
Guillaume Roguez committed
192 193 194 195
    }

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

204
    return false;
Guillaume Roguez's avatar
Guillaume Roguez committed
205 206
}

207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231
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;

    Manager::instance().getVideoControls()->stoppedDecoding(id_, sink_.openedName());
    detach(&sink_);
    sink_.stop();
}

void VideoReceiveThread::exitConference()
Guillaume Roguez's avatar
Guillaume Roguez committed
232
{
233 234 235 236 237 238 239 240 241 242 243 244 245
    if (!isRunning())
        return;

    // Sink startup
    EXIT_IF_FAIL(sink_.start(), "RX: sink startup failed");
    Manager::instance().getVideoControls()->startedDecoding(id_,
                                                            sink_.openedName(),
                                                            dstWidth_,
                                                            dstHeight_);
    DEBUG("RX: shm sink <%s> started: size = %dx%d",
          sink_.openedName().c_str(), dstWidth_, dstHeight_);

    attach(&sink_);
Guillaume Roguez's avatar
Guillaume Roguez committed
246 247
}

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

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

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

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

261
} // end namespace sfl_video