Skip to content

Commit

Permalink
Add fake keyframe handler (#1332)
Browse files Browse the repository at this point in the history
  • Loading branch information
lodoyun authored Nov 13, 2018
1 parent e21c150 commit cfe9e81
Show file tree
Hide file tree
Showing 8 changed files with 346 additions and 2 deletions.
2 changes: 2 additions & 0 deletions erizo/src/erizo/MediaStream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include "rtp/RtpRetransmissionHandler.h"
#include "rtp/RtcpFeedbackGenerationHandler.h"
#include "rtp/RtpPaddingRemovalHandler.h"
#include "rtp/FakeKeyframeGeneratorHandler.h"
#include "rtp/StatsHandler.h"
#include "rtp/SRPacketHandler.h"
#include "rtp/SenderBandwidthEstimationHandler.h"
Expand Down Expand Up @@ -353,6 +354,7 @@ void MediaStream::initializePipeline() {
pipeline_->addFront(std::make_shared<LayerBitrateCalculationHandler>());
pipeline_->addFront(std::make_shared<QualityFilterHandler>());
pipeline_->addFront(std::make_shared<IncomingStatsHandler>());
pipeline_->addFront(std::make_shared<FakeKeyframeGeneratorHandler>());
pipeline_->addFront(std::make_shared<RtpTrackMuteHandler>());
pipeline_->addFront(std::make_shared<RtpSlideShowHandler>());
pipeline_->addFront(std::make_shared<RtpPaddingGeneratorHandler>());
Expand Down
97 changes: 97 additions & 0 deletions erizo/src/erizo/rtp/FakeKeyframeGeneratorHandler.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#include "rtp/FakeKeyframeGeneratorHandler.h"

#include <algorithm>
#include <string>

#include "./MediaDefinitions.h"
#include "./MediaStream.h"
#include "./RtpUtils.h"

namespace erizo {

DEFINE_LOGGER(FakeKeyframeGeneratorHandler, "rtp.FakeKeyframeGeneratorHandler");

constexpr uint64_t kPliPeriodMs = 300;

FakeKeyframeGeneratorHandler::FakeKeyframeGeneratorHandler() :
stream_{nullptr}, enabled_{true}, first_keyframe_received_{false}, plis_scheduled_{false},
video_source_ssrc_{0}, video_sink_ssrc_{0} {
}

void FakeKeyframeGeneratorHandler::enable() {
enabled_ = true;
}

void FakeKeyframeGeneratorHandler::disable() {
enabled_ = false;
}

void FakeKeyframeGeneratorHandler::notifyUpdate() {
auto pipeline = getContext()->getPipelineShared();
if (pipeline && !stream_) {
stream_ = pipeline->getService<MediaStream>().get();
}
if (stream_) {
video_source_ssrc_ = stream_->getVideoSourceSSRC();
video_sink_ssrc_ = stream_->getVideoSinkSSRC();
}
}

void FakeKeyframeGeneratorHandler::read(Context *ctx, std::shared_ptr<DataPacket> packet) {
ctx->fireRead(std::move(packet));
}

void FakeKeyframeGeneratorHandler::write(Context *ctx, std::shared_ptr<DataPacket> packet) {
RtcpHeader *chead = reinterpret_cast<RtcpHeader*>(packet->data);
if (enabled_) {
if (!first_keyframe_received_ && packet->type == VIDEO_PACKET && !chead->isRtcp()) {
if (!packet->is_keyframe) {
if (!plis_scheduled_) {
plis_scheduled_ = true;
ELOG_DEBUG("Scheduling PLIs");
sendPLI();
schedulePLI();
}
ELOG_DEBUG("Building a black keyframe from packet");
auto keyframe_packet = transformIntoKeyframePacket(packet);
ctx->fireWrite(keyframe_packet);
return;
} else {
ELOG_DEBUG("First part of a keyframe received, stop rewriting packets");
first_keyframe_received_ = true;
}
}
}
ctx->fireWrite(std::move(packet));
}

std::shared_ptr<DataPacket> FakeKeyframeGeneratorHandler::transformIntoKeyframePacket
(std::shared_ptr<DataPacket> packet) {
if (packet->codec == "VP8") {
auto keyframe_packet = RtpUtils::makeVP8BlackKeyframePacket(packet);
return keyframe_packet;
} else {
ELOG_DEBUG("Generate keyframe packet is not available for codec %s", packet->codec);
return packet;
}
}
void FakeKeyframeGeneratorHandler::sendPLI() {
getContext()->fireRead(RtpUtils::createPLI(video_sink_ssrc_, video_source_ssrc_));
}
void FakeKeyframeGeneratorHandler::schedulePLI() {
std::weak_ptr<FakeKeyframeGeneratorHandler> weak_this = shared_from_this();
stream_->getWorker()->scheduleEvery([weak_this] {
if (auto this_ptr = weak_this.lock()) {
if (!this_ptr->first_keyframe_received_) {
ELOG_DEBUG("Sending PLI in FakeGenerator, scheduling another");
this_ptr->sendPLI();
return true;
} else {
ELOG_DEBUG("Stop sending scheduled PLI packets");
return false;
}
}
return false;
}, std::chrono::milliseconds(kPliPeriodMs));
}
} // namespace erizo
53 changes: 53 additions & 0 deletions erizo/src/erizo/rtp/FakeKeyframeGeneratorHandler.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#ifndef ERIZO_SRC_ERIZO_RTP_FAKEKEYFRAMEGENERATORHANDLER_H_
#define ERIZO_SRC_ERIZO_RTP_FAKEKEYFRAMEGENERATORHANDLER_H_

#include <string>

#include "./logger.h"
#include "pipeline/Handler.h"
#include "lib/Clock.h"
#include "lib/TokenBucket.h"
#include "thread/Worker.h"
#include "rtp/SequenceNumberTranslator.h"
#include "rtp/RtpVP8Parser.h"
#include "./Stats.h"

namespace erizo {

class MediaStream;

class FakeKeyframeGeneratorHandler: public Handler, public std::enable_shared_from_this<FakeKeyframeGeneratorHandler> {
DECLARE_LOGGER();

public:
FakeKeyframeGeneratorHandler();
void enable() override;
void disable() override;

std::string getName() override {
return "fake-keyframe-generator";
}

void read(Context *ctx, std::shared_ptr<DataPacket> packet) override;
void write(Context *ctx, std::shared_ptr<DataPacket> packet) override;
void notifyUpdate() override;

private:
std::shared_ptr<DataPacket> transformIntoKeyframePacket(std::shared_ptr<DataPacket> packet);
void sendPLI();
void schedulePLI();

private:
MediaStream* stream_;
bool enabled_;
bool first_keyframe_received_;
bool plis_scheduled_;
uint32_t video_source_ssrc_;
uint32_t video_sink_ssrc_;
RtpVP8Parser vp8_parser_;
std::shared_ptr<ScheduledTaskReference> scheduled_task_;
};

} // namespace erizo

#endif // ERIZO_SRC_ERIZO_RTP_FAKEKEYFRAMEGENERATORHANDLER_H_
57 changes: 56 additions & 1 deletion erizo/src/erizo/rtp/RtpUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ namespace erizo {


constexpr int kMaxPacketSize = 1500;

bool RtpUtils::sequenceNumberLessThan(uint16_t first, uint16_t last) {
return RtpUtils::numberLessThan(first, last, 16);
}
Expand Down Expand Up @@ -160,4 +159,60 @@ std::shared_ptr<DataPacket> RtpUtils::makePaddingPacket(std::shared_ptr<DataPack
return std::make_shared<DataPacket>(packet->comp, packet_buffer, packet_length, packet->type);
}

std::shared_ptr<DataPacket> RtpUtils::makeVP8BlackKeyframePacket(std::shared_ptr<DataPacket> packet) {
uint8_t vp8_keyframe[] = {
(uint8_t) 0x90, (uint8_t) 0xe0, (uint8_t) 0x80, (uint8_t) 0x01, // payload header 1
(uint8_t) 0x00, (uint8_t) 0x20, (uint8_t) 0x10, (uint8_t) 0x0f, // payload header 2
(uint8_t) 0x00, (uint8_t) 0x9d, (uint8_t) 0x01, (uint8_t) 0x2a,
(uint8_t) 0x40, (uint8_t) 0x01, (uint8_t) 0xb4, (uint8_t) 0x00,
(uint8_t) 0x07, (uint8_t) 0x07, (uint8_t) 0x09, (uint8_t) 0x03,
(uint8_t) 0x0b, (uint8_t) 0x0b, (uint8_t) 0x11, (uint8_t) 0x33,
(uint8_t) 0x09, (uint8_t) 0x10, (uint8_t) 0x4b, (uint8_t) 0x00,
(uint8_t) 0x00, (uint8_t) 0x0c, (uint8_t) 0x2c, (uint8_t) 0x09,
(uint8_t) 0xee, (uint8_t) 0x0d, (uint8_t) 0x02, (uint8_t) 0xc9,
(uint8_t) 0x3e, (uint8_t) 0xd7, (uint8_t) 0xb7, (uint8_t) 0x36,
(uint8_t) 0x4e, (uint8_t) 0x70, (uint8_t) 0xf6, (uint8_t) 0x4e,
(uint8_t) 0x70, (uint8_t) 0xf6, (uint8_t) 0x4e, (uint8_t) 0x70,
(uint8_t) 0xf6, (uint8_t) 0x4e, (uint8_t) 0x70, (uint8_t) 0xf6,
(uint8_t) 0x4e, (uint8_t) 0x70, (uint8_t) 0xf6, (uint8_t) 0x4e,
(uint8_t) 0x70, (uint8_t) 0xf6, (uint8_t) 0x4e, (uint8_t) 0x70,
(uint8_t) 0xf6, (uint8_t) 0x4e, (uint8_t) 0x70, (uint8_t) 0xf6,
(uint8_t) 0x4e, (uint8_t) 0x70, (uint8_t) 0xf6, (uint8_t) 0x4e,
(uint8_t) 0x70, (uint8_t) 0xf6, (uint8_t) 0x4e, (uint8_t) 0x70,
(uint8_t) 0xf6, (uint8_t) 0x4e, (uint8_t) 0x70, (uint8_t) 0xf6,
(uint8_t) 0x4e, (uint8_t) 0x70, (uint8_t) 0xf6, (uint8_t) 0x4e,
(uint8_t) 0x70, (uint8_t) 0xf6, (uint8_t) 0x4e, (uint8_t) 0x70,
(uint8_t) 0xf6, (uint8_t) 0x4e, (uint8_t) 0x70, (uint8_t) 0xf6,
(uint8_t) 0x4e, (uint8_t) 0x70, (uint8_t) 0xf6, (uint8_t) 0x4e,
(uint8_t) 0x70, (uint8_t) 0xf6, (uint8_t) 0x4e, (uint8_t) 0x70,
(uint8_t) 0xf6, (uint8_t) 0x4e, (uint8_t) 0x70, (uint8_t) 0xf6,
(uint8_t) 0x4e, (uint8_t) 0x70, (uint8_t) 0xf6, (uint8_t) 0x4e,
(uint8_t) 0x70, (uint8_t) 0xf6, (uint8_t) 0x4e, (uint8_t) 0x70,
(uint8_t) 0xf6, (uint8_t) 0x4e, (uint8_t) 0x70, (uint8_t) 0xf6,
(uint8_t) 0x4e, (uint8_t) 0x70, (uint8_t) 0xf6, (uint8_t) 0x4e,
(uint8_t) 0x70, (uint8_t) 0xf6, (uint8_t) 0x4e, (uint8_t) 0x70,
(uint8_t) 0xf6, (uint8_t) 0x4e, (uint8_t) 0x70, (uint8_t) 0xf6,
(uint8_t) 0x4e, (uint8_t) 0x70, (uint8_t) 0xf6, (uint8_t) 0x4e,
(uint8_t) 0x70, (uint8_t) 0xf6, (uint8_t) 0x4e, (uint8_t) 0x70,
(uint8_t) 0xf6, (uint8_t) 0x4e, (uint8_t) 0x5c, (uint8_t) 0x00,
(uint8_t) 0xfe, (uint8_t) 0xef, (uint8_t) 0xb9, (uint8_t) 0x00
};

uint16_t keyframe_length = sizeof(vp8_keyframe)/sizeof(vp8_keyframe[0]);
erizo::RtpHeader *header = reinterpret_cast<RtpHeader*>(packet->data);
const uint16_t packet_length = header->getHeaderLength() + keyframe_length;
char packet_buffer[kMaxPacketSize];

erizo::RtpHeader *new_header = reinterpret_cast<RtpHeader*>(packet_buffer);
memset(packet_buffer, 0, packet_length);
memcpy(packet_buffer, reinterpret_cast<char*>(header), header->getHeaderLength());
memcpy(packet_buffer + header->getHeaderLength(), reinterpret_cast<char*>(vp8_keyframe), keyframe_length);
new_header->setMarker(true);
std::shared_ptr<DataPacket> keyframe_packet =
std::make_shared<DataPacket>(packet->comp, packet_buffer, packet_length, packet->type);
keyframe_packet->is_keyframe = true;

return keyframe_packet;
}

} // namespace erizo
1 change: 1 addition & 0 deletions erizo/src/erizo/rtp/RtpUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class RtpUtils {
static int getPaddingLength(std::shared_ptr<DataPacket> packet);

static std::shared_ptr<DataPacket> makePaddingPacket(std::shared_ptr<DataPacket> packet, uint8_t padding_size);
static std::shared_ptr<DataPacket> makeVP8BlackKeyframePacket(std::shared_ptr<DataPacket> packet);
};

} // namespace erizo
Expand Down
128 changes: 128 additions & 0 deletions erizo/src/test/rtp/FakeKeyframeGeneratorHandlerTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include <rtp/FakeKeyframeGeneratorHandler.h>
#include <rtp/RtpHeaders.h>
#include <MediaDefinitions.h>
#include <WebRtcConnection.h>
#include <stats/StatNode.h>

#include <queue>
#include <string>
#include <vector>

#include "../utils/Mocks.h"
#include "../utils/Tools.h"
#include "../utils/Matchers.h"

using ::testing::_;
using ::testing::IsNull;
using ::testing::Args;
using ::testing::Return;
using ::testing::AtLeast;
using ::testing::Not;
using erizo::DataPacket;
using erizo::packetType;
using erizo::AUDIO_PACKET;
using erizo::VIDEO_PACKET;
using erizo::MovingIntervalRateStat;
using erizo::IceConfig;
using erizo::RtpMap;
using erizo::FakeKeyframeGeneratorHandler;
using erizo::WebRtcConnection;
using erizo::Pipeline;
using erizo::InboundHandler;
using erizo::OutboundHandler;
using erizo::Worker;
using std::queue;


class FakeKeyframeGeneratorHandlerTest : public erizo::HandlerTest {
public:
FakeKeyframeGeneratorHandlerTest() {}

protected:
void setHandler() {
keyframe_generator = std::make_shared<FakeKeyframeGeneratorHandler>();
pipeline->addBack(keyframe_generator);
}
std::shared_ptr<FakeKeyframeGeneratorHandler> keyframe_generator;
};

TEST_F(FakeKeyframeGeneratorHandlerTest, basicBehaviourShouldReadPackets) {
auto packet = erizo::PacketTools::createDataPacket(erizo::kArbitrarySeqNumber, AUDIO_PACKET);

EXPECT_CALL(*reader.get(), read(_, _)).
With(Args<1>(erizo::RtpHasSequenceNumber(erizo::kArbitrarySeqNumber))).Times(1);
pipeline->read(packet);
}

TEST_F(FakeKeyframeGeneratorHandlerTest, basicBehaviourShouldWritePackets) {
auto packet = erizo::PacketTools::createDataPacket(erizo::kArbitrarySeqNumber, AUDIO_PACKET);

EXPECT_CALL(*writer.get(), write(_, _)).
With(Args<1>(erizo::RtpHasSequenceNumber(erizo::kArbitrarySeqNumber))).Times(1);
pipeline->write(packet);
}

TEST_F(FakeKeyframeGeneratorHandlerTest, shouldNotChangePacketsWhenDisabled) {
keyframe_generator->disable();

auto packet = erizo::PacketTools::createVP8Packet(erizo::kArbitrarySeqNumber, false, false);
int length = packet->length;
EXPECT_CALL(*writer.get(), write(_, _)).
With(Args<1>(erizo::PacketLengthIs(length))).Times(1);
pipeline->write(packet);
}

TEST_F(FakeKeyframeGeneratorHandlerTest, shouldTransformVP8Packet) {
keyframe_generator->enable();
EXPECT_CALL(*writer.get(), write(_, _)).
With(Args<1>(erizo::PacketIsKeyframe())).Times(1);
pipeline->write(erizo::PacketTools::createVP8Packet(erizo::kArbitrarySeqNumber, false, false));
}

TEST_F(FakeKeyframeGeneratorHandlerTest, shouldNotTransformVP8Keyframes) {
auto packet = erizo::PacketTools::createVP8Packet(erizo::kArbitrarySeqNumber, true, true);
int length = packet->length;
EXPECT_CALL(*writer.get(), write(_, _)).
With(Args<1>(erizo::PacketLengthIs(length))).Times(1);
pipeline->write(packet);
}

TEST_F(FakeKeyframeGeneratorHandlerTest, shouldNotTransformUnsupportedCodecPackets) {
auto packet = erizo::PacketTools::createVP9Packet(erizo::kArbitrarySeqNumber, false, false);
int length = packet->length;
EXPECT_CALL(*writer.get(), write(_, _)).
With(Args<1>(erizo::PacketLengthIs(length))).Times(1);
pipeline->write(packet);
}

TEST_F(FakeKeyframeGeneratorHandlerTest, shouldSendPLIInmediatelyIfNoKeyframeIsReceived) {
auto packet = erizo::PacketTools::createVP8Packet(erizo::kArbitrarySeqNumber, false, false);
EXPECT_CALL(*reader.get(), read(_, _)).With(Args<1>(erizo::IsPLI())).Times(1);
pipeline->write(packet);
}

TEST_F(FakeKeyframeGeneratorHandlerTest, shouldNotSendPLIIfKeyframeIsFirstPacket) {
auto packet = erizo::PacketTools::createVP8Packet(erizo::kArbitrarySeqNumber, true, true);
EXPECT_CALL(*reader.get(), read(_, _)).With(Args<1>(erizo::IsPLI())).Times(0);
pipeline->write(packet);
}

TEST_F(FakeKeyframeGeneratorHandlerTest, shouldSendPLIsPeriodically) {
auto packet = erizo::PacketTools::createVP8Packet(erizo::kArbitrarySeqNumber, false, false);
EXPECT_CALL(*reader.get(), read(_, _)).With(Args<1>(erizo::IsPLI())).Times(2);
pipeline->write(packet);
executeTasksInNextMs(350);
}

TEST_F(FakeKeyframeGeneratorHandlerTest, shouldNotSendPeriodicPLISIfKeyframeIsReceived) {
auto packet = erizo::PacketTools::createVP8Packet(erizo::kArbitrarySeqNumber, false, false);
auto keyframe_packet = erizo::PacketTools::createVP8Packet(erizo::kArbitrarySeqNumber, true, true);
EXPECT_CALL(*reader.get(), read(_, _)).With(Args<1>(erizo::IsPLI())).Times(1);
pipeline->write(packet);
pipeline->write(keyframe_packet);
executeTasksInNextMs(350);
}

4 changes: 3 additions & 1 deletion erizo/src/test/utils/Matchers.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ MATCHER(IsFIR, "") {
return RtpUtils::isFIR(packet);
}

MATCHER_P(PacketLengthIs, packet_length, "") {
return (std::get<0>(arg)->length == packet_length);
}
MATCHER_P(PacketBelongsToSpatialLayer, spatial_layer_id, "") {
return std::get<0>(arg)->belongsToSpatialLayer(spatial_layer_id);
}
Expand All @@ -80,7 +83,6 @@ MATCHER(PacketIsNotKeyframe, "") {
return !std::get<0>(arg)->is_keyframe;
}


MATCHER(IsKeyframeFirstPacket, "") {
erizo::RtpHeader *packet = reinterpret_cast<erizo::RtpHeader*>(std::get<0>(arg));
char* data_pointer;
Expand Down
Loading

0 comments on commit cfe9e81

Please sign in to comment.