X-Git-Url: http://git.tuebingen.mpg.de/?p=paraslash.git;a=blobdiff_plain;f=opus_afh.c;h=ed6fe5c80625487d6d6cc852a349650f4cd80ad8;hp=10fe25d8e8dc00a3e085b4d6f2eae832f913b8a2;hb=4643fd6eeee81acc0e82e47e7895a3111775bbba;hpb=d1e6b28f66e243516d01916f9125baee75dd98d6 diff --git a/opus_afh.c b/opus_afh.c index 10fe25d8..ed6fe5c8 100644 --- a/opus_afh.c +++ b/opus_afh.c @@ -1,8 +1,4 @@ -/* - * Copyright (C) 2012-2014 Andre Noll - * - * Licensed under the GPL v2. For licencing details see COPYING. - */ +/* Copyright (C) 2012 Andre Noll , see file COPYING. */ /** \file opus_afh.c Audio format handler for ogg/opus files. */ @@ -17,7 +13,9 @@ #include "opus_common.h" #include "ogg_afh_common.h" -static const char* opus_suffixes[] = {"opus", NULL}; +static const char * const opus_suffixes[] = {"opus", NULL}; + +#define OPUS_COMMENT_HEADER "OpusTags" static bool copy_if_tag_type(const char *tag, int taglen, const char *type, char **p) @@ -40,7 +38,7 @@ static int opus_get_comments(char *comments, int length, /* min size of a opus header is 16 bytes */ if (length < 16) return -E_OPUS_COMMENT; - if (memcmp(p, "OpusTags", 8) != 0) + if (memcmp(p, OPUS_COMMENT_HEADER, strlen(OPUS_COMMENT_HEADER)) != 0) return -E_OPUS_COMMENT; p += 8; val = read_u32(p); @@ -53,7 +51,7 @@ static int opus_get_comments(char *comments, int length, p += 4; if (p + ntags * 4 > end) return -E_OPUS_COMMENT; - PARA_INFO_LOG("found %d tag(s)\n", ntags); + PARA_INFO_LOG("found %u tag(s)\n", ntags); for (i = 0; i < ntags; i++, p += val) { char *tag; @@ -82,6 +80,14 @@ static int opus_get_comments(char *comments, int length, return 1; } +/* + * Ogg/Opus has two mandatory header packets: + * + * 1. ID header (identifies the stream as Opus). Dedicated "BOS" ogg page. + * 2. Comment header (metadata). May span multiple pages. + * + * See doc/draft-ietf-codec-oggopus.xml in the opus source tree for details. + */ static int opus_packet_callback(ogg_packet *packet, int packet_num, __a_unused int serial, struct afh_info *afhi, void *private_data) @@ -94,7 +100,8 @@ static int opus_packet_callback(ogg_packet *packet, int packet_num, if (ret < 0) return ret; afhi->channels = oh->channels; - afhi->techinfo = make_message("header version %d, input sample rate: %dHz", + afhi->techinfo = make_message( + "header version %d, input sample rate: %" PRIu32 "Hz", oh->version, oh->input_sample_rate); /* * The input sample rate is irrelevant for afhi->frequency as @@ -120,11 +127,11 @@ static int opus_get_file_info(char *map, size_t numbytes, __a_unused int fd, int ret, ms; struct opus_header oh = {.version = 0}; - struct ogg_afh_callback_info opus_callback_info = { + struct oac_callback_info opus_callback_info = { .packet_callback = opus_packet_callback, .private_data = &oh, }; - ret = ogg_get_file_info(map, numbytes, afhi, &opus_callback_info); + ret = oac_get_file_info(map, numbytes, afhi, &opus_callback_info); if (ret < 0) return ret; ret = (afhi->chunk_table[afhi->chunks_total] - afhi->chunk_table[0]) * 8; /* bits */ @@ -133,6 +140,156 @@ static int opus_get_file_info(char *map, size_t numbytes, __a_unused int fd, return 1; } +static size_t opus_make_meta_packet(struct taginfo *tags, char **result) +{ + size_t sz; + char *buf, *p; + size_t comment_len = strlen(tags->comment), + artist_len = strlen(tags->artist), + title_len = strlen(tags->title), + album_len = strlen(tags->album), + year_len = strlen(tags->year); + uint32_t comment_sz = comment_len, + artist_sz = artist_len + strlen("artist="), + title_sz = title_len + strlen("title="), + album_sz = album_len + strlen("album="), + year_sz = year_len + strlen("year="); + uint32_t num_tags; + + sz = strlen(OPUS_COMMENT_HEADER) + + 4 /* comment length (always present) */ + + comment_sz + + 4; /* number of tags */ + num_tags = 0; + if (artist_len) { + num_tags++; + sz += 4 + artist_sz; + } + if (title_len) { + num_tags++; + sz += 4 + title_sz; + } + if (album_len) { + num_tags++; + sz += 4 + album_sz; + } + if (year_len) { + num_tags++; + sz += 4 + year_sz; + } + PARA_DEBUG_LOG("meta packet size: %zu bytes\n", sz); + /* terminating zero byte for the last sprintf() */ + buf = p = para_malloc(sz + 1); + memcpy(p, OPUS_COMMENT_HEADER, strlen(OPUS_COMMENT_HEADER)); + p += strlen(OPUS_COMMENT_HEADER); + write_u32(p, comment_sz); + p += 4; + strcpy(p, tags->comment); + p += comment_sz; + write_u32(p, num_tags); + p += 4; + if (artist_len) { + write_u32(p, artist_sz); + p += 4; + sprintf(p, "artist=%s", tags->artist); + p += artist_sz; + } + if (title_len) { + write_u32(p, title_sz); + p += 4; + sprintf(p, "title=%s", tags->title); + p += title_sz; + } + if (album_len) { + write_u32(p, album_sz); + p += 4; + sprintf(p, "album=%s", tags->album); + p += album_sz; + } + if (year_len) { + write_u32(p, year_sz); + p += 4; + sprintf(p, "year=%s", tags->year); + p += year_sz; + } + assert(p == buf + sz); + *result = buf; + return sz; +} + +static int opus_rewrite_tags(const char *map, size_t mapsize, + struct taginfo *tags, int output_fd, + __a_unused const char *filename) +{ + char *meta_packet; + size_t meta_sz; + int ret; + + meta_sz = opus_make_meta_packet(tags, &meta_packet); + ret = oac_rewrite_tags(map, mapsize, output_fd, meta_packet, meta_sz); + free(meta_packet); + return ret; +} + +/* + * See doc/draft-ietf-codec-oggopus.xml in the opus source tree for details + * about the format of the comment header. + */ +static int opus_get_header_callback(ogg_packet *packet, int packet_num, + int serial, __a_unused struct afh_info *afhi, void *private_data) +{ + struct oac_custom_header *h = private_data; + int ret; + static unsigned char dummy_tags[] = { /* a minimal comment header */ + 'O', 'p', 'u', 's', 'T', 'a', 'g', 's', + 0x06, 0x00, 0x00, 0x00, /* vendor string length */ + 'd', 'u', 'm', 'm', 'y', '\0', /* vendor string */ + 0x00, 0x00, 0x00, 0x00, /* user comment list length */ + }; + ogg_packet replacement; + + if (packet_num == 0) { + oac_custom_header_init(serial, h); + ret = oac_custom_header_append(packet, h); + if (ret < 0) + return ret; + oac_custom_header_flush(h); + return 1; + } + assert(packet_num == 1); + PARA_INFO_LOG("replacing metadata packet\n"); + replacement = *packet; + replacement.packet = dummy_tags; + replacement.bytes = sizeof(dummy_tags); + ret = oac_custom_header_append(&replacement, h); + if (ret < 0) + return ret; + oac_custom_header_flush(h); + return 0; +} + +static void opus_get_header(void *map, size_t mapsize, char **buf, + size_t *len) +{ + int ret; + struct oac_custom_header *h = oac_custom_header_new(); + struct oac_callback_info cb = { + .packet_callback = opus_get_header_callback, + .private_data = h, + }; + + ret = oac_get_file_info(map, mapsize, NULL, &cb); + *len = oac_custom_header_get(buf, h); + if (ret < 0) { + PARA_ERROR_LOG("could not create custom header: %s\n", + para_strerror(-ret)); + free(*buf); + *buf = NULL; + *len = 0; + } else + PARA_INFO_LOG("created %zu byte ogg/opus header\n", *len); +} + /** * The init function of the ogg/opus audio format handler. * @@ -141,5 +298,7 @@ static int opus_get_file_info(char *map, size_t numbytes, __a_unused int fd, void opus_afh_init(struct audio_format_handler *afh) { afh->get_file_info = opus_get_file_info, + afh->get_header = opus_get_header; afh->suffixes = opus_suffixes; + afh->rewrite_tags = opus_rewrite_tags; }