kopia lustrzana https://github.com/F5OEO/rpitx
Remove dependency on libsndfile
Wav files include the length of the data in the file header. libsndfile expects this length to be there and be sane. Unfortunately, when piping data from avconv, this length is dummied out because the full length is not known yet, so libsndfile can't be used to process a pipe data stream. This removes the libsndfile dependency and replaces the used functions with hand written ones.pull/72/head
rodzic
8f25432e4f
commit
b85ece8df6
|
@ -1,6 +1,5 @@
|
|||
#include <Python.h>
|
||||
#include <assert.h>
|
||||
#include <sndfile.h>
|
||||
|
||||
#include "../RpiTx.h"
|
||||
#include "../RpiGpio.h"
|
||||
|
@ -18,48 +17,16 @@ struct module_state {
|
|||
static struct module_state _state;
|
||||
#endif
|
||||
|
||||
int streamFileNo = 0;
|
||||
static SNDFILE* sndFile;
|
||||
static int bitRate;
|
||||
|
||||
|
||||
// These methods used by libsndfile's virtual file open function
|
||||
static sf_count_t virtualSndfileGetLength(void* unused) {
|
||||
assert(0 && "virtualSndfileGetLength should not be called");
|
||||
return 0;
|
||||
}
|
||||
static sf_count_t virtualSndfileRead(void* const dest, sf_count_t count, void* const unused) {
|
||||
// So, the WAV file format starts with 12 bytes: the letters "RIFF", 4 byte
|
||||
// chunk size, and the letters "WAVE". Because we're streaming to a pipe, we
|
||||
// don't know the size of the file, and avconv just sticks in 0xFFFFFFFF, so
|
||||
// we'll just fudge a value in.
|
||||
if (count == 12) {
|
||||
read(streamFileNo, dest, count); // Just skip these
|
||||
memcpy(dest, "RIFF\x42\xB0\x0A\x00WAVE", count);
|
||||
return count;
|
||||
}
|
||||
|
||||
const ssize_t bytesRead = read(streamFileNo, dest, count);
|
||||
return bytesRead;
|
||||
}
|
||||
static sf_count_t virtualSndfileTell(void* const unused) {
|
||||
assert(0 && "virtualSndfileTell should not be called");
|
||||
return 0;
|
||||
}
|
||||
static sf_count_t virtualSndfileSeek(
|
||||
const sf_count_t offset,
|
||||
const int whence,
|
||||
void* const unused
|
||||
) {
|
||||
assert(0 && "virtualSndfileSeek should not be called");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int streamFileNo = 0;
|
||||
static int sampleRate = 48000;
|
||||
|
||||
typedef struct {
|
||||
double frequency;
|
||||
uint32_t waitForThisSample;
|
||||
} samplerf_t;
|
||||
|
||||
static size_t read_float(int streamFileNo, float* wavBuffer, const size_t count);
|
||||
|
||||
/**
|
||||
* Formats a chunk of an array of a mono 48k wav at a time and outputs IQ
|
||||
* formatted array for broadcast.
|
||||
|
@ -76,10 +43,10 @@ static ssize_t formatRfWrapper(void* const outBuffer, const size_t count) {
|
|||
const int excursion = 6000;
|
||||
int numBytesWritten = 0;
|
||||
samplerf_t samplerf;
|
||||
samplerf.waitForThisSample = 1e9 / ((float)bitRate); //en 100 de nanosecond
|
||||
samplerf.waitForThisSample = 1e9 / ((float)sampleRate); //en 100 de nanosecond
|
||||
char* const out = outBuffer;
|
||||
|
||||
while (numBytesWritten < count) {
|
||||
while (numBytesWritten < count - sizeof(samplerf_t)) {
|
||||
for (
|
||||
;
|
||||
numBytesWritten <= count - sizeof(samplerf_t) && wavOffset < wavFilled;
|
||||
|
@ -94,12 +61,27 @@ static ssize_t formatRfWrapper(void* const outBuffer, const size_t count) {
|
|||
assert(wavOffset <= wavFilled);
|
||||
|
||||
if (wavOffset == wavFilled) {
|
||||
wavFilled = sf_readf_float(sndFile, wavBuffer, COUNT_OF(wavBuffer));
|
||||
wavFilled = read_float(streamFileNo, wavBuffer, COUNT_OF(wavBuffer));
|
||||
wavOffset = 0;
|
||||
}
|
||||
}
|
||||
return numBytesWritten;
|
||||
}
|
||||
static size_t read_float(int streamFileNo, float* wavBuffer, const size_t count) {
|
||||
// Samples are stored as 16 bit signed integers in range of -32768 to 32767
|
||||
uint16_t sample;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
const int byteCount = read(streamFileNo, &sample, sizeof(sample));
|
||||
if (byteCount != sizeof(sample)) {
|
||||
return i;
|
||||
}
|
||||
// TODO: I don't know if this should be dividing by 32767 or 32768. Probably
|
||||
// doesn't matter too much.
|
||||
*wavBuffer = ((float)le16toh(sample)) / 32768;
|
||||
++wavBuffer;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
static void dummyFunction(void) {
|
||||
assert(0 && "dummyFunction should not be called");
|
||||
}
|
||||
|
@ -109,34 +91,111 @@ static PyObject*
|
|||
_rpitx_broadcast_fm(PyObject* self, PyObject* args) {
|
||||
float frequency;
|
||||
|
||||
struct module_state *st = GETSTATE(self);
|
||||
|
||||
if (!PyArg_ParseTuple(args, "if", &streamFileNo, &frequency)) {
|
||||
struct module_state *st = GETSTATE(self);
|
||||
PyErr_SetString(st->error, "Invalid arguments");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SF_INFO sfInfo;
|
||||
SF_VIRTUAL_IO virtualIo = {
|
||||
.get_filelen = virtualSndfileGetLength,
|
||||
.seek = virtualSndfileSeek,
|
||||
.read = virtualSndfileRead,
|
||||
.write = NULL,
|
||||
.tell = virtualSndfileTell
|
||||
};
|
||||
sndFile = sf_open_virtual(&virtualIo, SFM_READ, &sfInfo, NULL);
|
||||
if (sf_error(sndFile) != SF_ERR_NO_ERROR) {
|
||||
char message[100];
|
||||
snprintf(
|
||||
message,
|
||||
COUNT_OF(message),
|
||||
"Unable to open pipe: %s",
|
||||
sf_strerror(sndFile));
|
||||
message[COUNT_OF(message) - 1] = '\0';
|
||||
struct module_state *st = GETSTATE(self);
|
||||
PyErr_SetString(st->error, message);
|
||||
union {
|
||||
char char4[4];
|
||||
uint32_t uint32;
|
||||
uint16_t uint16;
|
||||
} buffer;
|
||||
size_t byteCount = read(streamFileNo, buffer.char4, 4);
|
||||
if (byteCount != 4 || strncmp(buffer.char4, "RIFF", 4) != 0) {
|
||||
PyErr_SetString(st->error, "Not a WAV file");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Skip chunk size
|
||||
byteCount = read(streamFileNo, buffer.char4, 4);
|
||||
if (byteCount != 4) {
|
||||
PyErr_SetString(st->error, "Not a WAV file");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
byteCount = read(streamFileNo, buffer.char4, 4);
|
||||
if (byteCount != 4 || strncmp(buffer.char4, "WAVE", 4) != 0) {
|
||||
PyErr_SetString(st->error, "Not a WAV file");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
byteCount = read(streamFileNo, buffer.char4, 4);
|
||||
if (byteCount != 4 || strncmp(buffer.char4, "fmt ", 4) != 0) {
|
||||
PyErr_SetString(st->error, "Not a WAV file");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
byteCount = read(streamFileNo, &buffer.uint32, 4);
|
||||
buffer.uint32 = le32toh(buffer.uint32);
|
||||
if (byteCount != 4 || buffer.uint32 != 16) {
|
||||
PyErr_SetString(st->error, "Not a PCM WAV file");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
byteCount = read(streamFileNo, &buffer.uint16, 2);
|
||||
buffer.uint16 = le16toh(buffer.uint16);
|
||||
if (byteCount != 2 || buffer.uint16 != 1) {
|
||||
PyErr_SetString(st->error, "Not an uncompressed WAV file");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
byteCount = read(streamFileNo, &buffer.uint16, 2);
|
||||
buffer.uint16 = le16toh(buffer.uint16);
|
||||
if (byteCount != 2 || buffer.uint16 != 1) {
|
||||
PyErr_SetString(st->error, "Not a mono WAV file");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
byteCount = read(streamFileNo, &buffer.uint32, 4);
|
||||
sampleRate = le32toh(buffer.uint32);
|
||||
// TODO: Is this required to be 48000?
|
||||
if (byteCount != 4) {
|
||||
PyErr_SetString(st->error, "Not a WAV file");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Skip byte rate
|
||||
byteCount = read(streamFileNo, &buffer.uint32, 4);
|
||||
if (byteCount != 4) {
|
||||
PyErr_SetString(st->error, "Not a WAV file");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Skip block align
|
||||
byteCount = read(streamFileNo, &buffer.uint16, 2);
|
||||
if (byteCount != 2) {
|
||||
PyErr_SetString(st->error, "Not a WAV file");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
byteCount = read(streamFileNo, &buffer.uint16, 2);
|
||||
buffer.uint16 = le16toh(buffer.uint16);
|
||||
if (byteCount != 2 || buffer.uint16 != 16) {
|
||||
PyErr_SetString(st->error, "Not a 16 bit WAV file");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
byteCount = read(streamFileNo, buffer.char4, 4);
|
||||
// Normal WAV files have "data" here, but avcov spits out "LIST"
|
||||
if (
|
||||
byteCount != 4 || (
|
||||
strncmp(buffer.char4, "data", 4) != 0
|
||||
&& strncmp(buffer.char4, "LIST", 4) != 0
|
||||
)
|
||||
) {
|
||||
PyErr_SetString(st->error, "Not a WAV file");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Skip subchunk2 size
|
||||
byteCount = read(streamFileNo, &buffer.uint32, 4);
|
||||
if (byteCount != 4) {
|
||||
PyErr_SetString(st->error, "Not a WAV file");
|
||||
return NULL;
|
||||
}
|
||||
bitRate = sfInfo.samplerate;
|
||||
|
||||
int skipSignals[] = {
|
||||
SIGALRM,
|
||||
|
@ -146,7 +205,7 @@ _rpitx_broadcast_fm(PyObject* self, PyObject* args) {
|
|||
0
|
||||
};
|
||||
|
||||
pitx_run(MODE_RF, bitRate, frequency * 1000.0, 0.0, 0, formatRfWrapper, dummyFunction, skipSignals, 0);
|
||||
pitx_run(MODE_RF, sampleRate, frequency * 1000.0, 0.0, 0, formatRfWrapper, dummyFunction, skipSignals, 0);
|
||||
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
@ -158,9 +217,9 @@ static PyMethodDef _rpitx_methods[] = {
|
|||
_rpitx_broadcast_fm,
|
||||
METH_VARARGS,
|
||||
"Low-level broadcasting.\n\n"
|
||||
"Broadcast a WAV formatted 48KHz file.\n"
|
||||
"Broadcast a WAV formatted 48KHz file from a pipe file descriptor.\n"
|
||||
"Args:\n"
|
||||
" file_name (str): The WAV file to broadcast.\n"
|
||||
" pipe_file_no (int): The fileno of the pipe that the WAV is being written to.\n"
|
||||
" frequency (float): The frequency, in MHz, to broadcast on.\n"
|
||||
},
|
||||
{NULL, NULL, 0, NULL}
|
||||
|
|
|
@ -62,7 +62,7 @@ def broadcast_fm(media_file_name, frequency):
|
|||
for line in lines:
|
||||
logger.debug(line)
|
||||
|
||||
#thread = threading.Thread(target=log_stdout).start()
|
||||
thread = threading.Thread(target=log_stdout).start()
|
||||
|
||||
logger.debug('Calling broadcast_fm')
|
||||
_rpitx.broadcast_fm(stream_process.stdout.fileno(), frequency)
|
||||
|
|
Ładowanie…
Reference in New Issue