220 lines
7.4 KiB
C++
220 lines
7.4 KiB
C++
|
/*
|
||
|
* StreamEncoder.cpp
|
||
|
* -----------------
|
||
|
* Purpose: Exporting streamed music files.
|
||
|
* Notes : none
|
||
|
* Authors: Joern Heusipp
|
||
|
* OpenMPT Devs
|
||
|
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
|
||
|
*/
|
||
|
|
||
|
#include "stdafx.h"
|
||
|
|
||
|
#include "StreamEncoder.h"
|
||
|
#include "StreamEncoderFLAC.h"
|
||
|
|
||
|
#include "Mptrack.h"
|
||
|
#include "TrackerSettings.h"
|
||
|
|
||
|
#include <FLAC/metadata.h>
|
||
|
#include <FLAC/format.h>
|
||
|
#include <FLAC/stream_encoder.h>
|
||
|
|
||
|
|
||
|
OPENMPT_NAMESPACE_BEGIN
|
||
|
|
||
|
|
||
|
class FLACStreamWriter : public StreamWriterBase
|
||
|
{
|
||
|
private:
|
||
|
const FLACEncoder &enc;
|
||
|
Encoder::Settings settings;
|
||
|
FLAC__StreamMetadata *flac_metadata[1];
|
||
|
FLAC__StreamEncoder *encoder;
|
||
|
std::vector<FLAC__int32> sampleBuf;
|
||
|
private:
|
||
|
static FLAC__StreamEncoderWriteStatus FLACWriteCallback(const FLAC__StreamEncoder *flacenc, const FLAC__byte buffer[], size_t bytes, unsigned samples, unsigned current_frame, void *client_data)
|
||
|
{
|
||
|
return reinterpret_cast<FLACStreamWriter*>(client_data)->WriteCallback(flacenc, buffer, bytes, samples, current_frame);
|
||
|
}
|
||
|
static FLAC__StreamEncoderSeekStatus FLACSeekCallback(const FLAC__StreamEncoder *flacenc, FLAC__uint64 absolute_byte_offset, void *client_data)
|
||
|
{
|
||
|
return reinterpret_cast<FLACStreamWriter*>(client_data)->SeekCallback(flacenc, absolute_byte_offset);
|
||
|
}
|
||
|
static FLAC__StreamEncoderTellStatus FLACTellCallback(const FLAC__StreamEncoder *flacenc, FLAC__uint64 *absolute_byte_offset, void *client_data)
|
||
|
{
|
||
|
return reinterpret_cast<FLACStreamWriter*>(client_data)->TellCallback(flacenc, absolute_byte_offset);
|
||
|
}
|
||
|
FLAC__StreamEncoderWriteStatus WriteCallback(const FLAC__StreamEncoder *flacenc, const FLAC__byte buffer[], size_t bytes, unsigned samples, unsigned current_frame)
|
||
|
{
|
||
|
MPT_UNREFERENCED_PARAMETER(flacenc);
|
||
|
MPT_UNREFERENCED_PARAMETER(samples);
|
||
|
MPT_UNREFERENCED_PARAMETER(current_frame);
|
||
|
f.write(reinterpret_cast<const char*>(buffer), bytes);
|
||
|
if(!f) return FLAC__STREAM_ENCODER_WRITE_STATUS_FATAL_ERROR;
|
||
|
return FLAC__STREAM_ENCODER_WRITE_STATUS_OK;
|
||
|
}
|
||
|
FLAC__StreamEncoderSeekStatus SeekCallback(const FLAC__StreamEncoder *flacenc, FLAC__uint64 absolute_byte_offset)
|
||
|
{
|
||
|
MPT_UNREFERENCED_PARAMETER(flacenc);
|
||
|
f.seekp(absolute_byte_offset);
|
||
|
if(!f) return FLAC__STREAM_ENCODER_SEEK_STATUS_ERROR;
|
||
|
return FLAC__STREAM_ENCODER_SEEK_STATUS_OK;
|
||
|
}
|
||
|
FLAC__StreamEncoderTellStatus TellCallback(const FLAC__StreamEncoder *flacenc, FLAC__uint64 *absolute_byte_offset)
|
||
|
{
|
||
|
MPT_UNREFERENCED_PARAMETER(flacenc);
|
||
|
if(absolute_byte_offset)
|
||
|
{
|
||
|
*absolute_byte_offset = f.tellp();
|
||
|
}
|
||
|
if(!f) return FLAC__STREAM_ENCODER_TELL_STATUS_ERROR;
|
||
|
return FLAC__STREAM_ENCODER_TELL_STATUS_OK;
|
||
|
}
|
||
|
private:
|
||
|
void AddCommentField(const std::string &field, const mpt::ustring &data)
|
||
|
{
|
||
|
if(!field.empty() && !data.empty())
|
||
|
{
|
||
|
FLAC__StreamMetadata_VorbisComment_Entry entry;
|
||
|
FLAC__metadata_object_vorbiscomment_entry_from_name_value_pair(&entry, field.c_str(), mpt::ToCharset(mpt::Charset::UTF8, data).c_str());
|
||
|
FLAC__metadata_object_vorbiscomment_append_comment(flac_metadata[0], entry, false);
|
||
|
}
|
||
|
}
|
||
|
public:
|
||
|
FLACStreamWriter(const FLACEncoder &enc_, std::ostream &stream, const Encoder::Settings &settings_, const FileTags &tags)
|
||
|
: StreamWriterBase(stream)
|
||
|
, enc(enc_)
|
||
|
, settings(settings_)
|
||
|
{
|
||
|
flac_metadata[0] = nullptr;
|
||
|
encoder = nullptr;
|
||
|
|
||
|
MPT_ASSERT(settings.Format.GetSampleFormat().IsValid());
|
||
|
MPT_ASSERT(settings.Samplerate > 0);
|
||
|
MPT_ASSERT(settings.Channels > 0);
|
||
|
|
||
|
encoder = FLAC__stream_encoder_new();
|
||
|
|
||
|
FLAC__stream_encoder_set_channels(encoder, settings.Channels);
|
||
|
FLAC__stream_encoder_set_bits_per_sample(encoder, settings.Format.GetSampleFormat().GetBitsPerSample());
|
||
|
FLAC__stream_encoder_set_sample_rate(encoder, settings.Samplerate);
|
||
|
|
||
|
int compressionLevel = settings.Details.FLACCompressionLevel;
|
||
|
FLAC__stream_encoder_set_compression_level(encoder, compressionLevel);
|
||
|
|
||
|
if(settings.Tags)
|
||
|
{
|
||
|
flac_metadata[0] = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT);
|
||
|
AddCommentField("ENCODER", tags.encoder);
|
||
|
AddCommentField("SOURCEMEDIA", U_("tracked music file"));
|
||
|
AddCommentField("TITLE", tags.title );
|
||
|
AddCommentField("ARTIST", tags.artist );
|
||
|
AddCommentField("ALBUM", tags.album );
|
||
|
AddCommentField("DATE", tags.year );
|
||
|
AddCommentField("COMMENT", tags.comments );
|
||
|
AddCommentField("GENRE", tags.genre );
|
||
|
AddCommentField("CONTACT", tags.url );
|
||
|
AddCommentField("BPM", tags.bpm ); // non-standard
|
||
|
AddCommentField("TRACKNUMBER", tags.trackno );
|
||
|
FLAC__stream_encoder_set_metadata(encoder, flac_metadata, 1);
|
||
|
}
|
||
|
|
||
|
FLAC__stream_encoder_init_stream(encoder, FLACWriteCallback, FLACSeekCallback, FLACTellCallback, nullptr, this);
|
||
|
|
||
|
}
|
||
|
SampleFormat GetSampleFormat() const
|
||
|
{
|
||
|
return settings.Format.GetSampleFormat();
|
||
|
}
|
||
|
template <typename Tsample>
|
||
|
void WriteInterleavedInt(std::size_t frameCount, const Tsample *p)
|
||
|
{
|
||
|
MPT_ASSERT(settings.Format.GetSampleFormat() == SampleFormatTraits<Tsample>::sampleFormat());
|
||
|
sampleBuf.resize(frameCount * settings.Channels);
|
||
|
for(std::size_t frame = 0; frame < frameCount; ++frame)
|
||
|
{
|
||
|
for(int channel = 0; channel < settings.Channels; ++channel)
|
||
|
{
|
||
|
sampleBuf[frame * settings.Channels + channel] = *p;
|
||
|
p++;
|
||
|
}
|
||
|
}
|
||
|
while(frameCount > 0)
|
||
|
{
|
||
|
unsigned int frameCountChunk = mpt::saturate_cast<unsigned int>(frameCount);
|
||
|
FLAC__stream_encoder_process_interleaved(encoder, sampleBuf.data(), frameCountChunk);
|
||
|
frameCount -= frameCountChunk;
|
||
|
}
|
||
|
}
|
||
|
void WriteInterleaved(std::size_t frameCount, const int8 *interleaved) override
|
||
|
{
|
||
|
WriteInterleavedInt(frameCount, interleaved);
|
||
|
}
|
||
|
void WriteInterleaved(std::size_t frameCount, const int16 *interleaved) override
|
||
|
{
|
||
|
WriteInterleavedInt(frameCount, interleaved);
|
||
|
}
|
||
|
void WriteInterleaved(std::size_t frameCount, const int24 *interleaved) override
|
||
|
{
|
||
|
WriteInterleavedInt(frameCount, interleaved);
|
||
|
}
|
||
|
void WriteFinalize() override
|
||
|
{
|
||
|
FLAC__stream_encoder_finish(encoder);
|
||
|
}
|
||
|
virtual ~FLACStreamWriter()
|
||
|
{
|
||
|
FLAC__stream_encoder_delete(encoder);
|
||
|
encoder = nullptr;
|
||
|
|
||
|
if(flac_metadata[0])
|
||
|
{
|
||
|
FLAC__metadata_object_delete(flac_metadata[0]);
|
||
|
flac_metadata[0] = nullptr;
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
|
||
|
|
||
|
FLACEncoder::FLACEncoder()
|
||
|
{
|
||
|
Encoder::Traits traits;
|
||
|
traits.fileExtension = P_("flac");
|
||
|
traits.fileShortDescription = U_("FLAC");
|
||
|
traits.fileDescription = U_("Free Lossless Audio Codec");
|
||
|
traits.encoderSettingsName = U_("FLAC");
|
||
|
traits.canTags = true;
|
||
|
traits.maxChannels = 4;
|
||
|
traits.samplerates = TrackerSettings::Instance().GetSampleRates();
|
||
|
traits.modes = Encoder::ModeLossless;
|
||
|
traits.formats.push_back({ Encoder::Format::Encoding::Integer, 24, mpt::get_endian() });
|
||
|
traits.formats.push_back({ Encoder::Format::Encoding::Integer, 16, mpt::get_endian() });
|
||
|
traits.formats.push_back({ Encoder::Format::Encoding::Integer, 8, mpt::get_endian() });
|
||
|
traits.defaultSamplerate = 48000;
|
||
|
traits.defaultChannels = 2;
|
||
|
traits.defaultMode = Encoder::ModeLossless;
|
||
|
traits.defaultFormat = { Encoder::Format::Encoding::Integer, 24, mpt::get_endian() };
|
||
|
SetTraits(traits);
|
||
|
}
|
||
|
|
||
|
|
||
|
bool FLACEncoder::IsAvailable() const
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
std::unique_ptr<IAudioStreamEncoder> FLACEncoder::ConstructStreamEncoder(std::ostream &file, const Encoder::Settings &settings, const FileTags &tags) const
|
||
|
{
|
||
|
if(!IsAvailable())
|
||
|
{
|
||
|
return nullptr;
|
||
|
}
|
||
|
return std::make_unique<FLACStreamWriter>(*this, file, settings, tags);
|
||
|
}
|
||
|
|
||
|
|
||
|
OPENMPT_NAMESPACE_END
|