使用自定义视频编写器库编写音频的错误
Bug writing audio using custom video writer library
我正在尝试包装一小段方便的 C++ 代码,旨在使用 VFW 在 windows 上生成视频+音频,C++ 库存在于 here 并且描述说:
Uses Video for Windows (so it's not portable). Handy if you want to
quickly record a video somewhere and don't feel like wading through
the VfW docs yourself.
我想在 Python 上使用那个 C++ 库,所以我决定使用 swig 将它包装起来。
事实是,我在编码音频时遇到了一些问题,出于某种原因,我试图理解为什么生成的视频被破坏,似乎音频没有正确写入视频文件。这意味着,如果我尝试使用 VLC 或任何类似的视频播放器打开视频,我将收到一条消息,指出视频播放器无法识别音频或视频编解码器。视频图像很好,所以这肯定是我将音频写入文件的方式有问题。
我正在附加 swig 接口和一个小 Python 测试,它试图成为原始 c++ test.
的端口
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;
= (unsigned char*)buffer;
%}
%typemap(in) (const void* buffer) (char* buffer, Py_ssize_t length) %{
if(PyBytes_AsStringAndSize($input,&buffer,&length) == -1)
SWIG_fail;
= (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) 上面的代码似乎没有使用函数 AVIFileCreateStreamA
和 AVIStreamSetFormat
所需的 writer.SetAudioFormat
。问题是我不知道如何在 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 上使用它,您如何使用类似于 test.cpp
的 memset?即:memset(&wfx,0,sizeof(wfx));
从我在代码中看到的情况来看,您没有初始化音频格式。这是通过在第 44 行调用 writer.SetAudioFormat(&wfx);
在原始 test.cpp
代码中完成的,然后将其设置为单声道 44.1 kHz PCM。我相信是因为你没有初始化,所以写的是空白header,视频播放器是打不开未知格式的
更新
因为你只需要传递二进制header结构,而不需要使用结构并在aviwriter.i
中声明它。您可以直接从 Python 使用以下代码:
import struct
from collection import namedtuple
WAVEFORMATEX = namedtuple('WAVEFORMATEX', 'wFormatTag nChannels nSamplesPerSec nAvgBytesPerSec nBlockAlign wBitsPerSample cbSize ')
wfx = WAVEFORMATEX(
wFormatTag = 1,
nChannels = 1,
nSamplesPerSec = sampleRate,
nAvgBytesPerSec = sampleRate * 2,
nBlockAlign = 2,
wBitsPerSample = 16,
cbSize = 0)
audio_format_obj = struct.pack('<HHIIHHH', *list(wfx))
writer.SetAudioFormat(audio_format_obj)
这将自动解决您的第二个和第三个问题。
至于memset(&wfx,0,sizeof(wfx));
这只是旧 C 将结构中的所有变量归零的一种丑陋方式。
P.S。正如@MichaelsonBritt 提到的,您的音频数据格式必须与 header 中的声明相匹配。但不是转换为 16 位 short
,您可以声明 2 个声道,这样您将获得立体声,其中一个声道静音。
两条建议:
首先,根据 C++ 测试,将音频格式的数据打包为 short
而不是 int
。音频数据是 16 位,而不是 32 位。对打包格式使用 'h' 扩展名。例如,struct.pack(f'{len(samples)}h', *samples)
.
其次,看下面的代码修改。通过编辑 aviwriter.i
,通过 SWIG 公开 WAVEFORMATX
。然后从 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 header 来声明结构 ... 并打开一罐蠕虫,因为那些 header 太大太复杂,暴露了更多问题。相反,您可以像上面那样单独定义 WAVEFORMATEX
。不过,C++ 类型 WORD
和 DWORD
仍未声明。包含 SWIG 文件 windows.i
只会创建包装器,例如,允许将 Python 脚本文件中的字符串 "WORD" 理解为指示内存中的 16 位数据。但这并没有从 C++ 的角度声明 WORD
类型。要解决此问题,请在 aviwriter.i
中的此 %inline
语句中为 WORD
和 DWORD
添加 typedef 强制 SWIG 将该代码直接内联复制到包装器 C++ 文件中,使声明可用.这也会触发生成 get/set 包装器。或者,如果您愿意编辑它,您可以将内联代码包含在 aviwriter.h 中。
简而言之,这里的想法是将所有类型完全封装到独立的 header 或声明块中。请记住,.i 和 .h 文件具有不同的功能(包装器和数据转换,而不是被包装的功能)。同样,请注意 aviwriter.h
如何在 aviwriter.i
中包含两次,一次是触发生成 Python 所需的包装器,一次是在生成的 C++ 所需的包装器代码中声明类型。
我正在尝试包装一小段方便的 C++ 代码,旨在使用 VFW 在 windows 上生成视频+音频,C++ 库存在于 here 并且描述说:
Uses Video for Windows (so it's not portable). Handy if you want to quickly record a video somewhere and don't feel like wading through the VfW docs yourself.
我想在 Python 上使用那个 C++ 库,所以我决定使用 swig 将它包装起来。
事实是,我在编码音频时遇到了一些问题,出于某种原因,我试图理解为什么生成的视频被破坏,似乎音频没有正确写入视频文件。这意味着,如果我尝试使用 VLC 或任何类似的视频播放器打开视频,我将收到一条消息,指出视频播放器无法识别音频或视频编解码器。视频图像很好,所以这肯定是我将音频写入文件的方式有问题。
我正在附加 swig 接口和一个小 Python 测试,它试图成为原始 c++ test.
的端口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;
= (unsigned char*)buffer;
%}
%typemap(in) (const void* buffer) (char* buffer, Py_ssize_t length) %{
if(PyBytes_AsStringAndSize($input,&buffer,&length) == -1)
SWIG_fail;
= (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) 上面的代码似乎没有使用函数 AVIFileCreateStreamA
和 AVIStreamSetFormat
所需的 writer.SetAudioFormat
。问题是我不知道如何在 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 上使用它,您如何使用类似于 test.cpp
的 memset?即:memset(&wfx,0,sizeof(wfx));
从我在代码中看到的情况来看,您没有初始化音频格式。这是通过在第 44 行调用 writer.SetAudioFormat(&wfx);
在原始 test.cpp
代码中完成的,然后将其设置为单声道 44.1 kHz PCM。我相信是因为你没有初始化,所以写的是空白header,视频播放器是打不开未知格式的
更新
因为你只需要传递二进制header结构,而不需要使用结构并在aviwriter.i
中声明它。您可以直接从 Python 使用以下代码:
import struct
from collection import namedtuple
WAVEFORMATEX = namedtuple('WAVEFORMATEX', 'wFormatTag nChannels nSamplesPerSec nAvgBytesPerSec nBlockAlign wBitsPerSample cbSize ')
wfx = WAVEFORMATEX(
wFormatTag = 1,
nChannels = 1,
nSamplesPerSec = sampleRate,
nAvgBytesPerSec = sampleRate * 2,
nBlockAlign = 2,
wBitsPerSample = 16,
cbSize = 0)
audio_format_obj = struct.pack('<HHIIHHH', *list(wfx))
writer.SetAudioFormat(audio_format_obj)
这将自动解决您的第二个和第三个问题。
至于memset(&wfx,0,sizeof(wfx));
这只是旧 C 将结构中的所有变量归零的一种丑陋方式。
P.S。正如@MichaelsonBritt 提到的,您的音频数据格式必须与 header 中的声明相匹配。但不是转换为 16 位 short
,您可以声明 2 个声道,这样您将获得立体声,其中一个声道静音。
两条建议:
首先,根据 C++ 测试,将音频格式的数据打包为
short
而不是int
。音频数据是 16 位,而不是 32 位。对打包格式使用 'h' 扩展名。例如,struct.pack(f'{len(samples)}h', *samples)
.其次,看下面的代码修改。通过编辑
aviwriter.i
,通过 SWIG 公开WAVEFORMATX
。然后从 Python. 调用 在我的测试中,
memset()
不是必需的。从 python 您可以手动将字段cbSize
设置为零,这应该足够了。其他六个字段是强制性的,因此您无论如何都要设置它们。看起来这个结构将来不会被修改,因为它没有结构大小字段,而且cbSize
的语义(将任意数据附加到结构的末尾)与无论如何扩展。
writer.SetAudioFormat(wfx)
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 header 来声明结构 ... 并打开一罐蠕虫,因为那些 header 太大太复杂,暴露了更多问题。相反,您可以像上面那样单独定义 WAVEFORMATEX
。不过,C++ 类型 WORD
和 DWORD
仍未声明。包含 SWIG 文件 windows.i
只会创建包装器,例如,允许将 Python 脚本文件中的字符串 "WORD" 理解为指示内存中的 16 位数据。但这并没有从 C++ 的角度声明 WORD
类型。要解决此问题,请在 aviwriter.i
中的此 %inline
语句中为 WORD
和 DWORD
添加 typedef 强制 SWIG 将该代码直接内联复制到包装器 C++ 文件中,使声明可用.这也会触发生成 get/set 包装器。或者,如果您愿意编辑它,您可以将内联代码包含在 aviwriter.h 中。
简而言之,这里的想法是将所有类型完全封装到独立的 header 或声明块中。请记住,.i 和 .h 文件具有不同的功能(包装器和数据转换,而不是被包装的功能)。同样,请注意 aviwriter.h
如何在 aviwriter.i
中包含两次,一次是触发生成 Python 所需的包装器,一次是在生成的 C++ 所需的包装器代码中声明类型。