Декодирование аудиоданных Opus

Я пытаюсь декодировать файл Opus обратно в raw 48 кГц. Однако я не могу найти любой образец кода для этого.

мой текущий код такой:

void COpusCodec::Decode(unsigned char* encoded, short* decoded, unsigned int len)
{
     int max_size=960*6;//not sure about this one

     int error;
     dec = opus_decoder_create(48000, 1, &error);//decode to 48kHz mono

     int frame_size=opus_decode(dec, encoded, len, decoded, max_size, 0);
}

аргумент "закодирован" может быть большим количеством данных, поэтому я думаю, что мне нужно разделить его на фреймы. Я не знаю, как это сделать.

и будучи новичком с Opus, я действительно боюсь что-то испортить.

может ли кто-нибудь помочь?

2 ответов


я думаю С источник tarball то, что вы хотите.

это довольно сложно, хотя, из-за всего несвязанного кода, относящегося к

  • кодирование, разбор параметров кодировщика из аргументов командной строки
  • искусственная инъекция потери пакетов
  • случайный выбор размера кадров / изменение на лету
  • inband FEC (что означает декодирование в два буфера, переключение между два)
  • отладка и проверка
  • статистика битовой скорости отчетности

удаление всех этих битов очень нудная работа, как оказалось. Но как только вы это сделаете, вы получите довольно чистый, понятный код, см. ниже.

обратите внимание, что я

  • сохранил код протокола "потеря пакетов" (даже если потеря пакетов не произойдет при чтении из файла) для справки
  • хранил код, который проверяет конечный диапазон после декодирования каждого кадра

в основном потому, что это, похоже, не усложняет код, и вам может быть интересно.

я протестировал эту программу двумя способами:

  • aurally (путем проверки того, что моно wav ранее кодировался с помощью opus_demo был правильно декодирован с помощью этого декодера). Тестовый wav был ~23 Мб, сжатый 2.9 Мб.
  • регрессия протестирована вместе с vanilla opus_demo при вызове с ./opus_demo -d 48000 1 <opus-file> <pcm-file>. Полученный файл имел то же самое md5sum контрольная сумма как одна декодированная с использованием разделенного декодера здесь.

ОСНОВНОЕ ОБНОВЛЕНИЕ I c++ - ified код. Это должно привести вас куда-то с помощью iostreams.

  • обратите внимание на петлю на fin.readsome теперь; этот цикл может быть сделан "асинхронным" (т. е. он может быть возвращен и продолжить чтение при поступлении новых данных (при следующем вызове вашего Decode функции?)[1]
  • я сократил зависимости от opus.h из файла заголовка
  • Я заменил" все " ручное управление памятью стандартной библиотекой (vector, unique_ptr) за исключением безопасности и надежности.
  • я реализовал OpusErrorException класс, производный от класса std::exception, который используется для распространения ошибок от libopus

см. весь код + Makefile здесь: https://github.com/sehe/opus/tree/master/contrib

[1] для истинных асинхронного ввода-вывода (например, через сеть или последовательный communinication) рекомендуется использовать буст Асио, см., например,http://www.boost.org/doc/libs/1_53_0/doc/html/boost_asio/overview/networking/iostreams.html

Заголовочный Файл

// (c) Seth Heeren 2013
//
// Based on src/opus_demo.c in opus-1.0.2
// License see http://www.opus-codec.org/license/
#include <stdexcept>
#include <memory>
#include <iosfwd>

struct OpusErrorException : public virtual std::exception
{
    OpusErrorException(int code) : code(code) {}
    const char* what() const noexcept;
private:
    const int code;
};

struct COpusCodec
{
    COpusCodec(int32_t sampling_rate, int channels);
    ~COpusCodec();

    bool decode_frame(std::istream& fin, std::ostream& fout);
private:
    struct Impl;
    std::unique_ptr<Impl> _pimpl;
};

Файл

// (c) Seth Heeren 2013
//
// Based on src/opus_demo.c in opus-1.0.2
// License see http://www.opus-codec.org/license/
#include "COpusCodec.hpp"
#include <vector>
#include <iomanip>
#include <memory>
#include <sstream>

#include "opus.h"

#define MAX_PACKET 1500

const char* OpusErrorException::what() const noexcept
{
    return opus_strerror(code);
}

// I'd suggest reading with boost::spirit::big_dword or similar
static uint32_t char_to_int(char ch[4])
{
    return static_cast<uint32_t>(static_cast<unsigned char>(ch[0])<<24) |
        static_cast<uint32_t>(static_cast<unsigned char>(ch[1])<<16) |
        static_cast<uint32_t>(static_cast<unsigned char>(ch[2])<< 8) |
        static_cast<uint32_t>(static_cast<unsigned char>(ch[3])<< 0);
}

struct COpusCodec::Impl
{
    Impl(int32_t sampling_rate = 48000, int channels = 1)
    : 
        _channels(channels),
        _decoder(nullptr, &opus_decoder_destroy),
        _state(_max_frame_size, MAX_PACKET, channels)
    {
        int err = OPUS_OK;
        auto raw = opus_decoder_create(sampling_rate, _channels, &err);
        _decoder.reset(err == OPUS_OK? raw : throw OpusErrorException(err) );
    }

    bool decode_frame(std::istream& fin, std::ostream& fout)
    {
        char ch[4] = {0};

        if (!fin.read(ch, 4) && fin.eof())
            return false;

        uint32_t len = char_to_int(ch);

        if(len>_state.data.size())
            throw std::runtime_error("Invalid payload length");

        fin.read(ch, 4);
        const uint32_t enc_final_range = char_to_int(ch);
        const auto data = reinterpret_cast<char*>(&_state.data.front());

        size_t read = 0ul;
        for (auto append_position = data; fin && read<len; append_position += read)
        {
            read += fin.readsome(append_position, len-read);
        }

        if(read<len)
        {
            std::ostringstream oss;
            oss << "Ran out of input, expecting " << len << " bytes got " << read << " at " << fin.tellg();
            throw std::runtime_error(oss.str());
        }

        int output_samples;
        const bool lost = (len==0);
        if(lost)
        {
            opus_decoder_ctl(_decoder.get(), OPUS_GET_LAST_PACKET_DURATION(&output_samples));
        }
        else
        {
            output_samples = _max_frame_size;
        }

        output_samples = opus_decode(
                _decoder.get(), 
                lost ? NULL : _state.data.data(),
                len,
                _state.out.data(),
                output_samples,
                0);

        if(output_samples>0)
        {
            for(int i=0; i<(output_samples)*_channels; i++)
            {
                short s;
                s=_state.out[i];
                _state.fbytes[2*i]   = s&0xFF;
                _state.fbytes[2*i+1] = (s>>8)&0xFF;
            }
            if(!fout.write(reinterpret_cast<char*>(_state.fbytes.data()), sizeof(short)* _channels * output_samples))
                throw std::runtime_error("Error writing");
        }
        else
        {
            throw OpusErrorException(output_samples); // negative return is error code
        }

        uint32_t dec_final_range;
        opus_decoder_ctl(_decoder.get(), OPUS_GET_FINAL_RANGE(&dec_final_range));

        /* compare final range encoder rng values of encoder and decoder */
        if(enc_final_range!=0
                && !lost && !_state.lost_prev
                && dec_final_range != enc_final_range)
        {
            std::ostringstream oss;
            oss << "Error: Range coder state mismatch between encoder and decoder in frame " << _state.frameno << ": " <<
                    "0x" << std::setw(8) << std::setfill('0') << std::hex << (unsigned long)enc_final_range <<
                    "0x" << std::setw(8) << std::setfill('0') << std::hex << (unsigned long)dec_final_range;

            throw std::runtime_error(oss.str());
        }

        _state.lost_prev = lost;
        _state.frameno++;

        return true;
    }
private:
    const int _channels;
    const int _max_frame_size = 960*6;
    std::unique_ptr<OpusDecoder, void(*)(OpusDecoder*)> _decoder;

    struct State
    {
        State(int max_frame_size, int max_payload_bytes, int channels) :
            out   (max_frame_size*channels),
            fbytes(max_frame_size*channels*sizeof(decltype(out)::value_type)),
            data  (max_payload_bytes)
        { }

        std::vector<short>         out;
        std::vector<unsigned char> fbytes, data;
        int32_t frameno   = 0;
        bool    lost_prev = true;
    };
    State _state;
};

COpusCodec::COpusCodec(int32_t sampling_rate, int channels)
    : _pimpl(std::unique_ptr<Impl>(new Impl(sampling_rate, channels)))
{
    //
}

COpusCodec::~COpusCodec()
{
    // this instantiates the pimpl deletor code on the, now-complete, pimpl class
}

bool COpusCodec::decode_frame(
        std::istream& fin,
        std::ostream& fout)
{
    return _pimpl->decode_frame(fin, fout);
}

libopus предоставляет API для превращения пакетов opus в куски данных PCM и наоборот.

но для хранения пакетов opus в файле вам нужен какой-то формат контейнера, в котором хранятся границы пакетов. opus_demo Это, ну, демо-приложение: он имеет свой собственный минимальный формат контейнера для целей тестирования, который не документирован, и, следовательно, файлы, созданные opus_demo Не следует распространять. Стандартный формат контейнера для файлов opus-Ogg, который также обеспечивает поддержку метаданные и образец-точное декодирование и эффективный поиск потоков с переменным битрейтом. Ogg Opus файлы имеют расширение".опус."

спецификация Ogg Opus находится на https://wiki.xiph.org/OggOpus.

(так как Opus также является кодеком VoIP, есть использование Opus, которые не требуют контейнера, например, передачи пакетов Opus непосредственно через UDP.)

поэтому сначала вы должны кодировать свои файлы с помощью opusenc из opus-tools, не opus_demo. Другой программное обеспечение также может создавать файлы Ogg Opus (я считаю, что gstreamer и ffmpeg могут, например), но вы не можете ошибиться с opus-tools, поскольку это эталонная реализация.

затем, предполагая, что ваши файлы являются стандартными файлами Ogg Opus (которые могут быть прочитаны, скажем, Firefox), вам нужно сделать следующее: (a) извлечь пакеты opus из контейнера Ogg; (b) передать пакеты libopus и получить raw PCM обратно.

удобно, есть библиотека под названием libopusfile, которая делает точно этот. libopusfile поддерживает все функции потоков Ogg Opus, включая метаданные и поиск (включая поиск по HTTP-соединению).

libopusfile доступен по адресу https://git.xiph.org/?p=opusfile.git и https://github.com/xiph/opusfile. API документирован здесь и opusfile_example.c (xiph.org | github) приводится пример кода для декодирования в WAV. Поскольку вы находитесь в windows, я должен добавить на скачать