1 /*************************************************************************
2 OpusDecoder.cpp - sub decoder for Opus in an Ogg container
3 -------------------
4 begin : Wed Dec 26 2012
5 copyright : (C) 2012 by Thomas Eschenbacher
6 email : Thomas.Eschenbacher@gmx.de
7 ***************************************************************************/
8
9 /***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
18 #include "config.h"
19
20 #include <limits>
21 #include <math.h>
22 #include <stdlib.h>
23 #include <new>
24
25 #include <opus/opus_defines.h>
26
27 #include <QApplication>
28 #include <QDate>
29 #include <QIODevice>
30 #include <QString>
31 #include <QtEndian>
32
33 #include <KLocalizedString>
34
35 #include "libkwave/BitrateMode.h"
36 #include "libkwave/Compression.h"
37 #include "libkwave/Connect.h"
38 #include "libkwave/MessageBox.h"
39 #include "libkwave/MultiStreamWriter.h"
40 #include "libkwave/MultiTrackSource.h"
41 #include "libkwave/MultiWriter.h"
42 #include "libkwave/Sample.h"
43 #include "libkwave/SampleArray.h"
44 #include "libkwave/StandardBitrates.h"
45 #include "libkwave/Utils.h"
46 #include "libkwave/Writer.h"
47 #include "libkwave/modules/RateConverter.h"
48
49 #include "OpusCommon.h"
50 #include "OpusDecoder.h"
51
52 /** maximum frame size in samples, 120ms at 48000 */
53 #define MAX_FRAME_SIZE (960 * 6)
54
55 //***************************************************************************
OpusDecoder(QIODevice * source,ogg_sync_state & oy,ogg_stream_state & os,ogg_page & og,ogg_packet & op)56 Kwave::OpusDecoder::OpusDecoder(QIODevice *source,
57 ogg_sync_state &oy,
58 ogg_stream_state &os,
59 ogg_page &og,
60 ogg_packet &op)
61 :m_source(source), m_stream_start_pos(0), m_samples_written(0),
62 m_oy(oy), m_os(os), m_og(og), m_op(op), m_opus_decoder(Q_NULLPTR),
63 m_comments_map(), m_raw_buffer(Q_NULLPTR), m_buffer(Q_NULLPTR),
64 m_rate_converter(Q_NULLPTR),
65 m_output_is_connected(false),
66 m_packet_count(0), m_samples_raw(0), m_bytes_count(0),
67 m_packet_len_min(0), m_packet_len_max(0),
68 m_packet_size_min(0), m_packet_size_max(0),
69 m_granule_first(0), m_granule_last(0), m_granule_offset(0),
70 m_preskip(0)
71 {
72 }
73
74 //***************************************************************************
parseComment(Kwave::FileInfo & info,const QString & comment)75 void Kwave::OpusDecoder::parseComment(Kwave::FileInfo &info,
76 const QString &comment)
77 {
78 // assume that there is a '=' between tag and value
79 int pos = comment.indexOf(_("="));
80 if (pos < 1) {
81 qWarning("OpusDecoder: malformed comment: '%s'", DBG(comment));
82 return;
83 }
84
85 // split into tag and value
86 QString tag = comment.left(pos).toUpper();
87 QString val = comment.mid(pos + 1).trimmed();
88
89 // special handling for gain
90 if ((tag == _("REPLAY_TRACK_GAIN")) || (tag == _("REPLAY_ALBUM_GAIN"))) {
91 // gain in dB:
92 // remove "dB" from the end
93 val = val.left(val.indexOf(_("dB"), 0, Qt::CaseInsensitive)).trimmed();
94
95 // convert to a integer Q8 dB value
96 bool ok = false;
97 int q8gain = Kwave::toInt(rint(val.toDouble(&ok) * 256.0));
98 if (ok && q8gain) {
99 m_opus_header.gain += q8gain;
100 qDebug(" OpusDecoder: %s %+0.1g dB", DBG(tag),
101 static_cast<double>(q8gain) / 256.0);
102 return;
103 }
104 } else if ((tag == _("R128_TRACK_GAIN")) || (tag == _("R128_ALBUM_GAIN"))) {
105 // R128_... already is a 7.8 integer value
106 bool ok = false;
107 int q8gain = Kwave::toInt(rint(val.toDouble(&ok)));
108 if (ok && q8gain) {
109 m_opus_header.gain += q8gain;
110 qDebug(" OpusDecoder: %s %+0.1g dB", DBG(tag),
111 static_cast<double>(q8gain) / 256.0);
112 return;
113 }
114 }
115
116 // check for unknown properties
117 if (!m_comments_map.contains(tag)) {
118 qDebug("unsupported tag '%s', value='%s'", DBG(tag), DBG(val));
119 return;
120 }
121
122 // set the new value
123 Kwave::FileProperty property = m_comments_map[tag];
124 if (info.contains(property)) {
125 // property already exists, append it
126 val = info.get(property).toString() + _("; ") + val;
127 }
128
129 info.set(property, val);
130 }
131
132 //***************************************************************************
parseOpusTags(QWidget * widget,Kwave::FileInfo & info)133 int Kwave::OpusDecoder::parseOpusTags(QWidget *widget, Kwave::FileInfo &info)
134 {
135 bool comments_ok = false;
136 unsigned int counter = 0;
137 while (counter < 1) {
138 while(counter < 1) {
139 int result = ogg_sync_pageout(&m_oy, &m_og);
140 if (result == 0) break; // Need more data
141 if (result == 1) {
142 ogg_stream_pagein(&m_os, &m_og);
143 counter++;
144 }
145 }
146
147 // no harm in not checking before adding more
148 char *buffer = ogg_sync_buffer(&m_oy, 4096);
149 qint64 bytes = m_source->read(buffer, 4096);
150 if (!bytes && counter < 1) {
151 Kwave::MessageBox::error(widget, i18n(
152 "End of file before finding Opus Comment headers."));
153 return -1;
154 }
155 ogg_sync_wrote(&m_oy, static_cast<long int>(bytes));
156 }
157
158 bool first_packet = true;
159 unsigned int fields = 0;
160 while (ogg_stream_packetout(&m_os, &m_op) == 1) {
161 const unsigned char *c = m_op.packet;
162 unsigned long int length = m_op.bytes;
163
164 // check length of comments and magic value
165 if (length < 16) {
166 qWarning("OpusDecoder::parseHeader(): comment length < 16 (%lu)",
167 length);
168 break;
169 }
170 if (memcmp(c, "OpusTags", 8) != 0) {
171 qWarning("OpusDecoder::parseHeader(): OpusTags magic not found");
172 break;
173 }
174 c += 8;
175 length -= 8; // length is [0...8] now
176
177 if (first_packet) {
178 // the start of the first packet contains the software
179
180 // read length of the comment
181 quint32 len = qFromLittleEndian<quint32>(c);
182 c += 4;
183 length -= 4; // length is [0...4] now
184 if (len > length) {
185 // comment extends beyond end of packet
186 qWarning("OpusDecoder::parseHeader(): encoder name truncated "
187 "(len=%u, max=%lu)", len, length);
188 len = static_cast<quint32>(length);
189 }
190 QString encoder =
191 QString::fromUtf8(reinterpret_cast<const char *>(c), len);
192 c += len;
193 length -= len;
194 qDebug(" Encoded with '%s'", DBG(encoder));
195 /* info.set(Kwave::INF_SOFTWARE, software); */
196
197 if (length < 4) {
198 qWarning("OpusDecoder::parseHeader(): tag is too short (%lu)",
199 length);
200 break;
201 }
202 fields = qFromLittleEndian<quint32>(c);
203 c += 4;
204 length -= 4;
205
206 first_packet = false;
207 }
208
209 while (fields && (length > 4)) {
210 quint32 len = qFromLittleEndian<quint32>(c);
211 c += 4;
212 length -= 4;
213 if (len > length) {
214 // comment extends beyond end of packet
215 qWarning("OpusDecoder::parseHeader(): comment truncated "
216 "(len=%u, max=%lu)", len, length);
217 len = static_cast<quint32>(length);
218 }
219 QString comment =
220 QString::fromUtf8(reinterpret_cast<const char *>(c), len);
221 c += len;
222 length -= len;
223
224 parseComment(info, comment);
225
226 fields--;
227 }
228
229 comments_ok = (fields == 0);
230 break;
231 }
232
233 if (!comments_ok) {
234 qDebug("OpusDecoder: WARNING: no comment block found!?");
235 }
236
237 return 1;
238 }
239
240 //***************************************************************************
parseOpusHead(QWidget * widget,Kwave::FileInfo & info)241 int Kwave::OpusDecoder::parseOpusHead(QWidget *widget, Kwave::FileInfo &info)
242 {
243 memset(&m_opus_header, 0x00, sizeof(m_opus_header));
244 memset(&m_opus_header.map, 0xFF, sizeof(m_opus_header.map));
245
246 bool header_ok = false;
247 do {
248 if (!m_op.b_o_s || (m_op.bytes < 19)) {
249 qWarning("OpusDecoder::parseHeader(): header too short");
250 break;
251 }
252
253 Kwave::opus_header_t *h =
254 reinterpret_cast<Kwave::opus_header_t *>(m_op.packet);
255
256 // magic value
257 memcpy(&(m_opus_header.magic[0]), &(h->magic[0]),
258 sizeof(m_opus_header.magic));
259 if (memcmp(&(m_opus_header.magic[0]), "OpusHead", 8) != 0) {
260 qWarning("OpusDecoder::parseHeader(): OpusHead magic not found");
261 break; // this is no Opus stream ?
262 }
263
264 // version number, only major version 0 is supported
265 m_opus_header.version = h->version;
266 if ((m_opus_header.version >> 6) != 0) {
267 qWarning("OpusDecoder::parseHeader(): unsupported version %d.%d",
268 (m_opus_header.version >> 6), (m_opus_header.version & 0x3F));
269 break; // unsupported version
270 }
271
272 // number of channels
273 m_opus_header.channels = h->channels;
274 if (m_opus_header.channels < 1) {
275 qWarning("OpusDecoder::parseHeader(): channels==0");
276 break; // no channels?
277 }
278
279 // preskip
280 m_opus_header.preskip = qFromLittleEndian<quint16>(h->preskip);
281
282 // sample rate
283 m_opus_header.sample_rate = qFromLittleEndian<quint32>(h->sample_rate);
284
285 // for debugging rate conversion issues:
286 // m_opus_header.sample_rate =
287 // _opus_next_sample_rate(m_opus_header.sample_rate);
288
289 // gain
290 m_opus_header.gain = qFromLittleEndian<quint16>(h->gain);
291
292 // channel mapping
293 m_opus_header.channel_mapping = h->channel_mapping;
294
295 // multi stream support
296 if (m_opus_header.channel_mapping) {
297 // number of streams, must be >= 1
298 m_opus_header.streams = h->streams;
299 if (m_opus_header.streams < 1) {
300 qWarning("OpusDecoder::parseHeader(): streams==0");
301 break;
302 }
303
304 // number of coupled
305 m_opus_header.coupled = h->coupled;
306 if (m_opus_header.coupled > m_opus_header.streams) {
307 qWarning("OpusDecoder::parseHeader(): coupled=%d > %d",
308 m_opus_header.coupled, m_opus_header.streams);
309 break; // must be <= number of streams
310 }
311 if ((m_opus_header.coupled + m_opus_header.streams) >= 256) {
312 qWarning("OpusDecoder::parseHeader(): "
313 "coupled + streams = %d (> 256)",
314 m_opus_header.coupled + m_opus_header.streams);
315 break; // must be less that 256
316 }
317
318 // coupling map
319 unsigned int i;
320 for (i = 0; i < m_opus_header.channels; i++) {
321 quint8 c = h->map[i];
322 if (c > (m_opus_header.coupled + m_opus_header.streams)) {
323 qWarning("OpusDecoder::parseHeader(): mapping[%d]"
324 "out of range: %d (> %d)", i, c,
325 m_opus_header.coupled + m_opus_header.streams);
326 break; // mapping out of range
327 }
328 if (m_opus_header.map[i] != 0xFF) {
329 qWarning("OpusDecoder::parseHeader(): mapping[%d]"
330 "already occupied: %d", i,
331 m_opus_header.map[i]);
332 break; // mapping already occupied
333 }
334
335 m_opus_header.map[i] = c;
336 }
337 if (i < m_opus_header.channels) break; // something went wrong
338 } else {
339 if (m_opus_header.channels > 2) {
340 qWarning("OpusDecoder::parseHeader(): channels > 2"
341 "(%d) but no mapping", m_opus_header.channels);
342 break;
343 }
344 m_opus_header.streams = 1;
345 m_opus_header.coupled = (m_opus_header.channels > 1) ? 1 : 0;
346 m_opus_header.map[0] = 0;
347 m_opus_header.map[1] = 1;
348 }
349
350 header_ok = true;
351 } while (false);
352
353 if (!header_ok) {
354 // error case; not an Opus header
355 Kwave::MessageBox::error(widget, i18n(
356 "This Ogg bitstream does not contain valid Opus audio data."));
357 return -1;
358 }
359
360 // get the standard properties
361 info.setTracks(m_opus_header.channels);
362 info.setRate(m_opus_header.sample_rate);
363 info.set(Kwave::INF_COMPRESSION, Kwave::Compression::OGG_OPUS);
364
365 return 1;
366 }
367
368 //***************************************************************************
open(QWidget * widget,Kwave::FileInfo & info)369 int Kwave::OpusDecoder::open(QWidget *widget, Kwave::FileInfo &info)
370 {
371 // extract the initial header from the first page and verify that the
372 // Ogg bitstream is in fact valid/usable Opus data
373 if (parseOpusHead(widget, info) < 1)
374 return -1;
375
376 // extract the second packet, it should contain the comments...
377 if (parseOpusTags(widget, info) < 1)
378 return -1;
379
380 // allocate memory for the output data
381 if (m_raw_buffer) free(m_raw_buffer);
382 m_raw_buffer = static_cast<float *>(
383 malloc(sizeof(float) * MAX_FRAME_SIZE * m_opus_header.channels));
384 if (!m_raw_buffer) {
385 Kwave::MessageBox::error(widget, i18n("Out of memory"));
386 return -1;
387 }
388
389 int err = OPUS_BAD_ARG;
390 qDebug(" sample rate = %d", m_opus_header.sample_rate);
391 m_opus_decoder = opus_multistream_decoder_create(
392 Kwave::opus_next_sample_rate(m_opus_header.sample_rate),
393 m_opus_header.channels,
394 m_opus_header.streams,
395 m_opus_header.coupled,
396 m_opus_header.map,
397 &err
398 );
399
400 if ((err != OPUS_OK) || !m_opus_decoder) {
401 info.dump();
402 Kwave::MessageBox::error(widget, Kwave::opus_error(err),
403 i18n("Opus decoder failed"));
404 return -1;
405 }
406
407 #ifdef OPUS_SET_GAIN
408 if (m_opus_header.gain) {
409 err = opus_multistream_decoder_ctl(
410 m_opus_decoder, OPUS_SET_GAIN(m_opus_header.gain)
411 );
412 if (err == OPUS_OK) {
413 qDebug(" OpusDecoder: gain adjusted to %d Q8dB",
414 m_opus_header.gain);
415 m_opus_header.gain = 0;
416 }
417 }
418 #endif /* OPUS_SET_GAIN */
419
420 const unsigned int tracks = m_opus_header.channels;
421 int rate_orig = m_opus_header.sample_rate;
422 int rate_supp = Kwave::opus_next_sample_rate(rate_orig);
423
424 // create a multi track sample buffer
425 m_buffer = new(std::nothrow)
426 Kwave::MultiTrackSink<Kwave::SampleBuffer, true>(tracks);
427 Q_ASSERT(m_buffer);
428 if (!m_buffer) return -1;
429
430 // handle sample rate conversion
431 if (rate_orig != rate_supp) {
432 bool ok = true;
433
434 qDebug(" OpusDecoder::open(): converting sample rate: %d -> %d",
435 rate_supp, rate_orig);
436
437 m_rate_converter = new(std::nothrow)
438 Kwave::MultiTrackSource<Kwave::RateConverter, true>(tracks);
439 Q_ASSERT(m_rate_converter);
440 if (!m_rate_converter) ok = false;
441
442 if (ok) {
443 double rate_from = static_cast<double>(rate_supp);
444 double rate_to = static_cast<double>(rate_orig);
445 m_rate_converter->setAttribute(
446 SLOT(setRatio(QVariant)),
447 QVariant(rate_to / rate_from)
448 );
449
450 ok = Kwave::connect(
451 *m_buffer, SIGNAL(output(Kwave::SampleArray)),
452 *m_rate_converter, SLOT(input(Kwave::SampleArray))
453 );
454 }
455
456 if (!ok) {
457 qWarning("OpusDecoder::open(): creating rate converter failed!");
458 }
459
460 if (!ok) {
461 qDebug("OpusDecoder::open(): sample rate %d is not "
462 "supported but rate conversion is not available "
463 "-> setting to %d", rate_orig, rate_supp);
464 m_opus_header.sample_rate = rate_supp;
465 }
466 }
467
468 // estimate the length of the file from file size, bitrate, channels
469 if (!m_source->isSequential()) {
470 qint64 file_size = m_source->size();
471 qreal bitrate = 196000; // just guessed
472 qreal rate = rate_orig;
473 qreal seconds = static_cast<qreal>(file_size) / (bitrate / 8);
474 sample_index_t samples = static_cast<sample_index_t>(seconds * rate);
475
476 qDebug(" OpusDecoder: estimated length: %llu samples", samples);
477 info.set(Kwave::INF_ESTIMATED_LENGTH, samples);
478 }
479
480 m_stream_start_pos = m_source->pos();
481 m_samples_written = 0;
482 m_packet_count = 0;
483 m_samples_raw = 0;
484 m_bytes_count = 0;
485
486 m_packet_len_min = std::numeric_limits<int>::max();
487 m_packet_len_max = 0;
488 m_packet_size_min = std::numeric_limits<int>::max();
489 m_packet_size_max = 0;
490
491 m_preskip = m_opus_header.preskip;
492 m_granule_first = std::numeric_limits<qint64>::max();
493 m_granule_last = 0;
494 m_granule_offset = 0;
495
496 return 1;
497 }
498
499 //***************************************************************************
decode(Kwave::MultiWriter & dst)500 int Kwave::OpusDecoder::decode(Kwave::MultiWriter &dst)
501 {
502 if (!m_opus_decoder || !m_raw_buffer || !m_buffer)
503 return -1;
504
505 // get some statistical data, for bitrate mode detection
506 m_packet_count++;
507
508 int frames = opus_packet_get_nb_frames(
509 m_op.packet,
510 static_cast<opus_int32>(m_op.bytes)
511 );
512 if(frames < 1 || frames > 48) {
513 qWarning("WARNING: Invalid packet TOC in packet #%llu",
514 static_cast<unsigned long long int>(m_op.packetno));
515 }
516 int spf = opus_packet_get_samples_per_frame(m_op.packet, 48000);
517 int spp = frames * spf;
518 if (spp < 120 || spp > 5760 || (spp % 120) != 0) {
519 qWarning("WARNING: Invalid packet TOC in packet #%llu",
520 static_cast<unsigned long long int>(m_op.packetno));
521 }
522
523 if (spp < m_packet_len_min) m_packet_len_min = spp;
524 if (spp > m_packet_len_max) m_packet_len_max = spp;
525 if (m_op.bytes < m_packet_size_min)
526 m_packet_size_min = Kwave::toInt(m_op.bytes);
527 if (m_op.bytes > m_packet_size_max)
528 m_packet_size_max = Kwave::toInt(m_op.bytes);
529
530 // total count of samples and bytes
531 m_samples_raw += spp;
532 m_bytes_count += m_op.bytes;
533
534 // granule pos handling
535 const qint64 gp = static_cast<qint64>(ogg_page_granulepos(&m_og));
536 if (gp >= 0) {
537 if (gp < m_granule_first) m_granule_first = gp;
538 if (gp > m_granule_last) m_granule_last = gp;
539 if (m_granule_first == m_granule_last) {
540 // calculate how many samples might be missing at the start
541 m_granule_offset = m_granule_first - m_samples_raw;
542 }
543 }
544
545 // decode the audio data into a buffer with float values
546 int length = opus_multistream_decode_float(
547 m_opus_decoder,
548 static_cast<const unsigned char *>(m_op.packet),
549 static_cast<opus_int32>(m_op.bytes),
550 m_raw_buffer, MAX_FRAME_SIZE, 0
551 );
552 if (length <= 0) {
553 qWarning("OpusDecoder::decode() failed: '%s'",
554 DBG(Kwave::opus_error(length)));
555 return -1;
556 }
557 Q_ASSERT(length <= MAX_FRAME_SIZE);
558
559 // manually apply the gain if necessary
560 if (m_opus_header.gain) {
561 const float g = powf(10.0f, m_opus_header.gain / (20.0f * 256.0f));
562 for (int i = 0; i < (length * m_opus_header.channels); i++)
563 m_raw_buffer[i] *= g;
564 }
565
566 const unsigned int tracks = m_opus_header.channels;
567
568 if (!m_output_is_connected) {
569 Kwave::StreamObject *src =
570 (m_rate_converter) ? m_rate_converter : m_buffer;
571 if (!Kwave::connect(*src, SIGNAL(output(Kwave::SampleArray)),
572 dst, SLOT(input(Kwave::SampleArray))) )
573 {
574 qWarning("OpusDecoder::decode() connecting output failed");
575 return -1;
576 }
577 m_output_is_connected = true;
578 }
579
580 // handle preskip
581 const float *p = m_raw_buffer;
582 if (m_preskip) {
583 if (m_preskip >= length) {
584 m_preskip -= length;
585 return 0; // skip the complete buffer
586 }
587 // shrink buffer by preskip samples
588 length -= m_preskip;
589 p += m_preskip * tracks;
590 m_preskip = 0;
591 }
592
593 // check trailing data at the end (after the granule)
594 const sample_index_t last = (m_granule_last - m_granule_offset) -
595 m_opus_header.preskip;
596
597 if ((m_samples_written + length) > last) {
598 int diff = Kwave::toInt((m_samples_written + length) - last);
599 if (diff > length) return 0;
600 length -= diff;
601 }
602
603 // convert the buffer from float to sample_t, blockwise...
604 for (unsigned int t = 0; t < tracks; t++) {
605 Kwave::SampleBuffer *buffer = m_buffer->at(t);
606 const float *in = p + t;
607 for (int frame = 0; frame < length; frame++) {
608 // scale, use some primitive noise shaping + clipping
609 double noise = (drand48() - double(0.5)) / double(SAMPLE_MAX);
610 double d = static_cast<double>(*in);
611 sample_t s = qBound<sample_t>(
612 SAMPLE_MIN, double2sample(d + noise), SAMPLE_MAX
613 );
614 buffer->put(s);
615 in += tracks;
616 }
617 }
618
619 m_samples_written += length;
620
621 // update the progress bar
622 QApplication::processEvents();
623
624 return 0;
625 }
626
627 //***************************************************************************
reset()628 void Kwave::OpusDecoder::reset()
629 {
630 if (m_opus_decoder)
631 opus_multistream_decoder_destroy(m_opus_decoder);
632 m_opus_decoder = Q_NULLPTR;
633
634 if (m_raw_buffer)
635 free(m_raw_buffer);
636 m_raw_buffer = Q_NULLPTR;
637
638 }
639
640 //***************************************************************************
close(Kwave::FileInfo & info)641 void Kwave::OpusDecoder::close(Kwave::FileInfo &info)
642 {
643 // flush the buffer of the sample rate converter, to avoid missing
644 // samples due to the limitations of libsamplerate
645 if (m_buffer) {
646 const unsigned int tracks = m_opus_header.channels;
647 for (unsigned int t = 0; t < tracks; t++) {
648 Kwave::SampleBuffer *buffer = m_buffer->at(t);
649 Q_ASSERT(buffer);
650 buffer->finished();
651 }
652 }
653
654 if (m_buffer) delete m_buffer;
655 m_buffer = Q_NULLPTR;
656
657 delete m_rate_converter;
658 m_rate_converter = Q_NULLPTR;
659
660 m_output_is_connected = false;
661
662 qDebug(" OpusDecoder: packet count=%u", m_packet_count);
663 qDebug(" OpusDecoder: packet length: %d...%d samples",
664 m_packet_len_min, m_packet_len_max);
665 qDebug(" OpusDecoder: packet size: %d...%d bytes",
666 m_packet_size_min, m_packet_size_max);
667
668 if ( (m_packet_len_min == m_packet_len_max) &&
669 (m_packet_size_min == m_packet_size_max) )
670 {
671 // detected hard CBR mode
672 info.set(INF_BITRATE_MODE, Kwave::BITRATE_MODE_CBR_HARD);
673 qDebug(" OpusDecoder: hard CBR mode");
674 } else {
675 // otherwise default to VBR mode
676 info.set(INF_BITRATE_MODE, Kwave::BITRATE_MODE_VBR);
677 qDebug(" OpusDecoder: VBR mode");
678 }
679
680 // determine the avarage frame length in ms
681 qreal avg_ms = (static_cast<qreal>(m_samples_raw) /
682 static_cast<qreal>(m_packet_count)) / 48.0;
683 qDebug(" OpusDecoder: average frame length: %0.1f ms", avg_ms);
684 info.set(INF_OPUS_FRAME_LEN, QVariant(avg_ms));
685
686 // calculate the bitrate == n_bits / n_seconds
687 // n_bits = n_bytes * 8
688 // n_seconds = n_samples / sample_rate
689 // => bitrate = (n_bytes * 8) / (n_samples / sample_rate)
690 const double sr = Kwave::opus_next_sample_rate(m_opus_header.sample_rate);
691 int bitrate = Kwave::toInt((static_cast<double>(m_bytes_count * 8) * sr) /
692 static_cast<double>(m_samples_written));
693 qDebug(" OpusDecoder: average bitrate: %d bits/sec", bitrate);
694 info.set(INF_BITRATE_NOMINAL, QVariant(bitrate));
695
696 reset();
697 }
698
699 //***************************************************************************
700 //***************************************************************************
701