-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a simple class to handle G.722 encode / decode task.
- Loading branch information
Showing
4 changed files
with
327 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,242 @@ | ||
#include <stdbool.h> | ||
|
||
#include <Python.h> | ||
|
||
#include "g722_encoder.h" | ||
#include "g722_decoder.h" | ||
|
||
#define MODULE_BASENAME G722 | ||
|
||
#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 | ||
G722_DEC_CTX *g722_dctx; | ||
G722_ENC_CTX *g722_ectx; | ||
int sample_rate; | ||
int bit_rate; | ||
} PyG722; | ||
|
||
static int PyG722_init(PyG722* self, PyObject* args, PyObject* kwds) { | ||
int sample_rate, bit_rate, options; | ||
static char *kwlist[] = {"sample_rate", "bit_rate", NULL}; | ||
|
||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "ii", kwlist, &sample_rate, &bit_rate)) { | ||
return -1; | ||
} | ||
|
||
if (sample_rate != 8000 && sample_rate != 16000) { | ||
PyErr_SetString(PyExc_ValueError, "Sample rate must be 8000 or 16000"); | ||
return -1; | ||
} | ||
|
||
if (bit_rate != 48000 && bit_rate != 56000 && bit_rate != 64000) { | ||
PyErr_SetString(PyExc_ValueError, "Bit rate must be 48000, 56000 or 64000"); | ||
return -1; | ||
} | ||
options = (sample_rate == 8000) ? G722_SAMPLE_RATE_8000 : G722_DEFAULT; | ||
self->g722_ectx = g722_encoder_new(bit_rate, options); | ||
if(self->g722_ectx == NULL) { | ||
PyErr_SetString(PyExc_RuntimeError, "Error initializing G.722 encoder"); | ||
return -1; | ||
} | ||
self->g722_dctx = g722_decoder_new(bit_rate, options); | ||
if(self->g722_dctx == NULL) { | ||
g722_encoder_destroy(self->g722_ectx); | ||
PyErr_SetString(PyExc_RuntimeError, "Error initializing G.722 decoder"); | ||
return -1; | ||
} | ||
self->sample_rate = sample_rate; | ||
self->bit_rate = bit_rate; | ||
|
||
return 0; | ||
} | ||
|
||
// The __del__ method for PyG722 objects | ||
static void PyG722_dealloc(PyG722* self) { | ||
g722_encoder_destroy(self->g722_ectx); | ||
g722_decoder_destroy(self->g722_dctx); | ||
Py_TYPE(self)->tp_free((PyObject*)self); | ||
} | ||
|
||
// The encode method for PyG722 objects | ||
static PyObject * | ||
PyG722_encode(PyG722* self, PyObject* args) { | ||
PyObject* item; | ||
PyObject* seq; | ||
int16_t* array; | ||
Py_ssize_t length, i, olength; | ||
|
||
PyObject *rval = NULL; | ||
if (!PyArg_ParseTuple(args, "O", &item)) { | ||
PyErr_SetString(PyExc_TypeError, "Takes exactly one argument"); | ||
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; | ||
} | ||
|
||
// 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; | ||
} | ||
if (tv < -32768 || tv > 32767) { | ||
PyErr_SetString(PyExc_ValueError, "Value out of range"); | ||
goto e2; | ||
} | ||
array[i] = (int16_t)tv; | ||
} | ||
olength = self->sample_rate == 8000 ? length : length / 2; | ||
PyObject *obuf_obj = PyBytes_FromStringAndSize(NULL, olength); | ||
if (obuf_obj == NULL) { | ||
rval = PyErr_NoMemory(); | ||
goto e2; | ||
} | ||
uint8_t *buffer = (uint8_t *)PyBytes_AsString(obuf_obj); | ||
if (!buffer) { | ||
goto e3; | ||
} | ||
g722_encode(self->g722_ectx, array, length, buffer); | ||
rval = obuf_obj; | ||
goto e2; | ||
e3: | ||
Py_DECREF(obuf_obj); | ||
e2: | ||
free(array); | ||
e1: | ||
Py_DECREF(seq); | ||
e0: | ||
return rval; | ||
} | ||
|
||
// The get method for PyG722 objects | ||
static PyObject * | ||
PyG722_decode(PyG722* self, PyObject* args) { | ||
PyObject* item; | ||
uint8_t* buffer; | ||
int16_t* array; | ||
Py_ssize_t length, olength, i; | ||
|
||
// Parse the input tuple to get a bytes object | ||
if (!PyArg_ParseTuple(args, "O", &item)) { | ||
PyErr_SetString(PyExc_TypeError, "Argument must be a bytes object"); | ||
return NULL; | ||
} | ||
|
||
// Ensure the object is a bytes object | ||
if (!PyBytes_Check(item)) { | ||
PyErr_SetString(PyExc_TypeError, "Argument must be a bytes object"); | ||
return NULL; | ||
} | ||
|
||
// Get the buffer and its length from the bytes object | ||
buffer = (uint8_t *)PyBytes_AsString(item); | ||
if (!buffer) { | ||
return NULL; // PyErr_SetString is called by PyBytes_AsString if something goes wrong | ||
} | ||
length = PyBytes_Size(item); | ||
if (length < 0) { | ||
return NULL; // PyErr_SetString is called by PyBytes_Size if something goes wrong | ||
} | ||
olength = self->sample_rate == 8000 ? length : length * 2; | ||
array = (int16_t*) malloc(olength * sizeof(array[0])); | ||
if (array == NULL) { | ||
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 | ||
} | ||
|
||
// Cleanup and return the list | ||
free(array); | ||
return listObj; | ||
e1: | ||
Py_DECREF(listObj); | ||
e0: | ||
free(array); | ||
return PyErr_NoMemory(); | ||
} | ||
|
||
static PyMethodDef PyG722_methods[] = { | ||
{"encode", (PyCFunction)PyG722_encode, METH_VARARGS, "Encode signed linear PCM samples to G.722 format"}, | ||
{"decode", (PyCFunction)PyG722_decode, METH_VARARGS, "Decode G.722 format to signed linear PCM samples"}, | ||
{NULL} // Sentinel | ||
}; | ||
|
||
static PyTypeObject PyG722Type = { | ||
PyVarObject_HEAD_INIT(NULL, 0) | ||
.tp_name = MODULE_NAME_STR "." MODULE_NAME_STR, | ||
.tp_doc = "Implementation of ITU-T G.722 audio codec in Python using C extension.", | ||
.tp_basicsize = sizeof(PyG722), | ||
.tp_itemsize = 0, | ||
.tp_flags = Py_TPFLAGS_DEFAULT, | ||
.tp_new = PyType_GenericNew, | ||
.tp_init = (initproc)PyG722_init, | ||
.tp_dealloc = (destructor)PyG722_dealloc, | ||
.tp_methods = PyG722_methods, | ||
}; | ||
|
||
static struct PyModuleDef G722_module = { | ||
PyModuleDef_HEAD_INIT, | ||
.m_name = MODULE_NAME_STR, | ||
.m_doc = "Python interface for the ITU-T G.722 audio codec.", | ||
.m_size = -1, | ||
}; | ||
|
||
// Module initialization function | ||
PyMODINIT_FUNC PY_INIT_FUNC(void) { | ||
PyObject* module; | ||
if (PyType_Ready(&PyG722Type) < 0) | ||
return NULL; | ||
|
||
module = PyModule_Create(&G722_module); | ||
if (module == NULL) | ||
return NULL; | ||
|
||
Py_INCREF(&PyG722Type); | ||
PyModule_AddObject(module, MODULE_NAME_STR, (PyObject*)&PyG722Type); | ||
|
||
return module; | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
from sys import exit | ||
from distutils.core import setup, Extension | ||
from setuptools.command.test import test as TestCommand | ||
from os.path import realpath, dirname, join as path_join | ||
from sys import argv as sys_argv | ||
|
||
mod_name = 'G722' | ||
mod_name_dbg = mod_name + '_debug' | ||
|
||
mod_dir = dirname(realpath(sys_argv[0])) | ||
src_dir = '../' | ||
|
||
compile_args = [f'-I{src_dir}', '-flto'] | ||
link_args = ['-flto', '-Wl,--version-script=symbols.map'] | ||
debug_cflags = ['-g3', '-O0', '-DDEBUG_MOD'] | ||
mod_common_args = { | ||
'sources': [mod_name + '_mod.c', 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 | ||
|
||
module1 = Extension(mod_name, **mod_common_args) | ||
module2 = Extension(mod_name_dbg, **mod_debug_args) | ||
|
||
class PyTest(TestCommand): | ||
user_options = [('pytest-args=', 'a', "Arguments to pass to pytest")] | ||
|
||
def initialize_options(self): | ||
TestCommand.initialize_options(self) | ||
self.pytest_args = [] | ||
|
||
def run_tests(self): | ||
import pytest | ||
errno = pytest.main(self.pytest_args) | ||
exit(errno) | ||
|
||
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}, | ||
) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
global: | ||
PyInit_G722*; | ||
local: | ||
*; | ||
}; |