summaryrefslogtreecommitdiff
path: root/mp3dec_filter.c
blob: 8aea72e159f9d8ab3077fb37573f0628cc0b890c (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
/* SPDX-License-Identifier: GPL-2.0 */

/** \file mp3dec_filter.c Paraslash's mp3 decoder, based on libmad. */

/** \cond doxygen_ignore */

#include <mad.h>
#include <lopsub.h>

#include "filter_cmd.lsg.h"
#include "para.h"
#include "list.h"
#include "sched.h"
#include "buffer_tree.h"
#include "filter.h"
#include "error.h"
#include "string.h"

/* Convert a sample value from libmad to a signed short. */
static signed short mad_to_short(mad_fixed_t val)
{
	if (val >= MAD_F_ONE)
		return SHRT_MAX;
	if (val <= -MAD_F_ONE)
		return -SHRT_MAX;
	return val >> (MAD_F_FRACBITS - 15);
}

struct private_mp3dec_data {
	/* Information about the current mp3 stream. */
	struct mad_stream stream;
	/** Information about the frame being decoded. */
	struct mad_frame frame;
	/* Contains the PCM output. */
	struct mad_synth synth;
	/* The number of channels of the current stream. */
	unsigned int channels;
	/* Current sample rate in Hz. */
	unsigned int sample_rate;
};

/* Returns negative on serious errors. */
static int handle_decode_error(struct private_mp3dec_data *pmd)
{
	if (!MAD_RECOVERABLE(pmd->stream.error)
			&& pmd->stream.error != MAD_ERROR_BUFLEN) {
		PARA_ERROR_LOG("%s\n", mad_stream_errorstr(&pmd->stream));
		return -E_MAD_FRAME_DECODE;
	}
	PARA_DEBUG_LOG("%s\n", mad_stream_errorstr(&pmd->stream));
	return 0;
}

static void consume(struct btr_node *btrn, struct mad_stream *s,
		size_t max)
{
	size_t used;

	if (!s->next_frame)
		used = max;
	else { /* we still have some data */
		used = s->next_frame - s->buffer;
		assert(used <= max);
	}
	btr_consume(btrn, used);
}

static void mp3dec_close(struct filter_node *fn)
{
	struct private_mp3dec_data *pmd = fn->private_data;

	mad_synth_finish(&pmd->synth);
	mad_frame_finish(&pmd->frame);
	mad_stream_finish(&pmd->stream);

	free(pmd);
	fn->private_data = NULL;
}

#define MP3DEC_MAX_FRAME 8192

static int mp3dec_post_monitor(__a_unused struct sched *s, void *context)
{
	struct filter_node *fn = context;
	int i, ret;
	struct private_mp3dec_data *pmd = fn->private_data;
	struct btr_node *btrn = fn->btrn;
	size_t loaded = 0, len;
	char *inbuffer, *outbuffer;

next_buffer:
	pmd->stream.error = 0;
	ret = btr_node_status(btrn, fn->min_iqs, BTR_NT_INTERNAL);
	if (ret < 0)
		goto err;
	if (ret == 0)
		return 0;
	btr_merge(btrn, fn->min_iqs);
	len = btr_next_buffer(btrn, &inbuffer);
	/*
	 * Decode at most 8K in one go to give the post_monitor() functions of
	 * other buffer tree nodes a chance to run. This is necessary to avoid
	 * buffer underruns on slow machines.
	 */
	len = PARA_MIN(len, (size_t)MP3DEC_MAX_FRAME);
	mad_stream_buffer(&pmd->stream, (unsigned char *)inbuffer, len);
next_frame:
	ret = mad_header_decode(&pmd->frame.header, &pmd->stream);
	if (ret < 0) {
		consume(btrn, &pmd->stream, len);
		if (pmd->stream.error == MAD_ERROR_BUFLEN) {
			if (fn->min_iqs > 0 && btr_no_parent(btrn)) {
				ret = -E_EOF;
				goto err;
			}
			fn->min_iqs += 100;
			ret = -E_MP3DEC_CORRUPT;
			if (fn->min_iqs > MP3DEC_MAX_FRAME)
				goto err;
		}
		if (loaded == 0)
			goto next_buffer;
		return 0;
	}
	pmd->sample_rate = pmd->frame.header.samplerate;
	pmd->channels = MAD_NCHANNELS(&pmd->frame.header);
decode:
	ret = mad_frame_decode(&pmd->frame, &pmd->stream);
	if (ret != 0) {
		ret = handle_decode_error(pmd);
		if (ret < 0)
			goto err;
		mad_stream_sync(&pmd->stream);
		if (pmd->stream.error == MAD_ERROR_BUFLEN) {
			ret = -E_EOF;
			if (btr_no_parent(btrn))
				goto err;
			fn->min_iqs += 100;
			ret = -E_MP3DEC_CORRUPT;
			if (fn->min_iqs > MP3DEC_MAX_FRAME)
				goto err;
			consume(btrn, &pmd->stream, len);
			return 0;
		}
		if (pmd->stream.error != MAD_ERROR_BADDATAPTR)
			goto decode;
		consume(btrn, &pmd->stream, len);
		return 0;
	}
	fn->min_iqs = 0;
	mad_synth_frame(&pmd->synth, &pmd->frame);
	outbuffer = arr_alloc(pmd->synth.pcm.length, 2 * pmd->channels);
	loaded = 0;
	for (i = 0; i < pmd->synth.pcm.length; i++) {
		int sample = mad_to_short(pmd->synth.pcm.samples[0][i]);
		write_int16_host_endian(outbuffer + loaded, sample);
		loaded += 2;
		if (pmd->channels == 2) { /* stereo */
			sample = mad_to_short(pmd->synth.pcm.samples[1][i]);
			write_int16_host_endian(outbuffer + loaded, sample);
			loaded += 2;
		}
	}
	btr_add_output(outbuffer, loaded, btrn);
	goto next_frame;
err:
	assert(ret < 0);
	btr_remove_node(&fn->btrn);
	return ret;
}

static void mp3dec_open(struct filter_node *fn)
{
	struct private_mp3dec_data *pmd = zalloc(sizeof(*pmd));

	fn->private_data = pmd;
	mad_stream_init(&pmd->stream);
	mad_frame_init(&pmd->frame);
	mad_synth_init(&pmd->synth);
	if (FILTER_CMD_OPT_GIVEN(MP3DEC, IGNORE_CRC, fn->lpr))
		mad_stream_options(&pmd->stream, MAD_OPTION_IGNORECRC);
}

static int mp3dec_execute(const struct btr_node *btrn, const char *cmd,
		char **result)
{
	struct filter_node *fn = btr_context(btrn);
	struct private_mp3dec_data *pmd = fn->private_data;

	return decoder_execute(cmd, pmd->sample_rate, pmd->channels, result);
}

/** \endcond
 * The filter methods of the mp3 decoder.
 *
 * The ->execute method just calls \ref decoder_execute() to provide
 * standard decoder information such as the sample rate. Also, \ref
 * generic_filter_pre_monitor() is used as the ->pre_monitor method.
 */
const struct filter lsg_filter_cmd_com_mp3dec_user_data = {
	.open = mp3dec_open,
	.close = mp3dec_close,
	.pre_monitor = generic_filter_pre_monitor,
	.post_monitor = mp3dec_post_monitor,
	.execute = mp3dec_execute,
};