Technical documentation for libdvdread and libdvdcss DVD-Audio extensions
GSoC 2025 Project Documentation
The following is a brief set of notes on notable additions and behaviors for users of the new DVD-Audio API for libdvdread and libdvdcss.
These extensions allow for minimal modification to the existing API while providing full DVD-Audio support including IFO reading, path structures, and decryption capabilities.
Two new functions were added for opening DVD-Audio discs:
Opens a DVD-Audio disc for reading
Opens a DVD-Audio stream for reading
Using these functions instead of the standard DVDOpen functions will store an internal state that allows the behavior of all subsequent calls to be modified for DVD-Audio. This includes which IFOs are opened and how they're read, the path names, and the decryption methods used for the dvdinput functions.
This approach allowed for as little modification as possible to the existing API.
DVDOpenFile has been extended to work for both DVD-Audio and DVD-Video with the desired behavior in both cases, with the following limitations:
In addition, ifoOpenVMGI and ifoOpenVTSI will open their respective audio versions if DVDOpenAudio is used instead of DVDOpen to get the dvd_reader struct, along with ifoRead_TT and ifoRead_TIF.
Information taken from the Discwelder Chrome Manual: available here for reference
Each DVD-Audio disc represents an Album. Inside this album, there are several Titlesets or "Groups". Each Group/Titleset contains several Titles. Titles contain Tracks and can be different Audio format types. For example, one title could be LPCM, one could be MLP, DTS, etc., possibly of the same tracks.
I've also seen a layout where some titles are "extras" or other similar information. Inside Titles, along with the tracks, there can be "trackpoints". Trackpoints are areas in the disc that the author would like the user to be able to seek to automatically. These trackpoints can be assigned their own still images as well. I have seen this be used to display "Lyrics" (each set of lyrics with its own trackpoints).
No official specifications are released, and information may be flimsy in some areas.
IFOs are files that describe the structure of a DVD disc and what is contained within it so that it can be read properly by a DVD-Reader.
DVD-Audio IFOs are different and contain different tables and layouts. For this reason, the IFO struct has been placed in a union struct: one for DVD-Audio IFO type and one for DVD-Video, with a new "ifo_format" member to determine the type of IFO.
/* format type will be used to reduce amount of code refactoring, hopefully add some
* detection mechanism later as well.
*/
typedef struct {
union{
struct{
/* VMGI */
vmgi_mat_t *vmgi_mat;
tt_srpt_t *tt_srpt;
pgc_t *first_play_pgc;
ptl_mait_t *ptl_mait;
vts_atrt_t *vts_atrt;
txtdt_mgi_t *txtdt_mgi;
/* Common */
pgci_ut_t *pgci_ut;
c_adt_t *menu_c_adt;
vobu_admap_t *menu_vobu_admap;
/* VTSI */
vtsi_mat_t *vtsi_mat;
vts_ptt_srpt_t *vts_ptt_srpt;
pgcit_t *vts_pgcit;
vts_tmapt_t *vts_tmapt;
c_adt_t *vts_c_adt;
vobu_admap_t *vts_vobu_admap;
};
struct{
/* SAMG */
samg_mat_t *samg_mat;
/* AMGI */
amgi_mat_t *amgi_mat;
tracks_info_table_t *info_table_first_sector;
tracks_info_table_t *info_table_second_sector;
/* ATSI */
atsi_mat_t *atsi_mat;
atsi_title_table_t *atsi_title_table;
};
};
ifo_format_t ifo_format;
} ifo_handle_t;
This approach allowed for the least amount of code refactoring, since all future open functions will use the same type. The following new IFO structs have been documented here, with some fields removed or modified based on errors spotted and new information gathered from examples.
DVD-Audio contains two main audio managers: AUDIO_TS.IFO along with its ATS (Audio Title Set) IFOs and AUDIO_PP.IFO. According to the unofficial documentation, AUDIO_PP.IFO is used for simple DVD-Audio players, and it seems that this IFO file alone is sufficient to describe the disc structure. Though, it seems this cannot be used to play menus with video linking on complex discs.
typedef struct {
char samg_identifier[12];
uint16_t nr_chapters;
uint16_t specification_version;
samg_chapter_t *samg_chapters;
} ATTRIBUTE_PACKED samg_mat_t;
#define SAMG_MAT_SIZE 16U
| Field | Description |
|---|---|
samg_identifier |
Generally all files on a DVD contain a 12-character identifier that describes the name of the file |
nr_chapters |
All tables should contain a header that describes how to read the table, usually with some indicator of its size (how to allocate the arrays inside it) |
specification_version |
A specifications version is in all IFO files and will likely be 0x0012 |
typedef struct {
uint16_t zero_1;
uint8_t group_num;
uint8_t chapter_num; /* chapter number within group*/
uint32_t timestamp_pts; /* this is MPEG time, Not DVD time */
uint32_t chapter_len;
uint32_t zero_2;
uint8_t record_code;
uint8_t bit_depth;
uint8_t sampling_rate;
uint8_t nr_channels;
/* some DVDs made with authoring software keep downmix coefficients here */
/* since I do not have samples of commercial discs that do this, I will not include it */
uint8_t zero_3[20];
uint32_t start_sector_1; /*aob start sector*/
uint32_t start_sector_2; /*aob start sector is repeated again */
uint32_t end_sector;
} ATTRIBUTE_PACKED samg_chapter_t;
#define SAMG_CHAPTER_SIZE 52U
SAMG chapters are like "Tracks", but according to unofficial documentation also seem to contain video "chapters", under the condition that chapter_len is set to zero. Video linking behavior has not yet been written due to no existing documentation and seems to be tied to menu integration. For this reason, this struct is not the one used in the VLC submodule.
| Field | Description |
|---|---|
group_num |
Each group has one IFO and can contain one or more AOB files. Each entry here describes where to find the track (what specific address in the group to access). Groups in libdvdread are opened as one block (all the AOBs in one go) |
start_sector_1/2 |
The fact that the start address is repeated twice is not a mistake. It is likely there is a reason for this, that there may be some special case where the second value describes something slightly different with a different offset, though for now this is unknown |
Information on the nature of the Audio (nr_channels, bit_depth) seems unnecessary and is deduced from the Audio packets by the demuxer.
/* Downmix coefficients can be used to reduce 5.1 channels to stereo in DVD-Audio Discs */
/** Downmix equations
* Left_out = Lf_left * Lf
* + Rf_left * Rf
* + C_left * C
* + LFE_left * LFE
* + Ls_left * Ls
* + Rs_left * Rs;
*
* Right_out = Lf_right * Lf
* + Rf_right * Rf
* + C_right * C
* + LFE_right * LFE
* + Ls_right * Ls
* + Rs_right * Rs;
*
* Where:
* - Lf, Rf, C, LFE, Ls, Rs are the 5.1 input channels
* - Left_out, Right_out are the stereo output channels
* - Each coefficient (e.g. Lf_left, C_right) is an 8-bit gain factor
*/
typedef struct {
/* it seems each entry is started and ended with padding */
uint16_t zero_1;
/* each coefficient corresponds to stereo side for a channel in 5.1 */
uint8_t Lf_left;
uint8_t Lf_right;
uint8_t Rf_left;
uint8_t Rf_right;
uint8_t C_left;
uint8_t C_right;
uint8_t LFE_left;
uint8_t LFE_right;
uint8_t Ls_left;
uint8_t Ls_right;
uint8_t Rs_left;
uint8_t Rs_right;
uint16_t zero_2;
} ATTRIBUTE_PACKED downmix_coeff_t;
#define DOWNMIX_COEFF_SIZE 16U
Downmix coefficients can be set in commercial authoring software and are used to turn 5.1 channel audio into stereo. They currently are not used anywhere. Discs authored with Discwelder Chrome II have downmix coefficients in place of what is listed as padding in the SAMG chapters, with some padding. Discwelder-generated discs, however, should not be considered standard, and since I have seen no officially authored disc that does this, I have not included it. This should not affect behavior, only this zero check may fail in the case of a Discwelder-authored disc.
/**
* AMGI
*
* The following structures relate to the Audio Manager, exclusive to DVD-Audio discs
*/
/**
* Audio Manager Information Management Table.
*/
typedef struct {
char amg_identifier[12];
uint32_t amg_start_sector;
uint8_t zero_1[12];
uint32_t amgi_last_sector;
uint16_t specification_version;
uint8_t zero_2[4];
uint16_t amg_nr_of_volumes;
uint16_t amg_this_volume_nr;
uint8_t disc_side;
uint8_t zero_3[4];
uint8_t autoplay;
uint32_t audio_sv_ifo_relative_p;
uint16_t unknown_1; /* some discs have a value here, undocumented in DVD-Audio specs */
uint8_t zero_4[8];
uint8_t vmg_nr_of_title_sets; /* Number of video titlesets in audio zone. */
uint8_t amg_nr_of_title_sets; /* Number of audio titlesets in audio zone. */
uint8_t unknown_2[32]; /* may be set to zeros */
uint8_t unknown_3[8]; /* may be set to zeros */
uint8_t zero_5[24];
uint32_t amg_end_byte_address;
uint8_t unknown_4[4]; /* may be set to zeros */
uint8_t zero_6[56];
uint16_t menu_prescence_1; /* may be set to zero, or some other value, optional field*/
uint8_t unknown_5[4];
uint16_t unknown_6; /* should be 0x01 */
uint8_t zero_7[2];
uint16_t amg_nr_of_zones; /* may be set to 0x02*/
uint8_t zero_8[2];
uint16_t menu_prescence_2; /* may be set to 0x03*/
uint8_t zero_9[48];
uint8_t last_sector_audio_sys_space;
uint8_t zero_10[79];
uint8_t menu_prescence_3; /* will be set to 0x01*/
/* XXX lots of padding after this to complete the sector*/
} ATTRIBUTE_PACKED amgi_mat_t;
#define AMGI_MAT_SIZE 337U
The AMGI_MAT is one of the structures contained within AUDIO_TS.IFO. It very closely resembles VMGI_MAT in DVD-Video and, according to the unofficial documentation, contains a lot of the same fields. These fields seem to be used for video linking and menus. I would not consider these fields to be set in stone; there seem to be inaccuracies.
the unknown_1 field is not in the unofficial specifications and is instead a part of the padding bellow it, as is written. However, Some values were discovered here on commercial discs so it was best to leave it as an unknown field.
/* Sector 2 may have video tracks, sector 3 will not. If there are no video tracks the tables will be the same*/
/*this struct repeats for every audio or video track*/
typedef struct {
uint8_t type_and_rank; /*first nibble is type, second rank, from 1-9*/
uint8_t nr_chapters_in_title;
uint8_t nr_visible_chapters_in_vts_title; /* this will be zeros in an audio record */
uint8_t zero_1;
uint32_t len_audio_zone_pts; /* this is MPEG time, Not DVD time */
uint8_t group_property; /* in a video track, this is video titleset number, in audio its rank of group */
uint8_t title_property; /* in video track this is title number , in audio track this is rank of title*/
uint32_t ts_pointer_relative_sector; /* for ats or vts*/
} ATTRIBUTE_PACKED track_info_t;
#define TRACK_INFO_SIZE 14U
typedef struct {
uint16_t nr_of_titles;
uint16_t last_byte_in_table;
track_info_t *tracks_info;
} ATTRIBUTE_PACKED tracks_info_table_t;
#define TRACKS_INFO_TABLE_SIZE 4U
There are two of these tables, and if there is no video linking, they will be identical. The second table contains only the audio titles on the disc (on the AUDIO_TS side), so it is ideal for simple audio title playback. On its own, these tables are not enough to play DVD-Audio discs but must be used to open the ATS IFOs in order to access track-specific information.
| Field | Description |
|---|---|
track_info_t |
This structure will be used for both Video and Audio tracks. All the same structure. For this reason, some fields (group_property, title_property) are a little ambiguous. |
typedef struct {
/* 0x0000 for lpcm or 0x0100 for mlp, DTS seems to be 0x0500 and has different fields*/
uint8_t encoding;
uint8_t unknown1;
/* for stereo 0f 16 bit, 1f 20 bit, 2f 24 bit*/
/* f is a placeholder for the nibbles that is used for channel groups*/
/* otherwise if its 5.1 channels each nibble represents a channel group (G1, G2),*/
uint8_t bitrate;
/* 8f 44khz, 0f 48khz, 1f 96khz, 2f 192khz*/
/* otherwise if its 5.1 channels each nibble represents a channel group (G1, G2)*/
uint8_t sampling_frequency;
/* 00 1 channel, 01 2 channels, 11 5.1 channels*/
uint8_t nr_channels;
uint8_t unknown2;
uint8_t zero[10];
} ATTRIBUTE_PACKED atsi_record_t;
#define ATSI_RECORD_SIZE 16U
/**
* ATS
*
* Structures relating to the Audio Title Set (ATS).
*/
/**
* Audio Title Set Information Management Table. Exclusive to DVD-Audio discs
*/
#define ATSI_RECORD_MAX_SIZE 8
#define DOWNMIX_COEFF_MAX_SIZE 16
typedef struct {
char ats_identifier[12];
uint32_t ats_last_sector; /* last sector of ATS_XX_0.BUP*/
uint8_t zero_1[12];
uint32_t atsi_last_sector; /* last sector of ATS_XX_0.IFO*/
uint16_t specification_version;
uint32_t unknown_1;
uint8_t zero_2[90];
uint32_t atsi_last_byte;
uint8_t zero_3[60];
uint32_t vtsm_vobs; /* may be zeros */
uint32_t atst_aobs; /* atst or vtst */
uint32_t vts_ptt_srpt; /* may be zeros */
uint32_t ats_pgci_ut; /* sector */
uint32_t vtsm_pgci_ut; /* may be zeros */
uint32_t vts_tmapt; /* sector */
uint32_t vtsm_c_adt; /* sector */
uint32_t vtsm_vobu_admap; /* sector */
uint32_t vts_c_adt; /* sector */
uint32_t vts_vobu_admap; /* sector */
uint8_t zero_4[24];
/* the majority, or even all of these entries may be zero*/
atsi_record_t atsi_record[ATSI_RECORD_MAX_SIZE];
downmix_coeff_t downmix_coefficients[DOWNMIX_COEFF_MAX_SIZE];
} ATTRIBUTE_PACKED atsi_mat_t;
#define ATSI_MAT_SIZE 640U
This is the head of the ATS IFOs, and it once again resembles its DVD-Video counterpart. (Only the tables seem to be new additions.)
| Field | Description |
|---|---|
atsi_record_t |
This seems to describe the available formats in the Audio Titleset. Once again, this information is not needed and is automatically deduced by the demuxer from the AOB sectors. |
typedef struct {
uint16_t unknown_1; /* appears to be index, +0x100 for each iter*/
uint16_t unkown_2; /* either 0x0000 or 0x0100*/
uint32_t offset_record_table;
} ATTRIBUTE_PACKED atsi_title_index_t;
#define ATSI_TITLE_INDEX_SIZE 8U
/* this will be repeated for each track in each title*/
typedef struct {
uint16_t unknown_1; /* will be 0x0000*/
uint16_t unknown_2; /* will be 0x0000*/
uint8_t track_number_in_title;
uint8_t unknown_3; /* will be 0x00*/
uint32_t first_pts_of_track; /* this is MPEG time, Not DVD time */
uint32_t length_pts_of_track; /* this is MPEG time, Not DVD time */
uint8_t zero[6];
} ATTRIBUTE_PACKED atsi_track_timestamp_t;
#define ATSI_TRACK_TIMESTAMP_SIZE 20U
/* this will come after all of the track timestamps, a set of 12-byte sector pointer records. One for each track*/
typedef struct {
uint32_t unknown_1; /* will be 0x01000000*/
uint32_t start_sector; /* relative to first AOB file*/
uint32_t end_sector; /* relative to first end of AOB file */
} ATTRIBUTE_PACKED atsi_track_pointer_t;
#define ATSI_TRACK_POINTER_SIZE 12U
typedef struct {
uint16_t unknown_1; /* will be 0x0000*/
uint8_t nr_tracks; /* unsure if this holds up for other files*/
uint8_t nr_pointer_records; /* unsure if this holds up for other files*/
uint32_t length_pts; /* this is MPEG time, Not DVD time */
uint16_t unknown_3; /* will be 0x0000*/
uint16_t unknown_4; /* will be 0x0010*/
uint16_t start_sector_pointers_table; /* pointer to start of sector pointers table, relative to start of title record*/
uint16_t unknown_5; /* will be 0x0000*/
atsi_track_timestamp_t *atsi_track_timestamp_rows; /*length determined by nr_tracks*/
atsi_track_pointer_t *atsi_track_pointer_rows;
} ATTRIBUTE_PACKED atsi_title_record_t;
#define ATSI_TITLE_ROW_TABLE_SIZE 16U
typedef struct {
uint16_t nr_titles;
uint16_t zero_1;
uint32_t last_byte_address;
atsi_title_index_t *atsi_index_rows; /* length determined by nr_titles*/
atsi_title_record_t *atsi_title_row_tables; /* length determined by nr_titles*/
} ATTRIBUTE_PACKED atsi_title_table_t;
#define ATSI_TITLE_TABLE_SIZE 8U
This is likely the hardest table to read and is the main table used in playback. Its structure is somewhat bizarre, and there are fields and even row types that are not documented anywhere and that only appear in certain discs. (I assume they are related to menus.) Due to the index offset records used to navigate the table, this does not cause any issues though.
Some information on this is available at: https://code.videolan.org/videolan/libdvdread/-/issues/22
The structure should be clarified since it is not as it is described in the unofficial specifications
In atsi_title_record_t, the unofficial specifications mistakingly includes an extra unknown field. This was removed since no discs display this
| Structure | Description |
|---|---|
atsi_title_table_t |
This is the main table and contains two arrays: one with the index offsets, which corresponds to the number of titles, and another array of tables that is also determined by the number of titles, atsi_title_row_tables. This is an array of tables with different types of rows/records. |
atsi_title_record_t |
This table describes the title track layout. It contains two dynamically allocated arrays. These two are allocated based off different fields, and this I suspect is because of trackpoints. nr_tracks is used to determine the size of the timestamp rows and nr_pointer_records determines the size of the pointer rows. This was clarified from analysis of a hex data dump of some discs. It seems that the size of the pointer rows includes, in addition to tracks, trackpoints that can be seeked to. |
Along with all of this, there is a series of new IFO Prints and Reads for these structures. All methods of initially selecting an IFO file to open should also set the ifo_format.
A new header file is provided for the dvdinput functions. These reuse the dvdcss struct but ignore the fields related exclusively to CSS encryption and use their own internal CPXM struct for decryption.
In order to properly decrypt the audio data, libdvdcss needs access to the disc content (this will be explained later). libdvdcss however can not read files.
For this reason, this task is delegated to libdvdread; dvdcpxm_init will be fed a pointer to a byte array of this file through functions in libdvdread. The reason it had to be done this way was because the initial libdvdcpxm library was written for Windows, and on Windows optical drives are mounted and can be inspected as such. For other operating systems, they may remain devices. To make this functionality OS-agnostic, libdvdread is used for the reading part, and the parsing and processing is done in libdvdcss.
CPXM (the X being a placeholder variable that can either be R or P) encryption was created by the 4C entity, with the specifications for the format as well as DVD-Audio encryption in particular available at this source with specifically the following "C2 Block Cipher Specification, Revision 1.0", "CPPM Specification, Introduction and Common Cryptographic Elements" and "CPPM Specification, DVD Book, Revision 0.93" being seemingly the main sources for DVD-Audio discs
Based off these specifications, libdvdcpxm was written by a user of the DOOM9 forum named "paradynamic" and was originally distributed here, the core decryption logic and imports (which exist in the files libdvdcpxm.c, libdvdcpxm.h and dvdcpxm.h) was borrowed from this library and integrated into libdvdcss. It seems originally this library borrowed some logic from libdvdcss, such as the ioctl calls, and the USB disc drive validation which is necessary for use with a computer (this is the same system used in CSS encryption). Details on this are available here in section 2.4
A similar DVD-Audio plugin exists for foobar available here https://sourceforge.net/projects/dvdadecoder/, written by the user Maxim V.Anisiutkin with a slightly reworked libdvdcpxm in cpp that has partially implemented ioctl calls in macOS, Linux for CPRM, as well as a "watermark detector" and the ability to view album art.
Should be used regardless, as they will be the same
An additional step that did not exist previously in the dvdcss methods. This initialization of CPXM discs needed to be split into two: one before the disc was actually opened, and one after.
This uses libdvdcpxm decrypt functions in place of css_unscramble, which utilizes the C2 cipher
Standard seek, removed all of the additional behavior inside of dvdcss functions that is unnecessary
As mentioned before, dvdcpxm_init is a second initialization step that is needed after dvdcss_open, this is because this encryption method needs to read a file that exists within the disc called the MKB (The Media Key Block) in order to initialize the internal "cpxm" struct added to dvdcss that is used for decryption
typedef struct cpxm
{
uint64_t media_key;
uint64_t id_album;
uint64_t id_media;
uint64_t vr_k_te;
} cpxm;
This struct has its values filled by dvdcpxm_init, the important members here are id_album and media_key. As listed in the 4C entity specifications, these two keys are used on each encrypted block of audio data in order to decipher it. media_key is produced from processing the MKB. These two keys are then used in conjunction with keys stored in the first 128 bytes of each sector of AOB data to crack the encryption. id_media and vr_k_te are related to CPRM, which may not be fully implemented at the moment.
the main export from libdvdcpxm is dvdcpxm_decrypt, this is what has been used to build the new dvdpcpxm header for libdvdcss
CPRM is included due to its inclusion in the libdvdcpxm library, though it is untested due to lack of material, and it is likely that very few DVD-Audio discs use this scheme (if any). Though if there were such a disc, this library "may" be able to decrypt it.