Commit afd1934d authored by Philippe Gorley's avatar Philippe Gorley Committed by Sébastien Blin

encoder: refactor for reusability

Refactors the MediaEncoder class so it can be reused elsewhere in the
codebase.

Concretely, MediaEncoder now supports multiple streams and file formats.

Adds a unit test for MediaEncoder.

Change-Id: I27d6f2e8538809e01fef5c35978a0c35dcf8cae4
Reviewed-by: Sébastien Blin's avatarSebastien Blin <sebastien.blin@savoirfairelinux.com>
parent e9737c9c
......@@ -118,9 +118,9 @@ AudioSender::setup(SocketPair& socketPair)
try {
/* Encoder setup */
RING_DBG("audioEncoder_->openOutput %s", dest_.c_str());
RING_DBG("audioEncoder_->openLiveOutput %s", dest_.c_str());
audioEncoder_->setMuted(muteState_);
audioEncoder_->openOutput(dest_, args_);
audioEncoder_->openLiveOutput(dest_, args_);
audioEncoder_->setInitSeqVal(seqVal_);
audioEncoder_->setIOContext(muxContext_);
audioEncoder_->startIO();
......
This diff is collapsed.
......@@ -30,6 +30,7 @@
#include "noncopyable.h"
#include "media_buffer.h"
#include "media_codec.h"
#include "media_device.h"
#include <map>
......@@ -63,7 +64,9 @@ public:
void setInterruptCallback(int (*cb)(void*), void *opaque);
void setDeviceOptions(const DeviceParams& args);
void openOutput(const std::string& filename, const MediaDescription& args);
void openLiveOutput(const std::string& filename, const MediaDescription& args);
void openFileOutput(const std::string& filename, std::map<std::string, std::string> options);
int addStream(const SystemCodecInfo& codec, std::string parameters = "");
void startIO();
void setIOContext(const std::unique_ptr<MediaIOHandle> &ioctx);
......@@ -80,7 +83,7 @@ public:
std::string print_sdp();
/* getWidth and getHeight return size of the encoded frame.
* Values have meaning only after openOutput call.
* Values have meaning only after openLiveOutput call.
*/
int getWidth() const { return device_.width; }
int getHeight() const { return device_.height; }
......@@ -92,18 +95,19 @@ public:
bool useCodec(const AccountCodecInfo* codec) const noexcept;
unsigned getStreamCount() const;
private:
NON_COPYABLE(MediaEncoder);
void setOptions(const MediaDescription& args);
void setScaleDest(void *data, int width, int height, int pix_fmt);
void prepareEncoderContext(bool is_video);
void forcePresetX264();
AVCodecContext* prepareEncoderContext(AVCodec* outputCodec, bool is_video);
void forcePresetX264(AVCodecContext* encoderCtx);
void extractProfileLevelID(const std::string &parameters, AVCodecContext *ctx);
AVCodec *outputEncoder_ = nullptr;
AVCodecContext *encoderCtx_ = nullptr;
std::vector<AVCodecContext*> encoders_;
AVFormatContext *outputCtx_ = nullptr;
AVStream *stream_ = nullptr;
int currentStreamIdx_ = -1;
unsigned sent_samples = 0;
#ifdef RING_VIDEO
......@@ -113,7 +117,6 @@ private:
std::vector<uint8_t> scaledFrameBuffer_;
int scaledFrameBufferSize_ = 0;
int streamIndex_ = -1;
bool is_muted = false;
protected:
......
......@@ -44,7 +44,7 @@ VideoSender::VideoSender(const std::string& dest, const DeviceParams& dev,
{
videoEncoder_->setDeviceOptions(dev);
keyFrameFreq_ = dev.framerate.numerator() * KEY_FRAME_PERIOD;
videoEncoder_->openOutput(dest, args);
videoEncoder_->openLiveOutput(dest, args);
videoEncoder_->setInitSeqVal(seqVal);
videoEncoder_->setIOContext(muxContext_);
videoEncoder_->startIO();
......
......@@ -61,4 +61,10 @@ ut_string_utils_SOURCES = string_utils/testString_utils.cpp
check_PROGRAMS += ut_video_input
ut_video_input_SOURCES = media/video/testVideo_input.cpp
#
# media_encoder
#
check_PROGRAMS += ut_media_encoder
ut_media_encoder_SOURCES = media/test_media_encoder.cpp
TESTS = $(check_PROGRAMS)
/*
* Copyright (C) 2018 Savoir-faire Linux Inc.
*
* Author: Philippe Gorley <philippe.gorley@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.
*/
#include <cppunit/TestAssert.h>
#include <cppunit/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h>
#include "dring.h"
#include "fileutils.h"
#include "libav_deps.h"
#include "media_encoder.h"
#include "media_io_handle.h"
#include "system_codec_container.h"
#include "../../test_runner.h"
namespace ring { namespace test {
class MediaEncoderTest : public CppUnit::TestFixture {
public:
static std::string name() { return "media_encoder"; }
void setUp();
void tearDown();
private:
void testMultiStream();
CPPUNIT_TEST_SUITE(MediaEncoderTest);
CPPUNIT_TEST(testMultiStream);
CPPUNIT_TEST_SUITE_END();
std::unique_ptr<MediaEncoder> encoder_;
std::unique_ptr<MediaIOHandle> ioHandle_;
std::vector<std::string> files_;
std::string cacheDir_;
};
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(MediaEncoderTest, MediaEncoderTest::name());
void
MediaEncoderTest::setUp()
{
DRing::init(DRing::InitFlag(DRing::DRING_FLAG_DEBUG | DRing::DRING_FLAG_CONSOLE_LOG));
libav_utils::ring_avcodec_init();
encoder_.reset(new MediaEncoder);
cacheDir_ = fileutils::get_cache_dir() + DIR_SEPARATOR_CH;
files_.push_back(cacheDir_ + "test.mkv");
}
void
MediaEncoderTest::tearDown()
{
// clean up behind ourselves
for (const auto& file : files_)
fileutils::remove(cacheDir_ + file);
DRing::fini();
}
static AVFrame*
getVideoFrame(int width, int height, int frame_index)
{
int x, y;
AVFrame* frame = av_frame_alloc();
if (!frame)
return nullptr;
frame->format = AV_PIX_FMT_YUV420P;
frame->width = width;
frame->height = height;
if (av_frame_get_buffer(frame, 32) < 0) {
av_frame_free(&frame);
return nullptr;
}
/* Y */
for (y = 0; y < height; y++)
for (x = 0; x < width; x++)
frame->data[0][y * frame->linesize[0] + x] = x + y + frame_index * 3;
/* Cb and Cr */
for (y = 0; y < height / 2; y++) {
for (x = 0; x < width / 2; x++) {
frame->data[1][y * frame->linesize[1] + x] = 128 + y + frame_index * 2;
frame->data[2][y * frame->linesize[2] + x] = 64 + x + frame_index * 5;
}
}
return frame;
}
static AVFrame*
getAudioFrame(int sampleRate, int nbSamples, int nbChannels)
{
const constexpr float pi = 3.14159265358979323846264338327950288; // M_PI
const float tincr = 2 * pi * 440.0 / sampleRate;
float t = 0;
AVFrame* frame = av_frame_alloc();
if (!frame)
return nullptr;
frame->format = AV_SAMPLE_FMT_S16;
frame->channels = nbChannels;
frame->channel_layout = av_get_default_channel_layout(nbChannels);
frame->nb_samples = nbSamples;
frame->sample_rate = sampleRate;
if (av_frame_get_buffer(frame, 0) < 0) {
av_frame_free(&frame);
return nullptr;
}
auto samples = reinterpret_cast<uint16_t*>(frame->data[0]);
for (int i = 0; i < 200; ++i) {
for (int j = 0; j < nbSamples; ++j) {
samples[2 * j] = static_cast<int>(sin(t) * 10000);
for (int k = 1; k < nbChannels; ++k) {
samples[2 * j + k] = samples[2 * j];
}
t += tincr;
}
}
return frame;
}
void
MediaEncoderTest::testMultiStream()
{
const constexpr int sampleRate = 48000;
const constexpr int nbChannels = 2;
const constexpr int width = 320;
const constexpr int height = 240;
std::map<std::string, std::string> options;
options["sample_rate"] = std::to_string(sampleRate);
options["channels"] = std::to_string(nbChannels);
options["width"] = std::to_string(width);
options["height"] = std::to_string(height);
auto vp8Codec = std::static_pointer_cast<ring::SystemVideoCodecInfo>(
getSystemCodecContainer()->searchCodecByName("VP8", ring::MEDIA_VIDEO)
);
auto opusCodec = std::static_pointer_cast<SystemAudioCodecInfo>(
getSystemCodecContainer()->searchCodecByName("opus", ring::MEDIA_AUDIO)
);
try {
encoder_->openFileOutput(cacheDir_ + "test.mkv", options);
encoder_->setIOContext(ioHandle_);
int videoIdx = encoder_->addStream(*vp8Codec.get());
CPPUNIT_ASSERT(videoIdx >= 0);
CPPUNIT_ASSERT(encoder_->getStreamCount() == 1);
int audioIdx = encoder_->addStream(*opusCodec.get());
CPPUNIT_ASSERT(audioIdx >= 0);
CPPUNIT_ASSERT(videoIdx != audioIdx);
CPPUNIT_ASSERT(encoder_->getStreamCount() == 2);
encoder_->startIO();
int sentSamples = 0;
AVFrame* audio = nullptr;
AVFrame* video = nullptr;
for (int i = 0; i < 25; ++i) {
audio = getAudioFrame(sampleRate, 0.02*sampleRate, nbChannels);
CPPUNIT_ASSERT(audio);
audio->pts = sentSamples;
video = getVideoFrame(width, height, i);
CPPUNIT_ASSERT(video);
video->pts = i;
CPPUNIT_ASSERT(encoder_->encode(audio, audioIdx) >= 0);
sentSamples += audio->nb_samples;
CPPUNIT_ASSERT(encoder_->encode(video, videoIdx) >= 0);
av_frame_free(&audio);
av_frame_free(&video);
}
CPPUNIT_ASSERT(encoder_->flush() >= 0);
} catch (const MediaEncoderException& e) {
CPPUNIT_FAIL(e.what());
}
}
}} // namespace ring::test
RING_TEST_RUNNER(ring::test::MediaEncoderTest::name());
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment