337 lines
7.6 KiB
C++
337 lines
7.6 KiB
C++
/*
|
|
* Copyright (C) 2015 Emeric Poupon
|
|
*
|
|
* This file is part of LMS.
|
|
*
|
|
* LMS 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.
|
|
*
|
|
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
#include <atomic>
|
|
#include <mutex>
|
|
|
|
#include "utils/Path.hpp"
|
|
#include "utils/Logger.hpp"
|
|
|
|
#include "AvTranscoder.hpp"
|
|
|
|
namespace Av {
|
|
|
|
#define LMS_LOG_TRANSCODE(sev) LMS_LOG(TRANSCODE, INFO) << "[" << _id << "] - "
|
|
|
|
struct EncodingInfo
|
|
{
|
|
Encoding encoding;
|
|
std::string mimetype;
|
|
int id;
|
|
};
|
|
|
|
static std::vector<EncodingInfo> encodingInfos =
|
|
{
|
|
{Encoding::MP3, "audio/mp3", 0},
|
|
{Encoding::OGA, "audio/ogg", 1},
|
|
{Encoding::OGV, "video/ogg", 2},
|
|
{Encoding::WEBMA, "audio/webm", 3},
|
|
{Encoding::WEBMV, "video/webm", 4},
|
|
{Encoding::M4A, "audio/mp4", 5},
|
|
{Encoding::M4V, "video/mp4", 6},
|
|
};
|
|
|
|
std::string encoding_to_mimetype(Encoding encoding)
|
|
{
|
|
for (auto encodingInfo : encodingInfos)
|
|
{
|
|
if (encodingInfo.encoding == encoding)
|
|
return encodingInfo.mimetype;
|
|
}
|
|
|
|
throw std::logic_error("encoding_to_mimetype failed!");
|
|
}
|
|
|
|
int encoding_to_int(Encoding encoding)
|
|
{
|
|
for (auto encodingInfo : encodingInfos)
|
|
{
|
|
if (encodingInfo.encoding == encoding)
|
|
return encodingInfo.id;
|
|
}
|
|
|
|
throw std::logic_error("encoding_to_int failed!");
|
|
}
|
|
|
|
Encoding encoding_from_int(int encodingId)
|
|
{
|
|
for (auto encodingInfo : encodingInfos)
|
|
{
|
|
if (encodingInfo.id == encodingId)
|
|
return encodingInfo.encoding;
|
|
}
|
|
|
|
throw std::logic_error("encoding_from_int failed!");
|
|
}
|
|
|
|
// TODO, parametrize?
|
|
static const std::vector<std::string> execNames =
|
|
{
|
|
"avconv",
|
|
"ffmpeg",
|
|
};
|
|
|
|
static std::mutex transcoderMutex;
|
|
static boost::filesystem::path avConvPath = boost::filesystem::path();
|
|
static std::atomic<size_t> globalId = {0};
|
|
|
|
void
|
|
Transcoder::init()
|
|
{
|
|
for (std::string execName : execNames)
|
|
{
|
|
boost::filesystem::path p = searchExecPath(execName);
|
|
if (!p.empty())
|
|
{
|
|
avConvPath = p;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!avConvPath.empty())
|
|
LMS_LOG(TRANSCODE, INFO) << "Using transcoder " << avConvPath;
|
|
else
|
|
throw std::runtime_error("Cannot find any transcoder binary!");
|
|
}
|
|
|
|
Transcoder::Transcoder(boost::filesystem::path filePath, TranscodeParameters parameters)
|
|
: _filePath(filePath),
|
|
_parameters(parameters),
|
|
_isComplete(false),
|
|
_id(globalId++)
|
|
{
|
|
|
|
}
|
|
|
|
bool
|
|
Transcoder::start()
|
|
{
|
|
if (!boost::filesystem::exists(_filePath))
|
|
return false;
|
|
else if (!boost::filesystem::is_regular( _filePath) )
|
|
return false;
|
|
|
|
LMS_LOG_TRANSCODE(INFO) << "Transcoding file '" << _filePath << "'";
|
|
|
|
std::vector<std::string> args;
|
|
|
|
args.push_back(avConvPath.string());
|
|
|
|
// Make sure we do not produce anything in the stderr output
|
|
// in order not to block the whole forked process
|
|
args.push_back("-loglevel");
|
|
args.push_back("quiet");
|
|
args.push_back("-nostdin");
|
|
|
|
// input Offset
|
|
if (_parameters.getOffset().total_seconds() > 0)
|
|
{
|
|
args.push_back("-ss");
|
|
args.push_back(std::to_string(_parameters.getOffset().total_seconds()));
|
|
}
|
|
|
|
// Input file
|
|
args.push_back("-i");
|
|
args.push_back(_filePath.string());
|
|
|
|
// Output bitrates
|
|
args.push_back("-b:a");
|
|
args.push_back(std::to_string(_parameters.getBitrate(Stream::Type::Audio)));
|
|
// if (_parameters.getOutputFormat().getType() == Format::Video)
|
|
// oss << " -b:v " << _parameters.getOutputBitrate(Stream::Video);
|
|
|
|
// Stream mapping
|
|
for (int streamId : _parameters.getSelectedStreamIds())
|
|
{
|
|
// 0 means the first input file
|
|
args.push_back("-map");
|
|
args.push_back("0:" + std::to_string(streamId));
|
|
}
|
|
|
|
// Codecs and formats
|
|
switch( _parameters.getEncoding())
|
|
{
|
|
case Encoding::MP3:
|
|
args.push_back("-f");
|
|
args.push_back("mp3");
|
|
break;
|
|
|
|
case Encoding::OGA:
|
|
args.push_back("-acodec");
|
|
args.push_back("libvorbis");
|
|
args.push_back("-f");
|
|
args.push_back("ogg");
|
|
break;
|
|
|
|
case Encoding::OGV:
|
|
args.push_back("-acodec");
|
|
args.push_back("libvorbis");
|
|
args.push_back("-ac");
|
|
args.push_back("2");
|
|
args.push_back("-ar");
|
|
args.push_back("44100");
|
|
args.push_back("-vcodec");
|
|
args.push_back("libtheora");
|
|
args.push_back("-threads");
|
|
args.push_back("4");
|
|
args.push_back("-f");
|
|
args.push_back("ogg");
|
|
break;
|
|
case Encoding::WEBMA:
|
|
args.push_back("-codec:a");
|
|
args.push_back("libvorbis");
|
|
args.push_back("-f");
|
|
args.push_back("webm");
|
|
break;
|
|
|
|
case Encoding::WEBMV:
|
|
args.push_back("-acodec");
|
|
args.push_back("libvorbis");
|
|
args.push_back("-ac");
|
|
args.push_back("2");
|
|
args.push_back("-ar");
|
|
args.push_back("44100");
|
|
args.push_back("-vcodec");
|
|
args.push_back("libvpx");
|
|
args.push_back("-threads");
|
|
args.push_back("4");
|
|
args.push_back("-f");
|
|
args.push_back("webm");
|
|
break;
|
|
|
|
case Encoding::M4A:
|
|
args.push_back("-acodec");
|
|
args.push_back("aac");
|
|
args.push_back("-f");
|
|
args.push_back("mp4");
|
|
args.push_back("-strict");
|
|
args.push_back("experimental");
|
|
break;
|
|
|
|
case Encoding::M4V:
|
|
args.push_back("-acodec");
|
|
args.push_back("aac");
|
|
args.push_back("-strict");
|
|
args.push_back("experimental");
|
|
args.push_back("-ac");
|
|
args.push_back("2");
|
|
args.push_back("-ar");
|
|
args.push_back("-44100");
|
|
args.push_back("-vcodec");
|
|
args.push_back("libx264");
|
|
args.push_back("-f");
|
|
args.push_back("m4v");
|
|
break;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
args.push_back("pipe:1");
|
|
|
|
LMS_LOG_TRANSCODE(INFO) << "Dumping args (" << args.size() << ")";
|
|
for (std::string arg : args)
|
|
LMS_LOG_TRANSCODE(DEBUG) << "Arg = '" << arg << "'";
|
|
|
|
// make sure only one thread is executing this part of code
|
|
{
|
|
std::lock_guard<std::mutex> lock(transcoderMutex);
|
|
|
|
_child = std::make_shared<redi::ipstream>();
|
|
|
|
// Caution: stdin must have been closed before
|
|
_child->open(avConvPath.string(), args);
|
|
if (!_child->is_open())
|
|
{
|
|
LMS_LOG_TRANSCODE(DEBUG) << "Exec failed!";
|
|
return false;
|
|
}
|
|
|
|
if (_child->out().eof())
|
|
{
|
|
LMS_LOG_TRANSCODE(DEBUG) << "Early end of file!";
|
|
return false;
|
|
}
|
|
|
|
}
|
|
LMS_LOG_TRANSCODE(DEBUG) << "Stream opened!";
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
Transcoder::process(std::vector<unsigned char>& output, std::size_t maxSize)
|
|
{
|
|
if (!_child || _isComplete)
|
|
return;
|
|
|
|
if (_child->out().fail())
|
|
{
|
|
LMS_LOG_TRANSCODE(DEBUG) << "Stdout FAILED 2";
|
|
}
|
|
|
|
if (_child->out().eof())
|
|
{
|
|
LMS_LOG_TRANSCODE(DEBUG) << "Stdout ENDED 2";
|
|
}
|
|
|
|
output.resize(maxSize);
|
|
|
|
LMS_LOG_TRANSCODE(DEBUG) << "Reading up to " << output.size() << " bytes";
|
|
|
|
//Read on the output stream
|
|
_child->out().read(reinterpret_cast<char*>(&output[0]), maxSize);
|
|
output.resize(_child->out().gcount());
|
|
|
|
LMS_LOG_TRANSCODE(DEBUG) << "Read " << output.size() << " bytes";
|
|
|
|
if (_child->out().fail())
|
|
{
|
|
LMS_LOG_TRANSCODE(DEBUG) << "Stdout FAILED";
|
|
}
|
|
|
|
if (_child->out().eof())
|
|
{
|
|
LMS_LOG_TRANSCODE(DEBUG) << "Stdout EOF!";
|
|
_child->clear();
|
|
|
|
_isComplete = true;
|
|
_child.reset();
|
|
}
|
|
|
|
_total += output.size();
|
|
|
|
LMS_LOG_TRANSCODE(DEBUG) << "nb bytes = " << output.size() << ", total = " << _total;
|
|
}
|
|
|
|
Transcoder::~Transcoder()
|
|
{
|
|
LMS_LOG_TRANSCODE(DEBUG) << ", ~Transcoder called! Total produced bytes = " << _total;
|
|
|
|
if (_child)
|
|
{
|
|
LMS_LOG_TRANSCODE(DEBUG) << "Child still here!";
|
|
_child->rdbuf()->kill(SIGKILL);
|
|
LMS_LOG_TRANSCODE(DEBUG) << "Closing...";
|
|
_child->rdbuf()->close();
|
|
LMS_LOG_TRANSCODE(DEBUG) << "Closing DONE";
|
|
}
|
|
}
|
|
|
|
} // namespace Transcode
|