1cabdff1aSopenharmony_ci/*
2cabdff1aSopenharmony_ci * LEGO Racers ALP (.tun & .pcm) (de)muxer
3cabdff1aSopenharmony_ci *
4cabdff1aSopenharmony_ci * Copyright (C) 2020 Zane van Iperen (zane@zanevaniperen.com)
5cabdff1aSopenharmony_ci *
6cabdff1aSopenharmony_ci * This file is part of FFmpeg.
7cabdff1aSopenharmony_ci *
8cabdff1aSopenharmony_ci * FFmpeg is free software; you can redistribute it and/or
9cabdff1aSopenharmony_ci * modify it under the terms of the GNU Lesser General Public
10cabdff1aSopenharmony_ci * License as published by the Free Software Foundation; either
11cabdff1aSopenharmony_ci * version 2.1 of the License, or (at your option) any later version.
12cabdff1aSopenharmony_ci *
13cabdff1aSopenharmony_ci * FFmpeg is distributed in the hope that it will be useful,
14cabdff1aSopenharmony_ci * but WITHOUT ANY WARRANTY; without even the implied warranty of
15cabdff1aSopenharmony_ci * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16cabdff1aSopenharmony_ci * Lesser General Public License for more details.
17cabdff1aSopenharmony_ci *
18cabdff1aSopenharmony_ci * You should have received a copy of the GNU Lesser General Public
19cabdff1aSopenharmony_ci * License along with FFmpeg; if not, write to the Free Software
20cabdff1aSopenharmony_ci * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21cabdff1aSopenharmony_ci */
22cabdff1aSopenharmony_ci
23cabdff1aSopenharmony_ci#include "config_components.h"
24cabdff1aSopenharmony_ci
25cabdff1aSopenharmony_ci#include "libavutil/channel_layout.h"
26cabdff1aSopenharmony_ci#include "avformat.h"
27cabdff1aSopenharmony_ci#include "internal.h"
28cabdff1aSopenharmony_ci#include "rawenc.h"
29cabdff1aSopenharmony_ci#include "libavutil/intreadwrite.h"
30cabdff1aSopenharmony_ci#include "libavutil/internal.h"
31cabdff1aSopenharmony_ci#include "libavutil/opt.h"
32cabdff1aSopenharmony_ci
33cabdff1aSopenharmony_ci#define ALP_TAG            MKTAG('A', 'L', 'P', ' ')
34cabdff1aSopenharmony_ci#define ALP_MAX_READ_SIZE  4096
35cabdff1aSopenharmony_ci
36cabdff1aSopenharmony_citypedef struct ALPHeader {
37cabdff1aSopenharmony_ci    uint32_t    magic;          /*< Magic Number, {'A', 'L', 'P', ' '} */
38cabdff1aSopenharmony_ci    uint32_t    header_size;    /*< Header size (after this). */
39cabdff1aSopenharmony_ci    char        adpcm[6];       /*< "ADPCM" */
40cabdff1aSopenharmony_ci    uint8_t     unk1;           /*< Unknown */
41cabdff1aSopenharmony_ci    uint8_t     num_channels;   /*< Channel Count. */
42cabdff1aSopenharmony_ci    uint32_t    sample_rate;    /*< Sample rate, only if header_size >= 12. */
43cabdff1aSopenharmony_ci} ALPHeader;
44cabdff1aSopenharmony_ci
45cabdff1aSopenharmony_citypedef enum ALPType {
46cabdff1aSopenharmony_ci    ALP_TYPE_AUTO = 0, /*< Autodetect based on file extension. */
47cabdff1aSopenharmony_ci    ALP_TYPE_TUN  = 1, /*< Force a .TUN file. */
48cabdff1aSopenharmony_ci    ALP_TYPE_PCM  = 2, /*< Force a .PCM file. */
49cabdff1aSopenharmony_ci} ALPType;
50cabdff1aSopenharmony_ci
51cabdff1aSopenharmony_citypedef struct ALPMuxContext {
52cabdff1aSopenharmony_ci    const AVClass *class;
53cabdff1aSopenharmony_ci    ALPType type;
54cabdff1aSopenharmony_ci} ALPMuxContext;
55cabdff1aSopenharmony_ci
56cabdff1aSopenharmony_ci#if CONFIG_ALP_DEMUXER
57cabdff1aSopenharmony_cistatic int alp_probe(const AVProbeData *p)
58cabdff1aSopenharmony_ci{
59cabdff1aSopenharmony_ci    uint32_t i;
60cabdff1aSopenharmony_ci
61cabdff1aSopenharmony_ci    if (AV_RL32(p->buf) != ALP_TAG)
62cabdff1aSopenharmony_ci        return 0;
63cabdff1aSopenharmony_ci
64cabdff1aSopenharmony_ci    /* Only allowed header sizes are 8 and 12. */
65cabdff1aSopenharmony_ci    i = AV_RL32(p->buf + 4);
66cabdff1aSopenharmony_ci    if (i != 8 && i != 12)
67cabdff1aSopenharmony_ci        return 0;
68cabdff1aSopenharmony_ci
69cabdff1aSopenharmony_ci    if (strncmp("ADPCM", p->buf + 8, 6) != 0)
70cabdff1aSopenharmony_ci        return 0;
71cabdff1aSopenharmony_ci
72cabdff1aSopenharmony_ci    return AVPROBE_SCORE_MAX - 1;
73cabdff1aSopenharmony_ci}
74cabdff1aSopenharmony_ci
75cabdff1aSopenharmony_cistatic int alp_read_header(AVFormatContext *s)
76cabdff1aSopenharmony_ci{
77cabdff1aSopenharmony_ci    int ret;
78cabdff1aSopenharmony_ci    AVStream *st;
79cabdff1aSopenharmony_ci    ALPHeader *hdr = s->priv_data;
80cabdff1aSopenharmony_ci    AVCodecParameters *par;
81cabdff1aSopenharmony_ci
82cabdff1aSopenharmony_ci    if ((hdr->magic = avio_rl32(s->pb)) != ALP_TAG)
83cabdff1aSopenharmony_ci        return AVERROR_INVALIDDATA;
84cabdff1aSopenharmony_ci
85cabdff1aSopenharmony_ci    hdr->header_size = avio_rl32(s->pb);
86cabdff1aSopenharmony_ci
87cabdff1aSopenharmony_ci    if (hdr->header_size != 8 && hdr->header_size != 12) {
88cabdff1aSopenharmony_ci        return AVERROR_INVALIDDATA;
89cabdff1aSopenharmony_ci    }
90cabdff1aSopenharmony_ci
91cabdff1aSopenharmony_ci    if ((ret = avio_read(s->pb, hdr->adpcm, sizeof(hdr->adpcm))) < 0)
92cabdff1aSopenharmony_ci        return ret;
93cabdff1aSopenharmony_ci    else if (ret != sizeof(hdr->adpcm))
94cabdff1aSopenharmony_ci        return AVERROR(EIO);
95cabdff1aSopenharmony_ci
96cabdff1aSopenharmony_ci    if (strncmp("ADPCM", hdr->adpcm, sizeof(hdr->adpcm)) != 0)
97cabdff1aSopenharmony_ci        return AVERROR_INVALIDDATA;
98cabdff1aSopenharmony_ci
99cabdff1aSopenharmony_ci    hdr->unk1                   = avio_r8(s->pb);
100cabdff1aSopenharmony_ci    hdr->num_channels           = avio_r8(s->pb);
101cabdff1aSopenharmony_ci
102cabdff1aSopenharmony_ci    if (hdr->header_size == 8) {
103cabdff1aSopenharmony_ci        /* .TUN music file */
104cabdff1aSopenharmony_ci        hdr->sample_rate        = 22050;
105cabdff1aSopenharmony_ci
106cabdff1aSopenharmony_ci    } else {
107cabdff1aSopenharmony_ci        /* .PCM sound file */
108cabdff1aSopenharmony_ci        hdr->sample_rate        = avio_rl32(s->pb);
109cabdff1aSopenharmony_ci    }
110cabdff1aSopenharmony_ci
111cabdff1aSopenharmony_ci    if (hdr->sample_rate > 44100) {
112cabdff1aSopenharmony_ci        avpriv_request_sample(s, "Sample Rate > 44100");
113cabdff1aSopenharmony_ci        return AVERROR_PATCHWELCOME;
114cabdff1aSopenharmony_ci    }
115cabdff1aSopenharmony_ci
116cabdff1aSopenharmony_ci    if (!(st = avformat_new_stream(s, NULL)))
117cabdff1aSopenharmony_ci        return AVERROR(ENOMEM);
118cabdff1aSopenharmony_ci
119cabdff1aSopenharmony_ci    par                         = st->codecpar;
120cabdff1aSopenharmony_ci    par->codec_type             = AVMEDIA_TYPE_AUDIO;
121cabdff1aSopenharmony_ci    par->codec_id               = AV_CODEC_ID_ADPCM_IMA_ALP;
122cabdff1aSopenharmony_ci    par->format                 = AV_SAMPLE_FMT_S16;
123cabdff1aSopenharmony_ci    par->sample_rate            = hdr->sample_rate;
124cabdff1aSopenharmony_ci
125cabdff1aSopenharmony_ci    if (hdr->num_channels > 2 || hdr->num_channels == 0)
126cabdff1aSopenharmony_ci        return AVERROR_INVALIDDATA;
127cabdff1aSopenharmony_ci
128cabdff1aSopenharmony_ci    av_channel_layout_default(&par->ch_layout, hdr->num_channels);
129cabdff1aSopenharmony_ci    par->bits_per_coded_sample  = 4;
130cabdff1aSopenharmony_ci    par->block_align            = 1;
131cabdff1aSopenharmony_ci    par->bit_rate               = par->ch_layout.nb_channels *
132cabdff1aSopenharmony_ci                                  par->sample_rate *
133cabdff1aSopenharmony_ci                                  par->bits_per_coded_sample;
134cabdff1aSopenharmony_ci
135cabdff1aSopenharmony_ci    avpriv_set_pts_info(st, 64, 1, par->sample_rate);
136cabdff1aSopenharmony_ci    return 0;
137cabdff1aSopenharmony_ci}
138cabdff1aSopenharmony_ci
139cabdff1aSopenharmony_cistatic int alp_read_packet(AVFormatContext *s, AVPacket *pkt)
140cabdff1aSopenharmony_ci{
141cabdff1aSopenharmony_ci    int ret;
142cabdff1aSopenharmony_ci    AVCodecParameters *par = s->streams[0]->codecpar;
143cabdff1aSopenharmony_ci
144cabdff1aSopenharmony_ci    if ((ret = av_get_packet(s->pb, pkt, ALP_MAX_READ_SIZE)) < 0)
145cabdff1aSopenharmony_ci        return ret;
146cabdff1aSopenharmony_ci
147cabdff1aSopenharmony_ci    pkt->flags         &= ~AV_PKT_FLAG_CORRUPT;
148cabdff1aSopenharmony_ci    pkt->stream_index   = 0;
149cabdff1aSopenharmony_ci    pkt->duration       = ret * 2 / par->ch_layout.nb_channels;
150cabdff1aSopenharmony_ci
151cabdff1aSopenharmony_ci    return 0;
152cabdff1aSopenharmony_ci}
153cabdff1aSopenharmony_ci
154cabdff1aSopenharmony_cistatic int alp_seek(AVFormatContext *s, int stream_index,
155cabdff1aSopenharmony_ci                     int64_t pts, int flags)
156cabdff1aSopenharmony_ci{
157cabdff1aSopenharmony_ci    const ALPHeader *hdr = s->priv_data;
158cabdff1aSopenharmony_ci
159cabdff1aSopenharmony_ci    if (pts != 0)
160cabdff1aSopenharmony_ci        return AVERROR(EINVAL);
161cabdff1aSopenharmony_ci
162cabdff1aSopenharmony_ci    return avio_seek(s->pb, hdr->header_size + 8, SEEK_SET);
163cabdff1aSopenharmony_ci}
164cabdff1aSopenharmony_ci
165cabdff1aSopenharmony_ciconst AVInputFormat ff_alp_demuxer = {
166cabdff1aSopenharmony_ci    .name           = "alp",
167cabdff1aSopenharmony_ci    .long_name      = NULL_IF_CONFIG_SMALL("LEGO Racers ALP"),
168cabdff1aSopenharmony_ci    .priv_data_size = sizeof(ALPHeader),
169cabdff1aSopenharmony_ci    .read_probe     = alp_probe,
170cabdff1aSopenharmony_ci    .read_header    = alp_read_header,
171cabdff1aSopenharmony_ci    .read_packet    = alp_read_packet,
172cabdff1aSopenharmony_ci    .read_seek      = alp_seek,
173cabdff1aSopenharmony_ci};
174cabdff1aSopenharmony_ci#endif
175cabdff1aSopenharmony_ci
176cabdff1aSopenharmony_ci#if CONFIG_ALP_MUXER
177cabdff1aSopenharmony_ci
178cabdff1aSopenharmony_cistatic int alp_write_init(AVFormatContext *s)
179cabdff1aSopenharmony_ci{
180cabdff1aSopenharmony_ci    ALPMuxContext *alp = s->priv_data;
181cabdff1aSopenharmony_ci    AVCodecParameters *par;
182cabdff1aSopenharmony_ci
183cabdff1aSopenharmony_ci    if (alp->type == ALP_TYPE_AUTO) {
184cabdff1aSopenharmony_ci        if (av_match_ext(s->url, "pcm"))
185cabdff1aSopenharmony_ci            alp->type = ALP_TYPE_PCM;
186cabdff1aSopenharmony_ci        else
187cabdff1aSopenharmony_ci            alp->type = ALP_TYPE_TUN;
188cabdff1aSopenharmony_ci    }
189cabdff1aSopenharmony_ci
190cabdff1aSopenharmony_ci    if (s->nb_streams != 1) {
191cabdff1aSopenharmony_ci        av_log(s, AV_LOG_ERROR, "Too many streams\n");
192cabdff1aSopenharmony_ci        return AVERROR(EINVAL);
193cabdff1aSopenharmony_ci    }
194cabdff1aSopenharmony_ci
195cabdff1aSopenharmony_ci    par = s->streams[0]->codecpar;
196cabdff1aSopenharmony_ci
197cabdff1aSopenharmony_ci    if (par->codec_id != AV_CODEC_ID_ADPCM_IMA_ALP) {
198cabdff1aSopenharmony_ci        av_log(s, AV_LOG_ERROR, "%s codec not supported\n",
199cabdff1aSopenharmony_ci               avcodec_get_name(par->codec_id));
200cabdff1aSopenharmony_ci        return AVERROR(EINVAL);
201cabdff1aSopenharmony_ci    }
202cabdff1aSopenharmony_ci
203cabdff1aSopenharmony_ci    if (par->ch_layout.nb_channels > 2) {
204cabdff1aSopenharmony_ci        av_log(s, AV_LOG_ERROR, "A maximum of 2 channels are supported\n");
205cabdff1aSopenharmony_ci        return AVERROR(EINVAL);
206cabdff1aSopenharmony_ci    }
207cabdff1aSopenharmony_ci
208cabdff1aSopenharmony_ci    if (par->sample_rate > 44100) {
209cabdff1aSopenharmony_ci        av_log(s, AV_LOG_ERROR, "Sample rate too large\n");
210cabdff1aSopenharmony_ci        return AVERROR(EINVAL);
211cabdff1aSopenharmony_ci    }
212cabdff1aSopenharmony_ci
213cabdff1aSopenharmony_ci    if (alp->type == ALP_TYPE_TUN && par->sample_rate != 22050) {
214cabdff1aSopenharmony_ci        av_log(s, AV_LOG_ERROR, "Sample rate must be 22050 for TUN files\n");
215cabdff1aSopenharmony_ci        return AVERROR(EINVAL);
216cabdff1aSopenharmony_ci    }
217cabdff1aSopenharmony_ci    return 0;
218cabdff1aSopenharmony_ci}
219cabdff1aSopenharmony_ci
220cabdff1aSopenharmony_cistatic int alp_write_header(AVFormatContext *s)
221cabdff1aSopenharmony_ci{
222cabdff1aSopenharmony_ci    ALPMuxContext *alp = s->priv_data;
223cabdff1aSopenharmony_ci    AVCodecParameters *par = s->streams[0]->codecpar;
224cabdff1aSopenharmony_ci
225cabdff1aSopenharmony_ci    avio_wl32(s->pb,  ALP_TAG);
226cabdff1aSopenharmony_ci    avio_wl32(s->pb,  alp->type == ALP_TYPE_PCM ? 12 : 8);
227cabdff1aSopenharmony_ci    avio_write(s->pb, "ADPCM", 6);
228cabdff1aSopenharmony_ci    avio_w8(s->pb,    0);
229cabdff1aSopenharmony_ci    avio_w8(s->pb,    par->ch_layout.nb_channels);
230cabdff1aSopenharmony_ci    if (alp->type == ALP_TYPE_PCM)
231cabdff1aSopenharmony_ci        avio_wl32(s->pb, par->sample_rate);
232cabdff1aSopenharmony_ci
233cabdff1aSopenharmony_ci    return 0;
234cabdff1aSopenharmony_ci}
235cabdff1aSopenharmony_ci
236cabdff1aSopenharmony_cienum { AE = AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_ENCODING_PARAM };
237cabdff1aSopenharmony_ci
238cabdff1aSopenharmony_cistatic const AVOption alp_options[] = {
239cabdff1aSopenharmony_ci    {
240cabdff1aSopenharmony_ci        .name        = "type",
241cabdff1aSopenharmony_ci        .help        = "set file type",
242cabdff1aSopenharmony_ci        .offset      = offsetof(ALPMuxContext, type),
243cabdff1aSopenharmony_ci        .type        = AV_OPT_TYPE_INT,
244cabdff1aSopenharmony_ci        .default_val = {.i64 = ALP_TYPE_AUTO},
245cabdff1aSopenharmony_ci        .min         = ALP_TYPE_AUTO,
246cabdff1aSopenharmony_ci        .max         = ALP_TYPE_PCM,
247cabdff1aSopenharmony_ci        .flags       = AE,
248cabdff1aSopenharmony_ci        .unit        = "type",
249cabdff1aSopenharmony_ci    },
250cabdff1aSopenharmony_ci    {
251cabdff1aSopenharmony_ci        .name        = "auto",
252cabdff1aSopenharmony_ci        .help        = "autodetect based on file extension",
253cabdff1aSopenharmony_ci        .offset      = 0,
254cabdff1aSopenharmony_ci        .type        = AV_OPT_TYPE_CONST,
255cabdff1aSopenharmony_ci        .default_val = {.i64 = ALP_TYPE_AUTO},
256cabdff1aSopenharmony_ci        .min         = 0,
257cabdff1aSopenharmony_ci        .max         = 0,
258cabdff1aSopenharmony_ci        .flags       = AE,
259cabdff1aSopenharmony_ci        .unit        = "type"
260cabdff1aSopenharmony_ci    },
261cabdff1aSopenharmony_ci    {
262cabdff1aSopenharmony_ci        .name        = "tun",
263cabdff1aSopenharmony_ci        .help        = "force .tun, used for music",
264cabdff1aSopenharmony_ci        .offset      = 0,
265cabdff1aSopenharmony_ci        .type        = AV_OPT_TYPE_CONST,
266cabdff1aSopenharmony_ci        .default_val = {.i64 = ALP_TYPE_TUN},
267cabdff1aSopenharmony_ci        .min         = 0,
268cabdff1aSopenharmony_ci        .max         = 0,
269cabdff1aSopenharmony_ci        .flags       = AE,
270cabdff1aSopenharmony_ci        .unit        = "type"
271cabdff1aSopenharmony_ci    },
272cabdff1aSopenharmony_ci    {
273cabdff1aSopenharmony_ci        .name        = "pcm",
274cabdff1aSopenharmony_ci        .help        = "force .pcm, used for sfx",
275cabdff1aSopenharmony_ci        .offset      = 0,
276cabdff1aSopenharmony_ci        .type        = AV_OPT_TYPE_CONST,
277cabdff1aSopenharmony_ci        .default_val = {.i64 = ALP_TYPE_PCM},
278cabdff1aSopenharmony_ci        .min         = 0,
279cabdff1aSopenharmony_ci        .max         = 0,
280cabdff1aSopenharmony_ci        .flags       = AE,
281cabdff1aSopenharmony_ci        .unit        = "type"
282cabdff1aSopenharmony_ci    },
283cabdff1aSopenharmony_ci    { NULL }
284cabdff1aSopenharmony_ci};
285cabdff1aSopenharmony_ci
286cabdff1aSopenharmony_cistatic const AVClass alp_muxer_class = {
287cabdff1aSopenharmony_ci    .class_name = "alp",
288cabdff1aSopenharmony_ci    .item_name  = av_default_item_name,
289cabdff1aSopenharmony_ci    .option     = alp_options,
290cabdff1aSopenharmony_ci    .version    = LIBAVUTIL_VERSION_INT
291cabdff1aSopenharmony_ci};
292cabdff1aSopenharmony_ci
293cabdff1aSopenharmony_ciconst AVOutputFormat ff_alp_muxer = {
294cabdff1aSopenharmony_ci    .name           = "alp",
295cabdff1aSopenharmony_ci    .long_name      = NULL_IF_CONFIG_SMALL("LEGO Racers ALP"),
296cabdff1aSopenharmony_ci    .extensions     = "tun,pcm",
297cabdff1aSopenharmony_ci    .audio_codec    = AV_CODEC_ID_ADPCM_IMA_ALP,
298cabdff1aSopenharmony_ci    .video_codec    = AV_CODEC_ID_NONE,
299cabdff1aSopenharmony_ci    .init           = alp_write_init,
300cabdff1aSopenharmony_ci    .write_header   = alp_write_header,
301cabdff1aSopenharmony_ci    .write_packet   = ff_raw_write_packet,
302cabdff1aSopenharmony_ci    .priv_class     = &alp_muxer_class,
303cabdff1aSopenharmony_ci    .priv_data_size = sizeof(ALPMuxContext)
304cabdff1aSopenharmony_ci};
305cabdff1aSopenharmony_ci#endif
306