Sony DCR-TRV40E 摄像机
MiniDV 磁带 10 盒
ThinkPad T410
1394 4-pin to 4-pin 线
Sony MiniDV 新磁带 2 盒(2DVM60R3,非必须)
Sony MiniDV 干式清洁带(DVM4CLD2,非必须)
T410 安装操作系统:Ubuntu 24.04 LTS
dvgrab:直接从 apt 安装,源代码在 https://github.com/ddennedy/dvgrab
2022 年 1 月 29 日,我在家里“重新发现”了一台旧的索尼摄像机(DCR-TRV40E)和一台照相机(DSC-W1)。另有 10 盒 MiniDV 磁带和 3 条 Memory Stick 保存在一个红色的布盒子里,Memory Stick 的容量分别是 8 MB、128 MB 和 1 GB。我在索尼的产品支持页面找到了 DCR-TRV40E 摄像机的使用说明书。
第二天,我(好像是用读卡器,记不清了)把 Memory Stick 中的全部文件复制到电脑上,除了一些系统文件外都是照片和视频。至于 MiniDV 磁带,虽然可以在摄像机的屏幕上观看,但我不知道如何将摄像机连接到电脑,也就无法将视频导入电脑中。好消息是,MiniDV 磁带上的数据已经是数字的了,所以不需要像 VHS 等更老的磁带格式一样进行数字化转换,只需要考虑怎么将它无损传输到电脑。
索尼在 DCR-TRV40E 的支持页面列出了这个软件,支持 Windows 和 Mac。我在安装之后发现没有什么作用。它里面提供了一些图片、视频管理和剪辑的功能,但在底层仍然依赖操作系统识别摄像机硬件。本来识别不到的硬件并不会因为安装了它就能识别出来了。

DCR-TRV40E 摄像机上有一个 USB Mini B 接口,可以通过一根 Mini B 到 Type A 的线连接到电脑上。但是,用这种方法连接之前必须先安装驱动程序。

说明书第 14 页提到了这张 CD-ROM(SPVD-008 USB 驱动程序),但我在摄像机附带的一堆东西里没有找到,可能是存放时放到别的地方或者扔了。我在 Google 上搜索,找到几个驱动下载站(solodrivers[dot]com, driverfix[dot]com,现在看都是广告引流网站),下载了几个 .exe 格式的驱动安装程序。说明书提到驱动支持的操作系统最高到 Windows XP,所以我在 VMWare 里开了一个虚拟机,逐个尝试安装之后连接摄像机。结果自然是失败了,识别不到设备。
2023 年 2 月 19 日,我尝试在 archive.org 搜索,找到并下载了 CD-ROM 的镜像文件和它的说明书,但是可能是后面忘记了,好像没有重新安装并测试连接。
2026 年 2 月 4 日写这篇文章时我又试了一下,在 Windows XP 虚拟机里加载光盘镜像,可以正常安装驱动和 PIXELA Image Mixer 软件。考虑到虚拟机的宿主系统没有这个驱动、可能识别不到,直接用 Windows XP 物理机连接摄像机,成功的概率应该更大。另外,我在其中一盒磁带里也看到当时刚买摄像机、测试连接电脑时录制的视频,视频中电脑屏幕上确实是 Image Mixer 这个软件界面,说明当时用 CD-ROM 是可以正常安装驱动、连接电脑的。
不过,这个软件的发布时间有点太久远了,开发公司早已经不知所终。我在说明书中看到它的设置支持使用 AVI 格式存储视频,但是看不到源代码,不知道这个 AVI 选项保存的是原始的 DV 数据,还是做了什么转换。综合来看,这恐怕不是最好的采集方法。

看上去这样连接是最方便的,但这两个接口无法转换,原因是协议完全不同:1394 接口传输 DV 数据流这种操作 USB 标准根本就不支持。所有声称能转换的线或者转接头都是假的。
(为什么上文中摄像机又可以通过 USB-USB 线连接电脑?我推测可能是摄像机内部先有一个数据转换,把 DV 数据转换成能在 USB 协议上传输的格式,再从 USB 传到电脑上。)
输出是模拟的,摄像机会先做一遍数模转换,采集时又要模数转换,画质损失较大。
输出图像的质量比 A/V 接口稍高,但传输的仍然是模拟信号,画质有损失。
经过详细调查,我确认在摄像机上的这么多接口中,只有 1394 接口支持 MiniDV 磁带数据的无损传输。

(i.LINK 是 Sony 对 1394 接口的别称,苹果称它为 FireWire。)

摄像机上有一个 1394 4-pin 的接口,因此需要连接一台有相同接口的电脑。

一般有这样几种连接方案:
由于我没有台式机,而方案 2 的钱又足够买到一台有 1394 接口的笔记本,所以我选择了方案 3。在闲鱼上蹲了几周之后,我在 2024 年 2 月 28 日买了一台 ThinkPad T410(¥180)。这台笔记本于 2010 年生产,配有 i5-540M CPU、4 GB DDR3 内存、NVIDIA NVS 3100M 显卡和 320GB 磁盘,最重要的是在右侧靠近屏幕转轴的位置有一个 IEEE 1394 接口。

2024 年 3 月 14 日,我又买了一根 1394 4-pin to 4-pin 的线(¥40)。第二天我在日本亚马逊上买了两盒同款的新磁带(874 日元)用于测试、一盒干式清洁带(584 日元)用于在采集的视频质量较差时清洗摄像机的磁头。(我买 1394 线时专门在闲鱼上挑了同城的买家,要求发顺丰同城,说明当时很急,但为什么收到后没有立即采集而要买磁带?是否当时就采集了,但画面质量有问题?我现在也记不清楚了)这样,采集所需的硬件就准备齐全了。
由于买的磁带需要蹭我在任你购上买日本音乐 CD 大概每 3 个月发回国一次的包裹,直到 2024 年 5 月 8 日我才收到。由于各种记不清了的原因,这件事又搁置下去了,正式开始采集 MiniDV 磁带要等到 2025 年 1 月 19 日。即使之前已经准备了大量设备和工具,也做了些前期调研 [5][6],采集时仍然遇到了诸多问题。
第一个问题就是选择哪个软件采集。之前在 Hacker News [1] 和 r/DataHoarder [2][3][4] 上搜索一番后,我发现了这几个选项:dvgrab、WinDV、ScenalyzerLive、DVRescue(及同一组织开发的DV Analyzer)。我给 T410 安装了 Windows 7 和 Ubuntu 16.04 的双系统,逐个测试。
Windows 上的两个软件——WinDV 和 Scenalyzer ,前者的界面太简陋,后者的界面太古老,而且最后发布的时间分别是 2003 年和 2006 年,之后就没有再维护了,我不敢信任它们俩。
Hacker News 讨论里还有人推荐 DVRescue 和 DV Analyzer。但是我在 Windows 7 上用 DVRescue GUI 连接摄像机后识别不到设备。当时没读明白文档,也可能是受到 vrecord 的影响,误以为 DVRescue 的采集功能必须要连接它支持的 deck,看了一下巨贵。(后面在 Ubuntu 24 上使用 DVRescue 的 CLI 版本就连接成功了,发现文档也新加了一段,注明 Windows/Linux 的 GUI 版本暂不支持采集。)这两个软件功能很多,看得人眼花缭乱;文档内容又比较繁杂,读着费劲,不好上手。
最后还是从第一性原理分析。我现在手头上有的采集设备只有这台 DV 摄像机,而且这些磁带就是用它录的,读取效果肯定比其它 DV 设备好;买一个 deck 又太贵了。所以最终选择用 dvgrab 采集。这个软件包目前还在维护,而且 Ubuntu/Debian 上可以直接用 apt 安装:
sudo apt install dvgrab
dvgrab --help
至于 DVRescue 和 DV Analyzer 的一堆分析功能,它们只是分析视频哪里有错误,而没法修复错误——唯有 DVRescue 的 merge 功能除外。
我先将所有 10 卷磁带快退回起点,并贴上便签,标注编号(tape01 到 tape10)和开头部分画面的内容。测试播放时画面条纹(术语叫 dropout)很多,用清洁带播放了 10 秒后似乎好一些了。
将摄像机用 1394 线连接到电脑上,然后运行 dvgrab,试着采集一盒 MiniDV 磁带:
dvgrab -autosplit -rewind -showstatus -size 0 -srt -timecode -timestamp tape01-pass1-
这些选项的含义分别是:
-autosplit:当检测到一段新录像时,创建新文件-rewind:在采集前将磁带完全倒回开头-showstatus:在采集时显示状态-size:最大文件大小,0表示无限制-srt:将录像日期写入 SRT 文件-timecode:在文件名中记录第一帧的时间码(Timecode)-timestamp:在文件名中记录录像日期和时间tape01-pass1-:文件名的前缀最终生成的文件名类似tape01-pass1-2005.07.23_16-45-22--00:12:25:22.dv。-format选项使用默认的raw就很好,不需要专门指明。
摄像机播放磁带的同时,终端上显示的文件大小、帧数也滚动增加。大约 1 小时后,采集顺利完成。录像日期的 SRT 文件没什么用,我就删掉了。文件名中时间码的格式是hh:mm:ss:frame,而 Windows 系统不支持包含冒号:的文件名,所以最好不用这个选项。
观看采集下来的视频时,我发现总是出现周期性的 dropout。播放磁带时把摄像机的外放声音调小,机器内部也有“哒、哒、哒”的响声,并且每次发出“哒”声时(摄像机屏幕上的)画面就会或多或少地 dropout。又采集了三卷磁带,问题相同。我开始怀疑是摄像机的问题。
我尝试用新磁带录一段播放看看,排除旧磁带老化的问题,但是发现录完播放出的画面是全黑,估计摄像机的镜头部分已经坏了。
在闲鱼上搜索同型号的 DV 摄像机:便宜的三四百,贵的七八百。于是我转而在小红书和闲鱼上找 DV 维修的师傅,找到一位评价很好的。1 月 19 日晚上询问了一下,尝试了把磁带快进快退,没有用。师傅判断是带仓故障,因为播放清洁带的时候也有响声。
将摄像机寄给师傅,师傅发现是机芯问题:主导轮的皮带有一处“毛”了,每转一圈转到这里就会卡一下。由于现在已经没法单独买到皮带,所以师傅把机芯整个换掉了。


维修费用是¥320,我在 2025 年 2 月 26 日收到师傅寄回来的摄像机。
再次将摄像机连接到电脑,运行 dvgrab:
dvgrab -autosplit -rewind -showstatus -size 0 -timestamp tape01-pass1-
检查这一次采集的视频文件,播放过程中(除了在两端视频切换处)画面上没有条纹,声音也正常。于是我继续采集其它的磁带。tape02 到 tape09 都没什么问题。tape10 的后半部分似乎有点问题,录制时间显示 2067.02.15_22-26-25 不动。由于每个片段开始时 dvgrab 会新创建文件,而新文件和旧文件同名,所以所有前面的片段都被覆盖掉了,只剩下最后的一个片段。无奈之下,我只能先加上 -timecode 选项:
dvgrab -autosplit -rewind -showstatus -size 0 -timestamp -timecode tape10-pass2-
采集完再把文件名中的所有:改成_。
由于 MiniDV 磁带记录的本来就是数字数据,而从摄像机到电脑的数据传输也是数字的,因此这样采集的视频文件就是磁带内容的精确复制。DCR-TRV40E 使用 PAL 制式,每一个 DV 帧的大小固定为 144000 字节,每秒 25 帧,1 小时的视频大约占 13 GB 的存储空间。在 2000 年这还是一块硬盘存不下的大小,现在就稀松平常了。
采集工作虽然完成了,但是还有一件事让我一直惦记着,就是 DVRescue 的 merge 功能,它能从对同一盒磁带的多次采集中,以帧和更小的数据块(Block)为单位,提取各个输入中的最佳选择并合并为一份最完整的数据。在播放采集到的视频文件时,我注意到还有少许画面有轻微的条纹,有一处两个镜头切换时比较严重。如果采集多次再用 DVRescue 合并就能获得错误更少的文件,那自然最好。
2026 年 1 月 2 日,我把这一套采集设备重新拿出来、组装好,完整地再次阅读了 DVRescue 的文档。文档中添加了不少步骤和例子,比之前读时更完善一些。一个重要发现是它还支持 rewind 功能,在有错误的帧自动倒带重读,这应该能省下好几次完整采集的时间。
然而在 Windows 7 上,GUI 版本的窗口太大,T410 的小屏幕显示不完整;CLI 版本缺少 dll 无法运行。至于 Linux,官方 apt 源只支持这几年的 Ubuntu 版本(别的 distro 应该也一样),我之前装的 Ubuntu 16 太老了。
所以,我先把 Ubuntu 24 装在之前 16 的分区,中间由于 WiFi 时断时续失败了一次;然后添加 apt 源:
wget https://mediaarea.net/repo/deb/repo-mediaarea-snapshots_1.0-26_all.deb
sudo dpkg -i repo-mediaarea-snapshots_1.0-26_all.deb
sudo apt-get update
sudo apt list | grep dvrescue
安装 dvrescue、dvrescue-gui 和 ffmpeg:
sudo apt install dvrescue
sudo apt install dvrescue-gui
sudo apt install ffmpeg
中途 Ubuntu 还死机了两三次,Gemini 分析是显卡驱动问题,我只好禁用硬件图形加速。
将摄像机开机、用 1394 线连接到电脑,查看 DVRescue 能否识别到设备:
dvrescue --list_devices
DVRescue 看着很专业,实际用起来 bug 不少。我首先遇到的问题是采集时能控制摄像机播放,但读取不到画面,必须用 sudo 执行才能解决。
sudo dvrescue device://0x8004601023e402f --rewind-count 3 --merge tape01-pass01.dv --xml-output tape01-pass01.xml --merge-log tape01-pass01.log --verbosity 7 --capture
这些选项的含义分别是:
-rewind-count 3:遇到损坏的帧时,自动倒回最后一个好的帧并重新采集,最多 3 次(也就是最多读取同一帧 4 次)--merge:将所有输入合并到输出文件,挑选每个输入中最好的部分--xml-output:保存 XML 输出到文件--merge-log:保存合并日志到文件--verbosity 7:日志级别,7 表示输出有问题的帧的信息和总结--capture:开始采集因为没有采集前倒带的选项,需要先在摄像机上按 REW⏪ 键将磁带倒回开头。采集开始后,终端也会滚动更新当前的总帧数和时间码。DVRescue 也没有 dvgrab 那样的自动切片选项,而是会把所有数据输出到一个完整的 .dv 文件,我推测这也是为了后续的分析、merge 方便。如果遇到需要 rewind 的帧,DVRescue 会将各次读取的结果输出到单独的 .dv 文件,文件名的格式是 tape01-pass01.dv.dvrescue.take0.frames33-33.dv。
输出的 XML 我读不太懂,应该不是给人类看的。日志文件如下:
DVRescue v.24.07.20251227 (MediaInfoLib v.25.10.20251228) by MIPoPS
File 0: device://0x8004601023e402f
File 1: auto-rewind
File 2: auto-rewind
File 3: auto-rewind
#|Abst |HH:MM:SS:FF|U|St |Comments
0 216 00:00:00:18 0 ( 7 frames)
7 00:00:00:25 M ( 5 frames)
12 300 00:00:01:00 0 ( 21 frames)
33 552 00:00:01:21 X P 1601 block picks & 127 remaining block errors
Rewind to frame 33
34 564 00:00:01:22 0
33 552 00:00:01:21 1 P
34 564 00:00:01:22 0 P
35 576 00:00:01:23 0 ( 2 frames)
37 00:00:01:25 M ( 5 frames)
42 600 00:00:02:00 0 ( 25 frames)
67 00:00:02:25 M ( 5 frames)
72 900 00:00:03:00 0 ( 25 frames)
97 00:00:03:25 M ( 5 frames)
102 1200 00:00:04:00 0 ( 25 frames)
127 00:00:04:25 M ( 5 frames)
132 1500 00:00:05:00 0 ( 25 frames)
157 00:00:05:25 M ( 5 frames)
162 1800 00:00:06:00 0 ( 25 frames)
187 00:00:06:25 M ( 5 frames)
192 2100 00:00:07:00 0 ( 25 frames)
217 00:00:07:25 M ( 5 frames)
222 2400 00:00:08:00 0 ( 25 frames)
247 00:00:08:25 M ( 5 frames)
252 2700 00:00:09:00 0 ( 25 frames)
(...中间内容省略...)
28212 282300 00:15:41:00 0 ( 25 frames)
28237 00:15:41:25 M ( 5 frames)
28242 282600 00:15:42:00 0 ( 25 frames)
28267 00:15:42:25 M ( 5 frames)
28272 282900 00:15:43:00 0 ( 25 frames)
28297 00:15:43:25 M ( 5 frames)
28302 283200 00:15:44:00 0 ( 25 frames)
28327 00:15:44:25 M ( 5 frames)
28332 283500 00:15:45:00 0 ( 25 frames)
28357 00:15:45:25 M ( 5 frames)
28362 283800 00:15:46:00 0
28363 283812 00:15:46:01 X P 1635 block picks & 93 remaining block errors
28364 283825 00:15:46:02 X P 1025 block picks & 703 remaining block errors
28365 283836 00:15:46:03 X P 1635 block picks & 93 remaining block errors
Rewind to frame 28363
28366 283848 00:15:46:04 0
28363 283812 00:15:46:01 X PP 0 1696 block picks & 32 remaining block errors
28364 ?????? 00:15:46:02 X PP 0 1692 block picks & 36 remaining block errors, abst 283825 283824
28365 283836 00:15:46:03 1 P
Rewind to frame 28363
28366 283848 00:15:46:04 0
28363 283812 00:15:46:01 X PPP 0 1 1717 block picks & 10 remaining block errors
28364 ?????? 00:15:46:02 X PPP 0 2 1715 block picks & 11 remaining block errors, abst 283825 283824 283824
28365 283836 00:15:46:03 1 P
Rewind to frame 28363
28366 283848 00:15:46:04 0
28363 283812 00:15:46:01 X PPPP 0 1 1717 0 block picks & 10 remaining block errors
28364 ?????? 00:15:46:02 X PPPP 0 2 1715 0 block picks & 11 remaining block errors, abst 283825 283824 283824 283824
28365 283836 00:15:46:03 1 P
28366 283848 00:15:46:04 0 ( 21 frames)
28387 00:15:46:25 M ( 5 frames)
28392 284100 00:15:47:00 0 ( 15 frames)
28407 284286 00:15:47:15 X P 1149 block picks & 579 remaining block errors
28408 284292 00:15:47:16 X P 1219 block picks & 509 remaining block errors
28409 284304 00:15:47:17 X P 1715 block picks & 13 remaining block errors
Rewind to frame 28407
28410 284316 00:15:47:18 0
28407 284286 00:15:47:15 X PP 1149 182 block picks & 397 remaining block errors
28408 ?????? 00:15:47:16 X PP 1219 0 block picks & 509 remaining block errors, abst 284292 284299
28409 284304 00:15:47:17 X PP 1715 7 block picks & 6 remaining block errors
28410 284316 00:15:47:18 0 P
Rewind to frame 28407
28407 ?????? 00:15:47:15 X PPP 54 20 1328 block picks & 326 remaining block errors, abst 284286 284286 284287
28408 ?????? 00:15:47:16 X PPP 1219 0 69 block picks & 440 remaining block errors, abst 284292 284299 284293
28409 ?????? 00:15:47:17 X PPP 1715 7 0 block picks & 6 remaining block errors, abst 284304 284304 284305
28410 ?????? 00:15:47:18 0 PP abst 284316 284316 284317
Rewind to frame 28407
28407 ?????? 00:15:47:15 X PPPP 54 18 1328 46 block picks & 282 remaining block errors, abst 284286 284286 284287 284286
28408 ?????? 00:15:47:16 X PPPP 1219 0 69 95 block picks & 345 remaining block errors, abst 284292 284299 284293 284292
28409 ?????? 00:15:47:17 X PPPP 1715 2 0 7 block picks & 4 remaining block errors, abst 284304 284304 284305 284304
28410 ?????? 00:15:47:18 0 PPP abst 284316 284316 284317 284316
28411 284328 00:15:47:19 0 ( 6 frames)
28417 00:15:47:25 M ( 5 frames)
28422 284400 00:15:48:00 0 ( 25 frames)
28447 00:15:48:25 M ( 5 frames)
28452 284700 00:15:49:00 0 ( 25 frames)
28477 00:15:49:25 M ( 5 frames)
28482 285000 00:15:50:00 0 ( 25 frames)
28507 00:15:50:25 M ( 5 frames)
28512 285300 00:15:51:00 0 ( 25 frames)
28537 00:15:51:25 M ( 5 frames)
28542 285600 00:15:52:00 0 ( 25 frames)
28567 00:15:52:25 M ( 5 frames)
(...中间内容省略...)
59647 00:33:08:25 M ( 5 frames)
59652 596700 00:33:09:00 0 ( 25 frames)
59677 00:33:09:25 M ( 5 frames)
59682 597000 00:33:10:00 0 ( 25 frames)
59707 00:33:10:25 M ( 5 frames)
59712 597300 00:33:11:00 0 ( 25 frames)
59737 00:33:11:25 M ( 5 frames)
59742 597600 00:33:12:00 0 ( 25 frames)
59767 00:33:12:25 M ( 5 frames)
59772 597900 00:33:13:00 0 ( 25 frames)
59797 00:33:13:25 M ( 5 frames)
59802 598200 00:33:14:00 0 ( 25 frames)
59827 00:33:14:25 M ( 5 frames)
59832 598500 00:33:15:00 0 ( 4 frames)
59836 ??:??:??:?? X P 0 block picks & 1728 remaining block errors
59837 ??:??:??:?? X P 0 block picks & 1728 remaining block errors
59838 ??:??:??:?? X P 0 block picks & 1728 remaining block errors
59839 ??:??:??:?? X P 0 block picks & 1728 remaining block errors
59840 ??:??:??:?? X P 0 block picks & 1728 remaining block errors
59841 ??:??:??:?? X P 0 block picks & 1728 remaining block errors
59842 ??:??:??:?? X P 0 block picks & 1728 remaining block errors
59843 ??:??:??:?? X P 0 block picks & 1728 remaining block errors
59844 frames in total.
How frames are merged:
49854 = 83.3066% of frames are recovered from full frames.
13 = 0.0217% of frames remain with errors.
(in these frames, errors reduced from 2582 = 3.6781% of blocks bad in file 3
to 14476 = 20.6211% of blocks bad).
9977 = 16.6717% of frames are still missing.
Usage of input files:
89756753 = 83.3247% of blocks from file 0 used.
21826 = 0.0203% of blocks from file 1 used.
8873 = 0.0082% of blocks from file 2 used.
148 = 0.0001% of blocks from file 3 used.
Input files summary:
File 0: 31237485 = 28.9990% of blocks have errors, in 17480 = 29.2093% of frames
Rew 1: 3784 = 0.0035% of blocks have errors, in 7 = 0.0117% of frames
Rew 2: 4521 = 0.0042% of blocks have errors, in 6 = 0.0100% of frames
Rew 3: 2611 = 0.0024% of blocks have errors, in 6 = 0.0100% of frames
Result:
17973076 = 16.6851% of blocks have errors, in 9990 = 16.6934% of frames
(- 12.3139) (- 12.5159)
日志中各列分别表示:
#:最终输出文件中的帧序号。Abst:绝对轨道号(Absolute Track Number),对应磁带上的一条磁迹,(理论上)唯一且连续递增。若多个输入文件的 Abst 值不一致,则显示 ??????。HH:MM:SS:FF:时间码,若所有输入文件都没有有效时间码,则显示 ??:??:??:??。U:输出使用的输入,若某一个输入的帧内无错误,则使用该帧并显示序号(0, 1, 2 等);若没有完美帧,则需要将各帧进行块级合并,并显示 X;若所有输入都缺失此帧,则显示 M。St:从左到右每个字符表示对应输入帧的状态,M表示缺失该帧,P表示部分块错误,T表示时间码有问题,空格表示该帧完好。Comments:详细信息,包括下列几项——... block picks:从各个输入选取的块数。& ... remaining block errors:所有输入中都有错误的块数。abst:Abst不一致时各个输入的值。(... frames):连续多帧状态相同时压缩为一行日志。日志中有大段大段的0 ( 25 frames)、M ( 5 frames)连续交替的行,我推测是将 25 fps 的 PAL 输入错误识别为了 30 fps 的 NTSC,因此每秒都会“缺”最后 5 帧,实际是不缺的。
采集 tape01 两遍之后,在检查视频文件时,我才发现一个严重的问题。这两遍输出的文件不完整,都只有 33 分钟长,而这盒磁带是基本录满的,应该有 1 个小时;和 dvgrab 采集的文件比较,前半部分的片段内容一致,但在 33 分钟处的一个镜头结束后,后面的片段就缺失了。我在摄像机上播放这一段时看到这个片段后紧接着的就是一段无内容、摄像机屏幕显示蓝屏的间隔,猜测可能是 DVRescue 碰到这个就以为视频结束了,没有接着读;但是采集时磁带却是播放到了结尾才停,很令人困惑。
我又用 dvgrab 采集了一次,视频内容完整,说明并非磁带或摄像机的问题。1 月 18 日,我 clone 了 DVRescue 的源码,尝试使用 Claude Code debug。Claude 先分析出可能是采集结束条件的判断有问题,改完源代码重新编译、把可执行文件拷到 T410 上、再采集,发现采集出的文件还是只有 33 分钟,bug 没修好。
重新拷打 Claude,在经过若干次拉扯和反复检查之后,它终于分析出了错误原因:在两个片段交界处,DVRescue 先遇到一段错误帧、触发 rewind 逻辑;紧接着下一个好帧的 timecode 归零,需要建立并切换为新的 Segment。在一个遍历 Inputs 数组、检查是否需要建立新 Segment 的 for 循环中,错误地没有跳过 DoNotUseFile flag 为 True 的 auto-rewind 输入,导致切换 Segment 的检查失败。之后采集的所有帧都被加入新的 Segment 中,但是由于永远不会切换到这个新 Segment,也就不会向文件输出。
在检查所有遍历 Inputs 的循环后,Claude Code 进行了下列修改:
diff --git a/Source/Common/Merge.cpp b/Source/Common/Merge.cpp
index 998911d..8d8eb8e 100644
--- a/Source/Common/Merge.cpp
+++ b/Source/Common/Merge.cpp
@@ -722,6 +722,8 @@ bool dv_merge_private::Process(float Speed)
auto Segment_Pos1 = Segment_Pos + 1;
for (const auto Input : Inputs)
{
+ if (Input->DoNotUseFile)
+ continue;
if (Segment_Pos1 >= Input->Segments.size())
return true;
}
@@ -993,6 +995,8 @@ bool dv_merge_private::Process(float Speed)
for (size_t i = 0; i < Input_Count; i++)
{
auto& Input = Inputs[i];
+ if (Input->DoNotUseFile)
+ continue;
auto& Frames = Input->Segments[Segment_Pos].Frames;
auto& Frame = Frames[Frame_Pos];
if (!Frame.Status[Status_FrameMissing])
@@ -1382,6 +1386,11 @@ bool dv_merge_private::Process(float Speed)
{
Log_Line << Log_Separator;
auto& Input = Inputs[i];
+ if (Input->DoNotUseFile)
+ {
+ Log_Line << "[DoNotUseF]"; // 11 characters, matching timecode width
+ continue;
+ }
auto& Frames = Input->Segments[Segment_Pos].Frames;
auto& Frame = Frames[Frame_Pos];
Log_Line << (Frame.Status[Status_TimeCodeIssue] ? "??:??:??:??" : TC_String(Frame.TC));
再重新编译、采集,修改后的 DVRescue 在 33 分钟的视频片段切换处,遇到后一片段的第一帧后马上开始向前倒带(我推测是 auto-rewind 生效了,想要重新读取这一段),但即使磁带位置已经回到蓝屏区域之前、前一片段的完好帧,也没有停止倒带。我等了一分钟,感觉倒带肯定不会结束了,只能强行终止了进程。这次 Claude 似乎也分析不出来具体是什么原因,分析了几次都是错的,我只能大致猜测是结束倒带的逻辑判断有问题。到这里我已经 debug 了一个晚上,有点累了,DVRescue 的代码又很长、没什么注释,很难看懂。我找到一个 2024 年的 open issue 非常类似,但没有下文。最终,我决定放弃继续 debug,转而用 dvgrab 采集多次后,再用 DVRescue merge。
如果用这种方法,一个新的问题就是 2025 年用 dvgrab autosplit 选项采集的视频应该怎么 merge。如果新采集时也用 autosplit、也输出十几个文件、每个文件分别 merge,是非常麻烦的。幸好 dvgrab 也有源代码,Claude Code 分析源码之后确定 raw DV 文件就是一帧帧数据直接拼起来的,所以只要把同一条磁带上的所有视频片段按(文件系统上的)最后修改时间 cat 起来,就相当于当时不用 autosplit 所输出的完整文件。
ls -1t tape01-pass1/*.dv | tac | xargs cat > tape01-pass1-merged.dv
需要注意的是,不能按文件名排序,因为文件名中的录制时间和时间码在磁带上不一定单调递增。1 月 25 至 28 日,我用 dvgrab 把所有磁带重新采集了一遍。
1 月 29 日,我又让 Claude 分析了一下 DVRescue 的 merge 算法,发现也不是特别严谨,我不敢完全相信。DVRescue 在不同输入源之间对齐帧主要依赖于 timecode,对于没有 timecode 的帧就按上一帧的 timecode + 1 来推断。但这样问题就来了,如果在两个视频片段的交界处,前一片段的末尾和后一片段的开头都有若干无 timecode 的帧,合并时就会把后一片段开头的帧误认为是前一片段末尾的。如果摄像机由于物理原因在读取交界处时多了或少了一两帧,后一片段的开头就对不齐,merge 的结果中反而会引入新的错误,这是我不能接受的。(之后我尝试 merge 过一次,确实在画面条纹较多的片段交界处出现了同一段音频的重复,说明的确有这个问题。)可是磁带上的元数据就这么多,还有什么更好的 merge 算法吗?似乎也想不到。
事已至此,转念一想,我那几盒磁带里有这么多数据错误,值得费这么大劲去修复吗?(我还是太爱折腾了,一想到 Data Hoarding 就发了狠了。)我又注意到用 DVRescue 采集的几个日志的 summary 中有错误的 block 和 frame 比例都是 16.6% 左右,这差不多就是 $\frac{1}{6}=\frac{5}{25 + 5}$。上文提到,日志中每隔 25 帧就缺失 5 帧,这么高的错误率是否是因为帧率错误算出来的呢?
我让 Claude Code 再次分析源代码,的确如此:代码中有 5 处 30 /*TEMP*/,明显是开发时先临时硬编码了 NTSC 的帧率,但后续没有适配 PAL。由于我只处理 PAL 磁带,可以把它们全部改成 25:
diff --git a/Source/Common/Merge.cpp b/Source/Common/Merge.cpp
index 998911d..95f27ad 100644
--- a/Source/Common/Merge.cpp
+++ b/Source/Common/Merge.cpp
@@ -505,7 +505,7 @@ bool dv_merge_private::AppendFrameToList(size_t InputPos, const MediaInfo_Event_
// Time code jumps - after first frame
timecode TC_Temp(FrameData);
if (TC_Temp.HasValue())
- CurrentFrame.TC = TimeCode(TC_Temp.TimeInSeconds() / 3600, (TC_Temp.TimeInSeconds() / 60) % 60, TC_Temp.TimeInSeconds() % 60, TC_Temp.Frames(), 30 /*TEMP*/, TC_Temp.DropFrame());
+ CurrentFrame.TC = TimeCode(TC_Temp.TimeInSeconds() / 3600, (TC_Temp.TimeInSeconds() / 60) % 60, TC_Temp.TimeInSeconds() % 60, TC_Temp.Frames(), 25 /*PAL*/, TC_Temp.DropFrame());
if (!Frames.empty() && Frames.back().TC.HasValue())
{
TimeCode TC_Previous(Frames.back().TC);
diff --git a/Source/Common/ProcessFile.cpp b/Source/Common/ProcessFile.cpp
index 608454b..078821e 100644
--- a/Source/Common/ProcessFile.cpp
+++ b/Source/Common/ProcessFile.cpp
@@ -678,7 +678,7 @@ void file::AddFrameAnalysis(const MediaInfo_Event_DvDif_Analysis_Frame_1* FrameD
timecode TC_Temp(FrameData);
if (TC_Temp.HasValue())
{
- TimeCode TC(TC_Temp.TimeInSeconds() / 3600, (TC_Temp.TimeInSeconds() / 60) % 60, TC_Temp.TimeInSeconds() % 60, TC_Temp.Frames(), 30 /*TEMP*/, TC_Temp.DropFrame());
+ TimeCode TC(TC_Temp.TimeInSeconds() / 3600, (TC_Temp.TimeInSeconds() / 60) % 60, TC_Temp.TimeInSeconds() % 60, TC_Temp.Frames(), 25 /*PAL*/, TC_Temp.DropFrame());
if (Verbosity == 10)
{
cerr << "Rewind ";
@@ -706,7 +706,7 @@ void file::AddFrameAnalysis(const MediaInfo_Event_DvDif_Analysis_Frame_1* FrameD
timecode TC_Temp(FrameData);
if (TC_Temp.HasValue())
{
- TimeCode TC(TC_Temp.TimeInSeconds() / 3600, (TC_Temp.TimeInSeconds() / 60) % 60, TC_Temp.TimeInSeconds() % 60, TC_Temp.Frames(), 30 /*TEMP*/, TC_Temp.DropFrame());
+ TimeCode TC(TC_Temp.TimeInSeconds() / 3600, (TC_Temp.TimeInSeconds() / 60) % 60, TC_Temp.TimeInSeconds() % 60, TC_Temp.Frames(), 25 /*PAL*/, TC_Temp.DropFrame());
if (RewindMode==Rewind_Mode_TimeCode && TC.ToFrames()<RewindTo_TC.ToFrames())
{
if (Verbosity == 10)
@@ -791,7 +791,7 @@ void file::AddFrameAnalysis(const MediaInfo_Event_DvDif_Analysis_Frame_1* FrameD
timecode TC_Temp(FrameData);
if (TC_Temp.HasValue())
{
- TimeCode TC(TC_Temp.TimeInSeconds() / 3600, (TC_Temp.TimeInSeconds() / 60) % 60, TC_Temp.TimeInSeconds() % 60, TC_Temp.Frames(), 30 /*TEMP*/, TC_Temp.DropFrame());
+ TimeCode TC(TC_Temp.TimeInSeconds() / 3600, (TC_Temp.TimeInSeconds() / 60) % 60, TC_Temp.TimeInSeconds() % 60, TC_Temp.Frames(), 25 /*PAL*/, TC_Temp.DropFrame());
if (TC.ToFrames()<RewindTo_TC.ToFrames())
return;
RewindTo_TC = TimeCode();
@@ -884,7 +884,7 @@ void file::AddFrameAnalysis(const MediaInfo_Event_DvDif_Analysis_Frame_1* FrameD
timecode TC_Temp(FrameData);
if (TC_Temp.HasValue())
{
- TimeCode TC(TC_Temp.TimeInSeconds() / 3600, (TC_Temp.TimeInSeconds() / 60) % 60, TC_Temp.TimeInSeconds() % 60, TC_Temp.Frames(), 30 /*TEMP*/, TC_Temp.DropFrame());
+ TimeCode TC(TC_Temp.TimeInSeconds() / 3600, (TC_Temp.TimeInSeconds() / 60) % 60, TC_Temp.TimeInSeconds() % 60, TC_Temp.Frames(), 25 /*PAL*/, TC_Temp.DropFrame());
Text += ' ';
Text += TC.ToString();
}
重新编译,丢弃 merge 的输出文件,只保留日志,以此分析已用 dvgrab 采集的视频:
dvrescue-patched-v3-PAL tape01-pass03001.dv --merge /dev/null --merge-log tape01-pass03001.log --xml-output tape01-pass03001.xml --verbosity 7
输出日志如下:
DVRescue v.24.07 (MediaInfoLib v.25.10.20260120) by MIPoPS
File 0: tape01-pass03001.dv
#|Abst |HH:MM:SS:FF|U|S|Comments
0 216 00:00:00:18 0 ( 23257 frames)
23257 279300 00:15:31:00 X P 1710 block picks & 18 remaining block errors
23258 279312 00:15:31:01 0 ( 375 frames)
23633 283812 00:15:46:01 X P 1705 block picks & 23 remaining block errors
23634 283824 00:15:46:02 X P 1716 block picks & 12 remaining block errors
23635 283836 00:15:46:03 0 ( 37 frames)
23672 284287 00:15:47:15 X P 1653 block picks & 75 remaining block errors
23673 284292 00:15:47:16 X P 744 block picks & 984 remaining block errors
23674 284304 00:15:47:17 X P 864 block picks & 864 remaining block errors
23675 284316 00:15:47:18 X P 864 block picks & 864 remaining block errors
23676 284328 00:15:47:19 X P 864 block picks & 864 remaining block errors
23677 284340 00:15:47:20 X P 864 block picks & 864 remaining block errors
23678 284352 00:15:47:21 X P 864 block picks & 864 remaining block errors
23679 284364 00:15:47:22 X P 864 block picks & 864 remaining block errors
23680 284376 00:15:47:23 X P 864 block picks & 864 remaining block errors
23681 284388 00:15:47:24 X P 864 block picks & 864 remaining block errors
23682 284400 00:15:48:00 X P 864 block picks & 864 remaining block errors
23683 284412 00:15:48:01 X P 864 block picks & 864 remaining block errors
(...中间内容省略...)
71177 874093 00:00:03:16 X P 864 block picks & 864 remaining block errors
71178 874105 00:00:03:17 X P 1486 block picks & 242 remaining block errors
71179 874116 00:00:03:18 0 ( 15113 frames)
86292 ??:??:??:?? X P 0 block picks & 1728 remaining block errors
86293 ??:??:??:?? X P 0 block picks & 1728 remaining block errors
86294 ??:??:??:?? X P 0 block picks & 1728 remaining block errors
86295 ??:??:??:?? X P 0 block picks & 1728 remaining block errors
86296 ??:??:??:?? X P 0 block picks & 1728 remaining block errors
86297 ??:??:??:?? X P 0 block picks & 1728 remaining block errors
86298 frames in total.
How frames are merged:
86001 = 99.6558% of frames are recovered from full frames.
297 = 0.3442% of frames remain with errors.
Usage of input files:
155336400 = 100.0000% of blocks from file 0 used.
Input files summary:
File 0: 285950 = 0.1841% of blocks have errors, in 297 = 0.3442% of frames
Result:
285950 = 0.1841% of blocks have errors, in 297 = 0.3442% of frames
现在日志就短了很多,因为大量(可能是成千上万行的)连续的正常帧被压缩为一行日志。我像这样分析了之前所有用 dvgrab 采集的视频文件,所有文件的帧错误率都在 1% 以下,块错误率更低,说明这些磁带状况很好,其实没有必要进行额外的修复。由于我希望只保留一份原始数据,现在问题就变成了:我应该保留哪次采集的视频文件?
既然现在已经能计算出每次采集的实际错误率,自然应该保留每盒磁带的错误率最低的那次采集。当我分别对比每盒磁带在 2025 年 2 月和 2026 年 1 月采集的分析日志时,惊讶地发现它们的帧错误率和块错误率基本都升高了,只有 tape07 例外。
| 采集时间 | 2025-02 | 2026-01 |
|---|---|---|
| tape01 | 86305 frames in total. 243491 = 0.1567% of blocks have errors, in 268 = 0.3105% of frames | 86298 frames in total. 285950 = 0.1841% of blocks have errors, in 297 = 0.3442% of frames |
| tape02 | 91278 frames in total. 181300 = 0.1103% of blocks have errors, in 247 = 0.2706% of frames | 91278 frames in total. 182945 = 0.1113% of blocks have errors, in 265 = 0.2903% of frames |
| tape03 | 27111 frames in total. 24448 = 0.0501% of blocks have errors, in 23 = 0.0848% of frames | 27110 frames in total. 25180 = 0.0516% of blocks have errors, in 20 = 0.0738% of frames |
| tape04 | 86931 frames in total. 149609 = 0.0956% of blocks have errors, in 100 = 0.1150% of frames | 86932 frames in total. 151848 = 0.0970% of blocks have errors, in 104 = 0.1196% of frames |
| tape05 | 84372 frames in total. 82470 = 0.0543% of blocks have errors, in 96 = 0.1138% of frames | 84372 frames in total. 87591 = 0.0577% of blocks have errors, in 114 = 0.1351% of frames |
| tape06 | 29232 frames in total. 35582 = 0.0676% of blocks have errors, in 53 = 0.1813% of frames | 29231 frames in total. 44650 = 0.0849% of blocks have errors, in 70 = 0.2395% of frames |
| tape07 | 26908 frames in total. 54657 = 0.1128% of blocks have errors, in 43 = 0.1598% of frames | 26906 frames in total. 51154 = 0.1056% of blocks have errors, in 37 = 0.1375% of frames |
| tape08 | 35771 frames in total. 10368 = 0.0161% of blocks have errors, in 6 = 0.0168% of frames | 35773 frames in total. 12199 = 0.0189% of blocks have errors, in 11 = 0.0307% of frames |
| tape09 | 79363 frames in total. 78548 = 0.0550% of blocks have errors, in 55 = 0.0693% of frames | 79365 frames in total. 82671 = 0.0579% of blocks have errors, in 58 = 0.0731% of frames |
| tape10 | 87762 frames in total. 45276 = 0.0287% of blocks have errors, in 43 = 0.0490% of frames | 87762 frames in total. 52375 = 0.0332% of blocks have errors, in 46 = 0.0524% of frames |
我让 Claude 画了一张图,直观地展示两种错误率随时间的变化:

tape01 和 tape06 的升高最为明显,但是在 2025 年的采集之前它们就已经保存了二十多年,如果每年磁带都以这种速率损坏的话,数据错误率应该远高于当前水平。思来想去,我回忆起在2022 年最初发现它们之前,这些磁带是放在完全避光的柜子里的;而 2025 年第一次采集后,我就把它们装在一个塑料袋里,和摄像机、T410 笔记本一起放在柜子外面。虽然温度、湿度也基本稳定,但是能见光。如果是这个原因,那说明光照对磁带的损害还是比较大的。
幸好,我在 2025 年已经把所有磁带用可靠的方法采集下来了。最终我决定保留所有 2025 年 2 月采集的文件,也包括后来错误率降低的 tape07。(多错的这几帧数据已经不值得投入更多精力折腾了,错就错吧。)到此,这个横跨五年的超长项目就圆满结束了。
下面是一些我在调查时保存到浏览器书签栏的讨论和文章,按添加时间排序。
[1] Digitize MiniDV Tapes with Linux | Hacker News - 2022-11-29 添加
[2] Found a bunch of my parents’ MiniDVs… any advice on how I can digitize these? : DataHoarder - 2024-03-03 添加
[3] The "How do I digitize/transfer/capture video tapes" quick info thread. VHS VHSC HI8 Video8 Digital8 miniDV : DataHoarder - 2024-03-03 添加
[4] How I archive miniDV tapes - a short guide : DataHoarder - 2024-03-03 添加
[5] The challenges (and rewards!) of preserving video from MiniDV tapes – University of Bristol Theatre Collection Blog - 2025-01-15 添加
[6] Gather: Digitize MiniDVs - Elgrito - 2025-01-15 添加