PR #25414 opened by Simon Capriotti (capriots)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25414
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25414.patch

Adds a decoder and parser for ATRAC3+ streams that include an "ATS" header 
(Sony calls it that in official documentation, but I couldn't find any 
reference to what it actually means).

The ATS header is at the start of each regular ATRAC3+ frame.
It includes a sync word, then sample rate, channel config, and block_align 
parameters then downmix levels.
Downmixing isn't implemented in this PR.

ATRAC3+ ATS streams are used in PSMF movies included in PSP games and 
UMD-Videos, and PAMF movies included in PS3 games.
Additionally, the official encoder tool outputs Wave files with the extension 
.atx and GUID 0d5c1350-1ba2-ec4b-91ed-6eddb83c8c66.

The attached samples are the FATE samples for regular ATRAC3+ modified to 
include the ATS header. Please let me know if you want other specific 
samples/tests.

<!--
If this PR requires new FATE test samples, attach them to the PR and
list their target paths below (relative to the fate-suite root).

Attached filenames must match the sample's filename:

```fate-samples
# e.g. vorbis/new-sample.ogg
```
-->
```fate-samples
atrac3p-ats/at3p_sample1.atx
atrac3p-ats/at3p_sample1.pcm
atrac3p-ats/sonateno14op27-2-cut.atx
atrac3p-ats/sonateno14op27-2-cut.pcm
```


>From f832cb7d5cb1200b32fc06dd5ed1a09135e1aac1 Mon Sep 17 00:00:00 2001
From: Simon Capriotti <[email protected]>
Date: Sat, 17 Oct 2026 14:29:38 +0200
Subject: [PATCH 1/7] avcodec: add ATRAC3+ ATS codec ID and descriptor

Signed-off-by: Simon Capriotti <[email protected]>
---
 libavcodec/codec_desc.c | 7 +++++++
 libavcodec/codec_id.h   | 1 +
 2 files changed, 8 insertions(+)

diff --git a/libavcodec/codec_desc.c b/libavcodec/codec_desc.c
index 70d8df71f0b..80d55efbfbb 100644
--- a/libavcodec/codec_desc.c
+++ b/libavcodec/codec_desc.c
@@ -3603,6 +3603,13 @@ static const AVCodecDescriptor codec_descriptors[] = {
         .long_name = NULL_IF_CONFIG_SMALL("Apple Positional Audio Codec"),
         .props     = AV_CODEC_PROP_LOSSY,
     },
+    {
+        .id        = AV_CODEC_ID_ATRAC3P_ATS,
+        .type      = AVMEDIA_TYPE_AUDIO,
+        .name      = "atrac3plus_ats",
+        .long_name = NULL_IF_CONFIG_SMALL("ATRAC3+ ATS (Adaptive TRansform 
Acoustic Coding 3+ ATS Syntax)"),
+        .props     = AV_CODEC_PROP_INTRA_ONLY | AV_CODEC_PROP_LOSSY,
+    },
 
     /* subtitle codecs */
     {
diff --git a/libavcodec/codec_id.h b/libavcodec/codec_id.h
index a4e8e30dfd9..79b203337fd 100644
--- a/libavcodec/codec_id.h
+++ b/libavcodec/codec_id.h
@@ -565,6 +565,7 @@ enum AVCodecID {
     AV_CODEC_ID_G728,
     AV_CODEC_ID_AHX,
     AV_CODEC_ID_APPLE_APAC,
+    AV_CODEC_ID_ATRAC3P_ATS,
 
     /* subtitle codecs */
     AV_CODEC_ID_FIRST_SUBTITLE = 0x17000,          ///< A dummy ID pointing at 
the start of subtitle codecs.
-- 
2.54.0


>From 6a402e33579c8c9f8bf1488e4b068c92cb67c011 Mon Sep 17 00:00:00 2001
From: Simon Capriotti <[email protected]>
Date: Sat, 17 Oct 2026 14:30:19 +0200
Subject: [PATCH 2/7] avcodec: add an ATRAC3+ ATS parser

Signed-off-by: Simon Capriotti <[email protected]>
---
 libavcodec/Makefile                |   1 +
 libavcodec/atrac3plus.h            |   4 +
 libavcodec/atrac3plus_ats_parser.c | 157 +++++++++++++++++++++++++++++
 libavcodec/atrac3plus_ats_parser.h |  54 ++++++++++
 libavcodec/parsers.c               |   1 +
 5 files changed, 217 insertions(+)
 create mode 100644 libavcodec/atrac3plus_ats_parser.c
 create mode 100644 libavcodec/atrac3plus_ats_parser.h

diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index 2ea4f9417e6..8925c3f061c 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -1261,6 +1261,7 @@ OBJS-$(CONFIG_ADX_PARSER)              += adx_parser.o
 OBJS-$(CONFIG_AHX_PARSER)              += ahx_parser.o
 OBJS-$(CONFIG_AMR_PARSER)              += amr_parser.o
 OBJS-$(CONFIG_APV_PARSER)              += apv_parser.o
+OBJS-$(CONFIG_ATRAC3P_ATS_PARSER)      += atrac3plus_ats_parser.o
 OBJS-$(CONFIG_AV1_PARSER)              += av1_parser.o av1_parse.o
 OBJS-$(CONFIG_AVS2_PARSER)             += avs2.o avs2_parser.o
 OBJS-$(CONFIG_AVS3_PARSER)             += avs3_parser.o
diff --git a/libavcodec/atrac3plus.h b/libavcodec/atrac3plus.h
index aa949a7e4ca..adbbce3bd87 100644
--- a/libavcodec/atrac3plus.h
+++ b/libavcodec/atrac3plus.h
@@ -48,6 +48,10 @@
 /** Global constants */
 #define ATRAC3P_POWER_COMP_OFF  15  ///< disable power compensation
 
+/** ATS header constants */
+#define ATRAC3P_ATS_SYNC_WORD    0x0fd0
+#define ATRAC3P_ATS_HEADER_SIZE  8
+
 /** ATRAC3+ channel unit types */
 enum Atrac3pChannelUnitTypes {
     CH_UNIT_MONO       = 0, ///< unit containing one coded channel
diff --git a/libavcodec/atrac3plus_ats_parser.c 
b/libavcodec/atrac3plus_ats_parser.c
new file mode 100644
index 00000000000..25f8c35f1e7
--- /dev/null
+++ b/libavcodec/atrac3plus_ats_parser.c
@@ -0,0 +1,157 @@
+/*
+ * ATRAC3+ ATS parser
+ * Copyright (c) 2026 Simon Capriotti
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "atrac3plus.h"
+#include "atrac3plus_ats_parser.h"
+#include "codec_id.h"
+#include "get_bits.h"
+#include "parser.h"
+#include "parser_internal.h"
+
+int ff_atrac3p_ats_parse_header(const uint8_t *buf, int buf_size, 
AVCodecContext *avctx,
+                                Atrac3pAtsDownmixLevels *downmix_levels)
+{
+    static const uint16_t ch_layouts_tab[7] = {
+        AV_CH_LAYOUT_MONO,
+        AV_CH_LAYOUT_STEREO,
+        AV_CH_LAYOUT_SURROUND,
+        AV_CH_LAYOUT_4POINT0,
+        AV_CH_LAYOUT_5POINT1_BACK,
+        AV_CH_LAYOUT_6POINT1_BACK,
+        AV_CH_LAYOUT_7POINT1
+    };
+    static const uint16_t sample_rate_tab[3] = { 32000, 44100, 48000 };
+
+    GetBitContext gb;
+
+    if (buf_size < ATRAC3P_ATS_HEADER_SIZE) {
+        av_log(avctx, AV_LOG_ERROR, "buf_size is too small\n");
+        return AVERROR_INVALIDDATA;
+    }
+
+    const int ret = init_get_bits8(&gb, buf, buf_size);
+    av_assert0(ret == 0);
+
+    const uint16_t sync_word = get_bits(&gb, 16);
+    if (sync_word != ATRAC3P_ATS_SYNC_WORD) {
+        av_log(avctx, AV_LOG_ERROR, "Invalid sync word in the ATS header 
(%04X)\n",
+               sync_word);
+        return AVERROR_INVALIDDATA;
+    }
+
+    const uint8_t sample_rate_idc = get_bits(&gb, 3);
+    if (sample_rate_idc >= FF_ARRAY_ELEMS(sample_rate_tab)) {
+        av_log(avctx, AV_LOG_ERROR, "Invalid sample rate indicator in the ATS 
header (%d)\n",
+               sample_rate_idc);
+        return AVERROR_INVALIDDATA;
+    }
+
+    const uint16_t sample_rate = sample_rate_tab[sample_rate_idc];
+
+    const uint8_t channel_config_idc = get_bits(&gb, 3);
+    if (channel_config_idc - 1 >= FF_ARRAY_ELEMS(ch_layouts_tab)) {
+        av_log(avctx, AV_LOG_ERROR, "Invalid channel configuration indicator 
in the ATS header (%d)\n",
+               channel_config_idc);
+        return AVERROR_INVALIDDATA;
+    }
+
+    const uint16_t ch_layout_mask = ch_layouts_tab[channel_config_idc - 1];
+
+    const uint16_t block_align = (get_bits(&gb, 10) + 1) * 8 + 
ATRAC3P_ATS_HEADER_SIZE;
+
+    if (downmix_levels) {
+        downmix_levels->front_downmix_level = get_bits(&gb, 3);
+        downmix_levels->center_downmix_level = get_bits(&gb, 3);
+        downmix_levels->back_downmix_level = get_bits(&gb, 3);
+        downmix_levels->lfe_downmix_level = get_bits(&gb, 3);
+        downmix_levels->side_downmix_level = get_bits(&gb, 3);
+    }
+
+    avctx->sample_rate = sample_rate;
+    av_channel_layout_uninit(&avctx->ch_layout);
+    av_channel_layout_from_mask(&avctx->ch_layout, ch_layout_mask);
+    avctx->block_align = block_align;
+    avctx->frame_size = ATRAC3P_FRAME_SAMPLES;
+    avctx->bit_rate = block_align * CHAR_BIT * sample_rate / 
ATRAC3P_FRAME_SAMPLES;
+    return 0;
+}
+
+typedef struct Atrac3pAtsParseContext {
+    ParseContext pc;
+    int remaining_size;
+} Atrac3pAtsParseContext;
+
+static int parse(AVCodecParserContext *s1, AVCodecContext *avctx,
+                 const uint8_t **poutbuf, int *poutbuf_size,
+                 const uint8_t *buf, int buf_size)
+{
+    Atrac3pAtsParseContext *const s = s1->priv_data;
+    int next = END_NOT_FOUND;
+
+    s1->duration  = ATRAC3P_FRAME_SAMPLES;
+
+    if (s1->flags & PARSER_FLAG_COMPLETE_FRAMES) {
+        (void)ff_atrac3p_ats_parse_header(buf, buf_size, avctx, NULL);
+        next = buf_size;
+    } else {
+        if (!s->pc.frame_start_found)
+            for (int i = 0; i < buf_size; i++) {
+                s->pc.state64 = (s->pc.state64 << 8) | buf[i];
+                if (s->pc.state64 >> 48 == ATRAC3P_ATS_SYNC_WORD) {
+                    uint8_t tmp_buf[ATRAC3P_ATS_HEADER_SIZE + 
AV_INPUT_BUFFER_PADDING_SIZE];
+                    AV_WB64(tmp_buf, s->pc.state64);
+                    if (ff_atrac3p_ats_parse_header(tmp_buf, 
ATRAC3P_ATS_HEADER_SIZE, avctx, NULL) < 0)
+                        continue;
+
+                    s->pc.frame_start_found = 1;
+                    s->remaining_size = avctx->block_align + i - 7;
+                    break;
+                }
+            }
+
+        if (s->pc.frame_start_found)
+            if (s->remaining_size <= buf_size) {
+                next = s->remaining_size;
+                s->remaining_size = 0;
+                s->pc.frame_start_found = 0;
+                s->pc.state64 = 0;
+            } else {
+                s->remaining_size -= buf_size;
+            }
+
+        if (ff_combine_frame(&s->pc, next, &buf, &buf_size) < 0) {
+            *poutbuf      = NULL;
+            *poutbuf_size = 0;
+            return buf_size;
+        }
+    }
+
+    *poutbuf      = buf;
+    *poutbuf_size = buf_size;
+    return next;
+}
+
+const FFCodecParser ff_atrac3p_ats_parser = {
+    PARSER_CODEC_LIST(AV_CODEC_ID_ATRAC3P_ATS),
+    .priv_data_size = sizeof(Atrac3pAtsParseContext),
+    .parse          = parse,
+    .close          = ff_parse_close,
+};
diff --git a/libavcodec/atrac3plus_ats_parser.h 
b/libavcodec/atrac3plus_ats_parser.h
new file mode 100644
index 00000000000..70b51a2214a
--- /dev/null
+++ b/libavcodec/atrac3plus_ats_parser.h
@@ -0,0 +1,54 @@
+/*
+ * ATRAC3plus ATS parser
+ * Copyright (c) 2026 Simon Capriotti
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef AVCODEC_ATRAC3PLUS_ATS_PARSER_H
+#define AVCODEC_ATRAC3PLUS_ATS_PARSER_H
+
+#include <stdint.h>
+
+/* TODO downmix levels from 0 to 7 are the following:
+ -0dB, -1.5dB, -3dB, -4.5dB, -6dB, -7.5dB, -9dB, -infinity dB */
+typedef struct Atrac3pAtsDownmixLevels {
+    uint8_t front_downmix_level : 3;
+    uint8_t center_downmix_level : 3;
+    uint8_t back_downmix_level : 3;
+    uint8_t lfe_downmix_level : 3;
+    uint8_t side_downmix_level : 3;
+} Atrac3pAtsDownmixLevels;
+
+/**
+ * Parses the ATS header and updates the codec context with the values from the
+ * header.
+ *
+ * Optionally retrieves downmix levels from the header.
+ *
+ * @param buf            Input buffer containing the header
+ * @param buf_size       Input buffer size
+ * @param avctx          Codec context to update
+ * @param downmix_levels Will contain the downmix levels after parsing.
+ *                       Can be NULL
+ * @retval 0                   Success
+ * @retval AVERROR_INVALIDDATA The header contained an invalid value
+ */
+int ff_atrac3p_ats_parse_header(const uint8_t *buf, int buf_size, 
AVCodecContext *avctx,
+                                Atrac3pAtsDownmixLevels *downmix_levels);
+
+#endif /* AVCODEC_ATRAC3PLUS_ATS_PARSER_H */
diff --git a/libavcodec/parsers.c b/libavcodec/parsers.c
index 263b606407e..8b107773fce 100644
--- a/libavcodec/parsers.c
+++ b/libavcodec/parsers.c
@@ -28,6 +28,7 @@ extern const FFCodecParser ff_adx_parser;
 extern const FFCodecParser ff_ahx_parser;
 extern const FFCodecParser ff_amr_parser;
 extern const FFCodecParser ff_apv_parser;
+extern const FFCodecParser ff_atrac3p_ats_parser;
 extern const FFCodecParser ff_av1_parser;
 extern const FFCodecParser ff_avs2_parser;
 extern const FFCodecParser ff_avs3_parser;
-- 
2.54.0


>From a1a89dbab130dde7b17dcaa773da58b842203b02 Mon Sep 17 00:00:00 2001
From: Simon Capriotti <[email protected]>
Date: Sat, 17 Oct 2026 14:30:27 +0200
Subject: [PATCH 3/7] avcodec/atrac3plusdec: split initialization of channel
 units into separate function

For ATRAC3+ streams with an ATS header, the channel units need to be 
initialized on decoding the first frame, since the number of channels isn't 
known until then.

Signed-off-by: Simon Capriotti <[email protected]>
---
 libavcodec/atrac3plusdec.c | 67 +++++++++++++++++++++++---------------
 1 file changed, 41 insertions(+), 26 deletions(-)

diff --git a/libavcodec/atrac3plusdec.c b/libavcodec/atrac3plusdec.c
index 9696a523be6..f7748e7d3cb 100644
--- a/libavcodec/atrac3plusdec.c
+++ b/libavcodec/atrac3plusdec.c
@@ -169,40 +169,16 @@ static av_cold void atrac3p_init_static(void)
     ff_atrac3p_init_dsp_static();
 }
 
-static av_cold int atrac3p_decode_init(AVCodecContext *avctx)
+static av_cold int init_ch_units(ATRAC3PContext *ctx, AVCodecContext *avctx)
 {
-    static AVOnce init_static_once = AV_ONCE_INIT;
-    ATRAC3PContext *ctx = avctx->priv_data;
-    float scale;
     int i, ch, ret;
 
-    if (!avctx->block_align) {
-        av_log(avctx, AV_LOG_ERROR, "block_align is not set\n");
-        return AVERROR(EINVAL);
-    }
-
-    /* initialize IPQF */
-    scale = 32.0 / 32768.0;
-    ret = av_tx_init(&ctx->ipqf_dct_ctx, &ctx->ipqf_dct_fn, AV_TX_FLOAT_MDCT,
-                     1, 16, &scale, 0);
-    if (ret < 0)
-        return ret;
-
-    scale = -1.0f;
-    ret = av_tx_init(&ctx->mdct_ctx, &ctx->mdct_fn, AV_TX_FLOAT_MDCT,
-                     1, 128, &scale, AV_TX_FULL_IMDCT);
-    if (ret < 0)
-        return ret;
-
-    ff_atrac_init_gain_compensation(&ctx->gainc_ctx, 6, 2);
-
     if ((ret = set_channel_params(ctx, avctx)) < 0)
         return ret;
 
     ctx->ch_units = av_calloc(ctx->num_channel_blocks, sizeof(*ctx->ch_units));
-    ctx->fdsp = avpriv_float_dsp_alloc(avctx->flags & AV_CODEC_FLAG_BITEXACT);
 
-    if (!ctx->ch_units || !ctx->fdsp) {
+    if (!ctx->ch_units) {
         return AVERROR(ENOMEM);
     }
 
@@ -221,6 +197,45 @@ static av_cold int atrac3p_decode_init(AVCodecContext 
*avctx)
         ctx->ch_units[i].waves_info_prev = 
&ctx->ch_units[i].wave_synth_hist[1];
     }
 
+    return 0;
+}
+
+static av_cold int atrac3p_decode_init(AVCodecContext *avctx)
+{
+    static AVOnce init_static_once = AV_ONCE_INIT;
+    ATRAC3PContext *ctx = avctx->priv_data;
+    float scale;
+    int ret;
+
+    if (!avctx->block_align) {
+        av_log(avctx, AV_LOG_ERROR, "block_align is not set\n");
+        return AVERROR(EINVAL);
+    }
+
+    ret = init_ch_units(ctx, avctx);
+    if (ret < 0)
+        return ret;
+
+    /* initialize IPQF */
+    scale = 32.0 / 32768.0;
+    ret = av_tx_init(&ctx->ipqf_dct_ctx, &ctx->ipqf_dct_fn, AV_TX_FLOAT_MDCT,
+                     1, 16, &scale, 0);
+    if (ret < 0)
+        return ret;
+
+    scale = -1.0f;
+    ret = av_tx_init(&ctx->mdct_ctx, &ctx->mdct_fn, AV_TX_FLOAT_MDCT,
+                     1, 128, &scale, AV_TX_FULL_IMDCT);
+    if (ret < 0)
+        return ret;
+
+    ff_atrac_init_gain_compensation(&ctx->gainc_ctx, 6, 2);
+
+    ctx->fdsp = avpriv_float_dsp_alloc(avctx->flags & AV_CODEC_FLAG_BITEXACT);
+
+    if (!ctx->fdsp)
+        return AVERROR(ENOMEM);
+
     avctx->sample_fmt = AV_SAMPLE_FMT_FLTP;
 
     ff_thread_once(&init_static_once, atrac3p_init_static);
-- 
2.54.0


>From d7e53ed046d0cadcf4152989bf9e466d5d69d137 Mon Sep 17 00:00:00 2001
From: Simon Capriotti <[email protected]>
Date: Sat, 17 Oct 2026 14:30:32 +0200
Subject: [PATCH 4/7] avcodec: add an ATRAC3+ ATS decoder

Signed-off-by: Simon Capriotti <[email protected]>
---
 configure                  |  1 +
 libavcodec/allcodecs.c     |  1 +
 libavcodec/atrac3plusdec.c | 88 ++++++++++++++++++++++++++++++++++----
 libavcodec/utils.c         |  1 +
 4 files changed, 82 insertions(+), 9 deletions(-)

diff --git a/configure b/configure
index 083bd652c49..76d537126e9 100755
--- a/configure
+++ b/configure
@@ -3106,6 +3106,7 @@ asv2_encoder_select="aandcttables bswapdsp fdctdsp 
pixblockdsp"
 atrac1_decoder_select="sinewin"
 atrac3p_decoder_select="sinewin"
 atrac3pal_decoder_select="sinewin"
+atrac3p_ats_decoder_select="atrac3p_ats_parser atrac3p_decoder"
 av1_decoder_select="cbs_av1 itut_t35"
 bink_decoder_select="blockdsp hpeldsp"
 binkaudio_dct_decoder_select="wma_freqs"
diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c
index b12dcc405aa..dea3a985560 100644
--- a/libavcodec/allcodecs.c
+++ b/libavcodec/allcodecs.c
@@ -449,6 +449,7 @@ extern const FFCodec ff_atrac3_decoder;
 extern const FFCodec ff_atrac3al_decoder;
 extern const FFCodec ff_atrac3p_decoder;
 extern const FFCodec ff_atrac3pal_decoder;
+extern const FFCodec ff_atrac3p_ats_decoder;
 extern const FFCodec ff_atrac9_decoder;
 extern const FFCodec ff_binkaudio_dct_decoder;
 extern const FFCodec ff_binkaudio_rdft_decoder;
diff --git a/libavcodec/atrac3plusdec.c b/libavcodec/atrac3plusdec.c
index f7748e7d3cb..aa0bc78bc16 100644
--- a/libavcodec/atrac3plusdec.c
+++ b/libavcodec/atrac3plusdec.c
@@ -2,6 +2,7 @@
  * ATRAC3+ compatible decoder
  *
  * Copyright (c) 2010-2013 Maxim Poliakovski
+ * Copyright (c) 2026 Simon Capriotti
  *
  * This file is part of FFmpeg.
  *
@@ -25,7 +26,9 @@
  * Sony ATRAC3+ compatible decoder.
  *
  * Container formats used to store its data:
- * RIFF WAV (.at3) and Sony OpenMG (.oma, .aa3).
+ * RIFF WAV (.at3, .atx), Sony OpenMG (.oma, .aa3),
+ * and MPEG-PS (PSP Movie Format (for UMD Video): .mps,
+ * PSP Movie Format (for games): .pmf, PlayStation Advanced Movie Format: .pam)
  *
  * Technical description of this codec can be found here:
  * http://wiki.multimedia.cx/index.php?title=ATRAC3plus
@@ -34,6 +37,8 @@
  * for their precious technical help!
  */
 
+#include "config_components.h"
+
 #include <stdint.h>
 #include <string.h>
 
@@ -207,14 +212,16 @@ static av_cold int atrac3p_decode_init(AVCodecContext 
*avctx)
     float scale;
     int ret;
 
-    if (!avctx->block_align) {
-        av_log(avctx, AV_LOG_ERROR, "block_align is not set\n");
-        return AVERROR(EINVAL);
-    }
+    if (avctx->codec_id != AV_CODEC_ID_ATRAC3P_ATS) {
+        if (!avctx->block_align) {
+            av_log(avctx, AV_LOG_ERROR, "block_align is not set\n");
+            return AVERROR(EINVAL);
+        }
 
-    ret = init_ch_units(ctx, avctx);
-    if (ret < 0)
-        return ret;
+        ret = init_ch_units(ctx, avctx);
+        if (ret < 0)
+            return ret;
+    }
 
     /* initialize IPQF */
     scale = 32.0 / 32768.0;
@@ -429,7 +436,9 @@ static int atrac3p_decode_frame(AVCodecContext *avctx, 
AVFrame *frame,
 
     *got_frame_ptr = 1;
 
-    return avctx->codec_id == AV_CODEC_ID_ATRAC3P ? FFMIN(avctx->block_align, 
avpkt->size) : avpkt->size;
+    return avctx->codec_id == AV_CODEC_ID_ATRAC3P ? FFMIN(avctx->block_align, 
avpkt->size) :
+           avctx->codec_id == AV_CODEC_ID_ATRAC3P_ATS ? 
FFMIN(avctx->block_align - ATRAC3P_ATS_HEADER_SIZE, avpkt->size) :
+           avpkt->size;
 }
 
 const FFCodec ff_atrac3p_decoder = {
@@ -457,3 +466,64 @@ const FFCodec ff_atrac3pal_decoder = {
     .close          = atrac3p_decode_close,
     FF_CODEC_DECODE_CB(atrac3p_decode_frame),
 };
+
+#if CONFIG_ATRAC3P_ATS_DECODER
+
+#include "atrac3plus_ats_parser.h"
+
+typedef struct ATRAC3PATSContext {
+    ATRAC3PContext atrac3p_ctx;
+    uint64_t prev_header;
+} ATRAC3PATSContext;
+
+static int atrac3p_ats_decode_frame(AVCodecContext *avctx, AVFrame *frame,
+                                    int *got_frame_ptr, AVPacket *avpkt)
+{
+    ATRAC3PATSContext *const ctx = avctx->priv_data;
+
+    if (avpkt->size < ATRAC3P_ATS_HEADER_SIZE) {
+        av_log(avctx, AV_LOG_ERROR, "Packet too small!\n");
+        return AVERROR_INVALIDDATA;
+    }
+
+    const uint64_t ats_header = AV_RB64(avpkt->data);
+    if (ats_header != ctx->prev_header) {
+        Atrac3pAtsDownmixLevels downmix_levels;
+
+        int ret = ff_atrac3p_ats_parse_header(avpkt->data, avpkt->size, avctx, 
&downmix_levels);
+        if (ret < 0)
+            return ret;
+
+        av_freep(&ctx->atrac3p_ctx.ch_units);
+        ret = init_ch_units(&ctx->atrac3p_ctx, avctx);
+        if (ret < 0)
+            return ret;
+
+        if (downmix_levels.front_downmix_level || 
downmix_levels.center_downmix_level ||
+            downmix_levels.back_downmix_level || 
downmix_levels.lfe_downmix_level ||
+            downmix_levels.side_downmix_level)
+            avpriv_report_missing_feature(avctx, "Downmixing");
+
+        ctx->prev_header = ats_header;
+    }
+
+    avpkt->data += ATRAC3P_ATS_HEADER_SIZE;
+    avpkt->size -= ATRAC3P_ATS_HEADER_SIZE;
+
+    return atrac3p_decode_frame(avctx, frame, got_frame_ptr, avpkt);
+}
+
+const FFCodec ff_atrac3p_ats_decoder = {
+    .p.name         = "atrac3plus_ats",
+    CODEC_LONG_NAME("ATRAC3+ ATS (Adaptive TRansform Acoustic Coding 3+ ATS 
syntax)"),
+    .p.type         = AVMEDIA_TYPE_AUDIO,
+    .p.id           = AV_CODEC_ID_ATRAC3P_ATS,
+    .p.capabilities = AV_CODEC_CAP_CHANNEL_CONF | AV_CODEC_CAP_DR1,
+    .caps_internal  = FF_CODEC_CAP_INIT_CLEANUP,
+    .priv_data_size = sizeof(ATRAC3PATSContext),
+    .init           = atrac3p_decode_init,
+    .close          = atrac3p_decode_close,
+    FF_CODEC_DECODE_CB(atrac3p_ats_decode_frame),
+};
+
+#endif
diff --git a/libavcodec/utils.c b/libavcodec/utils.c
index 306696ecc5c..2d0d1b3dcd7 100644
--- a/libavcodec/utils.c
+++ b/libavcodec/utils.c
@@ -608,6 +608,7 @@ static int get_audio_frame_duration(enum AVCodecID id, int 
sr, int ch, int ba,
         if (framecount > INT_MAX/1024)
             return 0;
         return 1024 * framecount;
+    case AV_CODEC_ID_ATRAC3P_ATS:
     case AV_CODEC_ID_ATRAC3P:      return 2048;
     case AV_CODEC_ID_MP2:
     case AV_CODEC_ID_MUSEPACK7:    return 1152;
-- 
2.54.0


>From 3867736e4b4acef71f870e852b67ef570df14d2f Mon Sep 17 00:00:00 2001
From: Simon Capriotti <[email protected]>
Date: Sat, 17 Oct 2026 14:30:50 +0200
Subject: [PATCH 5/7] avformat/riffdec: add support for ATRAC3+ ATS

Signed-off-by: Simon Capriotti <[email protected]>
---
 libavformat/riff.c    | 1 +
 libavformat/riffdec.c | 3 +++
 2 files changed, 4 insertions(+)

diff --git a/libavformat/riff.c b/libavformat/riff.c
index 50c14ecb661..4a4c5564deb 100644
--- a/libavformat/riff.c
+++ b/libavformat/riff.c
@@ -662,6 +662,7 @@ const struct AVCodecTag *avformat_get_riff_audio_tags(void)
 const AVCodecGuid ff_codec_wav_guids[] = {
     { AV_CODEC_ID_AC3,      { 0x2C, 0x80, 0x6D, 0xE0, 0x46, 0xDB, 0xCF, 0x11, 
0xB4, 0xD1, 0x00, 0x80, 0x5F, 0x6C, 0xBB, 0xEA } },
     { AV_CODEC_ID_ATRAC3P,  { 0xBF, 0xAA, 0x23, 0xE9, 0x58, 0xCB, 0x71, 0x44, 
0xA1, 0x19, 0xFF, 0xFA, 0x01, 0xE4, 0xCE, 0x62 } },
+    { AV_CODEC_ID_ATRAC3P_ATS, { 0x0D, 0x5C, 0x13, 0x50, 0x1B, 0xA2, 0xEC, 
0x4B, 0x91, 0xED, 0x6E, 0xDD, 0xB8, 0x3C, 0x8C, 0x66 } },
     { AV_CODEC_ID_ATRAC9,   { 0xD2, 0x42, 0xE1, 0x47, 0xBA, 0x36, 0x8D, 0x4D, 
0x88, 0xFC, 0x61, 0x65, 0x4F, 0x8C, 0x83, 0x6C } },
     { AV_CODEC_ID_EAC3,     { 0xAF, 0x87, 0xFB, 0xA7, 0x02, 0x2D, 0xFB, 0x42, 
0xA4, 0xD4, 0x05, 0xCD, 0x93, 0x84, 0x3B, 0xDD } },
     { AV_CODEC_ID_MP2,      { 0x2B, 0x80, 0x6D, 0xE0, 0x46, 0xDB, 0xCF, 0x11, 
0xB4, 0xD1, 0x00, 0x80, 0x5F, 0x6C, 0xBB, 0xEA } },
diff --git a/libavformat/riffdec.c b/libavformat/riffdec.c
index cb258897982..9b63d358f53 100644
--- a/libavformat/riffdec.c
+++ b/libavformat/riffdec.c
@@ -254,6 +254,9 @@ int ff_get_wav_header(AVFormatContext *s, AVIOContext *pb,
     /* override bits_per_coded_sample for G.726 */
     if (par->codec_id == AV_CODEC_ID_ADPCM_G726 && par->sample_rate)
         par->bits_per_coded_sample = par->bit_rate / par->sample_rate;
+    /* add the size of the ATS header for ATRAC3+ ATS */
+    if (par->codec_id == AV_CODEC_ID_ATRAC3P_ATS)
+        par->block_align += 8;
 
     if (par->codec_id == AV_CODEC_ID_ADPCM_RHETOREX && 
par->bits_per_coded_sample == 3)
         par->sample_rate = par->sample_rate * 3LL / 4;
-- 
2.54.0


>From fdc244a6cf7e9e2292072dda9a908a2f4dfbbc8c Mon Sep 17 00:00:00 2001
From: Simon Capriotti <[email protected]>
Date: Sat, 17 Oct 2026 14:30:56 +0200
Subject: [PATCH 6/7] Changelog: add entry for ATRAC3+ ATS

Signed-off-by: Simon Capriotti <[email protected]>
---
 Changelog | 1 +
 1 file changed, 1 insertion(+)

diff --git a/Changelog b/Changelog
index 055b0cfa797..5d4c49746b1 100644
--- a/Changelog
+++ b/Changelog
@@ -3,6 +3,7 @@ releases are sorted from youngest to oldest.
 
 version <next>:
 - JPEG Multi-Picture Format demuxer
+- ATRAC3+ ATS decoder and parser
 
 version 9.1:
 - ASTC image encoding and decoding via libastcenc, with .astc and KTX 1.0 
muxers and demuxers
-- 
2.54.0


>From 14d8cfa9fdac20a4a7a78343b0e4cbf8ef5c16ec Mon Sep 17 00:00:00 2001
From: Simon Capriotti <[email protected]>
Date: Sat, 17 Oct 2026 14:31:01 +0200
Subject: [PATCH 7/7] tests/fate/atrac: add tests for ATRAC3+ ATS

Signed-off-by: Simon Capriotti <[email protected]>
---
 tests/fate/atrac.mak | 13 ++++++++++++-
 1 file changed, 12 insertions(+), 1 deletion(-)

diff --git a/tests/fate/atrac.mak b/tests/fate/atrac.mak
index 847eed5b619..1e18bc9b239 100644
--- a/tests/fate/atrac.mak
+++ b/tests/fate/atrac.mak
@@ -33,7 +33,17 @@ fate-atrac3p-2: REF = 
$(SAMPLES)/atrac3p/sonateno14op27-2-cut.pcm
 
 FATE_ATRAC3P-$(call PCM, OMA, ATRAC3P, ARESAMPLE_FILTER) += $(FATE_ATRAC3P)
 
-FATE_ATRAC_ALL = $(FATE_ATRAC1-yes) $(FATE_ATRAC3-yes) $(FATE_ATRAC3P-yes)
+FATE_ATRAC3P_ATS += fate-atrac3p-ats-1
+fate-atrac3p-ats-1: CMD = pcm -i $(TARGET_SAMPLES)/atrac3p-ats/at3p_sample1.atx
+fate-atrac3p-ats-1: REF = $(SAMPLES)/atrac3p-ats/at3p_sample1.pcm
+
+FATE_ATRAC3P_ATS += fate-atrac3p-ats-2
+fate-atrac3p-ats-2: CMD = pcm -i 
$(TARGET_SAMPLES)/atrac3p-ats/sonateno14op27-2-cut.atx
+fate-atrac3p-ats-2: REF = $(SAMPLES)/atrac3p-ats/sonateno14op27-2-cut.pcm
+
+FATE_ATRAC3P_ATS-$(call PCM, WAV, ATRAC3P_ATS, ARESAMPLE_FILTER) += 
$(FATE_ATRAC3P_ATS)
+
+FATE_ATRAC_ALL = $(FATE_ATRAC1-yes) $(FATE_ATRAC3-yes) $(FATE_ATRAC3P-yes) 
$(FATE_ATRAC3P_ATS-yes)
 
 $(FATE_ATRAC_ALL): CMP = oneoff
 
@@ -50,3 +60,4 @@ FATE_SAMPLES_FFMPEG += $(FATE_ATRAC_ALL) 
$(FATE_ATRAC_REMUX-yes)
 fate-atrac:   $(FATE_ATRAC_ALL) $(FATE_ATRAC_REMUX-yes)
 fate-atrac3:  $(FATE_ATRAC3-yes)
 fate-atrac3p: $(FATE_ATRAC3P-yes)
+fate-atrac3p-ats: $(FATE_ATRAC3P_ATS-yes)
-- 
2.54.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to