Skip to content

Commit 0712481

Browse files
lrgirdwoclaude
andcommitted
audio: ffmpeg_dec: add encoder mode (PCM -> MP3 via libshine)
Add CONFIG_FFMPEG_DEC_ENCODE_MODE: build the module as an encoder using the modern .process_raw_data path in reverse of the decoder - avcodec_send_frame/avcodec_receive_packet. ffmpeg_dec-encode.c opens the libshine MP3 encoder, converts SOF interleaved S32 to the encoder sample format per frame, and emits the compressed elementary stream. The interface ladder in ffmpeg_dec.c selects encoder / filter / decoder ops by Kconfig. libshine is linked into the module (after libavcodec, which references it) from its own cross-build install dir. ffmpeg.cmake now allows an encoder-only build (no decoder) and makes the decoder configure flags conditional. The encoder path pulls a few more libc symbols, added to the local surface: calloc/posix_memalign (alloc), modf/localtime_r/iconv*/__xpg_strerror_r (shims). fastmathf is now always built for the real backend (its sofm_log2f backs the shims' log10()). Verified building and load-ready (no unresolved externals, signed) for ace30 (ptl) as an encoder-only module. Runtime encode + real-time framing need hardware. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent d81b938 commit 0712481

8 files changed

Lines changed: 300 additions & 13 deletions

File tree

src/audio/ffmpeg_dec/Kconfig

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,16 @@ config FFMPEG_FILTER_AFFTDN
7171
once the module gains an avfilter-graph backend; selecting it here just
7272
builds the filter into the archive.
7373

74+
config FFMPEG_DEC_ENCODE_MODE
75+
bool "Build as an audio encoder (PCM -> compressed) instead of a decoder [EXPERIMENTAL]"
76+
select FFMPEG_ENC_MP3
77+
help
78+
Build the module as an encoder: PCM in, compressed elementary stream out
79+
(MP3 via libshine), driven with avcodec_send_frame/avcodec_receive_packet
80+
(the reverse of the decoder). Selects the libshine MP3 encoder. Do not set
81+
together with FFMPEG_DEC_FILTER_MODE. Structurally complete and load-ready;
82+
real-time framing (MP3 fixed 1152-sample frames) needs on-hardware tuning.
83+
7484
config FFMPEG_DEC_FILTER_MODE
7585
bool "Build as a PCM filter effect instead of a decoder [EXPERIMENTAL]"
7686
select FFMPEG_FILTER_AFFTDN

src/audio/ffmpeg_dec/ffmpeg.cmake

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,16 @@ if(CONFIG_FFMPEG_DEC_MP3)
4242
list(APPEND _ff_decoders mp3)
4343
list(APPEND _ff_parsers mpegaudio)
4444
endif()
45-
if(NOT _ff_decoders)
46-
message(FATAL_ERROR "ffmpeg_dec: no decoder selected (Kconfig FFMPEG_DEC_*)")
45+
# A decoder is required unless this is an encoder-only build.
46+
if(NOT _ff_decoders AND NOT CONFIG_FFMPEG_ENC_MP3)
47+
message(FATAL_ERROR "ffmpeg_dec: no decoder or encoder selected (Kconfig FFMPEG_DEC_*/FFMPEG_ENC_*)")
48+
endif()
49+
set(_ff_dec_cfg "")
50+
if(_ff_decoders)
51+
list(JOIN _ff_decoders "," _ff_dec_csv)
52+
list(JOIN _ff_parsers "," _ff_par_csv)
53+
set(_ff_dec_cfg --enable-decoder=${_ff_dec_csv} --enable-parser=${_ff_par_csv})
4754
endif()
48-
list(JOIN _ff_decoders "," _ff_dec_csv)
49-
list(JOIN _ff_parsers "," _ff_par_csv)
5055

5156
# --- 2b. Kconfig -> libavfilter audio filters (off unless a filter is chosen) ---
5257
set(_ff_avfilter_cfg --disable-avfilter)
@@ -182,7 +187,7 @@ ExternalProject_Add(ffmpeg_ext
182187
--disable-pthreads --disable-w32threads --disable-os2threads
183188
--disable-runtime-cpudetect --disable-debug
184189
--enable-avcodec --enable-avutil --enable-swresample
185-
--enable-decoder=${_ff_dec_csv} --enable-parser=${_ff_par_csv}
190+
${_ff_dec_cfg}
186191
${_ff_shine_cfg}
187192
--enable-small --enable-pic
188193
"--extra-cflags=${_ff_extra_cflags}"

src/audio/ffmpeg_dec/ffmpeg_dec-alloc.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,27 @@ void *realloc(void *ptr, size_t size)
7777

7878
return new_ptr;
7979
}
80+
81+
void *calloc(size_t nmemb, size_t size)
82+
{
83+
size_t total = nmemb * size;
84+
void *ptr = malloc(total);
85+
86+
if (ptr)
87+
memset(ptr, 0, total);
88+
return ptr;
89+
}
90+
91+
/* av_malloc may use posix_memalign. Our allocator returns 16-byte-aligned blocks,
92+
* which is sufficient for the --disable-asm (no-SIMD) build; larger alignment
93+
* requests are satisfied at 16 bytes. */
94+
int posix_memalign(void **memptr, size_t alignment, size_t size)
95+
{
96+
void *ptr = malloc(size);
97+
98+
(void)alignment;
99+
if (!ptr)
100+
return 12; /* ENOMEM */
101+
*memptr = ptr;
102+
return 0;
103+
}
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
// SPDX-License-Identifier: BSD-3-Clause
2+
//
3+
// Copyright(c) 2026 Intel Corporation.
4+
//
5+
// libavcodec encode backend for the ffmpeg_dec module (encode mode): PCM in,
6+
// compressed elementary stream out (MP3 via libshine). It is the mirror of the
7+
// decoder path - avcodec_send_frame / avcodec_receive_packet instead of
8+
// send_packet / receive_frame.
9+
//
10+
// SOF PCM is interleaved S32; the encoder is opened in whatever sample format it
11+
// supports (libshine: S16), so S32 is converted to the encoder's format per
12+
// frame. MP3 uses fixed 1152-sample frames; this feeds one encoder frame worth
13+
// of input per process call when enough is available.
14+
15+
#include <sof/audio/module_adapter/module/generic.h>
16+
#include <sof/audio/source_api.h>
17+
#include <rtos/string.h>
18+
#include <errno.h>
19+
#include <stdio.h>
20+
#include "ffmpeg_dec.h"
21+
22+
#include <libavcodec/avcodec.h>
23+
#include <libavutil/channel_layout.h>
24+
#include <libavutil/samplefmt.h>
25+
26+
LOG_MODULE_DECLARE(ffmpeg_dec, CONFIG_SOF_LOG_LEVEL);
27+
28+
#define FFMPEG_ENC_DEFAULT_BITRATE 128000
29+
30+
struct ffmpeg_enc_data {
31+
const AVCodec *codec;
32+
AVCodecContext *avctx;
33+
AVPacket *pkt;
34+
AVFrame *frame;
35+
int frame_bytes; /* SOF S32 input frame size (all channels) */
36+
};
37+
38+
int ffmpeg_enc_mod_init(struct processing_module *mod)
39+
{
40+
struct ffmpeg_dec_comp_data *cd = module_get_private_data(mod);
41+
struct comp_dev *dev = mod->dev;
42+
struct ffmpeg_enc_data *e;
43+
44+
ffmpeg_dec_libc_bind(mod);
45+
46+
e = mod_zalloc(mod, sizeof(*e));
47+
if (!e)
48+
return -ENOMEM;
49+
50+
/* libshine is the fixed-point MP3 encoder built into the archive. */
51+
e->codec = avcodec_find_encoder_by_name("libshine");
52+
if (!e->codec) {
53+
comp_err(dev, "libshine MP3 encoder not found");
54+
mod_free(mod, e);
55+
return -ENODEV;
56+
}
57+
58+
e->avctx = avcodec_alloc_context3(e->codec);
59+
e->pkt = av_packet_alloc();
60+
e->frame = av_frame_alloc();
61+
if (!e->avctx || !e->pkt || !e->frame) {
62+
if (e->frame)
63+
av_frame_free(&e->frame);
64+
if (e->pkt)
65+
av_packet_free(&e->pkt);
66+
if (e->avctx)
67+
avcodec_free_context(&e->avctx);
68+
mod_free(mod, e);
69+
return -ENOMEM;
70+
}
71+
72+
cd->backend_data = e;
73+
return 0;
74+
}
75+
76+
int ffmpeg_enc_mod_prepare(struct processing_module *mod,
77+
struct sof_source **sources, int num_of_sources,
78+
struct sof_sink **sinks, int num_of_sinks)
79+
{
80+
struct ffmpeg_dec_comp_data *cd = module_get_private_data(mod);
81+
struct ffmpeg_enc_data *e = cd->backend_data;
82+
struct comp_dev *dev = mod->dev;
83+
int ret;
84+
85+
if (num_of_sources != 1)
86+
return -EINVAL;
87+
88+
ffmpeg_dec_libc_bind(mod);
89+
90+
cd->out_channels = source_get_channels(sources[0]);
91+
cd->out_rate = source_get_rate(sources[0]);
92+
e->frame_bytes = source_get_frame_bytes(sources[0]);
93+
94+
e->avctx->sample_rate = cd->out_rate;
95+
av_channel_layout_default(&e->avctx->ch_layout, cd->out_channels);
96+
/* Use the encoder's native sample format (libshine: S16). */
97+
e->avctx->sample_fmt = e->codec->sample_fmts ?
98+
e->codec->sample_fmts[0] : AV_SAMPLE_FMT_S16;
99+
e->avctx->bit_rate = FFMPEG_ENC_DEFAULT_BITRATE;
100+
e->avctx->thread_count = 1;
101+
102+
ret = avcodec_open2(e->avctx, e->codec, NULL);
103+
if (ret < 0) {
104+
comp_err(dev, "avcodec_open2 (encoder) failed %d", ret);
105+
return -EIO;
106+
}
107+
108+
comp_info(dev, "ffmpeg_enc: libshine MP3, rate %u ch %u frame_size %d",
109+
cd->out_rate, cd->out_channels, e->avctx->frame_size);
110+
return 0;
111+
}
112+
113+
/* Convert one interleaved S32 frame block to the encoder AVFrame, then encode. */
114+
int ffmpeg_enc_mod_process(struct processing_module *mod,
115+
struct input_stream_buffer *input_buffers, int num_input_buffers,
116+
struct output_stream_buffer *output_buffers, int num_output_buffers)
117+
{
118+
struct ffmpeg_dec_comp_data *cd = module_get_private_data(mod);
119+
struct ffmpeg_enc_data *e = cd->backend_data;
120+
struct comp_dev *dev = mod->dev;
121+
int ch = cd->out_channels;
122+
int nb = e->avctx->frame_size ? e->avctx->frame_size : 1152;
123+
const int32_t *in;
124+
uint8_t *out = output_buffers[0].data;
125+
size_t out_off = 0;
126+
int planar, i, c, ret;
127+
128+
if (num_input_buffers != 1 || num_output_buffers != 1)
129+
return -EINVAL;
130+
131+
ffmpeg_dec_libc_bind(mod);
132+
133+
/* Need a full encoder frame of input. */
134+
if (input_buffers[0].size < (uint32_t)(nb * e->frame_bytes))
135+
return -ENODATA;
136+
137+
e->frame->nb_samples = nb;
138+
e->frame->format = e->avctx->sample_fmt;
139+
e->frame->sample_rate = cd->out_rate;
140+
av_channel_layout_copy(&e->frame->ch_layout, &e->avctx->ch_layout);
141+
if (av_frame_get_buffer(e->frame, 0) < 0)
142+
return -ENOMEM;
143+
144+
/* S32 interleaved -> S16 (planar or packed per the encoder format). */
145+
in = input_buffers[0].data;
146+
planar = av_sample_fmt_is_planar(e->frame->format);
147+
for (i = 0; i < nb; i++)
148+
for (c = 0; c < ch; c++) {
149+
int16_t s = (int16_t)(in[i * ch + c] >> 16);
150+
151+
if (planar)
152+
((int16_t *)e->frame->data[c])[i] = s;
153+
else
154+
((int16_t *)e->frame->data[0])[i * ch + c] = s;
155+
}
156+
input_buffers[0].consumed = nb * e->frame_bytes;
157+
158+
ret = avcodec_send_frame(e->avctx, e->frame);
159+
av_frame_unref(e->frame);
160+
if (ret < 0) {
161+
comp_err(dev, "avcodec_send_frame failed %d", ret);
162+
return -EIO;
163+
}
164+
165+
/* Drain compressed packets into the output buffer. */
166+
while (ret >= 0) {
167+
ret = avcodec_receive_packet(e->avctx, e->pkt);
168+
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
169+
break;
170+
if (ret < 0) {
171+
comp_err(dev, "avcodec_receive_packet failed %d", ret);
172+
return -EIO;
173+
}
174+
if (out_off + e->pkt->size <= output_buffers[0].size) {
175+
memcpy_s(out + out_off, output_buffers[0].size - out_off,
176+
e->pkt->data, e->pkt->size);
177+
out_off += e->pkt->size;
178+
} else {
179+
comp_warn(dev, "output full, MP3 packet dropped");
180+
}
181+
av_packet_unref(e->pkt);
182+
ret = 0;
183+
}
184+
185+
output_buffers[0].size = out_off;
186+
return 0;
187+
}
188+
189+
int ffmpeg_enc_mod_free(struct processing_module *mod)
190+
{
191+
struct ffmpeg_dec_comp_data *cd = module_get_private_data(mod);
192+
struct ffmpeg_enc_data *e = cd->backend_data;
193+
194+
ffmpeg_dec_libc_bind(mod);
195+
if (!e)
196+
return 0;
197+
198+
if (e->frame)
199+
av_frame_free(&e->frame);
200+
if (e->pkt)
201+
av_packet_free(&e->pkt);
202+
if (e->avctx)
203+
avcodec_free_context(&e->avctx);
204+
mod_free(mod, e);
205+
cd->backend_data = NULL;
206+
return 0;
207+
}

src/audio/ffmpeg_dec/ffmpeg_dec-shims.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,15 @@ char *getenv(const char *name) { (void)name; return NULL; }
5151
unsigned long clock(void) { return 0; }
5252
void *gmtime_r(const void *timep, void *result) { (void)timep; (void)result; return NULL; }
5353
void *localtime(const void *timep) { (void)timep; return NULL; }
54+
void *localtime_r(const void *timep, void *result) { (void)timep; (void)result; return NULL; }
55+
56+
/* iconv (avutil text/metadata charset conversion) - unused on the audio path. */
57+
void *iconv_open(const char *to, const char *from) { (void)to; (void)from; return (void *)-1; }
58+
int iconv_close(void *cd) { (void)cd; return 0; }
59+
size_t iconv(void *cd, char **in, size_t *inb, char **out, size_t *outb)
60+
{ (void)cd; (void)in; (void)inb; (void)out; (void)outb; return (size_t)-1; }
61+
int __xpg_strerror_r(int errnum, char *buf, size_t buflen)
62+
{ (void)errnum; if (buflen) buf[0] = '\0'; return 0; }
5463
long mktime(void *tm) { (void)tm; return -1; }
5564
size_t strftime(char *s, size_t max, const char *fmt, const void *tm)
5665
{ (void)fmt; (void)tm; if (max) s[0] = '\0'; return 0; }
@@ -211,6 +220,8 @@ double sqrt(double x) { (void)x; return 0; }
211220
double tan(double x) { (void)x; return 0; }
212221
double tanh(double x) { (void)x; return 0; }
213222
double trunc(double x) { (void)x; return 0; }
223+
double modf(double x, double *iptr)
224+
{ long long i = (long long)x; if (iptr) *iptr = (double)i; return x - (double)i; }
214225

215226
/* Small real libm helpers pulled in by libavfilter (afftdn). fmax/fmin/rint
216227
* are exact; log10 is routed through the fast single-precision log2f (see

src/audio/ffmpeg_dec/ffmpeg_dec.c

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ SOF_DEFINE_REG_UUID(ffmpeg_dec);
2323
LOG_MODULE_REGISTER(ffmpeg_dec, CONFIG_SOF_LOG_LEVEL);
2424

2525
/* Decoder-mode ops (compressed -> PCM). In filter mode the module uses the
26-
* avfilter-graph PCM effect ops from ffmpeg_dec-filter.c instead.
26+
* avfilter-graph PCM effect ops (ffmpeg_dec-filter.c); in encode mode the
27+
* PCM->compressed ops (ffmpeg_dec-encode.c) instead.
2728
*/
28-
#if !CONFIG_FFMPEG_DEC_FILTER_MODE
29+
#if !CONFIG_FFMPEG_DEC_FILTER_MODE && !CONFIG_FFMPEG_DEC_ENCODE_MODE
2930
int ffmpeg_dec_store_extradata(struct processing_module *mod,
3031
const uint8_t *data, size_t size)
3132
{
@@ -277,10 +278,18 @@ __cold static int ffmpeg_dec_free(struct processing_module *mod)
277278
mod_free(mod, cd);
278279
return 0;
279280
}
280-
#endif /* !CONFIG_FFMPEG_DEC_FILTER_MODE */
281+
#endif /* !CONFIG_FFMPEG_DEC_FILTER_MODE && !CONFIG_FFMPEG_DEC_ENCODE_MODE */
281282

282283
/* This defines the module operations */
283-
#if CONFIG_FFMPEG_DEC_FILTER_MODE
284+
#if CONFIG_FFMPEG_DEC_ENCODE_MODE
285+
/* PCM -> compressed encoder (ffmpeg_dec-encode.c). */
286+
static const struct module_interface ffmpeg_dec_interface = {
287+
.init = ffmpeg_enc_mod_init,
288+
.prepare = ffmpeg_enc_mod_prepare,
289+
.process_raw_data = ffmpeg_enc_mod_process,
290+
.free = ffmpeg_enc_mod_free
291+
};
292+
#elif CONFIG_FFMPEG_DEC_FILTER_MODE
284293
/* PCM source/sink effect driving an avfilter graph (ffmpeg_dec-filter.c). */
285294
static const struct module_interface ffmpeg_dec_interface = {
286295
.init = ffmpeg_af_mod_init,

src/audio/ffmpeg_dec/ffmpeg_dec.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,16 @@ struct ffmpeg_dec_comp_data {
116116
void *af_graph;
117117
};
118118

119+
/* Encode-mode module ops (PCM -> compressed), in ffmpeg_dec-encode.c. */
120+
int ffmpeg_enc_mod_init(struct processing_module *mod);
121+
int ffmpeg_enc_mod_prepare(struct processing_module *mod,
122+
struct sof_source **sources, int num_of_sources,
123+
struct sof_sink **sinks, int num_of_sinks);
124+
int ffmpeg_enc_mod_process(struct processing_module *mod,
125+
struct input_stream_buffer *input_buffers, int num_input_buffers,
126+
struct output_stream_buffer *output_buffers, int num_output_buffers);
127+
int ffmpeg_enc_mod_free(struct processing_module *mod);
128+
119129
/* Filter-mode module ops (PCM source/sink effect), in ffmpeg_dec-filter.c. */
120130
int ffmpeg_af_mod_init(struct processing_module *mod);
121131
int ffmpeg_af_mod_prepare(struct processing_module *mod,

0 commit comments

Comments
 (0)