Skip to content

Commit

Permalink
Return decoded array of int16_t's as numpy.array, not as list
Browse files Browse the repository at this point in the history
of integers and conversely, accept numpy.array as an input for the
encode.
  • Loading branch information
sobomax committed Apr 22, 2024
1 parent 38a1273 commit 166aff7
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 45 deletions.
1 change: 1 addition & 0 deletions .github/workflows/build_and_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ jobs:
run: |
python -m pip install --upgrade pip
pip install setuptools wheel
pip install -r python/requirements.txt
- name: build
run: CC=${COMPILER} ${PYTHON_CMD} python/setup.py build
Expand Down
122 changes: 78 additions & 44 deletions python/G722_mod.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#include <assert.h>
#include <stdbool.h>

#include <Python.h>
#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
#include <numpy/arrayobject.h>

#include "g722_encoder.h"
#include "g722_decoder.h"
Expand Down Expand Up @@ -86,37 +89,42 @@ PyG722_encode(PyG722* self, PyObject* args) {
goto e0;
}

// Convert PyObject to a sequence if possible
seq = PySequence_Fast(item, "Expected a sequence");
if (seq == NULL) {
PyErr_SetString(PyExc_TypeError, "Expected a sequence");
goto e0;
}
if (PyArray_Check(item) && PyArray_TYPE((PyArrayObject*)item) == NPY_INT16) {
array = (int16_t *)PyArray_DATA((PyArrayObject*)item);
length = PyArray_SIZE((PyArrayObject*)item);
} else {
// Convert PyObject to a sequence if possible
seq = PySequence_Fast(item, "Expected a sequence");
if (seq == NULL) {
PyErr_SetString(PyExc_TypeError, "Expected a sequence");
goto e0;
}

// Get the length of the sequence
length = PySequence_Size(seq);
if (length == -1) {
PyErr_SetString(PyExc_TypeError, "Error getting sequence length");
goto e1;
}
// Get the length of the sequence
length = PySequence_Size(seq);
if (length == -1) {
PyErr_SetString(PyExc_TypeError, "Error getting sequence length");
goto e1;
}

// Allocate memory for the int array
array = (int16_t*) malloc(length * sizeof(array[0]));
if (!array) {
rval = PyErr_NoMemory();
goto e1;
}
for (i = 0; i < length; i++) {
PyObject* temp_item = PySequence_Fast_GET_ITEM(seq, i); // Borrowed reference, no need to Py_DECREF
long tv = PyLong_AsLong(temp_item);
if (PyErr_Occurred()) {
goto e2;
// Allocate memory for the int array
array = (int16_t*) malloc(length * sizeof(array[0]));
if (!array) {
rval = PyErr_NoMemory();
goto e1;
}
if (tv < -32768 || tv > 32767) {
PyErr_SetString(PyExc_ValueError, "Value out of range");
goto e2;
for (i = 0; i < length; i++) {
PyObject* temp_item = PySequence_Fast_GET_ITEM(seq, i); // Borrowed reference, no need to Py_DECREF
long tv = PyLong_AsLong(temp_item);
if (PyErr_Occurred()) {
goto e2;
}
if (tv < -32768 || tv > 32767) {
PyErr_SetString(PyExc_ValueError, "Value out of range");
goto e2;
}
array[i] = (int16_t)tv;
}
array[i] = (int16_t)tv;
}
olength = self->sample_rate == 8000 ? length : length / 2;
PyObject *obuf_obj = PyBytes_FromStringAndSize(NULL, olength);
Expand All @@ -128,19 +136,41 @@ PyG722_encode(PyG722* self, PyObject* args) {
if (!buffer) {
goto e3;
}
g722_encode(self->g722_ectx, array, length, buffer);
int obytes = g722_encode(self->g722_ectx, array, length, buffer);
assert(obytes == olength);
rval = obuf_obj;
goto e2;
e3:
Py_DECREF(obuf_obj);
e2:
free(array);
if (!PyArray_Check(item)) {
free(array);
}
e1:
Py_DECREF(seq);
e0:
return rval;
}

typedef struct {
PyObject_HEAD
void *data; // Pointer to the data buffer
} PyDataOwner;

static void
DataOwner_dealloc(PyDataOwner* self) {
free(self->data); // Free the memory when the object is deallocated
Py_TYPE(self)->tp_free((PyObject*)self);
}

static PyTypeObject PyDataOwnerType = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "DataOwner",
.tp_basicsize = sizeof(PyDataOwner),
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_dealloc = (destructor)DataOwner_dealloc,
};

// The get method for PyG722 objects
static PyObject *
PyG722_decode(PyG722* self, PyObject* args) {
Expand Down Expand Up @@ -176,26 +206,25 @@ PyG722_decode(PyG722* self, PyObject* args) {
return PyErr_NoMemory();
}
g722_decode(self->g722_dctx, buffer, length, array);
// Create a new list to hold the integers
PyObject *listObj = PyList_New(0);
if (listObj == NULL) goto e0;

// Convert each int16_t in array to a Python integer and append to list
for (i = 0; i < olength; i++) {
PyObject* intObj = PyLong_FromLong(array[i]);
if (intObj == NULL) goto e1;
PyList_Append(listObj, intObj);
Py_DECREF(intObj); // PyList_Append increments the ref count
PyDataOwner* owner = PyObject_New(PyDataOwner, &PyDataOwnerType);
if (!owner) {
free(array);
return PyErr_NoMemory();
}

// Cleanup and return the list
free(array);
return listObj;
owner->data = array;

// Create a new numpy array to hold the integers
long dims[1] = {olength};
PyObject* numpy_array = PyArray_SimpleNewFromData(1, dims, NPY_INT16, (void *)array);
if (numpy_array == NULL) goto e1;
PyArray_SetBaseObject((PyArrayObject*)numpy_array, (PyObject*)owner);
return numpy_array;
e1:
Py_DECREF(listObj);
Py_DECREF(owner);
e0:
free(array);
return PyErr_NoMemory();
return NULL;
}

static PyMethodDef PyG722_methods[] = {
Expand Down Expand Up @@ -237,6 +266,11 @@ PyMODINIT_FUNC PY_INIT_FUNC(void) {
Py_INCREF(&PyG722Type);
PyModule_AddObject(module, MODULE_NAME_STR, (PyObject*)&PyG722Type);

import_array();

if (PyType_Ready(&PyDataOwnerType) < 0)
return NULL;

return module;
}

1 change: 1 addition & 0 deletions python/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
numpy
8 changes: 7 additions & 1 deletion python/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from setuptools.command.test import test as TestCommand
from os.path import exists, realpath, dirname, join as path_join
from sys import argv as sys_argv
import numpy as np

mod_name = 'G722'
mod_name_dbg = mod_name + '_debug'
Expand All @@ -12,17 +13,19 @@
mod_fname = mod_name + '_mod.c'
mod_dir = '' if exists(mod_fname) else 'python/'

compile_args = [f'-I{src_dir}', '-flto']
compile_args = [f'-I{src_dir}', '-flto', f'-I{np.get_include()}']
smap_fname = f'{mod_dir}symbols.map'
link_args = ['-flto', f'-Wl,--version-script={smap_fname}']
debug_cflags = ['-g3', '-O0', '-DDEBUG_MOD']
debug_link_args = ['-g3', '-O0']
mod_common_args = {
'sources': [mod_dir + mod_fname, src_dir + 'g722_decode.c', src_dir + 'g722_encode.c'],
'extra_compile_args': compile_args,
'extra_link_args': link_args
}
mod_debug_args = mod_common_args.copy()
mod_debug_args['extra_compile_args'] = mod_debug_args['extra_compile_args'] + debug_cflags
mod_debug_args['extra_link_args'] = mod_debug_args['extra_link_args'] + debug_link_args

module1 = Extension(mod_name, **mod_common_args)
module2 = Extension(mod_name_dbg, **mod_debug_args)
Expand All @@ -39,11 +42,14 @@ def run_tests(self):
errno = pytest.main(self.pytest_args)
exit(errno)

requirements = [x.strip() for x in open(mod_dir + "requirements.txt", "r").readlines()]

setup (name = mod_name,
version = '1.0',
description = 'This is a package for G.722 module',
ext_modules = [module1, module2],
tests_require=['pytest'],
cmdclass={'test': PyTest},
install_requires = requirements,
)

0 comments on commit 166aff7

Please sign in to comment.