diff mbox

[FFmpeg-devel,2/5] mpeg: add experimental support for PSMF audio.

Message ID 20171226080652.84864-3-misty@brew.sh
State Superseded
Headers show

Commit Message

misty@brew.sh Dec. 26, 2017, 8:06 a.m. UTC
From: Maxim Poliakovski <maximumspatium@googlemail.com>

Changes by Misty De Meo <mistydemeo@gmail.com>:

atrac3plus_parser: remove return statements for invalid data

atrac3plus_parser: use libavcodec's oma

Signed-off-by: Misty De Meo <mistydemeo@gmail.com>
---
 libavcodec/Makefile            |   1 +
 libavcodec/allcodecs.c         |   1 +
 libavcodec/atrac3plus_parser.c | 150 +++++++++++++++++++++++++++++++++++++++++
 libavformat/mpeg.c             |  27 +++++++-
 4 files changed, 178 insertions(+), 1 deletion(-)
 create mode 100644 libavcodec/atrac3plus_parser.c

Comments

Michael Niedermayer Dec. 27, 2017, 10:02 p.m. UTC | #1
On Tue, Dec 26, 2017 at 04:06:49PM +0800, misty@brew.sh wrote:
> From: Maxim Poliakovski <maximumspatium@googlemail.com>
> 
> Changes by Misty De Meo <mistydemeo@gmail.com>:
> 
> atrac3plus_parser: remove return statements for invalid data
> 
> atrac3plus_parser: use libavcodec's oma
> 
> Signed-off-by: Misty De Meo <mistydemeo@gmail.com>
> ---
>  libavcodec/Makefile            |   1 +
>  libavcodec/allcodecs.c         |   1 +
>  libavcodec/atrac3plus_parser.c | 150 +++++++++++++++++++++++++++++++++++++++++
>  libavformat/mpeg.c             |  27 +++++++-
>  4 files changed, 178 insertions(+), 1 deletion(-)
>  create mode 100644 libavcodec/atrac3plus_parser.c

This breaks playback of atrac3p (atrac3p\ phase\ inv\ 2.at3)

Ive not investigated, but i doubt very much that simply removing the
error returns will work. Every single error path must be handled
appropriately and you should ensure that its all correct.
Not loosing or corrupting data on any error path and not doing
anything else bad or undefined ...

[...]
misty@brew.sh Dec. 29, 2017, 10:51 a.m. UTC | #2
From: Misty De Meo <mistydemeo@gmail.com>

Thanks for pointing this out. I hadn't had a separate .at3 file to test
with, and didn't realize that this broke regular ATRAC3P files.
I've added a new fate test which detects this regression; it passes on
master and fails in the MPEG-ATRAC3 branch.
I'll keep working on my branch, but meanwhile I've included the test with
this message - I figured it'd be useful regardless.

The files used in this test can be downloaded here:
https://public.drac.at/bgm01.at3
https://public.drac.at/bgm01.s16

Misty De Meo (1):
  fate: add atrac3p conversion test

 tests/fate/atrac.mak | 4 ++++
 1 file changed, 4 insertions(+)
diff mbox

Patch

diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index ca72138c02..e0e3f1ebac 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -977,6 +977,7 @@  OBJS-$(CONFIG_AAC_PARSER)              += aac_parser.o aac_ac3_parser.o \
                                           mpeg4audio.o
 OBJS-$(CONFIG_AC3_PARSER)              += ac3tab.o aac_ac3_parser.o
 OBJS-$(CONFIG_ADX_PARSER)              += adx_parser.o adx.o
+OBJS-$(CONFIG_ATRAC3P_PARSER)          += atrac3plus_parser.o
 OBJS-$(CONFIG_BMP_PARSER)              += bmp_parser.o
 OBJS-$(CONFIG_CAVSVIDEO_PARSER)        += cavs_parser.o
 OBJS-$(CONFIG_COOK_PARSER)             += cook_parser.o
diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c
index ed1e7ab06e..81d5d2814a 100644
--- a/libavcodec/allcodecs.c
+++ b/libavcodec/allcodecs.c
@@ -623,6 +623,7 @@  static void register_all(void)
     REGISTER_PARSER(AAC_LATM,           aac_latm);
     REGISTER_PARSER(AC3,                ac3);
     REGISTER_PARSER(ADX,                adx);
+    REGISTER_PARSER(ATRAC3P,            atrac3p);
     REGISTER_PARSER(BMP,                bmp);
     REGISTER_PARSER(CAVSVIDEO,          cavsvideo);
     REGISTER_PARSER(COOK,               cook);
diff --git a/libavcodec/atrac3plus_parser.c b/libavcodec/atrac3plus_parser.c
new file mode 100644
index 0000000000..9f54efddd2
--- /dev/null
+++ b/libavcodec/atrac3plus_parser.c
@@ -0,0 +1,150 @@ 
+/*
+ * Copyright (C) 2014 Maxim Poliakovski
+ *
+ * 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 "parser.h"
+#include "get_bits.h"
+#include "oma.h"
+
+typedef struct Atrac3PlusParseContext {
+    ParseContext pc;
+    uint8_t hdr[8];
+    int hdr_bytes_needed, got_bytes;
+    int sample_rate, channel_id, frame_size;
+} Atrac3PlusParseContext;
+
+static int parse_sound_frame_header(Atrac3PlusParseContext *c,
+                                    const uint8_t *buf)
+{
+    uint16_t atrac_config;
+
+    if (AV_RB16(buf) != 0x0FD0)
+        return AVERROR_INVALIDDATA;
+
+    atrac_config = AV_RB16(&buf[2]);
+    c->sample_rate = oma_srate_tab[(atrac_config >> 13) & 7] * 100;
+    c->channel_id  = (atrac_config >> 10) & 7;
+    c->frame_size  = ((atrac_config & 0x3FF) * 8) + 8;
+
+    if (!c->channel_id || !c->sample_rate || !c->frame_size)
+        return AVERROR_INVALIDDATA;
+
+    return 0;
+}
+
+static int ff_atrac3p_parse(AVCodecParserContext *s,
+                 AVCodecContext *avctx,
+                 const uint8_t **poutbuf, int *poutbuf_size,
+                 const uint8_t *buf, int buf_size)
+{
+    Atrac3PlusParseContext *ctx = s->priv_data;
+    const uint8_t *hdr_buf = buf;
+    size_t bytes_remain;
+    int frame_size, hdr_bytes = 8;
+    int next = 0;
+
+    if (s->flags & PARSER_FLAG_COMPLETE_FRAMES || !buf_size) {
+        next = buf_size;
+    } else {
+        if (buf_size >= 2) {
+            bytes_remain = AV_RB16(buf);
+
+            if (bytes_remain != 0xFD0) {
+                next += 2;
+                buf  += 2;
+                buf_size -= 2;
+                hdr_buf = buf;
+
+                if (bytes_remain && !ctx->pc.index && !ctx->hdr_bytes_needed) {
+                    av_log(avctx, AV_LOG_ERROR,
+                           "2nd frame portion found but the 1st one is missing!\n");
+                }
+
+                if (ctx->hdr_bytes_needed) {
+                    if (buf_size >= ctx->hdr_bytes_needed) {
+                        memcpy(&ctx->hdr[8 - ctx->hdr_bytes_needed],
+                               buf, ctx->hdr_bytes_needed);
+                        hdr_bytes = ctx->hdr_bytes_needed;
+                        ctx->hdr_bytes_needed = 0;
+                        hdr_buf = ctx->hdr;
+                    }
+                } else if (bytes_remain) {
+                    if (buf_size < bytes_remain) {
+                        av_log(avctx, AV_LOG_ERROR,
+                               "Couldn't combine frame: bytes needed=%d, bytes supplied=%d\n",
+                               bytes_remain, buf_size);
+                    }
+
+                    next += bytes_remain;
+                    ff_combine_frame(&ctx->pc, bytes_remain, &buf, &buf_size);
+
+                    *poutbuf      = buf;
+                    *poutbuf_size = buf_size;
+                    return next;
+                }
+            }
+        }
+
+        if (buf_size < hdr_bytes) {
+            /* looks like we got an incomplete header */
+            memcpy(ctx->hdr, buf, buf_size);
+            ctx->hdr_bytes_needed = 8 - buf_size;
+            *poutbuf = NULL;
+            *poutbuf_size = 0;
+            return buf_size;
+        }
+
+        if (parse_sound_frame_header(ctx, hdr_buf)) {
+            av_log(avctx, AV_LOG_ERROR, "Invalid sound frame header!\n");
+        }
+
+        avctx->sample_rate    = ctx->sample_rate;
+        avctx->block_align    = ctx->frame_size;
+        avctx->bit_rate       = ctx->sample_rate * ctx->frame_size * 8 / 2048;
+        avctx->channels       = oma_chid_to_num_channels[ctx->channel_id - 1];
+        avctx->channel_layout = oma_chid_to_native_layout[ctx->channel_id - 1];
+
+        next += hdr_bytes;
+        buf  += hdr_bytes;
+        buf_size -= hdr_bytes;
+        frame_size = ctx->frame_size;
+
+        if (buf_size < frame_size)
+            frame_size = END_NOT_FOUND;
+
+        if (ff_combine_frame(&ctx->pc, frame_size, &buf, &buf_size) < 0) {
+            *poutbuf = NULL;
+            *poutbuf_size = 0;
+            return buf_size + next;
+        }
+
+        next += frame_size;
+    }
+
+    *poutbuf      = buf;
+    *poutbuf_size = buf_size;
+    return next;
+}
+
+AVCodecParser ff_atrac3p_parser = {
+    .codec_ids      = { AV_CODEC_ID_ATRAC3P },
+    .priv_data_size = sizeof(Atrac3PlusParseContext),
+    .parser_parse   = ff_atrac3p_parse,
+    .parser_close   = ff_parse_close,
+};
diff --git a/libavformat/mpeg.c b/libavformat/mpeg.c
index 50fe7a1a76..895c6fb231 100644
--- a/libavformat/mpeg.c
+++ b/libavformat/mpeg.c
@@ -128,6 +128,7 @@  typedef struct MpegDemuxContext {
     int sofdec;
     int dvd;
     int imkh_cctv;
+    int sony_psmf; // true if Play Station Movie file signature is present
 #if CONFIG_VOBSUB_DEMUXER
     AVFormatContext *sub_ctx;
     FFDemuxSubtitlesQueue q[32];
@@ -147,6 +148,8 @@  static int mpegps_read_header(AVFormatContext *s)
     avio_get_str(s->pb, 6, buffer, sizeof(buffer));
     if (!memcmp("IMKH", buffer, 4)) {
         m->imkh_cctv = 1;
+    } else if (!memcmp("PSMF00", buffer, 6)) {
+        m->sony_psmf = 1;
     } else if (!memcmp("Sofdec", buffer, 6)) {
         m->sofdec = 1;
     } else
@@ -441,7 +444,7 @@  redo:
         goto redo;
     }
 
-    if (startcode == PRIVATE_STREAM_1) {
+    if (startcode == PRIVATE_STREAM_1 && !m->sony_psmf) {
         startcode = avio_r8(s->pb);
         len--;
     }
@@ -541,6 +544,28 @@  redo:
         else
             request_probe= 1;
         type = AVMEDIA_TYPE_VIDEO;
+    } else if (startcode == PRIVATE_STREAM_1 && m->sony_psmf) {
+        uint8_t stream_id;
+
+        if (len < 2)
+            goto skip;
+        stream_id = avio_r8(s->pb);
+        avio_r8(s->pb); // skip padding
+        len -= 2;
+        if (!(stream_id & 0xF0)) { // seems like we got an ATRAC stream
+            /* check if an appropriate stream already exists */
+            for (i = 0; i < s->nb_streams; i++) {
+                st = s->streams[i];
+                if (st->codec->codec_type == AVMEDIA_TYPE_AUDIO &&
+                    st->codec->codec_id == AV_CODEC_ID_ATRAC3P &&
+                    st->id - 0x1BD0 == (stream_id & 0xF))
+                    goto found;
+            }
+
+            startcode = 0x1BD0 + (stream_id & 0xF);
+            type      = AVMEDIA_TYPE_AUDIO;
+            codec_id  = AV_CODEC_ID_ATRAC3P;
+        }
     } else if (startcode == PRIVATE_STREAM_2) {
         type = AVMEDIA_TYPE_DATA;
         codec_id = AV_CODEC_ID_DVD_NAV;