【问题标题】:Bug writing audio using custom video writer library使用自定义视频编写器库编写音频的错误
【发布时间】:2018-10-17 03:42:09
【问题描述】:

我正在尝试包装一些方便的 C++ 代码,该代码旨在使用 VFW 在 Windows 上生成视频+音频,C++ 库位于 here 并且描述说:

使用适用于 Windows 的视频(因此它不是便携式的)。如果你想方便 在某处快速录制视频,不想涉水 VfW 文档自己。

我想在 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;
  $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,这对于函数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;

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

2) 我不希望修改 aviwriter.h && aviwriter.cpp,因为这基本上是外部工作代码。

3) 假设我能够包装 WAVEFORMATEX 以便在 Python 上使用它,那么你是如何使用类似于 test.cpp 的 memset 的?即:memset(&wfx,0,sizeof(wfx));

【问题讨论】:

    标签: python c++ windows audio swig


    【解决方案1】:

    两个建议:

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

    • 其次,参见下面的代码修改。通过 SWIG 公开 WAVEFORMATX,通过编辑 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++ 类型 WORDDWORD 仍未声明。包含 SWIG 文件 windows.i 仅创建包装器,例如,允许将 Python 脚本文件中的字符串“WORD”理解为表示内存中的 16 位数据。但这并没有从 C++ 的角度声明 WORD 类型。要解决此问题,请在 aviwriter.i 的此 %inline 语句中添加 WORDDWORD 的 typedef,强制 SWIG 将该代码直接内联复制到包装器 C++ 文件中,从而使声明可用。这也会触发生成 get/set 包装器。或者,如果您愿意编辑,您可以在 aviwriter.h 中包含该内联代码。

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

    【讨论】:

    • 更新了 aviwriter.i 代码,并添加了关于 SWIG 的注释进行解释。通常是的,SWIG 变得相当混乱。希望这会有所帮助。
    • 是的,你是对的,像 windows.h 这样包装头文件听起来像很多额外的问题,这些头文件通常有无数的依赖关系,所以最后我想我会浪费很多时间来设置正确的编译器选项,让我们使用更简单的方法。剩下的就是看看这个包装器如何处理 GPU 生成的内容,因为在 python 端计算视频数据已被证明慢了 10 倍,只需比较 test.cpp 与 test.py 即可了解我的意思。顺便说一句,你知道如何避免视频编码对话框吗?即:mmioFOURCC 使用了哪些字符串?无论如何,tyvm!
    • 我没有详尽的编解码器列表,并假设它取决于安装的内容。我的系统有:'DIB'= [68,73,66,32] 未压缩,'i420'=[105,52,50,48],'iyuv'=[105,121,117,118],'msvc'=[109,115,118,99 ],'mrle'=[109,114,108,101]。此外,对于压缩格式,您需要质量值。我建议在AVISaveOptions() 调用后设置断点并关闭对话框,以获得您想要的默认编解码器和质量。在 test.cpp 中使用 mmioFOURCC 调用 writer.Init() 时传递那些。注意quality 参数是可选的,不在当前代码中传递。
    【解决方案2】:

    根据我在代码中看到的,您没有初始化音频格式。这是通过在第 44 行调用 writer.SetAudioFormat(&wfx); 在原始 test.cpp 代码中完成的,然后将其设置为单声道 44.1 kHz PCM。我相信由于你没有初始化,空白的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(&amp;wfx,0,sizeof(wfx));,这只是旧C将结构中所有变量归零的一种丑陋方式。

    附:正如@MichaelsonB​​ritt 提到的,您的音频数据格式必须与标题中的声明相匹配。但不是转换为 16 位 short,您可以声明 2 个通道,这样您将获得一个通道静音的立体声。

    【讨论】:

      猜你喜欢
      • 2017-12-31
      • 1970-01-01
      • 2020-09-03
      • 2012-04-24
      • 1970-01-01
      • 2017-07-12
      • 1970-01-01
      • 1970-01-01
      • 2013-09-10
      相关资源
      最近更新 更多