mirror of
https://github.com/badaix/snapcast.git
synced 2025-05-25 06:56:15 +02:00
ogg: support different sample formats
This commit is contained in:
parent
16be4f53b8
commit
16ea1a5d71
4 changed files with 106 additions and 83 deletions
|
@ -153,21 +153,21 @@ FLAC__StreamDecoderWriteStatus write_callback(const FLAC__StreamDecoder *decoder
|
||||||
|
|
||||||
if (sampleFormat.sampleSize == 1)
|
if (sampleFormat.sampleSize == 1)
|
||||||
{
|
{
|
||||||
int8_t* pcm = (int8_t*)(pcmChunk->payload + pcmChunk->payloadSize);
|
int8_t* chunkBuffer = (int8_t*)(pcmChunk->payload + pcmChunk->payloadSize);
|
||||||
for (size_t i = 0; i < frame->header.blocksize; i++)
|
for (size_t i = 0; i < frame->header.blocksize; i++)
|
||||||
pcm[sampleFormat.channels*i + channel] = (int8_t)(buffer[channel][i]);
|
chunkBuffer[sampleFormat.channels*i + channel] = (int8_t)(buffer[channel][i]);
|
||||||
}
|
}
|
||||||
else if (sampleFormat.sampleSize == 2)
|
else if (sampleFormat.sampleSize == 2)
|
||||||
{
|
{
|
||||||
int16_t* pcm = (int16_t*)(pcmChunk->payload + pcmChunk->payloadSize);
|
int16_t* chunkBuffer = (int16_t*)(pcmChunk->payload + pcmChunk->payloadSize);
|
||||||
for (size_t i = 0; i < frame->header.blocksize; i++)
|
for (size_t i = 0; i < frame->header.blocksize; i++)
|
||||||
pcm[sampleFormat.channels*i + channel] = SWAP_16((int16_t)(buffer[channel][i]));
|
chunkBuffer[sampleFormat.channels*i + channel] = SWAP_16((int16_t)(buffer[channel][i]));
|
||||||
}
|
}
|
||||||
else if (sampleFormat.sampleSize == 4)
|
else if (sampleFormat.sampleSize == 4)
|
||||||
{
|
{
|
||||||
int32_t* pcm = (int32_t*)(pcmChunk->payload + pcmChunk->payloadSize);
|
int32_t* chunkBuffer = (int32_t*)(pcmChunk->payload + pcmChunk->payloadSize);
|
||||||
for (size_t i = 0; i < frame->header.blocksize; i++)
|
for (size_t i = 0; i < frame->header.blocksize; i++)
|
||||||
pcm[sampleFormat.channels*i + channel] = SWAP_32((int32_t)(buffer[channel][i]));
|
chunkBuffer[sampleFormat.channels*i + channel] = SWAP_32((int32_t)(buffer[channel][i]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pcmChunk->payloadSize += bytes;
|
pcmChunk->payloadSize += bytes;
|
||||||
|
|
|
@ -29,18 +29,15 @@
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
|
||||||
OggDecoder::OggDecoder() : Decoder(), buffer(NULL), bytes(0)
|
OggDecoder::OggDecoder() : Decoder()
|
||||||
{
|
{
|
||||||
ogg_sync_init(&oy); /* Now we can read pages */
|
ogg_sync_init(&oy); /* Now we can read pages */
|
||||||
convsize = 4096;
|
|
||||||
convbuffer = (ogg_int16_t*)malloc(convsize * sizeof(ogg_int16_t));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
OggDecoder::~OggDecoder()
|
OggDecoder::~OggDecoder()
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
free(convbuffer);
|
|
||||||
vorbis_block_clear(&vb);
|
vorbis_block_clear(&vb);
|
||||||
vorbis_dsp_clear(&vd);
|
vorbis_dsp_clear(&vd);
|
||||||
ogg_stream_clear(&os);
|
ogg_stream_clear(&os);
|
||||||
|
@ -57,14 +54,13 @@ bool OggDecoder::decode(msg::PcmChunk* chunk)
|
||||||
(which is guaranteed to be small and only contain the Vorbis
|
(which is guaranteed to be small and only contain the Vorbis
|
||||||
stream initial header) We need the first page to get the stream
|
stream initial header) We need the first page to get the stream
|
||||||
serialno. */
|
serialno. */
|
||||||
bytes = chunk->payloadSize;
|
int size = chunk->payloadSize;
|
||||||
buffer = ogg_sync_buffer(&oy, bytes);
|
char *buffer = ogg_sync_buffer(&oy, size);
|
||||||
memcpy(buffer, chunk->payload, bytes);
|
memcpy(buffer, chunk->payload, size);
|
||||||
ogg_sync_wrote(&oy,bytes);
|
ogg_sync_wrote(&oy, size);
|
||||||
|
|
||||||
|
|
||||||
chunk->payloadSize = 0;
|
chunk->payloadSize = 0;
|
||||||
convsize = 4096;//bytes/vi.channels;
|
|
||||||
/* The rest is just a straight decode loop until end of stream */
|
/* The rest is just a straight decode loop until end of stream */
|
||||||
// while(!eos){
|
// while(!eos){
|
||||||
while(true)
|
while(true)
|
||||||
|
@ -79,8 +75,7 @@ bool OggDecoder::decode(msg::PcmChunk* chunk)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
ogg_stream_pagein(&os,&og); /* can safely ignore errors at
|
ogg_stream_pagein(&os,&og); /* can safely ignore errors at this point */
|
||||||
this point */
|
|
||||||
while(1)
|
while(1)
|
||||||
{
|
{
|
||||||
result = ogg_stream_packetout(&os, &op);
|
result = ogg_stream_packetout(&os, &op);
|
||||||
|
@ -101,67 +96,73 @@ bool OggDecoder::decode(msg::PcmChunk* chunk)
|
||||||
if (vorbis_synthesis(&vb,&op) == 0) /* test for success! */
|
if (vorbis_synthesis(&vb,&op) == 0) /* test for success! */
|
||||||
vorbis_synthesis_blockin(&vd, &vb);
|
vorbis_synthesis_blockin(&vd, &vb);
|
||||||
/*
|
/*
|
||||||
|
|
||||||
**pcm is a multichannel float vector. In stereo, for
|
**pcm is a multichannel float vector. In stereo, for
|
||||||
example, pcm[0] is left, and pcm[1] is right. samples is
|
example, pcm[0] is left, and pcm[1] is right. samples is
|
||||||
the size of each channel. Convert the float values
|
the size of each channel. Convert the float values
|
||||||
(-1.<=range<=1.) to whatever PCM format and write it out */
|
(-1.<=range<=1.) to whatever PCM format and write it out */
|
||||||
|
|
||||||
while ((samples = vorbis_synthesis_pcmout(&vd, &pcm)) > 0)
|
while ((samples = vorbis_synthesis_pcmout(&vd, &pcm)) > 0)
|
||||||
{
|
{
|
||||||
int bout = (samples<convsize?samples:convsize);
|
size_t bytes = sampleFormat_.sampleSize * vi.channels * samples;
|
||||||
//cout << "samples: " << samples << ", convsize: " << convsize << "\n";
|
chunk->payload = (char*)realloc(chunk->payload, chunk->payloadSize + bytes);
|
||||||
/* convert floats to 16 bit signed ints (host order) and
|
for (int channel = 0; channel < vi.channels; ++channel)
|
||||||
interleave */
|
|
||||||
for(int i=0; i<vi.channels; i++)
|
|
||||||
{
|
{
|
||||||
ogg_int16_t *ptr=convbuffer+i;
|
if (sampleFormat_.sampleSize == 1)
|
||||||
#ifdef HAS_TREMOR
|
|
||||||
ogg_int32_t *mono=pcm[i];
|
|
||||||
#else
|
|
||||||
float *mono=pcm[i];
|
|
||||||
#endif
|
|
||||||
for (int j=0; j<bout; j++)
|
|
||||||
{
|
{
|
||||||
|
int8_t* chunkBuffer = (int8_t*)(chunk->payload + chunk->payloadSize);
|
||||||
|
for (int i = 0; i < samples; i++)
|
||||||
|
{
|
||||||
|
int8_t& val = chunkBuffer[sampleFormat_.channels*i + channel];
|
||||||
#ifdef HAS_TREMOR
|
#ifdef HAS_TREMOR
|
||||||
ogg_int32_t val = mono[j] >> 9;
|
val = clip<int8_t>(pcm[channel][i], -128, 127);
|
||||||
#else
|
#else
|
||||||
ogg_int32_t val = floor(mono[j]*32767.f+.5f);
|
val = clip<int8_t>(floor(pcm[channel][i]*127.f + .5f), -128, 127);
|
||||||
#endif
|
#endif
|
||||||
/* might as well guard against clipping */
|
}
|
||||||
if(val>32767)
|
}
|
||||||
val=32767;
|
else if (sampleFormat_.sampleSize == 2)
|
||||||
else if(val<-32768)
|
{
|
||||||
val=-32768;
|
int16_t* chunkBuffer = (int16_t*)(chunk->payload + chunk->payloadSize);
|
||||||
*ptr = SWAP_16(val);
|
for (int i = 0; i < samples; i++)
|
||||||
ptr += vi.channels;
|
{
|
||||||
|
int16_t& val = chunkBuffer[sampleFormat_.channels*i + channel];
|
||||||
|
#ifdef HAS_TREMOR
|
||||||
|
val = SWAP_16(clip<int16_t>(pcm[channel][i] >> 9, -32768, 32767));
|
||||||
|
#else
|
||||||
|
val = SWAP_16(clip<int16_t>(floor(pcm[channel][i]*32767.f + .5f), -32768, 32767));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (sampleFormat_.sampleSize == 4)
|
||||||
|
{
|
||||||
|
int32_t* chunkBuffer = (int32_t*)(chunk->payload + chunk->payloadSize);
|
||||||
|
for (int i = 0; i < samples; i++)
|
||||||
|
{
|
||||||
|
int32_t& val = chunkBuffer[sampleFormat_.channels*i + channel];
|
||||||
|
#ifdef HAS_TREMOR
|
||||||
|
val = SWAP_32(clip<int32_t>(pcm[channel][i] << 8, -2147483648, 2147483647));
|
||||||
|
#else
|
||||||
|
val = SWAP_32(clip<int32_t>(floor(pcm[channel][i]*2147483647.f + .5f), -2147483648, 2147483647));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t oldSize = chunk->payloadSize;
|
chunk->payloadSize += bytes;
|
||||||
size_t size = 2*vi.channels * bout;
|
vorbis_synthesis_read(&vd, samples);
|
||||||
chunk->payloadSize += size;
|
|
||||||
chunk->payload = (char*)realloc(chunk->payload, chunk->payloadSize);
|
|
||||||
memcpy(chunk->payload + oldSize, convbuffer, size);
|
|
||||||
/* tell libvorbis how many samples we actually consumed */
|
|
||||||
vorbis_synthesis_read(&vd,bout);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// if(ogg_page_eos(&og))eos=1;
|
|
||||||
// ogg_stream_clear(&os);
|
|
||||||
// vorbis_comment_clear(&vc);
|
|
||||||
// vorbis_info_clear(&vi); /* must be called last */
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
SampleFormat OggDecoder::setHeader(msg::CodecHeader* chunk)
|
SampleFormat OggDecoder::setHeader(msg::CodecHeader* chunk)
|
||||||
{
|
{
|
||||||
bytes = chunk->payloadSize;
|
int size = chunk->payloadSize;
|
||||||
buffer=ogg_sync_buffer(&oy, bytes);
|
char *buffer = ogg_sync_buffer(&oy, size);
|
||||||
memcpy(buffer, chunk->payload, bytes);
|
memcpy(buffer, chunk->payload, size);
|
||||||
ogg_sync_wrote(&oy, bytes);
|
ogg_sync_wrote(&oy, size);
|
||||||
|
|
||||||
if (ogg_sync_pageout(&oy, &og) != 1)
|
if (ogg_sync_pageout(&oy, &og) != 1)
|
||||||
throw SnapException("Input does not appear to be an Ogg bitstream");
|
throw SnapException("Input does not appear to be an Ogg bitstream");
|
||||||
|
@ -212,16 +213,6 @@ SampleFormat OggDecoder::setHeader(msg::CodecHeader* chunk)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Throw the comments plus a few lines about the bitstream we're decoding */
|
|
||||||
char **ptr=vc.user_comments;
|
|
||||||
while (*ptr)
|
|
||||||
{
|
|
||||||
logO << "comment: " << *ptr << "\n";;
|
|
||||||
++ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
logO << "Encoded by: " << vc.vendor << "\n";
|
|
||||||
|
|
||||||
/// OK, got and parsed all three headers. Initialize the Vorbis packet->PCM decoder.
|
/// OK, got and parsed all three headers. Initialize the Vorbis packet->PCM decoder.
|
||||||
if (vorbis_synthesis_init(&vd, &vi) == 0)
|
if (vorbis_synthesis_init(&vd, &vi) == 0)
|
||||||
vorbis_block_init(&vd, &vb);
|
vorbis_block_init(&vd, &vb);
|
||||||
|
@ -229,9 +220,22 @@ SampleFormat OggDecoder::setHeader(msg::CodecHeader* chunk)
|
||||||
/// local state for most of the decode so multiple block decodes can proceed
|
/// local state for most of the decode so multiple block decodes can proceed
|
||||||
/// in parallel. We could init multiple vorbis_block structures for vd here
|
/// in parallel. We could init multiple vorbis_block structures for vd here
|
||||||
|
|
||||||
|
sampleFormat_.setFormat(vi.rate, 16, vi.channels);
|
||||||
|
|
||||||
SampleFormat sampleFormat(vi.rate, 16, vi.channels);
|
/* Throw the comments plus a few lines about the bitstream we're decoding */
|
||||||
return sampleFormat;
|
char **ptr=vc.user_comments;
|
||||||
|
while (*ptr)
|
||||||
|
{
|
||||||
|
std::string comment(*ptr);
|
||||||
|
if (comment.find("SAMPLE_FORMAT=") == 0)
|
||||||
|
sampleFormat_.setFormat(comment.substr(comment.find("=") + 1));
|
||||||
|
logO << "comment: " << comment << "\n";;
|
||||||
|
++ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
logO << "Encoded by: " << vc.vendor << "\n";
|
||||||
|
|
||||||
|
return sampleFormat_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,13 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool decodePayload(msg::PcmChunk* chunk);
|
bool decodePayload(msg::PcmChunk* chunk);
|
||||||
|
template <typename T>
|
||||||
|
T clip(const T& value, const T& lower, const T& upper) const
|
||||||
|
{
|
||||||
|
if (value > upper) return upper;
|
||||||
|
if (value < lower) return lower;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
ogg_sync_state oy; /// sync and verify incoming physical bitstream
|
ogg_sync_state oy; /// sync and verify incoming physical bitstream
|
||||||
ogg_stream_state os; /// take physical pages, weld into a logical stream of packets
|
ogg_stream_state os; /// take physical pages, weld into a logical stream of packets
|
||||||
|
@ -47,11 +54,7 @@ private:
|
||||||
vorbis_dsp_state vd; /// central working state for the packet->PCM decoder
|
vorbis_dsp_state vd; /// central working state for the packet->PCM decoder
|
||||||
vorbis_block vb; /// local working space for packet->PCM decode
|
vorbis_block vb; /// local working space for packet->PCM decode
|
||||||
|
|
||||||
ogg_int16_t* convbuffer; /// take 8k out of the data segment, not the stack
|
SampleFormat sampleFormat_;
|
||||||
int convsize;
|
|
||||||
|
|
||||||
char *buffer;
|
|
||||||
int bytes;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -55,19 +55,34 @@ void OggEncoder::encode(const msg::PcmChunk* chunk)
|
||||||
{
|
{
|
||||||
double res = 0;
|
double res = 0;
|
||||||
logD << "payload: " << chunk->payloadSize << "\tframes: " << chunk->getFrameCount() << "\tduration: " << chunk->duration<chronos::msec>().count() << "\n";
|
logD << "payload: " << chunk->payloadSize << "\tframes: " << chunk->getFrameCount() << "\tduration: " << chunk->duration<chronos::msec>().count() << "\n";
|
||||||
int bytes = chunk->payloadSize / 4;
|
int frames = chunk->getFrameCount();
|
||||||
float **buffer=vorbis_analysis_buffer(&vd, bytes);
|
float **buffer=vorbis_analysis_buffer(&vd, frames);
|
||||||
|
|
||||||
/* uninterleave samples */
|
/* uninterleave samples */
|
||||||
for (int i=0; i<bytes; i++)
|
for (size_t channel = 0; channel < sampleFormat_.channels; ++channel)
|
||||||
{
|
{
|
||||||
int idx = 4*i;
|
if (sampleFormat_.sampleSize == 1)
|
||||||
buffer[0][i]=((((int8_t)chunk->payload[idx+1]) << 8) | (0x00ff & ((int8_t)chunk->payload[idx])))/32768.f;
|
{
|
||||||
buffer[1][i]=((((int8_t)chunk->payload[idx+3]) << 8) | (0x00ff & ((int8_t)chunk->payload[idx+2])))/32768.f;
|
int8_t* chunkBuffer = (int8_t*)chunk->payload;
|
||||||
|
for (int i=0; i<frames; i++)
|
||||||
|
buffer[channel][i]= chunkBuffer[sampleFormat_.channels*i + channel] / 128.f;
|
||||||
|
}
|
||||||
|
else if (sampleFormat_.sampleSize == 2)
|
||||||
|
{
|
||||||
|
int16_t* chunkBuffer = (int16_t*)chunk->payload;
|
||||||
|
for (int i=0; i<frames; i++)
|
||||||
|
buffer[channel][i]= chunkBuffer[sampleFormat_.channels*i + channel] / 32768.f;
|
||||||
|
}
|
||||||
|
else if (sampleFormat_.sampleSize == 4)
|
||||||
|
{
|
||||||
|
int32_t* chunkBuffer = (int32_t*)chunk->payload;
|
||||||
|
for (int i=0; i<frames; i++)
|
||||||
|
buffer[channel][i]= chunkBuffer[sampleFormat_.channels*i + channel] / 2147483648.f;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* tell the library how much we actually submitted */
|
/* tell the library how much we actually submitted */
|
||||||
vorbis_analysis_wrote(&vd, bytes);
|
vorbis_analysis_wrote(&vd, frames);
|
||||||
|
|
||||||
msg::PcmChunk* oggChunk = new msg::PcmChunk(chunk->format, 0);
|
msg::PcmChunk* oggChunk = new msg::PcmChunk(chunk->format, 0);
|
||||||
|
|
||||||
|
@ -193,6 +208,7 @@ void OggEncoder::initEncoder()
|
||||||
vorbis_comment_init(&vc);
|
vorbis_comment_init(&vc);
|
||||||
vorbis_comment_add_tag(&vc, "TITLE", "SnapStream");
|
vorbis_comment_add_tag(&vc, "TITLE", "SnapStream");
|
||||||
vorbis_comment_add_tag(&vc, "VERSION", VERSION);
|
vorbis_comment_add_tag(&vc, "VERSION", VERSION);
|
||||||
|
vorbis_comment_add_tag(&vc, "SAMPLE_FORMAT", sampleFormat_.getFormat().c_str());
|
||||||
|
|
||||||
/* set up the analysis state and auxiliary encoding storage */
|
/* set up the analysis state and auxiliary encoding storage */
|
||||||
vorbis_analysis_init(&vd, &vi);
|
vorbis_analysis_init(&vd, &vi);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue