簡體   English   中英

解碼Opus音頻數據

[英]Decoding Opus audio data

我正在嘗試將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);
}

“編碼”這個參數可能是更大量的數據,所以我認為我必須將它分成幀。 我不知道怎么能這樣做。

作為Opus的初學者,我真的害怕搞砸了。

有人可能會幫忙嗎?

我認為源tarball中opus_demo.c程序有你想要的。

但它非常復雜,因為所有不相關的代碼都與之相關

  • 編碼,從命令行參數解析編碼器參數
  • 人工丟包注入
  • 隨機幀大小選擇/即時更改
  • 帶內FEC(意思是解碼成兩個緩沖區,在兩者之間切換)
  • 調試和驗證
  • 比特率統計報告

事實證明,刪除所有這些位是一項非常繁瑣的工作。 但是一旦你這樣做,你最終會得到非常干凈,易懂的代碼,見下文。

請注意我

  • 保留“數據包丟失”協議代碼(即使不會從文件中讀取丟包)以供參考
  • 在解碼每個幀之后保留驗證最終范圍的代碼

主要是因為它似乎沒有使代碼復雜化,您可能對它感興趣。

我用兩種方式測試了這個程序:

  • 聽覺上(通過驗證先前使用opus_demo編碼的單聲道wav使用此剝離解碼器正確解碼)。 測試波形為~23Mb,壓縮2.9Mb。
  • 使用./opus_demo -d 48000 1 <opus-file> <pcm-file>調用時,與vanilla opus_demo一起進行回歸測試。 結果文件與使用剝離解碼器解碼的文件具有相同的md5sum校驗和。

主要更新 I C ++ - ified代碼。 這應該可以讓你使用iostreams。

  • 現在注意fin.readsome上的循環; 這個循環可以做成'異步'(即它可以返回,並在新數據到達時繼續讀取( 在下一次調用你的Decode函數時?[1]
  • 我已經從頭文件中刪除了對opus.h的依賴
  • 我已經用標准庫( vectorunique_ptr )替換了“全部”手動內存管理,以實現異常安全性和健壯性。
  • 我已經實現了一個從std::exception派生的OpusErrorException類,它用於傳播來自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提供了一個API,用於將opus數據包轉換為PCM數據塊,反之亦然。

但是要將opus數據包存儲在文件中,您需要某種容器格式來存儲數據包邊界。 opus_demo是一個演示應用程序:它有自己的最小容器格式,用於測試目的,沒有記錄,因此opus_demo生成的文件不應該分發。 opus文件的標准容器格式是Ogg,它還支持元數據和樣本精確解碼以及高效搜索可變比特率流。 Ogg Opus文件的擴展名為“.opus”。

Ogg Opus規范位於https://wiki.xiph.org/OggOpus

(由於Opus也是一種VoIP編解碼器,因此Opus的使用不需要容器,例如直接通過UDP傳輸Opus數據包。)

首先,您應該使用opus工具中的opusenc編碼文件,而不是opus_demo 其他軟件也可以生成Ogg Opus文件(例如,我相信gstreamer和ffmpeg可以)但是你不能真的出錯opus-tools,因為它是參考實現。

然后,假設您的文件是標准的Ogg Opus文件(可以通過Firefox讀取),您需要做的是:(a)從Ogg容器中提取opus數據包; (b)將數據包傳遞給libopus並獲取原始PCM。

方便的是,有一個名為libopusfile的庫正是這樣做的。 libopusfile支持Ogg Opus流的所有功能,包括元數據和搜索(包括通過HTTP連接尋找)。

libopus文件位於https://git.xiph.org/?p=opusfile.githttps://github.com/xiph/opusfile 此處記錄 API, opusfile_example.cxiph.org | github )提供了解碼為WAV的示例代碼。 因為你在Windows上我應該添加下載頁面上有預建的DLL。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM