diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 94f9324..92d531d 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -29,11 +29,14 @@ jobs: python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] compiler: ['gcc', 'clang'] os: [macos, ubuntu] + include: + - python-version: '3.10' + compiler: microsoft + os: windows runs-on: ${{ matrix.os }}-latest env: COMPILER: ${{ matrix.compiler }} - PYTHON_CMD: "python${{ matrix.python-version }}" # Steps represent a sequence of tasks that will be executed as part of the job steps: @@ -49,17 +52,21 @@ jobs: run: | python -m pip install --upgrade pip pip install setuptools wheel + shell: bash - name: build - run: CC=${COMPILER} LDSHARED="${COMPILER} -shared" ${PYTHON_CMD} setup.py build sdist + run: CC=${COMPILER} LDSHARED="${COMPILER} -shared" python setup.py build sdist + shell: bash - name: install run: pip install dist/[Rr]tp*.gz + shell: bash - name: test run: | - CC=${COMPILER} ${PYTHON_CMD} setup.py runctest - ${PYTHON_CMD} tests/test_jbuf.py + CC=${COMPILER} python setup.py runctest + python tests/test_jbuf.py + shell: bash publish_wheels: needs: build_and_test_python diff --git a/MANIFEST.in b/MANIFEST.in index 124f19b..defa846 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,4 @@ include setup/__init__.py setup/RunCTest.py include src/rsth_timeops.h src/rtp.h src/rtp_info.h src/rtpjbuf.h src/rtpsynth.h src/Symbol.map +include src/winnet.h python/RtpSynth_mod.c include README.md diff --git a/python/RtpSynth.py b/python/RtpSynth.py index ddaa57f..6d5e34a 100644 --- a/python/RtpSynth.py +++ b/python/RtpSynth.py @@ -23,11 +23,12 @@ from ctypes import cdll, c_void_p, c_int, create_string_buffer, c_uint from math import modf -import sys, os, site, sysconfig +import sys, os, site +from sysconfig import get_config_var, get_platform from .env import RSTH_MOD_NAME -_esuf = sysconfig.get_config_var('EXT_SUFFIX') +_esuf = get_config_var('EXT_SUFFIX') if not _esuf: _esuf = '.so' try: @@ -48,6 +49,10 @@ else: _rsth = cdll.LoadLibrary('librtpsynth.so') +if get_platform().startswith('win'): + for a in 'rsynth_ctor', 'rsynth_dtor', 'rsynth_next_pkt', 'rsynth_set_mbt', + 'rsynth_resync', 'rsynth_skip', 'rsynth_pkt_free': + setattr(_rsth, a, getattr(_rsth, f'_{a}')) _rsth.rsynth_ctor.argtypes = [c_int, c_int] _rsth.rsynth_ctor.restype = c_void_p _rsth.rsynth_dtor.argtypes = [c_void_p,] diff --git a/python/RtpSynth_mod.c b/python/RtpSynth_mod.c new file mode 100644 index 0000000..58cd5e1 --- /dev/null +++ b/python/RtpSynth_mod.c @@ -0,0 +1,75 @@ +#include +#include + +#include + +#define MODULE_BASENAME _rtpsynth + +#define CONCATENATE_DETAIL(x, y) x##y +#define CONCATENATE(x, y) CONCATENATE_DETAIL(x, y) + +#if !defined(DEBUG_MOD) +#define MODULE_NAME MODULE_BASENAME +#else +#define MODULE_NAME CONCATENATE(MODULE_BASENAME, _debug) +#endif + +#define STRINGIFY(x) #x +#define TOSTRING(x) STRINGIFY(x) + +#define MODULE_NAME_STR TOSTRING(MODULE_NAME) +#define PY_INIT_FUNC CONCATENATE(PyInit_, MODULE_NAME) + +typedef struct { + PyObject_HEAD +} PyRtpSynth; + +static int PyRtpSynth_init(PyRtpSynth* self, PyObject* args, PyObject* kwds) { + + return 0; +} + +// The __del__ method for PyRtpSynth objects +static void PyRtpSynth_dealloc(PyRtpSynth* self) { + Py_TYPE(self)->tp_free((PyObject*)self); +} + +static PyMethodDef PyRtpSynth_methods[] = { + {NULL} // Sentinel +}; + +static PyTypeObject PyRtpSynthType = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = MODULE_NAME_STR "." MODULE_NAME_STR, + .tp_doc = "[TODO]", + .tp_basicsize = sizeof(PyRtpSynth), + .tp_itemsize = 0, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_new = PyType_GenericNew, + .tp_init = (initproc)PyRtpSynth_init, + .tp_dealloc = (destructor)PyRtpSynth_dealloc, + .tp_methods = PyRtpSynth_methods, +}; + +static struct PyModuleDef RtpSynth_module = { + PyModuleDef_HEAD_INIT, + .m_name = MODULE_NAME_STR, + .m_doc = "Python interface XXX.", + .m_size = -1, +}; + +// Module initialization function +PyMODINIT_FUNC PY_INIT_FUNC(void) { + PyObject* module; + if (PyType_Ready(&PyRtpSynthType) < 0) + return NULL; + + module = PyModule_Create(&RtpSynth_module); + if (module == NULL) + return NULL; + + Py_INCREF(&PyRtpSynthType); + PyModule_AddObject(module, MODULE_NAME_STR, (PyObject*)&PyRtpSynthType); + + return module; +} diff --git a/setup.py b/setup.py index 4fe82fc..4dda223 100644 --- a/setup.py +++ b/setup.py @@ -9,15 +9,25 @@ from python.env import RSTH_MOD_NAME from setup.RunCTest import RunCTest +is_win = get_platform().startswith('win') +is_mac = get_platform().startswith('macosx-') + rs_srcs = ['src/rtpsynth.c', 'src/rtp.c', 'src/rtpjbuf.c'] -extra_compile_args = ['--std=c11', '-Wno-zero-length-array', '-Wall', '-pedantic', '-flto'] -extra_link_args = ['-flto'] +if not is_mac: + rs_srcs.append('python/RtpSynth_mod.c') + +extra_compile_args = ['-Wall'] +if not is_win: + extra_compile_args += ['--std=c11', '-Wno-zero-length-array', + '-flto', '-pedantic'] +extra_link_args = ['-flto'] if not is_win else [] debug_opts = (('-g3', '-O0')) -if not get_platform().startswith('macosx-'): + +if not is_mac and not is_win: nodebug_opts = (('-march=native', '-O3')) else: - nodebug_opts = (('-O3',)) + nodebug_opts = (('-O3',)) if not is_win else () if False: extra_compile_args.extend(debug_opts) extra_link_args.extend(debug_opts) @@ -32,7 +42,7 @@ RunCTest.extra_link_args = extra_link_args.copy() RunCTest.extra_compile_args = extra_compile_args -if not get_platform().startswith('macosx-'): +if not is_mac and not is_win: extra_link_args.append('-Wl,--version-script=src/Symbol.map') def get_ex_mod(): diff --git a/src/rtp.c b/src/rtp.c index 1d8db08..dcebae4 100644 --- a/src/rtp.c +++ b/src/rtp.c @@ -28,9 +28,14 @@ #include #include #include +#if !defined(_WIN32) && !defined(_WIN64) #include +#else +#include "winnet.h" +#endif #include #include +#include #include "rtp.h" #include "rtp_info.h" diff --git a/src/rtp.h b/src/rtp.h index cf03dc5..4918423 100644 --- a/src/rtp.h +++ b/src/rtp.h @@ -78,10 +78,17 @@ typedef enum rtp_type rtp_type_t; # endif #endif +#ifdef _MSC_VER +# define PACKED_STRUCT(name) \ + __pragma(pack(push, 1)) struct name __pragma(pack(pop)) +#elif defined(__GNUC__) +# define PACKED_STRUCT(name) struct __attribute__((packed)) name +#endif + /* * RTP data header */ -struct rtp_hdr { +PACKED_STRUCT(rtp_hdr) { #if BYTE_ORDER == BIG_ENDIAN unsigned int version:2; /* protocol version */ unsigned int p:1; /* padding flag */ @@ -101,13 +108,13 @@ struct rtp_hdr { uint32_t ts; /* timestamp */ uint32_t ssrc; /* synchronization source */ uint32_t csrc[0]; /* optional CSRC list */ -} __attribute__((__packed__)); +}; -struct rtp_hdr_ext { +PACKED_STRUCT(rtp_hdr_ext) { uint16_t profile; /* defined by profile */ uint16_t length; /* length of the following array in 32-byte words */ uint32_t extension[0]; /* actual extension data */ -} __attribute__((__packed__)); +}; #if !defined(rtp_hdr_t_DEFINED) typedef struct rtp_hdr rtp_hdr_t; diff --git a/src/rtpjbuf.c b/src/rtpjbuf.c index 692c767..12d7207 100644 --- a/src/rtpjbuf.c +++ b/src/rtpjbuf.c @@ -1,4 +1,8 @@ +#if !defined(_WIN32) && !defined(_WIN64) #include +#else +#include "winnet.h" +#endif #include #include @@ -12,7 +16,11 @@ #define LMS_DEFAULT LRS_DEFAULT #define BOOLVAL(x) (x) +#if !defined(_WIN32) && !defined(_WIN64) #define x_unlikely(x) (__builtin_expect((x), 0), (x)) +#else +#define x_unlikely(x) (x) +#endif struct jitter_buffer { struct rtp_frame *head; diff --git a/src/rtpsynth.c b/src/rtpsynth.c index 9ac897e..264d3f2 100644 --- a/src/rtpsynth.c +++ b/src/rtpsynth.c @@ -23,9 +23,17 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#if defined(_WIN32) || defined(_WIN64) +#define _CRT_RAND_S +#endif + #define _DEFAULT_SOURCE +#if !defined(_WIN32) && !defined(_WIN64) #include +#else +#include "winnet.h" +#endif #include #include @@ -33,6 +41,17 @@ #include #include +#if defined(_WIN32) || defined(_WIN64) +static long +random(void) +{ + unsigned int r; + + rand_s(&r); + return r; +} +#endif + #include "rtp.h" #include "rtpsynth.h" #include "rsth_timeops.h" @@ -42,10 +61,36 @@ struct rsynth_inst { int ptime; struct rsynth_seq l; int ts_inc; - struct rtp_hdr model; struct timespec last_ts; + struct rtp_hdr model; }; +#if defined(_WIN32) || defined(_WIN64) +#include + +int clock_gettime_monotonic(struct timespec *tv) +{ + static LARGE_INTEGER ticksPerSec; + LARGE_INTEGER ticks; + + if (!ticksPerSec.QuadPart) { + QueryPerformanceFrequency(&ticksPerSec); + if (!ticksPerSec.QuadPart) { + errno = ENOTSUP; + return -1; + } + } + + QueryPerformanceCounter(&ticks); + + tv->tv_sec = (long)(ticks.QuadPart / ticksPerSec.QuadPart); + tv->tv_nsec = (long)(((ticks.QuadPart % ticksPerSec.QuadPart) * NSEC_IN_SEC) / ticksPerSec.QuadPart); + + return 0; +} +#define clock_gettime(_, x) clock_gettime_monotonic(x) +#endif + void * rsynth_ctor(int srate, int ptime) { diff --git a/src/winnet.h b/src/winnet.h new file mode 100644 index 0000000..eeb5e35 --- /dev/null +++ b/src/winnet.h @@ -0,0 +1,8 @@ +#pragma once + +#include +#define LITTLE_ENDIAN 1234 +#define BIG_ENDIAN 4321 +#define BYTE_ORDER LITTLE_ENDIAN + +#pragma comment(lib, "Ws2_32.lib") diff --git a/tests/test_synth.c b/tests/test_synth.c index 70f5de1..a114be6 100644 --- a/tests/test_synth.c +++ b/tests/test_synth.c @@ -5,11 +5,22 @@ #include "rtpsynth.h" +#if !defined(_WIN32) && !defined(_WIN64) static void inline taint(char *p) { __asm__ volatile ("" : : "m" (*p)); } +#else +#include + +static inline void taint(char *p) { + // Use a dummy intrinsic to reference the pointer + _ReadWriteBarrier(); + *((volatile char*)p) = *((volatile char*)p); + _ReadWriteBarrier(); +} +#endif int main(void) { double tdur = 1.0;