我正在尝试打包一个方便的C ++代码,用于使用VFW在Windows上生成视频+音频,C ++库存在here,描述说:
使用适用于Windows的视频(因此它不可移植)。如果你愿意,很方便 快速录制某个地方的视频,并不想趟过 VfW自己做文档。
我想在Python上使用那个C ++库,所以我决定用swig把它包起来。
事情是,我在编码音频方面遇到了一些问题,出于某种原因,我试图理解为什么生成的视频被破坏,似乎音频在视频文件中没有正确写入。这意味着,如果我尝试用VLC或任何类似的视频播放器打开视频,我会收到一条消息,说视频播放器无法识别音频或视频编解码器。视频图像很好,所以我将音频写入文件的方式肯定是个问题。
我正在附加swig接口和一个试图成为原始c++ test的端口的Python测试。
aviwriter.i
%module aviwriter
%{
#include "aviwriter.h"
%}
%typemap(in) (const unsigned char* buffer) (char* buffer, Py_ssize_t length) %{
if(PyBytes_AsStringAndSize($input,&buffer,&length) == -1)
SWIG_fail;
$1 = (unsigned char*)buffer;
%}
%typemap(in) (const void* buffer) (char* buffer, Py_ssize_t length) %{
if(PyBytes_AsStringAndSize($input,&buffer,&length) == -1)
SWIG_fail;
$1 = (void*)buffer;
%}
%include "aviwriter.h"
test.py
import argparse
import sys
import struct
from distutils.util import strtobool
from aviwriter import AVIWriter
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("-audio", action="store", default="1")
parser.add_argument('-width', action="store",
dest="width", type=int, default=400)
parser.add_argument('-height', action="store",
dest="height", type=int, default=300)
parser.add_argument('-numframes', action="store",
dest="numframes", type=int, default=256)
parser.add_argument('-framerate', action="store",
dest="framerate", type=int, default=60)
parser.add_argument('-output', action="store",
dest="output", type=str, default="checker.avi")
args = parser.parse_args()
audio = strtobool(args.audio)
framerate = args.framerate
num_frames = args.numframes
width = args.width
height = args.height
output = args.output
writer = AVIWriter()
if not writer.Init(output, framerate):
print("Couldn't open video file!")
sys.exit(1)
writer.SetSize(width, height)
data = [0]*width*height
sampleRate = 44100
samples_per_frame = 44100 / framerate
samples = [0]*int(samples_per_frame)
c1, s1, f1 = 24000.0, 0.0, 0.03
c2, s2, f2 = 1.0, 0.0, 0.0013
for frame in range(num_frames):
print(f"frame {frame}")
i = 0
for y in range(height):
for x in range(width):
on = ((x + frame) & 32) ^ ((y+frame) & 32)
data[i] = 0xffffffff if on else 0xff000000
i += 1
writer.WriteFrame(
struct.pack(f'{len(data)}L', *data),
width*4
)
if audio:
for i in range(int(samples_per_frame)):
c1 -= f1*s1
s1 += f1*c1
c2 += f2*s2
s2 -= f2*c2
val = s1 * (0.75 + 0.25 * c2)
if(frame == num_frames - 1):
val *= 1.0 * (samples_per_frame - 1 - i) / \
samples_per_frame
samples[i] = int(val)
if frame==0:
print(f"i={i} val={int(val)}")
writer.WriteAudioFrame(
struct.pack(f'{len(samples)}i', *samples),
int(samples_per_frame)
)
writer.Exit()
我不认为samples
生成错误,因为我已经将python端生成的值与c ++端生成的值进行了比较,只是为第0帧编写的数据包。
我对错误的一些怀疑是我在swig上创建类型图的方式,也许这不好......或者问题可能存在于行writer.WriteAudioFrame(struct.pack(f'{len(samples)}i', *samples), int(samples_per_frame))
中,我不知道会发生什么当然,我将音频缓冲区从Python发送到C ++包装器的方式并不好。
那么,您是否知道如何修复附加代码,以便test.py能够生成具有与c ++测试类似的正确音频的视频?
当生成好后,视频将显示一个魔术滚动棋盘,催眠正弦作为音频背景:D
附加说明:
1)上面的代码似乎没有使用函数writer.SetAudioFormat
和AVIFileCreateStreamA
所需的AVIStreamSetFormat
。问题是我不知道如何在swig上导出这个结构,这样我就可以在Python上以与test.cpp
相同的方式使用它,来自Mmreg.h我看到结构看起来像这样:
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;
不幸的是我不知道如何在aviwriter.i上包装那些东西?我已经尝试过使用%include windows.i并将这些内容直接包含在块%{
... %}
中,但我所得到的只是一堆错误:/
2)我不想修改aviwriter.h&& aviwriter.cpp,因为它基本上是外部工作代码。
3)假设我能够包装WAVEFORMATEX
所以我可以在Python上使用它,你如何使用memset与test.cpp
类似?即:memset(&wfx,0,sizeof(wfx));
答案 0 :(得分:5)
两个建议:
首先,根据C ++测试,将数据打包为short
而不是int
作为音频格式。音频数据是16位,而不是32位。使用' h'包装格式的延伸。例如,struct.pack(f'{len(samples)}h', *samples)
。
其次,请参阅下面的代码修改。通过编辑WAVEFORMATX
,通过SWIG公开aviwriter.i
。然后从Python调用writer.SetAudioFormat(wfx)
。
在我的测试中,memset()
没有必要。从python你可以手动将字段cbSize
设置为零,这应该足够了。其他六个字段是强制性的,因此您无论如何都要设置它们。看起来这个结构在未来不打算进行修改,因为它没有结构大小字段,还有cbSize
的语义(将任意数据附加到结构的末尾)冲突无论如何都有延伸。
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
稍后在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)
关于SWIG的注释:由于aviwriter.h仅提供tWAVEFORMATEX
的前向声明,因此不向SWIG提供其他信息,从而阻止生成get / set包装器。您可以要求SWIG包装声明结构的Windows标头...并打开一堆蠕虫,因为这些标头太大而且复杂,暴露了更多问题。相反,您可以单独定义WAVEFORMATEX
,如上所述。但是,C ++类型WORD
和DWORD
仍未声明。包括SWIG文件windows.i
只会创建包装器,例如,允许字符串" WORD"在Python脚本文件中,可以理解为在内存中指示16位数据。但是,从C ++的角度来看,这并没有声明WORD
类型。要解决此问题,请在WORD
中的DWORD
语句中为%inline
和aviwriter.i
添加typedef,强制SWIG将该代码直接复制到包装器C ++文件中,使声明可用。这也会触发生成get / set包装器。或者,如果您愿意编辑它,可以在aviwriter.h中包含内联代码。
简而言之,这里的想法是将所有类型完全包含在独立标题或声明块中。请记住.i和.h文件具有单独的功能(包装和数据转换,而不是包装的功能)。同样,请注意aviwriter.h
中aviwriter.i
如何包含两次,一次触发Python生成包装器的生成,一次在C ++生成的包装器代码中声明类型。