mirror of
https://github.com/badaix/snapcast.git
synced 2025-05-21 04:56:13 +02:00
clean up
This commit is contained in:
parent
1d22986619
commit
aba04a0675
10 changed files with 202 additions and 221 deletions
|
@ -16,9 +16,9 @@
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
***/
|
***/
|
||||||
|
|
||||||
#include "pcmChunk.h"
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include "pcmChunk.h"
|
||||||
#include "common/log.h"
|
#include "common/log.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -75,6 +75,11 @@ public:
|
||||||
return (payloadSize / format.frameSize);
|
return (payloadSize / format.frameSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline size_t getSampleCount() const
|
||||||
|
{
|
||||||
|
return (payloadSize / format.sampleSize);
|
||||||
|
}
|
||||||
|
|
||||||
SampleFormat format;
|
SampleFormat format;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -16,12 +16,14 @@
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
***/
|
***/
|
||||||
|
|
||||||
#include "sampleFormat.h"
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <boost/algorithm/string.hpp>
|
#include <boost/algorithm/string.hpp>
|
||||||
#include <boost/lexical_cast.hpp>
|
#include <boost/lexical_cast.hpp>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "sampleFormat.h"
|
||||||
|
#include "common/log.h"
|
||||||
|
|
||||||
|
|
||||||
namespace msg
|
namespace msg
|
||||||
{
|
{
|
||||||
|
@ -64,6 +66,7 @@ void SampleFormat::setFormat(uint32_t rate, uint16_t bits, uint16_t channels)
|
||||||
if (bits == 24)
|
if (bits == 24)
|
||||||
sampleSize = 4;
|
sampleSize = 4;
|
||||||
frameSize = channels*sampleSize;
|
frameSize = channels*sampleSize;
|
||||||
|
logD << "SampleFormat: " << rate << ":" << bits << ":" << channels << "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,16 @@
|
||||||
namespace msg
|
namespace msg
|
||||||
{
|
{
|
||||||
|
|
||||||
|
// sample and frame as defined in alsa:
|
||||||
|
// http://www.alsa-project.org/main/index.php/FramesPeriods
|
||||||
|
// Say we want to work with a stereo, 16-bit, 44.1 KHz stream, one-way (meaning, either in playback or in capture direction). Then we have:
|
||||||
|
// 'stereo' = number of channels: 2
|
||||||
|
// 1 analog sample is represented with 16 bits = 2 bytes
|
||||||
|
// 1 frame represents 1 analog sample from all channels; here we have 2 channels, and so:
|
||||||
|
// 1 frame = (num_channels) * (1 sample in bytes) = (2 channels) * (2 bytes (16 bits) per sample) = 4 bytes (32 bits)
|
||||||
|
// To sustain 2x 44.1 KHz analog rate - the system must be capable of data transfer rate, in Bytes/sec:
|
||||||
|
// Bps_rate = (num_channels) * (1 sample in bytes) * (analog_rate) = (1 frame) * (analog_rate) = ( 2 channels ) * (2 bytes/sample) * (44100 samples/sec) = 2*2*44100 = 176400 Bytes/sec (link to formula img)
|
||||||
|
|
||||||
class SampleFormat : public BaseMessage
|
class SampleFormat : public BaseMessage
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -39,7 +49,10 @@ public:
|
||||||
uint16_t bits;
|
uint16_t bits;
|
||||||
uint16_t channels;
|
uint16_t channels;
|
||||||
|
|
||||||
|
// size in [bytes] of a single mono sample, e.g. 2 bytes (= 16 bits)
|
||||||
uint16_t sampleSize;
|
uint16_t sampleSize;
|
||||||
|
|
||||||
|
// size in [bytes] of a frame (sum of sample sizes = #channel*sampleSize), e.g. 4 bytes (= 2 channel * 16 bit)
|
||||||
uint16_t frameSize;
|
uint16_t frameSize;
|
||||||
|
|
||||||
inline double msRate() const
|
inline double msRate() const
|
||||||
|
|
|
@ -26,26 +26,26 @@
|
||||||
class Encoder
|
class Encoder
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Encoder(const msg::SampleFormat& format) : sampleFormat(format), headerChunk(NULL)
|
Encoder(const msg::SampleFormat& format) : sampleFormat_(format), headerChunk_(NULL)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual ~Encoder()
|
virtual ~Encoder()
|
||||||
{
|
{
|
||||||
if (headerChunk != NULL)
|
if (headerChunk_ != NULL)
|
||||||
delete headerChunk;
|
delete headerChunk_;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual double encode(msg::PcmChunk* chunk) = 0;
|
virtual double encode(msg::PcmChunk* chunk) = 0;
|
||||||
|
|
||||||
virtual msg::Header* getHeader()
|
virtual msg::Header* getHeader()
|
||||||
{
|
{
|
||||||
return headerChunk;
|
return headerChunk_;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
msg::SampleFormat sampleFormat;
|
msg::SampleFormat sampleFormat_;
|
||||||
msg::Header* headerChunk;
|
msg::Header* headerChunk_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -22,65 +22,87 @@
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
#define READSIZE 16384
|
|
||||||
|
|
||||||
static FLAC__int32 pcm[READSIZE/*samples*/ * 2/*channels*/];
|
FlacEncoder::FlacEncoder(const msg::SampleFormat& format) : Encoder(format), encoder_(NULL), pcmBufferSize_(0), encodedSamples_(0)
|
||||||
size_t encodedSamples = 0;
|
|
||||||
static msg::PcmChunk* encodedChunk = NULL;
|
|
||||||
|
|
||||||
|
|
||||||
FlacEncoder::FlacEncoder(const msg::SampleFormat& format) : Encoder(format), encoder(NULL)
|
|
||||||
{
|
{
|
||||||
encodedChunk = new msg::PcmChunk();
|
encodedChunk_ = new msg::PcmChunk();
|
||||||
headerChunk = new msg::Header("flac");
|
headerChunk_ = new msg::Header("flac");
|
||||||
|
pcmBuffer_ = (FLAC__int32*)malloc(pcmBufferSize_ * sizeof(FLAC__int32));
|
||||||
initEncoder();
|
initEncoder();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
FlacEncoder::~FlacEncoder()
|
FlacEncoder::~FlacEncoder()
|
||||||
{
|
{
|
||||||
if (encoder != NULL)
|
if (encoder_ != NULL)
|
||||||
{
|
{
|
||||||
FLAC__stream_encoder_finish(encoder);
|
FLAC__stream_encoder_finish(encoder_);
|
||||||
FLAC__metadata_object_delete(metadata[0]);
|
FLAC__metadata_object_delete(metadata_[0]);
|
||||||
FLAC__metadata_object_delete(metadata[1]);
|
FLAC__metadata_object_delete(metadata_[1]);
|
||||||
FLAC__stream_encoder_delete(encoder);
|
FLAC__stream_encoder_delete(encoder_);
|
||||||
}
|
}
|
||||||
|
|
||||||
delete encodedChunk;
|
delete encodedChunk_;
|
||||||
}
|
free(pcmBuffer_);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
msg::Header* FlacEncoder::getHeaderChunk()
|
|
||||||
{
|
|
||||||
return headerChunk;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
double FlacEncoder::encode(msg::PcmChunk* chunk)
|
double FlacEncoder::encode(msg::PcmChunk* chunk)
|
||||||
{
|
{
|
||||||
logD << "payload: " << chunk->payloadSize << "\tsamples: " << chunk->payloadSize/4 << "\n";
|
int samples = chunk->getSampleCount();
|
||||||
int samples = chunk->payloadSize / 4;
|
int frames = chunk->getFrameCount();
|
||||||
for(int i=0; i<samples*2/*channels*/; i++)
|
logO << "payload: " << chunk->payloadSize << "\tframes: " << frames << "\tsamples: " << samples << "\tduration: " << chunk->duration<chronos::msec>().count() << "\n";
|
||||||
{
|
|
||||||
pcm[i] = (FLAC__int32)(((FLAC__int16)(FLAC__int8)chunk->payload[2*i+1] << 8) | (FLAC__int16)(0x00ff&chunk->payload[2*i]));
|
|
||||||
}
|
|
||||||
FLAC__stream_encoder_process_interleaved(encoder, pcm, samples);
|
|
||||||
|
|
||||||
double res = encodedSamples / ((double)sampleFormat.rate / 1000.);
|
if (pcmBufferSize_ < samples)
|
||||||
if (encodedSamples > 0)
|
|
||||||
{
|
{
|
||||||
logD << "encoded: " << chunk->payloadSize << "\tsamples: " << encodedSamples << "\tres: " << res << "\n";
|
pcmBufferSize_ = samples;
|
||||||
encodedSamples = 0;
|
pcmBuffer_ = (FLAC__int32*)realloc(pcmBuffer_, pcmBufferSize_ * sizeof(FLAC__int32));
|
||||||
chunk->payloadSize = encodedChunk->payloadSize;
|
|
||||||
chunk->payload = (char*)realloc(chunk->payload, encodedChunk->payloadSize);
|
|
||||||
memcpy(chunk->payload, encodedChunk->payload, encodedChunk->payloadSize);
|
|
||||||
encodedChunk->payloadSize = 0;
|
|
||||||
encodedChunk->payload = (char*)realloc(encodedChunk->payload, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;//chunk->duration<chronos::msec>().count();
|
for(int i=0; i<samples; i++)
|
||||||
|
{
|
||||||
|
pcmBuffer_[i] = (FLAC__int32)(((FLAC__int16)(FLAC__int8)chunk->payload[2*i+1] << 8) | (FLAC__int16)(0x00ff&chunk->payload[2*i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
FLAC__stream_encoder_process_interleaved(encoder_, pcmBuffer_, frames);
|
||||||
|
|
||||||
|
double resMs = encodedSamples_ / ((double)sampleFormat_.rate / 1000.);
|
||||||
|
if (encodedSamples_ > 0)
|
||||||
|
{
|
||||||
|
logD << "encoded: " << chunk->payloadSize << "\tframes: " << encodedSamples_ << "\tres: " << resMs << "\n";
|
||||||
|
encodedSamples_ = 0;
|
||||||
|
chunk->payloadSize = encodedChunk_->payloadSize;
|
||||||
|
chunk->payload = (char*)realloc(chunk->payload, encodedChunk_->payloadSize);
|
||||||
|
memcpy(chunk->payload, encodedChunk_->payload, encodedChunk_->payloadSize);
|
||||||
|
encodedChunk_->payloadSize = 0;
|
||||||
|
encodedChunk_->payload = (char*)realloc(encodedChunk_->payload, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return resMs;//chunk->duration<chronos::msec>().count();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
FLAC__StreamEncoderWriteStatus FlacEncoder::write_callback(const FLAC__StreamEncoder *encoder,
|
||||||
|
const FLAC__byte buffer[],
|
||||||
|
size_t bytes,
|
||||||
|
unsigned samples,
|
||||||
|
unsigned current_frame)
|
||||||
|
{
|
||||||
|
logD << "write_callback: " << bytes << ", " << samples << ", " << current_frame << "\n";
|
||||||
|
if ((current_frame == 0) && (bytes > 0) && (samples == 0))
|
||||||
|
{
|
||||||
|
headerChunk_->payload = (char*)realloc(headerChunk_->payload, headerChunk_->payloadSize + bytes);
|
||||||
|
memcpy(headerChunk_->payload + headerChunk_->payloadSize, buffer, bytes);
|
||||||
|
headerChunk_->payloadSize += bytes;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
encodedChunk_->payload = (char*)realloc(encodedChunk_->payload, encodedChunk_->payloadSize + bytes);
|
||||||
|
memcpy(encodedChunk_->payload + encodedChunk_->payloadSize, buffer, bytes);
|
||||||
|
encodedChunk_->payloadSize += bytes;
|
||||||
|
encodedSamples_ += samples;
|
||||||
|
}
|
||||||
|
return FLAC__STREAM_ENCODER_WRITE_STATUS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -91,29 +113,11 @@ FLAC__StreamEncoderWriteStatus write_callback(const FLAC__StreamEncoder *encoder
|
||||||
unsigned current_frame,
|
unsigned current_frame,
|
||||||
void *client_data)
|
void *client_data)
|
||||||
{
|
{
|
||||||
logD << "write_callback: " << bytes << ", " << samples << ", " << current_frame << "\n";
|
|
||||||
FlacEncoder* flacEncoder = (FlacEncoder*)client_data;
|
FlacEncoder* flacEncoder = (FlacEncoder*)client_data;
|
||||||
if ((current_frame == 0) && (bytes > 0) && (samples == 0))
|
return flacEncoder->write_callback(encoder, buffer, bytes, samples, current_frame);
|
||||||
{
|
|
||||||
msg::Header* headerChunk = flacEncoder->getHeaderChunk();
|
|
||||||
headerChunk->payload = (char*)realloc(headerChunk->payload, headerChunk->payloadSize + bytes);
|
|
||||||
memcpy(headerChunk->payload + headerChunk->payloadSize, buffer, bytes);
|
|
||||||
headerChunk->payloadSize += bytes;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
encodedChunk->payload = (char*)realloc(encodedChunk->payload, encodedChunk->payloadSize + bytes);
|
|
||||||
memcpy(encodedChunk->payload + encodedChunk->payloadSize, buffer, bytes);
|
|
||||||
encodedChunk->payloadSize += bytes;
|
|
||||||
encodedSamples += samples;
|
|
||||||
}
|
|
||||||
return FLAC__STREAM_ENCODER_WRITE_STATUS_OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void FlacEncoder::initEncoder()
|
void FlacEncoder::initEncoder()
|
||||||
{
|
{
|
||||||
FLAC__bool ok = true;
|
FLAC__bool ok = true;
|
||||||
|
@ -121,93 +125,53 @@ void FlacEncoder::initEncoder()
|
||||||
FLAC__StreamMetadata_VorbisComment_Entry entry;
|
FLAC__StreamMetadata_VorbisComment_Entry entry;
|
||||||
|
|
||||||
// allocate the encoder
|
// allocate the encoder
|
||||||
if((encoder = FLAC__stream_encoder_new()) == NULL) {
|
if ((encoder_ = FLAC__stream_encoder_new()) == NULL)
|
||||||
|
{
|
||||||
fprintf(stderr, "ERROR: allocating encoder\n");
|
fprintf(stderr, "ERROR: allocating encoder\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ok &= FLAC__stream_encoder_set_verify(encoder, true);
|
ok &= FLAC__stream_encoder_set_verify(encoder_, true);
|
||||||
ok &= FLAC__stream_encoder_set_compression_level(encoder, 5);
|
// compression levels (0-8):
|
||||||
ok &= FLAC__stream_encoder_set_channels(encoder, sampleFormat.channels);
|
// https://xiph.org/flac/api/group__flac__stream__encoder.html#gae49cf32f5256cb47eecd33779493ac85
|
||||||
ok &= FLAC__stream_encoder_set_bits_per_sample(encoder, sampleFormat.bits);
|
// latency:
|
||||||
ok &= FLAC__stream_encoder_set_sample_rate(encoder, sampleFormat.rate);
|
// 0-2: 1152 frames, ~26.1224ms
|
||||||
|
// 3-8: 4096 frames, ~92.8798ms
|
||||||
|
ok &= FLAC__stream_encoder_set_compression_level(encoder_, 2);
|
||||||
|
ok &= FLAC__stream_encoder_set_channels(encoder_, sampleFormat_.channels);
|
||||||
|
ok &= FLAC__stream_encoder_set_bits_per_sample(encoder_, sampleFormat_.bits);
|
||||||
|
ok &= FLAC__stream_encoder_set_sample_rate(encoder_, sampleFormat_.rate);
|
||||||
|
|
||||||
// now add some metadata; we'll add some tags and a padding block
|
// now add some metadata; we'll add some tags and a padding block
|
||||||
if(ok) {
|
if (ok)
|
||||||
if(
|
{
|
||||||
(metadata[0] = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT)) == NULL ||
|
if (
|
||||||
(metadata[1] = FLAC__metadata_object_new(FLAC__METADATA_TYPE_PADDING)) == NULL ||
|
(metadata_[0] = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT)) == NULL ||
|
||||||
|
(metadata_[1] = FLAC__metadata_object_new(FLAC__METADATA_TYPE_PADDING)) == NULL ||
|
||||||
// there are many tag (vorbiscomment) functions but these are convenient for this particular use:
|
// there are many tag (vorbiscomment) functions but these are convenient for this particular use:
|
||||||
!FLAC__metadata_object_vorbiscomment_entry_from_name_value_pair(&entry, "ARTIST", "Some Artist") ||
|
!FLAC__metadata_object_vorbiscomment_entry_from_name_value_pair(&entry, "TITLE", "SnapStream") ||
|
||||||
!FLAC__metadata_object_vorbiscomment_append_comment(metadata[0], entry, false) ||
|
!FLAC__metadata_object_vorbiscomment_append_comment(metadata_[0], entry, false) ||
|
||||||
!FLAC__metadata_object_vorbiscomment_entry_from_name_value_pair(&entry, "YEAR", "1984") ||
|
!FLAC__metadata_object_vorbiscomment_entry_from_name_value_pair(&entry, "VERSION", VERSION) ||
|
||||||
!FLAC__metadata_object_vorbiscomment_append_comment(metadata[0], entry, false)
|
!FLAC__metadata_object_vorbiscomment_append_comment(metadata_[0], entry, false)
|
||||||
) {
|
)
|
||||||
|
{
|
||||||
fprintf(stderr, "ERROR: out of memory or tag error\n");
|
fprintf(stderr, "ERROR: out of memory or tag error\n");
|
||||||
ok = false;
|
ok = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
metadata[1]->length = 1234; // set the padding length
|
metadata_[1]->length = 1234; // set the padding length
|
||||||
|
ok = FLAC__stream_encoder_set_metadata(encoder_, metadata_, 2);
|
||||||
ok = FLAC__stream_encoder_set_metadata(encoder, metadata, 2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// initialize encoder
|
// initialize encoder
|
||||||
if(ok) {
|
if (ok)
|
||||||
init_status = FLAC__stream_encoder_init_stream(encoder, write_callback, NULL, NULL, NULL, this);
|
{
|
||||||
if(init_status != FLAC__STREAM_ENCODER_INIT_STATUS_OK) {
|
init_status = FLAC__stream_encoder_init_stream(encoder_, ::write_callback, NULL, NULL, NULL, this);
|
||||||
|
if(init_status != FLAC__STREAM_ENCODER_INIT_STATUS_OK)
|
||||||
|
{
|
||||||
fprintf(stderr, "ERROR: initializing encoder: %s\n", FLAC__StreamEncoderInitStatusString[init_status]);
|
fprintf(stderr, "ERROR: initializing encoder: %s\n", FLAC__StreamEncoderInitStatusString[init_status]);
|
||||||
ok = false;
|
ok = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
// read blocks of samples from WAVE file and feed to encoder
|
|
||||||
if(ok) {
|
|
||||||
size_t left = (size_t)total_samples;
|
|
||||||
while(ok && left) {
|
|
||||||
size_t need = (left>READSIZE? (size_t)READSIZE : (size_t)left);
|
|
||||||
if(fread(buffer, channels*(bps/8), need, fin) != need) {
|
|
||||||
fprintf(stderr, "ERROR: reading from WAVE file\n");
|
|
||||||
ok = false;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// convert the packed little-endian 16-bit PCM samples from WAVE into an interleaved FLAC__int32 buffer for libFLAC
|
|
||||||
size_t i;
|
|
||||||
for(i = 0; i < need*channels; i++) {
|
|
||||||
// inefficient but simple and works on big- or little-endian machines
|
|
||||||
pcm[i] = (FLAC__int32)(((FLAC__int16)(FLAC__int8)buffer[2*i+1] << 8) | (FLAC__int16)buffer[2*i]);
|
|
||||||
}
|
|
||||||
// feed samples to encoder
|
|
||||||
ok = FLAC__stream_encoder_process_interleaved(encoder, pcm, need);
|
|
||||||
}
|
|
||||||
left -= need;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ok &= FLAC__stream_encoder_finish(encoder);
|
|
||||||
|
|
||||||
fprintf(stderr, "encoding: %s\n", ok? "succeeded" : "FAILED");
|
|
||||||
fprintf(stderr, " state: %s\n", FLAC__StreamEncoderStateString[FLAC__stream_encoder_get_state(encoder)]);
|
|
||||||
|
|
||||||
// now that encoding is finished, the metadata can be freed
|
|
||||||
FLAC__metadata_object_delete(metadata[0]);
|
|
||||||
FLAC__metadata_object_delete(metadata[1]);
|
|
||||||
|
|
||||||
FLAC__stream_encoder_delete(encoder);
|
|
||||||
fclose(fin);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
void progress_callback(const FLAC__StreamEncoder *encoder, FLAC__uint64 bytes_written, FLAC__uint64 samples_written, unsigned frames_written, unsigned total_frames_estimate, void *client_data)
|
|
||||||
{
|
|
||||||
(void)encoder, (void)client_data;
|
|
||||||
fprintf(stderr, "wrote %d bytes, %d, %u samples, %u/%u frames\n", bytes_written, samples_written, total_samples, frames_written, total_frames_estimate);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -33,13 +33,20 @@ public:
|
||||||
FlacEncoder(const msg::SampleFormat& format);
|
FlacEncoder(const msg::SampleFormat& format);
|
||||||
~FlacEncoder();
|
~FlacEncoder();
|
||||||
virtual double encode(msg::PcmChunk* chunk);
|
virtual double encode(msg::PcmChunk* chunk);
|
||||||
msg::Header* getHeaderChunk();
|
|
||||||
|
FLAC__StreamEncoderWriteStatus write_callback(const FLAC__StreamEncoder *encoder, const FLAC__byte buffer[], size_t bytes, unsigned samples, unsigned current_frame);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void initEncoder();
|
void initEncoder();
|
||||||
FLAC__StreamEncoder *encoder;
|
|
||||||
FLAC__StreamMetadata *metadata[2];
|
FLAC__StreamEncoder *encoder_;
|
||||||
// virtual void progress_callback(FLAC__uint64 bytes_written, FLAC__uint64 samples_written, unsigned frames_written, unsigned total_frames_estimate);
|
FLAC__StreamMetadata *metadata_[2];
|
||||||
|
|
||||||
|
FLAC__int32 *pcmBuffer_;
|
||||||
|
int pcmBufferSize_;
|
||||||
|
|
||||||
|
msg::PcmChunk* encodedChunk_;
|
||||||
|
size_t encodedSamples_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -24,10 +24,9 @@
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
|
||||||
OggEncoder::OggEncoder(const msg::SampleFormat& format) : Encoder(format), eos(0)
|
OggEncoder::OggEncoder(const msg::SampleFormat& format) : Encoder(format), lastGranulepos(0), eos(0)
|
||||||
{
|
{
|
||||||
init();
|
init();
|
||||||
lastGranulepos = -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -35,21 +34,14 @@ OggEncoder::OggEncoder(const msg::SampleFormat& format) : Encoder(format), eos(0
|
||||||
double OggEncoder::encode(msg::PcmChunk* chunk)
|
double OggEncoder::encode(msg::PcmChunk* chunk)
|
||||||
{
|
{
|
||||||
double res = 0;
|
double res = 0;
|
||||||
//logD << "-> pcm: " << wireChunk->length << endl;
|
logO << "payload: " << chunk->payloadSize << "\tframes: " << chunk->getFrameCount() << "\tduration: " << chunk->duration<chronos::msec>().count() << "\n";
|
||||||
int bytes = chunk->payloadSize / 4;
|
int bytes = chunk->payloadSize / 4;
|
||||||
float **buffer=vorbis_analysis_buffer(&vd, bytes);
|
float **buffer=vorbis_analysis_buffer(&vd, bytes);
|
||||||
|
|
||||||
/* uninterleave samples */
|
/* uninterleave samples */
|
||||||
for(int i=0; i<bytes; i++)
|
for (int i=0; i<bytes; i++)
|
||||||
{
|
{
|
||||||
int idx = 4*i;
|
int idx = 4*i;
|
||||||
/* int8_t high = chunk->payload[idx+1];
|
|
||||||
int8_t low = chunk->payload[idx];
|
|
||||||
buffer[0][i]=((high << 8) | (0x00ff&low))/32768.f;
|
|
||||||
high = chunk->payload[idx+3];
|
|
||||||
low = chunk->payload[idx+2];
|
|
||||||
buffer[1][i]=((high << 8) | (0x00ff&low))/32768.f;
|
|
||||||
*/
|
|
||||||
buffer[0][i]=((((int8_t)chunk->payload[idx+1]) << 8) | (0x00ff&((int8_t)chunk->payload[idx])))/32768.f;
|
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;
|
buffer[1][i]=((((int8_t)chunk->payload[idx+3]) << 8) | (0x00ff&((int8_t)chunk->payload[idx+2])))/32768.f;
|
||||||
}
|
}
|
||||||
|
@ -61,27 +53,27 @@ double OggEncoder::encode(msg::PcmChunk* chunk)
|
||||||
more involved (potentially parallel) processing. Get a single
|
more involved (potentially parallel) processing. Get a single
|
||||||
block for encoding now */
|
block for encoding now */
|
||||||
size_t pos = 0;
|
size_t pos = 0;
|
||||||
while(vorbis_analysis_blockout(&vd,&vb)==1)
|
while (vorbis_analysis_blockout(&vd,&vb)==1)
|
||||||
{
|
{
|
||||||
/* analysis, assume we want to use bitrate management */
|
/* analysis, assume we want to use bitrate management */
|
||||||
vorbis_analysis(&vb,NULL);
|
vorbis_analysis(&vb,NULL);
|
||||||
vorbis_bitrate_addblock(&vb);
|
vorbis_bitrate_addblock(&vb);
|
||||||
|
|
||||||
while(vorbis_bitrate_flushpacket(&vd,&op))
|
while (vorbis_bitrate_flushpacket(&vd,&op))
|
||||||
{
|
{
|
||||||
/* weld the packet into the bitstream */
|
/* weld the packet into the bitstream */
|
||||||
ogg_stream_packetin(&os,&op);
|
ogg_stream_packetin(&os, &op);
|
||||||
|
|
||||||
/* write out pages (if any) */
|
/* write out pages (if any) */
|
||||||
while(true)
|
while (true)
|
||||||
{
|
{
|
||||||
// int result = ogg_stream_pageout(&os,&og);
|
int result = ogg_stream_flush(&os, &og);
|
||||||
int result = ogg_stream_flush(&os,&og);
|
|
||||||
if (result == 0)
|
if (result == 0)
|
||||||
break;
|
break;
|
||||||
res = true;
|
res = os.granulepos - lastGranulepos;
|
||||||
|
|
||||||
size_t nextLen = pos + og.header_len + og.body_len;
|
size_t nextLen = pos + og.header_len + og.body_len;
|
||||||
|
// make chunk larger
|
||||||
if (chunk->payloadSize < nextLen)
|
if (chunk->payloadSize < nextLen)
|
||||||
chunk->payload = (char*)realloc(chunk->payload, nextLen);
|
chunk->payload = (char*)realloc(chunk->payload, nextLen);
|
||||||
|
|
||||||
|
@ -95,17 +87,17 @@ double OggEncoder::encode(msg::PcmChunk* chunk)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (res)
|
|
||||||
|
if (res > 0)
|
||||||
{
|
{
|
||||||
if (lastGranulepos == -1)
|
res /= (sampleFormat_.rate / 1000.);
|
||||||
res = os.granulepos;
|
logO << "res: " << res << "\n";
|
||||||
else
|
|
||||||
res = os.granulepos - lastGranulepos;
|
|
||||||
res /= (sampleFormat.rate / 1000.);
|
|
||||||
lastGranulepos = os.granulepos;
|
lastGranulepos = os.granulepos;
|
||||||
|
// make chunk smaller
|
||||||
chunk->payload = (char*)realloc(chunk->payload, pos);
|
chunk->payload = (char*)realloc(chunk->payload, pos);
|
||||||
chunk->payloadSize = pos;
|
chunk->payloadSize = pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,17 +136,19 @@ void OggEncoder::init()
|
||||||
|
|
||||||
*********************************************************************/
|
*********************************************************************/
|
||||||
|
|
||||||
ret=vorbis_encode_init_vbr(&vi, sampleFormat.channels, sampleFormat.rate, 1.0);
|
ret = vorbis_encode_init_vbr(&vi, sampleFormat_.channels, sampleFormat_.rate, 1.0);
|
||||||
|
|
||||||
/* do not continue if setup failed; this can happen if we ask for a
|
/* do not continue if setup failed; this can happen if we ask for a
|
||||||
mode that libVorbis does not support (eg, too low a bitrate, etc,
|
mode that libVorbis does not support (eg, too low a bitrate, etc,
|
||||||
will return 'OV_EIMPL') */
|
will return 'OV_EIMPL') */
|
||||||
|
|
||||||
if(ret)exit(1);
|
if (ret)
|
||||||
|
exit(1);
|
||||||
|
|
||||||
/* add a comment */
|
/* add a comment */
|
||||||
vorbis_comment_init(&vc);
|
vorbis_comment_init(&vc);
|
||||||
vorbis_comment_add_tag(&vc,"ENCODER","snapstream");
|
vorbis_comment_add_tag(&vc, "TITLE", "SnapStream");
|
||||||
|
vorbis_comment_add_tag(&vc, "VERSION", VERSION);
|
||||||
|
|
||||||
/* 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);
|
||||||
|
@ -185,28 +179,21 @@ void OggEncoder::init()
|
||||||
/* This ensures the actual
|
/* This ensures the actual
|
||||||
* audio data will start on a new page, as per spec
|
* audio data will start on a new page, as per spec
|
||||||
*/
|
*/
|
||||||
// while(!eos){
|
|
||||||
size_t pos(0);
|
size_t pos(0);
|
||||||
headerChunk = new msg::Header("ogg");
|
headerChunk_ = new msg::Header("ogg");
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
int result=ogg_stream_flush(&os,&og);
|
int result=ogg_stream_flush(&os,&og);
|
||||||
if (result == 0)
|
if (result == 0)
|
||||||
break;
|
break;
|
||||||
headerChunk->payloadSize += og.header_len + og.body_len;
|
headerChunk_->payloadSize += og.header_len + og.body_len;
|
||||||
headerChunk->payload = (char*)realloc(headerChunk->payload, headerChunk->payloadSize);
|
headerChunk_->payload = (char*)realloc(headerChunk_->payload, headerChunk_->payloadSize);
|
||||||
logD << "HeadLen: " << og.header_len << ", bodyLen: " << og.body_len << ", result: " << result << "\n";
|
logD << "HeadLen: " << og.header_len << ", bodyLen: " << og.body_len << ", result: " << result << "\n";
|
||||||
memcpy(headerChunk->payload + pos, og.header, og.header_len);
|
memcpy(headerChunk_->payload + pos, og.header, og.header_len);
|
||||||
pos += og.header_len;
|
pos += og.header_len;
|
||||||
memcpy(headerChunk->payload + pos, og.body, og.body_len);
|
memcpy(headerChunk_->payload + pos, og.body, og.body_len);
|
||||||
pos += og.body_len;
|
pos += og.body_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// fwrite(og.header,1,og.header_len,stdout);
|
|
||||||
// fwrite(og.body,1,og.body_len,stdout);
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
|
|
||||||
PcmEncoder::PcmEncoder(const msg::SampleFormat& format) : Encoder(format)
|
PcmEncoder::PcmEncoder(const msg::SampleFormat& format) : Encoder(format)
|
||||||
{
|
{
|
||||||
headerChunk = new msg::Header("pcm");
|
headerChunk_ = new msg::Header("pcm");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -88,66 +88,68 @@ int main(int argc, char* argv[])
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (runAsDaemon)
|
|
||||||
{
|
|
||||||
daemonize("/var/run/snapserver.pid");
|
|
||||||
logS(kLogNotice) << "daemon started." << endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::clog.rdbuf(new Log("snapserver", LOG_DAEMON));
|
std::clog.rdbuf(new Log("snapserver", LOG_DAEMON));
|
||||||
|
|
||||||
using namespace std; // For atoi.
|
|
||||||
|
|
||||||
timeval tvChunk;
|
|
||||||
gettimeofday(&tvChunk, NULL);
|
|
||||||
long nextTick = chronos::getTickCount();
|
|
||||||
|
|
||||||
umask(0);
|
|
||||||
mkfifo(fifoName.c_str(), 0666);
|
|
||||||
msg::SampleFormat format(sampleFormat);
|
msg::SampleFormat format(sampleFormat);
|
||||||
size_t duration = 50;
|
|
||||||
//size_t chunkSize = duration*format.rate*format.frameSize / 1000;
|
|
||||||
std::unique_ptr<Encoder> encoder;
|
std::unique_ptr<Encoder> encoder;
|
||||||
if (codec == "ogg")
|
if (codec == "ogg")
|
||||||
encoder.reset(new OggEncoder(sampleFormat));
|
encoder.reset(new OggEncoder(format));
|
||||||
else if (codec == "pcm")
|
else if (codec == "pcm")
|
||||||
encoder.reset(new PcmEncoder(sampleFormat));
|
encoder.reset(new PcmEncoder(format));
|
||||||
else if (codec == "flac")
|
else if (codec == "flac")
|
||||||
encoder.reset(new FlacEncoder(sampleFormat));
|
encoder.reset(new FlacEncoder(format));
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
cout << "unknown codec: " << codec << "\n";
|
cout << "unknown codec: " << codec << "\n";
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
umask(0);
|
||||||
|
mkfifo(fifoName.c_str(), 0666);
|
||||||
|
int fd = open(fifoName.c_str(), O_RDONLY | O_NONBLOCK);
|
||||||
|
if (fd == -1)
|
||||||
|
{
|
||||||
|
cout << "failed to open fifo: " << fifoName << "\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
msg::ServerSettings serverSettings;
|
msg::ServerSettings serverSettings;
|
||||||
serverSettings.bufferMs = bufferMs;
|
serverSettings.bufferMs = bufferMs;
|
||||||
|
|
||||||
|
signal(SIGHUP, signal_handler);
|
||||||
|
signal(SIGTERM, signal_handler);
|
||||||
|
signal(SIGINT, signal_handler);
|
||||||
|
|
||||||
|
if (runAsDaemon)
|
||||||
|
{
|
||||||
|
daemonize("/var/run/snapserver.pid");
|
||||||
|
logS(kLogNotice) << "daemon started." << endl;
|
||||||
|
}
|
||||||
|
|
||||||
ControlServer* controlServer = new ControlServer(port);
|
ControlServer* controlServer = new ControlServer(port);
|
||||||
controlServer->setServerSettings(&serverSettings);
|
controlServer->setServerSettings(&serverSettings);
|
||||||
controlServer->setFormat(&format);
|
controlServer->setFormat(&format);
|
||||||
controlServer->setHeader(encoder->getHeader());
|
controlServer->setHeader(encoder->getHeader());
|
||||||
controlServer->start();
|
controlServer->start();
|
||||||
|
|
||||||
signal(SIGHUP, signal_handler);
|
|
||||||
signal(SIGTERM, signal_handler);
|
|
||||||
signal(SIGINT, signal_handler);
|
|
||||||
|
|
||||||
PublishAvahi publishAvahi("SnapCast");
|
PublishAvahi publishAvahi("SnapCast");
|
||||||
std::vector<AvahiService> services;
|
std::vector<AvahiService> services;
|
||||||
services.push_back(AvahiService("_snapcast._tcp", port));
|
services.push_back(AvahiService("_snapcast._tcp", port));
|
||||||
publishAvahi.publish(services);
|
publishAvahi.publish(services);
|
||||||
|
|
||||||
|
timeval tvChunk;
|
||||||
|
gettimeofday(&tvChunk, NULL);
|
||||||
|
long nextTick = chronos::getTickCount();
|
||||||
|
size_t pcmReadMs = 20;
|
||||||
|
|
||||||
while (!g_terminated)
|
while (!g_terminated)
|
||||||
{
|
{
|
||||||
int fd = open(fifoName.c_str(), O_RDONLY | O_NONBLOCK);
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
shared_ptr<msg::PcmChunk> chunk;
|
shared_ptr<msg::PcmChunk> chunk;
|
||||||
while (!g_terminated)//cin.good())
|
while (!g_terminated)//cin.good())
|
||||||
{
|
{
|
||||||
chunk.reset(new msg::PcmChunk(sampleFormat, duration));
|
chunk.reset(new msg::PcmChunk(sampleFormat, pcmReadMs));
|
||||||
int toRead = chunk->payloadSize;
|
int toRead = chunk->payloadSize;
|
||||||
int len = 0;
|
int len = 0;
|
||||||
do
|
do
|
||||||
|
@ -166,9 +168,9 @@ int main(int argc, char* argv[])
|
||||||
double chunkDuration = encoder->encode(chunk.get());
|
double chunkDuration = encoder->encode(chunk.get());
|
||||||
if (chunkDuration > 0)
|
if (chunkDuration > 0)
|
||||||
controlServer->send(chunk);
|
controlServer->send(chunk);
|
||||||
//logD << chunk->tv_sec << ", " << chunk->tv_usec / 1000 << "\n";
|
// logO << chunkDuration << "\n";
|
||||||
// addUs(tvChunk, 1000*chunk->getDuration());
|
// addUs(tvChunk, 1000*chunk->getDuration());
|
||||||
nextTick += duration;
|
nextTick += pcmReadMs;
|
||||||
chronos::addUs(tvChunk, chunkDuration * 1000);
|
chronos::addUs(tvChunk, chunkDuration * 1000);
|
||||||
long currentTick = chronos::getTickCount();
|
long currentTick = chronos::getTickCount();
|
||||||
if (nextTick > currentTick)
|
if (nextTick > currentTick)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue