Coverage Report

Created: 2026-01-16 07:48

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