使用 ctypes 将音频数据从 Python 传递到 C
Passing audio data from Python to C with ctypes
我有一个对音频数据执行分析的 C++ 库,以及一个 C API 库。 C API 函数之一采用 const int16_t*
数据指针和 returns 分析结果。
我正在尝试为这个 API 构建一个 Python 接口,大部分都在工作,但是我在获取 ctypes 指针用作此函数的参数时遇到了问题。由于 C 端的指针指向 const
,我觉得应该可以使任何连续数据正常工作。但是,以下内容不起作用:
import ctypes
import wave
_native_lib = ctypes.cdll.LoadLibrary('libsound.so')
_native_function = _native_lib.process_sound_data
_native_function.argtypes = [ctypes.POINTER(ctypes.c_int16),
ctypes.c_size_t]
_native_function.restype = ctypes.c_int
wav_path = 'hello.wav'
with wave.open(wav_path, mode='rb') as wav_file:
wav_bytes = wav_file.readframes(wav_file.getnframes())
data_start = ctypes.POINTER(ctypes.c_int16).from_buffer(wav_bytes) # ERROR: data is immutable
_native_function(data_start, len(wav_bytes)//2)
手动复制wav_bytes
到bytearray
允许构造指针但导致本机代码段错误,表明它接收到的地址是错误的(它通过了读入数据的单元测试来自 C++)。通过获取正确的地址来解决这个问题在技术上可以解决问题,但我觉得有更好的方法。
当然可以只获取一些数据的地址并保证它是正确的格式并且不会被更改?我不想将我所有 Python 存储的音频数据深度复制到 ctypes 格式,因为如果我能得到指向它们的指针,大概字节就在那里!
理想情况下,我希望能够做这样的事情
data_start = cast_to(address_of(data[0]), c_int16_pointer)
_native_function(data_start, len(data))
这将适用于任何具有 [0]
和 len
的东西。有没有办法在 ctypes 中做这样的事情?如果不是,是否有技术原因导致它不可能,还有其他我应该使用的东西吗?
这应该适合你。使用 array
作为可写缓冲区并创建引用缓冲区的 ctypes 数组。
data = array.array('h',wav_bytes)
addr,size = data.buffer_info()
arr = (c_short * size).from_address(addr)
_native_function(arr,size)
或者,要跳过 wav_bytes
的副本到 data
数组,您可以在 argtypes 中谎报指针类型。 ctypes
知道如何将字节字符串转换为 c_char_p
。指针只是一个地址,因此 _native_function
将接收地址但在内部将其用作 int*
:
_native_function.argtypes = c_char_p,c_size_t
_native_function(wav_bytes,len(wav_bytes) // 2)
另一种解决 "underlying buffer is not writable" 错误的方法是利用 c_char_p
,它允许使用不可变的字节字符串,然后将其显式转换为您想要的指针类型:
_native_function.argtypes = POINTER(c_short),c_size_t
p = cast(c_char_p(wav_bytes),POINTER(c_short))
_native_function(p,len(wav_bytes) // 2)
在后一种情况下,您必须确保您实际上没有写入缓冲区,因为它会破坏保存数据的不可变 Python 对象。
我查看了 CPython 错误跟踪器,看看以前是否出现过这个问题,似乎是 raised as an issue in 2011。我同意发帖者的说法是严重的mis-design,但当时的开发者似乎没有。
Eryk Sun 对该线程的评论表明实际上可以直接使用 ctypes.cast
。以下是部分评论:
cast
calls ctypes._cast(obj, obj, typ)
. _cast
is a ctypes function pointer defined as follows:
_cast = PYFUNCTYPE(py_object,
c_void_p, py_object, py_object)(_cast_addr)
Since cast
makes an FFI call that converts the first arg to c_void_p
, you can directly cast bytes
to a pointer type:
>>> from ctypes import *
>>> data = b'123\x00abc'
>>> ptr = cast(data, c_void_p)
我有点不清楚这是否真的是标准所要求的,或者它是否只是一个 CPython 实现细节,但以下在 CPython 中对我有用:
import ctypes
data = b'imagine this string is 16-bit sound data'
data_ptr = ctypes.cast(data, ctypes.POINTER(ctypes.c_int16))
documentation on cast
表示如下:
ctypes.cast(obj, type)
This function is similar to the cast operator in C. It returns a new instance of type which points to the same memory block as obj. type must be a pointer type, and obj must be an object that can be interpreted as a pointer.
所以 CPython 似乎认为 bytes
'can be interpreted as a pointer'。这对我来说似乎很可疑,但这些现代 pointer-hiding 语言有一种方法会扰乱我的直觉。
我有一个对音频数据执行分析的 C++ 库,以及一个 C API 库。 C API 函数之一采用 const int16_t*
数据指针和 returns 分析结果。
我正在尝试为这个 API 构建一个 Python 接口,大部分都在工作,但是我在获取 ctypes 指针用作此函数的参数时遇到了问题。由于 C 端的指针指向 const
,我觉得应该可以使任何连续数据正常工作。但是,以下内容不起作用:
import ctypes
import wave
_native_lib = ctypes.cdll.LoadLibrary('libsound.so')
_native_function = _native_lib.process_sound_data
_native_function.argtypes = [ctypes.POINTER(ctypes.c_int16),
ctypes.c_size_t]
_native_function.restype = ctypes.c_int
wav_path = 'hello.wav'
with wave.open(wav_path, mode='rb') as wav_file:
wav_bytes = wav_file.readframes(wav_file.getnframes())
data_start = ctypes.POINTER(ctypes.c_int16).from_buffer(wav_bytes) # ERROR: data is immutable
_native_function(data_start, len(wav_bytes)//2)
手动复制wav_bytes
到bytearray
允许构造指针但导致本机代码段错误,表明它接收到的地址是错误的(它通过了读入数据的单元测试来自 C++)。通过获取正确的地址来解决这个问题在技术上可以解决问题,但我觉得有更好的方法。
当然可以只获取一些数据的地址并保证它是正确的格式并且不会被更改?我不想将我所有 Python 存储的音频数据深度复制到 ctypes 格式,因为如果我能得到指向它们的指针,大概字节就在那里!
理想情况下,我希望能够做这样的事情
data_start = cast_to(address_of(data[0]), c_int16_pointer)
_native_function(data_start, len(data))
这将适用于任何具有 [0]
和 len
的东西。有没有办法在 ctypes 中做这样的事情?如果不是,是否有技术原因导致它不可能,还有其他我应该使用的东西吗?
这应该适合你。使用 array
作为可写缓冲区并创建引用缓冲区的 ctypes 数组。
data = array.array('h',wav_bytes)
addr,size = data.buffer_info()
arr = (c_short * size).from_address(addr)
_native_function(arr,size)
或者,要跳过 wav_bytes
的副本到 data
数组,您可以在 argtypes 中谎报指针类型。 ctypes
知道如何将字节字符串转换为 c_char_p
。指针只是一个地址,因此 _native_function
将接收地址但在内部将其用作 int*
:
_native_function.argtypes = c_char_p,c_size_t
_native_function(wav_bytes,len(wav_bytes) // 2)
另一种解决 "underlying buffer is not writable" 错误的方法是利用 c_char_p
,它允许使用不可变的字节字符串,然后将其显式转换为您想要的指针类型:
_native_function.argtypes = POINTER(c_short),c_size_t
p = cast(c_char_p(wav_bytes),POINTER(c_short))
_native_function(p,len(wav_bytes) // 2)
在后一种情况下,您必须确保您实际上没有写入缓冲区,因为它会破坏保存数据的不可变 Python 对象。
我查看了 CPython 错误跟踪器,看看以前是否出现过这个问题,似乎是 raised as an issue in 2011。我同意发帖者的说法是严重的mis-design,但当时的开发者似乎没有。
Eryk Sun 对该线程的评论表明实际上可以直接使用 ctypes.cast
。以下是部分评论:
cast
callsctypes._cast(obj, obj, typ)
._cast
is a ctypes function pointer defined as follows:_cast = PYFUNCTYPE(py_object, c_void_p, py_object, py_object)(_cast_addr)
Since
cast
makes an FFI call that converts the first arg toc_void_p
, you can directly castbytes
to a pointer type:>>> from ctypes import * >>> data = b'123\x00abc' >>> ptr = cast(data, c_void_p)
我有点不清楚这是否真的是标准所要求的,或者它是否只是一个 CPython 实现细节,但以下在 CPython 中对我有用:
import ctypes
data = b'imagine this string is 16-bit sound data'
data_ptr = ctypes.cast(data, ctypes.POINTER(ctypes.c_int16))
documentation on cast
表示如下:
ctypes.cast(obj, type)
This function is similar to the cast operator in C. It returns a new instance of type which points to the same memory block as obj. type must be a pointer type, and obj must be an object that can be interpreted as a pointer.
所以 CPython 似乎认为 bytes
'can be interpreted as a pointer'。这对我来说似乎很可疑,但这些现代 pointer-hiding 语言有一种方法会扰乱我的直觉。