Merge "backtrace.c: Fix casting pointer to/from integral type."
[asterisk/asterisk.git] / formats / format_ogg_speex.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2011-2016, Timo Teräs
5  *
6  * See http://www.asterisk.org for more information about
7  * the Asterisk project. Please do not directly contact
8  * any of the maintainers of this project for assistance;
9  * the project provides a web site, mailing lists and IRC
10  * channels for your use.
11  *
12  * This program is free software, distributed under the terms of
13  * the GNU General Public License Version 2. See the LICENSE file
14  * at the top of the source tree.
15  */
16
17 /*! \file
18  *
19  * \brief OGG/Speex streams.
20  * \arg File name extension: spx
21  * \ingroup formats
22  */
23
24 /*** MODULEINFO
25         <depend>speex</depend>
26         <depend>ogg</depend>
27         <support_level>extended</support_level>
28  ***/
29
30 #include "asterisk.h"
31
32 #include "asterisk/mod_format.h"
33 #include "asterisk/module.h"
34 #include "asterisk/format_cache.h"
35
36 #include <speex/speex_header.h>
37 #include <ogg/ogg.h>
38
39 #define BLOCK_SIZE      4096            /* buffer size for feeding OGG routines */
40 #define BUF_SIZE        200
41
42 struct speex_desc {     /* format specific parameters */
43         /* structures for handling the Ogg container */
44         ogg_sync_state oy;
45         ogg_stream_state os;
46         ogg_page og;
47         ogg_packet op;
48
49         int serialno;
50
51         /*! \brief Indicates whether an End of Stream condition has been detected. */
52         int eos;
53 };
54
55 static int read_packet(struct ast_filestream *fs)
56 {
57         struct speex_desc *s = (struct speex_desc *)fs->_private;
58         char *buffer;
59         int result;
60         size_t bytes;
61
62         while (1) {
63                 /* Get one packet */
64                 result = ogg_stream_packetout(&s->os, &s->op);
65                 if (result > 0) {
66                         if (s->op.bytes >= 5 && !memcmp(s->op.packet, "Speex", 5)) {
67                                 s->serialno = s->os.serialno;
68                         }
69                         if (s->serialno == -1 || s->os.serialno != s->serialno) {
70                                 continue;
71                         }
72                         return 0;
73                 }
74
75                 if (result < 0) {
76                         ast_log(LOG_WARNING,
77                                 "Corrupt or missing data at this page position; continuing...\n");
78                 }
79
80                 /* No more packets left in the current page... */
81                 if (s->eos) {
82                         /* No more pages left in the stream */
83                         return -1;
84                 }
85
86                 while (!s->eos) {
87                         /* See if OGG has any pages in it's internal buffers */
88                         result = ogg_sync_pageout(&s->oy, &s->og);
89                         if (result > 0) {
90                                 /* Read all streams. */
91                                 if (ogg_page_serialno(&s->og) != s->os.serialno) {
92                                         ogg_stream_reset_serialno(&s->os, ogg_page_serialno(&s->og));
93                                 }
94                                 /* Yes, OGG has more pages in it's internal buffers,
95                                    add the page to the stream state */
96                                 result = ogg_stream_pagein(&s->os, &s->og);
97                                 if (result == 0) {
98                                         /* Yes, got a new, valid page */
99                                         if (ogg_page_eos(&s->og) &&
100                                             ogg_page_serialno(&s->og) == s->serialno)
101                                                 s->eos = 1;
102                                         break;
103                                 }
104                                 ast_log(LOG_WARNING,
105                                         "Invalid page in the bitstream; continuing...\n");
106                         }
107
108                         if (result < 0) {
109                                 ast_log(LOG_WARNING,
110                                         "Corrupt or missing data in bitstream; continuing...\n");
111                         }
112
113                         /* No, we need to read more data from the file descrptor */
114                         /* get a buffer from OGG to read the data into */
115                         buffer = ogg_sync_buffer(&s->oy, BLOCK_SIZE);
116                         bytes = fread(buffer, 1, BLOCK_SIZE, fs->f);
117                         ogg_sync_wrote(&s->oy, bytes);
118                         if (bytes == 0) {
119                                 s->eos = 1;
120                         }
121                 }
122         }
123 }
124
125 /*!
126  * \brief Create a new OGG/Speex filestream and set it up for reading.
127  * \param fs File that points to on disk storage of the OGG/Speex data.
128  * \return The new filestream.
129  */
130 static int ogg_speex_open(struct ast_filestream *fs)
131 {
132         char *buffer;
133         size_t bytes;
134         struct speex_desc *s = (struct speex_desc *)fs->_private;
135         SpeexHeader *hdr = NULL;
136         int i, result, expected_rate;
137
138         expected_rate = ast_format_get_sample_rate(fs->fmt->format);
139         s->serialno = -1;
140         ogg_sync_init(&s->oy);
141
142         buffer = ogg_sync_buffer(&s->oy, BLOCK_SIZE);
143         bytes = fread(buffer, 1, BLOCK_SIZE, fs->f);
144         ogg_sync_wrote(&s->oy, bytes);
145
146         result = ogg_sync_pageout(&s->oy, &s->og);
147         if (result != 1) {
148                 if(bytes < BLOCK_SIZE) {
149                         ast_log(LOG_ERROR, "Run out of data...\n");
150                 } else {
151                         ast_log(LOG_ERROR, "Input does not appear to be an Ogg bitstream.\n");
152                 }
153                 ogg_sync_clear(&s->oy);
154                 return -1;
155         }
156
157         ogg_stream_init(&s->os, ogg_page_serialno(&s->og));
158         if (ogg_stream_pagein(&s->os, &s->og) < 0) {
159                 ast_log(LOG_ERROR, "Error reading first page of Ogg bitstream data.\n");
160                 goto error;
161         }
162
163         if (read_packet(fs) < 0) {
164                 ast_log(LOG_ERROR, "Error reading initial header packet.\n");
165                 goto error;
166         }
167
168         hdr = speex_packet_to_header((char*)s->op.packet, s->op.bytes);
169         if (memcmp(hdr->speex_string, "Speex   ", 8)) {
170                 ast_log(LOG_ERROR, "OGG container does not contain Speex audio!\n");
171                 goto error;
172         }
173         if (hdr->frames_per_packet != 1) {
174                 ast_log(LOG_ERROR, "Only one frame-per-packet OGG/Speex files are currently supported!\n");
175                 goto error;
176         }
177         if (hdr->nb_channels != 1) {
178                 ast_log(LOG_ERROR, "Only monophonic OGG/Speex files are currently supported!\n");
179                 goto error;
180         }
181         if (hdr->rate != expected_rate) {
182                 ast_log(LOG_ERROR, "Unexpected sampling rate (%d != %d)!\n",
183                         hdr->rate, expected_rate);
184                 goto error;
185         }
186
187         /* this packet is the comment */
188         if (read_packet(fs) < 0) {
189                 ast_log(LOG_ERROR, "Error reading comment packet.\n");
190                 goto error;
191         }
192         for (i = 0; i < hdr->extra_headers; i++) {
193                 if (read_packet(fs) < 0) {
194                         ast_log(LOG_ERROR, "Error reading extra header packet %d.\n", i+1);
195                         goto error;
196                 }
197         }
198         speex_header_free(hdr);
199
200         return 0;
201 error:
202         if (hdr) {
203                 speex_header_free(hdr);
204         }
205         ogg_stream_clear(&s->os);
206         ogg_sync_clear(&s->oy);
207         return -1;
208 }
209
210 /*!
211  * \brief Close a OGG/Speex filestream.
212  * \param fs A OGG/Speex filestream.
213  */
214 static void ogg_speex_close(struct ast_filestream *fs)
215 {
216         struct speex_desc *s = (struct speex_desc *)fs->_private;
217
218         ogg_stream_clear(&s->os);
219         ogg_sync_clear(&s->oy);
220 }
221
222 /*!
223  * \brief Read a frame full of audio data from the filestream.
224  * \param fs The filestream.
225  * \param whennext Number of sample times to schedule the next call.
226  * \return A pointer to a frame containing audio data or NULL ifthere is no more audio data.
227  */
228 static struct ast_frame *ogg_speex_read(struct ast_filestream *fs,
229                                          int *whennext)
230 {
231         struct speex_desc *s = (struct speex_desc *)fs->_private;
232
233         if (read_packet(fs) < 0) {
234                 return NULL;
235         }
236
237         AST_FRAME_SET_BUFFER(&fs->fr, fs->buf, AST_FRIENDLY_OFFSET, BUF_SIZE);
238         memcpy(fs->fr.data.ptr, s->op.packet, s->op.bytes);
239         fs->fr.datalen = s->op.bytes;
240         fs->fr.samples = *whennext = ast_codec_samples_count(&fs->fr);
241
242         return &fs->fr;
243 }
244
245 /*!
246  * \brief Trucate an OGG/Speex filestream.
247  * \param s The filestream to truncate.
248  * \return 0 on success, -1 on failure.
249  */
250
251 static int ogg_speex_trunc(struct ast_filestream *s)
252 {
253         ast_log(LOG_WARNING, "Truncation is not supported on OGG/Speex streams!\n");
254         return -1;
255 }
256
257 /*!
258  * \brief Seek to a specific position in an OGG/Speex filestream.
259  * \param s The filestream to truncate.
260  * \param sample_offset New position for the filestream, measured in 8KHz samples.
261  * \param whence Location to measure
262  * \return 0 on success, -1 on failure.
263  */
264 static int ogg_speex_seek(struct ast_filestream *s, off_t sample_offset, int whence)
265 {
266         ast_log(LOG_WARNING, "Seeking is not supported on OGG/Speex streams!\n");
267         return -1;
268 }
269
270 static off_t ogg_speex_tell(struct ast_filestream *s)
271 {
272         ast_log(LOG_WARNING, "Telling is not supported on OGG/Speex streams!\n");
273         return -1;
274 }
275
276 static struct ast_format_def speex_f = {
277         .name = "ogg_speex",
278         .exts = "spx",
279         .open = ogg_speex_open,
280         .seek = ogg_speex_seek,
281         .trunc = ogg_speex_trunc,
282         .tell = ogg_speex_tell,
283         .read = ogg_speex_read,
284         .close = ogg_speex_close,
285         .buf_size = BUF_SIZE + AST_FRIENDLY_OFFSET,
286         .desc_size = sizeof(struct speex_desc),
287 };
288
289 static struct ast_format_def speex16_f = {
290         .name = "ogg_speex16",
291         .exts = "spx16",
292         .open = ogg_speex_open,
293         .seek = ogg_speex_seek,
294         .trunc = ogg_speex_trunc,
295         .tell = ogg_speex_tell,
296         .read = ogg_speex_read,
297         .close = ogg_speex_close,
298         .buf_size = BUF_SIZE + AST_FRIENDLY_OFFSET,
299         .desc_size = sizeof(struct speex_desc),
300 };
301
302 static struct ast_format_def speex32_f = {
303         .name = "ogg_speex32",
304         .exts = "spx32",
305         .open = ogg_speex_open,
306         .seek = ogg_speex_seek,
307         .trunc = ogg_speex_trunc,
308         .tell = ogg_speex_tell,
309         .read = ogg_speex_read,
310         .close = ogg_speex_close,
311         .buf_size = BUF_SIZE + AST_FRIENDLY_OFFSET,
312         .desc_size = sizeof(struct speex_desc),
313 };
314
315 static int unload_module(void)
316 {
317         int res = 0;
318         res |= ast_format_def_unregister(speex_f.name);
319         res |= ast_format_def_unregister(speex16_f.name);
320         res |= ast_format_def_unregister(speex32_f.name);
321         return res;
322 }
323
324 static int load_module(void)
325 {
326         speex_f.format = ast_format_speex;
327         speex16_f.format = ast_format_speex16;
328         speex32_f.format = ast_format_speex32;
329
330         if (ast_format_def_register(&speex_f) ||
331             ast_format_def_register(&speex16_f) ||
332             ast_format_def_register(&speex32_f)) {
333                 unload_module();
334                 return AST_MODULE_LOAD_DECLINE;
335         }
336
337         return AST_MODULE_LOAD_SUCCESS;
338 }
339
340 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "OGG/Speex audio",
341         .support_level = AST_MODULE_SUPPORT_EXTENDED,
342         .load = load_module,
343         .unload = unload_module,
344         .load_pri = AST_MODPRI_APP_DEPEND
345 );