[英]Passing audio data from Python to C with ctypes
我有一個 C++ 庫,可以對音頻數據進行分析,還有一個 C API。 其中一個 C API 函數采用const int16_t*
指向數據的指針並返回分析結果。
我正在嘗試為此 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
將接收該地址,但在_native_function
將其用作int*
:
_native_function.argtypes = c_char_p,c_size_t
_native_function(wav_bytes,len(wav_bytes) // 2)
解決“底層緩沖區不可寫”錯誤的另一種方法是利用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 錯誤跟蹤器,看看以前是否出現過這種情況,似乎它是在 2011 年作為問題提出的。 我同意海報上的嚴重錯誤設計,但當時的開發人員似乎沒有。
Eryk Sun 對該線程的評論表明,實際上可以直接使用ctypes.cast
。 這是評論的一部分:
cast
調用ctypes._cast(obj, obj, typ)
。_cast
是一個 ctypes 函數指針,定義如下:_cast = PYFUNCTYPE(py_object, c_void_p, py_object, py_object)(_cast_addr)
由於
cast
進行了將第一個 arg 轉換為c_void_p
的 FFI 調用,因此您可以直接將bytes
轉換為指針類型:>>> 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))
ctypes.cast(obj, type)
此函數類似於 C 中的強制轉換運算符。它返回一個新的 type 實例,該實例指向與 obj 相同的內存塊。 type 必須是指針類型,obj 必須是可以解釋為指針的對象。
因此,CPython 似乎認為bytes
“可以解釋為指針”。 這對我來說似乎很可疑,但是這些現代的指針隱藏語言有一種干擾我的直覺的方式。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.