解码Opusaudio数据

我试图解码一个Opus文件回到原始的48千赫。 不过,我无法find任何示例代码来做到这一点。

我目前的代码是这样的:

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的初学者,我真的害怕把事情弄糟。

有人可能帮忙吗?

我认为从源代码压缩包的opus_demo.c程序有你想要的。

虽然这是相当复杂的,因为所有的无关的代码有关

  • 编码,从命令行参数解析编码器参数
  • 人为包丢失注入
  • 随机的帧尺寸选择/随时改变
  • 带内FEC(意味着解码成两个缓冲区,在两者之间切换)
  • 调试和验证
  • 比特率统计报告

事实证明,除去所有这些部分是非常繁琐的工作。 但是一旦你这样做了,你会得到相当干净,可以理解的代码,见下文。

请注意,我

  • 保持“丢包”协议代码(即使丢包不会发生在文件中),以供参考
  • 在解码每一帧之后保留验证最终范围的代码

主要是因为它似乎并不复杂的代码,你可能会对它感兴趣。

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

  • (通过验证先前使用opus_demo编码的单声道wav使用这个剥离的解码器被正确解码)。 测试波形为〜23Mb,压缩为2.9Mb。
  • 当用./opus_demo -d 48000 1 <opus-file> <pcm-file>调用时,回归与vanilla opus_demo一起进行测试。 结果文件与在此使用解压缩解码器解码的文件具有相同的md5sum校验和。

主要更新我C ++ – 编码的代码。 这应该让你在某个地方使用iostreams。

  • 现在注意fin.readsome上的循环; 这个循环可以做成“异步的”(即它可以返回,并在新数据到达时继续读取( 在下一次调用Decode函数时
  • 我已经从头文件中减少了对opus.h的依赖关系
  • 我用标准库( vectorunique_ptr )取代了“全部”手动内存管理,以提高异常的安全性和健壮性。
  • 我已经实现了一个派生自std::exceptionOpusErrorException类,用于从libopus传播错误

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

[1]真正的异步IO(例如网络或串行通信)考虑使用Boost Asio,参见例如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); } 

TEST.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; } } 

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

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

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

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

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

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

方便的是,有一个名为libopusfile的库就是这样做的。 libopusfile支持Ogg Opus流的所有功能,包括元数据和查找(包括通过HTTP连接查找)。

libopusfile位于https://git.xiph.org/?p=opusfile.git 。 API 在这里被记录,并且有一些用于解码到WAV的示例代码 。 既然你在Windows上,我应该添加下载页面上有预建的DLL。