2 * Copyright (C) 2008 by INdT
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * @author Andre Moreira Magalhaes <andre.magalhaes@openbossa.org>
25 * http://wiki.multimedia.cx/index.php?title=RealMedia
27 * real media file parser.
30 #include <lightmediascanner_plugin.h>
31 #include <lightmediascanner_db.h>
32 #include <shared/util.h>
34 #include <sys/types.h>
46 struct lms_string_size title;
47 struct lms_string_size artist;
48 struct lms_string_size codec;
51 uint32_t length; /* msec */
53 uint16_t sampling_rate;
55 enum lms_stream_type stream_type; /* we only care for the first stream */
58 struct rm_file_header {
62 } __attribute__((packed));
65 struct lms_plugin plugin;
66 lms_db_audio_t *audio_db;
67 lms_db_video_t *video_db;
70 static const char _name[] = "rm";
71 static const struct lms_string_size _exts[] = {
72 LMS_STATIC_STRING_SIZE(".ra"),
73 LMS_STATIC_STRING_SIZE(".rv"),
74 LMS_STATIC_STRING_SIZE(".rm"),
75 LMS_STATIC_STRING_SIZE(".rmj"),
76 LMS_STATIC_STRING_SIZE(".rmvb")
78 static const char *_cats[] = {
84 static const char *_authors[] = {
85 "Andre Moreira Magalhaes",
90 * A real media file header has the following format:
91 * dword chunk type ('.RMF')
92 * dword chunk size (typically 0x12)
95 * dword number of headers
97 * Old RealAudio files (up to version 5) are not supported - they have the
101 _parse_file_header(int fd, struct rm_file_header *file_header)
103 if (read(fd, file_header, sizeof(struct rm_file_header)) == -1) {
104 fprintf(stderr, "ERROR: could not read file header\n");
108 if (memcmp(file_header->type, ".RMF", 4) != 0) {
109 fprintf(stderr, "ERROR: invalid header type\n");
113 file_header->size = be32toh(file_header->size);
116 fprintf(stderr, "file_header type=%.*s\n", 4, file_header->type);
117 fprintf(stderr, "file_header size=%d\n", file_header->size);
118 fprintf(stderr, "file_header version=%d\n", file_header->version);
121 /* TODO we should ignore these fields just when version is 0 or 1,
122 * but using the test files, if we don't ignore them for version 256
124 /* ignore file header extra fields
125 * file version and number of headers */
126 lseek(fd, 8, SEEK_CUR);
132 _read_header_type_and_size(int fd, char *type, uint32_t *size)
134 if (read(fd, type, 4) != 4)
137 if (read(fd, size, 4) != 4)
140 *size = be32toh(*size);
143 fprintf(stderr, "header type=%.*s\n", 4, type);
144 fprintf(stderr, "header size=%d\n", *size);
151 _read_string(int fd, char **out, unsigned int *out_len)
156 if (read(fd, &len, 2) == -1)
163 s = malloc(sizeof(char) * (len + 1));
164 if (read(fd, s, len) == -1) {
175 lseek(fd, len, SEEK_CUR);
181 * A CONT header has the following format
182 * dword Chunk type ('CONT')
184 * word Chunk version (always 0, for every known file)
185 * word Title string length
186 * byte[] Title string
187 * word Author string length
188 * byte[] Author string
189 * word Copyright string length
190 * byte[] Copyright string
191 * word Comment string length
192 * byte[] Comment string
195 _parse_cont_header(int fd, struct rm_info *info)
198 /* Ps.: type and size were already read */
201 pos1 = lseek(fd, 2, SEEK_CUR);
205 _read_string(fd, &info->title.str, &info->title.len);
206 _read_string(fd, &info->artist.str, &info->artist.len);
207 _read_string(fd, NULL, NULL); /* copyright */
208 _read_string(fd, NULL, NULL); /* comment */
210 return lseek(fd, 0, SEEK_CUR) - pos1;
213 static struct lms_string_size
214 _ra_codec_to_str(uint8_t fourcc[4])
218 struct lms_string_size str;
219 } *iter, _codecs[] = {
220 { "RV10", LMS_STATIC_STRING_SIZE("rv10") },
221 { "RV20", LMS_STATIC_STRING_SIZE("rv20") },
222 { "RV20", LMS_STATIC_STRING_SIZE("rv20") },
223 { "RVTR", LMS_STATIC_STRING_SIZE("rv20") },
224 { "RV20", LMS_STATIC_STRING_SIZE("rv20") },
225 { "RV30", LMS_STATIC_STRING_SIZE("rv30") },
226 { "RV40", LMS_STATIC_STRING_SIZE("rv40") },
227 { "dnet", LMS_STATIC_STRING_SIZE("ac3") },
228 { "lpcj", LMS_STATIC_STRING_SIZE("rm_144") },
229 { "28_8", LMS_STATIC_STRING_SIZE("rm_288") },
230 { "cook", LMS_STATIC_STRING_SIZE("cook") },
231 { "atrc", LMS_STATIC_STRING_SIZE("atrac3") },
232 { "atrc", LMS_STATIC_STRING_SIZE("atrac3") },
233 { "sipr", LMS_STATIC_STRING_SIZE("sipr") },
234 { "raac", LMS_STATIC_STRING_SIZE("aac") },
235 { "racp", LMS_STATIC_STRING_SIZE("aac") },
236 { "LSD:", LMS_STATIC_STRING_SIZE("ralf") },
240 for (iter = _codecs; iter->str.str; iter++)
241 if (memcmp(fourcc, iter->fourcc, 4) == 0)
248 _parse_mdpr_codec_header(int fd, struct rm_info *info)
255 if (read(fd, &size, sizeof(size)) != sizeof(size)
256 || read(fd, fourcc, sizeof(fourcc)) != sizeof(fourcc))
259 if (memcmp(fourcc, ".ra\xfd", 4) != 0)
262 if (read(fd, &version, sizeof(version)) != sizeof(version))
264 version = be16toh(version);
267 info->codec = LMS_STATIC_STRING_SIZE("rm_144");
268 info->sampling_rate = 8000;
274 else if (version == 5)
279 if (lseek(fd, skipbytes, SEEK_CUR) < 0
280 && read(fd, &info->sampling_rate, 2) != 2)
283 info->sampling_rate = be16toh(info->sampling_rate);
285 if (lseek(fd, 4, SEEK_CUR) < 0
286 || read(fd, &info->channels, 2) != 2)
289 info->channels = be16toh(info->channels);
296 if (read(fd, fourcc, 4) != 4)
299 info->codec = _ra_codec_to_str(fourcc);
304 _parse_mdpr_header(int fd, struct rm_info *info, bool *has_mdpr)
306 uint16_t object_version;
311 pos1 = lseek(fd, 0, SEEK_CUR);
313 if (read(fd, &object_version, sizeof(object_version)) !=
314 sizeof(object_version))
317 if (object_version != 0)
318 return sizeof(object_version);
320 lseek(fd, 7 * sizeof(uint32_t), SEEK_CUR);
322 /* stream description string: ignore */
323 if (read(fd, &slen, sizeof(slen)) != sizeof(slen))
325 lseek(fd, slen, SEEK_CUR);
327 /* mime type string */
328 if (read(fd, &slen, sizeof(slen)) != sizeof(slen)
330 || read(fd, buf, slen) != slen)
335 if (strcmp(buf, "audio/x-pn-realaudio") != 0 &&
336 strcmp(buf, "audio/x-pn-multirate-realaudio") != 0)
339 *has_mdpr = _parse_mdpr_codec_header(fd, info);
341 info->stream_type = LMS_STREAM_TYPE_AUDIO;
344 return lseek(fd, 0, SEEK_CUR) - pos1;
348 _parse_prop_header(int fd, struct rm_info *info)
350 uint16_t object_version;
352 uint32_t max_bit_rate;
353 uint32_t avg_bit_rate;
354 uint32_t max_packet_size;
355 uint32_t avg_packet_size;
356 uint32_t num_packets;
359 uint32_t index_offset;
360 uint32_t data_offset;
361 uint16_t num_streams;
363 } __attribute__((packed, aligned)) hdr;
365 if (read(fd, &object_version, sizeof(object_version))
366 != sizeof(object_version)
367 || object_version != 0)
370 if (read(fd, &hdr, sizeof(hdr)) != sizeof(hdr))
373 info->bitrate = be32toh(hdr.avg_bit_rate);
374 info->length = be32toh(hdr.duration);
376 return sizeof(object_version) + sizeof(hdr);
380 _match(struct plugin *p, const char *path, int len, int base)
384 i = lms_which_extension(path, len, _exts, LMS_ARRAY_SIZE(_exts));
388 return (void*)(i + 1);
392 _parse(struct plugin *plugin, struct lms_context *ctxt, const struct lms_file_info *finfo, void *match)
394 struct rm_info info = { .stream_type = LMS_STREAM_TYPE_UNKNOWN };
395 struct lms_audio_info audio_info = { };
396 struct lms_video_info video_info = { };
398 struct rm_file_header file_header;
401 bool has_cont = false, has_prop = false, has_mdpr = false;
403 fd = open(finfo->path, O_RDONLY);
409 if (_parse_file_header(fd, &file_header) != 0) {
415 if (_read_header_type_and_size(fd, type, &size) != 0) {
420 /* Give up, already reached DATA section */
421 if (memcmp(type, "DATA", 4) == 0)
424 if (memcmp(type, "CONT", 4) == 0) {
425 r = _parse_cont_header(fd, &info);
428 lseek(fd, size - 8 - r, SEEK_CUR);
430 } else if (memcmp(type, "PROP", 4) == 0) {
431 r = _parse_prop_header(fd, &info);
434 lseek(fd, size - 8 - r, SEEK_CUR);
436 } else if (memcmp(type, "MDPR", 4)) {
437 r = _parse_mdpr_header(fd, &info, &has_mdpr);
440 lseek(fd, size - 8 - r, SEEK_CUR);
442 /* Ignore other headers */
443 lseek(fd, size - 8, SEEK_CUR);
444 } while (!has_cont && !has_prop && !has_mdpr);
446 /* try to define stream type by extension */
447 if (info.stream_type == LMS_STREAM_TYPE_UNKNOWN) {
448 long ext_idx = ((long)match) - 1;
449 if (strcmp(_exts[ext_idx].str, ".ra") == 0)
450 info.stream_type = LMS_STREAM_TYPE_AUDIO;
451 /* consider rv, rm, rmj and rmvb as video */
453 info.stream_type = LMS_STREAM_TYPE_VIDEO;
456 lms_string_size_strip_and_free(&info.title);
457 lms_string_size_strip_and_free(&info.artist);
460 info.title = str_extract_name_from_path(finfo->path, finfo->path_len,
462 &_exts[((long) match) - 1],
465 lms_charset_conv(ctxt->cs_conv, &info.title.str, &info.title.len);
468 lms_charset_conv(ctxt->cs_conv, &info.artist.str, &info.artist.len);
471 fprintf(stderr, "file %s info\n", finfo->path);
472 fprintf(stderr, "\ttitle=%s\n", info.title);
473 fprintf(stderr, "\tartist=%s\n", info.artist);
476 if (info.stream_type == LMS_STREAM_TYPE_AUDIO) {
477 audio_info.id = finfo->id;
478 audio_info.title = info.title;
479 audio_info.artist = info.artist;
480 r = lms_db_audio_add(plugin->audio_db, &audio_info);
483 video_info.id = finfo->id;
484 video_info.title = info.title;
485 video_info.artist = info.artist;
486 r = lms_db_video_add(plugin->video_db, &video_info);
490 free(info.title.str);
491 free(info.artist.str);
493 posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED);
500 _setup(struct plugin *plugin, struct lms_context *ctxt)
502 plugin->audio_db = lms_db_audio_new(ctxt->db);
503 if (!plugin->audio_db)
505 plugin->video_db = lms_db_video_new(ctxt->db);
506 if (!plugin->video_db)
513 _start(struct plugin *plugin, struct lms_context *ctxt)
516 r = lms_db_audio_start(plugin->audio_db);
517 r |= lms_db_video_start(plugin->video_db);
522 _finish(struct plugin *plugin, struct lms_context *ctxt)
524 if (plugin->audio_db)
525 lms_db_audio_free(plugin->audio_db);
526 if (plugin->video_db)
527 lms_db_video_free(plugin->video_db);
533 _close(struct plugin *plugin)
539 API struct lms_plugin *
540 lms_plugin_open(void)
542 struct plugin *plugin;
544 plugin = (struct plugin *)malloc(sizeof(*plugin));
545 plugin->plugin.name = _name;
546 plugin->plugin.match = (lms_plugin_match_fn_t)_match;
547 plugin->plugin.parse = (lms_plugin_parse_fn_t)_parse;
548 plugin->plugin.close = (lms_plugin_close_fn_t)_close;
549 plugin->plugin.setup = (lms_plugin_setup_fn_t)_setup;
550 plugin->plugin.start = (lms_plugin_start_fn_t)_start;
551 plugin->plugin.finish = (lms_plugin_finish_fn_t)_finish;
553 return (struct lms_plugin *)plugin;
556 API const struct lms_plugin_info *
557 lms_plugin_info(void)
559 static struct lms_plugin_info info = {
562 "Real Networks audio and video files (RA, RV, RM, RMJ, RMVB)",
565 "http://lms.garage.maemo.org"