10#include <libavcodec/avcodec.h>
23 : m_queue(
std::make_unique<Memory::LockFreeQueue<
WorkItem, k_queue_capacity>>())
29 if (
m_open.load(std::memory_order_acquire) && !
m_closing.exchange(
true)) {
31 if (fut.wait_for(std::chrono::seconds(5)) == std::future_status::timeout) {
33 "SoundFileWriter destructor timed out; worker detached, file may be incomplete");
49 AVCodecID explicit_codec)
51 if (
m_open.load(std::memory_order_acquire)) {
52 set_error(
"open() called while already open");
59 m_closing.store(
false, std::memory_order_release);
62 filepath, channels, sample_rate, explicit_codec);
64 constexpr int k_spin_ms = 500;
65 constexpr int k_sleep_us = 500;
66 for (
int i = 0; i < (k_spin_ms * 1000 / k_sleep_us); ++i) {
67 if (
m_open.load(std::memory_order_acquire))
69 std::this_thread::sleep_for(std::chrono::microseconds(k_sleep_us));
82 return std::async(std::launch::deferred,
92 if (!
m_open.load(std::memory_order_acquire) || interleaved.empty())
95 uint32_t frames = num_frames > 0
100 .
samples = std::vector<double>(interleaved.begin(), interleaved.end()),
101 .num_frames = frames });
106 if (!
m_open.load(std::memory_order_acquire) || planar.empty())
109 const auto& ch0 = std::get<std::vector<double>>(planar[0]);
110 auto frames =
static_cast<uint32_t
>(ch0.size());
116 chunk.
channels.reserve(planar.size());
117 for (
const auto& v : planar)
118 chunk.
channels.push_back(std::get<std::vector<double>>(v));
120 post(std::move(chunk));
125 if (!
m_open.load(std::memory_order_acquire) || !buffer)
128 const auto& data = buffer->get_data();
137 if (!
m_open.load(std::memory_order_acquire) || !container)
140 const auto& data = container->get_data();
178 uint32_t sample_rate,
184 auto fail = [&](std::string msg) {
186 m_open.store(
false, std::memory_order_release);
190 if (!mux.
open(filepath)) {
195 if (!enc.
open(mux, sample_rate, channels, codec_id)) {
205 m_open.store(
true, std::memory_order_release);
212 std::this_thread::yield();
216 if (std::holds_alternative<FrameChunk>(*item)) {
217 auto& fc = std::get<FrameChunk>(*item);
218 if (!enc.
encode_frames(std::span<const double>(fc.samples), fc.num_frames, mux)) {
222 }
else if (std::holds_alternative<PlanarChunk>(*item)) {
223 auto& pc = std::get<PlanarChunk>(*item);
224 const auto ch =
static_cast<uint32_t
>(pc.channels.size());
225 std::vector<double> interleaved(
static_cast<size_t>(pc.num_frames) * ch);
227 for (uint32_t f = 0; f < pc.num_frames; ++f) {
228 for (uint32_t c = 0; c < ch; ++c)
229 interleaved[(
size_t)f * ch + c] = pc.channels[c][f];
237 if (ok && !enc.
drain(mux)) {
242 m_open.store(
false, std::memory_order_release);
#define MF_ERROR(comp, ctx,...)
const std::string & last_error() const
bool drain(FFmpegMuxContext &mux)
Flush the FIFO remainder and encoder internal delay to the mux.
bool open(FFmpegMuxContext &mux, uint32_t sample_rate, uint32_t channels, AVCodecID codec_id)
Open the encoder and register an audio stream in the mux context.
bool encode_frames(std::span< const double > interleaved, uint32_t num_frames, FFmpegMuxContext &mux)
Encode a block of interleaved double-precision PCM frames.
RAII owner of one audio stream's encoder, resampler, and sample FIFO.
bool open(const std::string &filepath, const std::string &explicit_format={})
Allocate an output context and open the avio layer for writing.
bool write_header()
Write the container header to the output file.
const std::string & last_error() const
void close()
Write the container trailer, flush avio, and release all resources.
RAII owner of a single AVFormatContext on the write path.
std::variant< FrameChunk, PlanarChunk, CloseCmd > WorkItem
bool post(const WorkItem &item)
std::string last_error() const
std::future< bool > close()
Post a close command to the worker.
std::promise< bool > m_close_promise
bool open(const std::string &filepath, uint32_t channels, uint32_t sample_rate, AVCodecID explicit_codec)
Open the output file and spawn the worker thread.
void write(std::span< const double > interleaved, uint32_t num_frames=0)
Post interleaved double-precision frames to the work queue.
std::unique_ptr< Memory::LockFreeQueue< WorkItem, k_queue_capacity > > m_queue
std::atomic< bool > m_closing
void set_error(std::string msg)
std::atomic< bool > m_open
std::shared_future< bool > m_close_future
void worker_loop(const std::string &filepath, uint32_t channels, uint32_t sample_rate, AVCodecID codec_id)
@ FileIO
Filesystem I/O operations.
@ IO
Networking, file handling, streaming.
std::vector< double > samples
std::vector< std::vector< double > > channels