Расшифровка аудиоданных Opus

Я пытаюсь декодировать файл Opus обратно до 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, я действительно боюсь что-то испортить.

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

15

Решение

я думаю opus_demo.c программа из исходный архив есть то, что вы хотите.

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

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

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

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

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

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

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

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

ОСНОВНОЕ ОБНОВЛЕНИЕ Я 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] для истинного асинхронного ввода-вывода (например, сети или последовательной связи) рассмотрите возможность использования 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;
}
}
31

Другие решения

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

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

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

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

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

Затем, предполагая, что ваши файлы являются стандартными файлами Ogg Opus (которые могут быть прочитаны, скажем, Firefox), вам нужно сделать следующее: (a) извлечь пакеты opus из контейнера Ogg; (б) передать пакеты в libopus и вернуть необработанные 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, я должен добавить, что на загрузок стр.

11