Cách tôi khôi phục dữ liệu ở định dạng không xác định từ băng từ

thời tiền sử

Là người yêu thích phần cứng cổ điển, tôi đã từng mua ZX Spectrum+ từ một người bán ở Anh. Đi kèm với máy tính, tôi đã nhận được một số băng ghi âm có trò chơi (trong bao bì gốc có hướng dẫn), cũng như các chương trình được ghi trên băng mà không có dấu hiệu đặc biệt. Điều đáng ngạc nhiên là dữ liệu từ những chiếc băng cassette 40 năm tuổi vẫn có thể đọc được và tôi có thể tải xuống hầu hết các trò chơi và chương trình từ chúng.

Cách tôi khôi phục dữ liệu ở định dạng không xác định từ băng từ

Tuy nhiên, trên một số băng cassette, tôi thấy các bản ghi rõ ràng không phải do máy tính ZX Spectrum tạo ra. Chúng nghe có vẻ hoàn toàn khác nhau và không giống như các bản ghi từ máy tính được đề cập, chúng không bắt đầu bằng bộ tải khởi động BASIC ngắn, thường có trong bản ghi của tất cả các chương trình và trò chơi.

Điều này đã ám ảnh tôi một thời gian - tôi thực sự muốn tìm hiểu điều gì ẩn giấu trong chúng. Nếu bạn có thể đọc tín hiệu âm thanh dưới dạng một chuỗi byte, bạn có thể tìm kiếm các ký tự hoặc bất kỳ thứ gì cho biết nguồn gốc của tín hiệu. Một loại khảo cổ học retro.

Bây giờ tôi đã xem hết nhãn của các cuộn băng, tôi mỉm cười vì

câu trả lời đã ở ngay trước mắt tôi
Trên nhãn của băng cassette bên trái có tên máy tính TRS-80 và ngay phía dưới tên nhà sản xuất: “Sản xuất bởi Radio Shack ở Mỹ”

(Nếu bạn muốn giữ được tình tiết cho đến cuối cùng, đừng đi theo spoiler)

So sánh tín hiệu âm thanh

Trước hết, hãy số hóa các bản ghi âm. Bạn có thể nghe nó nghe như thế nào:


Và như thường lệ, bản ghi âm từ máy tính ZX Spectrum phát ra:


Trong cả hai trường hợp, ở đầu bản ghi đều có cái gọi là giai điệu thí điểm - một âm thanh có cùng tần số (trong lần ghi đầu tiên, nó rất ngắn <1 giây, nhưng có thể phân biệt được). Âm thử báo hiệu cho máy tính chuẩn bị nhận dữ liệu. Theo quy định, mỗi máy tính chỉ nhận dạng âm thử “của riêng mình” dựa trên hình dạng và tần số của tín hiệu.

Cần phải nói điều gì đó về hình dạng tín hiệu. Ví dụ: trên ZX Spectrum, hình dạng của nó là hình chữ nhật:

Cách tôi khôi phục dữ liệu ở định dạng không xác định từ băng từ

Khi phát hiện âm thử, ZX Spectrum hiển thị các thanh màu đỏ và xanh xen kẽ trên viền màn hình để cho biết tín hiệu đã được nhận dạng. Giai điệu thí điểm kết thúc xung đồng bộ, báo hiệu cho máy tính bắt đầu nhận dữ liệu. Nó được đặc trưng bởi thời lượng ngắn hơn (so với âm thử và dữ liệu tiếp theo) (xem hình)

Sau khi nhận được xung đồng bộ, máy tính sẽ ghi lại từng lần tăng/giảm tín hiệu, đo thời lượng của nó. Nếu thời lượng nhỏ hơn một giới hạn nhất định, bit 1 sẽ được ghi vào bộ nhớ, nếu không thì là 0. Các bit được thu thập thành byte và quá trình được lặp lại cho đến khi nhận được N byte. Số N thường được lấy từ tiêu đề của tệp đã tải xuống. Trình tự nạp như sau:

  1. giai điệu thí điểm
  2. tiêu đề (độ dài cố định), chứa kích thước của dữ liệu đã tải xuống (N), tên và loại tệp
  3. giai điệu thí điểm
  4. chính dữ liệu

Để đảm bảo dữ liệu được tải chính xác, ZX Spectrum đọc cái gọi là byte chẵn lẻ (byte chẵn lẻ), được tính khi lưu tệp bằng cách XOR tất cả byte của dữ liệu đã ghi. Khi đọc một tệp, máy tính sẽ tính toán byte chẵn lẻ từ dữ liệu nhận được và nếu kết quả khác với kết quả đã lưu thì sẽ hiển thị thông báo lỗi “R Lỗi tải băng”. Nói đúng ra, máy tính có thể đưa ra thông báo này sớm hơn nếu khi đọc, nó không thể nhận ra xung (bị lỡ hoặc thời lượng của nó không tương ứng với các giới hạn nhất định)

Vì vậy, bây giờ chúng ta hãy xem tín hiệu không xác định trông như thế nào:

Cách tôi khôi phục dữ liệu ở định dạng không xác định từ băng từ

Đây là giai điệu thí điểm. Hình dạng của tín hiệu có sự khác biệt đáng kể, nhưng rõ ràng là tín hiệu bao gồm các xung ngắn lặp lại ở một tần số nhất định. Ở tần số lấy mẫu là 44100 Hz, khoảng cách giữa các “đỉnh” là khoảng 48 mẫu (tương ứng với tần số ~918 Hz). Hãy nhớ hình này.

Bây giờ chúng ta hãy xem đoạn dữ liệu:

Cách tôi khôi phục dữ liệu ở định dạng không xác định từ băng từ

Nếu chúng ta đo khoảng cách giữa các xung riêng lẻ, thì hóa ra khoảng cách giữa các xung “dài” vẫn là ~48 mẫu và giữa các xung ngắn - ~24. Nhìn về phía trước một chút, tôi sẽ nói rằng cuối cùng hóa ra các xung “tham chiếu” có tần số 918 Hz theo sau liên tục, từ đầu đến cuối tệp. Có thể giả định rằng khi truyền dữ liệu, nếu gặp thêm một xung giữa các xung tham chiếu thì chúng ta coi đó là bit 1, nếu không thì là 0.

Còn xung đồng bộ thì sao? Hãy nhìn vào phần đầu của dữ liệu:

Cách tôi khôi phục dữ liệu ở định dạng không xác định từ băng từ

Âm thử kết thúc và dữ liệu bắt đầu ngay lập tức. Một lát sau, sau khi phân tích một số bản ghi âm thanh khác nhau, chúng tôi có thể phát hiện ra rằng byte dữ liệu đầu tiên luôn giống nhau (10100101b, A5h). Máy tính có thể bắt đầu đọc dữ liệu sau khi nhận được dữ liệu.

Bạn cũng có thể chú ý đến sự dịch chuyển của xung tham chiếu đầu tiên ngay sau xung đầu tiên cuối cùng trong byte đồng bộ. Nó được phát hiện muộn hơn nhiều trong quá trình phát triển chương trình nhận dạng dữ liệu, khi dữ liệu ở đầu tệp không thể đọc ổn định.

Bây giờ hãy thử mô tả một thuật toán sẽ xử lý tệp âm thanh và tải dữ liệu.

Đang tải dữ liệu

Trước tiên, hãy xem xét một số giả định để giữ cho thuật toán đơn giản:

  1. Chúng tôi sẽ chỉ xem xét các tệp ở định dạng WAV;
  2. Tệp âm thanh phải bắt đầu bằng âm thử và không được chứa khoảng lặng ở đầu
  3. Tệp nguồn phải có tốc độ lấy mẫu là 44100 Hz. Trong trường hợp này, khoảng cách giữa các xung tham chiếu của 48 mẫu đã được xác định và chúng ta không cần phải tính toán theo chương trình;
  4. Định dạng mẫu có thể là bất kỳ (8/16 bit/dấu phẩy động) - vì khi đọc, chúng ta có thể chuyển đổi nó thành định dạng mong muốn;
  5. Chúng tôi giả sử rằng tệp nguồn được chuẩn hóa theo biên độ, điều này sẽ ổn định kết quả;

Thuật toán đọc sẽ như sau:

  1. Chúng ta đọc file vào bộ nhớ, đồng thời chuyển đổi định dạng mẫu thành 8 bit;
  2. Xác định vị trí của xung đầu tiên trong dữ liệu âm thanh. Để làm điều này, bạn cần tính số lượng mẫu có biên độ tối đa. Để đơn giản, chúng ta sẽ tính toán thủ công một lần. Hãy lưu nó vào biến prev_pos;
  3. Thêm 48 vào vị trí của xung cuối cùng (pos := prev_pos + 48)
  4. Vì việc tăng vị trí lên 48 không đảm bảo rằng chúng ta sẽ đến được vị trí của xung tham chiếu tiếp theo (lỗi băng, hoạt động không ổn định của cơ cấu truyền động băng, v.v.), nên chúng ta cần điều chỉnh vị trí của xung. Để làm điều này, hãy lấy một đoạn dữ liệu nhỏ (pos-8;pos+8) và tìm giá trị biên độ tối đa trên đó. Vị trí tương ứng với mức tối đa sẽ được lưu trữ trong pos. Ở đây 8 = 48/6 là hằng số thu được bằng thực nghiệm, đảm bảo rằng chúng ta sẽ xác định mức tối đa chính xác và sẽ không ảnh hưởng đến các xung khác có thể ở gần đó. Trong những trường hợp rất xấu, khi khoảng cách giữa các xung nhỏ hơn hoặc lớn hơn 48, bạn có thể thực hiện tìm kiếm bắt buộc đối với xung, nhưng trong phạm vi bài viết, tôi sẽ không mô tả điều này trong thuật toán;
  5. Ở bước trước, cũng cần phải kiểm tra xem xung tham chiếu có được tìm thấy hay không. Nghĩa là, nếu bạn chỉ tìm kiếm mức tối đa, điều này không đảm bảo rằng xung lực sẽ xuất hiện trong phân khúc này. Trong lần triển khai chương trình đọc mới nhất của tôi, tôi kiểm tra sự khác biệt giữa các giá trị biên độ tối đa và tối thiểu trên một đoạn và nếu nó vượt quá một giới hạn nhất định, tôi sẽ đếm sự hiện diện của xung. Câu hỏi đặt ra là phải làm gì nếu không tìm thấy xung tham chiếu. Có 2 tùy chọn: dữ liệu đã kết thúc và sau đó là sự im lặng hoặc đây được coi là lỗi đọc. Tuy nhiên, chúng ta sẽ bỏ qua điều này để đơn giản hóa thuật toán;
  6. Ở bước tiếp theo, chúng ta cần xác định sự hiện diện của xung dữ liệu (bit 0 hoặc 1), để làm được điều này, chúng ta lấy phần giữa của đoạn (prev_pos;pos) middle_pos bằng middle_pos := (prev_pos+pos)/2 và trong một số vùng lân cận của middle_pos trên đoạn (middle_pos-8;middle_pos +8), hãy tính biên độ cực đại và cực tiểu. Nếu chênh lệch giữa chúng lớn hơn 10, chúng ta ghi bit 1 vào kết quả, nếu không thì 0. 10 là hằng số thu được bằng thực nghiệm;
  7. Lưu vị trí hiện tại trong prev_pos (prev_pos := pos)
  8. Lặp lại bắt đầu từ bước 3 cho đến khi chúng tôi đọc toàn bộ tệp;
  9. Mảng bit kết quả phải được lưu dưới dạng tập hợp byte. Vì chúng tôi không tính đến byte đồng bộ khi đọc nên số bit có thể không phải là bội số của 8 và độ lệch bit yêu cầu cũng không xác định. Trong lần triển khai thuật toán đầu tiên, tôi không biết về sự tồn tại của byte đồng bộ và do đó chỉ lưu 8 tệp với số bit bù khác nhau. Một trong số chúng chứa dữ liệu chính xác. Trong thuật toán cuối cùng, tôi chỉ cần loại bỏ tất cả các bit lên đến A5h, điều này cho phép tôi nhận được ngay tệp đầu ra chính xác

Thuật toán trong Ruby, dành cho những người quan tâm
Tôi chọn Ruby làm ngôn ngữ để viết chương trình, vì... Tôi lập trình trên đó hầu hết thời gian. Tùy chọn này không mang lại hiệu suất cao nhưng nhiệm vụ làm cho tốc độ đọc càng nhanh càng tốt là không đáng.

# Используем gem 'wavefile'
require 'wavefile'

reader = WaveFile::Reader.new('input.wav')
samples = []
format = WaveFile::Format.new(:mono, :pcm_8, 44100)

# Читаем WAV файл, конвертируем в формат Mono, 8 bit 
# Массив samples будет состоять из байт со значениями 0-255
reader.each_buffer(10000) do |buffer|
  samples += buffer.convert(format).samples
end

# Позиция первого импульса (вместо 0)
prev_pos = 0
# Расстояние между импульсами
distance = 48
# Значение расстояния для окрестности поиска локального максимума
delta = (distance / 6).floor
# Биты будем сохранять в виде строки из "0" и "1"
bits = ""

loop do
  # Рассчитываем позицию следующего импульса
  pos = prev_pos + distance
  
  # Выходим из цикла если данные закончились 
  break if pos + delta >= samples.size

  # Корректируем позицию pos обнаружением максимума на отрезке [pos - delta;pos + delta]
  (pos - delta..pos + delta).each { |p| pos = p if samples[p] > samples[pos] }

  # Находим середину отрезка [prev_pos;pos]
  middle_pos = ((prev_pos + pos) / 2).floor

  # Берем окрестность в середине 
  sample = samples[middle_pos - delta..middle_pos + delta]

  # Определяем бит как "1" если разница между максимальным и минимальным значением на отрезке превышает 10
  bit = sample.max - sample.min > 10
  bits += bit ? "1" : "0"
end

# Определяем синхро-байт и заменяем все предшествующие биты на 256 бит нулей (согласно спецификации формата) 
bits.gsub! /^[01]*?10100101/, ("0" * 256) + "10100101"

# Сохраняем выходной файл, упаковывая биты в байты
File.write "output.cas", [bits].pack("B*")

Kết quả

Sau khi thử một số biến thể của thuật toán và hằng số, tôi thật may mắn khi nhận được một điều cực kỳ thú vị:

Cách tôi khôi phục dữ liệu ở định dạng không xác định từ băng từ

Vì vậy, xét theo chuỗi ký tự, chúng ta có một chương trình vẽ đồ thị. Tuy nhiên, không có từ khóa trong văn bản chương trình. Tất cả từ khóa được mã hóa dưới dạng byte (mỗi giá trị > 80h). Bây giờ chúng ta cần tìm ra máy tính nào từ những năm 80 có thể lưu chương trình ở định dạng này.

Trên thực tế, nó rất giống với một chương trình BASIC. Máy tính ZX Spectrum lưu trữ các chương trình ở định dạng gần giống nhau trong bộ nhớ và lưu chương trình vào băng. Để đề phòng, tôi đã kiểm tra các từ khóa dựa trên chiếc bàn. Tuy nhiên, kết quả rõ ràng là âm tính.

Tôi cũng đã kiểm tra các từ khóa BASIC của Atari, Commodore 64 phổ biến và một số máy tính khác vào thời điểm đó, tôi đã có thể tìm thấy tài liệu về chúng, nhưng không thành công - kiến ​​​​thức của tôi về các loại máy tính cổ điển hóa ra không quá rộng.

Sau đó tôi quyết định đi danh sách, rồi ánh mắt tôi rơi vào tên nhà sản xuất Radio Shack và máy tính TRS-80. Đây là những cái tên được viết trên nhãn của những cuộn băng nằm trên bàn của tôi! Trước đây tôi không biết những cái tên này và cũng không quen với máy tính TRS-80 nên đối với tôi, Radio Shack là nhà sản xuất băng cassette như BASF, Sony hay TDK và TRS-80 là máy phát lại âm thanh. Tại sao không?

Máy tính Tandy/Radio Shack TRS-80

Rất có thể bản ghi âm được đề cập, mà tôi đã đưa ra làm ví dụ ở đầu bài viết, được thực hiện trên một máy tính như thế này:

Cách tôi khôi phục dữ liệu ở định dạng không xác định từ băng từ

Hóa ra chiếc máy tính này và các loại của nó (Model I/Model III/Model IV, v.v.) đã có một thời rất phổ biến (tất nhiên, không phải ở Nga). Điều đáng chú ý là bộ xử lý họ sử dụng cũng là Z80. Đối với máy tính này, bạn có thể tìm thấy trên Internet rất nhiều thông tin. Vào những năm 80, thông tin máy tính được phân phối ở tạp chí thời sự. Hiện tại có một số trình giả lập máy tính cho các nền tảng khác nhau.

Tôi đã tải xuống trình giả lập trs80gp và lần đầu tiên tôi có thể thấy chiếc máy tính này hoạt động như thế nào. Tất nhiên, máy tính không hỗ trợ đầu ra màu, độ phân giải màn hình chỉ 128x48 pixel nhưng có nhiều tiện ích mở rộng và sửa đổi có thể tăng độ phân giải màn hình. Ngoài ra còn có nhiều tùy chọn cho hệ điều hành cho máy tính này và các tùy chọn để triển khai ngôn ngữ BASIC (không giống như ZX Spectrum, trong một số kiểu máy thậm chí không được "chụp" vào ROM và bất kỳ tùy chọn nào cũng có thể được tải từ đĩa mềm, giống như chính hệ điều hành)

Tôi cũng tìm thấy tính thiết thực để chuyển đổi bản ghi âm thanh sang định dạng CAS, được hỗ trợ bởi trình mô phỏng, nhưng vì lý do nào đó, tôi không thể đọc bản ghi âm từ băng cassette của mình bằng cách sử dụng chúng.

Sau khi đã tìm ra định dạng tệp CAS (hóa ra chỉ là bản sao từng bit của dữ liệu từ băng mà tôi đã có trong tay, ngoại trừ tiêu đề có sự hiện diện của byte đồng bộ), tôi đã tạo một một số thay đổi đối với chương trình của tôi và có thể xuất ra tệp CAS đang hoạt động trong trình mô phỏng (TRS-80 Model III):

Cách tôi khôi phục dữ liệu ở định dạng không xác định từ băng từ

Tôi đã thiết kế phiên bản mới nhất của tiện ích chuyển đổi với khả năng tự động xác định xung đầu tiên và khoảng cách giữa các xung tham chiếu dưới dạng gói GEM, mã nguồn có sẵn tại Github.

Kết luận

Con đường chúng ta đã đi hóa ra lại là một cuộc hành trình hấp dẫn về quá khứ, và tôi rất vui vì cuối cùng mình đã tìm được câu trả lời. Trong số những thứ khác, tôi:

  • Tôi đã tìm ra định dạng lưu dữ liệu trong ZX Spectrum và nghiên cứu các quy trình ROM tích hợp để lưu/đọc dữ liệu từ băng cassette
  • Tôi đã làm quen với máy tính TRS-80 và các loại máy tính của nó, nghiên cứu hệ điều hành, xem các chương trình mẫu và thậm chí có cơ hội gỡ lỗi trong mã máy (xét cho cùng, tất cả các thuật nhớ Z80 đều quen thuộc với tôi)
  • Đã viết một tiện ích chính thức để chuyển đổi bản ghi âm sang định dạng CAS, tiện ích này có thể đọc dữ liệu mà tiện ích “chính thức” không nhận ra

Nguồn: www.habr.com

Thêm một lời nhận xét