summaryrefslogtreecommitdiff
path: root/lib/rbcodec/codecs/opus.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rbcodec/codecs/opus.c')
-rw-r--r--lib/rbcodec/codecs/opus.c461
1 files changed, 461 insertions, 0 deletions
diff --git a/lib/rbcodec/codecs/opus.c b/lib/rbcodec/codecs/opus.c
new file mode 100644
index 0000000000..19bdb8daae
--- /dev/null
+++ b/lib/rbcodec/codecs/opus.c
@@ -0,0 +1,461 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2012 Frederik M.J. Vestre
11 * Based on speex.c codec interface:
12 * Copyright (C) 2006 Frederik M.J. Vestre
13 * Based on vorbis.c codec interface:
14 * Copyright (C) 2002 Björn Stenberg
15 *
16 * This program is free software; you can redistribute it and/or
17 * modify it under the terms of the GNU General Public License
18 * as published by the Free Software Foundation; either version 2
19 * of the License, or (at your option) any later version.
20 *
21 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
22 * KIND, either express or implied.
23 *
24 ****************************************************************************/
25
26#include "codeclib.h"
27#include "inttypes.h"
28#include "libopus/opus.h"
29#include "libopus/opus_header.h"
30
31
32#include "libopus/ogg/ogg.h"
33#ifdef SIMULATOR
34#include <tlsf.h>
35#endif
36
37CODEC_HEADER
38
39#define SEEK_REWIND 3840 /* 80 ms @ 48 kHz */
40
41/* the opus pseudo stack pointer */
42extern char *global_stack;
43
44/* Room for 120 ms of stereo audio at 48 kHz */
45#define MAX_FRAME_SIZE (2*120*48)
46#define CHUNKSIZE (16*1024)
47#define SEEK_CHUNKSIZE 7*CHUNKSIZE
48
49static int get_more_data(ogg_sync_state *oy)
50{
51 int bytes;
52 char *buffer;
53
54 buffer = (char *)ogg_sync_buffer(oy, CHUNKSIZE);
55 bytes = ci->read_filebuf(buffer, CHUNKSIZE);
56 ogg_sync_wrote(oy,bytes);
57
58 return bytes;
59}
60/* The read/seek functions track absolute position within the stream */
61static int64_t get_next_page(ogg_sync_state *oy, ogg_page *og,
62 int64_t boundary)
63{
64 int64_t localoffset = ci->curpos;
65 long more;
66 long ret;
67
68 if (boundary > 0)
69 boundary += ci->curpos;
70
71 while (1) {
72 more = ogg_sync_pageseek(oy,og);
73
74 if (more < 0) {
75 /* skipped n bytes */
76 localoffset-=more;
77 } else {
78 if (more == 0) {
79 /* send more data */
80 if(!boundary)return(-1);
81 {
82 ret = get_more_data(oy);
83 if (ret == 0)
84 return(-2);
85
86 if (ret < 0)
87 return(-3);
88 }
89 } else {
90 /* got a page. Return the offset at the page beginning,
91 advance the internal offset past the page end */
92
93 int64_t ret=localoffset;
94
95 return(ret);
96 }
97 }
98 }
99}
100
101static int64_t seek_backwards(ogg_sync_state *oy, ogg_page *og,
102 int64_t wantedpos)
103{
104 int64_t crofs;
105 int64_t *curoffset=&crofs;
106 *curoffset=ci->curpos;
107 int64_t begin=*curoffset;
108 int64_t end=begin;
109 int64_t ret;
110 int64_t offset=-1;
111 int64_t avgpagelen=-1;
112 int64_t lastgranule=-1;
113
114 short time = -1;
115
116 while (offset == -1) {
117
118 begin -= SEEK_CHUNKSIZE;
119
120 if (begin < 0) {
121 if (time < 0) {
122 begin = 0;
123 time++;
124 } else {
125 LOGF("Can't seek that early:%lld\n",begin);
126 return -3; /* too early */
127 }
128 }
129
130 *curoffset = begin;
131
132 ci->seek_buffer(*curoffset);
133
134 ogg_sync_reset(oy);
135
136 lastgranule = -1;
137
138 while (*curoffset < end) {
139 ret = get_next_page(oy,og,end-*curoffset);
140
141 if (ret > 0) {
142 if (lastgranule != -1) {
143 if (avgpagelen < 0)
144 avgpagelen = (ogg_page_granulepos(og)-lastgranule);
145 else
146 avgpagelen=((ogg_page_granulepos(og)-lastgranule)
147 + avgpagelen) / 2;
148 }
149
150 lastgranule=ogg_page_granulepos(og);
151
152 if ((lastgranule - (avgpagelen/4)) < wantedpos &&
153 (lastgranule + avgpagelen + (avgpagelen/4)) > wantedpos) {
154
155 /*wanted offset found Yeay!*/
156
157 /*LOGF("GnPagefound:%d,%d,%d,%d\n",ret,
158 lastgranule,wantedpos,avgpagelen);*/
159
160 return ret;
161
162 } else if (lastgranule > wantedpos) { /*too late, seek more*/
163 if (offset != -1) {
164 LOGF("Toolate, returnanyway:%lld,%lld,%lld,%lld\n",
165 ret,lastgranule,wantedpos,avgpagelen);
166 return ret;
167 }
168 break;
169 } else{ /*if (ogg_page_granulepos(&og)<wantedpos)*/
170 /*too early*/
171 offset = ret;
172 continue;
173 }
174 } else if (ret == -3)
175 return(-3);
176 else if (ret<=0)
177 break;
178 else if (*curoffset < end) {
179 /*this should not be possible*/
180
181 //LOGF("Seek:get_earlier_page:Offset:not_cached by granule:"\"%d,%d,%d,%d,%d\n",*curoffset,end,begin,wantedpos,curpos);
182
183 offset=ret;
184 }
185 }
186 }
187 return -1;
188}
189
190static int speex_seek_page_granule(int64_t pos, int64_t curpos,
191 ogg_sync_state *oy,
192 int64_t headerssize)
193{
194 /* TODO: Someone may want to try to implement seek to packet,
195 instead of just to page (should be more accurate, not be any
196 faster) */
197
198 int64_t crofs;
199 int64_t *curbyteoffset = &crofs;
200 *curbyteoffset = ci->curpos;
201 int64_t curoffset;
202 curoffset = *curbyteoffset;
203 int64_t offset = 0;
204 ogg_page og = {0,0,0,0};
205 int64_t avgpagelen = -1;
206 int64_t lastgranule = -1;
207
208 if(abs(pos-curpos)>10000 && headerssize>0 && curoffset-headerssize>10000) {
209 /* if seeking for more that 10sec,
210 headersize is known & more than 10kb is played,
211 try to guess a place to seek from the number of
212 bytes playe for this position, this works best when
213 the bitrate is relativly constant.
214 */
215
216 curoffset = (((*curbyteoffset-headerssize) * pos)/curpos)*98/100;
217 if (curoffset < 0)
218 curoffset=0;
219
220 //int64_t toffset=curoffset;
221
222 ci->seek_buffer(curoffset);
223
224 ogg_sync_reset(oy);
225
226 offset = get_next_page(oy,&og,-1);
227
228 if (offset < 0) { /* could not find new page,use old offset */
229 LOGF("Seek/guess/fault:%lld->-<-%d,%lld:%lld,%d,%ld,%d\n",
230 curpos,0,pos,offset,0,
231 ci->curpos,/*stream_length*/0);
232
233 curoffset = *curbyteoffset;
234
235 ci->seek_buffer(curoffset);
236
237 ogg_sync_reset(oy);
238 } else {
239 if (ogg_page_granulepos(&og) == 0 && pos > 5000) {
240 LOGF("SEEK/guess/fault:%lld->-<-%lld,%lld:%lld,%d,%ld,%d\n",
241 curpos,ogg_page_granulepos(&og),pos,
242 offset,0,ci->curpos,/*stream_length*/0);
243
244 curoffset = *curbyteoffset;
245
246 ci->seek_buffer(curoffset);
247
248 ogg_sync_reset(oy);
249 } else {
250 curoffset = offset;
251 curpos = ogg_page_granulepos(&og);
252 }
253 }
254 }
255
256 /* which way do we want to seek? */
257
258 if (curpos > pos) { /* backwards */
259 offset = seek_backwards(oy,&og,pos);
260
261 if (offset > 0) {
262 *curbyteoffset = curoffset;
263 return 1;
264 }
265 } else { /* forwards */
266
267 while ( (offset = get_next_page(oy,&og,-1)) > 0) {
268 if (lastgranule != -1) {
269 if (avgpagelen < 0)
270 avgpagelen = (ogg_page_granulepos(&og) - lastgranule);
271 else
272 avgpagelen = ((ogg_page_granulepos(&og) - lastgranule)
273 + avgpagelen) / 2;
274 }
275
276 lastgranule = ogg_page_granulepos(&og);
277
278 if ( ((lastgranule - (avgpagelen/4)) < pos && ( lastgranule +
279 avgpagelen + (avgpagelen / 4)) > pos) ||
280 lastgranule > pos) {
281
282 /*wanted offset found Yeay!*/
283
284 *curbyteoffset = offset;
285
286 return offset;
287 }
288 }
289 }
290
291 ci->seek_buffer(*curbyteoffset);
292
293 ogg_sync_reset(oy);
294
295 LOGF("Seek failed:%lld\n", offset);
296
297 return -1;
298}
299
300
301/* this is the codec entry point */
302enum codec_status codec_main(enum codec_entry_call_reason reason)
303{
304 (void)reason;
305
306 return CODEC_OK;
307}
308
309/* this is called for each file to process */
310enum codec_status codec_run(void)
311{
312 int error = CODEC_ERROR;
313 intptr_t param;
314 ogg_sync_state oy;
315 ogg_page og;
316 ogg_packet op;
317 ogg_stream_state os;
318 int64_t page_granule = 0;
319 int stream_init = 0;
320 int sample_rate = 48000;
321 OpusDecoder *st = NULL;
322 OpusHeader header;
323 int ret;
324 unsigned long strtoffset = ci->id3->offset;
325 int skip = 0;
326 int64_t seek_target;
327 uint64_t granule_pos;
328
329 /* reset our simple malloc */
330 if (codec_init()) {
331 goto done;
332 }
333 global_stack = 0;
334
335 /* pre-init the ogg_sync_state buffer, so it won't need many reallocs */
336 ogg_sync_init(&oy);
337 oy.storage = 64*1024;
338 oy.data = codec_malloc(oy.storage);
339
340 /* allocate output buffer */
341 uint16_t *output = (uint16_t*) codec_malloc(MAX_FRAME_SIZE*sizeof(uint16_t));
342
343 ci->seek_buffer(0);
344 ci->set_elapsed(0);
345
346 while (1) {
347 enum codec_command_action action = ci->get_command(&param);
348
349 if (action == CODEC_ACTION_HALT)
350 break;
351
352 if (action == CODEC_ACTION_SEEK_TIME) {
353 if (st != NULL) {
354 /* calculate granule to seek to (including seek rewind) */
355 seek_target = (48LL * param) + header.preskip;
356 skip = MIN(seek_target, SEEK_REWIND);
357 seek_target -= skip;
358
359 LOGF("Opus seek page:%lld,%lld,%ld\n",
360 seek_target, page_granule, (long)param);
361 speex_seek_page_granule(seek_target, page_granule, &oy, 0);
362 }
363
364 ci->set_elapsed(param);
365 ci->seek_complete();
366 }
367
368 /*Get the ogg buffer for writing*/
369 if (get_more_data(&oy) < 1) {
370 goto done;
371 }
372
373 /* Loop for all complete pages we got (most likely only one) */
374 while (ogg_sync_pageout(&oy, &og) == 1) {
375 if (stream_init == 0) {
376 ogg_stream_init(&os, ogg_page_serialno(&og));
377 stream_init = 1;
378 }
379
380 /* Add page to the bitstream */
381 ogg_stream_pagein(&os, &og);
382
383 page_granule = ogg_page_granulepos(&og);
384 granule_pos = page_granule;
385
386 while ((ogg_stream_packetout(&os, &op) == 1) && !op.e_o_s) {
387 if (op.packetno == 0){
388 /* identification header */
389
390 if (opus_header_parse(op.packet, op.bytes, &header) == 0) {
391 LOGF("Could not parse header");
392 goto done;
393 }
394 skip = header.preskip;
395
396 st = opus_decoder_create(sample_rate, header.channels, &ret);
397 if (ret != OPUS_OK) {
398 LOGF("opus_decoder_create failed %d", ret);
399 goto done;
400 }
401 LOGF("Decoder inited");
402
403 codec_set_replaygain(ci->id3);
404
405 opus_decoder_ctl(st, OPUS_SET_GAIN(header.gain));
406
407 ci->configure(DSP_SET_FREQUENCY, sample_rate);
408 ci->configure(DSP_SET_SAMPLE_DEPTH, 16);
409 ci->configure(DSP_SET_STEREO_MODE, (header.channels == 2) ?
410 STEREO_INTERLEAVED : STEREO_MONO);
411
412 } else if (op.packetno == 1) {
413 /* Comment header */
414 } else {
415 if (strtoffset) {
416 ci->seek_buffer(strtoffset);
417 ogg_sync_reset(&oy);
418 strtoffset = 0;
419 break;//next page
420 }
421
422 /* report progress */
423 ci->set_elapsed((granule_pos - header.preskip) / 48);
424
425 /* Decode audio packets */
426 ret = opus_decode(st, op.packet, op.bytes, output, MAX_FRAME_SIZE, 0);
427
428 if (ret > 0) {
429 if (skip > 0) {
430 if (ret <= skip) {
431 /* entire output buffer is skipped */
432 skip -= ret;
433 ret = 0;
434 } else {
435 /* part of output buffer is played */
436 ret -= skip;
437 ci->pcmbuf_insert(&output[skip * header.channels], NULL, ret);
438 skip = 0;
439 }
440 } else {
441 /* entire buffer is played */
442 ci->pcmbuf_insert(output, NULL, ret);
443 }
444 granule_pos += ret;
445 } else {
446 if (ret < 0) {
447 LOGF("opus_decode failed %d", ret);
448 goto done;
449 }
450 break;
451 }
452 }
453 }
454 }
455 }
456 LOGF("Returned OK");
457 error = CODEC_OK;
458done:
459 return error;
460}
461