Developer Assistant

------------------------------------------

Leggere i CD-Audio senza MMOS/2

Questa sezione è dedicata a quelle persone che vogliono utilizzare al massimo il proprio lettore di CD. In maniera perticolare mi occuperò della lettura delle tracce audio in formato digitale e di come si possa sapere se il proprio lettore di CD supporta questa funzione.
Purtroppo non ho abbastanza conoscenza dei formati Audio digitali e quindi sono costretto a limitare la mia trattazione a livello di utilizzo di tutte le funzionalità del lettore di CD. In modo particolare vorrei suggerire un insieme di strutture in C che ho realizzato a partire dalla documentazione di IBM che mi sembrano abbastanza versatili per qualsiasi tipo di utilizzo. Personalmente mi sono spinto oltre ed ho inglobato queste strutture in una serie di classi C++ che semplificano notevolmente l'utilizzo del CD come device. Se qualcuno fosse interessato a queste classi può contattarmi via e-mail (ancora però non sono complete).

Le DevIOCtls

Per sfruttare al massimo le capacità dei lettori di CD-ROM è necessario accedere direttamente al device driver. In OS/2 per comunicare con i device driver si utilizzano le famose DevIOCtls (cioè Device I/O controls) queste altro non fanno che inviare al driver dei comandi specifici che vengono identificati da due numeri: il primo identifica la categoria, cioè il tipo di dispositivo che si vuole controllare e il secondo identifica la funzione che si vuol ottenere.

CD DevIOCtls
Categoria Funzione Commento
80h
CD-ROM Drive & Disc
40h Reset Drive
44h Eject Disc
45h Close Tray
46h Lock/Unlock Door
50h Seek
60h Device Status
61h Identify CD-ROM Driver
63h Return Sector Size
70h Report Location of Drive Head
72h Read Long
78h Return Volume Size
79h Get UPC
81h
CD-ROM Audio
40h Set Audio-Channel Control
50h Play Audio
51h Stop Audio
52h Resume Audio
60h Return Audio-Channel Information
61h Return Audio-Disc Information
62h Return Audio-Track Information
63h Return Audio-Subchannel Q Information
65h Return Audio-Status Information

Prima di poter usare la DevIOCtls bisogna ottenere tramite la DosOpen un handle per il device. Nel caso di un lettore di CD a cui è assegnata la lettera di unità H si può lo si può ottenere scrivendo il seguente codice:

HFILE hDev;
ULONG ulAction;
APIRET rc;
	
ulAction=0; 	      	// azione intrapresa da DosOpen
rc=DosOpen( "H:",      	// Nome del device
	    &hDev,     	// puntatore all'handle
	    &ulAction,	// puntatore all'azione
	    0,		// non usato per i device
	    0,		// non usato
	    OPEN_ACTION_OPEN_IF_EXISTS,
	    OPEN_ACCESS_READONLY | OPEN_SHARE_DENYNONE
            | OPEN_FLAGS_DASD,
	    NULL);
Se rc è uguale a NO_ERROR la variabile hDev contiene un handle valido per il device.
A questo punto possiamo usare la DosDevIOCtl, vediamo quindi quali sono i parametri più importanti:
APIRET rc;

rc=DosDevIOCtl(	hDevice,	//handle per il device
		category,	//categoria
		function,	//funzione
		pParam,		
		cbParmLenMax,
		pcbParmLen,
		pData,
		cbDataLenMax,
		pcbDataLen);		
I due puntatori pParam e pData fanno riferimento a due strutture, rispettivamente, per i parametri in ingresso e per i parametri in uscita. La definizione delle strutture varia da funzione a funzione e quindi entrambi i puntatori non sono tipizzati. Gli altri quattro parametri indicano lo spazio massimo allocato per ciascuna struttura (cbParmLenMax e cbDataLenMax) e lo spazio effettivamente usato nelle strutture (pcbParmLen e pcbDataLen). Attenzione che gli ultimi due parametri sono puntatori in quanto al ritorno della funzione le due variabili puntate contengono il numero di byte della struttura effettivamente utilizzati, e, nel caso che non ci fosse spazio sufficiente, contengono il numero di byte che sarebbero necessari per completare il comando con successo.
È necessario prestare particolare attenzione ai parametri passati a questa funzione perché le strutture vengono passate direttamente al device driver ( a ring 0 !) e quindi se si sono passati dei valori non corretti si può anche piantare il sistema. Inoltre è opportuno tener presente che la maggior parte dei compilatori allinea gli elementi delle strutture in modo da ottimizzarne l'accesso, ma essendo i device driver scritti in assembler le strutture sono impacchettate senza lasciare alcuno spazio vuoto tra un elemto e l'altro. Questo problema può essere facilmente aggirato ricorrendo alla direttiva #pragma pack(1), disponibile praticamente in tutti i compilatori C/C++.

Le strutture

L'insieme di strutture da me realizzato mira a ridurre al minimo il numero di passaggi necessario per passare da una chiamata DosDevIOCtl e l'altra, a scapito magari di un maggior uso di memoria (in realtà assai limitato).
L'idea su cui si basa questo lavoro deriva dall'osservazione che alcuni gruppi di funzioni hanno strutture di parameri e dati molto simili (in genere hanno una parte comune e degli elementi specifici aggiunti). Quindi per ottenere il mio scopo ho cercato di raggruppare le funzioni e creare una struttura (composta a sua volta da altre strutture e unions) che permettesse di usare tutte le funzioni.
Ed ecco le due strutture risultanti:

#pragma pack(1)

typedef struct _CD_Param	
{
    ULONG	Signature;
    union 
    {
        struct _CD_Play_Param		Play;
        struct _CD_TrackInfo_Param	TrackInfo;
        struct _CD_LockUnLock_Param	Lock;
        struct _CD_Seek_Param		Seek;
        struct _CD_GetHeadLoc_Param	Head;
        struct _CD_ReadLong_Param	Read;
    } p;
}  CD_Param;

typedef union _CD_Data 	
{
    struct _CD_TrackInfo_Data		TrackInfo;
    struct _CD_DiskInfo_Data		DiskInfo;
    struct _CD_ChannelCtrl_Data 	ChannelCtrl;
    struct _CD_SubchannelQ_Data 	SubchannelQ;
    struct _CD_AudioStatus_Data 	AudioStatus;
    struct _CD_DeviceStatus_Data	DeviceStatus;
    struct _CD_GetDriver_Data		Driver;
    struct _CD_SectorSize_Data		Sector;
    struct _CD_GetHeadLoc_Data		Head;
    struct _CD_VolumeSize_Data		Volume;
    struct _CD_GetUPC_Data		UPC;
}  CD_Data;
						
#pragma pack()
Ogni struttura è composta da altre struttura ognuna con un nome che richiama direttamente al tipo di funzione svolta. Nella struttura CD_Param c'è una Signature comune ad ogni chiamata relativa ai CD-ROM, che deve contenere sempre la stringa "CD01" senza zero terminale (infatti sono quattro caratteri che formano appunto un ULONG e per inizializzarla si può usare la costante '10DC').
Usando l'union ogni struttura è sovrapposta a tutte le altre e quindi l'utilizzo di memoria è pari alla più grande delle strutture (nella prima c'è anche la signature). Bisogna però tener presente che se la struttura viene usata per chiamate diverse bisogna inizializzare solo la parte sovrascritta dalla chiamata precedente (ad esempio la Signature non viene mai sovrascritta e quindi può essere inizzializzata solo la prima volta che si usa la struttura).
È importante indicare la dimensione della struttura tramite l'operatore sizeof() in quanto calcolarsi a mano la dimensione della struttura può essere complicato.

Vediamo ora le singole strutture partendo da quelle relative ai parametri in ingresso e carcando di raggrupparle:


#define CDAUDIO_ADDRESS_LOGICAL	0x0000
#define CDAUDIO_ADDRESS_MSF	0x0001

#pragma pack (1)

struct _CD_Play_Param	
{
    UCHAR   AddrMode;
    ULONG   StartSector;
    ULONG   EndSector;
};

struct _CD_Seek_Param
{
    UCHAR   AddrMode;
    ULONG   StartSector;
};

struct _CD_GetHeadLoc_Param
{
    UCHAR   AddrMode;
};

struct _CD_ReadLong_Param
{
    UCHAR   AddrMode;
    USHORT  Sectors;
    ULONG   StartSector;
    UCHAR   dummy;
    UCHAR   Interleave;
};

#pragma pack()
Tutte queste strutture sono relative a funzione che devono indicare delle zone del CD.
Per indicare un settore del CD si possono usare due metodi (questo spiega la presenza del campo AddrMode e delle due macro iniziali): indirizzi logici e indirizzi in minuti, secondi e frame (MSF). Gli indirizzi logici altro non sono che il numero progressivo del settore del CD considerando che il primo settore è il numero 150 (?). Gli indirizzzi MSF invece derivano direttamente dallo standard dei CD audio e possono essere facilmente essere convertiti in indirizzi logici se si considera che ogni frame corrisponde ad un settore e che ci sono 75 frames in ogni secondo e 60 secondi in ogni minuto. Nel formato MSF il byte meno significativo contiene il numero dei frames in binario, il byte successivo il numero di secondi e il terzo byte il numero di minuti (sempre in binario), il quarto byte deve essere zero.
L'ultima struttura contiene due campi, dummy e Interleave che non sono usati e devono essere messi a zero. Invece il campo Sectors serve per specificare il numero di settori lunghi 2352 bytes che deve essere letto.

struct _CD_TrackInfo_Param	
{
    UCHAR	Track;
};

struct _CD_LockUnLock_Param
{
    UCHAR   Flag;
};
Queste due strutture sono molto semplici la prima contiene il numero della traccia in binario, la seconda se contiene il valore 00 sblocca il caricamento del CD se contiene 01 lo blocca.

Eccoci quindi alle strutture relative ai parametri restituiti dal device driver:

struct _CD_DiskInfo_Data
{
    UCHAR	FirstTrack;
    UCHAR	LastTrack;
    ULONG	LeadOutAddress;
};
					
struct _CD_TrackInfo_Data	
{
    ULONG	TrackAddr;
    UCHAR	TrackCtrl;
};

struct _CD_GetHeadLoc_Data
{
    ULONG	Location;
};
La prima struttura contiene il numero della prima e dell'ultima traccia, insieme all'ultimo indirizzo valido del CD (in formato MSF).
La seconda struttura contiene l'indirizzo della traccia in formato MSF più un campo di bit che contiene informazioni sulla traccia:

TrackCtrl
Bit Descrizione
7 0 = audio 2 canali
1 = audio 4 canali
6 0 = traccia audio
1 = traccia dati
5 0 = copia digitale proibita
1 = copia digitale permessa
4 0 = audio senza preenfasi
1 = audio con preenfasi
0-3 dati ADR della traccia

L'ultima struttura contiene l'indirizzo della posizione della testina di lettura nel formato specificato dai parametri in ingresso.

Eccoci quindi ad una serie di strutture per avere informazioni sullo stato del driver e delle operaziini in corso:

struct _CD_AudioStatus_Data
{
    USHORT  Status;
    ULONG   Start;
    ULONG   End;
};

struct _CD_DeviceStatus_Data
{
    ULONG   Status;
};

struct _CD_GetDriver_Data
{
    UCHAR   Signature[4];
};

struct _CD_SectorSize_Data
{
    USHORT  Size;
};

struct _CD_VolumeSize_Data
{
    ULONG   Size;
};

struct _CD_GetUPC_Data
{
    UCHAR   Control;
    UCHAR   UPC[7];
    UCHAR   dummy;
    UCHAR   Frame;
};
La prima struttura fornisce informazioni sullo stato della lettura di tracce audio. Il primo campo è zero se non siamo in pause, altrimenti vale uno (è una mappa di bit di cui solo il primo è definito). Il secondo campo è l'indirizzo da cui ripartire, nel caso di pause, altrimenti è l'indirzzo di partenza dell'ultimo comando di play. Il terzo campo indica l'indirizzo di termine dell'ultimo play. Tutti gli indizzi sono in formato MSF.
La seconda struttura contiene un campo di bit che indica le varie funzionalità supportate dal device o dal drive:

Status
Bit Descrizione
31 riservato; vale sempre 0
30 0 = Non è in grado di leggere settori CD-DA.
1 = è in grado di leggere settori CD-DA.
13-29 Riservati; valgono sempre 0
12 0 = Non stà leggendo tracce audio.
1 = Stà leggendo tracce audio.
11 0 = Disco inserito.
1 = Disco non inserito.
10 0 = Non è in grado di leggere settori in Modo 2.
1 = è in grado di legger settori in Modo 2
9 0 = Accetta indirizzi logici.
1 = Accetta indirizzi logici e in MSF.
8 0 = Non può elaborare il canale audio.
1 = Può elaborare il canale audio.
7 0 = Prefetcing not supported.
1 = Prefetcing supprted internally.
6 Riservato; vale sempre 0
5 0 = Non è ingrado di fare interleaving.
1 = Accetta l'interleaving ISO-9660
4 0 = Può leggere solo dati.
1 = Può leggere dati e musica.
3 0 = Solo lettura
1 = Lettura e registrazione.
2 0 = Legge solo settori di 2048 bytes.
1 = Legge settori di 2048 e 2352 bytes.
1 0 = Cassetto bloccato
1 = Cassetto sbloccato.
0 0 = Cassetto chiuso.
1 = Cassetto aperto.

La terza struttura restituisce esattamente la "signature" del driver in modo da essere sicuri che si stà lavorando su un CD.
La quarta e la quinta struttura restituiscono rispettivamente la dimensione del settore (in byte) e la dimensione, in settori, dell'intero CD.
L'ultima struttura è utile sostanzialmente per ottenere l'Universal Product Code del CD all'interno del lettore. Gli altri campi sono di scarsa utilità (o almeno io non lho capita).

Questa struttura ci fornisce informazioni sulla posizione corrente della testina durante la lettura delle tracce audio:

struct _CD_SubchannelQ_Data
{
    UCHAR   Ctrl;
    UCHAR   Track;
    UCHAR   TrackMin;
    UCHAR   TrackSec;
    UCHAR   TrackFrm;
    UCHAR   dummy;
    UCHAR   DiskMin;
    UCHAR   DiskSec;
    UCHAR   DiskFrm;
};
Il primo campo contiene le stesse informazioni che si ottengono chiedendo i dati di una traccia. Gli altri campi, mi sembra che siano autoesplicativi. I valori sono tutti in binario.

Questa truttura serve sia per assegnare che per leggere i settaggi dei canali audio:

struct _CD_ChannelCtrl_Data
{
    UCHAR   InOut0;
    UCHAR   Volume0;
    UCHAR   InOut1;
    UCHAR   Volume1;
    UCHAR   InOut2;
    UCHAR   Volume2;
    UCHAR   InOut3;
    UCHAR   Volume3;
};
La struttura contiene due parametri per ciascuno dei quattro canali: il primo indica il numero del canale letto che deve essere fatto uscire il secondo indica il volume (00-FF hex).

Questa ultima struttura è utile per trattare i settori di tipo lungo (2352 bytes):

typedef struct _CD_Sector
{
    UCHAR   Sync[12];
    UCHAR   Header[4];
    UCHAR   Data[2048];
    UCHAR   ECC[288];
} CD_Sector;

Breve descrizione di ciascuna funzione

Tutte le chiamate al driver del CD necessitano come parametro in ingresso la signature "CD01" (quando scrivo "nessun parametro" sottintendo che la signature deve essere fornita ugualmente).

CD DevIOCtls
Funzione Commento
Reset Drive Resetta il drive e il device driver. Nessun paramtro.
Eject Disc Espelle il disco. Nesssun parametro.
Close Tray Chiude il cassetto di caricamento del CD. Nessun parametro.
Lock/Unlock Door Blocca/Sblocca l'apertura del cassetto. Necessita della struttura _CD_LockUnLock_Param.
Seek Muove la testina di lettura fino alla posizione specificata. Richiede come parametro in ingresso la struttura _CD_Seek_Param.
Device Status Restituisce lo stato del drive o device. Restituisce lo stato nella struttura _CD_DeviceStatus_Data
Identify CD-ROM Driver Identifica il driver del CD. Restitisce la stringa di riconoscimento nella struttura _CD_GetDriver_Data
Return Sector Size Restituisce la dimensione del settore nella struttura _CD_SectorSize_Data.
Report Location of Drive Head Restituisce la posizione della testa di lettura nella struttura _CD_GetHeadLoc_Data nel formato specificato in _CD_GetHeadLoc_Param.
Read Long Legge a partire dall'idirizzo indicato nella struttura _CD_ReadLong_Param un numero di settori specificato nella stessa struttura. Il formato dei settori letto è rappresentato dalla struttura _CD_Sector. Bisogna fornire un'opportuna zona di memoria per contenere tutti i settori letti.
Return Volume Size Restituisce, nella struttura _CD_VolumeSize_Data, la dimensione in settori del CD nel lettore.
Get UPC Compila la struttura _CD_GetUPC_Data con i dati relativi al Cd nel lettore.
Set Audio-Channel Control Setta i canali audio in base all'informazioni nella struttura _CD_ChannelCtrl_Data. Attenzione che, questa struttura, pur essendo un dato in ingresso deve essere passata alla DosDevIOCtl come seconda struttura.
Play Audio Legge la traccia audio in base ai parametri forniti in _CD_Play_Param.
Stop Audio Interrompe la lettura audio (in realtà è una specie di pause). Nessun parametro.
Resume Audio Riprende la lettura dove era stata interrotta con lo stop. Nessun parametro.
Return Audio-Channel Information Compila la struttura _CD_ChannelCtrl_Data con i valori attuali.
Return Audio-Disc Information Compila la struttura _CD_DiskInfo_Data.
Return Audio-Track Information Compila la struttura _CD_TrackInfo_Data relativamente alla traccia specificata in _CD_TrackInfo_Param.
Return Audio-Subchannel Q Information Compila la struttura _CD_SubchannelQ_Data.
Return Audio-Status Information Compila la struttura _CD_AudioStatus_Data

---------------------------------------------------

Torna alla nostra HomePage - Pagina a cura di: Giovanni Pagni
Last modified 16-5-97