/src/ffmpeg/libavformat/apm.c
Line | Count | Source |
1 | | /* |
2 | | * Rayman 2 APM (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 "avformat.h" |
26 | | #include "avio_internal.h" |
27 | | #include "demux.h" |
28 | | #include "internal.h" |
29 | | #include "mux.h" |
30 | | #include "rawenc.h" |
31 | | #include "libavutil/channel_layout.h" |
32 | | #include "libavutil/internal.h" |
33 | | #include "libavutil/intreadwrite.h" |
34 | | |
35 | | #define APM_FILE_HEADER_SIZE 20 |
36 | 1.77k | #define APM_FILE_EXTRADATA_SIZE 80 |
37 | 1.28k | #define APM_EXTRADATA_SIZE 28 |
38 | | |
39 | 11.1k | #define APM_MAX_READ_SIZE 4096 |
40 | | |
41 | 964k | #define APM_TAG_CODEC 0x2000 |
42 | 2.05k | #define APM_TAG_VS12 MKTAG('v', 's', '1', '2') |
43 | 790 | #define APM_TAG_DATA MKTAG('D', 'A', 'T', 'A') |
44 | | |
45 | | typedef struct APMState { |
46 | | int32_t has_saved; |
47 | | int32_t predictor_r; |
48 | | int32_t step_index_r; |
49 | | int32_t saved_r; |
50 | | int32_t predictor_l; |
51 | | int32_t step_index_l; |
52 | | int32_t saved_l; |
53 | | } APMState; |
54 | | |
55 | | typedef struct APMExtraData { |
56 | | uint32_t magic; |
57 | | uint32_t file_size; |
58 | | uint32_t data_size; |
59 | | uint32_t unk1; |
60 | | uint32_t unk2; |
61 | | APMState state; |
62 | | uint32_t unk3[7]; |
63 | | uint32_t data; |
64 | | } APMExtraData; |
65 | | |
66 | | #if CONFIG_APM_DEMUXER |
67 | | static void apm_parse_extradata(APMExtraData *ext, const uint8_t *buf) |
68 | 776 | { |
69 | 776 | ext->magic = AV_RL32(buf + 0); |
70 | 776 | ext->file_size = AV_RL32(buf + 4); |
71 | 776 | ext->data_size = AV_RL32(buf + 8); |
72 | 776 | ext->unk1 = AV_RL32(buf + 12); |
73 | 776 | ext->unk2 = AV_RL32(buf + 16); |
74 | | |
75 | 776 | ext->state.has_saved = AV_RL32(buf + 20); |
76 | 776 | ext->state.predictor_r = AV_RL32(buf + 24); |
77 | 776 | ext->state.step_index_r = AV_RL32(buf + 28); |
78 | 776 | ext->state.saved_r = AV_RL32(buf + 32); |
79 | 776 | ext->state.predictor_l = AV_RL32(buf + 36); |
80 | 776 | ext->state.step_index_l = AV_RL32(buf + 40); |
81 | 776 | ext->state.saved_l = AV_RL32(buf + 44); |
82 | | |
83 | 6.20k | for (int i = 0; i < FF_ARRAY_ELEMS(ext->unk3); i++) |
84 | 5.43k | ext->unk3[i] = AV_RL32(buf + 48 + (i * 4)); |
85 | | |
86 | 776 | ext->data = AV_RL32(buf + 76); |
87 | 776 | } |
88 | | |
89 | | static int apm_probe(const AVProbeData *p) |
90 | 963k | { |
91 | 963k | if (AV_RL16(p->buf) != APM_TAG_CODEC) |
92 | 961k | return 0; |
93 | | |
94 | 2.11k | if (p->buf_size < 100) |
95 | 836 | return 0; |
96 | | |
97 | 1.28k | if (AV_RL32(p->buf + 20) != APM_TAG_VS12) |
98 | 1.22k | return 0; |
99 | | |
100 | 60 | if (AV_RL32(p->buf + 96) != APM_TAG_DATA) |
101 | 27 | return 0; |
102 | | |
103 | 33 | return AVPROBE_SCORE_MAX - 1; |
104 | 60 | } |
105 | | |
106 | | static int apm_read_header(AVFormatContext *s) |
107 | 1.12k | { |
108 | 1.12k | int64_t ret; |
109 | 1.12k | AVStream *st; |
110 | 1.12k | APMExtraData extradata; |
111 | 1.12k | AVCodecParameters *par; |
112 | 1.12k | uint8_t buf[APM_FILE_EXTRADATA_SIZE]; |
113 | 1.12k | int channels; |
114 | | |
115 | 1.12k | if (!(st = avformat_new_stream(s, NULL))) |
116 | 0 | return AVERROR(ENOMEM); |
117 | | |
118 | | /* |
119 | | * This is 98% a WAVEFORMATEX, but there's something screwy with the extradata |
120 | | * that ff_get_wav_header() can't (and shouldn't) handle properly. |
121 | | */ |
122 | 1.12k | if (avio_rl16(s->pb) != APM_TAG_CODEC) |
123 | 144 | return AVERROR_INVALIDDATA; |
124 | | |
125 | 982 | par = st->codecpar; |
126 | 982 | channels = avio_rl16(s->pb); |
127 | 982 | par->sample_rate = avio_rl32(s->pb); |
128 | | |
129 | | /* Skip the bitrate, it's usually wrong anyway. */ |
130 | 982 | if ((ret = avio_skip(s->pb, 4)) < 0) |
131 | 28 | return ret; |
132 | | |
133 | 954 | par->block_align = avio_rl16(s->pb); |
134 | 954 | par->bits_per_coded_sample = avio_rl16(s->pb); |
135 | | |
136 | 954 | if (avio_rl32(s->pb) != APM_FILE_EXTRADATA_SIZE) |
137 | 78 | return AVERROR_INVALIDDATA; |
138 | | |
139 | | /* 8 = bits per sample * max channels */ |
140 | 876 | if (par->sample_rate > (INT_MAX / 8)) |
141 | 3 | return AVERROR_INVALIDDATA; |
142 | | |
143 | 873 | if (par->bits_per_coded_sample != 4) |
144 | 23 | return AVERROR_INVALIDDATA; |
145 | | |
146 | 850 | if (channels > 2 || channels == 0) |
147 | 27 | return AVERROR_INVALIDDATA; |
148 | | |
149 | 823 | av_channel_layout_default(&par->ch_layout, channels); |
150 | 823 | par->codec_type = AVMEDIA_TYPE_AUDIO; |
151 | 823 | par->codec_id = AV_CODEC_ID_ADPCM_IMA_APM; |
152 | 823 | par->format = AV_SAMPLE_FMT_S16; |
153 | 823 | par->bit_rate = par->ch_layout.nb_channels * |
154 | 823 | (int64_t)par->sample_rate * |
155 | 823 | par->bits_per_coded_sample; |
156 | | |
157 | 823 | if ((ret = ffio_read_size(s->pb, buf, APM_FILE_EXTRADATA_SIZE)) < 0) |
158 | 47 | return ret; |
159 | | |
160 | 776 | apm_parse_extradata(&extradata, buf); |
161 | | |
162 | 776 | if (extradata.magic != APM_TAG_VS12 || extradata.data != APM_TAG_DATA) |
163 | 94 | return AVERROR_INVALIDDATA; |
164 | | |
165 | 682 | if (extradata.state.has_saved) { |
166 | 41 | avpriv_request_sample(s, "Saved Samples"); |
167 | 41 | return AVERROR_PATCHWELCOME; |
168 | 41 | } |
169 | | |
170 | 641 | if ((ret = ff_alloc_extradata(par, APM_EXTRADATA_SIZE)) < 0) |
171 | 0 | return ret; |
172 | | |
173 | | /* Use the entire state as extradata. */ |
174 | 641 | memcpy(par->extradata, buf + 20, APM_EXTRADATA_SIZE); |
175 | | |
176 | 641 | avpriv_set_pts_info(st, 64, 1, par->sample_rate); |
177 | 641 | st->start_time = 0; |
178 | 641 | st->duration = extradata.data_size * |
179 | 641 | (8 / par->bits_per_coded_sample) / |
180 | 641 | par->ch_layout.nb_channels; |
181 | 641 | return 0; |
182 | 641 | } |
183 | | |
184 | | static int apm_read_packet(AVFormatContext *s, AVPacket *pkt) |
185 | 11.1k | { |
186 | 11.1k | int ret; |
187 | 11.1k | AVCodecParameters *par = s->streams[0]->codecpar; |
188 | | |
189 | | /* |
190 | | * For future reference: if files with the `has_saved` field set ever |
191 | | * surface, `saved_l`, and `saved_r` will each contain 8 "saved" samples |
192 | | * that should be sent to the decoder before the actual data. |
193 | | */ |
194 | | |
195 | 11.1k | if ((ret = av_get_packet(s->pb, pkt, APM_MAX_READ_SIZE)) < 0) |
196 | 1.09k | return ret; |
197 | | |
198 | 10.0k | pkt->flags &= ~AV_PKT_FLAG_CORRUPT; |
199 | 10.0k | pkt->stream_index = 0; |
200 | 10.0k | pkt->duration = ret * (8 / par->bits_per_coded_sample) / par->ch_layout.nb_channels; |
201 | | |
202 | 10.0k | return 0; |
203 | 11.1k | } |
204 | | |
205 | | const FFInputFormat ff_apm_demuxer = { |
206 | | .p.name = "apm", |
207 | | .p.long_name = NULL_IF_CONFIG_SMALL("Ubisoft Rayman 2 APM"), |
208 | | .read_probe = apm_probe, |
209 | | .read_header = apm_read_header, |
210 | | .read_packet = apm_read_packet |
211 | | }; |
212 | | #endif |
213 | | |
214 | | #if CONFIG_APM_MUXER |
215 | | static int apm_write_init(AVFormatContext *s) |
216 | | { |
217 | | AVCodecParameters *par = s->streams[0]->codecpar; |
218 | | |
219 | | if (par->ch_layout.nb_channels > 2) { |
220 | | av_log(s, AV_LOG_ERROR, "APM files only support up to 2 channels\n"); |
221 | | return AVERROR(EINVAL); |
222 | | } |
223 | | |
224 | | if (par->sample_rate > (INT_MAX / 8)) { |
225 | | av_log(s, AV_LOG_ERROR, "Sample rate too large\n"); |
226 | | return AVERROR(EINVAL); |
227 | | } |
228 | | |
229 | | if (par->extradata_size != APM_EXTRADATA_SIZE) { |
230 | | av_log(s, AV_LOG_ERROR, "Invalid/missing extradata\n"); |
231 | | return AVERROR(EINVAL); |
232 | | } |
233 | | |
234 | | if (!(s->pb->seekable & AVIO_SEEKABLE_NORMAL)) { |
235 | | av_log(s, AV_LOG_ERROR, "Stream not seekable, unable to write output file\n"); |
236 | | return AVERROR(EINVAL); |
237 | | } |
238 | | |
239 | | return 0; |
240 | | } |
241 | | |
242 | | static int apm_write_header(AVFormatContext *s) |
243 | | { |
244 | | uint8_t buf[APM_FILE_EXTRADATA_SIZE] = { 0 }; |
245 | | AVCodecParameters *par = s->streams[0]->codecpar; |
246 | | |
247 | | /* |
248 | | * Bodge a WAVEFORMATEX manually, ff_put_wav_header() can't |
249 | | * be used because of the extra 2 bytes. |
250 | | */ |
251 | | avio_wl16(s->pb, APM_TAG_CODEC); |
252 | | avio_wl16(s->pb, par->ch_layout.nb_channels); |
253 | | avio_wl32(s->pb, par->sample_rate); |
254 | | /* This is the wrong calculation, but it's what the original files have. */ |
255 | | avio_wl32(s->pb, par->sample_rate * par->ch_layout.nb_channels * 2); |
256 | | avio_wl16(s->pb, par->block_align); |
257 | | avio_wl16(s->pb, par->bits_per_coded_sample); |
258 | | avio_wl32(s->pb, APM_FILE_EXTRADATA_SIZE); |
259 | | |
260 | | /* |
261 | | * Build the extradata. Assume the codec's given us correct data. |
262 | | * File and data sizes are fixed later. |
263 | | */ |
264 | | AV_WL32(buf + 0, APM_TAG_VS12); /* magic */ |
265 | | AV_WL32(buf + 12, 0xFFFFFFFF); /* unk1 */ |
266 | | memcpy( buf + 20, par->extradata, APM_EXTRADATA_SIZE); |
267 | | AV_WL32(buf + 76, APM_TAG_DATA); /* data */ |
268 | | |
269 | | avio_write(s->pb, buf, APM_FILE_EXTRADATA_SIZE); |
270 | | return 0; |
271 | | } |
272 | | |
273 | | static int apm_write_trailer(AVFormatContext *s) |
274 | | { |
275 | | int64_t file_size, data_size; |
276 | | |
277 | | file_size = avio_tell(s->pb); |
278 | | data_size = file_size - (APM_FILE_HEADER_SIZE + APM_FILE_EXTRADATA_SIZE); |
279 | | |
280 | | if (file_size >= UINT32_MAX) { |
281 | | av_log(s, AV_LOG_ERROR, |
282 | | "Filesize %"PRId64" invalid for APM, output file will be broken\n", |
283 | | file_size); |
284 | | return AVERROR(ERANGE); |
285 | | } |
286 | | |
287 | | avio_seek(s->pb, 24, SEEK_SET); |
288 | | avio_wl32(s->pb, (uint32_t)file_size); |
289 | | avio_wl32(s->pb, (uint32_t)data_size); |
290 | | |
291 | | return 0; |
292 | | } |
293 | | |
294 | | const FFOutputFormat ff_apm_muxer = { |
295 | | .p.name = "apm", |
296 | | .p.long_name = NULL_IF_CONFIG_SMALL("Ubisoft Rayman 2 APM"), |
297 | | .p.extensions = "apm", |
298 | | .p.audio_codec = AV_CODEC_ID_ADPCM_IMA_APM, |
299 | | .p.video_codec = AV_CODEC_ID_NONE, |
300 | | .p.subtitle_codec = AV_CODEC_ID_NONE, |
301 | | .flags_internal = FF_OFMT_FLAG_MAX_ONE_OF_EACH | |
302 | | FF_OFMT_FLAG_ONLY_DEFAULT_CODECS, |
303 | | .init = apm_write_init, |
304 | | .write_header = apm_write_header, |
305 | | .write_packet = ff_raw_write_packet, |
306 | | .write_trailer = apm_write_trailer |
307 | | }; |
308 | | #endif |