1cabdff1aSopenharmony_ci/*
2cabdff1aSopenharmony_ci * Tracked MOD demuxer (libopenmpt)
3cabdff1aSopenharmony_ci * Copyright (c) 2016 Josh de Kock
4cabdff1aSopenharmony_ci *
5cabdff1aSopenharmony_ci * This file is part of FFmpeg.
6cabdff1aSopenharmony_ci *
7cabdff1aSopenharmony_ci * FFmpeg is free software; you can redistribute it and/or
8cabdff1aSopenharmony_ci * modify it under the terms of the GNU Lesser General Public
9cabdff1aSopenharmony_ci * License as published by the Free Software Foundation; either
10cabdff1aSopenharmony_ci * version 2.1 of the License, or (at your option) any later version.
11cabdff1aSopenharmony_ci *
12cabdff1aSopenharmony_ci * FFmpeg is distributed in the hope that it will be useful,
13cabdff1aSopenharmony_ci * but WITHOUT ANY WARRANTY; without even the implied warranty of
14cabdff1aSopenharmony_ci * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15cabdff1aSopenharmony_ci * Lesser General Public License for more details.
16cabdff1aSopenharmony_ci *
17cabdff1aSopenharmony_ci * You should have received a copy of the GNU Lesser General Public
18cabdff1aSopenharmony_ci * License along with FFmpeg; if not, write to the Free Software
19cabdff1aSopenharmony_ci * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20cabdff1aSopenharmony_ci */
21cabdff1aSopenharmony_ci
22cabdff1aSopenharmony_ci#include <libopenmpt/libopenmpt.h>
23cabdff1aSopenharmony_ci#include <libopenmpt/libopenmpt_stream_callbacks_file.h>
24cabdff1aSopenharmony_ci#include <libopenmpt/libopenmpt_version.h>
25cabdff1aSopenharmony_ci/* Shims to support libopenmpt < 0.3.0 (as documented by libopenmpt) */
26cabdff1aSopenharmony_ci#if !defined(OPENMPT_API_VERSION_MAKE)
27cabdff1aSopenharmony_ci#define OPENMPT_API_VERSION_MAKE(major, minor, patch) (((major)<<24)|((minor)<<16)|((patch)<<0))
28cabdff1aSopenharmony_ci#endif
29cabdff1aSopenharmony_ci#if !defined(OPENMPT_API_VERSION_AT_LEAST)
30cabdff1aSopenharmony_ci#define OPENMPT_API_VERSION_AT_LEAST(major, minor, patch) (OPENMPT_API_VERSION >= OPENMPT_API_VERSION_MAKE((major), (minor), (patch)))
31cabdff1aSopenharmony_ci#endif
32cabdff1aSopenharmony_ci
33cabdff1aSopenharmony_ci#include "libavutil/avstring.h"
34cabdff1aSopenharmony_ci#include "libavutil/channel_layout.h"
35cabdff1aSopenharmony_ci#include "libavutil/opt.h"
36cabdff1aSopenharmony_ci#include "avformat.h"
37cabdff1aSopenharmony_ci#include "internal.h"
38cabdff1aSopenharmony_ci
39cabdff1aSopenharmony_citypedef struct OpenMPTContext {
40cabdff1aSopenharmony_ci    const AVClass *class;
41cabdff1aSopenharmony_ci    openmpt_module *module;
42cabdff1aSopenharmony_ci
43cabdff1aSopenharmony_ci    double duration;
44cabdff1aSopenharmony_ci    /* options */
45cabdff1aSopenharmony_ci    int sample_rate;
46cabdff1aSopenharmony_ci    AVChannelLayout ch_layout;
47cabdff1aSopenharmony_ci    int subsong;
48cabdff1aSopenharmony_ci} OpenMPTContext;
49cabdff1aSopenharmony_ci
50cabdff1aSopenharmony_ci#define OFFSET(x) offsetof(OpenMPTContext, x)
51cabdff1aSopenharmony_ci#define A AV_OPT_FLAG_AUDIO_PARAM
52cabdff1aSopenharmony_ci#define D AV_OPT_FLAG_DECODING_PARAM
53cabdff1aSopenharmony_cistatic const AVOption options[] = {
54cabdff1aSopenharmony_ci    { "sample_rate", "set sample rate",    OFFSET(sample_rate), AV_OPT_TYPE_INT,            { .i64 = 48000 },               1000, INT_MAX,   A | D },
55cabdff1aSopenharmony_ci    { "layout",      "set channel layout", OFFSET(ch_layout),   AV_OPT_TYPE_CHLAYOUT,       { .str = "stereo" },            0,    0,         A | D },
56cabdff1aSopenharmony_ci    { "subsong",     "set subsong",        OFFSET(subsong),     AV_OPT_TYPE_INT,            { .i64 = -2 },                  -2,   INT_MAX,   A | D, "subsong"},
57cabdff1aSopenharmony_ci    { "all",         "all",                0,                   AV_OPT_TYPE_CONST,          { .i64 = -1},                   0,    0,         A | D, "subsong" },
58cabdff1aSopenharmony_ci    { "auto",        "auto",               0,                   AV_OPT_TYPE_CONST,          { .i64 = -2},                   0,    0,         A | D, "subsong" },
59cabdff1aSopenharmony_ci    { NULL }
60cabdff1aSopenharmony_ci};
61cabdff1aSopenharmony_ci
62cabdff1aSopenharmony_cistatic void openmpt_logfunc(const char *message, void *userdata)
63cabdff1aSopenharmony_ci{
64cabdff1aSopenharmony_ci    int level = AV_LOG_INFO;
65cabdff1aSopenharmony_ci    if (strstr(message, "ERROR") != NULL) {
66cabdff1aSopenharmony_ci        level = AV_LOG_ERROR;
67cabdff1aSopenharmony_ci    }
68cabdff1aSopenharmony_ci    av_log(userdata, level, "%s\n", message);
69cabdff1aSopenharmony_ci}
70cabdff1aSopenharmony_ci
71cabdff1aSopenharmony_ci#define add_meta(s, name, meta)                    \
72cabdff1aSopenharmony_cido {                                               \
73cabdff1aSopenharmony_ci    const char *value = meta;                      \
74cabdff1aSopenharmony_ci    if (value && value[0])                         \
75cabdff1aSopenharmony_ci        av_dict_set(&s->metadata, name, value, 0); \
76cabdff1aSopenharmony_ci    openmpt_free_string(value);                    \
77cabdff1aSopenharmony_ci} while(0)
78cabdff1aSopenharmony_ci
79cabdff1aSopenharmony_cistatic int read_header_openmpt(AVFormatContext *s)
80cabdff1aSopenharmony_ci{
81cabdff1aSopenharmony_ci    AVStream *st;
82cabdff1aSopenharmony_ci    OpenMPTContext *openmpt = s->priv_data;
83cabdff1aSopenharmony_ci    int64_t size;
84cabdff1aSopenharmony_ci    char *buf;
85cabdff1aSopenharmony_ci#if OPENMPT_API_VERSION_AT_LEAST(0,3,0)
86cabdff1aSopenharmony_ci    int error;
87cabdff1aSopenharmony_ci#endif
88cabdff1aSopenharmony_ci    int ret;
89cabdff1aSopenharmony_ci
90cabdff1aSopenharmony_ci    size = avio_size(s->pb);
91cabdff1aSopenharmony_ci    if (size <= 0)
92cabdff1aSopenharmony_ci        return AVERROR_INVALIDDATA;
93cabdff1aSopenharmony_ci    buf = av_malloc(size);
94cabdff1aSopenharmony_ci    if (!buf)
95cabdff1aSopenharmony_ci        return AVERROR(ENOMEM);
96cabdff1aSopenharmony_ci    size = avio_read(s->pb, buf, size);
97cabdff1aSopenharmony_ci    if (size < 0) {
98cabdff1aSopenharmony_ci        av_log(s, AV_LOG_ERROR, "Reading input buffer failed.\n");
99cabdff1aSopenharmony_ci        av_freep(&buf);
100cabdff1aSopenharmony_ci        return size;
101cabdff1aSopenharmony_ci    }
102cabdff1aSopenharmony_ci
103cabdff1aSopenharmony_ci#if OPENMPT_API_VERSION_AT_LEAST(0,3,0)
104cabdff1aSopenharmony_ci    error = OPENMPT_ERROR_OK;
105cabdff1aSopenharmony_ci    openmpt->module = openmpt_module_create_from_memory2(buf, size, openmpt_logfunc, s, NULL, NULL, &error, NULL, NULL);
106cabdff1aSopenharmony_ci    av_freep(&buf);
107cabdff1aSopenharmony_ci    if (!openmpt->module) {
108cabdff1aSopenharmony_ci        if (error == OPENMPT_ERROR_OUT_OF_MEMORY)
109cabdff1aSopenharmony_ci            return AVERROR(ENOMEM);
110cabdff1aSopenharmony_ci        else if (error >= OPENMPT_ERROR_GENERAL)
111cabdff1aSopenharmony_ci            return AVERROR_INVALIDDATA;
112cabdff1aSopenharmony_ci        else
113cabdff1aSopenharmony_ci            return AVERROR_UNKNOWN;
114cabdff1aSopenharmony_ci    }
115cabdff1aSopenharmony_ci#else
116cabdff1aSopenharmony_ci    openmpt->module = openmpt_module_create_from_memory(buf, size, openmpt_logfunc, s, NULL);
117cabdff1aSopenharmony_ci    av_freep(&buf);
118cabdff1aSopenharmony_ci    if (!openmpt->module)
119cabdff1aSopenharmony_ci            return AVERROR_INVALIDDATA;
120cabdff1aSopenharmony_ci#endif
121cabdff1aSopenharmony_ci
122cabdff1aSopenharmony_ci    if (openmpt->subsong >= openmpt_module_get_num_subsongs(openmpt->module)) {
123cabdff1aSopenharmony_ci        av_log(s, AV_LOG_ERROR, "Invalid subsong index: %d\n", openmpt->subsong);
124cabdff1aSopenharmony_ci        return AVERROR(EINVAL);
125cabdff1aSopenharmony_ci    }
126cabdff1aSopenharmony_ci
127cabdff1aSopenharmony_ci    if (openmpt->subsong != -2) {
128cabdff1aSopenharmony_ci        if (openmpt->subsong >= 0) {
129cabdff1aSopenharmony_ci            av_dict_set_int(&s->metadata, "track", openmpt->subsong + 1, 0);
130cabdff1aSopenharmony_ci        }
131cabdff1aSopenharmony_ci        ret = openmpt_module_select_subsong(openmpt->module, openmpt->subsong);
132cabdff1aSopenharmony_ci        if (!ret){
133cabdff1aSopenharmony_ci            av_log(s, AV_LOG_ERROR, "Could not select requested subsong: %d", openmpt->subsong);
134cabdff1aSopenharmony_ci            return AVERROR(EINVAL);
135cabdff1aSopenharmony_ci        }
136cabdff1aSopenharmony_ci    }
137cabdff1aSopenharmony_ci
138cabdff1aSopenharmony_ci    openmpt->duration = openmpt_module_get_duration_seconds(openmpt->module);
139cabdff1aSopenharmony_ci
140cabdff1aSopenharmony_ci    add_meta(s, "artist",  openmpt_module_get_metadata(openmpt->module, "artist"));
141cabdff1aSopenharmony_ci    add_meta(s, "title",   openmpt_module_get_metadata(openmpt->module, "title"));
142cabdff1aSopenharmony_ci    add_meta(s, "encoder", openmpt_module_get_metadata(openmpt->module, "tracker"));
143cabdff1aSopenharmony_ci    add_meta(s, "comment", openmpt_module_get_metadata(openmpt->module, "message"));
144cabdff1aSopenharmony_ci    add_meta(s, "date",    openmpt_module_get_metadata(openmpt->module, "date"));
145cabdff1aSopenharmony_ci
146cabdff1aSopenharmony_ci    st = avformat_new_stream(s, NULL);
147cabdff1aSopenharmony_ci    if (!st)
148cabdff1aSopenharmony_ci        return AVERROR(ENOMEM);
149cabdff1aSopenharmony_ci    avpriv_set_pts_info(st, 64, 1, AV_TIME_BASE);
150cabdff1aSopenharmony_ci    st->duration = llrint(openmpt->duration*AV_TIME_BASE);
151cabdff1aSopenharmony_ci
152cabdff1aSopenharmony_ci    st->codecpar->codec_type  = AVMEDIA_TYPE_AUDIO;
153cabdff1aSopenharmony_ci    st->codecpar->codec_id    = AV_NE(AV_CODEC_ID_PCM_F32BE, AV_CODEC_ID_PCM_F32LE);
154cabdff1aSopenharmony_ci    st->codecpar->sample_rate = openmpt->sample_rate;
155cabdff1aSopenharmony_ci    ret = av_channel_layout_copy(&st->codecpar->ch_layout, &openmpt->ch_layout);
156cabdff1aSopenharmony_ci    if (ret < 0)
157cabdff1aSopenharmony_ci        return ret;
158cabdff1aSopenharmony_ci
159cabdff1aSopenharmony_ci    return 0;
160cabdff1aSopenharmony_ci}
161cabdff1aSopenharmony_ci
162cabdff1aSopenharmony_ci#define AUDIO_PKT_SIZE 2048
163cabdff1aSopenharmony_ci
164cabdff1aSopenharmony_cistatic int read_packet_openmpt(AVFormatContext *s, AVPacket *pkt)
165cabdff1aSopenharmony_ci{
166cabdff1aSopenharmony_ci    OpenMPTContext *openmpt = s->priv_data;
167cabdff1aSopenharmony_ci    int n_samples = AUDIO_PKT_SIZE / (openmpt->ch_layout.nb_channels ? openmpt->ch_layout.nb_channels*4 : 4);
168cabdff1aSopenharmony_ci    int ret;
169cabdff1aSopenharmony_ci
170cabdff1aSopenharmony_ci    if ((ret = av_new_packet(pkt, AUDIO_PKT_SIZE)) < 0)
171cabdff1aSopenharmony_ci        return ret;
172cabdff1aSopenharmony_ci
173cabdff1aSopenharmony_ci    switch (openmpt->ch_layout.nb_channels) {
174cabdff1aSopenharmony_ci    case 1:
175cabdff1aSopenharmony_ci        ret = openmpt_module_read_float_mono(openmpt->module, openmpt->sample_rate,
176cabdff1aSopenharmony_ci                                             n_samples, (float *)pkt->data);
177cabdff1aSopenharmony_ci        break;
178cabdff1aSopenharmony_ci    case 2:
179cabdff1aSopenharmony_ci        ret = openmpt_module_read_interleaved_float_stereo(openmpt->module, openmpt->sample_rate,
180cabdff1aSopenharmony_ci                                                           n_samples, (float *)pkt->data);
181cabdff1aSopenharmony_ci        break;
182cabdff1aSopenharmony_ci    case 4:
183cabdff1aSopenharmony_ci        ret = openmpt_module_read_interleaved_float_quad(openmpt->module, openmpt->sample_rate,
184cabdff1aSopenharmony_ci                                                         n_samples, (float *)pkt->data);
185cabdff1aSopenharmony_ci        break;
186cabdff1aSopenharmony_ci    default:
187cabdff1aSopenharmony_ci        av_log(s, AV_LOG_ERROR, "Unsupported number of channels: %d", openmpt->ch_layout.nb_channels);
188cabdff1aSopenharmony_ci        return AVERROR(EINVAL);
189cabdff1aSopenharmony_ci    }
190cabdff1aSopenharmony_ci
191cabdff1aSopenharmony_ci    if (ret < 1) {
192cabdff1aSopenharmony_ci        pkt->size = 0;
193cabdff1aSopenharmony_ci        return AVERROR_EOF;
194cabdff1aSopenharmony_ci    }
195cabdff1aSopenharmony_ci
196cabdff1aSopenharmony_ci    pkt->size = ret * (openmpt->ch_layout.nb_channels * 4);
197cabdff1aSopenharmony_ci
198cabdff1aSopenharmony_ci    return 0;
199cabdff1aSopenharmony_ci}
200cabdff1aSopenharmony_ci
201cabdff1aSopenharmony_cistatic int read_close_openmpt(AVFormatContext *s)
202cabdff1aSopenharmony_ci{
203cabdff1aSopenharmony_ci    OpenMPTContext *openmpt = s->priv_data;
204cabdff1aSopenharmony_ci    if (openmpt->module) {
205cabdff1aSopenharmony_ci        openmpt_module_destroy(openmpt->module);
206cabdff1aSopenharmony_ci        openmpt->module = NULL;
207cabdff1aSopenharmony_ci    }
208cabdff1aSopenharmony_ci    return 0;
209cabdff1aSopenharmony_ci}
210cabdff1aSopenharmony_ci
211cabdff1aSopenharmony_cistatic int read_seek_openmpt(AVFormatContext *s, int stream_idx, int64_t ts, int flags)
212cabdff1aSopenharmony_ci{
213cabdff1aSopenharmony_ci    OpenMPTContext *openmpt = s->priv_data;
214cabdff1aSopenharmony_ci    openmpt_module_set_position_seconds(openmpt->module, (double)ts/AV_TIME_BASE);
215cabdff1aSopenharmony_ci    return 0;
216cabdff1aSopenharmony_ci}
217cabdff1aSopenharmony_ci
218cabdff1aSopenharmony_cistatic int probe_openmpt_extension(const AVProbeData *p)
219cabdff1aSopenharmony_ci{
220cabdff1aSopenharmony_ci    const char *ext;
221cabdff1aSopenharmony_ci    if (p->filename) {
222cabdff1aSopenharmony_ci        ext = strrchr(p->filename, '.');
223cabdff1aSopenharmony_ci        if (ext && strlen(ext + 1) > 0) {
224cabdff1aSopenharmony_ci            ext++;  /* skip '.' */
225cabdff1aSopenharmony_ci            if (openmpt_is_extension_supported(ext) == 1)
226cabdff1aSopenharmony_ci                return AVPROBE_SCORE_EXTENSION;
227cabdff1aSopenharmony_ci        }
228cabdff1aSopenharmony_ci    }
229cabdff1aSopenharmony_ci    return 0;
230cabdff1aSopenharmony_ci}
231cabdff1aSopenharmony_ci
232cabdff1aSopenharmony_cistatic int read_probe_openmpt(const AVProbeData *p)
233cabdff1aSopenharmony_ci{
234cabdff1aSopenharmony_ci#if OPENMPT_API_VERSION_AT_LEAST(0,3,0)
235cabdff1aSopenharmony_ci    int probe_result;
236cabdff1aSopenharmony_ci    if (p->buf && p->buf_size > 0) {
237cabdff1aSopenharmony_ci        probe_result = openmpt_probe_file_header_without_filesize(
238cabdff1aSopenharmony_ci                           OPENMPT_PROBE_FILE_HEADER_FLAGS_DEFAULT,
239cabdff1aSopenharmony_ci                           p->buf, p->buf_size,
240cabdff1aSopenharmony_ci                           &openmpt_logfunc, NULL, NULL, NULL, NULL, NULL);
241cabdff1aSopenharmony_ci        if (probe_result == OPENMPT_PROBE_FILE_HEADER_RESULT_SUCCESS) {
242cabdff1aSopenharmony_ci            /* As probing here relies on code external to FFmpeg, do not return
243cabdff1aSopenharmony_ci             * AVPROBE_SCORE_MAX in order to reduce the impact in the rare
244cabdff1aSopenharmony_ci             * cases of false positives.
245cabdff1aSopenharmony_ci             */
246cabdff1aSopenharmony_ci            return AVPROBE_SCORE_MIME + 1;
247cabdff1aSopenharmony_ci        } else if (probe_result == OPENMPT_PROBE_FILE_HEADER_RESULT_WANTMOREDATA) {
248cabdff1aSopenharmony_ci            if (probe_openmpt_extension(p) > 0) {
249cabdff1aSopenharmony_ci                return AVPROBE_SCORE_RETRY;
250cabdff1aSopenharmony_ci            } else {
251cabdff1aSopenharmony_ci                if (p->buf_size >= openmpt_probe_file_header_get_recommended_size()) {
252cabdff1aSopenharmony_ci                    /* We have already received the recommended amount of data
253cabdff1aSopenharmony_ci                     * and still cannot decide. Return a rather low score.
254cabdff1aSopenharmony_ci                     */
255cabdff1aSopenharmony_ci                    return AVPROBE_SCORE_RETRY / 2;
256cabdff1aSopenharmony_ci                } else {
257cabdff1aSopenharmony_ci                    /* The file extension is unknown and we have very few data
258cabdff1aSopenharmony_ci                     * bytes available. libopenmpt cannot decide anything here,
259cabdff1aSopenharmony_ci                     * and returning any score > 0 would result in successful
260cabdff1aSopenharmony_ci                     * probing of random data.
261cabdff1aSopenharmony_ci                     */
262cabdff1aSopenharmony_ci                    return 0;
263cabdff1aSopenharmony_ci                }
264cabdff1aSopenharmony_ci            }
265cabdff1aSopenharmony_ci        } else if (probe_result == OPENMPT_PROBE_FILE_HEADER_RESULT_FAILURE) {
266cabdff1aSopenharmony_ci            return 0;
267cabdff1aSopenharmony_ci        }
268cabdff1aSopenharmony_ci    }
269cabdff1aSopenharmony_ci#endif
270cabdff1aSopenharmony_ci    /* for older libopenmpt, fall back to file extension probing */
271cabdff1aSopenharmony_ci    return probe_openmpt_extension(p);
272cabdff1aSopenharmony_ci}
273cabdff1aSopenharmony_ci
274cabdff1aSopenharmony_cistatic const AVClass class_openmpt = {
275cabdff1aSopenharmony_ci    .class_name = "libopenmpt",
276cabdff1aSopenharmony_ci    .item_name  = av_default_item_name,
277cabdff1aSopenharmony_ci    .option     = options,
278cabdff1aSopenharmony_ci    .version    = LIBAVUTIL_VERSION_INT,
279cabdff1aSopenharmony_ci};
280cabdff1aSopenharmony_ci
281cabdff1aSopenharmony_ciconst AVInputFormat ff_libopenmpt_demuxer = {
282cabdff1aSopenharmony_ci    .name           = "libopenmpt",
283cabdff1aSopenharmony_ci    .long_name      = NULL_IF_CONFIG_SMALL("Tracker formats (libopenmpt)"),
284cabdff1aSopenharmony_ci    .priv_data_size = sizeof(OpenMPTContext),
285cabdff1aSopenharmony_ci    .flags_internal = FF_FMT_INIT_CLEANUP,
286cabdff1aSopenharmony_ci    .read_probe     = read_probe_openmpt,
287cabdff1aSopenharmony_ci    .read_header    = read_header_openmpt,
288cabdff1aSopenharmony_ci    .read_packet    = read_packet_openmpt,
289cabdff1aSopenharmony_ci    .read_close     = read_close_openmpt,
290cabdff1aSopenharmony_ci    .read_seek      = read_seek_openmpt,
291cabdff1aSopenharmony_ci    .priv_class     = &class_openmpt,
292cabdff1aSopenharmony_ci#if OPENMPT_API_VERSION_AT_LEAST(0,3,0)
293cabdff1aSopenharmony_ci    .extensions     = "669,amf,ams,dbm,digi,dmf,dsm,dtm,far,gdm,ice,imf,it,j2b,m15,mdl,med,mmcmp,mms,mo3,mod,mptm,mt2,mtm,nst,okt,plm,ppm,psm,pt36,ptm,s3m,sfx,sfx2,st26,stk,stm,stp,ult,umx,wow,xm,xpk",
294cabdff1aSopenharmony_ci#else
295cabdff1aSopenharmony_ci    .extensions     = "669,amf,ams,dbm,digi,dmf,dsm,far,gdm,ice,imf,it,j2b,m15,mdl,med,mmcmp,mms,mo3,mod,mptm,mt2,mtm,nst,okt,plm,ppm,psm,pt36,ptm,s3m,sfx,sfx2,st26,stk,stm,ult,umx,wow,xm,xpk",
296cabdff1aSopenharmony_ci#endif
297cabdff1aSopenharmony_ci};
298