/*************************************************************************** * Copyright (C) 2013-2021 by Ilya Kotov * * forkotov02@ya.ru * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include #include #include #include #include "decoder_opus.h" // ic functions for libopusfile static int opusread (void *src, unsigned char *buf,int size) { DecoderOpus *d = static_cast(src); return d->input()->read((char *) buf, size); } static int opusseek(void *src, opus_int64 offset, int whence) { DecoderOpus *d = static_cast(src); if (d->input()->isSequential()) return -1; long start = 0; switch (whence) { case SEEK_END: start = d->input()->size(); break; case SEEK_CUR: start = d->input()->pos(); break; case SEEK_SET: default: start = 0; } if (d->input()->seek(start + offset)) return 0; return -1; } static opus_int64 opustell(void *src) { DecoderOpus *d = static_cast(src); return (opus_int64)d->input()->pos(); } // Decoder class DecoderOpus::DecoderOpus(const QString &url, QIODevice *i) : Decoder(i), m_url(url) {} DecoderOpus::~DecoderOpus() { if (m_opusfile) op_free(m_opusfile); m_opusfile = nullptr; } bool DecoderOpus::initialize() { qDebug("DecoderOpus: initialize"); m_chan = 0; m_totalTime = 0; if (!input()) { qDebug("DecoderOpus: cannot initialize. No input"); return false; } OpusFileCallbacks opuscb = { opusread, opusseek, opustell, nullptr, }; m_opusfile = op_open_callbacks(this, &opuscb, nullptr, 0, nullptr); if (!m_opusfile) { qWarning("DecoderOpus: cannot open stream"); return false; } m_bitrate = op_bitrate(m_opusfile, -1) / 1000; if((m_totalTime = op_pcm_total(m_opusfile, -1) / 48) < 0) m_totalTime = 0; const OpusHead *head = op_head(m_opusfile, -1); if (!head) { qWarning("DecoderOpus: unable to read header"); return false; } m_chan = head->channel_count; ChannelMap chmap = findChannelMap(m_chan); if(chmap.isEmpty()) { qWarning("DecoderOpus: unsupported number of channels: %d", m_chan); return false; } setProperty(Qmmp::FORMAT_NAME, "Ogg Opus"); configure(48000, chmap, Qmmp::PCM_FLOAT); //opus codec supports 48 kHz only return true; } qint64 DecoderOpus::totalTime() const { if (!m_opusfile) return 0; return m_totalTime; } int DecoderOpus::bitrate() const { return m_bitrate; } void DecoderOpus::seek(qint64 time) { op_pcm_seek(m_opusfile, time*48); } qint64 DecoderOpus::read(unsigned char *data, qint64 maxSize) { int frames = op_read_float(m_opusfile, (float*) data, maxSize / sizeof(float), nullptr); m_bitrate = op_bitrate_instant(m_opusfile) / 1000; return frames * m_chan * sizeof(float); } //https://tools.ietf.org/id/draft-ietf-codec-oggopus-04.txt ChannelMap DecoderOpus::findChannelMap(int channels) { ChannelMap map; switch (channels) { case 1: map << Qmmp::CHAN_FRONT_LEFT; break; case 2: map << Qmmp::CHAN_FRONT_LEFT << Qmmp::CHAN_FRONT_RIGHT; break; case 3: map << Qmmp::CHAN_FRONT_LEFT << Qmmp::CHAN_FRONT_CENTER << Qmmp::CHAN_FRONT_RIGHT; break; case 4: map << Qmmp::CHAN_FRONT_LEFT << Qmmp::CHAN_FRONT_RIGHT << Qmmp::CHAN_REAR_LEFT << Qmmp::CHAN_REAR_RIGHT; break; case 5: map << Qmmp::CHAN_FRONT_LEFT << Qmmp::CHAN_FRONT_CENTER << Qmmp::CHAN_FRONT_RIGHT << Qmmp::CHAN_REAR_LEFT << Qmmp::CHAN_REAR_RIGHT; break; case 6: map << Qmmp::CHAN_FRONT_LEFT << Qmmp::CHAN_FRONT_CENTER << Qmmp::CHAN_FRONT_RIGHT << Qmmp::CHAN_REAR_LEFT << Qmmp::CHAN_REAR_RIGHT << Qmmp::CHAN_LFE; break; case 7: map << Qmmp::CHAN_FRONT_LEFT << Qmmp::CHAN_FRONT_CENTER << Qmmp::CHAN_FRONT_RIGHT << Qmmp::CHAN_SIDE_LEFT << Qmmp::CHAN_SIDE_RIGHT << Qmmp::CHAN_REAR_CENTER << Qmmp::CHAN_LFE; break; case 8: map << Qmmp::CHAN_FRONT_LEFT << Qmmp::CHAN_FRONT_CENTER << Qmmp::CHAN_FRONT_RIGHT << Qmmp::CHAN_SIDE_LEFT << Qmmp::CHAN_SIDE_RIGHT << Qmmp::CHAN_REAR_LEFT << Qmmp::CHAN_REAR_RIGHT << Qmmp::CHAN_LFE; break; default: ; } return map; }