From 1c0adade68c5106f5d0ea51ee132f9a93db68250 Mon Sep 17 00:00:00 2001
From: Paul B Mahol <onemda@gmail.com>
Date: Thu, 12 Sep 2024 20:34:22 +0200
Subject: [PATCH] avformat: add DAT demuxer

---
 libavformat/Makefile     |   1 +
 libavformat/allformats.c |   1 +
 libavformat/dat.c        | 159 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 161 insertions(+)
 create mode 100644 libavformat/dat.c

diff --git a/libavformat/Makefile b/libavformat/Makefile
index 7ca68a7036..35bf8ef16c 100644
--- a/libavformat/Makefile
+++ b/libavformat/Makefile
@@ -172,6 +172,7 @@ OBJS-$(CONFIG_CODEC2RAW_DEMUXER)         += codec2.o pcm.o
 OBJS-$(CONFIG_CODEC2RAW_MUXER)           += rawenc.o
 OBJS-$(CONFIG_CONCAT_DEMUXER)            += concatdec.o
 OBJS-$(CONFIG_CRC_MUXER)                 += crcenc.o
+OBJS-$(CONFIG_DAT_DEMUXER)               += dat.o
 OBJS-$(CONFIG_DATA_DEMUXER)              += rawdec.o
 OBJS-$(CONFIG_DATA_MUXER)                += rawenc.o
 OBJS-$(CONFIG_DASH_MUXER)                += dash.o dashenc.o hlsplaylist.o
diff --git a/libavformat/allformats.c b/libavformat/allformats.c
index 305fa46532..f321579e5e 100644
--- a/libavformat/allformats.c
+++ b/libavformat/allformats.c
@@ -128,6 +128,7 @@ extern const FFInputFormat  ff_concat_demuxer;
 extern const FFOutputFormat ff_crc_muxer;
 extern const FFInputFormat  ff_dash_demuxer;
 extern const FFOutputFormat ff_dash_muxer;
+extern const FFInputFormat  ff_dat_demuxer;
 extern const FFInputFormat  ff_data_demuxer;
 extern const FFOutputFormat ff_data_muxer;
 extern const FFInputFormat  ff_daud_demuxer;
diff --git a/libavformat/dat.c b/libavformat/dat.c
new file mode 100644
index 0000000000..37548a8a73
--- /dev/null
+++ b/libavformat/dat.c
@@ -0,0 +1,159 @@
+/*
+ * DAT (Digital Audio Tape) demuxer
+ * Copyright (c) 2024 Paul B Mahol
+ *
+ * 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 "avformat.h"
+#include "demux.h"
+#include "internal.h"
+
+#define DAT_PACKET_SIZE 5822
+#define DAT_OFFSET 5760
+
+static const uint32_t encoded_rate[] = { 48000, 44100, 32000, 0 };
+static const uint16_t encoded_size[] = { 5760, 5292, 3840, 0 };
+static const uint8_t encoded_chans[] = { 2, 4, 0, 0 };
+static const enum AVCodecID encoded_codec[] = {
+    AV_CODEC_ID_PCM_S16LE,
+    AV_CODEC_ID_NONE, AV_CODEC_ID_NONE, AV_CODEC_ID_NONE,
+};
+
+static int valid_frame(uint8_t *frame)
+{
+    uint8_t *scode = frame+DAT_OFFSET;
+    uint8_t *subid = scode+7*8;
+    uint8_t *mainid = subid+4;
+    int chan_index = (mainid[0] >> 0) & 0x3;
+    int rate_index = (mainid[0] >> 2) & 0x3;
+    int enc_index  = (mainid[1] >> 6) & 0x3;
+    int dataid     = (subid[0] >> 0) & 0xf;
+
+    if (dataid != 0 || encoded_codec[enc_index] == AV_CODEC_ID_NONE ||
+        encoded_chans[chan_index] == 0 ||
+        encoded_rate[rate_index] == 0)
+        return 0;
+
+    return 1;
+}
+
+static int read_probe(const AVProbeData *p)
+{
+    const int cnt = p->buf_size / DAT_PACKET_SIZE;
+    int score = 0;
+
+    for (int i = 0; i < cnt; i++) {
+        const int ret = valid_frame(&p->buf[i * DAT_PACKET_SIZE]);
+
+        score += ret;
+        if (ret == 0)
+            break;
+    }
+
+    return FFMIN(score, AVPROBE_SCORE_MAX);
+}
+
+static int read_header(AVFormatContext *s)
+{
+    s->ctx_flags |= AVFMTCTX_NOHEADER;
+
+    return 0;
+}
+
+static int parse_frame(uint8_t *frame, AVCodecParameters *par)
+{
+    uint8_t *scode = frame+DAT_OFFSET;
+    uint8_t *subid = scode+7*8;
+    uint8_t *mainid = subid+4;
+    int chan_index = (mainid[0] >> 0) & 0x3;
+    int rate_index = (mainid[0] >> 2) & 0x3;
+    int enc_index  = (mainid[1] >> 6) & 0x3;
+    int dataid     = (subid[0] >> 0) & 0xf;
+
+    par->codec_type = AVMEDIA_TYPE_AUDIO;
+    par->codec_id = encoded_codec[enc_index];
+    av_channel_layout_default(&par->ch_layout, encoded_chans[chan_index]);
+    par->sample_rate = encoded_rate[rate_index];
+    par->bit_rate = (8LL * DAT_PACKET_SIZE * par->sample_rate) / FFMAX(1, av_get_audio_frame_duration2(par, encoded_size[rate_index]));
+
+    if (dataid != 0 || par->codec_id == AV_CODEC_ID_NONE ||
+        par->ch_layout.nb_channels <= 0 ||
+        par->sample_rate <= 0)
+        return AVERROR_INVALIDDATA;
+
+    return encoded_size[rate_index];
+}
+
+static int read_packet(AVFormatContext *s, AVPacket *pkt)
+{
+    uint8_t data[DAT_PACKET_SIZE];
+    AVIOContext *pb = s->pb;
+    int ret, pcm_size;
+    int64_t pos;
+
+    if (avio_feof(pb))
+        return AVERROR_EOF;
+
+    pos = avio_tell(pb);
+    if (avio_read(pb, data, sizeof(data)) != sizeof(data))
+        return AVERROR_EOF;
+
+    if (s->nb_streams == 0) {
+        AVStream *st;
+
+        st = avformat_new_stream(s, NULL);
+        if (!st)
+            return AVERROR(ENOMEM);
+
+        pcm_size = parse_frame(data, st->codecpar);
+
+        avpriv_set_pts_info(st, 64, 1, st->codecpar->sample_rate);
+    } else {
+        int old_sample_rate = s->streams[0]->codecpar->sample_rate;
+
+        pcm_size = parse_frame(data, s->streams[0]->codecpar);
+
+        if (old_sample_rate != s->streams[0]->codecpar->sample_rate) {
+            ret = ff_add_param_change(pkt, 0, 0, s->streams[0]->codecpar->sample_rate, 0, 0);
+            if (ret < 0)
+                return ret;
+        }
+    }
+
+    if (pcm_size < 0)
+        return 0;
+
+    if ((ret = av_new_packet(pkt, pcm_size)) < 0)
+        return ret;
+    memcpy(pkt->data, data, pcm_size);
+
+    pkt->stream_index = 0;
+    pkt->pos = pos;
+
+    return 0;
+}
+
+const FFInputFormat ff_dat_demuxer = {
+    .p.name         = "dat",
+    .p.long_name    = NULL_IF_CONFIG_SMALL("DAT (Digital Audio Tape)"),
+    .p.flags        = AVFMT_GENERIC_INDEX,
+    .p.extensions   = "dat",
+    .read_probe     = read_probe,
+    .read_header    = read_header,
+    .read_packet    = read_packet,
+};
-- 
2.46.0.windows.1

