ogg: support different sample formats

This commit is contained in:
badaix 2016-05-03 21:28:55 +02:00
parent 16be4f53b8
commit 16ea1a5d71
4 changed files with 106 additions and 83 deletions

View file

@ -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;

View file

@ -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)
@ -72,15 +68,14 @@ bool OggDecoder::decode(msg::PcmChunk* chunk)
int result = ogg_sync_pageout(&oy, &og); int result = ogg_sync_pageout(&oy, &og);
if (result == 0) if (result == 0)
break; /* need more data */ break; /* need more data */
if(result < 0) if (result < 0)
{ {
/* missing or corrupt data at this page position */ /* missing or corrupt data at this page position */
logE << "Corrupt or missing data in bitstream; continuing...\n"; logE << "Corrupt or missing data in bitstream; continuing...\n";
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_;
} }

View file

@ -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;
}; };

View file

@ -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);