MayaFlux 0.4.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
AudioEncodeContext.cpp
Go to the documentation of this file.
3
4extern "C" {
5#include <libavcodec/avcodec.h>
6#include <libavformat/avformat.h>
7#include <libavutil/audio_fifo.h>
8#include <libavutil/channel_layout.h>
9#include <libavutil/error.h>
10#include <libavutil/opt.h>
11#include <libavutil/samplefmt.h>
12#include <libswresample/swresample.h>
13}
14
15namespace MayaFlux::IO {
16
17// =========================================================================
18// Destructor
19// =========================================================================
20
25
27{
28 if (fifo) {
29 av_audio_fifo_free(fifo);
30 fifo = nullptr;
31 }
32 if (swr_context) {
33 swr_free(&swr_context);
34 swr_context = nullptr;
35 }
36 if (codec_context) {
37 avcodec_free_context(&codec_context);
38 codec_context = nullptr;
39 }
40 stream = nullptr;
41 m_stream_index = -1;
42 m_sample_rate = 0;
43 m_channels = 0;
44 m_pts = 0;
45 m_last_error.clear();
46}
47
48// =========================================================================
49// Open
50// =========================================================================
51
53 uint32_t sample_rate,
54 uint32_t channels,
55 AVCodecID codec_id)
56{
57 close();
59
60 if (!mux.is_open()) {
61 m_last_error = "AudioEncodeContext::open called on unopened mux";
62 return false;
63 }
64
65 if (codec_id == AV_CODEC_ID_NONE)
66 codec_id = mux.format_context->oformat->audio_codec;
67
68 if (codec_id == AV_CODEC_ID_NONE) {
69 m_last_error = "container has no default audio codec";
70 return false;
71 }
72
73 const AVCodec* codec = avcodec_find_encoder(codec_id);
74 if (!codec) {
75 m_last_error = std::string("avcodec_find_encoder failed for codec ")
76 + avcodec_get_name(codec_id);
77 return false;
78 }
79
80 codec_context = avcodec_alloc_context3(codec);
81 if (!codec_context) {
82 m_last_error = "avcodec_alloc_context3 failed";
83 return false;
84 }
85
86 AVSampleFormat enc_fmt = AV_SAMPLE_FMT_FLTP;
87 {
88 const AVSampleFormat* fmts = nullptr;
89 int n_fmts = 0;
90 if (avcodec_get_supported_config(nullptr, codec, AV_CODEC_CONFIG_SAMPLE_FORMAT,
91 0, reinterpret_cast<const void**>(&fmts), &n_fmts)
92 >= 0
93 && fmts && n_fmts > 0) {
94 enc_fmt = fmts[0];
95 for (int i = 0; i < n_fmts; ++i) {
96 if (fmts[i] == AV_SAMPLE_FMT_S16) {
97 enc_fmt = AV_SAMPLE_FMT_S16;
98 break;
99 }
100 if (fmts[i] == AV_SAMPLE_FMT_FLTP) {
101 enc_fmt = AV_SAMPLE_FMT_FLTP;
102 break;
103 }
104 }
105 }
106 }
107
108 uint32_t enc_rate = sample_rate;
109 {
110 const int* rates = nullptr;
111 int n_rates = 0;
112 if (avcodec_get_supported_config(nullptr, codec, AV_CODEC_CONFIG_SAMPLE_RATE,
113 0, reinterpret_cast<const void**>(&rates), &n_rates)
114 >= 0
115 && rates && n_rates > 0) {
116 bool found = false;
117 for (int i = 0; i < n_rates; ++i) {
118 if (static_cast<uint32_t>(rates[i]) == sample_rate) {
119 found = true;
120 break;
121 }
122 }
123 if (!found)
124 enc_rate = static_cast<uint32_t>(rates[0]);
125 }
126 }
127
128 av_channel_layout_default(&codec_context->ch_layout,
129 static_cast<int>(channels));
130 codec_context->sample_rate = static_cast<int>(enc_rate);
131 codec_context->sample_fmt = enc_fmt;
132 codec_context->time_base = { .num = 1, .den = static_cast<int>(enc_rate) };
133
134 if (mux.format_context->oformat->flags & AVFMT_GLOBALHEADER)
135 codec_context->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
136
137 if (avcodec_open2(codec_context, codec, nullptr) < 0) {
138 m_last_error = "avcodec_open2 failed";
139 close();
140 return false;
141 }
142
143 stream = mux.add_stream();
144 if (!stream) {
145 m_last_error = mux.last_error();
146 close();
147 return false;
148 }
149
150 if (avcodec_parameters_from_context(stream->codecpar, codec_context) < 0) {
151 m_last_error = "avcodec_parameters_from_context failed";
152 close();
153 return false;
154 }
155
156 stream->time_base = codec_context->time_base;
157 m_stream_index = stream->index;
158 m_sample_rate = enc_rate;
159 m_channels = channels;
160
161 if (!setup_resampler() || !setup_fifo()) {
162 close();
163 return false;
164 }
165
166 return true;
167}
168
169// =========================================================================
170// Resampler: AV_SAMPLE_FMT_DBL (interleaved) → encoder native format
171// =========================================================================
172
174{
175 AVChannelLayout layout {};
176 av_channel_layout_default(&layout, static_cast<int>(m_channels));
177
178 int ret = swr_alloc_set_opts2(
180 &layout, codec_context->sample_fmt, codec_context->sample_rate,
181 &layout, AV_SAMPLE_FMT_DBL, static_cast<int>(m_sample_rate),
182 0, nullptr);
183
184 av_channel_layout_uninit(&layout);
185
186 if (ret < 0 || !swr_context) {
187 m_last_error = "swr_alloc_set_opts2 failed";
188 return false;
189 }
190
191 if (swr_init(swr_context) < 0) {
192 m_last_error = "swr_init failed";
193 swr_free(&swr_context);
194 swr_context = nullptr;
195 return false;
196 }
197
198 return true;
199}
200
201// =========================================================================
202// FIFO: sized to two encoder frames so one encode_frames() call never stalls
203// =========================================================================
204
206{
207 int frame_size = codec_context->frame_size > 0
208 ? codec_context->frame_size
209 : 4096;
210
211 fifo = av_audio_fifo_alloc(
212 codec_context->sample_fmt,
213 static_cast<int>(m_channels),
214 frame_size * 2);
215
216 if (!fifo) {
217 m_last_error = "av_audio_fifo_alloc failed";
218 return false;
219 }
220
221 return true;
222}
223
224// =========================================================================
225// Encoding
226// =========================================================================
227
228bool AudioEncodeContext::encode_frames(std::span<const double> interleaved,
229 uint32_t num_frames,
230 FFmpegMuxContext& mux)
231{
232 if (!is_valid())
233 return false;
234
235 uint8_t** conv = nullptr;
236 int linesize = 0;
237 int alloc = av_samples_alloc_array_and_samples(
238 &conv, &linesize,
239 static_cast<int>(m_channels),
240 static_cast<int>(num_frames),
241 codec_context->sample_fmt, 0);
242
243 if (alloc < 0 || !conv) {
244 m_last_error = "av_samples_alloc_array_and_samples failed during encode";
245 return false;
246 }
247
248 const auto* src = reinterpret_cast<const uint8_t*>(interleaved.data());
249 int converted = swr_convert(
251 conv, static_cast<int>(num_frames),
252 &src, static_cast<int>(num_frames));
253
254 if (converted < 0) {
255 av_freep(static_cast<void*>(&conv[0]));
256 av_freep(static_cast<void*>(&conv));
257 m_last_error = "swr_convert failed";
258 return false;
259 }
260
261 int written = av_audio_fifo_write(fifo,
262 reinterpret_cast<void**>(conv), converted);
263
264 av_freep(static_cast<void*>(&conv[0]));
265 av_freep(static_cast<void*>(&conv));
266
267 if (written < converted) {
268 m_last_error = "av_audio_fifo_write wrote fewer samples than expected";
269 return false;
270 }
271
272 int frame_size = codec_context->frame_size > 0
273 ? codec_context->frame_size
274 : av_audio_fifo_size(fifo);
275
276 while (av_audio_fifo_size(fifo) >= frame_size) {
277 if (!send_fifo_frame(mux, false))
278 return false;
279 }
280
281 return true;
282}
283
284// =========================================================================
285// Drain
286// =========================================================================
287
289{
290 if (!is_valid())
291 return false;
292
293 while (av_audio_fifo_size(fifo) > 0) {
294 if (!send_fifo_frame(mux, true))
295 return false;
296 }
297
298 if (avcodec_send_frame(codec_context, nullptr) < 0) {
299 m_last_error = "avcodec_send_frame(null) failed during drain";
300 return false;
301 }
302
303 return drain_packets(mux);
304}
305
306// =========================================================================
307// Private helpers
308// =========================================================================
309
311{
312 int frame_size = codec_context->frame_size > 0
313 ? codec_context->frame_size
314 : av_audio_fifo_size(fifo);
315
316 AVFrame* frame = av_frame_alloc();
317 if (!frame) {
318 m_last_error = "av_frame_alloc failed";
319 return false;
320 }
321
322 frame->nb_samples = frame_size;
323 frame->format = codec_context->sample_fmt;
324 frame->sample_rate = codec_context->sample_rate;
325 av_channel_layout_copy(&frame->ch_layout, &codec_context->ch_layout);
326
327 if (av_frame_get_buffer(frame, 0) < 0) {
328 m_last_error = "av_frame_get_buffer failed";
329 av_frame_free(&frame);
330 return false;
331 }
332
333 if (av_frame_make_writable(frame) < 0) {
334 m_last_error = "av_frame_make_writable failed";
335 av_frame_free(&frame);
336 return false;
337 }
338
339 int available = av_audio_fifo_size(fifo);
340 int to_read = std::min(available, frame_size);
341
342 int read = av_audio_fifo_read(fifo,
343 reinterpret_cast<void**>(frame->data), to_read);
344
345 if (read < to_read) {
346 m_last_error = "av_audio_fifo_read returned fewer samples than expected";
347 av_frame_free(&frame);
348 return false;
349 }
350
351 if (pad_to_frame_size && read < frame_size) {
352 int pad_samples = frame_size - read;
353 int bytes_per_sample = av_get_bytes_per_sample(codec_context->sample_fmt);
354 bool is_planar = av_sample_fmt_is_planar(codec_context->sample_fmt);
355 int planes = is_planar ? static_cast<int>(m_channels) : 1;
356 int samples_per_plane = is_planar ? pad_samples : pad_samples * static_cast<int>(m_channels);
357
358 for (int p = 0; p < planes; ++p) {
359 std::memset(
360 frame->data[p] + static_cast<ptrdiff_t>(read) * bytes_per_sample * (is_planar ? 1 : static_cast<int>(m_channels)),
361 0,
362 static_cast<size_t>(samples_per_plane) * static_cast<size_t>(bytes_per_sample));
363 }
364 frame->nb_samples = frame_size;
365 } else {
366 frame->nb_samples = read;
367 }
368
369 frame->pts = m_pts;
370 m_pts += frame->nb_samples;
371
372 int ret = avcodec_send_frame(codec_context, frame);
373 av_frame_free(&frame);
374
375 if (ret < 0) {
376 char errbuf[AV_ERROR_MAX_STRING_SIZE];
377 av_strerror(ret, errbuf, sizeof(errbuf));
378 m_last_error = std::string("avcodec_send_frame failed: ") + errbuf;
379 return false;
380 }
381
382 return drain_packets(mux);
383}
384
386{
387 AVPacket* pkt = av_packet_alloc();
388 if (!pkt) {
389 m_last_error = "av_packet_alloc failed";
390 return false;
391 }
392
393 bool ok = true;
394 while (true) {
395 int ret = avcodec_receive_packet(codec_context, pkt);
396 if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
397 break;
398 if (ret < 0) {
399 char errbuf[AV_ERROR_MAX_STRING_SIZE];
400 av_strerror(ret, errbuf, sizeof(errbuf));
401 m_last_error = std::string("avcodec_receive_packet failed: ") + errbuf;
402 ok = false;
403 break;
404 }
405
406 pkt->stream_index = m_stream_index;
407 av_packet_rescale_ts(pkt,
408 codec_context->time_base,
409 stream->time_base);
410
411 if (!mux.write_packet(pkt)) {
412 m_last_error = mux.last_error();
413 ok = false;
414 break;
415 }
416
417 av_packet_unref(pkt);
418 }
419
420 av_packet_free(&pkt);
421 return ok;
422}
423
424} // namespace MayaFlux::IO
bool is_valid() const
True if codec, resampler, and FIFO are all ready.
bool drain_packets(FFmpegMuxContext &mux)
Drain all packets currently available from the encoder into mux.
void close()
Release all owned resources.
bool drain(FFmpegMuxContext &mux)
Flush the FIFO remainder and encoder internal delay to the mux.
bool send_fifo_frame(FFmpegMuxContext &mux, bool pad_to_frame_size)
Pull one frame from the FIFO and send it to the encoder.
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.
AVCodecContext * codec_context
Owned; freed in destructor.
SwrContext * swr_context
Owned; freed in destructor.
AVAudioFifo * fifo
Owned; freed in destructor.
bool encode_frames(std::span< const double > interleaved, uint32_t num_frames, FFmpegMuxContext &mux)
Encode a block of interleaved double-precision PCM frames.
AVStream * stream
Owned by mux; pointer cached here.
static void init_ffmpeg()
Initialise FFmpeg logging level once per process.
bool is_open() const
True if the context is open and ready to accept streams / packets.
AVStream * add_stream()
Allocate a new AVStream inside this context.
const std::string & last_error() const
bool write_packet(AVPacket *pkt)
Submit one encoded packet for interleaved writing.
AVFormatContext * format_context
Owned; freed in close().
RAII owner of a single AVFormatContext on the write path.