1
/*  This file is part of the KDE project.
2
3
Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4
5
This library is free software: you can redistribute it and/or modify
6
it under the terms of the GNU Lesser General Public License as published by
7
the Free Software Foundation, either version 2.1 or 3 of the License.
8
9
This library is distributed in the hope that it will be useful,
10
but WITHOUT ANY WARRANTY; without even the implied warranty of
11
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
GNU Lesser General Public License for more details.
13
14
You should have received a copy of the GNU Lesser General Public License
15
along with this library.  If not, see <http://www.gnu.org/licenses/>.
16
*/
17
18
#include "qaudiocdreader.h"
19
#include <dshow.h>
20
#include <initguid.h>
21
22
#include <winioctl.h> // needed for FILE_DEVICE_CD_ROM etc
23
24
#define IOCTL_CDROM_READ_TOC    CTL_CODE(FILE_DEVICE_CD_ROM, 0x0000, METHOD_BUFFERED, FILE_READ_ACCESS)
25
#define IOCTL_CDROM_RAW_READ    CTL_CODE(FILE_DEVICE_CD_ROM, 0x000F, METHOD_OUT_DIRECT,  FILE_READ_ACCESS)
26
27
QT_BEGIN_NAMESPACE
28
29
#ifndef QT_NO_PHONON_MEDIACONTROLLER
30
31
namespace Phonon
32
{
33
    namespace DS9
34
    {
35
        // {CA46BFE1-D55B-4adf-B803-BC2B9AD57824}
36
        DEFINE_GUID(IID_ITitleInterface, 
37
            0xca46bfe1, 0xd55b, 0x4adf, 0xb8, 0x3, 0xbc, 0x2b, 0x9a, 0xd5, 0x78, 0x24);
38
39
        struct TRACK_DATA {
40
            UCHAR Reserved;
41
            UCHAR Control : 4;
42
            UCHAR Adr : 4;
43
            UCHAR TrackNumber;
44
            UCHAR Reserved1;
45
            UCHAR Address[4];
46
        };
47
48
        struct CDROM_TOC {
49
            UCHAR Length[2];
50
            UCHAR FirstTrack;
51
            UCHAR LastTrack;
52
            TRACK_DATA TrackData[100];
53
        };
54
55
        struct WaveStructure
56
        {
57
            WaveStructure();
58
59
            char riff[4];
60
            qint32 chunksize;
61
            char wave[4];
62
            char fmt[4];
63
            const qint32 chunksize2;
64
            const quint16 formatTag;
65
            const quint16 nChannels;
66
            const quint32 nSamplesPerSec; 
67
            const quint32 nAvgBytesPerSec;
68
            const quint16 nBlockAlign;
69
            const quint16 bitsPerSample;
70
            char data[4];
71
            qint32 dataLength;
72
        };
73
74
        enum TRACK_MODE_TYPE {
75
            YellowMode2,
76
            XAForm2,
77
            CDDA
78
        };
79
80
81
        struct RAW_READ_INFO {
82
            LARGE_INTEGER DiskOffset;
83
            ULONG    SectorCount;
84
            TRACK_MODE_TYPE TrackMode;
85
        };
86
87
        class QAudioCDReader : public QAsyncReader, public ITitleInterface
88
        {
89
        public:
90
            QAudioCDReader(QBaseFilter *parent, QChar drive = QChar());
91
            ~QAudioCDReader();
92
93
            //reimplementation from IUnknown
94
            STDMETHODIMP_(ULONG) AddRef();
95
            STDMETHODIMP_(ULONG) Release();
96
97
            STDMETHODIMP Length(LONGLONG *,LONGLONG *);
98
            STDMETHODIMP QueryInterface(REFIID iid, void** out);
99
            QList<qint64> titles() const;
100
101
        protected:
102
            HRESULT read(LONGLONG pos, LONG length, BYTE *buffer, LONG *actual);
103
104
        private:
105
            HANDLE m_cddrive;
106
            CDROM_TOC m_toc;
107
            WaveStructure m_waveHeader;
108
            qint64 m_trackAddress;
109
        };
110
111
112
#define SECTOR_SIZE 2352
113
#define NB_SECTORS_READ 20
114
115
        static const AM_MEDIA_TYPE audioCDMediaType = { MEDIATYPE_Stream, MEDIASUBTYPE_WAVE, TRUE, FALSE, 1, GUID_NULL, 0, 0, 0};
116
 
117
        int addressToSectors(UCHAR address[4])
118
        {
119
            return ((address[0] * 60 + address[1]) * 60 + address[2]) * 75 + address[3] - 150;
120
        }
121
122
        WaveStructure::WaveStructure() : chunksize(0), chunksize2(16), 
123
            formatTag(WAVE_FORMAT_PCM), nChannels(2), nSamplesPerSec(44100), nAvgBytesPerSec(176400), nBlockAlign(4), bitsPerSample(16),
124
            dataLength(0)
125
        {
126
            qMemCopy(riff, "RIFF", 4);
127
            qMemCopy(wave, "WAVE", 4);
128
            qMemCopy(fmt,  "fmt ", 4);                    
129
            qMemCopy(data, "data", 4);
130
        }
131
132
133
        QAudioCDReader::QAudioCDReader(QBaseFilter *parent, QChar drive) : QAsyncReader(parent, QVector<AM_MEDIA_TYPE>() << audioCDMediaType)
134
        {
135
            //now open the cd-drive
136
            QString path; 
137
            if (drive.isNull()) {
138
                path = QString::fromLatin1("\\\\.\\Cdrom0"); 	 
139
            } else { 	 
140
                path = QString::fromLatin1("\\\\.\\%1:").arg(drive); 	 
141
            }
142
143
            m_cddrive = ::CreateFile((const wchar_t *)path.utf16(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
144
145
            qMemSet(&m_toc, 0, sizeof(CDROM_TOC));
146
            //read the TOC
147
            DWORD bytesRead = 0;
148
            bool tocRead = ::DeviceIoControl(m_cddrive, IOCTL_CDROM_READ_TOC, 0, 0, &m_toc, sizeof(CDROM_TOC), &bytesRead, 0);
149
150
            if (!tocRead) {
151
                qWarning("unable to load the TOC from the CD");
152
                return;
153
            }
154
155
            m_trackAddress = addressToSectors(m_toc.TrackData[0].Address);
156
            const qint32 nbSectorsToRead = (addressToSectors(m_toc.TrackData[m_toc.LastTrack + 1 - m_toc.FirstTrack].Address) 
157
                - m_trackAddress);
158
            const qint32 dataLength = nbSectorsToRead * SECTOR_SIZE;
159
160
            m_waveHeader.chunksize = 4 + (8 + m_waveHeader.chunksize2) + (8 + dataLength);
161
            m_waveHeader.dataLength = dataLength;
162
        }
163
164
        QAudioCDReader::~QAudioCDReader()
165
        {
166
            ::CloseHandle(m_cddrive);
167
        }
168
169
        STDMETHODIMP_(ULONG) QAudioCDReader::AddRef()
170
        {
171
            return QAsyncReader::AddRef();
172
        }
173
174
        STDMETHODIMP_(ULONG) QAudioCDReader::Release()
175
        {
176
            return QAsyncReader::Release();
177
        }
178
179
180
        STDMETHODIMP QAudioCDReader::Length(LONGLONG *total,LONGLONG *available)
181
        {
182
            const LONGLONG length = sizeof(WaveStructure) + m_waveHeader.dataLength;
183
            if (total) {
184
                *total = length;
185
            }
186
            if (available) {
187
                *available = length;
188
            }
189
190
            return S_OK;
191
        }
192
193
        STDMETHODIMP QAudioCDReader::QueryInterface(REFIID iid, void** out)
194
        {
195
            if (!out) {
196
                return E_POINTER;
197
            }
198
199
            if (iid == IID_ITitleInterface) {
200
                //we reroute that to the pin
201
                *out = static_cast<ITitleInterface*>(this);
202
                AddRef();
203
                return S_OK;
204
            } else {
205
                return QAsyncReader::QueryInterface(iid, out);
206
            }
207
        }
208
209
210
        HRESULT QAudioCDReader::read(LONGLONG pos, LONG length, BYTE *buffer, LONG *actual)
211
        {
212
            LONG nbRead = 0;
213
214
            if (actual) {
215
                *actual = 0;
216
            }
217
218
            if (pos < sizeof(WaveStructure)) {
219
                //we first copy the content of the structure
220
                nbRead = qMin(LONG(sizeof(WaveStructure) - pos), length);
221
                qMemCopy(buffer, reinterpret_cast<char*>(&m_waveHeader) + pos, nbRead);
222
            }
223
224
            const LONGLONG posInTrack = pos - sizeof(WaveStructure) + nbRead;
225
            const int bytesLeft = qMin(m_waveHeader.dataLength - posInTrack, LONGLONG(length - nbRead));
226
227
            if (bytesLeft > 0) {
228
229
                //we need to read again
230
231
                const int surplus = posInTrack % SECTOR_SIZE; //how many bytes too much at the beginning
232
                const int firstSector = posInTrack / SECTOR_SIZE, 
233
                    lastSector = (posInTrack + length - 1) / SECTOR_SIZE;
234
                const int sectorsNeeded = lastSector - firstSector + 1;
235
                int sectorsRead = 0;
236
237
                QByteArray ba(sectorsNeeded * SECTOR_SIZE, 0);
238
239
240
                RAW_READ_INFO ReadInfo;
241
                ReadInfo.TrackMode = CDDA; // Always use CDDA (numerical: 2)
242
                ReadInfo.DiskOffset.QuadPart = (m_trackAddress + firstSector) * 2048;
243
                ReadInfo.SectorCount = qMin(sectorsNeeded - sectorsRead, NB_SECTORS_READ);
244
                while (ReadInfo.SectorCount) {
245
                    DWORD dummy = 0;
246
                    if (::DeviceIoControl( m_cddrive, IOCTL_CDROM_RAW_READ,
247
                        &ReadInfo, sizeof(ReadInfo),
248
                        ba.data() + sectorsRead * SECTOR_SIZE,
249
                        ReadInfo.SectorCount * SECTOR_SIZE,
250
                        &dummy, NULL ) )
251
                    {
252
                        ReadInfo.DiskOffset.QuadPart += ReadInfo.SectorCount * 2048;
253
                        sectorsRead += ReadInfo.SectorCount;
254
                        ReadInfo.SectorCount = qMin(sectorsNeeded - sectorsRead, NB_SECTORS_READ);
255
                    }else {
256
                        qWarning("an error occurred while reading from the media");
257
                        return S_FALSE;
258
                    }
259
260
                }
261
262
                //consume bytes on the buffer
263
                qMemCopy(buffer + nbRead, ba.data() + surplus, bytesLeft);
264
265
                //at this point we have all we need in the buffer
266
                nbRead += bytesLeft;
267
            }
268
269
            if (actual) {
270
                *actual = nbRead;
271
            }
272
273
            return nbRead == length ? S_OK : S_FALSE;
274
        }
275
276
        QList<qint64> QAudioCDReader::titles() const
277
        {
278
            QList<qint64> ret;
279
            ret << 0;
280
            for(int i = m_toc.FirstTrack; i <= m_toc.LastTrack ; ++i) {
281
                const uchar *address = m_toc.TrackData[i].Address;
282
                ret << ((address[0] * 60 + address[1]) * 60 + address[2]) * 1000 + address[3]*1000/75 - 2000;
283
284
            }
285
            return ret;
286
        }
287
288
289
        QAudioCDPlayer::QAudioCDPlayer() : QBaseFilter(CLSID_NULL)
290
        {
291
            new QAudioCDReader(this);
292
        }
293
294
        QAudioCDPlayer::~QAudioCDPlayer()
295
        {
296
        }
297
298
        STDMETHODIMP QAudioCDPlayer::QueryInterface(REFIID iid, void** out)
299
        {
300
            if (iid == IID_ITitleInterface) {
301
                //we reroute that to the pin
302
                return pins().first()->QueryInterface(iid, out);
303
            } else {
304
                return QBaseFilter::QueryInterface(iid, out);
305
            }
306
        }
307
    }
308
}
309
310
#endif //QT_NO_PHONON_MEDIACONTROLLER
311
312
QT_END_NAMESPACE