/*** This file is part of snapcast Copyright (C) 2014-2020 Johannes Pohl 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, see . ***/ #include #include "common/aixlog.hpp" #include "common/snap_exception.hpp" #include "common/str_compat.hpp" #include "flac_encoder.hpp" using namespace std; namespace encoder { FlacEncoder::FlacEncoder(const std::string& codecOptions) : Encoder(codecOptions), encoder_(nullptr), pcmBufferSize_(0), encodedSamples_(0), flacChunk_(nullptr) { headerChunk_.reset(new msg::CodecHeader("flac")); pcmBuffer_ = (FLAC__int32*)malloc(pcmBufferSize_ * sizeof(FLAC__int32)); } FlacEncoder::~FlacEncoder() { if (encoder_ != nullptr) { FLAC__stream_encoder_finish(encoder_); FLAC__metadata_object_delete(metadata_[0]); FLAC__metadata_object_delete(metadata_[1]); FLAC__stream_encoder_delete(encoder_); } free(pcmBuffer_); } std::string FlacEncoder::getAvailableOptions() const { return "compression level: [0..8]"; } std::string FlacEncoder::getDefaultOptions() const { return "2"; } std::string FlacEncoder::name() const { return "flac"; } void FlacEncoder::encode(const msg::PcmChunk* chunk) { if (flacChunk_ == nullptr) flacChunk_ = make_shared(chunk->format, 0); int samples = chunk->getSampleCount(); int frames = chunk->getFrameCount(); // LOG(INFO) << "payload: " << chunk->payloadSize << "\tframes: " << frames << "\tsamples: " << samples << "\tduration: " << // chunk->duration().count() << "\n"; if (pcmBufferSize_ < samples) { pcmBufferSize_ = samples; pcmBuffer_ = (FLAC__int32*)realloc(pcmBuffer_, pcmBufferSize_ * sizeof(FLAC__int32)); } if (sampleFormat_.sampleSize() == 1) { FLAC__int8* buffer = (FLAC__int8*)chunk->payload; for (int i = 0; i < samples; i++) pcmBuffer_[i] = (FLAC__int32)(buffer[i]); } else if (sampleFormat_.sampleSize() == 2) { FLAC__int16* buffer = (FLAC__int16*)chunk->payload; for (int i = 0; i < samples; i++) pcmBuffer_[i] = (FLAC__int32)(buffer[i]); } else if (sampleFormat_.sampleSize() == 4) { FLAC__int32* buffer = (FLAC__int32*)chunk->payload; for (int i = 0; i < samples; i++) pcmBuffer_[i] = (FLAC__int32)(buffer[i]); } FLAC__stream_encoder_process_interleaved(encoder_, pcmBuffer_, frames); if (encodedSamples_ > 0) { double resMs = static_cast(encodedSamples_) / sampleFormat_.msRate(); // LOG(INFO) << "encoded: " << chunk->payloadSize << "\tframes: " << encodedSamples_ << "\tres: " << resMs << "\n"; encodedSamples_ = 0; listener_->onChunkEncoded(this, flacChunk_, resMs); flacChunk_ = make_shared(chunk->format, 0); } } FLAC__StreamEncoderWriteStatus FlacEncoder::write_callback(const FLAC__StreamEncoder* /*encoder*/, const FLAC__byte buffer[], size_t bytes, unsigned samples, unsigned current_frame) { // LOG(INFO) << "write_callback: " << bytes << ", " << samples << ", " << current_frame << "\n"; if ((current_frame == 0) && (bytes > 0) && (samples == 0)) { headerChunk_->payload = (char*)realloc(headerChunk_->payload, headerChunk_->payloadSize + bytes); memcpy(headerChunk_->payload + headerChunk_->payloadSize, buffer, bytes); headerChunk_->payloadSize += bytes; } else { flacChunk_->payload = (char*)realloc(flacChunk_->payload, flacChunk_->payloadSize + bytes); memcpy(flacChunk_->payload + flacChunk_->payloadSize, buffer, bytes); flacChunk_->payloadSize += bytes; encodedSamples_ += samples; } return FLAC__STREAM_ENCODER_WRITE_STATUS_OK; } namespace callback { FLAC__StreamEncoderWriteStatus write_callback(const FLAC__StreamEncoder* encoder, const FLAC__byte buffer[], size_t bytes, unsigned samples, unsigned current_frame, void* client_data) { FlacEncoder* flacEncoder = (FlacEncoder*)client_data; return flacEncoder->write_callback(encoder, buffer, bytes, samples, current_frame); } } // namespace callback void FlacEncoder::initEncoder() { int quality(2); try { quality = cpt::stoi(codecOptions_); } catch (...) { throw SnapException("Invalid codec option: \"" + codecOptions_ + "\""); } if ((quality < 0) || (quality > 8)) { throw SnapException("compression level has to be between 0 and 8"); } FLAC__bool ok = true; FLAC__StreamEncoderInitStatus init_status; FLAC__StreamMetadata_VorbisComment_Entry entry; // allocate the encoder if ((encoder_ = FLAC__stream_encoder_new()) == nullptr) throw SnapException("error allocating encoder"); ok &= FLAC__stream_encoder_set_verify(encoder_, true); // compression levels (0-8): // https://xiph.org/flac/api/group__flac__stream__encoder.html#gae49cf32f5256cb47eecd33779493ac85 // latency: // 0-2: 1152 frames, ~26.1224ms // 3-8: 4096 frames, ~92.8798ms ok &= FLAC__stream_encoder_set_compression_level(encoder_, quality); ok &= FLAC__stream_encoder_set_channels(encoder_, sampleFormat_.channels()); ok &= FLAC__stream_encoder_set_bits_per_sample(encoder_, sampleFormat_.bits()); ok &= FLAC__stream_encoder_set_sample_rate(encoder_, sampleFormat_.rate()); if (!ok) throw SnapException("error setting up encoder"); // now add some metadata; we'll add some tags and a padding block if ((metadata_[0] = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT)) == nullptr || (metadata_[1] = FLAC__metadata_object_new(FLAC__METADATA_TYPE_PADDING)) == nullptr || // there are many tag (vorbiscomment) functions but these are convenient for this particular use: !FLAC__metadata_object_vorbiscomment_entry_from_name_value_pair(&entry, "TITLE", "SnapStream") || !FLAC__metadata_object_vorbiscomment_append_comment(metadata_[0], entry, false) || !FLAC__metadata_object_vorbiscomment_entry_from_name_value_pair(&entry, "VERSION", VERSION) || !FLAC__metadata_object_vorbiscomment_append_comment(metadata_[0], entry, false)) throw SnapException("out of memory or tag error"); metadata_[1]->length = 1234; // set the padding length ok = FLAC__stream_encoder_set_metadata(encoder_, metadata_, 2); if (!ok) throw SnapException("error setting meta data"); // initialize encoder init_status = FLAC__stream_encoder_init_stream(encoder_, callback::write_callback, nullptr, nullptr, nullptr, this); if (init_status != FLAC__STREAM_ENCODER_INIT_STATUS_OK) throw SnapException("ERROR: initializing encoder: " + string(FLAC__StreamEncoderInitStatusString[init_status])); } } // namespace encoder