上一篇里的LAME项目已经展示了python如何与C语言交互,但程序仍不够理想,在python这一端仅仅是传递源文件和目标文件的路径,再调用C模块的encode方法来进行编码,但问题在于你无法控制encode函数,比如你想编码的源文件如果不是原始数据,而是wav文件或者其他格式呢?对于这个问题,有两种方法可以选择,一种模仿前面的C模块,在你的Python代码中读取数据,并将数据块逐个传递给encode函数,另一种方法是你传进去一个对象,这个对象带有一个read方法,这样你就可以在C模块里直接调用它的read方法来读取其数据。
听起来好像第二种更加面向对象,但实际上第一种方法反而是更为合适的选择,因为它更为灵活,下面我们就在上一篇的基础上,利用第一种思路对其进行改造。在这种新方法中,我们需要多次调用C模块的函数,类似于将其视为类的方法。可C语言是不支持类的,因此需要将状态信息存储在某个地方。除此以外,我们需要将 “类”暴露给外部的Python程序,使其能创建“类“的实例,并调用它的方法。在“类对象“的内部我们则将其写数据的文件信息储存在”对象“的状态中。听上去就是一种面向对象的方法,不是吗?
首先,遵循"测试先行"的原则,先来看我们改造后的Python这一端,你可以每次读取音频源文件的一个数据块,将其转递给Encoder对象的encode方法,这样无论你的源文件是何种格式,你都可以在Encoder中进行自由的控制,示例代码如下:
  1. import clame  
  2.  
  3. INBUFSIZE = 4096 
  4.  
  5. if __name__ == '__main__':  
  6.     encoder = clame.Encoder('test.mp3')  
  7.     input = file('test.raw', 'rb')  
  8.     data = input.read(INBUFSIZE)  
  9.  
  10.     while data != '':  
  11.         encoder.encode(data)  
  12.         data = input.read(INBUFSIZE)  
  13.     input.close()  
  14.     encoder.close() 

再来看C扩展模块这一端,下面是完整的代码:

  1. #include <Python.h> 
  2. #include <lame.h> 
  3.  
  4. typedef struct {  
  5.     PyObject_HEAD  
  6.     FILE* outfp;  
  7.     lame_global_flags* gfp;  
  8. }clame_EncoderObject;  
  9.  
  10. static PyObject* Encoder_new(PyTypeObject* type, PyObject* args, PyObject* kw) {  
  11.     clame_EncoderObject* self = (clame_EncoderObject* )type->tp_alloc(type, 0);  
  12.     self->outfp = NULL;  
  13.     self->gfp = NULL;  
  14.     return (PyObject*)self;  
  15. }  
  16.  
  17. static void Encoder_dealloc(clame_EncoderObject* self) {  
  18.     if (self->gfp) {  
  19.         lame_close(self->gfp);  
  20.     }  
  21.     if (self->outfp) {  
  22.         fclose(self->outfp);  
  23.     }  
  24.     self->ob_type->tp_free(self);  
  25. }  
  26.  
  27. static int Encoder_init(clame_EncoderObject* self, PyObject* args, PyObject* kw) {  
  28.     char* outPath;  
  29.     if (!PyArg_ParseTuple(args, "s", &outPath)) {  
  30.         return -1;  
  31.     }  
  32.     if (self->outfp || self->gfp) {      
  33.         PyErr_SetString(PyExc_Exception, "__init__ already called");  
  34.         return -1;  
  35.     }  
  36.     self->outfp = fopen(outPath, "wb");  
  37.     self->gfp = lame_init();  
  38.     lame_init_params(self->gfp);  
  39.     return 0;  
  40. }  
  41.  
  42. static PyObject* Encoder_encode(clame_EncoderObject* self, PyObject* args) {  
  43.     char* in_buffer;  
  44.     int in_length;  
  45.     int mp3_length;  
  46.     char* mp3_buffer;  
  47.     int mp3_bytes;  
  48.     if (!(self->outfp || self->gfp)) {  
  49.         PyErr_SetString(PyExc_Exception, "encoder not open");  
  50.         return NULL;  
  51.     }  
  52.     if (!PyArg_ParseTuple(args, "s#", &in_buffer, &in_length)) {  
  53.         return NULL;  
  54.     }  
  55.     in_length /= 2;  
  56.     mp3_length = (int)(1.25 * in_length) + 7200;  
  57.     mp3_buffer = (char*)malloc(mp3_length);  
  58.     if (in_length > 0) {  
  59.         mp3_bytes = lame_encode_buffer_interleaved(self->gfp, (short*)in_buffer, in_length/2, mp3_buffer, mp3_length);  
  60.         if (mp3_bytes > 0) {  
  61.             fwrite(mp3_buffer, 1, mp3_bytes, self->outfp);  
  62.         }  
  63.     }  
  64.     free(mp3_buffer);  
  65.     Py_RETURN_NONE;  
  66. }  
  67.  
  68. static PyObject* Encoder_close(clame_EncoderObject* self) {  
  69.     int mp3_length;  
  70.     char* mp3_buffer;  
  71.     int mp3_bytes;  
  72.     if (!(self->outfp && self->gfp)) {  
  73.         PyErr_SetString(PyExc_Exception, "encoder not open");  
  74.         return NULL;  
  75.     }  
  76.     mp3_length = 7200;  
  77.     mp3_buffer = (char*)malloc(mp3_length);  
  78.     mp3_bytes = lame_encode_flush(self->gfp, mp3_buffer, sizeof(mp3_buffer));  
  79.     if (mp3_bytes > 0) {  
  80.         fwrite(mp3_buffer, 1, mp3_bytes, self->outfp);          
  81.     }  
  82.     free(mp3_buffer);  
  83.     lame_close(self->gfp);  
  84.     self->gfp = NULL;  
  85.     fclose(self->outfp);  
  86.     self->outfp = NULL;  
  87.     Py_RETURN_NONE;  
  88. }  
  89.  
  90. static PyMethodDef Encoder_methods[] = {  
  91.     {"encode", (PyCFunction)Encoder_encode, METH_VARARGS, "encodes and writes data to the output file."},  
  92.     {"close", (PyCFunction)Encoder_close, METH_NOARGS, "close the output file."},  
  93.     {NULL, NULL, 0, NULL}  
  94. };  
  95.  
  96. static PyTypeObject clame_EncoderType = {  
  97.     PyObject_HEAD_INIT(NULL)  
  98.     0,                                    // ob_size  
  99.     "clame.Encoder",                    // tp_name  
  100.     sizeof(clame_EncoderObject),        // tp_basicsize  
  101.     0,                                    // tp_itemsize  
  102.     (destructor)Encoder_dealloc,        // tp_dealloc  
  103.     0,                                    // tp_print  
  104.     0,                                    // tp_getattr  
  105.     0,                                    // tp_setattr  
  106.     0,                                    // tp_compare  
  107.     0,                                    // tp_repr  
  108.     0,                                    // tp_as_number  
  109.     0,                                    // tp_as_sequence  
  110.     0,                                    // tp_as_mapping  
  111.     0,                                    // tp_hash  
  112.     0,                                     // tp_call  
  113.     0,                                    // tp_str  
  114.     0,                                    // tp_getattro  
  115.     0,                                    // tp_setattro  
  116.     0,                                    // tp_as_buffer  
  117.     Py_TPFLAGS_DEFAULT,                    // tp_flags  
  118.     "My first encoder object.",            // tp_doc  
  119.     0,                                    // tp_traverse  
  120.     0,                                    // tp_clear  
  121.     0,                                    // tp_richcompare  
  122.     0,                                    // tp_weaklistoffset  
  123.     0,                                    // tp_iter  
  124.     0,                                    // tp_iternext  
  125.     Encoder_methods,                    // tp_methods  
  126.     0,                                    // tp_members  
  127.     0,                                    // tp_getset  
  128.     0,                                    // tp_base  
  129.     0,                                    // tp_dict  
  130.     0,                                    // tp_descr_get  
  131.     0,                                    // tp_descr_set  
  132.     0,                                    // tp_dictoffset  
  133.     (initproc)Encoder_init,                // tp_init  
  134.     0,                                    // tp_alloc  
  135.     Encoder_new,                        // tp_new  
  136.     0,                                    // tp_free  
  137. };  
  138.  
  139. static PyMethodDef clame_methods[] = {      
  140.     {NULL, NULL, 0, NULL}  
  141. };  
  142.  
  143. PyMODINIT_FUNC initclame() {  
  144.     PyObject* m;  
  145.     if (PyType_Ready(&clame_EncoderType) < 0) {  
  146.         return;  
  147.       
  148.     m = Py_InitModule3("clame", clame_methods, "My second lame module.");  
  149.     Py_INCREF(&clame_EncoderType);  
  150.     PyModule_AddObject(m, "Encoder", (PyObject*) &clame_EncoderType);  

编译过程:

  1. gcc -shared -I /usr/include/python2.6 -I /usr/local/include/lame clame.c -lmp3lame -o clame.so 

首先定义了clame_EncoderObject结构体,这个结构体就是用来存储状态信息的,字段outfp用来存储输出文件,gfp则保存lame的状态,可以用来检查是否已经是重复调用已经调用过的函数了。

为了创建这个结构体的一个新实例,我们需要定义Encoder_new函数,你可以把这个函数视为Python里的__new__方法,当Python解释器需要创建你定义的类型的新实例时就会去调用这个方法。在这个方法里没作什么操作,仅仅是做初始化工作,把outfp和gfp都设置为NULL,此外,与 Encoder_new函数对应,还需要定义Encoder_dealloc方法来对实例进行析构,你可以把这个函数视为Python的__del__方法,clame_EncoderType结构体则是真正定义了我们的Encoder对象,它的各个字段指定了 _new,_close,_encode,_dealloc等方法。在initclame方法中,PyModuleObject则实际指定了在 Python程序中使用的Encoder对象。