summaryrefslogtreecommitdiff
path: root/ogg_afh_common.c
blob: 7162c34158c432a0682c97ddbb4d6128a07c06ba (plain)
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
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
/* SPDX-License-Identifier: GPL-2.0 */

/** \file ogg_afh_common.c Functions common to all ogg/ codecs. */

#include <ogg/ogg.h>

#include "para.h"
#include "afh.h"
#include "error.h"
#include "string.h"
#include "ogg_afh_common.h"
#include "fd.h"

/* Taken from decoder_example.c of libvorbis-1.2.3. */
static int process_packets_2_and_3(ogg_sync_state *oss,
		ogg_stream_state *stream, struct afh_info *afhi,
		struct oac_callback_info *ci)
{
	ogg_page page;
	ogg_packet packet;
	int i = 0;

	while (i < 2) {
		while (i < 2) {
			int ret = ogg_sync_pageout(oss, &page);
			if (ret == 0)
				break; /* Need more data */
			if (ret != 1)
				continue;
			/*
			 * We can ignore any errors here as they'll also become
			 * apparent at packetout.
			 */
			ogg_stream_pagein(stream, &page);
			PARA_INFO_LOG("ogg page serial: %d\n",
				ogg_page_serialno(&page));
			while (i < 2) {
				ret = ogg_stream_packetout(stream, &packet);
				if (ret == 0)
					break;
				if (ret < 0)
					return -E_STREAM_PACKETOUT;
				ret = ci->packet_callback(&packet, i + 1,
					ogg_page_serialno(&page), afhi,
					ci->private_data);
				if (ret < 0)
					return ret;
				if (ret == 0) /* header complete */
					return 1;
				i++;
			}
		}
	}
	return 1;
}

static int process_ogg_packets(ogg_sync_state *oss, struct afh_info *afhi,
		struct oac_callback_info *ci)
{
	ogg_packet packet;
	ogg_stream_state stream;
	ogg_page page;
	int ret;

	if (ogg_sync_pageout(oss, &page) != 1)
		return -E_SYNC_PAGEOUT;

	ret = ogg_page_serialno(&page);
	ogg_stream_init(&stream, ret);

	ret = -E_STREAM_PAGEIN;
	if (ogg_stream_pagein(&stream, &page) < 0)
		goto out;

	ret = -E_STREAM_PACKETOUT;
	if (ogg_stream_packetout(&stream, &packet) != 1)
		goto out;
	ret = ci->packet_callback(&packet, 0, ogg_page_serialno(&page),
		afhi, ci->private_data);
	if (ret < 0)
		goto out;
	ret = process_packets_2_and_3(oss, &stream, afhi, ci);
	if (ret < 0)
		goto out;
	ret = 1;
out:
	ogg_stream_clear(&stream);
	return ret;
}

static void set_chunk_tv(int frames_per_chunk, int frequency,
		struct timeval *result)
{
	uint64_t x = (uint64_t)frames_per_chunk * 1000 * 1000 / frequency;

	result->tv_sec = x / 1000 / 1000;
	result->tv_usec = x % (1000 * 1000);
	PARA_INFO_LOG("%d frames per chunk, chunk time: %lums\n",
		frames_per_chunk, tv2ms(result));
}

/**
 * Pass first three ogg packets to callback and build the chunk table.
 *
 * This function extracts the first three ogg packets of the audio data
 * given by \a map and \a numbytes and passes each packet to the callback
 * defined by \a ci.
 *
 * If the packet callback indicates success and \a afhi is not \p NULL, the
 * chunk table is built. Chunk zero contains the first three ogg packets while
 * all other chunks consist of exactly one ogg page.
 *
 * \param map Audio file data.
 * \param numbytes The length of \a map.
 * \param afhi Passed to the packet callback, contains chunk table.
 * \param ci The callback structure.
 *
 * \return Standard.
 */
int oac_get_file_info(char *map, size_t numbytes, struct afh_info *afhi,
		struct oac_callback_info *ci)
{
	ogg_sync_state oss;
	ogg_page op;
	char *buf;
	int ret, i, j, frames_per_chunk, ct_size, prev_pageno = 0;
	long long unsigned granule_skip = 0, num_frames = 0;
	int64_t granule = 0, prev_granule = 0;

	ogg_sync_init(&oss);
	ret = -E_OGG_SYNC;
	buf = ogg_sync_buffer(&oss, numbytes);
	if (!buf)
		goto out;
	memcpy(buf, map, numbytes);
	ret = -E_OGG_SYNC;
	if (ogg_sync_wrote(&oss, numbytes) < 0)
		goto out;
	ret = process_ogg_packets(&oss, afhi, ci);
	if (ret < 0)
		goto out;
	if (!afhi)
		goto out;
	afhi->header_len = oss.returned;
	oss.returned = 0;
	oss.fill = numbytes;
	/* count ogg pages and get duration of the file */
	for (i = 0; ogg_sync_pageseek(&oss, &op) > 0; i++) {
		int this_pageno = ogg_page_pageno(&op);
		int64_t this_granule = ogg_page_granulepos(&op);
		if (this_granule >= 0)
			granule = this_granule;
		if (i > 0 && this_pageno != prev_pageno + 1) /* hole */
			granule_skip += granule - prev_granule;
		prev_pageno = this_pageno;
		prev_granule = granule;
	}
	num_frames = granule - granule_skip;
	PARA_INFO_LOG("%d pages, %llu frames\n", i, num_frames);
	ret = -E_OGG_EMPTY;
	if (i == 0)
		goto out;
	afhi->seconds_total = num_frames / afhi->frequency;
	/* use roughly one page per chunk */
	frames_per_chunk = num_frames / i;
	PARA_INFO_LOG("%" PRIu32 " seconds, %d frames/chunk\n",
		afhi->seconds_total, frames_per_chunk);
	ct_size = 250;
	afhi->chunk_table = arr_alloc(ct_size, sizeof(uint32_t));
	afhi->chunk_table[0] = 0;
	afhi->chunk_table[1] = afhi->header_len;
	oss.returned = afhi->header_len;
	oss.fill = numbytes;
	for (j = 1; ogg_sync_pageseek(&oss, &op) > 0; /* nothing */) {
		granule = ogg_page_granulepos(&op);

		while (granule >= (j + 1) * frames_per_chunk) {
			j++;
			if (j >= ct_size) {
				ct_size *= 2;
				afhi->chunk_table = arr_realloc(
					afhi->chunk_table,
					ct_size, sizeof(uint32_t));
			}
			afhi->chunk_table[j] = oss.returned;
		}
	}
	afhi->chunks_total = j;
	set_max_chunk_size(afhi);
	set_chunk_tv(frames_per_chunk, afhi->frequency, &afhi->chunk_tv);
	ret = 0;
out:
	ogg_sync_clear(&oss);
	return ret;
}

static int write_ogg_page(int fd, const ogg_page *op)
{
	int ret;

	PARA_DEBUG_LOG("header/body: %li/%li\n", op->header_len, op->body_len);
	ret = xwrite(fd, (const char *)op->header, op->header_len);
	if (ret < 0)
		return ret;
	return xwrite(fd, (const char *)op->body, op->body_len);
}

/**
 * Change meta tags of ogg files.
 *
 * \param map The (read-only) memory map of the input file.
 * \param map_sz The size of the input file in bytes.
 * \param fd The output file descriptor.
 * \param meta_packet Codec-specific packet containing modified tags.
 * \param meta_sz Size of the metadata packet.
 *
 * This function writes a new ogg file content using file descriptor \a fd,
 * which must correspond to a file which has been opened for writing.  The
 * second packet is supposed to contain the metadata, and is replaced by \a
 * meta_packet. This output file has to be closed by the caller.
 *
 * \return Standard.
 */
int oac_rewrite_tags(const char *map, size_t map_sz, int fd,
		char *meta_packet, size_t meta_sz)
{
	ogg_sync_state oss_in, oss_out;
	ogg_stream_state stream_in, stream_out, *si = NULL, *so = NULL;
	ogg_packet packet;
	ogg_page op;
	char *buf;
	int serial, ret;
	long len = map_sz;

	ogg_sync_init(&oss_in);
	ogg_sync_init(&oss_out);

	ret = -E_OGG_SYNC;
	buf = ogg_sync_buffer(&oss_in, len);
	if (!buf)
		goto out;
	memcpy(buf, map, len);
	ret = -E_OGG_SYNC;
	if (ogg_sync_wrote(&oss_in, len) < 0)
		goto out;
	if (ogg_sync_pageout(&oss_in, &op) != 1)
		goto out;
	ret = ogg_page_serialno(&op);
	serial = ret;

	si = &stream_in;
	ogg_stream_init(si, serial);
	/* Packet #0 goes to an own page */
	ret = -E_STREAM_PAGEIN;
	if (ogg_stream_pagein(si, &op) < 0)
		goto out;
	ret = -E_STREAM_PACKETOUT;
	if (ogg_stream_packetout(si, &packet) != 1)
		goto out;
	ret = -E_STREAM_PACKETIN;
	so = &stream_out;
	ogg_stream_init(so, serial);
	if (ogg_stream_packetin(so, &packet) != 0)
		goto out;
	ret = ogg_stream_flush(so, &op);
	assert(ret != 0);
	/* packets have been flushed into the page. */
	ret = write_ogg_page(fd, &op);
	if (ret < 0)
		goto out;
	/*
	 * For all supported ogg/xxx audio formats the meta data packet is
	 * packet #1. Write out our modified version of this packet.
	 */
	packet.packetno = 1;
	packet.b_o_s = packet.e_o_s = 0;
	packet.packet = (typeof(packet.packet))meta_packet;
	packet.bytes = meta_sz;
	ret = -E_STREAM_PACKETIN;
	if (ogg_stream_packetin(so, &packet) != 0)
		goto out;
	/* Copy ogg packets, ignoring the meta data packet. */
	for (;;) {
		ret = ogg_stream_packetout(si, &packet);
		if (ret == -1)
			break;
		if (ret != 1) {
			ret = -E_STREAM_PAGEOUT;
			if (ogg_sync_pageout(&oss_in, &op) < 0)
				goto out;
			ret = -E_STREAM_PAGEIN;
			if (ogg_stream_pagein(si, &op))
				goto out;
			continue;
		}
		PARA_DEBUG_LOG("packet: bytes: %d, granule: %d, packetno: %d\n",
			(int)packet.bytes, (int)packet.granulepos,
			(int)packet.packetno);
		/* ignore meta data packet which we replaced */
		if (packet.packetno == 1)
			continue;
		ret = -E_STREAM_PACKETIN;
		if (ogg_stream_packetin(so, &packet) != 0)
			goto out;
		/* only create a new ogg page if granulepos is valid */
		if (packet.granulepos == -1)
			continue;
		/* force remaining packets into a page */
		for (;;) {
#ifdef HAVE_OGG_STREAM_FLUSH_FILL
			ret = ogg_stream_flush_fill(so, &op, INT_MAX);
#else
			ret = ogg_stream_flush(so, &op);
#endif
			if (ret <= 0)
				break;
			PARA_DEBUG_LOG("writing page (%li bytes)\n",
				op.header_len + op.body_len);
			ret = write_ogg_page(fd, &op);
			if (ret < 0)
				goto out;
		}
	}
	if (ogg_stream_flush(so, &op)) {
		/* write remaining data */
		ret = write_ogg_page(fd, &op);
		if (ret < 0)
			goto out;
	}
	ret = 1;
out:
	ogg_sync_clear(&oss_in);
	ogg_sync_clear(&oss_out);
	if (si)
		ogg_stream_clear(si);
	if (so)
		ogg_stream_clear(so);
	return ret;
}

/* Structure for providing custom headers for streaming. */
struct oac_custom_header {
	char *buf;
	size_t len;
	ogg_stream_state oss;
};

/**
 * Allocate and return a custom header structure.
 *
 * For some audio codecs which employ the ogg container format, the server side
 * wants to replace the meta tags at the beginning of the file because they are
 * not needed for streaming and can be arbitrary large. The structure returned
 * by this function is typically used as the ->private field of struct \ref
 * oac_callback_info for \ref oac_get_file_info(). This allows the audio format
 * handler to set up a custom header which is identical to the original header,
 * but with the meta data part replaced by fixed length dummy contents.
 *
 * \return The returned memory must be initialized with the serial number of
 * the ogg stream before ogg packets can be submitted to it. This is not done
 * here because the header structure is allocated before \ref
 * oac_get_file_info() is called, and the serial number is not known at this
 * point.
 *
 * \sa \ref oac_custom_header_init().
 */
__malloc struct oac_custom_header *oac_custom_header_new(void)
{
	return zalloc(sizeof(struct oac_custom_header));
}

/**
 * Set the serial number of an allocated header structure.
 *
 * \param serial Passed to the callback function.
 * \param h As returned from \ref oac_custom_header_new().
 *
 * This function must be called before any packets are submitted.
 */
void oac_custom_header_init(int serial, struct oac_custom_header *h)
{
	ogg_stream_init(&h->oss, serial);
}

/**
 * Submit an ogg packet to a custom header structure.
 *
 * \param op The packet to append.
 * \param h Must be initialized.
 *
 * The packet may be the one which was passed to the callback, or a completely
 * different one, like a dummy metadata packet.
 *
 * \return Standard.
 */
int oac_custom_header_append(ogg_packet *op, struct oac_custom_header *h)
{
	return ogg_stream_packetin(&h->oss, op) < 0? -E_OGG_PACKET_IN : 1;
}

/**
 * Force remaining packets into an ogg page.
 *
 * \param h Should contain submitted but not yet flushed packets.
 *
 * This is called after the first packet has been submitted with \ref
 * oac_custom_header_append() to make sure the first ogg page contains only
 * this packet. Also when header processing is complete, the callbacks call
 * this to force the previously submitted packets into a page.
 */
void oac_custom_header_flush(struct oac_custom_header *h)
{
	ogg_page og;

	while (ogg_stream_flush(&h->oss, &og)) {
		size_t len = og.header_len + og.body_len;
		h->buf = para_realloc(h->buf, h->len + len);
		memcpy(h->buf + h->len, og.header, og.header_len);
		memcpy(h->buf + h->len + og.header_len, og.body, og.body_len);
		h->len += len;
	}
}

/**
 * Return the custom header buffer and deallocate resources.
 *
 * This is called after the ogg packets which comprise the header have been
 * submitted and flushed.
 *
 * \param buf Result pointer.
 * \param h Must not be used any more after the call.
 *
 * \return The size of the header. This is the sum of the sizes of all ogg
 * pages that have been flushed out.
 */
size_t oac_custom_header_get(char **buf, struct oac_custom_header *h)
{
	size_t ret = h->len;

	*buf = h->buf;
	ogg_stream_clear(&h->oss);
	free(h);
	return ret;
}