YOROTSUKI
发布于 2025-05-03 / 72 阅读
0
0

如何批量将Bink video批量转为MP4

我将关于“Bink究竟是什么东西”之类的偏概念类的东西写到了本文的末尾

因为Epic的官方工具Rad GAME Tools并不支持将 .bik文件直接转换成MP4文件,所以我们在转换视频的时候只能先通过官方工具将 .bik 文件转换成 .avi 然后再通过其他诸如格式工厂、PR之类的软件将其转换成mp4。

为什么不直接使用avi文件呢,反正avi文件也是所有视频播放软件都必须适配的文件格式?

因为avi文件的体积太大了,让我们举个例子。 现在我们手里有一个以bink2格式压缩的 .bik文件(文件体积大约为27MB),我们如果将其直接转换成 .avi 文件,其最终的体积将来到恐怖的3.1GB

显而易见

而且官方工具并不支持你使用多线程对文件进行格式转换,这在效率上让人感觉有些尴尬 :(

直接进入代码参考模式

首先可以确定的是,我只在python上做到了这一点(尽管可能也许这套代码在C++或rust上的效率会更高)

首先我们需要为代码引入需要使用的python第三方库。如果你不想看我咬言砸字,可以直接跳到后面看最终的代码

import ctypes
from ctypes import Structure, POINTER, c_int, c_uint, c_ulong, c_char_p, c_void_p, byref
import numpy as np
import av

一般情况下你的python可能会确实numpy 和 av这个库,所以你可以直接使用pip安装这两个库

pip install numpy
pip install av

安装好后我们需要为代码引入dll,这个文件你可以直接在apex的根目录中找到 -> bink2w64.dll, 然后再代码中定义我们需要使用的函数、结构体之类的东西

bink_dll = ctypes.CDLL('./bink2w64.dll')

BINK_SURFACE32RGBA = 6

# Bink结构体
class BINKRECT(Structure):
    _fields_ = [
        ("Left", c_int),
        ("Top", c_int),
        ("Width", c_int),
        ("Height", c_int)
    ]

class BINK(Structure):
    _fields_ = [
        ("Width", c_int),
        ("Height", c_int),
        ("Frames", c_uint),
        ("FrameNum", c_uint),
        ("FrameRate", c_uint),
        ("FrameRateDiv", c_uint),
        ("ReadError", c_uint),
        ("OpenFlags", c_ulong),
        ("FrameRects", BINKRECT),
        ("NumRects", c_uint),
        ("FrameChangePercent", c_uint)
    ]

# Bink的签名
bink_dll.BinkOpen.argtypes = [c_char_p, c_ulong]
bink_dll.BinkOpen.restype = c_void_p

bink_dll.BinkClose.argtypes = [c_void_p]
bink_dll.BinkDoFrame.argtypes = [c_void_p]
bink_dll.BinkNextFrame.argtypes = [c_void_p]
bink_dll.BinkCopyToBuffer.argtypes = [c_void_p, ctypes.POINTER(ctypes.c_ubyte), c_int, c_uint, c_uint, c_uint, c_ulong]
bink_dll.BinkWait.argtypes = [c_void_p]
bink_dll.BinkWait.restype = c_int

至此,你的代码已经初具加载.bik文件的能力了。

以下是关于部分bink函数的解释

BinkOpen -> 打开.bik文件
    参数: 1. bik文件的地址(相对、绝对路径都可以) 2. flag(默认输入0即可)
    返回值: 函数指针
BinkClose -> 关闭bik文件
BinkDoFrame -> 开始处理当前帧的数据(只有图像数据)
BinkNextFrame -> 让指针前进到下一帧,以供↑获取新的数据
BinkCopyToBuffer -> 将帧数据复制到你定义的Buffer里
    参数:  1. bink指针 2. Buffer 3. Width * 4 4. Height 5. 0(起始X轴坐标) 6. 0(起始Y轴坐标) 7. bink常量(apex使用的常量默认为6,即已经定义的变量BINK_SURFACE32RGBA)

再然后就到了处理bink的数据然后写入到mp4文件里了

# 打开 Bink 文件
def open_bink(file_path):
    bink = bink_dll.BinkOpen(file_path.encode('utf-8'), 0)
    if not bink:
        raise RuntimeError("Failed to open Bink file.")
    return bink

# 播放 Bink 视频
def play_bink(file_path):
    """
    :param file_path: E:\\APEX\\media\\frontend.bik
    :return:
    """
    # 创建bik文件指针
    bink = open_bink(file_path)
    # 获取文件.bik文件名并变更为.mp4后缀
    output_file_name = file_path.split('\\')[-1].replace('.bik','.mp4')

    try:
        # 获取视频信息 结构参考class BINK
        bink_info = BINK()

        ctypes.memmove(ctypes.byref(bink_info), bink, ctypes.sizeof(BINK))
        width, height = bink_info.Width, bink_info.Height

        # 创建帧缓冲区
        buffer_size = width * height * 4  # RGBA
        buffer = (ctypes.c_ubyte * buffer_size)()

        # 创建容器
        container = av.open(output_file_name, mode='w')
        # 定义编码格式以及其他的东西, 帧数默认为30帧,几乎不会变
        stream = container.add_stream("h264", rate=30)
        stream.width = width
        stream.height = height
        stream.pix_fmt = "yuv420p"

        # 开始将数据写入容器
        while bink_dll.BinkWait(bink) == 0:
            bink_dll.BinkDoFrame(bink)
            bink_dll.BinkCopyToBuffer(bink, buffer, width * 4, height, 0, 0, BINK_SURFACE32RGBA)

            # 将缓冲区转换为图像
            frame_data = np.frombuffer(buffer, dtype=np.uint8).reshape((height, width, 4))
            rgb_frame = frame_data[:, :, :3]


            frame_av = av.VideoFrame.from_ndarray(rgb_frame, format="rgb24")
            for packet in stream.encode(frame_av):
                container.mux(packet)
            # img = Image.fromarray(frame_data)
            # img.show()

            bink_dll.BinkNextFrame(bink)
        for packet in stream.encode(None):  # 刷新缓冲区
            container.mux(packet)


        container.close()


    finally:
        bink_dll.BinkClose(bink)

然后写完以上代码后你就可以通过调用play_bink来把.bik文件转为mp4文件了

play_bink("E:\\APEX\\media\\frontend.bik")

完整的代码参考

import ctypes
from ctypes import Structure, POINTER, c_int, c_uint, c_ulong, c_char_p, c_void_p, byref
import numpy as np


import av


# 加载 Bink 动态库
bink_dll = ctypes.CDLL('./bink2w64.dll')
#ctypes.LibraryLoader

# 定义 Bink 的常量
BINK_SURFACE32RGBA = 6

# 定义结构体
class BINKRECT(Structure):
    _fields_ = [
        ("Left", c_int),
        ("Top", c_int),
        ("Width", c_int),
        ("Height", c_int)
    ]

class BINK(Structure):
    _fields_ = [
        ("Width", c_int),
        ("Height", c_int),
        ("Frames", c_uint),
        ("FrameNum", c_uint),
        ("FrameRate", c_uint),
        ("FrameRateDiv", c_uint),
        ("ReadError", c_uint),
        ("OpenFlags", c_ulong),
        ("FrameRects", BINKRECT),
        ("NumRects", c_uint),
        ("FrameChangePercent", c_uint)
    ]

# 定义 Bink 函数的签名
bink_dll.BinkOpen.argtypes = [c_char_p, c_ulong]
bink_dll.BinkOpen.restype = c_void_p

bink_dll.BinkClose.argtypes = [c_void_p]
bink_dll.BinkDoFrame.argtypes = [c_void_p]
bink_dll.BinkNextFrame.argtypes = [c_void_p]
bink_dll.BinkCopyToBuffer.argtypes = [c_void_p, ctypes.POINTER(ctypes.c_ubyte), c_int, c_uint, c_uint, c_uint, c_ulong]
bink_dll.BinkWait.argtypes = [c_void_p]
bink_dll.BinkWait.restype = c_int

# 打开 Bink 文件
def open_bink(file_path):
    bink = bink_dll.BinkOpen(file_path.encode('utf-8'), 0)
    if not bink:
        raise RuntimeError("Failed to open Bink file.")
    return bink

# 播放 Bink 视频
def play_bink(file_path):
    """
    :param file_path: E:\\APEX\\media\\frontend.bik
    :return:
    """
    # 创建bik文件指针
    bink = open_bink(file_path)
    # 获取文件.bik文件名并变更为.mp4后缀
    output_file_name = file_path.split('\\')[-1].replace('.bik','.mp4')

    try:
        # 获取视频信息 结构参考class BINK
        bink_info = BINK()

        ctypes.memmove(ctypes.byref(bink_info), bink, ctypes.sizeof(BINK))
        width, height = bink_info.Width, bink_info.Height

        # 创建帧缓冲区
        buffer_size = width * height * 4  # RGBA
        buffer = (ctypes.c_ubyte * buffer_size)()

        # 创建容器
        container = av.open(output_file_name, mode='w')
        # 定义编码格式以及其他的东西, 帧数默认为30帧,几乎不会变
        stream = container.add_stream("h264", rate=30)
        stream.width = width
        stream.height = height
        stream.pix_fmt = "yuv420p"

        # 开始将数据写入容器
        while bink_dll.BinkWait(bink) == 0:
            bink_dll.BinkDoFrame(bink)
            bink_dll.BinkCopyToBuffer(bink, buffer, width * 4, height, 0, 0, BINK_SURFACE32RGBA)

            # 将缓冲区转换为图像
            frame_data = np.frombuffer(buffer, dtype=np.uint8).reshape((height, width, 4))
            rgb_frame = frame_data[:, :, :3]


            frame_av = av.VideoFrame.from_ndarray(rgb_frame, format="rgb24")
            for packet in stream.encode(frame_av):
                container.mux(packet)
            # img = Image.fromarray(frame_data)
            # img.show()

            bink_dll.BinkNextFrame(bink)
        for packet in stream.encode(None):  # 刷新缓冲区
            container.mux(packet)


        container.close()


    finally:
        bink_dll.BinkClose(bink)

# 示例:播放 Bink 视频
play_bink(r"E:\\APEX\\media\\frontend.bik")

你可以使用threadingsqllite等库让代码自动判断那些bik文件是新的,然后批量转为mp4

比如:

from pathlib import Path
import threading

# 此处省略12390127349128行上文代码

def get_all_file_paths(directory):
    path = Path(directory)
    return [str(file) for file in path.rglob("*") if file.is_file() and str(file).endswith("bik")]

paths = get_all_file_paths(r"E:\\Apex\\media")
threads = []
for bink_path in paths:
    t = threading.Thread(
        target=play_bink,
        args=(bink_path,)
    )
    t.start()
    threads.append(t)

for t in threads:
    t.join()

概念类:什么是bink?

Bink 是 RAD Game Tools 提供的一套高性能音视频编解码解决方案。它被广泛应用于游戏领域(2020 年已有约 500 款游戏采用了 Bink 视频编解码器),支持多种平台。Bink 技术具有高压缩比低解码开销的特点:例如 Bink 音频编解码器可实现约 10:1 的感知无损压缩,其解码速度接近 ADPCM,同时借助 GPU 加速时,Bink 视频解码 4K 帧只需不到 1 毫秒,内存占用也非常低。

Apex Legends 第 22 赛季的音频编解码升级

在 Apex Legends 第 22 赛季中,开发团队对音频系统进行了重大升级。此前游戏使用的是 Bink Audio 2 编解码器,但社区中存在脚步声不清晰、音频偶尔缺失等问题。为此,重生工作室在这一版本中将旧版编解码器替换为 RAD 最新的 BinkAudioCodec(该技术根据虚幻引擎路线图在 UE5.5 预览版中首次引入),以解决持续存在的音频稳定性和性能问题。

  • 迁移动机:玩家长期反映竞争环境中音频指示(如脚步声)不够可靠,影响游戏体验。采用新版编解码器有望带来更好的音频质量和一致性。

  • 过程与来源:BinkAudioCodec 是 RAD Game Tools 最新的音频编解码技术,在 UE5.5中作为内置功能首次面世。Apex开发团队根据这一技术,构建了自定义分支,将其集成到自家的弗兰肯斯坦引擎中。

  • 定制优化:为了充分发挥新编解码器的优势,重生工作室还基于BinkAudioCodec创建了自己的分支,针对 Apex 的使用场景(例如海量并发音源)进行专项优化,使其性能与稳定性更符合游戏需求。


评论