1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
|
/* SPDX-License-Identifier: GPL-2.0 */
/** \file ogg_afh.c Audio format handler for ogg/vorbis files. */
#include <vorbis/codec.h>
#include "para.h"
#include "afh.h"
#include "error.h"
#include "string.h"
#include "ogg_afh_common.h"
struct private_vorbis_data {
vorbis_info vi;
vorbis_comment vc;
};
/*
* Vorbis uses three header packets, all of which are required: the
* identification header, the comments header, and the setup header.
*
* The identification header identifies the bitstream as Vorbis. It contains
* the Vorbis version and simple audio characteristics of the stream such as
* sample rate and number of channels.
*
* The comment header includes user text comments (tags) and a vendor string
* for the application/library that produced the bitstream.
*
* The setup header includes extensive CODEC setup information as well as the
* complete VQ and Huffman codebooks needed for decoding.
*/
static int vorbis_packet_callback(ogg_packet *packet, int packet_num,
__a_unused int serial, struct afh_info *afhi, void *private_data)
{
struct private_vorbis_data *pvd = private_data;
if (vorbis_synthesis_headerin(&pvd->vi, &pvd->vc, packet) < 0)
return -E_VORBIS;
if (packet_num == 0) {
if (pvd->vi.rate == 0)
return -E_VORBIS;
afhi->channels = pvd->vi.channels;
afhi->frequency = pvd->vi.rate;
afhi->bitrate = pvd->vi.bitrate_nominal / 1000;
PARA_DEBUG_LOG("channels: %i, sampling rate: %i, bitrate: %i\n",
afhi->channels, afhi->frequency, afhi->bitrate);
return 1;
}
if (packet_num == 1)
return 1; /* we also want to have packet #2 */
afhi->tags.artist = para_strdup(vorbis_comment_query(&pvd->vc, "artist", 0));
afhi->tags.title = para_strdup(vorbis_comment_query(&pvd->vc, "title", 0));
afhi->tags.album = para_strdup(vorbis_comment_query(&pvd->vc, "album", 0));
afhi->tags.year = para_strdup(vorbis_comment_query(&pvd->vc, "year", 0));
afhi->tags.comment = para_strdup(vorbis_comment_query(&pvd->vc, "comment", 0));
return 0;
}
static int ogg_vorbis_get_file_info(char *map, size_t numbytes,
struct afh_info *afhi)
{
int ret;
struct private_vorbis_data pvd;
struct oac_callback_info vorbis_callback_info = {
.packet_callback = vorbis_packet_callback,
.private_data = &pvd,
};
vorbis_info_init(&pvd.vi);
vorbis_comment_init(&pvd.vc);
ret = oac_get_file_info(map, numbytes, afhi, &vorbis_callback_info);
vorbis_info_clear(&pvd.vi);
vorbis_comment_clear(&pvd.vc);
return ret;
}
static int vorbis_get_header_callback(ogg_packet *packet, int packet_num,
int serial, __a_unused struct afh_info *afhi, void *private_data)
{
int ret;
struct oac_custom_header *h = private_data;
static unsigned char dummy_packet[] = {
0x03,
'v', 'o', 'r', 'b', 'i', 's',
0x06, 0x00, 0x00, 0x00,
'd', 'u', 'm', 'm', 'y', '\0',
0x00, 0x00, 0x00, 0x00, /* no comment :) */
0xff /* framing bit */
};
PARA_DEBUG_LOG("processing ogg packet #%d\n", packet_num);
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;
}
if (packet_num == 1) {
ogg_packet replacement = *packet;
PARA_INFO_LOG("replacing metadata packet\n");
replacement.packet = dummy_packet;
replacement.bytes = sizeof(dummy_packet);
ret = oac_custom_header_append(&replacement, h);
return ret < 0? ret : 1;
}
assert(packet_num == 2);
ret = oac_custom_header_append(packet, h);
if (ret < 0)
return ret;
oac_custom_header_flush(h);
return 0;
}
static void vorbis_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 = vorbis_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 ogg/vorbis header: %s\n",
para_strerror(-ret));
free(*buf);
*buf = NULL;
*len = 0;
} else
PARA_INFO_LOG("created %zu byte ogg vorbis header\n", *len);
}
static int vorbis_make_meta_packet(struct taginfo *tags, ogg_packet *result)
{
vorbis_comment vc;
int ret;
vorbis_comment_init(&vc);
vorbis_comment_add_tag(&vc, "artist", tags->artist);
vorbis_comment_add_tag(&vc, "title", tags->title);
vorbis_comment_add_tag(&vc, "album", tags->album);
vorbis_comment_add_tag(&vc, "year", tags->year);
vorbis_comment_add_tag(&vc, "comment", tags->comment);
ret = vorbis_commentheader_out(&vc, result);
vorbis_comment_clear(&vc);
if (ret != 0)
return -E_VORBIS_COMMENTHEADER;
return 1;
}
static int vorbis_rewrite_tags(const char *map, size_t mapsize,
struct taginfo *tags, int output_fd)
{
int ret;
ogg_packet packet;
ret = vorbis_make_meta_packet(tags, &packet);
if (ret < 0)
return ret;
ret = oac_rewrite_tags(map, mapsize, output_fd, (char *)packet.packet,
packet.bytes);
free(packet.packet);
return ret;
}
static const char * const ogg_suffixes[] = {"ogg", NULL};
/**
* The ogg vorbis audio format handler.
*
* It should actually be called vorbis because the ogg container format is also
* employed for the speex and flac codecs. Both the ogg and the vorbis
* libraries must be installed to get this audio format handler.
*/
const struct audio_format_handler ogg_afh = {
.get_file_info = ogg_vorbis_get_file_info,
.get_header = vorbis_get_header,
.suffixes = ogg_suffixes,
.rewrite_tags = vorbis_rewrite_tags
};
|