C++ 解码 Opus 音频数据

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16496288/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 20:24:32  来源:igfitidea点击:

Decoding Opus audio data

c++windowsaudiocodecopus

提问by tmighty

I am trying to decode an Opus file back to raw 48 kHz. However I am unable to find any sample code to do that.

我正在尝试将 Opus 文件解码回原始 48 kHz。但是我找不到任何示例代码来做到这一点。

My current code is this:

我目前的代码是这样的:

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);
}

The argument "encoded" might be larger amounts of data, so I think I have to split it into frames. I am not sure how I could do that.

“编码”的参数可能是大量的数据,所以我想我必须把它分成几帧。我不知道我怎么能做到这一点。

And with being a beginner with Opus, I am really afraid to mess something up.

作为 Opus 的初学者,我真的很害怕把事情搞砸。

Could anybody perhaps help?

有人可以帮忙吗?

回答by sehe

I think the opus_demo.cprogram from the source tarballhas what you want.

我认为源 tarball 中opus_demo.c程序具有您想要的功能。

It's pretty complicated though, because of all the unrelated code pertaining to

不过这很复杂,因为所有不相关的代码都与

  • encoding, parsing encoder parameters from command line arguments
  • artificial packet loss injection
  • random framesize selection/changing on-the-fly
  • inband FEC (meaning decoding into two buffers, toggling between the two)
  • debug and verification
  • bit-rate statistics reporting
  • 编码,从命令行参数解析编码器参数
  • 人工丢包注入
  • 随机帧大小选择/即时更改
  • 带内 FEC(意味着解码成两个缓冲区,在两者之间切换)
  • 调试和验证
  • 比特率统计报告

Removing all these bits is a verytedious job, as it turns out. But once you do, you end up with pretty clean, understandable code, see below.

事实证明,删除所有这些位是一项非常乏味的工作。但是一旦你这样做了,你就会得到非常干净、易于理解的代码,见下文。

Note that I

请注意,我

  • kept the 'packet-loss' protocol code (even though packet loss won't happen reading from a file) for reference
  • kept the code that verifies the final range after decoding each frame
  • 保留“丢包”协议代码(即使从文件读取不会发生丢包)以供参考
  • 保留解码每帧后验证最终范围的代码

Mostly because it doesn't seem to complicate the code, and you might be interested in it.

主要是因为它似乎不会使代码复杂化,而且您可能对它感兴趣。

I tested this program in two ways:

我用两种方式测试了这个程序:

  • aurally (by verifying that a mono wav previously encoded using opus_demowas correctly decoded using this stripped decoder). The test wav was ~23Mb, 2.9Mb compressed.
  • regression tested alongside the vanilla opus_demo when called with ./opus_demo -d 48000 1 <opus-file> <pcm-file>. The resultant file had the same md5sumchecksum as the one decoded using the stripped decoder here.
  • 听觉上(通过验证opus_demo使用此剥离解码器正确解码以前使用的单声道 wav )。测试 wav 约为 23Mb,压缩后为 2.9Mb。
  • 当使用./opus_demo -d 48000 1 <opus-file> <pcm-file>. 生成的文件与md5sum此处使用剥离解码器解码的文件具有相同的校验和。

MAJOR UPDATEI C++-ified the code. This should get you somewhere using iostreams.

主要更新我对代码进行了 C++ 化。这应该可以让您使用 iostreams 到达某个地方。

  • Note the loop on fin.readsomenow; this loop could be made 'asynchronous' (i.e. it could be made to return, and continue reading when new data arrives (on the next invocation of your Decodefunction?)[1]
  • I have cut the dependencies on opus.h from the header file
  • I have replaced "all" manual memory management by standard library (vector, unique_ptr) for exception safety and robustness.
  • I have implemented an OpusErrorExceptionclass deriving from std::exceptionwhich is used to propagate errors from libopus
  • 注意fin.readsome现在的循环;这个循环可以被设为“异步”(即它可以返回,并在新数据到达时继续读取(在您的Decode函数的下一次调用中?[1]
  • 我已经从头文件中删除了对 opus.h 的依赖
  • 为了异常安全和健壮性vector,我已经用标准库 ( , unique_ptr)替换了“所有”手动内存管理。
  • 我已经实现了一个OpusErrorException派生类std::exception,用于传播错误libopus

See all the code + Makefile here: https://github.com/sehe/opus/tree/master/contrib

在此处查看所有代码 + Makefile:https: //github.com/sehe/opus/tree/master/contrib

[1]for true async IO (e.g. network or serial communinication) consider using Boost Asio, see e.g. http://www.boost.org/doc/libs/1_53_0/doc/html/boost_asio/overview/networking/iostreams.html

[1]对于真正的异步 IO(例如网络或串行通信),请考虑使用 Boost Asio,参见例如http://www.boost.org/doc/libs/1_53_0/doc/html/boost_asio/overview/networking/iostreams.html

Header File

头文件

// (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;
};

Implementation File

实施文件

// (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);
}

test.cpp

测试.cpp

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

#include "COpusCodec.hpp"

int main(int argc, char *argv[])
{
    if(argc != 3)
    {
        std::cerr << "Usage: " << argv[0] << " <input> <output>\n";
        return 255;
    }

    std::basic_ifstream<char> fin (argv[1], std::ios::binary);
    std::basic_ofstream<char> fout(argv[2], std::ios::binary);

    if(!fin)  throw std::runtime_error("Could not open input file");
    if(!fout) throw std::runtime_error("Could not open output file");

    try
    {
        COpusCodec codec(48000, 1);

        size_t frames = 0;
        while(codec.decode_frame(fin, fout))
        {
            frames++;
        }

        std::cout << "Successfully decoded " << frames << " frames\n";
    }
    catch(OpusErrorException const& e)
    {
        std::cerr << "OpusErrorException: " << e.what() << "\n";
        return 255;
    }
}

回答by hexwab

libopus provides an API for turning opus packets into chunks of PCM data, and vice-versa.

libopus 提供了一个 API,用于将 opus 数据包转换为 PCM 数据块,反之亦然。

But to store opus packets in a file, you need some kind of container format that stores the packet boundaries. opus_demois, well, a demo app: it has its own minimal container format for testing purposes that is not documented, and thus files produced by opus_demoshould not be distributed. The standard container format for opus files is Ogg, which also provides support for metadata and sample-accurate decoding and efficient seeking for variable-bitrate streams. Ogg Opus files have the extension ".opus".

但是要将 opus 数据包存储在文件中,您需要某种存储数据包边界的容器格式。 opus_demo嗯,是一个演示应用程序:它有自己的最小容器格式,用于测试目的,没有记录,因此opus_demo不应分发由 生成的文件。opus 文件的标准容器格式是 Ogg,它还支持元数据和样本精确解码以及对可变比特率流的高效搜索。Ogg Opus 文件的扩展名为“.opus”。

The Ogg Opus spec is at https://wiki.xiph.org/OggOpus.

Ogg Opus 规范位于https://wiki.xiph.org/OggOpus

(Since Opus is also a VoIP codec, there are uses of Opus that do not require a container, such as transmitting Opus packets directly over UDP.)

(由于 Opus 也是 VoIP 编解码器,因此 Opus 的使用不需要容器,例如直接通过 UDP 传输 Opus 数据包。)

So firstly you should encode your files using opusencfrom opus-tools, not opus_demo. Other software can produce Ogg Opus files too (I believe gstreamer and ffmpeg can, for example) but you can't really go wrong with opus-tools as it's the reference implementation.

所以首先你应该使用opusencopus-tools编码你的文件,而不是opus_demo. 其他软件也可以生成 Ogg Opus 文件(例如,我相信 gstreamer 和 ffmpeg 可以)但是 opus-tools 不会出错,因为它是参考实现。

Then, assuming your files are standard Ogg Opus files (that can be read by, say, Firefox), what you need to do is: (a) extract opus packets from the Ogg container; (b) pass the packets to libopus and get raw PCM back.

然后,假设您的文件是标准的 Ogg Opus 文件(可以被 Firefox 读取),您需要做的是: (a) 从 Ogg 容器中提取 opus 数据包;(b) 将数据包传递给 libopus 并取回原始 PCM。

Conveniently, there's a library called libopusfile that does precisely this. libopusfile supports all of the features of Ogg Opus streams, including metadata and seeking (including seeking over an HTTP connection).

方便的是,有一个名为 libopusfile 的库正好可以做到这一点。libopusfile 支持 Ogg Opus 流的所有功能,包括元数据和搜索(包括通过 HTTP 连接进行搜索)。

libopusfile is available at https://git.xiph.org/?p=opusfile.gitand https://github.com/xiph/opusfile. The API is documented here, and opusfile_example.c(xiph.org| github) provides example code for decoding to WAV. Since you're on windows I should add there are prebuilt DLLs on the downloadspage.

libopusfile 可在https://git.xiph.org/?p=opusfile.githttps://github.com/xiph/opusfile 获得此处记录API ,并且opusfile_example.c( xiph.org| github) 提供了用于解码为 WAV 的示例代码。由于您使用的是 Windows,我应该在下载页面上添加预构建的 DLL 。