Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/audio/ffmpeg_dec/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@ if(CONFIG_COMP_FFMPEG_DEC STREQUAL "m" AND DEFINED CONFIG_LLEXT)
else()
add_local_sources(sof ffmpeg_dec.c)

if(CONFIG_FFMPEG_DEC_FLAC OR CONFIG_COMP_FFMPEG_DEC_STUB)
add_local_sources(sof ffmpeg_dec-flac.c)
endif()
if(CONFIG_FFMPEG_DEC_MP3 OR CONFIG_COMP_FFMPEG_DEC_STUB)
add_local_sources(sof ffmpeg_dec-mp3.c)
endif()
if(CONFIG_FFMPEG_DEC_AAC OR CONFIG_COMP_FFMPEG_DEC_STUB)
add_local_sources(sof ffmpeg_dec-aac.c)
endif()
if(CONFIG_FFMPEG_DEC_OPUS OR CONFIG_COMP_FFMPEG_DEC_STUB)
add_local_sources(sof ffmpeg_dec-opus.c)
endif()

if(CONFIG_COMP_FFMPEG_DEC_STUB)
add_local_sources(sof ffmpeg_dec-stub.c)
else()
Expand Down
36 changes: 36 additions & 0 deletions src/audio/ffmpeg_dec/ffmpeg_dec-aac.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: BSD-3-Clause
//
// Copyright(c) 2026 Intel Corporation.
//
// AAC per-codec container parsing and frame sizing for ffmpeg_dec.

#include <sof/audio/module_adapter/module/generic.h>
#include <ipc/compress_params.h>
#include "ffmpeg_dec.h"

static uint32_t ffmpeg_dec_aac_max_frame_samples(const struct ffmpeg_dec_comp_data *cd)
{
(void)cd;
return 2048; /* HE-AAC (SBR) */
}

static int ffmpeg_dec_aac_skip_container(struct processing_module *mod,
struct sof_source *src)
{
int ret;

do {
ret = ffmpeg_dec_skip_id3v2(mod, src);
} while (ret > 0);

return ret;
}

const struct ffmpeg_dec_codec_ops ffmpeg_dec_aac_ops = {
.id = FFMPEG_DEC_CODEC_AAC,
.snd_codec_id = SND_AUDIOCODEC_AAC,
.name = "aac",
.decoder_name = "aac_fixed",
.max_frame_samples = ffmpeg_dec_aac_max_frame_samples,
.skip_container = ffmpeg_dec_aac_skip_container,
};
9 changes: 2 additions & 7 deletions src/audio/ffmpeg_dec/ffmpeg_dec-builtin-alloc.c
Original file line number Diff line number Diff line change
Expand Up @@ -155,15 +155,10 @@ void *__wrap_realloc(void *ptr, size_t size)

#else /* !CONFIG_SOF_USERSPACE_APPLICATION */

/* Working-set heap: AAC-LC needs ~768 KiB (one ~541 KiB av_tx MDCT table alloc +
* decoder context). FLAC/MP3/Opus only need a small fraction of this, so sizing
* to 576 KiB reclaims over 300 KiB for sof_heap to accommodate the 64 KiB DP stack.
/* Working-set heap: with -DSOF_AAC_LC_ONLY=1, FLAC/MP3/AAC/Opus all fit within
* 576 KiB, reclaiming over 300 KiB for sof_heap to accommodate the 64 KiB DP stack.
*/
#if defined(CONFIG_FFMPEG_DEC_AAC)
#define FFMPEG_DEC_HEAP_BYTES (880 * 1024)
#else
#define FFMPEG_DEC_HEAP_BYTES (576 * 1024)
#endif

static uint8_t ffmpeg_dec_heap_buf[FFMPEG_DEC_HEAP_BYTES]
__aligned(FFMPEG_DEC_ALLOC_ALIGN);
Expand Down
21 changes: 5 additions & 16 deletions src/audio/ffmpeg_dec/ffmpeg_dec-ffmpeg.c
Original file line number Diff line number Diff line change
Expand Up @@ -131,11 +131,9 @@ static int ffmpeg_dec_ff_init(struct processing_module *mod)
return -ENOMEM;

id = ffmpeg_dec_av_codec_id(cd->codec);
#if defined(CONFIG_FFMPEG_DEC_AAC)
if (cd->codec == FFMPEG_DEC_CODEC_AAC)
ff->codec = avcodec_find_decoder_by_name("aac_fixed");
if (cd->codec_ops && cd->codec_ops->decoder_name)
ff->codec = avcodec_find_decoder_by_name(cd->codec_ops->decoder_name);
if (!ff->codec)
#endif
ff->codec = avcodec_find_decoder(id);
if (!ff->codec) {
comp_err(dev, "no libavcodec decoder for codec %d", cd->codec);
Expand Down Expand Up @@ -178,19 +176,10 @@ static int ffmpeg_dec_ff_configure(struct processing_module *mod)

ffmpeg_dec_libc_bind(mod);

/* Filter extradata by codec:
* - FLAC needs the 34-byte STREAMINFO.
* - Opus needs "OpusHead" (RFC 7845) or NULL (falls back to built-in stereo default).
* If the topology carries a FLAC header, ignore it so Opus uses its default.
*/
if (cd->codec == FFMPEG_DEC_CODEC_OPUS && cd->extradata && cd->extradata_size) {
if (cd->extradata_size < 8 || memcmp(cd->extradata, "OpusHead", 8) != 0) {
comp_info(dev, "ignoring non-Opus extradata from topology");
cd->extradata_size = 0;
}
}
if (cd->codec_ops && cd->codec_ops->prepare_extradata)
cd->codec_ops->prepare_extradata(mod);

if (cd->extradata && cd->extradata_size) {
if (cd->extradata_size) {
av_freep(&ff->avctx->extradata);
ff->avctx->extradata =
av_mallocz(cd->extradata_size + AV_INPUT_BUFFER_PADDING_SIZE);
Expand Down
153 changes: 153 additions & 0 deletions src/audio/ffmpeg_dec/ffmpeg_dec-flac.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// SPDX-License-Identifier: BSD-3-Clause
//
// Copyright(c) 2026 Intel Corporation.
//
// FLAC per-codec container parsing and frame sizing for ffmpeg_dec.

#include <sof/audio/module_adapter/module/generic.h>
#include <sof/audio/sink_source_utils.h>
#include <sof/audio/source_api.h>
#include <ipc/compress_params.h>
#include <rtos/string.h>
#include <errno.h>
#include "ffmpeg_dec.h"

LOG_MODULE_DECLARE(ffmpeg_dec, CONFIG_SOF_LOG_LEVEL);

static uint32_t ffmpeg_dec_flac_max_frame_samples(const struct ffmpeg_dec_comp_data *cd)
{
if (cd->extradata_size >= 4) {
const uint8_t *ed = cd->extradata;
uint32_t max_bs = ((uint32_t)ed[2] << 8) | (uint32_t)ed[3];

if (max_bs > 0)
return max_bs;
}
return 8192; /* standard max FLAC block (subset) */
}

/*
* Strip an optional leading ID3v2 tag, the 4-byte "fLaC" container marker, and
* all FLAC metadata blocks so the libavcodec parser receives raw FLAC frames.
* When the 34-byte STREAMINFO block (type 0) is encountered, dynamically store
* it as codec extradata if none was provided via set_configuration().
*/
static int ffmpeg_dec_flac_skip_container(struct processing_module *mod,
struct sof_source *src)
{
struct ffmpeg_dec_comp_data *cd = module_get_private_data(mod);
struct comp_dev *dev = mod->dev;
const void *sp, *sstart;
size_t sbytes, avail, req;
int ret;

/* Step 1: Drain any pending skip bytes from a previous FLAC metadata block */
if (cd->flac_parsing_meta && cd->skip_bytes > 0) {
avail = source_get_data_available(src);
if (!avail)
return -ENODATA;
size_t to_skip = MIN(avail, cd->skip_bytes);

source_drop_data(src, to_skip);
cd->skip_bytes -= to_skip;
if (cd->skip_bytes > 0)
return -ENODATA;

if (cd->flac_last_block) {
cd->flac_parsing_meta = false;
return 0;
}
}

/* Step 2: Parse ongoing FLAC metadata blocks across chunks */
while (cd->flac_parsing_meta) {
uint8_t hdr_bytes[4];
uint8_t hdr;
uint32_t len;
bool is_last;

avail = source_get_data_available(src);
if (avail < 4)
return -ENODATA;

ret = source_get_data(src, 4, &sp, &sstart, &sbytes);
if (ret)
return ret;
ffmpeg_dec_copy_from_circular(hdr_bytes, sp, sstart, sbytes, 4);
source_release_data(src, 0);

hdr = hdr_bytes[0];
len = ((uint32_t)hdr_bytes[1] << 16) |
((uint32_t)hdr_bytes[2] << 8) |
(uint32_t)hdr_bytes[3];
is_last = (hdr & 0x80) != 0;

/* STREAMINFO block (type 0, 34 bytes) */
if ((hdr & 0x7F) == 0 && len == 34) {
if (avail < 38)
return -ENODATA;

req = 38;
ret = source_get_data(src, req, &sp, &sstart, &sbytes);
if (ret)
return ret;
ffmpeg_dec_copy_from_circular(cd->in_buf, sp, sstart, sbytes, req);
source_release_data(src, 0);

if (!cd->extradata_size) {
ffmpeg_dec_store_extradata(mod, &cd->in_buf[4], 34);
comp_info(dev, "dynamically extracted 34-byte FLAC STREAMINFO");
}
source_drop_data(src, 38);
if (is_last) {
cd->flac_parsing_meta = false;
return 0;
}
continue;
}

/* Other metadata block (PADDING, PICTURE, VORBIS_COMMENT, etc.): skip 4 + len */
uint32_t block_total = 4 + len;

if (avail >= block_total) {
source_drop_data(src, block_total);
if (is_last) {
cd->flac_parsing_meta = false;
return 0;
}
continue;
}

/* Block exceeds available data in this period: skip available and track remainder */
source_drop_data(src, avail);
cd->skip_bytes = block_total - avail;
cd->flac_last_block = is_last;
return -ENODATA;
}

/* Step 3: Skip any leading ID3v2 tag before checking for the "fLaC" marker */
do {
ret = ffmpeg_dec_skip_id3v2(mod, src);
if (ret < 0)
return ret;
} while (ret > 0);

/* Step 4: Check for the 4-byte "fLaC" container marker */
if (memcmp(cd->in_buf, "fLaC", 4) == 0) {
comp_info(dev, "FLAC marker detected, consuming container header");
source_drop_data(src, 4);
cd->flac_parsing_meta = true;
return ffmpeg_dec_flac_skip_container(mod, src);
}

/* No container marker: raw FLAC elementary stream */
return 0;
}

const struct ffmpeg_dec_codec_ops ffmpeg_dec_flac_ops = {
.id = FFMPEG_DEC_CODEC_FLAC,
.snd_codec_id = SND_AUDIOCODEC_FLAC,
.name = "flac",
.max_frame_samples = ffmpeg_dec_flac_max_frame_samples,
.skip_container = ffmpeg_dec_flac_skip_container,
};
35 changes: 35 additions & 0 deletions src/audio/ffmpeg_dec/ffmpeg_dec-mp3.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: BSD-3-Clause
//
// Copyright(c) 2026 Intel Corporation.
//
// MP3 per-codec container parsing and frame sizing for ffmpeg_dec.

#include <sof/audio/module_adapter/module/generic.h>
#include <ipc/compress_params.h>
#include "ffmpeg_dec.h"

static uint32_t ffmpeg_dec_mp3_max_frame_samples(const struct ffmpeg_dec_comp_data *cd)
{
(void)cd;
return 1152; /* MPEG-1 Layer III */
}

static int ffmpeg_dec_mp3_skip_container(struct processing_module *mod,
struct sof_source *src)
{
int ret;

do {
ret = ffmpeg_dec_skip_id3v2(mod, src);
} while (ret > 0);

return ret;
}

const struct ffmpeg_dec_codec_ops ffmpeg_dec_mp3_ops = {
.id = FFMPEG_DEC_CODEC_MP3,
.snd_codec_id = SND_AUDIOCODEC_MP3,
.name = "mp3",
.max_frame_samples = ffmpeg_dec_mp3_max_frame_samples,
.skip_container = ffmpeg_dec_mp3_skip_container,
};
42 changes: 42 additions & 0 deletions src/audio/ffmpeg_dec/ffmpeg_dec-opus.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// SPDX-License-Identifier: BSD-3-Clause
//
// Copyright(c) 2026 Intel Corporation.
//
// Opus per-codec extradata validation and frame sizing for ffmpeg_dec.

#include <sof/audio/module_adapter/module/generic.h>
#include <ipc/compress_params.h>
#include <rtos/string.h>
#include "ffmpeg_dec.h"

LOG_MODULE_DECLARE(ffmpeg_dec, CONFIG_SOF_LOG_LEVEL);

static uint32_t ffmpeg_dec_opus_max_frame_samples(const struct ffmpeg_dec_comp_data *cd)
{
(void)cd;
return 5760; /* 120 ms @ 48 kHz */
}

static void ffmpeg_dec_opus_prepare_extradata(struct processing_module *mod)
{
struct ffmpeg_dec_comp_data *cd = module_get_private_data(mod);

/*
* Opus needs an "OpusHead" (RFC 7845) header or empty extradata
* (which falls back to the built-in stereo default). If the topology
* carries a non-Opus header, ignore it so Opus uses its default.
*/
if (cd->extradata_size &&
(cd->extradata_size < 8 || memcmp(cd->extradata, "OpusHead", 8) != 0)) {
comp_info(mod->dev, "ignoring non-Opus extradata from topology");
cd->extradata_size = 0;
}
}

const struct ffmpeg_dec_codec_ops ffmpeg_dec_opus_ops = {
.id = FFMPEG_DEC_CODEC_OPUS,
.snd_codec_id = SND_AUDIOCODEC_OPUS_RAW,
.name = "opus",
.max_frame_samples = ffmpeg_dec_opus_max_frame_samples,
.prepare_extradata = ffmpeg_dec_opus_prepare_extradata,
};
Loading
Loading