PR #23393 opened by Simon Capriotti (capriots) URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23393 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23393.patch
Adds a decoder for the LPCM format found in PAMF (.pam) videos from PS3 games. The LPCM format is identical to pcm_bluray, but without the header at the start of each frame. Sample rate, bits per sample, and channel layout parameters are instead located in the global header of PAMF files. The decoder also supports 20-bit samples. 20-bit samples can be treated like 24-bit samples since they are padded with zeros to 24 bits (this is likely also applicable to regular pcm_bluray, at least the PS3 decodes both Blu-ray and PAMF LPCM like that). >From 152817bac689cee5944c7cbd15cc47af85af7506 Mon Sep 17 00:00:00 2001 From: Simon Capriotti <[email protected]> Date: Sun, 31 May 2026 18:57:53 +0200 Subject: [PATCH 1/4] avcodec/pcm-bluray: split parsing of the header into separate function Makes it possible to decode raw HDMV PCM frames without a header at the start Signed-off-by: Simon Capriotti <[email protected]> --- libavcodec/pcm-bluray.c | 186 +++++++++++++++++++++------------------- 1 file changed, 97 insertions(+), 89 deletions(-) diff --git a/libavcodec/pcm-bluray.c b/libavcodec/pcm-bluray.c index 194224c67e..54ee1a1567 100644 --- a/libavcodec/pcm-bluray.c +++ b/libavcodec/pcm-bluray.c @@ -46,84 +46,8 @@ * 3/4+lfe L R C LS Rls Rrs RS lfe */ -/** - * Parse the header of a LPCM frame read from a Blu-ray MPEG-TS stream - * @param avctx the codec context - * @param header pointer to the first four bytes of the data packet - */ -static int pcm_bluray_parse_header(AVCodecContext *avctx, - const uint8_t *header) -{ - static const uint8_t bits_per_samples[4] = { 0, 16, 20, 24 }; - static const AVChannelLayout channel_layouts[16] = { - { 0 }, AV_CHANNEL_LAYOUT_MONO, { 0 }, - AV_CHANNEL_LAYOUT_STEREO, AV_CHANNEL_LAYOUT_SURROUND, AV_CHANNEL_LAYOUT_2_1, - AV_CHANNEL_LAYOUT_4POINT0, AV_CHANNEL_LAYOUT_2_2, AV_CHANNEL_LAYOUT_5POINT0, - AV_CHANNEL_LAYOUT_5POINT1, AV_CHANNEL_LAYOUT_7POINT0, AV_CHANNEL_LAYOUT_7POINT1, - { 0 }, { 0 }, { 0 }, { 0 }, - }; - uint8_t channel_layout = header[2] >> 4; - - if (avctx->debug & FF_DEBUG_PICT_INFO) - ff_dlog(avctx, "pcm_bluray_parse_header: header = %02x%02x%02x%02x\n", - header[0], header[1], header[2], header[3]); - - /* get the sample depth and derive the sample format from it */ - avctx->bits_per_coded_sample = bits_per_samples[header[3] >> 6]; - if (!(avctx->bits_per_coded_sample == 16 || avctx->bits_per_coded_sample == 24)) { - av_log(avctx, AV_LOG_ERROR, "unsupported sample depth (%d)\n", avctx->bits_per_coded_sample); - return AVERROR_INVALIDDATA; - } - avctx->sample_fmt = avctx->bits_per_coded_sample == 16 ? AV_SAMPLE_FMT_S16 - : AV_SAMPLE_FMT_S32; - if (avctx->sample_fmt == AV_SAMPLE_FMT_S32) - avctx->bits_per_raw_sample = avctx->bits_per_coded_sample; - - /* get the sample rate. Not all values are used. */ - switch (header[2] & 0x0f) { - case 1: - avctx->sample_rate = 48000; - break; - case 4: - avctx->sample_rate = 96000; - break; - case 5: - avctx->sample_rate = 192000; - break; - default: - avctx->sample_rate = 0; - av_log(avctx, AV_LOG_ERROR, "reserved sample rate (%d)\n", - header[2] & 0x0f); - return AVERROR_INVALIDDATA; - } - - /* - * get the channel number (and mapping). Not all values are used. - * It must be noted that the number of channels in the MPEG stream can - * differ from the actual meaningful number, e.g. mono audio still has two - * channels, one being empty. - */ - av_channel_layout_uninit(&avctx->ch_layout); - avctx->ch_layout = channel_layouts[channel_layout]; - if (!avctx->ch_layout.nb_channels) { - av_log(avctx, AV_LOG_ERROR, "reserved channel configuration (%d)\n", - channel_layout); - return AVERROR_INVALIDDATA; - } - - avctx->bit_rate = FFALIGN(avctx->ch_layout.nb_channels, 2) * avctx->sample_rate * - avctx->bits_per_coded_sample; - - if (avctx->debug & FF_DEBUG_PICT_INFO) - ff_dlog(avctx, - "pcm_bluray_parse_header: %d channels, %d bits per sample, %d Hz, %"PRId64" bit/s\n", - avctx->ch_layout.nb_channels, avctx->bits_per_coded_sample, - avctx->sample_rate, avctx->bit_rate); - return 0; -} - -static int pcm_bluray_decode_frame(AVCodecContext *avctx, AVFrame *frame, - int *got_frame_ptr, AVPacket *avpkt) +static int decode_frame(AVCodecContext *avctx, AVFrame *frame, + int *got_frame_ptr, AVPacket *avpkt) { const uint8_t *src = avpkt->data; int buf_size = avpkt->size; @@ -133,16 +57,6 @@ static int pcm_bluray_decode_frame(AVCodecContext *avctx, AVFrame *frame, int16_t *dst16; int32_t *dst32; - if (buf_size < 4) { - av_log(avctx, AV_LOG_ERROR, "PCM packet too small\n"); - return AVERROR_INVALIDDATA; - } - - if ((retval = pcm_bluray_parse_header(avctx, src))) - return retval; - src += 4; - buf_size -= 4; - bytestream2_init(&gb, src, buf_size); /* There's always an even number of channels in the source */ @@ -297,7 +211,101 @@ static int pcm_bluray_decode_frame(AVCodecContext *avctx, AVFrame *frame, if (avctx->debug & FF_DEBUG_BITSTREAM) ff_dlog(avctx, "pcm_bluray_decode_frame: decoded %d -> %d bytes\n", retval, buf_size); - return retval + 4; + return retval; +} + +/** + * Parse the header of a LPCM frame read from a Blu-ray MPEG-TS stream + * @param avctx the codec context + * @param header pointer to the first four bytes of the data packet + */ +static int pcm_bluray_parse_header(AVCodecContext *avctx, + const uint8_t *header) +{ + static const uint8_t bits_per_samples[4] = { 0, 16, 20, 24 }; + static const AVChannelLayout channel_layouts[16] = { + { 0 }, AV_CHANNEL_LAYOUT_MONO, { 0 }, + AV_CHANNEL_LAYOUT_STEREO, AV_CHANNEL_LAYOUT_SURROUND, AV_CHANNEL_LAYOUT_2_1, + AV_CHANNEL_LAYOUT_4POINT0, AV_CHANNEL_LAYOUT_2_2, AV_CHANNEL_LAYOUT_5POINT0, + AV_CHANNEL_LAYOUT_5POINT1, AV_CHANNEL_LAYOUT_7POINT0, AV_CHANNEL_LAYOUT_7POINT1, + { 0 }, { 0 }, { 0 }, { 0 }, + }; + uint8_t channel_layout = header[2] >> 4; + + if (avctx->debug & FF_DEBUG_PICT_INFO) + ff_dlog(avctx, "pcm_bluray_parse_header: header = %02x%02x%02x%02x\n", + header[0], header[1], header[2], header[3]); + + /* get the sample depth and derive the sample format from it */ + avctx->bits_per_coded_sample = bits_per_samples[header[3] >> 6]; + if (!(avctx->bits_per_coded_sample == 16 || avctx->bits_per_coded_sample == 24)) { + av_log(avctx, AV_LOG_ERROR, "unsupported sample depth (%d)\n", avctx->bits_per_coded_sample); + return AVERROR_INVALIDDATA; + } + avctx->sample_fmt = avctx->bits_per_coded_sample == 16 ? AV_SAMPLE_FMT_S16 + : AV_SAMPLE_FMT_S32; + if (avctx->sample_fmt == AV_SAMPLE_FMT_S32) + avctx->bits_per_raw_sample = avctx->bits_per_coded_sample; + + /* get the sample rate. Not all values are used. */ + switch (header[2] & 0x0f) { + case 1: + avctx->sample_rate = 48000; + break; + case 4: + avctx->sample_rate = 96000; + break; + case 5: + avctx->sample_rate = 192000; + break; + default: + avctx->sample_rate = 0; + av_log(avctx, AV_LOG_ERROR, "reserved sample rate (%d)\n", + header[2] & 0x0f); + return AVERROR_INVALIDDATA; + } + + /* + * get the channel number (and mapping). Not all values are used. + * It must be noted that the number of channels in the MPEG stream can + * differ from the actual meaningful number, e.g. mono audio still has two + * channels, one being empty. + */ + av_channel_layout_uninit(&avctx->ch_layout); + avctx->ch_layout = channel_layouts[channel_layout]; + if (!avctx->ch_layout.nb_channels) { + av_log(avctx, AV_LOG_ERROR, "reserved channel configuration (%d)\n", + channel_layout); + return AVERROR_INVALIDDATA; + } + + avctx->bit_rate = FFALIGN(avctx->ch_layout.nb_channels, 2) * avctx->sample_rate * + avctx->bits_per_coded_sample; + + if (avctx->debug & FF_DEBUG_PICT_INFO) + ff_dlog(avctx, + "pcm_bluray_parse_header: %d channels, %d bits per sample, %d Hz, %"PRId64" bit/s\n", + avctx->ch_layout.nb_channels, avctx->bits_per_coded_sample, + avctx->sample_rate, avctx->bit_rate); + return 0; +} + +static int pcm_bluray_decode_frame(AVCodecContext *avctx, AVFrame *frame, + int *got_frame_ptr, AVPacket *avpkt) +{ + int retval; + + if (avpkt->size < 4) { + av_log(avctx, AV_LOG_ERROR, "PCM packet too small\n"); + return AVERROR_INVALIDDATA; + } + + if ((retval = pcm_bluray_parse_header(avctx, avpkt->data))) + return retval; + + avpkt->data += 4; + avpkt->size -= 4; + return decode_frame(avctx, frame, got_frame_ptr, avpkt); } const FFCodec ff_pcm_bluray_decoder = { -- 2.52.0 >From 5118efe10ec197b8be49bb2aeb5d57044345d3f7 Mon Sep 17 00:00:00 2001 From: Simon Capriotti <[email protected]> Date: Mon, 25 May 2026 23:21:55 +0200 Subject: [PATCH 2/4] avcodec: add PCM PAMF 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 99e7c0de3d..8a57fd31d3 100644 --- a/libavcodec/codec_desc.c +++ b/libavcodec/codec_desc.c @@ -2271,6 +2271,13 @@ static const AVCodecDescriptor codec_descriptors[] = { .long_name = NULL_IF_CONFIG_SMALL("PCM SGA"), .props = AV_CODEC_PROP_INTRA_ONLY | AV_CODEC_PROP_LOSSLESS, }, + { + .id = AV_CODEC_ID_PCM_PAMF, + .type = AVMEDIA_TYPE_AUDIO, + .name = "pcm_pamf", + .long_name = NULL_IF_CONFIG_SMALL("PCM signed 16|20|24-bit big-endian for PAMF streams"), + .props = AV_CODEC_PROP_LOSSLESS, + }, /* various ADPCM codecs */ { diff --git a/libavcodec/codec_id.h b/libavcodec/codec_id.h index bb2c16a958..1f4eb27300 100644 --- a/libavcodec/codec_id.h +++ b/libavcodec/codec_id.h @@ -373,6 +373,7 @@ enum AVCodecID { AV_CODEC_ID_PCM_F24LE, AV_CODEC_ID_PCM_VIDC, AV_CODEC_ID_PCM_SGA, + AV_CODEC_ID_PCM_PAMF, /* various ADPCM codecs */ AV_CODEC_ID_ADPCM_IMA_QT = 0x11000, -- 2.52.0 >From 691fe9de22d94b700ff525be6aec413431f5d060 Mon Sep 17 00:00:00 2001 From: Simon Capriotti <[email protected]> Date: Sun, 31 May 2026 19:40:45 +0200 Subject: [PATCH 3/4] avcodec/pcm-bluray: add a PCM PAMF decoder PCM streams in PAMF files are pcm_bluray streams without the header at the start of each frame. Signed-off-by: Simon Capriotti <[email protected]> --- libavcodec/Makefile | 1 + libavcodec/allcodecs.c | 1 + libavcodec/pcm-bluray.c | 66 +++++++++++++++++++++++++++++++++++++++-- 3 files changed, 66 insertions(+), 2 deletions(-) diff --git a/libavcodec/Makefile b/libavcodec/Makefile index e68d2db15d..5e76e59cf7 100644 --- a/libavcodec/Makefile +++ b/libavcodec/Makefile @@ -912,6 +912,7 @@ OBJS-$(CONFIG_PCM_F64LE_ENCODER) += pcm.o OBJS-$(CONFIG_PCM_LXF_DECODER) += pcm.o OBJS-$(CONFIG_PCM_MULAW_DECODER) += pcm.o OBJS-$(CONFIG_PCM_MULAW_ENCODER) += pcm.o +OBJS-$(CONFIG_PCM_PAMF_DECODER) += pcm-bluray.o OBJS-$(CONFIG_PCM_S8_DECODER) += pcm.o OBJS-$(CONFIG_PCM_S8_ENCODER) += pcm.o OBJS-$(CONFIG_PCM_S8_PLANAR_DECODER) += pcm.o diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c index 0815d46f79..67b7cca03c 100644 --- a/libavcodec/allcodecs.c +++ b/libavcodec/allcodecs.c @@ -587,6 +587,7 @@ extern const FFCodec ff_pcm_f64le_decoder; extern const FFCodec ff_pcm_lxf_decoder; extern const FFCodec ff_pcm_mulaw_encoder; extern const FFCodec ff_pcm_mulaw_decoder; +extern const FFCodec ff_pcm_pamf_decoder; extern const FFCodec ff_pcm_s8_encoder; extern const FFCodec ff_pcm_s8_decoder; extern const FFCodec ff_pcm_s8_planar_encoder; diff --git a/libavcodec/pcm-bluray.c b/libavcodec/pcm-bluray.c index 54ee1a1567..a66b0e5249 100644 --- a/libavcodec/pcm-bluray.c +++ b/libavcodec/pcm-bluray.c @@ -1,6 +1,7 @@ /* - * LPCM codecs for PCM format found in Blu-ray PCM streams + * LPCM codecs for PCM format found in Blu-ray and PAMF PCM streams * Copyright (c) 2009, 2013 Christian Schmidt + * Copyright (c) 2026 Simon Capriotti * * This file is part of FFmpeg. * @@ -21,9 +22,11 @@ /** * @file - * PCM codec for Blu-ray PCM audio tracks + * PCM codec for Blu-ray and PAMF PCM audio tracks */ +#include "config_components.h" + #include "libavutil/channel_layout.h" #include "avcodec.h" #include "bytestream.h" @@ -214,6 +217,8 @@ static int decode_frame(AVCodecContext *avctx, AVFrame *frame, return retval; } +#if CONFIG_PCM_BLURAY_DECODER + /** * Parse the header of a LPCM frame read from a Blu-ray MPEG-TS stream * @param avctx the codec context @@ -316,3 +321,60 @@ const FFCodec ff_pcm_bluray_decoder = { FF_CODEC_DECODE_CB(pcm_bluray_decode_frame), .p.capabilities = AV_CODEC_CAP_DR1 | AV_CODEC_CAP_CHANNEL_CONF, }; + +#endif + +#if CONFIG_PCM_PAMF_DECODER + +static av_cold int pcm_pamf_init_decode(AVCodecContext *avctx) +{ + switch (avctx->bits_per_coded_sample) { + case 16: + avctx->sample_fmt = AV_SAMPLE_FMT_S16; + break; + case 20: + case 24: + avctx->sample_fmt = AV_SAMPLE_FMT_S32; + break; + default: + av_log(avctx, AV_LOG_FATAL, "Invalid sample depth (%d)\n", + avctx->bits_per_coded_sample); + return AVERROR(EINVAL); + } + + avctx->bits_per_raw_sample = avctx->bits_per_coded_sample; + + switch (avctx->ch_layout.u.mask) { + case AV_CH_LAYOUT_MONO: + case AV_CH_LAYOUT_STEREO: + case AV_CH_LAYOUT_SURROUND: + case AV_CH_LAYOUT_2_1: + case AV_CH_LAYOUT_4POINT0: + case AV_CH_LAYOUT_2_2: + case AV_CH_LAYOUT_5POINT0: + case AV_CH_LAYOUT_5POINT1: + case AV_CH_LAYOUT_7POINT0: + case AV_CH_LAYOUT_7POINT1: + break; + default: + av_log(avctx, AV_LOG_FATAL, "Invalid channel layout (%lX)\n", + avctx->ch_layout.u.mask); + return AVERROR(EINVAL); + } + + avctx->bit_rate = FFALIGN(avctx->ch_layout.nb_channels, 2) * + avctx->sample_rate * avctx->bits_per_coded_sample; + return 0; +} + +const FFCodec ff_pcm_pamf_decoder = { + .p.name = "pcm_pamf", + CODEC_LONG_NAME("PCM signed 16|20|24-bit big-endian for PAMF streams"), + .p.type = AVMEDIA_TYPE_AUDIO, + .p.id = AV_CODEC_ID_PCM_PAMF, + .init = pcm_pamf_init_decode, + FF_CODEC_DECODE_CB(decode_frame), + .p.capabilities = AV_CODEC_CAP_DR1, +}; + +#endif -- 2.52.0 >From 705783767b6c7af1941efdf405def3e81ec01241 Mon Sep 17 00:00:00 2001 From: Simon Capriotti <[email protected]> Date: Sun, 7 Jun 2026 15:38:17 +0200 Subject: [PATCH 4/4] Changelog: add entry for PCM PAMF Signed-off-by: Simon Capriotti <[email protected]> --- Changelog | 1 + 1 file changed, 1 insertion(+) diff --git a/Changelog b/Changelog index 3c48005026..b3ddf39e14 100644 --- a/Changelog +++ b/Changelog @@ -17,6 +17,7 @@ version <next>: - Remove CELT decoding support (doesn't affect Opus CELT) - Remove ogg/celt parsing - Bitstream filter to split Dolby Vision multi-layer HEVC +- PCM PAMF decoder version 8.1: -- 2.52.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
