Files
workinf_Blender_Wasm/blender-5.2.0/extern/audaspace/plugins/libsndfile/SndFileWriter.cpp
mes123456 f3e21c6b98
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled
chore: checkpoint web Blender migration source, tools and docs
Accumulated uncommitted work: GHOST Web backend, WebGL/GPU web layer,
web workflow engine and protocol, tooling, docs and task records.
Large generated evidence, user-testing archives, build snapshots and
imported test assets remain untracked.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-11-10 11:18:01 +08:00

234 lines
7.4 KiB
C++

/*******************************************************************************
* 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 <cstring>
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