Ошибка записи аудио с использованием пользовательской библиотеки Video Writer - PullRequest
0 голосов
/ 07 мая 2018

Я пытаюсь обернуть небольшой удобный фрагмент кода C ++, который предназначен для генерации видео + аудио в окнах с использованием VFW, библиотека C ++ живет здесь , а в описании написано:

Использует видео для Windows (поэтому оно не переносимо). Удобно, если вы хотите быстро записать видео где-нибудь и не хочется пробираться VfW самостоятельно.

Я бы хотел использовать эту библиотеку C ++ на Python, поэтому я решил обернуть ее с помощью 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"

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));

Ответы [ 2 ]

0 голосов
/ 14 мая 2018

Два предложения:

  • Сначала упакуйте данные как short вместо int для аудиоформата в соответствии с тестом C ++. Аудиоданные 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;
%}

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-битные данные в памяти. Но это не объявляет тип WORD с точки зрения C ++. Чтобы решить эту проблему, добавление typedefs для WORD и DWORD в этом операторе %inline в aviwriter.i заставляет SWIG копировать этот код, непосредственно встроенный в файл C ++ оболочки, делая объявления доступными. Это также вызывает генерацию оболочек get / set. Кроме того, вы можете включить этот встроенный код в aviwriter.h, если хотите его редактировать.

Короче говоря, идея в том, чтобы полностью заключить все типы в отдельные заголовки или блоки объявлений. Помните, что файлы .i и .h имеют отдельные функции (обертки и преобразование данных, в отличие от обертываемых функций). Аналогично, обратите внимание, как aviwriter.h включается дважды в aviwriter.i, один раз для запуска генерации оболочек, необходимых для Python, и один раз для объявления типов в сгенерированном коде оболочки, необходимом для C ++.

0 голосов
/ 13 мая 2018

Из того, что я видел в коде, вы не инициализируете аудиоформат. Это делается в исходном коде test.cpp путем вызова writer.SetAudioFormat(&wfx); в строке 44, затем он устанавливается для моно 44,1 кГц PCM. Я считаю, что, поскольку вы не инициализируете, пустой заголовок записывается, и видеоплеер не может открыть неизвестный формат.

Обновление

Поскольку вам нужно только передать структуру двоичного заголовка, и вам не нужно использовать структуру и объявить ее в 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, ваш формат аудиоданных должен соответствовать объявлению в заголовке. Но вместо преобразования в 16-битный short вы можете объявить 2 канала, так что вы получите стереозвук с одним беззвучным каналом.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...