C++ Opus音频数据解码

C++ Opus音频数据解码,c++,windows,audio,codec,opus,C++,Windows,Audio,Codec,Opus,我试图将Opus文件解码回原始48 kHz。 然而,我无法找到任何样本代码来做到这一点 我目前的代码是: 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, &

我试图将Opus文件解码回原始48 kHz。 然而,我无法找到任何样本代码来做到这一点

我目前的代码是:

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);
}
参数“encoded”可能是更大数量的数据,因此我认为必须将其拆分为多个帧。 我不知道我怎么能做到这一点

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


有人能帮忙吗?

我想来自的
opus\u demo.c
程序满足了您的需求

但这相当复杂,因为所有与之相关的无关代码

  • 编码,从命令行参数解析编码器参数
  • 人工丢包注入
  • 随机帧大小选择/动态更改
  • 带内FEC(意味着解码到两个缓冲区,在两个缓冲区之间切换)
  • 调试与验证
  • 比特率统计报告
事实证明,删除所有这些位是一项非常乏味的工作。但一旦你这样做了,你就会得到非常干净、易懂的代码,见下文

请注意,我

  • 保留“数据包丢失”协议代码(即使从文件读取时不会发生数据包丢失),以供参考
  • 保留解码每帧后验证最终范围的代码
主要是因为它似乎不会使代码复杂化,而且您可能对此感兴趣

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

  • 听觉上(通过验证先前使用opus_demo编码的单声道wav是否使用此解码器正确解码)。测试波形约23Mb,压缩2.9Mb
  • 使用
    /opus\u demo-d 48000 1
    调用时,回归测试与香草opus\u demo一起进行。生成的文件与此处使用剥离解码器解码的文件具有相同的
    md5sum
    校验和
主要更新我用C++编写了代码。这会让你使用iostreams

  • 注意
    fin.readsome
    now上的循环;该循环可以“异步”(即,可以使其返回,并在新数据到达时继续读取(下次调用
    Decode
    函数时?)[1]
  • 我已经从头文件中删除了对opus.h的依赖关系
  • 我已经用标准库(
    vector
    unique\u ptr
    )取代了“所有”手动内存管理,以实现异常安全性和健壮性
  • 我已经实现了一个从
    std::exception
    派生的
    OpusErrorException
    类,该类用于从
    libopus
    传播错误
请参见此处的所有代码+生成文件:

(1)对于真正的异步IO(例如网络或串行通信)考虑使用Boost ASIO,参见例如

头文件
/(c)Seth Heeren 2013
//
//基于opus-1.0.2中的src/opus_demo.c
//许可证见http://www.opus-codec.org/license/
#包括
#包括
#包括
结构OpusErrorException:公共虚拟std::exception
{
OpusErrorException(int代码):代码(代码){}
const char*what()const noexcept;
私人:
常量整数码;
};
结构COpusCodec
{
COpusCodec(int32采样率,int通道);
~COpusCodec();
bool解码帧(标准::istream和fin,标准::ostream和fout);
私人:
结构Impl;
std::唯一的\u ptr\u pimpl;
};
实现文件
/(c)Seth Heeren 2013
//
//基于opus-1.0.2中的src/opus_demo.c
//许可证见http://www.opus-codec.org/license/
#包括“COpusCodec.hpp”
#包括
#包括
#包括
#包括
#包括“h作品”
#定义最大数据包1500
const char*OpusErrorException::what()const noexcept
{
返回opus_strerror(代码);
}
//我建议用boost::spirit::big_dword或类似的语言阅读
静态uint32字符到整数(字符通道[4])
{

return static_cast(static_cast(ch[0])libopus提供了一个API,用于将opus数据包转换为PCM数据块,反之亦然

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

Ogg Opus规范位于

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

因此,首先,您应该使用opus tools中的
opusenc
对文件进行编码,而不是
opus\u demo
。其他软件也可以生成Ogg opus文件(例如,我相信gstreamer和ffmpeg可以),但您不能真正错误地使用opus工具,因为它是参考实现

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

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

libopusfile可在和处获得。
API是有文档记录的,
opusfile_example.c
(|)提供解码为WAV的示例代码。由于您在windows上,我应该在页面上添加预构建的DLL。

感谢您的努力和解释。您能更准确地理解我的代码吗?我也使用过Opus网站上的演示代码,毫无疑问,它是有效的。但我不理解它。我需要更详细的说明mpler表单。我已经读取了编码的字节,我想将其转换为解码的短片。我没有为r打开文件
// (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);
}
// (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;
    }
}