[.net2] lecture fichier .cda + extraction piste audio d'un cd

Description

Tout est dans le titre. Démo sur l'extraction sans dll/ocx externes des pistes audio d'un CD.
Donc cette source se divise en 3 classes.
-Classe CdaInfo //Classe fournissant des méthodes de lecture des fichiers .cda (sur le CD audio).
-Classe ExtractAudioTrack //Classe permettant l'extraction des pistes audio d'un CD.
-Classe ExtractTrackEventArgs //Classe fournissant les données pour l'événement Progression.

Le code est commenté...blindé de commentaires pour les non initiés.
Les méthodes sont simples, la démo fait appel à toute les possibilités offertes par les classes.

Voila, pas grand chose d'autres dire, je vous laisse la surprise d'ouvrir la source :)
J'espère que vous apprécierez ;)

Source / Exemple :


using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;

namespace ExtractCDA
{
    /// <summary>
    /// Classe permettant l'extraction des pistes audio d'un CD.
    /// </summary>
    public class ExtractAudioTrack
    {
        const int IOCTL_CDROM_READ_TOC = 0x24000;
        const int IOCTL_CDROM_RAW_READ = 0x2403E;
        const int RAW_SECTOR_SIZE = 0x930;
        const int LARGEST_SECTORS_PER_READ = 27;
        const uint GENERIC_READ = 0x80000000;
        const int FILE_SHARE_READ = 1;
        const int OPEN_EXISTING = 3;
        const int FILE_ATTRIBUTE_NORMAL = 0x80;

        /// <summary>
        /// Evénement pour la progression de l'extraction de piste.
        /// </summary>
        public event ExtractTrackEventHandler Progression;

        /// <summary>
        /// Extrait les données d'une piste audio dans un fichier wav.
        /// </summary>
        /// <param name="track">Structure TrackInfo contenant les informations sur à la piste à extraire.</param>
        /// <param name="device">Lettre du lecteur CD.</param>
        /// <param name="OutputFile">Fichier de sortie.</param>
        /// <remarks>S'execute dans un thread.</remarks>
        public void ExtractOneTrack(TrackInfo track, string device, string OutputFile)
        {
            Thread th = new Thread(new ParameterizedThreadStart(ThProcOne));

            th.Priority = ThreadPriority.Lowest;
            th.Start(new object[] { track, device, OutputFile, 1, 1 });
        }

        /// <summary>
        /// Extrait les données de plusieurs pistes audios dans des fichiers wav.
        /// </summary>
        /// <param name="tracks">Tableau de TrackInfo contenant les informations sur chaque piste à extraire.</param>
        /// <param name="device">Lettre du lecteur CD.</param>
        /// <param name="OutputDir">Dossier de sortie.</param>
        /// <remarks>S'execute dans un thread.</remarks>
        public void ExtrackTracks(TrackInfo[] tracks, string device, string OutputDir)
        {
            Thread th = new Thread(new ParameterizedThreadStart(ThProcTracks));

            th.Priority = ThreadPriority.Lowest;
            th.Start(new object[] { tracks, device, OutputDir });
        }

        /// <summary>
        /// Méthode chargée d'extraire les pistes audios sélectionnée les une après les autres.
        /// </summary>
        /// <param name="values">Tableau de paramètres contenant un tableau de TrackInfo,
        /// lettre du lecteur de sortie et le dossier de sortie des fichiers.</param>
        private void ThProcTracks(object values)
        {
            //Renseigne les variables utilisées.
            object[] param = (object[])values;

            TrackInfo[] tracks = (TrackInfo[])param[0];
            string device = param[1].ToString();
            string output = param[2].ToString();

            //Itération sur le tableau de TrackInfo
            for (short i = 0; i < tracks.Length; i++)
                ThProcOne(new object[] { tracks[i], device, Path.Combine(output, tracks[i].Name + ".wav"), i + 1, (short)tracks.Length });
        }

        /// <summary>
        /// Méthode chargée d'extraire les données d'une piste audio.
        /// </summary>
        /// <param name="values">Tableau de paramètres contenant un tableau de TrackInfo,
        /// lettre du lecteur de sortie, fichier de sortie,piste actuelle,nombre de pistes totale.</param>
        private void ThProcOne(object values)
        {
            object[] param = (object[])values;

            TrackInfo track = (TrackInfo)param[0];              //Information sur la piste.
            string device = param[1].ToString();                //Lettre lecteur CD.
            string output = param[2].ToString();                //Fichier de sortie.
            short num = Convert.ToInt16(param[3]);              //Piste en cours.
            short totaltracks = Convert.ToInt16(param[4]);      //Nombre de pistes.

            CDROM_TOC toc = new CDROM_TOC();                    //Info. sur la TOC du CD.
            WAVECHUNKHEADER chunk = new WAVECHUNKHEADER();      //Structure écrivant les en-tetes dans le fichier wav.
            WAVEFORMATEX fmt = new WAVEFORMATEX();              //Format du fichier wav.
            

            //Ouvre un flux en écriture sur le fichier de sortie.
            FileStream fs = new FileStream(output, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read);
            BinaryWriter bw = new BinaryWriter(fs);

            //En-tête RIFF.
            chunk.ChunkId = "RIFF".ToCharArray();
            chunk.ChunkSize = 0;

            //Tableau de bytes (buffer) recevant notre structure RIFF et lui alloue de la mémoire.
            int buffsize = Marshal.SizeOf(typeof(WAVECHUNKHEADER));
            byte[] buffer = new byte[buffsize];
            IntPtr pbuff = Marshal.AllocHGlobal(buffsize);

            //Copie notre structure dans un notre buffer.
            Marshal.StructureToPtr(chunk, pbuff, true);
            Marshal.Copy(pbuff, buffer, 0, buffer.Length);

            //Ecrit notre structure d'en-tête "RIFF" dans le fichier
            bw.Write(buffer);

            //Suivi du tag WAV.
            bw.Write("WAVE".ToCharArray());

            
            //En-tête fmt.
            chunk.ChunkId = "fmt ".ToCharArray();
            chunk.ChunkSize = Marshal.SizeOf(fmt);

            //Copie notre en-tête "fmt " dans le buffer.
            Marshal.StructureToPtr(chunk, pbuff, true);
            Marshal.Copy(pbuff, buffer, 0, buffer.Length);

            //Ecrit la structure.
            bw.Write(buffer);

            //Structure format WAV.
            fmt.wFormatTag = 1;
            fmt.nChannels = 2;
            fmt.nSamplesPerSec = 44100;
            fmt.nAvgBytesPerSec = 176400;
            fmt.nBlockAlign = 4;
            fmt.wBitsPerSample = 16;

            //Redimensionne notre buffer et lui alloue de la mémoire.
            buffsize = Marshal.SizeOf(typeof(WAVEFORMATEX));
            buffer = new byte[buffsize];
            pbuff = Marshal.AllocHGlobal(buffsize);

            //Copie notre structure WAVEFORMATEX dans le buffer.
            Marshal.StructureToPtr(fmt, pbuff, true);
            Marshal.Copy(pbuff, buffer, 0, buffer.Length);

            //Ecrit la structure.
            bw.Write(buffer);

            //Position dans le fichier pointant vers l'en-tête "data" (contient la taille des données).
            int pos_datasize = (int)bw.BaseStream.Position;

            //En-tête data + définie la taille des données à 0.
            chunk.ChunkId = "data".ToCharArray();
            chunk.ChunkSize = 0;

            ////Redimensionne notre buffer et lui alloue de la mémoire.
            buffsize = Marshal.SizeOf(typeof(WAVECHUNKHEADER));
            buffer = new byte[buffsize];
            pbuff = Marshal.AllocHGlobal(buffsize);

            //Copie notre structure "data" dans le buffer.
            Marshal.StructureToPtr(chunk, pbuff, true);
            Marshal.Copy(pbuff, buffer, 0, buffer.Length);

            //Ecrit la structure.
            bw.Write(buffer);

            int sector = LARGEST_SECTORS_PER_READ;          //Curseur sur la piste.
            int pos = track.StartTrackHSG;                  //Position de début de la piste.
            uint bytesread = 0;                             //Nombres d'octets lut.

            buffsize = RAW_SECTOR_SIZE * sector;            //Taille du buffer recevant les données brut.
            buffer = new byte[buffsize];                    //Buffer pour les données brut.
            pbuff = Marshal.AllocHGlobal(buffsize);         //Alloue un espace mémoire pour le buffer.

            //Ouverture d'un descripteur sur le lecteur CD.
            IntPtr hdev = NativeMethods.CreateFile(@"\\.\" + device, GENERIC_READ, FILE_SHARE_READ, IntPtr.Zero,
                OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, IntPtr.Zero);

            //Allocation mémoire pour recevoir la structure CDROM_TOC.
            IntPtr ptoc = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(CDROM_TOC)));

            //Lecture de la TOC du CD.
            //Si lecture OK ont poursuit.
            bool bret = NativeMethods.DeviceIoControl(hdev, IOCTL_CDROM_READ_TOC, IntPtr.Zero, 0, ptoc, (uint)Marshal.SizeOf(typeof(CDROM_TOC)), ref bytesread, IntPtr.Zero);

            if (bret)
            {
                int rrisize = Marshal.SizeOf(typeof(RAW_READ_INFO));        //Taille de la structure RAW_READ_INFO
                int lengthdata = 0;                                         //Total données lut.
                int endpos = track.EndTrackHSG - track.StartTrackHSG;       //Secteur de fin de la piste.

                //Itération du secteur de début jusqu'au secteur de fin de la piste.
                while (pos + sector < track.EndTrackHSG)
                {
                    RAW_READ_INFO rri = new RAW_READ_INFO();        //Contient les infos sur la lecture des données dans la piste.

                    rri.DiskOffset.LowPart = pos * 2048;            //Position sur le bloc des données brut à récupérer.
                    rri.SectorCount = sector;                       //Nombre de secteur à lire.
                    rri.TrackMode = TRACK_MODE_TYPE.CDDA;           //De type CDDA.

                    //Alloue un espace mémoire afin de permettre à l'API DeviceIoControl d'accéder à la structure rri(RAW_READ_INFO).
                    GCHandle gch = GCHandle.Alloc(rri, GCHandleType.Pinned);
                    IntPtr prri = gch.AddrOfPinnedObject();

                    //Lecture des données.
                    bret = NativeMethods.DeviceIoControl(hdev, IOCTL_CDROM_RAW_READ, prri, (uint)Marshal.SizeOf(rri), pbuff,
                        (uint)(buffsize), ref bytesread, IntPtr.Zero);

                    //Si lecture OK écriture de celles-ci dans le fichier.
                    if (bret)
                    {
                        Marshal.Copy(pbuff, buffer, 0, buffsize);

                        bw.Write(buffer);

                        lengthdata += buffer.Length;    //Taille des données déjà lues.
                        pos += sector;                  //Position dans la piste.
                    }
                    else
                    //Sinon libère le handle (descripteur) sur le lecteur.
                    //Et sort de la boucle.
                    {
                        NativeMethods.CloseHandle(hdev);
                        pos = track.EndTrackHSG;
                    }

                    //Libère la mémoire réservée par notre structure rri.
                    gch.Free();

                    //Lève l'événement Progression et fournie des informations sur la progression de la piste.
                    if (Progression != null)
                        Progression(new ExtractTrackEventArgs(track.Name, output, num, totaltracks, pos - track.StartTrackHSG, endpos));
                }

                //Libère le handle sur le lecteur.
                NativeMethods.CloseHandle(hdev);

                //Re-écrit l'en-tête "data" avec la taille des données brutes écrites.
                chunk.ChunkId = "data".ToCharArray();
                chunk.ChunkSize = lengthdata;

                buffsize = Marshal.SizeOf(typeof(WAVECHUNKHEADER));
                pbuff = Marshal.AllocHGlobal(buffsize);

                Marshal.StructureToPtr(chunk, pbuff, true);
                buffer = new byte[buffsize];
                Marshal.Copy(pbuff, buffer, 0, buffer.Length);

                //Se repositionne et récrit à l'offset de l'en-tête "data".
                bw.BaseStream.Seek(pos_datasize, SeekOrigin.Begin);
                bw.Write(buffer);

                //Idem pour l'en-tête RIFF avec la taille total du fichier.
                chunk.ChunkId = "RIFF".ToCharArray();
                chunk.ChunkSize = (int)bw.BaseStream.Length - 4;

                Marshal.StructureToPtr(chunk, pbuff, true);
                Marshal.Copy(pbuff, buffer, 0, buffer.Length);

                bw.Seek(0, SeekOrigin.Begin);
                bw.Write(buffer);

                //Ferme le handle sur le fichier wav.
                bw.Close();
                fs.Close();

                //Libère l'objet fs.
                fs.Dispose();
            }
        }
    }
}

Conclusion :


Voila,
A vos plumes, laissez vos commentaires. :)
N'hésitez pas à noter aussi.

sa fait toujours plaisir ;)
Merci.

Codes Sources

A voir également

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.