diff options
author | Jef <jef@targetspot.com> | 2024-09-24 08:54:57 -0400 |
---|---|---|
committer | Jef <jef@targetspot.com> | 2024-09-24 08:54:57 -0400 |
commit | 20d28e80a5c861a9d5f449ea911ab75b4f37ad0d (patch) | |
tree | 12f17f78986871dd2cfb0a56e5e93b545c1ae0d0 /Src/Plugins/Input/in_cdda/DAEPlay.cpp | |
parent | 537bcbc86291b32fc04ae4133ce4d7cac8ebe9a7 (diff) | |
download | winamp-20d28e80a5c861a9d5f449ea911ab75b4f37ad0d.tar.gz |
Initial community commit
Diffstat (limited to 'Src/Plugins/Input/in_cdda/DAEPlay.cpp')
-rw-r--r-- | Src/Plugins/Input/in_cdda/DAEPlay.cpp | 423 |
1 files changed, 423 insertions, 0 deletions
diff --git a/Src/Plugins/Input/in_cdda/DAEPlay.cpp b/Src/Plugins/Input/in_cdda/DAEPlay.cpp new file mode 100644 index 00000000..30495ab9 --- /dev/null +++ b/Src/Plugins/Input/in_cdda/DAEPlay.cpp @@ -0,0 +1,423 @@ +#include "DAEPlay.h" +#include "api__in_cdda.h" +#include "../nu/AutoWide.h" +#include <strsafe.h> + +int DAEPlay::getTrackInfo() +{ + CDROM_TOC tableOfContents = {0}; + DWORD tocSize = sizeof(tableOfContents); + + if (!DeviceIoControl(hDrive, IOCTL_CDROM_READ_TOC, NULL, 0, &tableOfContents, tocSize, &tocSize, 0)) + { + return 1; + } + + for (int i = tableOfContents.FirstTrack - 1 ; i < tableOfContents.LastTrack ; i += 1) + { + if (i == g_track) + { + track_length = MSFToBlocks(tableOfContents.TrackData[i+1].Address) - (start_address = MSFToBlocks(tableOfContents.TrackData[i].Address)); + return 0; + } + } + + return 1; +} + +DAEPlay::CDTextArray* DAEPlay::getCDText() +{ + // if we've already cached it, return asap + if (cd_text != 0) + { + return cd_text; + } + + CDROM_TOC tableOfContents = {0}; + DWORD tocSize = sizeof(tableOfContents), returned = 0; + + if (!DeviceIoControl(hDrive, IOCTL_CDROM_READ_TOC, NULL, 0, &tableOfContents, tocSize, &tocSize, 0)) + { + // issue accessing drive + return (cd_text = (CDTextArray *)-1); + } + + // MMC-3 Draft Revision 10g: Table 222 Q Sub-channel control field + tableOfContents.TrackData[0].Control &= 5; + if (!(tableOfContents.TrackData[0].Control == 0/* || tableOfContents.TrackData[0 - 1].Control == 1*/)) + { + // invalid format + return (cd_text = (CDTextArray *)-1); + } + + CDROM_READ_TOC_EX tableOfContentsEx = {0}; + tableOfContentsEx.Format = CDROM_READ_TOC_EX_FORMAT_CDTEXT; + WORD tocSizeEx = 0; + // Read the contents to get the size of the actual data + if (!DeviceIoControl(hDrive, IOCTL_CDROM_READ_TOC_EX, &tableOfContentsEx, sizeof(tableOfContentsEx), &tocSizeEx, sizeof(tocSizeEx), &returned, 0)) + { + // issue accessing drive + return (cd_text = (CDTextArray *)-1); + } + + // The bytes are swapped so we need to switch them around + tocSizeEx = ((tocSizeEx>>8) | (tocSizeEx<<8)) + sizeof(tocSizeEx); + + // Allocate a buffer for reading the actual CD Text data block + char *pCDTextData = new char[tocSizeEx]; + if (!pCDTextData) + { + return (cd_text = (CDTextArray *)-1); + } + + memset(pCDTextData, 0, tocSizeEx); + + // Now read the data + if(!DeviceIoControl(hDrive, IOCTL_CDROM_READ_TOC_EX, &tableOfContentsEx, sizeof(tableOfContentsEx), pCDTextData, tocSizeEx, &returned, 0)) + { + delete []pCDTextData; + return (cd_text = (CDTextArray *)-1); + } + + tocSizeEx = (WORD)(returned - sizeof(CDROM_TOC_CD_TEXT_DATA)); + + if(!tocSizeEx) + { + delete []pCDTextData; + return (cd_text = (CDTextArray *)-1); + } + + // This is the stuff we really need. It's an array of packs with the data (mostly text) + CDROM_TOC_CD_TEXT_DATA_BLOCK* pCDTextBlock = ((CDROM_TOC_CD_TEXT_DATA*)(BYTE*)pCDTextData)->Descriptors; + + // As we go through the packets we'll store the strings in this array. The strings are often in different packets and need to be concatenated together. + cd_text = new CDTextArray[CD_TEXT_NUM_PACKS]; + UINT m_nGenreCode = -1; + + // Loop through all the packets extracting the data we need. Each packet starts with a packet type, the track number, language code, + // character offset, and character size. The packets end with a 2 byte CRC. We don't need most of this stuff to display the info. We can get + // the packets for any track and they'll have all the data for each track (it's duplicated). + for( ;tocSizeEx >= sizeof(CDROM_TOC_CD_TEXT_DATA_BLOCK); tocSizeEx -= sizeof(CDROM_TOC_CD_TEXT_DATA_BLOCK), pCDTextBlock++) + { + if (pCDTextBlock->TrackNumber > tableOfContents.LastTrack) // Track is beyond what is on the disc + continue; + + // Only looking for CD Text item packets + if ((pCDTextBlock->PackType -= 0x80) >= 0x10) + continue; + + // Genre is encoded with the code and the supplemental text in the same packet + if (m_nGenreCode == -1 && pCDTextBlock->PackType == CD_TEXT_GENRE) + { + // TODO + m_nGenreCode = pCDTextBlock->Text[0]*16 + pCDTextBlock->Text[1]; + /*CString Text = !pCDTextBlock->Unicode + ? CString(CStringA((CHAR*)pCDTextBlock->Text+2, CD_TEXT_FIELD_LENGTH-2)) + : CString(CStringW((WCHAR*)pCDTextBlock->WText+2, CD_TEXT_FIELD_LENGTH-2)); + cd_text[pCDTextBlock->PackType][0] = Text;*/ + } + else + { + // Parse the text. There could be more than one item in the text block separated by null bytes so we need to check the whole thing + // The text is in a block of up to 12 characters. We'll just keep adding to our buffers until we go through all of the packets. + int nLengthRemaining = CD_TEXT_FIELD_LENGTH; + UINT nTrack = pCDTextBlock->TrackNumber; + UINT nOffset = 0; + // We're at the end of text when: + // Used up 12 chars + // Got to a null + // On the last track + while (nTrack <= tableOfContents.LastTrack && nLengthRemaining > 0 && nOffset < CD_TEXT_FIELD_LENGTH) + { + wchar_t *text = (wchar_t*)calloc(nLengthRemaining + 1, sizeof(wchar_t)); + if (!text) continue; + + lstrcpyn(text, (pCDTextBlock->Unicode ? pCDTextBlock->WText + nOffset : AutoWide((char *)pCDTextBlock->Text + nOffset)), nLengthRemaining + 1); + + if (!cd_text[pCDTextBlock->PackType][nTrack]) + cd_text[pCDTextBlock->PackType][nTrack] = (wchar_t*)calloc(lstrlen(text) + 1, sizeof(wchar_t)); + else // TODO error handling + cd_text[pCDTextBlock->PackType][nTrack] = (wchar_t*)realloc(cd_text[pCDTextBlock->PackType][nTrack], (lstrlen(cd_text[pCDTextBlock->PackType][nTrack]) + lstrlen(text) + 1) * sizeof(wchar_t)); + + if (text[0]) + lstrcat(cd_text[pCDTextBlock->PackType][nTrack], text); + + nOffset += lstrlen(text) + 1; + nLengthRemaining = nLengthRemaining - lstrlen(text) - 1; + ++nTrack; + free(text); + } + } + } while (0); + + delete []pCDTextData; + return cd_text; +} + +int DAEPlay::threadProc2() +{ + while (1) + { + if (need_seek != -1) + { + current_sector = ((need_seek * CD_BLOCKS_PER_SECOND) / 1000) / DEF_SECTORS_PER_READ; + bytes_in_sbuf = 0; + line.outMod->Flush(need_seek); + need_seek = -1; + } + + if (fillBuffer(killswitch)) + { + PostMessage(line.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + return 0; + } + + if (!bytes_in_sbuf && !killswitch) + { + //wait for output to be finished + line.outMod->Write(NULL, 0); + while (!killswitch && line.outMod->IsPlaying()) Sleep(10); + if (!killswitch) + { + CloseHandle(hDrive); + hDrive = INVALID_HANDLE_VALUE; + PostMessage(line.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + } + return 0; + } + + if (killswitch) + { + CloseHandle(hDrive); + hDrive = INVALID_HANDLE_VALUE; + return 0; + } + + char sample_buffer[576*4*2] = {0}; + int bytes = sizeof(sample_buffer) / 2; // enough room for dsp bullcrap + bytes = min((int)bytes_in_sbuf, (int)bytes); + memcpy(sample_buffer, sbuf, bytes); + if (bytes_in_sbuf > bytes) memcpy(sbuf, sbuf + bytes, bytes_in_sbuf - bytes); + bytes_in_sbuf -= bytes; + + line.VSAAddPCMData(sample_buffer, g_nch, 16, line.outMod->GetWrittenTime()); + line.SAAddPCMData(sample_buffer, g_nch, 16, line.outMod->GetWrittenTime()); + + if (line.dsp_isactive()) + bytes = line.dsp_dosamples((short *)sample_buffer, bytes / g_nch / 2, 16, g_nch, 44100) * (g_nch * 2); + + while (line.outMod->CanWrite() < bytes && !killswitch) Sleep(66); + if (killswitch) + { + CloseHandle(hDrive); + hDrive = INVALID_HANDLE_VALUE; + return 0; + } + + line.outMod->Write(sample_buffer, bytes); + } + + CloseHandle(hDrive); + hDrive = INVALID_HANDLE_VALUE; + return 0; +} + +void DAEPlay::stop() +{ + if (hThread) + { + killswitch = 1; + WaitForSingleObject(hThread, INFINITE); + } + + line.outMod->Close(); +} + +int DAEPlay::open(wchar_t drive, int track) //called by winampGetExtendedRead +{ + g_track = track-1; + if (g_track < 0) + return 1; + + g_drive = drive; + + wchar_t CDDevice[8]=L"\\\\.\\x:"; + CDDevice[4] = drive; + + if (hDrive != INVALID_HANDLE_VALUE) + CloseHandle(hDrive); + + hDrive = CreateFile(CDDevice, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (hDrive == INVALID_HANDLE_VALUE) + return 1; + + // TODO only store the request track! + if (getTrackInfo()) + return 1; + + g_playlength = (track_length / CD_BLOCKS_PER_SECOND) * 1000; + + bytes_in_sbuf = 0; + + if (!sbuf) + sbuf = (unsigned char *)malloc(2352 * buf_size); + if (!sbuf) + return 1; + + data = new BYTE[DEF_SECTORS_PER_READ * CDROM_RAW_BYTES_PER_SECTOR]; + if (!data) + return 1; + + return 0; +} + +int DAEPlay::play(wchar_t drive, int track) //called by winamp2's normal(old) play() interface +{ + int old_drive = g_drive; + + if (open(drive, track)) return 1; + + // do this here as it helps to prevent an audio glitch on first playback and volume is set low + setvolume(a_v, a_p); + + int maxlat = line.outMod->Open(44100, g_nch, 16, -1, -1); + if (maxlat < 0) + { + g_drive = 0; + return 1; + } + + // to prevent re-spinning as we're not going to get cd-text later + // if it's not able to be obtained when first opening the device. + if (old_drive != drive) + { + if ((int)cd_text > 0) + delete []cd_text; + cd_text = NULL; + getCDText(); + } + + line.SetInfo(1411, 44, g_nch, 1); + line.SAVSAInit(maxlat, 44100); + line.VSASetInfo(g_nch, 44100); + line.is_seekable = 1; + + bytes_in_sbuf = 0; + current_sector = 0; + killswitch = 0; + DWORD thread_id = 0; + hThread = CreateThread(NULL, NULL, &threadProc, (LPVOID)this, NULL, &thread_id); + SetThreadPriority(hThread, AGAVE_API_CONFIG->GetInt(playbackConfigGroupGUID, L"priority", THREAD_PRIORITY_HIGHEST)); + + //open the device thru MCI (for getfileinfo to work properly) + g_playtrack = track; + + return 0; +} + +int DAEPlay::fillBuffer(int kill) +{ + if (!kill && bytes_in_sbuf <= 0) + { + if (current_sector < (track_length / DEF_SECTORS_PER_READ)) + { + // Contains an offset into the CD-ROM disc where data will be read. You can calculate this offset by multiplying the starting sector number for the request times 2048. + RAW_READ_INFO _RawReadInfo = {0}; + _RawReadInfo.DiskOffset.QuadPart = ((current_sector * DEF_SECTORS_PER_READ) + start_address) * CDROM_COOKED_BYTES_PER_SECTOR; + _RawReadInfo.TrackMode = CDDA; + _RawReadInfo.SectorCount = DEF_SECTORS_PER_READ; + + DWORD data_length = DEF_SECTORS_PER_READ * CDROM_RAW_BYTES_PER_SECTOR; + if (!DeviceIoControl(hDrive, IOCTL_CDROM_RAW_READ, &_RawReadInfo, sizeof(_RawReadInfo), + data, data_length, &data_length, 0)) + { + CloseHandle(hDrive); + hDrive = INVALID_HANDLE_VALUE; + return 1; + } + + // TODO make sure the buffer size is enough for our needs + memcpy(sbuf, data, DEF_SECTORS_PER_READ * CDROM_RAW_BYTES_PER_SECTOR); + bytes_in_sbuf += DEF_SECTORS_PER_READ * CDROM_RAW_BYTES_PER_SECTOR; + } + current_sector++; + } + return 0; +} + +int DAEPlay::read(char *dest, int len, int *killswitch) //called by winampGetExtendedRead_getData +{ + int l = 0; + + // TODO make sure this will handle a CD ejection... + while (l < len && !*killswitch) + { + if (fillBuffer(*killswitch)) + { + return -1; + } + + if (!bytes_in_sbuf) break; + + int bytes = min(bytes_in_sbuf, len - l); + memcpy(dest + l, sbuf, bytes); + + if (bytes_in_sbuf > bytes) memcpy(sbuf, sbuf + bytes, bytes_in_sbuf - bytes); + bytes_in_sbuf -= bytes; + + l += bytes; + } + + return l; +} + +DAEPlay::DAEPlay() +{ + sbuf = NULL; + data = NULL; + cd_text = NULL; + bytes_in_sbuf = 0; + buf_size = 64; //make it configitem + g_track = -1; + + // fix these three as that's normal + g_nch = 2; + g_srate = 44100; + g_bps = 16; + + killswitch = 0; + hDrive = INVALID_HANDLE_VALUE; + hThread = NULL; + need_seek = -1; + current_sector = 0; + start_address = 0; + track_length = 0; +} + +DAEPlay::~DAEPlay() +{ + if (hDrive != INVALID_HANDLE_VALUE) + { + CloseHandle(hDrive); + hDrive = INVALID_HANDLE_VALUE; + } + + if (sbuf) + { + free(sbuf); + sbuf = NULL; + } + + if (data) + { + delete []data; + data = NULL; + } + + if ((int)cd_text > 0) + { + delete []cd_text; + } + cd_text = NULL; +}
\ No newline at end of file |