Logical Block Addressing (LBA)

Sector addresses come in several flavours: MSF times, logical block addresses, DVD physical sector numbers, and byte offsets inside image files. They look similar and are off by fixed amounts from one another, so mixing them up is the most common source of off-by-150 (or off-by-0x30000) bugs. This page defines each one and shows how to convert between them.


1. Units: sectors, frames and blocks #

  • A sector is the smallest addressable unit of a disc (see encoding).
  • On a CD, a sector is 1/75 s of playing time. In time notation it is called a frame: "frame" in mm:ss:ff means a whole sector. It is not the 588-bit small frame from the encoding page.
  • Block is the SCSI/MMC word for the same thing. Logical block emphasises that it is the unit the host addresses.

On a CD a block is 2352 bytes raw (2048 bytes of user data for Mode 1). On a DVD it is 2048 bytes.


2. MSF: minutes, seconds, frames #

CDs grew out of audio, so their native address is a time: mm:ss:ff, where

  • mm = minutes (0–99),
  • ss = seconds (0–59),
  • ff = frames (0–74), 75 per second.

Converting a time to a sector count is simple arithmetic:

sectors = (mm × 60 + ss) × 75 + ff
mm = sectors / 4500         ss = (sectors / 75) % 60         ff = sectors % 75

Example: 12:07:45 = (12 × 60 + 7) × 75 + 45 = 54,570 sectors.

There are two different MSF times on a CD:

  • Absolute time (A-time; AMIN/ASEC/AFRAME in Q): counted from the start of the program area. Data sector headers carry this time, and drives seek with it.
  • Relative time (MIN/SEC/FRAME in Q): counted within a track, from its index 1. It counts down through the pregap. Only players use it.

When a document just says "MSF address", it means absolute time.

On the disc (in data sector headers and in the Q subchannel) MSF values are BCD: each decimal digit takes one nibble, so 59 is stored as 0x59. In MMC command responses and in most image descriptors they are plain binary (59 = 0x3B). The MDS and CCD formats store binary values. The CUE format stores them as decimal text.


3. LBA: logical block address #

An LBA is just a sector number. It is what an operating system or file system uses (ISO 9660 extents are LBAs), and what MMC READ commands take.

For CDs, LBA 0 is defined to be absolute time 00:02:00, the normal start of track 1's data. The 150 sectors (2 seconds) before it are track 1's mandatory pregap and are addressed as LBA −150 to −1:

LBA = (mm × 60 + ss) × 75 + ff − 150          (for mm < 90; see §4)
MSF of LBA n:  total = n + 150;  mm = total / 4500;  ss = (total / 75) % 60;  ff = total % 75
Absolute MSF LBA What's normally there
00:00:00 −150 Start of program area; track 1 pregap (index 0)
00:01:74 −1 Last pregap sector
00:02:00 0 Track 1, index 1. ISO 9660's "logical sector 0"
00:02:16 16 ISO 9660 Primary Volume Descriptor
74:00:00 332,850 Typical end of a 74-min disc
79:59:74 359,849 Last sector before 80:00:00

Why the 2-second offset? The Red Book requires every disc to begin with a 2-second pause, and data standards chose not to give user data addresses inside it. Many image formats begin at LBA 0 and do not store the first 150 sectors. A reader re-creates them as silence or zero-filled sectors.


4. Negative addresses and the 90-minute rule #

The lead-in comes before 00:00:00, so its sectors need negative LBAs. MSF has no sign. Instead, the Q subchannel of the lead-in counts up through MSF values in the 90s and wraps from 99:59:74 to 00:00:00 where the program area begins. The convention (used by MMC and by libmirage) is:

Any MSF with minutes ≥ 90 is treated as negative: subtract 100 minutes (450,000 sectors).

if mm >= 90:  LBA = (mm × 60 + ss) × 75 + ff − 150 − 450000

libmirage's helpers, in lib/cdemu/libmirage/mirage/utils.c:

gint mirage_helper_msf2lba (guint8 m, guint8 s, guint8 f, gboolean diff) {
    gint lba = (m*60+s)*75 + f;
    if (diff) lba -= 150;          // "diff" = absolute address, apply the 2-second offset
    if (m >= 90) lba -= 450000;    // lead-in: wrap negative
    return lba;
}
void mirage_helper_lba2msf (gint lba, gboolean diff, guint8 *m, guint8 *s, guint8 *f) {
    if (diff) lba += 150;
    if (lba < 0) lba += 450000;
    *m = lba/(75*60);  *s = (lba/75) % 60;  *f = lba % 75;
}

The diff flag separates addresses, which need the ±150, from lengths, which don't. Forgetting this distinction is a classic bug.

Examples:

MSF Interpretation LBA
99:59:74 last lead-in sector −151
97:26:10 inside a typical lead-in −11,690
90:00:00 earliest possible lead-in time −45,150

This is also why discs longer than about 90 minutes are trouble. The program area of a 99-minute disc runs into minute values that drives may interpret as negative lead-in addresses.

CloneCD's ALBA and PLBA fields are these LBAs, and can be negative (an ALBA of −150, or lead-in values, are common). Aaru's CloneCD writer uses a slightly different wrap rule (if lba > 405000 then lba = −(lba − 405000 + 300)). Readers should accept both conventions.


5. Track-relative, session-relative and file-relative addresses #

Several formats store addresses relative to something other than the start of the disc:

Where Address type Zero point
Q subchannel MIN/SEC/FRAME relative time index 1 of the current track (counts down in the pregap)
CUE INDEX nn mm:ss:ff file-relative sector count, written as MSF start of the current FILE. No 150 offset: INDEX 01 00:00:00 is byte 0 of the file
CUE PREGAP / POSTGAP length n/a
MDS "PLBA" (data block 24h) absolute LBA 00:02:00
MDS session start/end absolute LBA 00:02:00 (first session starts at −150)
MDS track start offset (28h) byte offset in the MDF start of the MDF file
CCD PLBA, ALBA absolute LBA (from PMSF/AMSF) 00:02:00
CCD [TRACK n] INDEX k LBA 00:02:00 (libmirage notes it may be session-relative on later sessions)
ISO 9660 extent locations logical sector number LBA 0 in practice. On multisession discs the newest volume descriptor is at (start of the last session's first track + 16), but extents in it are absolute disc LBAs, so files from earlier sessions can still be referenced

The CUE case trips people up. A CUE time of 03:20:15 does not mean absolute time 03:20:15. It means "sector 15,015 of this file". For a single-file BIN whose first track starts at byte 0, the absolute LBA equals that file sector number if the file includes every pregap. Any PREGAP lines (gaps not stored in the file) push later tracks further out on the disc than their file position suggests. See BIN/CUE.


6. From LBA to a byte offset in an image file #

Each format needs its own formula. With S = stored sector size in bytes:

Format Byte offset of LBA n
.iso n × 2048 (or n × S for raw ISO variants)
.img (CloneCD), single session n × 2352 (the file starts at LBA 0 and includes all later pregaps)
.sub (CloneCD) n × 96
.img, multisession as above, but subtract the lead-out + lead-in + pregap gap(s) between sessions, which are not stored
.bin (single file, all gaps stored as INDEX 00) n × S if every track has the same S. Otherwise sum length × S track by track
.bin (one file per track) (n − track_start_lba_in_file) × S within that track's file
.mdf track.start_offset + (n − track.PLBA) × S for the track containing n (S includes 96 bytes when subchannel is stored)

The MDF formula is the reliable one, because the MDS gives every track's start offset explicitly. Do not assume the MDF is a contiguous dump from LBA 0: some pregaps are omitted (see MDS/MDF).


7. DVD addresses: PSN and LBA #

DVD sectors carry a 24-bit Physical Sector Number (PSN) in their ID field (encoding §6.1). It is binary, not BCD. The data area of layer 0 begins at PSN 0x030000, which is LBA 0. Lead-in sectors have smaller PSNs.

Single layer, or layer 0 #

LBA = PSN − 0x30000          PSN = LBA + 0x30000

Parallel track path (PTP) dual layer #

Each layer has its own data area starting at 0x030000. Layer 1 sectors are told apart by the layer bit in the ID's sector information byte. LBAs continue from the end of layer 0.

Opposite track path (OTP) dual layer #

Layer 1's PSNs are the 24-bit complement of layer 0's at the same radius. If layer 0's data area ends at E0 (stored in the PFI as "end sector of layer 0"):

L0_sectors   = E0 − 0x30000 + 1
L1_first_PSN = ~E0 & 0xFFFFFF          (= 0xFFFFFF − E0)

For LBA n:
  if n < L0_sectors:  PSN = n + 0x30000                         (layer 0)
  else:               PSN = L1_first_PSN + (n − L0_sectors)     (layer 1)

Worked example, Empire Earth III (values from the PFI in its MDS; see MDS/MDF):

Quantity Value
Data area start 0x030000
End of layer 0, E0 0x151CDF
Layer 0 sectors 0x151CDF − 0x030000 + 1 = 0x121CE0 = 1,187,040
Layer 1 first PSN 0xFFFFFF − 0x151CDF = 0xEAE320
End of data area (last L1 PSN) 0xFCFFEF
Layer 1 sectors 0xFCFFEF − 0xEAE320 + 1 = 0x121CD0 = 1,187,024
Total (LBAs 0 … 2,374,063) 2,374,064
Layer break between LBA 1,187,039 and LBA 1,187,040
Check: last LBA → PSN 0xEAE320 + 1,187,023 = 0xFCFFEF ✓

The layers differ by 16 sectors (one ECC block). In an image the sectors are simply stored in LBA order, layer 1 straight after layer 0. The layer break position is metadata, kept in the PFI (MDS) or in a sidecar file (.dvd files used with ImgBurn and similar tools). A plain ISO loses it.


8. How the host asks for an address (MMC) #

  • READ(10)/READ(12)/READ CD take a 32-bit LBA. READ CD also accepts negative LBAs, which lets some drives read the track 1 pregap (−150..−1) and sometimes a little of the lead-in.
  • READ CD MSF takes absolute MSF start and end times (binary, not BCD).
  • READ TOC/PMA/ATIP returns addresses either as LBAs or as MSF (the "MSF" bit in the command picks which). Formats 0 and 1 give track start addresses. The Full TOC format always gives MSF (binary) values, the same values MDS and CCD store.
  • For DVD, READ DISC STRUCTURE returns the PFI with data area start, end and end-of-layer-0 as 24-bit PSNs (stored in 4-byte fields).

9. Quick reference #

sectors(m,s,f)        = (m*60 + s)*75 + f
LBA(abs MSF)          = sectors − 150,   and − 450000 more if m ≥ 90
abs MSF(LBA)          = from (LBA + 150), adding 450000 first if negative
CUE INDEX time        = file-relative sector count; no 150
DVD LBA(PSN, L0)      = PSN − 0x30000
DVD OTP L1 start PSN  = 0xFFFFFF − end_of_layer0_PSN
74 min = 333,000 sectors;  80 min = 360,000 sectors;  99:59:74 = 449,999 sectors

Sources #