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