| 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
 | 
|---|