diff --git a/src/media/media_encoder.cpp b/src/media/media_encoder.cpp index 193869e8cba1414d181315b5aece0face4380215..94c6e4c59a9cedd1bdeb37fcb1b9b88c704a8da5 100644 --- a/src/media/media_encoder.cpp +++ b/src/media/media_encoder.cpp @@ -27,6 +27,7 @@ #include "media_io_handle.h" #include "audio/audiobuffer.h" +#include "fileutils.h" #include "string_utils.h" #include "logger.h" @@ -34,9 +35,11 @@ extern "C" { #include <libavutil/parseutils.h> } +#include <algorithm> +#include <fstream> #include <iostream> +#include <json/json.h> #include <sstream> -#include <algorithm> #include <thread> // hardware_concurrency // Define following line if you need to debug libav SDP @@ -274,7 +277,8 @@ MediaEncoder::addStream(const SystemCodecInfo& systemCodecInfo, std::string para currentStreamIdx_ = stream->index; - if (avcodec_open2(encoderCtx, outputCodec, nullptr) < 0) + readConfig(&options_, outputCodec->name); + if (avcodec_open2(encoderCtx, outputCodec, &options_) < 0) throw MediaEncoderException("Could not open encoder"); #ifndef _WIN32 @@ -657,4 +661,49 @@ MediaEncoder::getStream(const std::string& name, int streamIdx) const return MediaStream(name, enc); } +void +MediaEncoder::readConfig(AVDictionary** dict, const std::string& encoder) +{ + std::string path = fileutils::get_config_dir() + DIR_SEPARATOR_STR + "encoder.json"; + if (fileutils::isFile(path)) { + try { + Json::Value root; + std::ifstream file(path); + file >> root; + if (!root.isObject()) { + RING_ERR() << "Invalid encoder configuration: root is not an object"; + return; + } + const auto& config = root[encoder]; + if (config.isNull()) { + RING_WARN() << "Encoder '" << encoder << "' not found in configuration file"; + return; + } + if (!config.isObject()) { + RING_ERR() << "Invalid encoder configuration: '" << encoder << "' is not an object"; + return; + } + // If users want to change these, they should use the settings page. + std::vector<std::string> ignoredKeys = { "width", "height", "framerate", "sample_rate", "channels", "frame_size", "parameters" }; + for (Json::Value::const_iterator it = config.begin(); it != config.end(); ++it) { + Json::Value v = *it; + if (!it.key().isConvertibleTo(Json::ValueType::stringValue) + || !v.isConvertibleTo(Json::ValueType::stringValue)) { + RING_ERR() << "Invalid configuration for '" << encoder << "'"; + return; + } + const auto& key = it.key().asString(); + const auto& value = v.asString(); + // TODO treat some keys specially, such as profile, level, bit_rate, rate control options, qmin, qmax, as these are AVCodecContext fields + if (std::find(ignoredKeys.cbegin(), ignoredKeys.cend(), key) != ignoredKeys.cend()) + continue; + else + libav_utils::setDictValue(dict, key, value); + } + } catch (const Json::Exception& e) { + RING_ERR() << "Failed to load encoder configuration file: " << e.what(); + } + } +} + } // namespace ring diff --git a/src/media/media_encoder.h b/src/media/media_encoder.h index 8791ed035ec53f82d137a7939715317ff86a9f54..6a878dbbde721a3039adcb647506a9158f5e3846 100644 --- a/src/media/media_encoder.h +++ b/src/media/media_encoder.h @@ -124,6 +124,7 @@ private: bool is_muted = false; protected: + void readConfig(AVDictionary** dict, const std::string& encoder); AVDictionary *options_ = nullptr; DeviceParams device_; std::shared_ptr<const AccountCodecInfo> codec_;