/******************************************************************************* * Copyright 2009-2016 Jörg Müller * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ #include "SndFileWriter.h" #include "Exception.h" #include AUD_NAMESPACE_BEGIN SndFileWriter::SndFileWriter(const std::string &filename, DeviceSpecs specs, Container format, Codec codec, unsigned int bitrate) : m_position(0), m_specs(specs) { SF_INFO sfinfo{}; sfinfo.channels = specs.channels; sfinfo.samplerate = int(specs.rate); switch(format) { #ifdef __EMSCRIPTEN__ case CONTAINER_MP3: if(codec != CODEC_MP3) AUD_THROW(FileException, "The MP3 container requires the MP3 codec."); if(sfinfo.channels < 1 || sfinfo.channels > 2) AUD_THROW(FileException, "MP3 supports mono or stereo; use split channels for multichannel output."); switch(sfinfo.samplerate) { case 8000: case 11025: case 12000: case 16000: case 22050: case 24000: case 32000: case 44100: case 48000: break; default: AUD_THROW(FileException, "MP3 requires an 8, 11.025, 12, 16, 22.05, 24, 32, 44.1 or 48 kHz sample rate."); } { // CBR frame rates accepted by LAME. Reject unsupported requests rather // than letting the encoder silently round Blender's bitrate setting. const unsigned int mpeg1[] = {32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320}; const unsigned int mpeg2[] = {8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}; const bool high_rate = sfinfo.samplerate >= 32000; const unsigned int* rates = high_rate ? mpeg1 : mpeg2; const unsigned int max_rate = high_rate ? 320 : sfinfo.samplerate >= 16000 ? 160 : 64; bool supported = false; for(unsigned int i = 0; i < 14; ++i) if(bitrate == rates[i] * 1000 && rates[i] <= max_rate) supported = true; if(!supported) AUD_THROW(FileException, "Unsupported MP3 constant bitrate for the selected sample rate (32-320 kbit/s at 32-48 kHz, up to 160 at 16-24 kHz, up to 64 below 16 kHz)."); } sfinfo.format = SF_FORMAT_MPEG | SF_FORMAT_MPEG_LAYER_III; break; #endif case CONTAINER_FLAC: sfinfo.format = SF_FORMAT_FLAC; switch(specs.format) { case FORMAT_S16: sfinfo.format |= SF_FORMAT_PCM_16; break; case FORMAT_S24: sfinfo.format |= SF_FORMAT_PCM_24; break; case FORMAT_S32: sfinfo.format |= SF_FORMAT_PCM_32; break; case FORMAT_FLOAT32: sfinfo.format |= SF_FORMAT_FLOAT; break; case FORMAT_FLOAT64: sfinfo.format |= SF_FORMAT_DOUBLE; break; default: sfinfo.format = 0; break; } break; case CONTAINER_OGG: if(codec == CODEC_VORBIS) sfinfo.format = SF_FORMAT_OGG | SF_FORMAT_VORBIS; #ifdef __EMSCRIPTEN__ else if(codec == CODEC_OPUS) sfinfo.format = SF_FORMAT_OGG | SF_FORMAT_OPUS; #endif else sfinfo.format = 0; break; case CONTAINER_WAV: sfinfo.format = SF_FORMAT_WAV; switch(specs.format) { case FORMAT_U8: sfinfo.format |= SF_FORMAT_PCM_U8; break; case FORMAT_S16: sfinfo.format |= SF_FORMAT_PCM_16; break; case FORMAT_S24: sfinfo.format |= SF_FORMAT_PCM_24; break; case FORMAT_S32: sfinfo.format |= SF_FORMAT_PCM_32; break; case FORMAT_FLOAT32: sfinfo.format |= SF_FORMAT_FLOAT; break; case FORMAT_FLOAT64: sfinfo.format |= SF_FORMAT_DOUBLE; break; default: sfinfo.format = 0; break; } break; default: sfinfo.format = 0; break; } if(sfinfo.format == 0 || !sf_format_check(&sfinfo)) AUD_THROW(FileException, "This format couldn't be written with libsndfile."); m_sndfile = sf_open(filename.c_str(), SFM_WRITE, &sfinfo); if(!m_sndfile) AUD_THROW(FileException, std::string("The file couldn't be written with libsndfile: ") + sf_strerror(nullptr)); #ifdef __EMSCRIPTEN__ if(format == CONTAINER_OGG && codec == CODEC_VORBIS) { // Preserve the operator's bits/s for this output stream (each mono // stream in split-channel mode). libvorbis selects its real managed // average bitrate mode and rejects unsupported rate/channel tuples. int target_bitrate = int(bitrate); if(sf_command(m_sndfile, SFC_WEB_SET_VORBIS_BITRATE, &target_bitrate, sizeof(target_bitrate)) != SF_TRUE) { sf_close(m_sndfile); m_sndfile = nullptr; AUD_THROW(FileException, "The native Vorbis encoder cannot use the requested average bitrate for this sample rate and channel count. Adjust Bitrate, Sample Rate or Channels (split channels encodes each file as mono)."); } } if(format == CONTAINER_MP3) { // libsndfile exposes LAME bitrate through compression, with an integer // truncation in mpeg_l3_encoder_set_quality. Target the interior of that // integer interval so floating point rounding cannot subtract 1 kbit/s. const double maximum = sfinfo.samplerate >= 32000 ? 320.0 : sfinfo.samplerate >= 16000 ? 160.0 : 64.0; const double minimum = sfinfo.samplerate >= 32000 ? 32.0 : 8.0; const double kbps = bitrate / 1000; double compression = kbps == maximum ? 0.0 : (maximum - kbps - 0.25) / (maximum - minimum); int mode = SF_BITRATE_MODE_CONSTANT; // Set a valid compression first: changing mode reuses that value. if(sf_command(m_sndfile, SFC_SET_COMPRESSION_LEVEL, &compression, sizeof(compression)) != SF_TRUE || sf_command(m_sndfile, SFC_SET_BITRATE_MODE, &mode, sizeof(mode)) != SF_TRUE) { sf_close(m_sndfile); m_sndfile = nullptr; AUD_THROW(FileException, "The native MP3 encoder could not set the requested constant bitrate."); } } if(format == CONTAINER_OGG && codec == CODEC_OPUS) { // libsndfile 1.2.2 maps compression to 6..256 kbit/s per channel. // Invert that native mapping to preserve Blender's target bitrate. double per_channel = double(bitrate) / sfinfo.channels; double compression = 1.0 - (per_channel - 6000.0) / 250000.0; if(per_channel < 6000.0 || per_channel > 256000.0 || sf_command(m_sndfile, SFC_SET_COMPRESSION_LEVEL, &compression, sizeof(compression)) != SF_TRUE) { sf_close(m_sndfile); m_sndfile = nullptr; AUD_THROW(FileException, "Opus bitrate must be between 6 and 256 kbit/s per output channel."); } } #endif } SndFileWriter::~SndFileWriter() { if(m_sndfile) sf_close(m_sndfile); } int SndFileWriter::getPosition() const { return m_position; } DeviceSpecs SndFileWriter::getSpecs() const { return m_specs; } void SndFileWriter::write(unsigned int length, sample_t* buffer) { if(!m_sndfile) AUD_THROW(FileException, "Cannot write to a finished audio file."); sf_count_t written = sf_writef_float(m_sndfile, buffer, length); if(written > 0) m_position += written; if(written != length) AUD_THROW(FileException, std::string("Audio file write failed: ") + sf_strerror(m_sndfile)); } void SndFileWriter::finish() { if(!m_sndfile) return; SNDFILE* file = m_sndfile; m_sndfile = nullptr; int error = sf_close(file); if(error) AUD_THROW(FileException, std::string("Audio encoding finalization failed: ") + sf_error_number(error)); } AUD_NAMESPACE_END