Asterisk - The Open Source Telephony Project  21.4.1
format_ogg_speex.c
Go to the documentation of this file.
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 Truncate 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 static int ogg_speex_write(struct ast_filestream *s, struct ast_frame *f)
258 {
259  ast_log(LOG_WARNING, "Writing is not supported on OGG/Speex streams!\n");
260  return -1;
261 }
262 
263 /*!
264  * \brief Seek to a specific position in an OGG/Speex filestream.
265  * \param s The filestream to truncate.
266  * \param sample_offset New position for the filestream, measured in 8KHz samples.
267  * \param whence Location to measure
268  * \return 0 on success, -1 on failure.
269  */
270 static int ogg_speex_seek(struct ast_filestream *s, off_t sample_offset, int whence)
271 {
272  ast_log(LOG_WARNING, "Seeking is not supported on OGG/Speex streams!\n");
273  return -1;
274 }
275 
276 static off_t ogg_speex_tell(struct ast_filestream *s)
277 {
278  ast_log(LOG_WARNING, "Telling is not supported on OGG/Speex streams!\n");
279  return -1;
280 }
281 
282 static struct ast_format_def speex_f = {
283  .name = "ogg_speex",
284  .exts = "spx",
285  .open = ogg_speex_open,
286  .write = ogg_speex_write,
287  .seek = ogg_speex_seek,
288  .trunc = ogg_speex_trunc,
289  .tell = ogg_speex_tell,
290  .read = ogg_speex_read,
291  .close = ogg_speex_close,
292  .buf_size = BUF_SIZE + AST_FRIENDLY_OFFSET,
293  .desc_size = sizeof(struct speex_desc),
294 };
295 
296 static struct ast_format_def speex16_f = {
297  .name = "ogg_speex16",
298  .exts = "spx16",
299  .open = ogg_speex_open,
300  .write = ogg_speex_write,
301  .seek = ogg_speex_seek,
302  .trunc = ogg_speex_trunc,
303  .tell = ogg_speex_tell,
304  .read = ogg_speex_read,
305  .close = ogg_speex_close,
306  .buf_size = BUF_SIZE + AST_FRIENDLY_OFFSET,
307  .desc_size = sizeof(struct speex_desc),
308 };
309 
310 static struct ast_format_def speex32_f = {
311  .name = "ogg_speex32",
312  .exts = "spx32",
313  .open = ogg_speex_open,
314  .write = ogg_speex_write,
315  .seek = ogg_speex_seek,
316  .trunc = ogg_speex_trunc,
317  .tell = ogg_speex_tell,
318  .read = ogg_speex_read,
319  .close = ogg_speex_close,
320  .buf_size = BUF_SIZE + AST_FRIENDLY_OFFSET,
321  .desc_size = sizeof(struct speex_desc),
322 };
323 
324 static int unload_module(void)
325 {
326  int res = 0;
327  res |= ast_format_def_unregister(speex_f.name);
328  res |= ast_format_def_unregister(speex16_f.name);
329  res |= ast_format_def_unregister(speex32_f.name);
330  return res;
331 }
332 
333 static int load_module(void)
334 {
335  speex_f.format = ast_format_speex;
336  speex16_f.format = ast_format_speex16;
337  speex32_f.format = ast_format_speex32;
338 
339  if (ast_format_def_register(&speex_f) ||
340  ast_format_def_register(&speex16_f) ||
341  ast_format_def_register(&speex32_f)) {
342  unload_module();
344  }
345 
347 }
348 
349 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "OGG/Speex audio",
350  .support_level = AST_MODULE_SUPPORT_EXTENDED,
351  .load = load_module,
352  .unload = unload_module,
353  .load_pri = AST_MODPRI_APP_DEPEND
354 );
Asterisk main include file. File version handling, generic pbx functions.
static int ogg_speex_trunc(struct ast_filestream *s)
Truncate an OGG/Speex filestream.
int eos
Indicates whether an End of Stream condition has been detected.
static int ogg_speex_seek(struct ast_filestream *s, off_t sample_offset, int whence)
Seek to a specific position in an OGG/Speex filestream.
Each supported file format is described by the following structure.
Definition: mod_format.h:43
Header for providers of file and format handling routines. Clients of these routines should include "...
int ast_format_def_unregister(const char *name)
Unregisters a file format.
Definition: file.c:162
#define AST_FRIENDLY_OFFSET
Offset into a frame's data buffer.
static void ogg_speex_close(struct ast_filestream *fs)
Close a OGG/Speex filestream.
struct ast_format_def * fmt
Definition: mod_format.h:103
struct ast_frame fr
frame produced by read, typically
Definition: mod_format.h:122
unsigned int ast_codec_samples_count(struct ast_frame *frame)
Get the number of samples contained within a frame.
Definition: codec.c:379
struct ast_format * ast_format_speex16
Built-in cached speex at 16kHz format.
Definition: format_cache.c:136
struct ast_format * format
Definition: mod_format.h:48
void * _private
Definition: mod_format.h:124
#define AST_FRAME_SET_BUFFER(fr, _base, _ofs, _datalen)
struct ast_format * ast_format_speex
Built-in cached speex format.
Definition: format_cache.c:131
char name[80]
Definition: mod_format.h:44
union ast_frame::@224 data
Module has failed to load, may be in an inconsistent state.
Definition: module.h:78
static struct ast_frame * ogg_speex_read(struct ast_filestream *fs, int *whennext)
Read a frame full of audio data from the filestream.
This structure is allocated by file.c in one chunk, together with buf_size and desc_size bytes of mem...
Definition: mod_format.h:101
unsigned int ast_format_get_sample_rate(const struct ast_format *format)
Get the sample rate of a media format.
Definition: format.c:379
Data structure associated with a single frame of data.
static int ogg_speex_open(struct ast_filestream *fs)
Create a new OGG/Speex filestream and set it up for reading.
struct ast_format * ast_format_speex32
Built-in cached speex at 32kHz format.
Definition: format_cache.c:141
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
Asterisk module definitions.
Media Format Cache API.