Bug writing audio using custom video writer library
Two suggestions:
First, pack the data as
short
instead ofint
for the audio format, as per the C++ test. Audio data is 16-bit, not 32-bit. Use the 'h' extension for the packing format. For example,struct.pack(f'{len(samples)}h', *samples)
.Second, see code modification below. Expose
WAVEFORMATX
via SWIG, by editingaviwriter.i
. Then callwriter.SetAudioFormat(wfx)
from Python.In my tests, the
memset()
was not necessary. From python you could manually set the fieldcbSize
to zero, that should be enough. The other six fields are mandatory so you'll be setting them anyways. It looks like this struct isn't meant to be revised in the future, because it does not have a struct size field, and also the semantics ofcbSize
(appending arbitrary data to the end of the struct) conflict with an extension anyways.
aviwriter.i:
%inline %{
typedef unsigned short WORD;
typedef unsigned long DWORD;
typedef struct tWAVEFORMATEX
{
WORD wFormatTag; /* format type */
WORD nChannels; /* number of channels (i.e. mono, stereo...) */
DWORD nSamplesPerSec; /* sample rate */
DWORD nAvgBytesPerSec; /* for buffer estimation */
WORD nBlockAlign; /* block size of data */
WORD wBitsPerSample; /* Number of bits per sample of mono data */
WORD cbSize; /* The count in bytes of the size of
extra information (after cbSize) */
} WAVEFORMATEX;
%}
test.py:
from aviwriter import WAVEFORMATEX
later in test.py:
wfx = WAVEFORMATEX()
wfx.wFormatTag = 1 #WAVE_FORMAT_PCM
wfx.nChannels = 1
wfx.nSamplesPerSec = sampleRate
wfx.nAvgBytesPerSec = sampleRate * 2
wfx.nBlockAlign = 2
wfx.wBitsPerSample = 16
writer.SetAudioFormat(wfx)
Notes on SWIG: Since aviwriter.h only provides a forward declaration of tWAVEFORMATEX
, no other information is provided to SWIG, preventing get/set wrappers from being generated. You could ask SWIG to wrap a Windows header declaring the struct ... and open a can of worms because those headers are too large and complex, exposing further problems. Instead, you can individually define WAVEFORMATEX
as done above. The C++ types WORD
and DWORD
still are not declared, though. Including the SWIG file windows.i
only creates wrappers which, for example, allow string "WORD" in a Python script file to be understood as indicating 16-bit data in memory. But that doesn't declare the WORD
type from a C++ perspective. To resolve this, adding typedefs for WORD
and DWORD
in this %inline
statement in aviwriter.i
forces SWIG to copy that code directly inlined into the wrapper C++ file, making the declarations available. This also triggers get/set wrappers to be generated. Alternately, you could include that inlined code inside aviwriter.h if you're willing to edit it.
In short, the idea here is to fully enclose all types into standalone headers or declaration blocks. Remember that .i and .h file have separate functionality (wrappers and data conversion, versus functionality being wrapped). Similarly, notice how aviwriter.h
is included twice in the aviwriter.i
, once to trigger the generation of wrappers needed for Python, and once to declare types in the generated wrapper code needed for C++.