/* * GSM 06.10 codec handling * Copyright (C) 2009 Maarten Lankhorst * * Based on msg711.acm * Copyright (C) 2002 Eric Pouech * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ #include "config.h" #include #include #include #include #ifdef HAVE_GSM_GSM_H #include #elif defined(HAVE_GSM_H) #include #endif #include "windef.h" #include "winbase.h" #include "wingdi.h" #include "winuser.h" #include "winnls.h" #include "mmsystem.h" #include "mmreg.h" #include "msacm.h" #include "msacmdrv.h" #include "wine/debug.h" WINE_DEFAULT_DEBUG_CHANNEL(gsm); #ifdef SONAME_LIBGSM static void *libgsm_handle; #define FUNCPTR(f) static typeof(f) * p##f FUNCPTR(gsm_create); FUNCPTR(gsm_destroy); FUNCPTR(gsm_option); FUNCPTR(gsm_encode); FUNCPTR(gsm_decode); #define LOAD_FUNCPTR(f) \ if((p##f = dlsym(libgsm_handle, #f)) == NULL) { \ dlclose(libgsm_handle); \ libgsm_handle = NULL; \ return FALSE; \ } /*********************************************************************** * GSM_drvLoad */ static BOOL GSM_drvLoad(void) { libgsm_handle = dlopen(SONAME_LIBGSM, RTLD_NOW); if (libgsm_handle) { LOAD_FUNCPTR(gsm_create); LOAD_FUNCPTR(gsm_destroy); LOAD_FUNCPTR(gsm_option); LOAD_FUNCPTR(gsm_encode); LOAD_FUNCPTR(gsm_decode); return TRUE; } else { ERR("Couldn't load " SONAME_LIBGSM ": %s\n", dlerror()); return FALSE; } } /*********************************************************************** * GSM_drvFree */ static LRESULT GSM_drvFree(void) { if (libgsm_handle) dlclose(libgsm_handle); return 1; } #else static LRESULT GSM_drvFree(void) { return 1; } #endif /*********************************************************************** * GSM_DriverDetails * */ static LRESULT GSM_DriverDetails(PACMDRIVERDETAILSW add) { add->fccType = ACMDRIVERDETAILS_FCCTYPE_AUDIOCODEC; add->fccComp = ACMDRIVERDETAILS_FCCCOMP_UNDEFINED; /* Details found from probing native msgsm32.acm */ add->wMid = MM_MICROSOFT; add->wPid = MM_MSFT_ACM_GSM610; add->vdwACM = 0x3320000; add->vdwDriver = 0x4000000; add->fdwSupport = ACMDRIVERDETAILS_SUPPORTF_CODEC; add->cFormatTags = 2; add->cFilterTags = 0; add->hicon = NULL; MultiByteToWideChar( CP_ACP, 0, "Microsoft GSM 6.10", -1, add->szShortName, ARRAY_SIZE( add->szShortName )); MultiByteToWideChar( CP_ACP, 0, "Wine GSM 6.10 libgsm codec", -1, add->szLongName, ARRAY_SIZE( add->szLongName )); MultiByteToWideChar( CP_ACP, 0, "Brought to you by the Wine team...", -1, add->szCopyright, ARRAY_SIZE( add->szCopyright )); MultiByteToWideChar( CP_ACP, 0, "Refer to LICENSE file", -1, add->szLicensing, ARRAY_SIZE( add->szLicensing )); add->szFeatures[0] = 0; return MMSYSERR_NOERROR; } /* Validate a WAVEFORMATEX structure */ static BOOL GSM_FormatValidate(const WAVEFORMATEX *wfx) { if (wfx->nChannels != 1) return FALSE; switch (wfx->wFormatTag) { case WAVE_FORMAT_PCM: if (wfx->wBitsPerSample != 16) { WARN("PCM wBitsPerSample %u\n", wfx->wBitsPerSample); return FALSE; } if (wfx->nBlockAlign != 2) { WARN("PCM nBlockAlign %u\n", wfx->nBlockAlign); return FALSE; } if (wfx->nAvgBytesPerSec != wfx->nBlockAlign * wfx->nSamplesPerSec) { WARN("PCM nAvgBytesPerSec %u/%u\n", wfx->nAvgBytesPerSec, wfx->nBlockAlign * wfx->nSamplesPerSec); return FALSE; } return TRUE; case WAVE_FORMAT_GSM610: if (wfx->cbSize < sizeof(WORD)) { WARN("GSM cbSize %u\n", wfx->cbSize); return FALSE; } if (wfx->wBitsPerSample != 0) { WARN("GSM wBitsPerSample %u\n", wfx->wBitsPerSample); return FALSE; } if (wfx->nBlockAlign != 65) { WARN("GSM nBlockAlign %u\n", wfx->nBlockAlign); return FALSE; } if (((const GSM610WAVEFORMAT*)wfx)->wSamplesPerBlock != 320) { WARN("GSM wSamplesPerBlock %u\n", ((const GSM610WAVEFORMAT*)wfx)->wSamplesPerBlock); return FALSE; } if (wfx->nAvgBytesPerSec != wfx->nSamplesPerSec * 65 / 320) { WARN("GSM nAvgBytesPerSec %d / %d\n", wfx->nAvgBytesPerSec, wfx->nSamplesPerSec * 65 / 320); return FALSE; } return TRUE; default: return FALSE; } return FALSE; } static const DWORD gsm_rates[] = { 8000, 11025, 22050, 44100, 48000, 96000 }; /*********************************************************************** * GSM_FormatTagDetails * */ static LRESULT GSM_FormatTagDetails(PACMFORMATTAGDETAILSW aftd, DWORD dwQuery) { static const WCHAR szPcm[]={'P','C','M',0}; static const WCHAR szGsm[]={'G','S','M',' ','6','.','1','0',0}; switch (dwQuery) { case ACM_FORMATTAGDETAILSF_INDEX: if (aftd->dwFormatTagIndex > 1) return ACMERR_NOTPOSSIBLE; break; case ACM_FORMATTAGDETAILSF_LARGESTSIZE: if (aftd->dwFormatTag == WAVE_FORMAT_UNKNOWN) { aftd->dwFormatTagIndex = 1; break; } /* fall through */ case ACM_FORMATTAGDETAILSF_FORMATTAG: switch (aftd->dwFormatTag) { case WAVE_FORMAT_PCM: aftd->dwFormatTagIndex = 0; break; case WAVE_FORMAT_GSM610: aftd->dwFormatTagIndex = 1; break; default: return ACMERR_NOTPOSSIBLE; } break; default: WARN("Unsupported query %08x\n", dwQuery); return MMSYSERR_NOTSUPPORTED; } aftd->fdwSupport = ACMDRIVERDETAILS_SUPPORTF_CODEC; switch (aftd->dwFormatTagIndex) { case 0: aftd->dwFormatTag = WAVE_FORMAT_PCM; aftd->cbFormatSize = sizeof(PCMWAVEFORMAT); aftd->cStandardFormats = ARRAY_SIZE(gsm_rates); lstrcpyW(aftd->szFormatTag, szPcm); break; case 1: aftd->dwFormatTag = WAVE_FORMAT_GSM610; aftd->cbFormatSize = sizeof(GSM610WAVEFORMAT); aftd->cStandardFormats = ARRAY_SIZE(gsm_rates); lstrcpyW(aftd->szFormatTag, szGsm); break; } return MMSYSERR_NOERROR; } /*********************************************************************** * GSM_FormatDetails * */ static LRESULT GSM_FormatDetails(PACMFORMATDETAILSW afd, DWORD dwQuery) { switch (dwQuery) { case ACM_FORMATDETAILSF_FORMAT: if (!GSM_FormatValidate(afd->pwfx)) return ACMERR_NOTPOSSIBLE; break; case ACM_FORMATDETAILSF_INDEX: afd->pwfx->wFormatTag = afd->dwFormatTag; switch (afd->dwFormatTag) { case WAVE_FORMAT_PCM: if (afd->dwFormatIndex >= ARRAY_SIZE(gsm_rates)) return ACMERR_NOTPOSSIBLE; afd->pwfx->nChannels = 1; afd->pwfx->nSamplesPerSec = gsm_rates[afd->dwFormatIndex]; afd->pwfx->wBitsPerSample = 16; afd->pwfx->nBlockAlign = 2; afd->pwfx->nAvgBytesPerSec = afd->pwfx->nSamplesPerSec * afd->pwfx->nBlockAlign; break; case WAVE_FORMAT_GSM610: if (afd->dwFormatIndex >= ARRAY_SIZE(gsm_rates)) return ACMERR_NOTPOSSIBLE; afd->pwfx->nChannels = 1; afd->pwfx->nSamplesPerSec = gsm_rates[afd->dwFormatIndex]; afd->pwfx->wBitsPerSample = 0; afd->pwfx->nBlockAlign = 65; afd->pwfx->nAvgBytesPerSec = afd->pwfx->nSamplesPerSec * 65 / 320; afd->pwfx->cbSize = sizeof(WORD); ((GSM610WAVEFORMAT*)afd->pwfx)->wSamplesPerBlock = 320; break; default: WARN("Unsupported tag %08x\n", afd->dwFormatTag); return MMSYSERR_INVALPARAM; } break; default: WARN("Unsupported query %08x\n", dwQuery); return MMSYSERR_NOTSUPPORTED; } afd->fdwSupport = ACMDRIVERDETAILS_SUPPORTF_CODEC; afd->szFormat[0] = 0; /* let MSACM format this for us... */ return MMSYSERR_NOERROR; } /*********************************************************************** * GSM_FormatSuggest * */ static LRESULT GSM_FormatSuggest(PACMDRVFORMATSUGGEST adfs) { /* some tests ... */ if (adfs->cbwfxSrc < sizeof(PCMWAVEFORMAT) || adfs->cbwfxDst < sizeof(PCMWAVEFORMAT) || !GSM_FormatValidate(adfs->pwfxSrc)) return ACMERR_NOTPOSSIBLE; /* FIXME: should do those tests against the real size (according to format tag */ /* If no suggestion for destination, then copy source value */ if (!(adfs->fdwSuggest & ACM_FORMATSUGGESTF_NCHANNELS)) adfs->pwfxDst->nChannels = adfs->pwfxSrc->nChannels; if (!(adfs->fdwSuggest & ACM_FORMATSUGGESTF_NSAMPLESPERSEC)) adfs->pwfxDst->nSamplesPerSec = adfs->pwfxSrc->nSamplesPerSec; if (!(adfs->fdwSuggest & ACM_FORMATSUGGESTF_WBITSPERSAMPLE)) { if (adfs->pwfxSrc->wFormatTag == WAVE_FORMAT_PCM) adfs->pwfxDst->wBitsPerSample = 0; else adfs->pwfxDst->wBitsPerSample = 16; } if (!(adfs->fdwSuggest & ACM_FORMATSUGGESTF_WFORMATTAG)) { switch (adfs->pwfxSrc->wFormatTag) { case WAVE_FORMAT_PCM: adfs->pwfxDst->wFormatTag = WAVE_FORMAT_GSM610; break; case WAVE_FORMAT_GSM610: adfs->pwfxDst->wFormatTag = WAVE_FORMAT_PCM; break; } } /* recompute other values */ switch (adfs->pwfxDst->wFormatTag) { case WAVE_FORMAT_PCM: adfs->pwfxDst->nBlockAlign = 2; adfs->pwfxDst->nAvgBytesPerSec = adfs->pwfxDst->nSamplesPerSec * 2; break; case WAVE_FORMAT_GSM610: if (adfs->pwfxDst->cbSize < sizeof(WORD)) return ACMERR_NOTPOSSIBLE; adfs->pwfxDst->nBlockAlign = 65; adfs->pwfxDst->nAvgBytesPerSec = adfs->pwfxDst->nSamplesPerSec * 65 / 320; ((GSM610WAVEFORMAT*)adfs->pwfxDst)->wSamplesPerBlock = 320; break; default: return ACMERR_NOTPOSSIBLE; } /* check if result is ok */ if (!GSM_FormatValidate(adfs->pwfxDst)) return ACMERR_NOTPOSSIBLE; return MMSYSERR_NOERROR; } #ifdef SONAME_LIBGSM /*********************************************************************** * GSM_StreamOpen * */ static LRESULT GSM_StreamOpen(PACMDRVSTREAMINSTANCE adsi) { int used = 1; gsm r; if (!GSM_FormatValidate(adsi->pwfxSrc) || !GSM_FormatValidate(adsi->pwfxDst)) return MMSYSERR_NOTSUPPORTED; if (adsi->pwfxSrc->nSamplesPerSec != adsi->pwfxDst->nSamplesPerSec) return MMSYSERR_NOTSUPPORTED; if (!GSM_drvLoad()) return MMSYSERR_NOTSUPPORTED; r = pgsm_create(); if (!r) return MMSYSERR_NOMEM; if (pgsm_option(r, GSM_OPT_WAV49, &used) < 0) { FIXME("Your libgsm library doesn't support GSM_OPT_WAV49\n"); FIXME("Please recompile libgsm with WAV49 support\n"); pgsm_destroy(r); return MMSYSERR_NOTSUPPORTED; } adsi->dwDriver = (DWORD_PTR)r; return MMSYSERR_NOERROR; } /*********************************************************************** * GSM_StreamClose * */ static LRESULT GSM_StreamClose(PACMDRVSTREAMINSTANCE adsi) { pgsm_destroy((gsm)adsi->dwDriver); return MMSYSERR_NOERROR; } /*********************************************************************** * GSM_StreamSize * */ static LRESULT GSM_StreamSize(const ACMDRVSTREAMINSTANCE *adsi, PACMDRVSTREAMSIZE adss) { switch (adss->fdwSize) { case ACM_STREAMSIZEF_DESTINATION: /* cbDstLength => cbSrcLength */ if (adsi->pwfxSrc->wFormatTag == WAVE_FORMAT_PCM && adsi->pwfxDst->wFormatTag == WAVE_FORMAT_GSM610) { adss->cbSrcLength = adss->cbDstLength / 65 * 640; } else if (adsi->pwfxSrc->wFormatTag == WAVE_FORMAT_GSM610 && adsi->pwfxDst->wFormatTag == WAVE_FORMAT_PCM) { adss->cbSrcLength = adss->cbDstLength / 640 * 65; } else { return MMSYSERR_NOTSUPPORTED; } return MMSYSERR_NOERROR; case ACM_STREAMSIZEF_SOURCE: /* cbSrcLength => cbDstLength */ if (adsi->pwfxSrc->wFormatTag == WAVE_FORMAT_PCM && adsi->pwfxDst->wFormatTag == WAVE_FORMAT_GSM610) { adss->cbDstLength = (adss->cbSrcLength + 639) / 640 * 65; } else if (adsi->pwfxSrc->wFormatTag == WAVE_FORMAT_GSM610 && adsi->pwfxDst->wFormatTag == WAVE_FORMAT_PCM) { adss->cbDstLength = adss->cbSrcLength / 65 * 640; } else { return MMSYSERR_NOTSUPPORTED; } return MMSYSERR_NOERROR; default: WARN("Unsupported query %08x\n", adss->fdwSize); return MMSYSERR_NOTSUPPORTED; } } /*********************************************************************** * GSM_StreamConvert * */ static LRESULT GSM_StreamConvert(PACMDRVSTREAMINSTANCE adsi, PACMDRVSTREAMHEADER adsh) { gsm r = (gsm)adsi->dwDriver; DWORD nsrc = 0; DWORD ndst = 0; BYTE *src = adsh->pbSrc; BYTE *dst = adsh->pbDst; int odd = 0; if (adsh->fdwConvert & ~(ACM_STREAMCONVERTF_BLOCKALIGN| ACM_STREAMCONVERTF_END| ACM_STREAMCONVERTF_START)) { FIXME("Unsupported fdwConvert (%08x), ignoring it\n", adsh->fdwConvert); } /* Reset the index to 0, just to be sure */ pgsm_option(r, GSM_OPT_FRAME_INDEX, &odd); /* The native ms codec writes 65 bytes, and this requires 2 libgsm calls. * First 32 bytes are written, or 33 bytes read * Second 33 bytes are written, or 32 bytes read */ /* Decode */ if (adsi->pwfxSrc->wFormatTag == WAVE_FORMAT_GSM610) { if (adsh->cbSrcLength / 65 * 640 > adsh->cbDstLength) { return ACMERR_NOTPOSSIBLE; } while (nsrc + 65 <= adsh->cbSrcLength) { /* Decode data */ if (pgsm_decode(r, src + nsrc, (gsm_signal*)(dst + ndst)) < 0) FIXME("Couldn't decode data\n"); ndst += 320; nsrc += 33; if (pgsm_decode(r, src + nsrc, (gsm_signal*)(dst + ndst)) < 0) FIXME("Couldn't decode data\n"); ndst += 320; nsrc += 32; } } else { /* Testing a little seems to reveal that despite being able to fit * inside the buffer if ACM_STREAMCONVERTF_BLOCKALIGN is set * it still rounds up */ if ((adsh->cbSrcLength + 639) / 640 * 65 > adsh->cbDstLength) { return ACMERR_NOTPOSSIBLE; } /* The packing algorithm writes 32 bytes, then 33 bytes, * and it seems to pad to align to 65 bytes always * adding extra data where necessary */ while (nsrc + 640 <= adsh->cbSrcLength) { /* Encode data */ pgsm_encode(r, (gsm_signal*)(src+nsrc), dst+ndst); nsrc += 320; ndst += 32; pgsm_encode(r, (gsm_signal*)(src+nsrc), dst+ndst); nsrc += 320; ndst += 33; } /* If ACM_STREAMCONVERTF_BLOCKALIGN isn't set pad with zeros */ if (!(adsh->fdwConvert & ACM_STREAMCONVERTF_BLOCKALIGN) && nsrc < adsh->cbSrcLength) { char emptiness[320]; int todo = adsh->cbSrcLength - nsrc; if (todo > 320) { pgsm_encode(r, (gsm_signal*)(src+nsrc), dst+ndst); ndst += 32; todo -= 320; nsrc += 320; memcpy(emptiness, src+nsrc, todo); memset(emptiness + todo, 0, 320 - todo); pgsm_encode(r, (gsm_signal*)emptiness, dst+ndst); ndst += 33; } else { memcpy(emptiness, src+nsrc, todo); memset(emptiness + todo, 0, 320 - todo); pgsm_encode(r, (gsm_signal*)emptiness, dst+ndst); ndst += 32; memset(emptiness, 0, todo); pgsm_encode(r, (gsm_signal*)emptiness, dst+ndst); ndst += 33; } nsrc = adsh->cbSrcLength; } } adsh->cbSrcLengthUsed = nsrc; adsh->cbDstLengthUsed = ndst; TRACE("%d(%d) -> %d(%d)\n", nsrc, adsh->cbSrcLength, ndst, adsh->cbDstLength); return MMSYSERR_NOERROR; } #endif /************************************************************************** * GSM_DriverProc [exported] */ LRESULT CALLBACK GSM_DriverProc(DWORD_PTR dwDevID, HDRVR hDriv, UINT wMsg, LPARAM dwParam1, LPARAM dwParam2) { TRACE("(%08lx %p %04x %08lx %08lx);\n", dwDevID, hDriv, wMsg, dwParam1, dwParam2); switch (wMsg) { case DRV_LOAD: return 1; case DRV_FREE: return GSM_drvFree(); case DRV_OPEN: return 1; case DRV_CLOSE: return 1; case DRV_ENABLE: return 1; case DRV_DISABLE: return 1; case DRV_QUERYCONFIGURE: return 1; case DRV_CONFIGURE: MessageBoxA(0, "GSM 06.10 codec", "Wine Driver", MB_OK); return 1; case DRV_INSTALL: return DRVCNF_RESTART; case DRV_REMOVE: return DRVCNF_RESTART; case ACMDM_DRIVER_NOTIFY: /* no caching from other ACM drivers is done so far */ return MMSYSERR_NOERROR; case ACMDM_DRIVER_DETAILS: return GSM_DriverDetails((PACMDRIVERDETAILSW)dwParam1); case ACMDM_FORMATTAG_DETAILS: return GSM_FormatTagDetails((PACMFORMATTAGDETAILSW)dwParam1, dwParam2); case ACMDM_FORMAT_DETAILS: return GSM_FormatDetails((PACMFORMATDETAILSW)dwParam1, dwParam2); case ACMDM_FORMAT_SUGGEST: return GSM_FormatSuggest((PACMDRVFORMATSUGGEST)dwParam1); #ifdef SONAME_LIBGSM case ACMDM_STREAM_OPEN: return GSM_StreamOpen((PACMDRVSTREAMINSTANCE)dwParam1); case ACMDM_STREAM_CLOSE: return GSM_StreamClose((PACMDRVSTREAMINSTANCE)dwParam1); case ACMDM_STREAM_SIZE: return GSM_StreamSize((PACMDRVSTREAMINSTANCE)dwParam1, (PACMDRVSTREAMSIZE)dwParam2); case ACMDM_STREAM_CONVERT: return GSM_StreamConvert((PACMDRVSTREAMINSTANCE)dwParam1, (PACMDRVSTREAMHEADER)dwParam2); #else case ACMDM_STREAM_OPEN: WARN("libgsm support not compiled in!\n"); case ACMDM_STREAM_CLOSE: case ACMDM_STREAM_SIZE: case ACMDM_STREAM_CONVERT: return MMSYSERR_NOTSUPPORTED; #endif case ACMDM_HARDWARE_WAVE_CAPS_INPUT: case ACMDM_HARDWARE_WAVE_CAPS_OUTPUT: /* this converter is not a hardware driver */ case ACMDM_FILTERTAG_DETAILS: case ACMDM_FILTER_DETAILS: /* this converter is not a filter */ case ACMDM_STREAM_RESET: /* only needed for asynchronous driver... we aren't, so just say it */ return MMSYSERR_NOTSUPPORTED; case ACMDM_STREAM_PREPARE: case ACMDM_STREAM_UNPREPARE: /* nothing special to do here... so don't do anything */ return MMSYSERR_NOERROR; default: return DefDriverProc(dwDevID, hDriv, wMsg, dwParam1, dwParam2); } }