Dekodowanie danych audio Opus
Próbuję dekodować plik Opus z powrotem do formatu raw 48 kHz. Jednak nie jestem w stanie znaleźć żadnego przykładowego kodu, aby to zrobić.
Mój obecny kod to:
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);
}
Argument "zakodowany" może być większą ilością danych, więc myślę, że muszę podzielić go na ramki. Nie jestem pewien, jak mógłbym to zrobić.
A będąc początkującym w Opus, naprawdę boję się coś zepsuć. Czy ktoś może pomóc?2 answers
Myślę, że opus_demo.c
program ze źródła tarball ma to, czego chcesz.
- kodowanie, parsowanie parametrów kodera z argumentów linii poleceń
- sztuczny zastrzyk utraty pakietów
- losowy wybór ramek/zmiana w locie W zależności od tego, czy jest to bufor, czy bufor, czy bufor, czy bufor, czy bufor, czy bufor są buforami.]}
- debugowanie i weryfikacja
- raportowanie statystyk o przepływności bitów
Usunięcie tych wszystkich bitów jest bardzo żmudną pracą, jak się okazuje. Ale kiedy to zrobisz, skończysz z całkiem czystym, zrozumiałym kodem, patrz poniżej.
Zauważ, że ja
- przechowywał kod protokołu " packet-loss "(nawet jeśli utrata pakietów nie nastąpi w odczycie z pliku) dla odniesienia
- przechowywał kod weryfikujący końcowy Zakres po dekodowaniu każdej klatki
Ponieważ to nie komplikuje kodu i może cię to zainteresować.
Testowałem ten program na dwa sposoby:
- aurally (sprawdzając, czy mono wav wcześniej zakodowany za pomocą
opus_demo
został poprawnie zdekodowany za pomocą tego dekodera). Test wav był ~23MB, 2.9 Mb skompresowany. - regresja testowana wraz z waniliowym opus_demo, gdy wywoływana jest przez
./opus_demo -d 48000 1 <opus-file> <pcm-file>
. Wynikowy plik miał taką samą sumę kontrolnąmd5sum
jak ten zdekodowany przy użyciu dekodera proszę.
MAJOR UPDATE I C++ - ified the code. To powinno cię gdzieś zabrać za pomocą iostreams.
- zwróć uwagę na pętlę na
fin.readsome
teraz; pętla ta może być "asynchroniczna" (tzn. może być zwrócona i czytaj dalej, gdy pojawią się nowe dane (przy następnym wywołaniu funkcjiDecode
?)[1] - usunąłem zależności od opus.h z pliku nagłówkowego
- zastąpiłem" wszystkie " ręczne zarządzanie pamięcią przez biblioteka standardowa (
vector
,unique_ptr
) dla wyjątkowego bezpieczeństwa i solidności. - zaimplementowałem klasę
OpusErrorException
pochodzącą zstd::exception
, która służy do propagowania błędów zlibopus
Zobacz cały kod + Makefile tutaj: https://github.com/sehe/opus/tree/master/contrib
[1] aby uzyskać prawdziwe asynchroniczne IO (np. komunikacja sieciowa lub szeregowa) rozważ użycie Boost Asio, patrz np. http://www.boost.org/doc/libs/1_53_0/doc/html/boost_asio/overview/networking/iostreams.html
Plik Nagłówkowy
// (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;
};
Plik Implementacji
// (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;
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-17 09:48:36
Libopus dostarcza API do przekształcania pakietów opus w fragmenty danych PCM i vice-versa.
Ale aby przechowywać pakiety opus w pliku, potrzebujesz jakiegoś formatu kontenera, który przechowuje granice pakietów. opus_demo
jest, cóż, aplikacją demonstracyjną: ma swój własny minimalny format kontenera do celów testowych, który nie jest udokumentowany, a zatem pliki wyprodukowane przez opus_demo
nie powinny być rozpowszechniane. Standardowym formatem kontenera dla plików opus jest Ogg, który zapewnia również obsługę metadanych i sample-dokładne dekodowanie i skuteczne wyszukiwanie strumieni o zmiennej przepływności. Pliki Ogg Opus mają rozszerzenie ".opus".
Ogg Opus spec jest na https://wiki.xiph.org/OggOpus .
(ponieważ Opus jest również kodekiem VoIP, istnieją zastosowania Opus, które nie wymagają kontenera, takie jak przesyłanie pakietów Opus bezpośrednio przez UDP.)
Więc najpierw powinieneś kodować swoje pliki za pomocą opusenc
z opus-tools, a nie opus_demo
. Inne programy mogą też produkować pliki Ogg Opus (I wierzę, że gstreamer i ffmpeg mogą, na przykład), ale naprawdę nie można się pomylić z Opus-tools, ponieważ jest to implementacja referencyjna.
Następnie, zakładając, że Twoje pliki są standardowymi plikami Ogg Opus (które może odczytać np. Firefox), musisz: (A) wyodrębnić Pakiety opus z kontenera Ogg; (b) przekazać pakiety do libopusa i odzyskać surowy PCM.
Wygodnie, istnieje Biblioteka o nazwie libopusfile, która robi dokładnie to. libopusfile obsługuje wszystkie funkcje Ogg Strumienie Opus, w tym metadane i szukanie (w tym szukanie przez połączenie HTTP).
Libopusfile jest dostępny pod adresem https://git.xiph.org/?p=opusfile.git i https://github.com/xiph/opusfile .
API jest udokumentowane tutaj , a opusfile_example.c
(xiph.org | github ) dostarcza przykładowy kod do dekodowania do WAV. Ponieważ jesteś na windows powinienem dodać tam są prebuilt dll na stronie downloads.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-24 01:26:03