使用自定义视频编写器库编写音频的Bug

BPL*_*BPL 6 c++ python windows audio swig

我正在尝试包装一些方便的c ++代码,用于在Windows上使用VFW生成视频+音频,c ++库存在于此处,描述如下:

使用Video for Windows(因此它不可移植).如果您想在某个地方快速录制视频并且不想亲自浏览VfW文档,那就太方便了.

我想在python上使用那个c ++库,所以我决定用swig将它包装起来.

事实上,我在编码音频方面遇到了一些问题,出于某种原因,我试图理解为什么生成的视频被破坏,似乎音频还没有在视频文件中正确写入.这意味着,如果我尝试用VLC或任何类似的视频播放器打开视频,我会收到一条消息,说视频播放器无法识别音频或视频编解码器.视频图像很好,所以我将音频写入文件的方式肯定是个问题.

我正在附加swig接口和一个小python测试,它试图成为原始c ++测试的一个端口.

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"
Run Code Online (Sandbox Code Playgroud)

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()
Run Code Online (Sandbox Code Playgroud)

我不认为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函数AVIFileCreateStreamAAVIStreamSetFormat.问题是我不知道如何在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;
Run Code Online (Sandbox Code Playgroud)

不幸的是我不知道如何在aviwriter.i上包装那些东西?我已经尝试过使用%include windows.i并将这些内容直接包含在一个块中%{... %}但我所得到的只是一堆错误:/

2)我不想完全修改aviwriter.h && aviwriter.cpp,因为它基本上是外部工作代码.

3)假设我能够包装,WAVEFORMATEX所以我可以在python上使用它,你是如何使用memset的test.cpp?即:memset(&wfx,0,sizeof(wfx));

Mic*_*itt 5

两个建议:

  • 首先,根据C++测试,将数据打包short而不是int音频格式.音频数据是16位,而不是32位.使用'h'扩展名作为打包格式.例如,struct.pack(f'{len(samples)}h', *samples).

  • 其次,请参阅下面的代码修改.WAVEFORMATX通过SWIG通过编辑公开aviwriter.i.然后writer.SetAudioFormat(wfx)从Python 调用.

  • 在我的测试中,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;
%}
Run Code Online (Sandbox Code Playgroud)

test.py:

from aviwriter import WAVEFORMATEX
Run Code Online (Sandbox Code Playgroud)

稍后在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)
Run Code Online (Sandbox Code Playgroud)

关于SWIG的注释: 由于aviwriter.h仅提供前向声明tWAVEFORMATEX,因此不向SWIG提供其他信息,从而阻止生成get/set包装器.您可以要求SWIG包装声明结构的Windows标头...并打开一堆蠕虫,因为这些标头太大而且复杂,暴露了更多问题.相反,您可以单独定义WAVEFORMATEX如上所述.C++的类型WORDDWORD仍然没有宣布,但.包括SWIG文件windows.i只会创建包装器,例如,允许Python脚本文件中的字符串"WORD"被理解为指示内存中的16位数据.但是这并没有WORD从C++的角度声明类型.为了解决这个问题,添加类型定义为WORDDWORD在这个%inline声明中aviwriter.i力痛饮复制代码直接联到包装C++文件,发表声明可用.这也会触发生成get/set包装器.或者,如果您愿意编辑它,可以在aviwriter.h中包含内联代码.

简而言之,这里的想法是将所有类型完全包含在独立标题或声明块中.请记住.i和.h文件具有单独的功能(包装和数据转换,而不是包装的功能).同样,请注意如何aviwriter.h包含两次aviwriter.i,一次触发Python所需的包装器的生成,一次在C++生成的包装器代码中声明类型.