Switched from sync transcoder to async transcoder
This commit is contained in:
@@ -17,7 +17,7 @@
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "av/AvInfo.hpp"
|
||||
#include "AudioFile.hpp"
|
||||
|
||||
extern "C"
|
||||
{
|
||||
@@ -44,20 +44,28 @@ static std::string averror_to_string(int error)
|
||||
return "Unknown error";
|
||||
}
|
||||
|
||||
MediaFileException::MediaFileException(int avError)
|
||||
: AvException {"MediaFileException: " + averror_to_string(avError)}
|
||||
class AudioFileException : public Av::Exception
|
||||
{
|
||||
public:
|
||||
AudioFileException(int avError)
|
||||
: Av::Exception {"AudioFileException: " + averror_to_string(avError)}
|
||||
{}
|
||||
};
|
||||
|
||||
std::unique_ptr<IAudioFile>
|
||||
parseAudioFile(const std::filesystem::path& p)
|
||||
{
|
||||
return std::make_unique<AudioFile>(p);
|
||||
}
|
||||
|
||||
|
||||
MediaFile::MediaFile(const std::filesystem::path& p)
|
||||
AudioFile::AudioFile(const std::filesystem::path& p)
|
||||
: _p {p}
|
||||
{
|
||||
int error = avformat_open_input(&_context, _p.string().c_str(), nullptr, nullptr);
|
||||
int error {avformat_open_input(&_context, _p.string().c_str(), nullptr, nullptr)};
|
||||
if (error < 0)
|
||||
{
|
||||
LMS_LOG(AV, ERROR) << "Cannot open " << _p.string() << ": " << averror_to_string(error);
|
||||
throw MediaFileException(error);
|
||||
throw AudioFileException {error};
|
||||
}
|
||||
|
||||
error = avformat_find_stream_info(_context, nullptr);
|
||||
@@ -65,32 +73,32 @@ MediaFile::MediaFile(const std::filesystem::path& p)
|
||||
{
|
||||
LMS_LOG(AV, ERROR) << "Cannot find stream information on " << _p.string() << ": " << averror_to_string(error);
|
||||
avformat_close_input(&_context);
|
||||
throw MediaFileException(error);
|
||||
throw AudioFileException {error};
|
||||
}
|
||||
}
|
||||
|
||||
MediaFile::~MediaFile()
|
||||
AudioFile::~AudioFile()
|
||||
{
|
||||
avformat_close_input(&_context);
|
||||
}
|
||||
|
||||
std::string
|
||||
MediaFile::getFormatName() const
|
||||
const std::filesystem::path&
|
||||
AudioFile::getPath() const
|
||||
{
|
||||
return _context->iformat->name;
|
||||
return _p;
|
||||
}
|
||||
|
||||
std::chrono::milliseconds
|
||||
MediaFile::getDuration() const
|
||||
AudioFile::getDuration() const
|
||||
{
|
||||
if (_context->duration == AV_NOPTS_VALUE)
|
||||
return std::chrono::milliseconds(0); // TODO estimate
|
||||
return std::chrono::milliseconds {0}; // TODO estimate
|
||||
|
||||
return std::chrono::milliseconds(_context->duration / AV_TIME_BASE * 1000);
|
||||
return std::chrono::milliseconds {_context->duration / AV_TIME_BASE * 1000};
|
||||
}
|
||||
|
||||
void
|
||||
getMetaDataFromDictionnary(AVDictionary* dictionnary, std::map<std::string, std::string>& res)
|
||||
getMetaDataFromDictionnary(AVDictionary* dictionnary, AudioFile::MetadataMap& res)
|
||||
{
|
||||
if (!dictionnary)
|
||||
return;
|
||||
@@ -102,10 +110,10 @@ getMetaDataFromDictionnary(AVDictionary* dictionnary, std::map<std::string, std:
|
||||
}
|
||||
}
|
||||
|
||||
std::map<std::string, std::string>
|
||||
MediaFile::getMetaData(void)
|
||||
AudioFile::MetadataMap
|
||||
AudioFile::getMetaData() const
|
||||
{
|
||||
std::map<std::string, std::string> res;
|
||||
MetadataMap res;
|
||||
|
||||
getMetaDataFromDictionnary(_context->metadata, res);
|
||||
|
||||
@@ -113,7 +121,7 @@ MediaFile::getMetaData(void)
|
||||
// If we did not find tags, search metadata in streams
|
||||
if (res.empty())
|
||||
{
|
||||
for (std::size_t i = 0; i < _context->nb_streams; ++i)
|
||||
for (std::size_t i {}; i < _context->nb_streams; ++i)
|
||||
{
|
||||
getMetaDataFromDictionnary(_context->streams[i]->metadata, res);
|
||||
|
||||
@@ -126,7 +134,7 @@ MediaFile::getMetaData(void)
|
||||
}
|
||||
|
||||
std::vector<StreamInfo>
|
||||
MediaFile::getStreamInfo() const
|
||||
AudioFile::getStreamInfo() const
|
||||
{
|
||||
std::vector<StreamInfo> res;
|
||||
|
||||
@@ -154,7 +162,7 @@ MediaFile::getStreamInfo() const
|
||||
}
|
||||
|
||||
std::optional<std::size_t>
|
||||
MediaFile::getBestStream() const
|
||||
AudioFile::getBestStream() const
|
||||
{
|
||||
int res = av_find_best_stream(_context,
|
||||
AVMEDIA_TYPE_AUDIO,
|
||||
@@ -170,7 +178,7 @@ MediaFile::getBestStream() const
|
||||
}
|
||||
|
||||
bool
|
||||
MediaFile::hasAttachedPictures(void) const
|
||||
AudioFile::hasAttachedPictures(void) const
|
||||
{
|
||||
for (std::size_t i = 0; i < _context->nb_streams; ++i)
|
||||
{
|
||||
@@ -182,9 +190,9 @@ MediaFile::hasAttachedPictures(void) const
|
||||
}
|
||||
|
||||
void
|
||||
MediaFile::visitAttachedPictures(std::function<void(const Picture&)> func) const
|
||||
AudioFile::visitAttachedPictures(std::function<void(const Picture&)> func) const
|
||||
{
|
||||
static const std::map<int, std::string> codecMimeMap =
|
||||
static const std::unordered_map<int, std::string> codecMimeMap =
|
||||
{
|
||||
{ AV_CODEC_ID_BMP, "image/x-bmp" },
|
||||
{ AV_CODEC_ID_GIF, "image/gif" },
|
||||
@@ -230,7 +238,7 @@ MediaFile::visitAttachedPictures(std::function<void(const Picture&)> func) const
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<MediaFileFormat>
|
||||
std::optional<AudioFileFormat>
|
||||
guessMediaFileFormat(const std::filesystem::path& file)
|
||||
{
|
||||
AVOutputFormat* format {av_guess_format(NULL,file.string().c_str(),NULL)};
|
||||
@@ -252,7 +260,7 @@ guessMediaFileFormat(const std::filesystem::path& file)
|
||||
else if (mimeTypes.size() > 1)
|
||||
LMS_LOG(AV, INFO) << "File '" << file.string() << "' reported several mime types: '" << format->mime_type << "'";
|
||||
|
||||
MediaFileFormat res;
|
||||
AudioFileFormat res;
|
||||
res.format = formats.front();
|
||||
res.mimeType = mimeTypes.empty() ? "application/octet-stream" : mimeTypes.front();
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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/>.
|
||||
*/
|
||||
|
||||
/* This file contains some classes in order to get info from file using the libavconv */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "av/IAudioFile.hpp"
|
||||
|
||||
struct AVFormatContext;
|
||||
|
||||
namespace Av
|
||||
{
|
||||
|
||||
class AudioFile final : public IAudioFile
|
||||
{
|
||||
public:
|
||||
AudioFile(const std::filesystem::path& p);
|
||||
~AudioFile();
|
||||
|
||||
AudioFile(const AudioFile&) = delete;
|
||||
AudioFile(AudioFile&&) = delete;
|
||||
AudioFile& operator=(const AudioFile&) = delete;
|
||||
AudioFile& operator=(AudioFile&&) = delete;
|
||||
|
||||
const std::filesystem::path& getPath() const override;
|
||||
std::chrono::milliseconds getDuration() const override;
|
||||
MetadataMap getMetaData() const override;
|
||||
std::vector<StreamInfo> getStreamInfo() const override;
|
||||
std::optional<std::size_t> getBestStream() const override;
|
||||
bool hasAttachedPictures() const override;
|
||||
void visitAttachedPictures(std::function<void(const Picture&)> func) const override;
|
||||
|
||||
private:
|
||||
|
||||
const std::filesystem::path _p;
|
||||
AVFormatContext* _context {};
|
||||
};
|
||||
|
||||
} // namespace Av
|
||||
|
||||
+20
-12
@@ -17,7 +17,7 @@
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "AvTranscodeResourceHandler.hpp"
|
||||
#include "TranscodeResourceHandler.hpp"
|
||||
|
||||
namespace Av
|
||||
{
|
||||
@@ -36,24 +36,32 @@ namespace Av
|
||||
_transcoder.start();
|
||||
}
|
||||
|
||||
void
|
||||
Wt::Http::ResponseContinuation*
|
||||
TranscodeResourceHandler::processRequest(const Wt::Http::Request& /*request*/, Wt::Http::Response& response)
|
||||
{
|
||||
response.setMimeType(_transcoder.getOutputMimeType());
|
||||
|
||||
if (!_transcoder.isComplete())
|
||||
if (_nbBytesReady > 0)
|
||||
{
|
||||
std::vector<unsigned char> buffer;
|
||||
|
||||
_transcoder.process(buffer, _chunkSize);
|
||||
response.out().write(reinterpret_cast<const char *>(&buffer[0]), buffer.size());
|
||||
response.out().write(reinterpret_cast<const char *>(&_buffer[0]), _nbBytesReady);
|
||||
_nbBytesReady = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
TranscodeResourceHandler::isFinished() const
|
||||
{
|
||||
return _transcoder.isComplete();
|
||||
if (!_transcoder.finished())
|
||||
{
|
||||
Wt::Http::ResponseContinuation *continuation {response.createContinuation()};
|
||||
continuation->waitForMoreData();
|
||||
_transcoder.asyncRead(_buffer.data(), _buffer.size(), [=](std::size_t nbBytesRead)
|
||||
{
|
||||
assert(_nbBytesReady == 0);
|
||||
_nbBytesReady = nbBytesRead;
|
||||
continuation->haveMoreData();
|
||||
});
|
||||
|
||||
return continuation;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
+8
-4
@@ -19,9 +19,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <filesystem>
|
||||
#include "av/AvTranscoder.hpp"
|
||||
|
||||
#include "av/TranscodeParameters.hpp"
|
||||
#include "utils/IResourceHandler.hpp"
|
||||
#include "Transcoder.hpp"
|
||||
|
||||
namespace Av
|
||||
{
|
||||
@@ -33,10 +36,11 @@ namespace Av
|
||||
|
||||
private:
|
||||
|
||||
void processRequest(const Wt::Http::Request& request, Wt::Http::Response& reponse) override;
|
||||
bool isFinished() const override;
|
||||
Wt::Http::ResponseContinuation* processRequest(const Wt::Http::Request& request, Wt::Http::Response& reponse) override;
|
||||
|
||||
static constexpr std::size_t _chunkSize {262144};
|
||||
static constexpr std::size_t _chunkSize {32768};
|
||||
std::array<std::byte, _chunkSize> _buffer;
|
||||
std::size_t _nbBytesReady {};
|
||||
const std::filesystem::path _trackPath;
|
||||
Transcoder _transcoder;
|
||||
};
|
||||
@@ -17,12 +17,12 @@
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "av/AvTranscoder.hpp"
|
||||
#include "Transcoder.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <iomanip>
|
||||
|
||||
#include "av/AvInfo.hpp"
|
||||
#include "utils/IChildProcessManager.hpp"
|
||||
#include "utils/IConfig.hpp"
|
||||
#include "utils/Path.hpp"
|
||||
#include "utils/Logger.hpp"
|
||||
@@ -32,7 +32,7 @@ namespace Av {
|
||||
|
||||
#define LOG(sev) LMS_LOG(TRANSCODE, sev) << "[" << _id << "] - "
|
||||
|
||||
static std::atomic<size_t> globalId {};
|
||||
static std::atomic<size_t> globalId {};
|
||||
static std::filesystem::path ffmpegPath;
|
||||
|
||||
void
|
||||
@@ -40,39 +40,41 @@ Transcoder::init()
|
||||
{
|
||||
ffmpegPath = Service<IConfig>::get()->getPath("ffmpeg-file", "/usr/bin/ffmpeg");
|
||||
if (!std::filesystem::exists(ffmpegPath))
|
||||
throw LmsException {"File '" + ffmpegPath.string() + "' does not exist!"};
|
||||
throw Exception {"File '" + ffmpegPath.string() + "' does not exist!"};
|
||||
}
|
||||
|
||||
Transcoder::Transcoder(const std::filesystem::path& filePath, const TranscodeParameters& parameters)
|
||||
: _filePath {filePath},
|
||||
_parameters {parameters},
|
||||
_id {globalId++}
|
||||
: _id {globalId++}
|
||||
, _filePath {filePath}
|
||||
, _parameters {parameters}
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
Transcoder::~Transcoder() = default;
|
||||
|
||||
bool
|
||||
Transcoder::start()
|
||||
{
|
||||
if (ffmpegPath.empty())
|
||||
init();
|
||||
|
||||
try
|
||||
{
|
||||
if (!std::filesystem::exists(_filePath))
|
||||
{
|
||||
LOG(ERROR) << "File '" << _filePath << "' does not exist!";
|
||||
_isComplete = true;
|
||||
return false;
|
||||
}
|
||||
else if (!std::filesystem::is_regular_file( _filePath) )
|
||||
{
|
||||
LOG(ERROR) << "File '" << _filePath << "' is not regular!";
|
||||
_isComplete = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (const std::filesystem::filesystem_error& e)
|
||||
{
|
||||
LOG(ERROR) << "File error on '" << _filePath.string() << "': " << e.what();
|
||||
_isComplete = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -82,17 +84,21 @@ Transcoder::start()
|
||||
|
||||
args.emplace_back(ffmpegPath.string());
|
||||
|
||||
// Make sure we do not produce anything in the stderr output
|
||||
// Make sure:
|
||||
// - we do not produce anything in the stderr output
|
||||
// - we do not rely on input
|
||||
// in order not to block the whole forked process
|
||||
args.emplace_back("-loglevel");
|
||||
args.emplace_back("quiet");
|
||||
args.emplace_back("-nostdin");
|
||||
|
||||
// input Offset
|
||||
if (_parameters.offset)
|
||||
{
|
||||
args.emplace_back("-ss");
|
||||
args.emplace_back(std::to_string((*_parameters.offset).count()));
|
||||
|
||||
std::ostringstream oss;
|
||||
oss << std::fixed << std::showpoint << std::setprecision(3) << (_parameters.offset.count() / float {1000});
|
||||
args.emplace_back(oss.str());
|
||||
}
|
||||
|
||||
// Input file
|
||||
@@ -120,6 +126,7 @@ Transcoder::start()
|
||||
args.emplace_back("-b:a");
|
||||
args.emplace_back(std::to_string(_parameters.bitrate));
|
||||
|
||||
|
||||
// Codecs and formats
|
||||
switch (_parameters.format)
|
||||
{
|
||||
@@ -157,7 +164,6 @@ Transcoder::start()
|
||||
break;
|
||||
|
||||
default:
|
||||
_isComplete = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -169,84 +175,56 @@ Transcoder::start()
|
||||
for (const std::string& arg : args)
|
||||
LOG(DEBUG) << "Arg = '" << arg << "'";
|
||||
|
||||
// make sure only one thread is executing this part of code
|
||||
// Caution: stdin must have been closed before
|
||||
try
|
||||
{
|
||||
static std::mutex transcoderMutex;
|
||||
|
||||
std::lock_guard<std::mutex> lock {transcoderMutex};
|
||||
|
||||
_child = std::make_shared<redi::ipstream>();
|
||||
|
||||
// Caution: stdin must have been closed before
|
||||
_child->open(ffmpegPath.string(), args);
|
||||
if (!_child->is_open())
|
||||
{
|
||||
LOG(DEBUG) << "Exec failed!";
|
||||
_isComplete = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_child->out().eof())
|
||||
{
|
||||
LOG(DEBUG) << "Early end of file!";
|
||||
_isComplete = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
_childProcess = Service<IChildProcessManager>::get()->spawnChildProcess(ffmpegPath, args);
|
||||
}
|
||||
catch (ChildProcessException& exception)
|
||||
{
|
||||
LOG(ERROR) << "Cannot execute '" << ffmpegPath << "': " << exception.what();
|
||||
return false;
|
||||
}
|
||||
LOG(DEBUG) << "Stream opened!";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
Transcoder::process(std::vector<unsigned char>& output, std::size_t maxSize)
|
||||
Transcoder::asyncWaitForData(WaitCallback cb)
|
||||
{
|
||||
if (!_child || _isComplete)
|
||||
return;
|
||||
assert(_childProcess);
|
||||
|
||||
output.resize(maxSize);
|
||||
LOG(DEBUG) << "Want to wait for data";
|
||||
|
||||
//Read on the output stream
|
||||
_child->out().read(reinterpret_cast<char*>(&output[0]), maxSize);
|
||||
output.resize(_child->out().gcount());
|
||||
|
||||
if (_child->out().fail())
|
||||
_childProcess->asyncWaitForData([cb = std::move(cb)]
|
||||
{
|
||||
LOG(DEBUG) << "Stdout FAILED";
|
||||
_isComplete = true;
|
||||
}
|
||||
|
||||
if (_child->out().eof())
|
||||
{
|
||||
LOG(DEBUG) << "Stdout EOF!";
|
||||
_isComplete = true;
|
||||
}
|
||||
|
||||
if (_isComplete)
|
||||
{
|
||||
LOG(DEBUG) << "Transcode complete!";
|
||||
_child->clear();
|
||||
_child.reset();
|
||||
}
|
||||
|
||||
_total += output.size();
|
||||
|
||||
LOG(DEBUG) << "nb bytes = " << output.size() << ", total = " << _total;
|
||||
cb();
|
||||
});
|
||||
}
|
||||
|
||||
Transcoder::~Transcoder()
|
||||
void
|
||||
Transcoder::asyncRead(std::byte* buffer, std::size_t bufferSize, ReadCallback readCallback)
|
||||
{
|
||||
LOG(DEBUG) << ", ~Transcoder called! Total produced bytes = " << _total;
|
||||
assert(_childProcess);
|
||||
|
||||
if (_child)
|
||||
return _childProcess->asyncRead(buffer, bufferSize, [readCallback {std::move(readCallback)}](IChildProcess::ReadResult /*res*/, std::size_t nbBytesRead)
|
||||
{
|
||||
LOG(DEBUG) << "Child still here!";
|
||||
_child->rdbuf()->kill(SIGKILL);
|
||||
LOG(DEBUG) << "Closing...";
|
||||
_child->rdbuf()->close();
|
||||
LOG(DEBUG) << "Closing DONE";
|
||||
}
|
||||
readCallback(nbBytesRead);
|
||||
});
|
||||
}
|
||||
|
||||
std::size_t
|
||||
Transcoder::readSome(std::byte* buffer, std::size_t bufferSize)
|
||||
{
|
||||
assert(_childProcess);
|
||||
|
||||
return _childProcess->readSome(buffer, bufferSize);
|
||||
}
|
||||
|
||||
bool
|
||||
Transcoder::finished() const
|
||||
{
|
||||
return _childProcess->finished();
|
||||
}
|
||||
|
||||
} // namespace Transcode
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
|
||||
#include "av/TranscodeParameters.hpp"
|
||||
#include "av/Types.hpp"
|
||||
|
||||
class IChildProcess;
|
||||
|
||||
namespace Av
|
||||
{
|
||||
class Transcoder
|
||||
{
|
||||
public:
|
||||
Transcoder(const std::filesystem::path& file, const TranscodeParameters& parameters);
|
||||
~Transcoder();
|
||||
|
||||
Transcoder(const Transcoder&) = delete;
|
||||
Transcoder& operator=(const Transcoder&) = delete;
|
||||
Transcoder(Transcoder&&) = delete;
|
||||
Transcoder& operator=(Transcoder&&) = delete;
|
||||
|
||||
bool start();
|
||||
|
||||
using WaitCallback = std::function<void()>;
|
||||
void asyncWaitForData(WaitCallback cb);
|
||||
|
||||
// non blocking calls
|
||||
using ReadCallback = std::function<void(std::size_t nbReadBytes)>;
|
||||
void asyncRead(std::byte* buffer, std::size_t bufferSize, ReadCallback);
|
||||
std::size_t readSome(std::byte* buffer, std::size_t bufferSize);
|
||||
|
||||
const std::string& getOutputMimeType() const { return _outputMimeType; }
|
||||
const TranscodeParameters& getParameters() const { return _parameters; }
|
||||
|
||||
bool finished() const;
|
||||
|
||||
private:
|
||||
static void init();
|
||||
|
||||
const std::size_t _id {};
|
||||
const std::filesystem::path _filePath;
|
||||
const TranscodeParameters _parameters;
|
||||
|
||||
std::unique_ptr<IChildProcess> _childProcess;
|
||||
|
||||
bool _finished {};
|
||||
std::string _outputMimeType;
|
||||
};
|
||||
|
||||
} // namespace Av
|
||||
|
||||
@@ -17,23 +17,25 @@
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "av/AvTypes.hpp"
|
||||
#include "av/Types.hpp"
|
||||
|
||||
namespace Av {
|
||||
|
||||
const char* formatToMimetype(Format format)
|
||||
namespace Av
|
||||
{
|
||||
switch (format)
|
||||
|
||||
std::string_view
|
||||
formatToMimetype(Format format)
|
||||
{
|
||||
case Format::MP3: return "audio/mpeg";
|
||||
case Format::OGG_OPUS: return "audio/opus";
|
||||
case Format::MATROSKA_OPUS: return "audio/x-matroska";
|
||||
case Format::OGG_VORBIS: return "audio/ogg";
|
||||
case Format::WEBM_VORBIS: return "audio/webm";
|
||||
switch (format)
|
||||
{
|
||||
case Format::MP3: return "audio/mpeg";
|
||||
case Format::OGG_OPUS: return "audio/opus";
|
||||
case Format::MATROSKA_OPUS: return "audio/x-matroska";
|
||||
case Format::OGG_VORBIS: return "audio/ogg";
|
||||
case Format::WEBM_VORBIS: return "audio/webm";
|
||||
}
|
||||
|
||||
throw Exception {"Invalid encoding"};
|
||||
}
|
||||
|
||||
throw AvException {"Invalid encoding"};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user