| /* -*- tab-width: 8; c-basic-offset: 4 -*- */ |
| |
| /* |
| * MSACM32 library |
| * |
| * Copyright 2000 Eric Pouech |
| * |
| * FIXME / TODO list |
| * + most of the computation should be done in fixed point arithmetic |
| * instead of floating point (16 bits for integral part, and 16 bits |
| * for fractional part for example) |
| * + implement PCM_FormatSuggest function |
| * + get rid of hack for PCM_DriverProc (msacm32.dll shouldn't export |
| * a DriverProc, but this would require implementing a generic |
| * embedded driver handling scheme in msacm32.dll which isn't done yet |
| */ |
| |
| #include <assert.h> |
| #include <string.h> |
| #include "winnls.h" |
| #include "winbase.h" |
| #include "wingdi.h" |
| #include "winuser.h" |
| #include "msacm.h" |
| #include "msacmdrv.h" |
| #include "debugtools.h" |
| |
| DEFAULT_DEBUG_CHANNEL(msacm); |
| |
| /*********************************************************************** |
| * PCM_drvOpen |
| */ |
| static DWORD PCM_drvOpen(LPCSTR str) |
| { |
| return 1; |
| } |
| |
| /*********************************************************************** |
| * PCM_drvClose |
| */ |
| static DWORD PCM_drvClose(DWORD dwDevID) |
| { |
| return 1; |
| } |
| |
| #define NUM_PCM_FORMATS (sizeof(PCM_Formats) / sizeof(PCM_Formats[0])) |
| #define NUM_OF(a,b) (((a)+(b)-1)/(b)) |
| |
| /* flags for fdwDriver */ |
| #define PCM_RESAMPLE 1 |
| |
| /* data used while converting */ |
| typedef struct tagAcmPcmData { |
| /* conversion routine, depending if rate conversion is required */ |
| union { |
| void (*cvtKeepRate)(const unsigned char*, int, unsigned char*); |
| void (*cvtChangeRate)(struct tagAcmPcmData*, const unsigned char*, |
| LPDWORD, unsigned char*, LPDWORD); |
| } cvt; |
| /* the following fields are used only with rate conversion) */ |
| DWORD srcPos; /* position in source stream */ |
| double dstPos; /* position in destination stream */ |
| double dstIncr; /* value to increment dst stream when src stream |
| is incremented by 1 */ |
| /* last source stream value read */ |
| union { |
| unsigned char b; /* 8 bit value */ |
| short s; /* 16 bit value */ |
| } last[2]; /* two channels max (stereo) */ |
| } AcmPcmData; |
| |
| /* table to list all supported formats... those are the basic ones. this |
| * also helps given a unique index to each of the supported formats |
| */ |
| static struct { |
| int nChannels; |
| int nBits; |
| int rate; |
| } PCM_Formats[] = { |
| {1, 8, 8000}, {2, 8, 8000}, {1, 16, 8000}, {2, 16, 8000}, |
| {1, 8, 11025}, {2, 8, 11025}, {1, 16, 11025}, {2, 16, 11025}, |
| {1, 8, 22050}, {2, 8, 22050}, {1, 16, 22050}, {2, 16, 22050}, |
| {1, 8, 44100}, {2, 8, 44100}, {1, 16, 44100}, {2, 16, 44100}, |
| }; |
| |
| /*********************************************************************** |
| * PCM_GetFormatIndex |
| */ |
| static DWORD PCM_GetFormatIndex(LPWAVEFORMATEX wfx) |
| { |
| int i; |
| |
| for (i = 0; i < NUM_PCM_FORMATS; i++) { |
| if (wfx->nChannels == PCM_Formats[i].nChannels && |
| wfx->nSamplesPerSec == PCM_Formats[i].rate && |
| wfx->wBitsPerSample == PCM_Formats[i].nBits) |
| return i; |
| } |
| return 0xFFFFFFFF; |
| } |
| |
| /* PCM Conversions: |
| * |
| * parameters: |
| * + 8 bit unsigned vs 16 bit signed |
| * + mono vs stereo (1 or 2 channels) |
| * + sampling rate (8.0, 11.025, 22.05, 44.1 kHz are defined, but algo shall work |
| * in all cases) |
| * |
| * mono => stereo: copy the same sample on Left & Right channels |
| * stereo =) mono: use the average value of samples from Left & Right channels |
| * resampling; we lookup for each destination sample the two source adjacent samples |
| * were src <= dst < src+1 (dst is increased by a fractional value which is |
| * equivalent to the increment by one on src); then we use a linear |
| * interpolation between src and src+1 |
| */ |
| |
| /*********************************************************************** |
| * C816 |
| * |
| * Converts a 8 bit sample to a 16 bit one |
| */ |
| static inline short C816(unsigned char b) |
| { |
| return (short)(b ^ 0x80) * 256; |
| } |
| |
| /*********************************************************************** |
| * C168 |
| * |
| * Converts a 16 bit sample to a 8 bit one (data loss !!) |
| */ |
| static inline unsigned char C168(short s) |
| { |
| return HIBYTE(s) ^ (unsigned char)0x80; |
| } |
| |
| /*********************************************************************** |
| * R16 |
| * |
| * Read a 16 bit sample (correctly handles endianess) |
| */ |
| static inline short R16(const unsigned char* src) |
| { |
| return (short)((unsigned short)src[0] | ((unsigned short)src[1] << 8)); |
| } |
| |
| /*********************************************************************** |
| * W16 |
| * |
| * Write a 16 bit sample (correctly handles endianess) |
| */ |
| static inline void W16(unsigned char* dst, short s) |
| { |
| dst[0] = LOBYTE(s); |
| dst[1] = HIBYTE(s); |
| } |
| |
| /*********************************************************************** |
| * M16 |
| * |
| * Convert the (l,r) 16 bit stereo sample into a 16 bit mono |
| * (takes the mid-point of the two values) |
| */ |
| static inline short M16(short l, short r) |
| { |
| return (l + r) / 2; |
| } |
| |
| /*********************************************************************** |
| * M8 |
| * |
| * Convert the (l,r) 8 bit stereo sample into a 8 bit mono |
| * (takes the mid-point of the two values) |
| */ |
| static inline unsigned char M8(unsigned char a, unsigned char b) |
| { |
| return (unsigned char)((a + b) / 2); |
| } |
| |
| /* the conversion routines without rate conversion are labelled cvt<X><Y><N><M>K |
| * where : |
| * <X> is the (M)ono/(S)tereo configuration of input channel |
| * <Y> is the (M)ono/(S)tereo configuration of output channel |
| * <N> is the number of bits of input channel (8 or 16) |
| * <M> is the number of bits of output channel (8 or 16) |
| * |
| * in the parameters, ns is always the number of samples, so the size of input |
| * buffer (resp output buffer) is ns * (<X> == 'Mono' ? 1:2) * (<N> == 8 ? 1:2) |
| */ |
| |
| static void cvtMM88K(const unsigned char* src, int ns, unsigned char* dst) |
| { |
| memcpy(dst, src, ns); |
| } |
| |
| static void cvtSS88K(const unsigned char* src, int ns, unsigned char* dst) |
| { |
| memcpy(dst, src, ns * 2); |
| } |
| |
| static void cvtMM1616K(const unsigned char* src, int ns, unsigned char* dst) |
| { |
| memcpy(dst, src, ns * 2); |
| } |
| |
| static void cvtSS1616K(const unsigned char* src, int ns, unsigned char* dst) |
| { |
| memcpy(dst, src, ns * 4); |
| } |
| |
| static void cvtMS88K(const unsigned char* src, int ns, unsigned char* dst) |
| { |
| while (ns--) { |
| *dst++ = *src; |
| *dst++ = *src++; |
| } |
| } |
| |
| static void cvtMS816K(const unsigned char* src, int ns, unsigned char* dst) |
| { |
| short v; |
| |
| while (ns--) { |
| v = C816(*src++); |
| W16(dst, v); dst += 2; |
| W16(dst, v); dst += 2; |
| } |
| } |
| |
| static void cvtMS168K(const unsigned char* src, int ns, unsigned char* dst) |
| { |
| unsigned char v; |
| |
| while (ns--) { |
| v = C168(R16(src)); src += 2; |
| *dst++ = v; |
| *dst++ = v; |
| } |
| } |
| |
| static void cvtMS1616K(const unsigned char* src, int ns, unsigned char* dst) |
| { |
| short v; |
| |
| while (ns--) { |
| v = R16(src); src += 2; |
| W16(dst, v); dst += 2; |
| W16(dst, v); dst += 2; |
| } |
| } |
| |
| static void cvtSM88K(const unsigned char* src, int ns, unsigned char* dst) |
| { |
| while (ns--) { |
| *dst++ = M8(src[0], src[1]); |
| src += 2; |
| } |
| } |
| |
| static void cvtSM816K(const unsigned char* src, int ns, unsigned char* dst) |
| { |
| short v; |
| |
| while (ns--) { |
| v = M16(C816(src[0]), C816(src[1])); |
| src += 2; |
| W16(dst, v); dst += 2; |
| } |
| } |
| |
| static void cvtSM168K(const unsigned char* src, int ns, unsigned char* dst) |
| { |
| while (ns--) { |
| *dst++ = C168(M16(R16(src), R16(src + 2))); |
| src += 4; |
| } |
| } |
| |
| static void cvtSM1616K(const unsigned char* src, int ns, unsigned char* dst) |
| { |
| while (ns--) { |
| W16(dst, M16(R16(src),R16(src+2))); dst += 2; |
| src += 4; |
| } |
| } |
| |
| static void cvtMM816K(const unsigned char* src, int ns, unsigned char* dst) |
| { |
| while (ns--) { |
| W16(dst, C816(*src++)); dst += 2; |
| } |
| } |
| |
| static void cvtSS816K(const unsigned char* src, int ns, unsigned char* dst) |
| { |
| while (ns--) { |
| W16(dst, C816(*src++)); dst += 2; |
| W16(dst, C816(*src++)); dst += 2; |
| } |
| } |
| |
| static void cvtMM168K(const unsigned char* src, int ns, unsigned char* dst) |
| { |
| while (ns--) { |
| *dst++ = C168(R16(src)); src += 2; |
| } |
| } |
| |
| static void cvtSS168K(const unsigned char* src, int ns, unsigned char* dst) |
| { |
| while (ns--) { |
| *dst++ = C168(R16(src)); src += 2; |
| *dst++ = C168(R16(src)); src += 2; |
| } |
| } |
| |
| static void (*PCM_ConvertKeepRate[16])(const unsigned char*, int, unsigned char*) = { |
| cvtSS88K, cvtSM88K, cvtMS88K, cvtMM88K, |
| cvtSS816K, cvtSM816K, cvtMS816K, cvtMM816K, |
| cvtSS168K, cvtSM168K, cvtMS168K, cvtMM168K, |
| cvtSS1616K, cvtSM1616K, cvtMS1616K, cvtMM1616K, |
| }; |
| |
| /*********************************************************************** |
| * I |
| * |
| * Interpolate the value at r (r in ]0, 1]) between the two points v1 and v2 |
| * Linear interpolation is used |
| */ |
| static inline double I(double v1, double v2, double r) |
| { |
| if (0.0 >= r || r > 1.0) FIXME("r!! %f\n", r); |
| return (1.0 - r) * v1 + r * v2; |
| } |
| |
| static void cvtSS88C(AcmPcmData* apd, const unsigned char* src, LPDWORD nsrc, |
| unsigned char* dst, LPDWORD ndst) |
| { |
| double r; |
| |
| while (*nsrc != 0 && *ndst != 0) { |
| while ((r = (double)apd->srcPos - apd->dstPos) <= 0) { |
| if (*nsrc == 0) return; |
| apd->last[0].b = *src++; |
| apd->last[1].b = *src++; |
| apd->srcPos++; |
| (*nsrc)--; |
| } |
| /* now do the interpolation */ |
| *dst++ = I(apd->last[0].b, src[0], r); |
| *dst++ = I(apd->last[1].b, src[1], r); |
| apd->dstPos += apd->dstIncr; |
| (*ndst)--; |
| } |
| } |
| |
| /* the conversion routines with rate conversion are labelled cvt<X><Y><N><M>C |
| * where : |
| * <X> is the (M)ono/(S)tereo configuration of input channel |
| * <Y> is the (M)ono/(S)tereo configuration of output channel |
| * <N> is the number of bits of input channel (8 or 16) |
| * <M> is the number of bits of output channel (8 or 16) |
| * |
| */ |
| static void cvtSM88C(AcmPcmData* apd, const unsigned char* src, LPDWORD nsrc, |
| unsigned char* dst, LPDWORD ndst) |
| { |
| double r; |
| |
| while (*nsrc != 0 && *ndst != 0) { |
| while ((r = (double)apd->srcPos - apd->dstPos) <= 0) { |
| if (*nsrc == 0) return; |
| apd->last[0].b = *src++; |
| apd->last[1].b = *src++; |
| apd->srcPos++; |
| (*nsrc)--; |
| } |
| /* now do the interpolation */ |
| *dst++ = I(M8(apd->last[0].b, apd->last[1].b), M8(src[0], src[1]), r); |
| apd->dstPos += apd->dstIncr; |
| (*ndst)--; |
| } |
| } |
| |
| static void cvtMS88C(AcmPcmData* apd, const unsigned char* src, LPDWORD nsrc, |
| unsigned char* dst, LPDWORD ndst) |
| { |
| double r; |
| |
| while (*nsrc != 0 && *ndst != 0) { |
| while ((r = (double)apd->srcPos - apd->dstPos) <= 0) { |
| if (*nsrc == 0) return; |
| apd->last[0].b = *src++; |
| apd->srcPos++; |
| (*nsrc)--; |
| } |
| /* now do the interpolation */ |
| dst[0] = dst[1] = I(apd->last[0].b, src[0], r); |
| dst += 2; |
| apd->dstPos += apd->dstIncr; |
| (*ndst)--; |
| } |
| } |
| |
| static void cvtMM88C(AcmPcmData* apd, const unsigned char* src, LPDWORD nsrc, |
| unsigned char* dst, LPDWORD ndst) |
| { |
| double r; |
| |
| while (*nsrc != 0 && *ndst != 0) { |
| while ((r = (double)apd->srcPos - apd->dstPos) <= 0) { |
| if (*nsrc == 0) return; |
| apd->last[0].b = *src++; |
| apd->srcPos++; |
| (*nsrc)--; |
| } |
| /* now do the interpolation */ |
| *dst++ = I(apd->last[0].b, src[0], r); |
| apd->dstPos += apd->dstIncr; |
| (*ndst)--; |
| } |
| } |
| |
| static void cvtSS816C(AcmPcmData* apd, const unsigned char* src, LPDWORD nsrc, |
| unsigned char* dst, LPDWORD ndst) |
| { |
| double r; |
| |
| while (*nsrc != 0 && *ndst != 0) { |
| while ((r = (double)apd->srcPos - apd->dstPos) <= 0) { |
| if (*nsrc == 0) return; |
| apd->last[0].b = *src++; |
| apd->last[1].b = *src++; |
| apd->srcPos++; |
| (*nsrc)--; |
| } |
| /* now do the interpolation */ |
| W16(dst, I(C816(apd->last[0].b), C816(src[0]), r)); dst += 2; |
| W16(dst, I(C816(apd->last[1].b), C816(src[1]), r)); dst += 2; |
| apd->dstPos += apd->dstIncr; |
| (*ndst)--; |
| } |
| } |
| |
| static void cvtSM816C(AcmPcmData* apd, const unsigned char* src, LPDWORD nsrc, |
| unsigned char* dst, LPDWORD ndst) |
| { |
| double r; |
| |
| while (*nsrc != 0 && *ndst != 0) { |
| while ((r = (double)apd->srcPos - apd->dstPos) <= 0) { |
| if (*nsrc == 0) return; |
| apd->last[0].b = *src++; |
| apd->last[1].b = *src++; |
| apd->srcPos++; |
| (*nsrc)--; |
| } |
| /* now do the interpolation */ |
| W16(dst, I(M16(C816(apd->last[0].b), C816(apd->last[1].b)), |
| M16(C816(src[0]), C816(src[1])), r)); |
| dst += 2; |
| apd->dstPos += apd->dstIncr; |
| (*ndst)--; |
| } |
| } |
| |
| static void cvtMS816C(AcmPcmData* apd, const unsigned char* src, LPDWORD nsrc, |
| unsigned char* dst, LPDWORD ndst) |
| { |
| double r; |
| short v; |
| |
| while (*nsrc != 0 && *ndst != 0) { |
| while ((r = (double)apd->srcPos - apd->dstPos) <= 0) { |
| if (*nsrc == 0) return; |
| apd->last[0].b = *src++; |
| apd->srcPos++; |
| (*nsrc)--; |
| } |
| /* now do the interpolation */ |
| v = I(C816(apd->last[0].b), C816(src[0]), r); |
| W16(dst, v); dst += 2; |
| W16(dst, v); dst += 2; |
| apd->dstPos += apd->dstIncr; |
| (*ndst)--; |
| } |
| } |
| |
| static void cvtMM816C(AcmPcmData* apd, const unsigned char* src, LPDWORD nsrc, |
| unsigned char* dst, LPDWORD ndst) |
| { |
| double r; |
| |
| while (*nsrc != 0 && *ndst != 0) { |
| while ((r = (double)apd->srcPos - apd->dstPos) <= 0) { |
| if (*nsrc == 0) return; |
| apd->last[0].b = *src++; |
| apd->srcPos++; |
| (*nsrc)--; |
| } |
| /* now do the interpolation */ |
| W16(dst, I(C816(apd->last[0].b), C816(src[0]), r)); |
| dst += 2; |
| apd->dstPos += apd->dstIncr; |
| (*ndst)--; |
| } |
| } |
| |
| static void cvtSS168C(AcmPcmData* apd, const unsigned char* src, LPDWORD nsrc, |
| unsigned char* dst, LPDWORD ndst) |
| { |
| double r; |
| |
| while (*nsrc != 0 && *ndst != 0) { |
| while ((r = (double)apd->srcPos - apd->dstPos) <= 0) { |
| if (*nsrc == 0) return; |
| apd->last[0].s = R16(src); src += 2; |
| apd->last[1].s = R16(src); src += 2; |
| apd->srcPos++; |
| (*nsrc)--; |
| } |
| /* now do the interpolation */ |
| *dst++ = C168(I(apd->last[0].s, R16(src) , r)); |
| *dst++ = C168(I(apd->last[1].s, R16(src+2), r)); |
| apd->dstPos += apd->dstIncr; |
| (*ndst)--; |
| } |
| } |
| |
| static void cvtSM168C(AcmPcmData* apd, const unsigned char* src, LPDWORD nsrc, |
| unsigned char* dst, LPDWORD ndst) |
| { |
| double r; |
| |
| while (*nsrc != 0 && *ndst != 0) { |
| while ((r = (double)apd->srcPos - apd->dstPos) <= 0) { |
| if (*nsrc == 0) return; |
| apd->last[0].s = R16(src); src += 2; |
| apd->last[1].s = R16(src); src += 2; |
| apd->srcPos++; |
| (*nsrc)--; |
| } |
| /* now do the interpolation */ |
| *dst++ = C168(I(M16(apd->last[0].s, apd->last[1].s), |
| M16(R16(src), R16(src + 2)), r)); |
| apd->dstPos += apd->dstIncr; |
| (*ndst)--; |
| } |
| } |
| |
| |
| static void cvtMS168C(AcmPcmData* apd, const unsigned char* src, LPDWORD nsrc, |
| unsigned char* dst, LPDWORD ndst) |
| { |
| double r; |
| |
| while (*nsrc != 0 && *ndst != 0) { |
| while ((r = (double)apd->srcPos - apd->dstPos) <= 0) { |
| if (*nsrc == 0) return; |
| apd->last[0].s = R16(src); src += 2; |
| apd->srcPos++; |
| (*nsrc)--; |
| } |
| /* now do the interpolation */ |
| dst[0] = dst[1] = C168(I(apd->last[0].s, R16(src), r)); dst += 2; |
| apd->dstPos += apd->dstIncr; |
| (*ndst)--; |
| } |
| } |
| |
| |
| static void cvtMM168C(AcmPcmData* apd, const unsigned char* src, LPDWORD nsrc, |
| unsigned char* dst, LPDWORD ndst) |
| { |
| double r; |
| |
| while (*nsrc != 0 && *ndst != 0) { |
| while ((r = (double)apd->srcPos - apd->dstPos) <= 0) { |
| if (*nsrc == 0) return; |
| apd->last[0].s = R16(src); src += 2; |
| apd->srcPos++; |
| (*nsrc)--; |
| } |
| /* now do the interpolation */ |
| *dst++ = C168(I(apd->last[0].s, R16(src), r)); |
| apd->dstPos += apd->dstIncr; |
| (*ndst)--; |
| } |
| } |
| |
| static void cvtSS1616C(AcmPcmData* apd, const unsigned char* src, LPDWORD nsrc, |
| unsigned char* dst, LPDWORD ndst) |
| { |
| double r; |
| |
| while (*nsrc != 0 && *ndst != 0) { |
| while ((r = (double)apd->srcPos - apd->dstPos) <= 0) { |
| if (*nsrc == 0) return; |
| apd->last[0].s = R16(src); src += 2; |
| apd->last[1].s = R16(src); src += 2; |
| apd->srcPos++; |
| (*nsrc)--; |
| } |
| /* now do the interpolation */ |
| W16(dst, I(apd->last[0].s, R16(src) , r)); dst += 2; |
| W16(dst, I(apd->last[1].s, R16(src+2), r)); dst += 2; |
| apd->dstPos += apd->dstIncr; |
| (*ndst)--; |
| } |
| } |
| |
| static void cvtSM1616C(AcmPcmData* apd, const unsigned char* src, LPDWORD nsrc, |
| unsigned char* dst, LPDWORD ndst) |
| { |
| double r; |
| |
| while (*nsrc != 0 && *ndst != 0) { |
| while ((r = (double)apd->srcPos - apd->dstPos) <= 0) { |
| if (*nsrc == 0) return; |
| apd->last[0].s = R16(src); src += 2; |
| apd->last[1].s = R16(src); src += 2; |
| apd->srcPos++; |
| (*nsrc)--; |
| } |
| /* now do the interpolation */ |
| W16(dst, I(M16(apd->last[0].s, apd->last[1].s), |
| M16(R16(src), R16(src+2)), r)); |
| dst += 2; |
| apd->dstPos += apd->dstIncr; |
| (*ndst)--; |
| } |
| } |
| |
| static void cvtMS1616C(AcmPcmData* apd, const unsigned char* src, LPDWORD nsrc, |
| unsigned char* dst, LPDWORD ndst) |
| { |
| double r; |
| short v; |
| |
| while (*nsrc != 0 && *ndst != 0) { |
| while ((r = (double)apd->srcPos - apd->dstPos) <= 0) { |
| if (*nsrc == 0) return; |
| apd->last[0].s = R16(src); src += 2; |
| apd->srcPos++; |
| (*nsrc)--; |
| } |
| /* now do the interpolation */ |
| v = I(apd->last[0].s, R16(src), r); |
| W16(dst, v); dst += 2; |
| W16(dst, v); dst += 2; |
| apd->dstPos += apd->dstIncr; |
| (*ndst)--; |
| } |
| } |
| |
| static void cvtMM1616C(AcmPcmData* apd, const unsigned char* src, LPDWORD nsrc, |
| unsigned char* dst, LPDWORD ndst) |
| { |
| double r; |
| |
| while (*nsrc != 0 && *ndst != 0) { |
| while ((r = (double)apd->srcPos - apd->dstPos) <= 0) { |
| if (*nsrc == 0) return; |
| apd->last[0].s = R16(src); src += 2; |
| apd->srcPos++; |
| (*nsrc)--; |
| } |
| /* now do the interpolation */ |
| W16(dst, I(apd->last[0].s, R16(src), r)); dst += 2; |
| apd->dstPos += apd->dstIncr; |
| (*ndst)--; |
| } |
| } |
| |
| static void (*PCM_ConvertChangeRate[16])(AcmPcmData* apd, |
| const unsigned char* src, LPDWORD nsrc, |
| unsigned char* dst, LPDWORD ndst) = { |
| cvtSS88C, cvtSM88C, cvtMS88C, cvtMM88C, |
| cvtSS816C, cvtSM816C, cvtMS816C, cvtMM816C, |
| cvtSS168C, cvtSM168C, cvtMS168C, cvtMM168C, |
| cvtSS1616C, cvtSM1616C, cvtMS1616C, cvtMM1616C, |
| }; |
| |
| /*********************************************************************** |
| * PCM_DriverDetails |
| * |
| */ |
| static LRESULT PCM_DriverDetails(PACMDRIVERDETAILSW add) |
| { |
| add->fccType = ACMDRIVERDETAILS_FCCTYPE_AUDIOCODEC; |
| add->fccComp = ACMDRIVERDETAILS_FCCCOMP_UNDEFINED; |
| add->wMid = 0xFF; |
| add->wPid = 0x00; |
| add->vdwACM = 0x01000000; |
| add->vdwDriver = 0x01000000; |
| add->fdwSupport = ACMDRIVERDETAILS_SUPPORTF_CONVERTER; |
| add->cFormatTags = 1; |
| add->cFilterTags = 0; |
| add->hicon = (HICON)0; |
| MultiByteToWideChar( CP_ACP, 0, "WINE-PCM", -1, |
| add->szShortName, sizeof(add->szShortName)/sizeof(WCHAR) ); |
| MultiByteToWideChar( CP_ACP, 0, "Wine PCM converter", -1, |
| add->szLongName, sizeof(add->szLongName)/sizeof(WCHAR) ); |
| MultiByteToWideChar( CP_ACP, 0, "Brought to you by the Wine team...", -1, |
| add->szCopyright, sizeof(add->szCopyright)/sizeof(WCHAR) ); |
| MultiByteToWideChar( CP_ACP, 0, "Refer to LICENSE file", -1, |
| add->szLicensing, sizeof(add->szLicensing)/sizeof(WCHAR) ); |
| add->szFeatures[0] = 0; |
| |
| return MMSYSERR_NOERROR; |
| } |
| |
| /*********************************************************************** |
| * PCM_FormatTagDetails |
| * |
| */ |
| static LRESULT PCM_FormatTagDetails(PACMFORMATTAGDETAILSW aftd, DWORD dwQuery) |
| { |
| switch (dwQuery) { |
| case ACM_FORMATTAGDETAILSF_INDEX: |
| if (aftd->dwFormatTagIndex != 0) return ACMERR_NOTPOSSIBLE; |
| break; |
| case ACM_FORMATTAGDETAILSF_FORMATTAG: |
| if (aftd->dwFormatTag != WAVE_FORMAT_PCM) return ACMERR_NOTPOSSIBLE; |
| break; |
| case ACM_FORMATTAGDETAILSF_LARGESTSIZE: |
| if (aftd->dwFormatTag != WAVE_FORMAT_UNKNOWN && |
| aftd->dwFormatTag != WAVE_FORMAT_UNKNOWN) |
| return ACMERR_NOTPOSSIBLE; |
| break; |
| default: |
| WARN("Unsupported query %08lx\n", dwQuery); |
| return MMSYSERR_NOTSUPPORTED; |
| } |
| |
| aftd->dwFormatTagIndex = 0; |
| aftd->dwFormatTag = WAVE_FORMAT_PCM; |
| aftd->cbFormatSize = sizeof(PCMWAVEFORMAT); |
| aftd->fdwSupport = ACMDRIVERDETAILS_SUPPORTF_CONVERTER; |
| aftd->cStandardFormats = NUM_PCM_FORMATS; |
| aftd->szFormatTag[0] = 0; |
| |
| return MMSYSERR_NOERROR; |
| } |
| |
| /*********************************************************************** |
| * PCM_FormatDetails |
| * |
| */ |
| static LRESULT PCM_FormatDetails(PACMFORMATDETAILSW afd, DWORD dwQuery) |
| { |
| switch (dwQuery) { |
| case ACM_FORMATDETAILSF_FORMAT: |
| afd->dwFormatIndex = PCM_GetFormatIndex(afd->pwfx); |
| if (afd->dwFormatIndex == 0xFFFFFFFF) return ACMERR_NOTPOSSIBLE; |
| break; |
| case ACM_FORMATDETAILSF_INDEX: |
| assert(afd->dwFormatIndex < NUM_PCM_FORMATS); |
| afd->pwfx->wFormatTag = WAVE_FORMAT_PCM; |
| afd->pwfx->nChannels = PCM_Formats[afd->dwFormatIndex].nChannels; |
| afd->pwfx->nSamplesPerSec = PCM_Formats[afd->dwFormatIndex].rate; |
| afd->pwfx->wBitsPerSample = PCM_Formats[afd->dwFormatIndex].nBits; |
| /* native MSACM uses a PCMWAVEFORMAT structure, so cbSize is not accessible |
| * afd->pwfx->cbSize = 0; |
| */ |
| afd->pwfx->nBlockAlign = |
| (afd->pwfx->nChannels * afd->pwfx->wBitsPerSample) / 8; |
| afd->pwfx->nAvgBytesPerSec = |
| afd->pwfx->nSamplesPerSec * afd->pwfx->nBlockAlign; |
| break; |
| default: |
| WARN("Unsupported query %08lx\n", dwQuery); |
| return MMSYSERR_NOTSUPPORTED; |
| } |
| |
| afd->dwFormatTag = WAVE_FORMAT_PCM; |
| afd->fdwSupport = ACMDRIVERDETAILS_SUPPORTF_CONVERTER; |
| afd->szFormat[0] = 0; /* let MSACM format this for us... */ |
| |
| return MMSYSERR_NOERROR; |
| } |
| |
| /*********************************************************************** |
| * PCM_FormatSuggest |
| * |
| */ |
| static LRESULT PCM_FormatSuggest(PACMDRVFORMATSUGGEST adfs) |
| { |
| FIXME("(%p);\n", adfs); |
| return MMSYSERR_NOTSUPPORTED; |
| } |
| |
| /*********************************************************************** |
| * PCM_Reset |
| * |
| */ |
| static void PCM_Reset(AcmPcmData* apd, int srcNumBits) |
| { |
| apd->srcPos = 0; |
| apd->dstPos = 0; |
| /* initialize with neutral value */ |
| if (srcNumBits == 16) { |
| apd->last[0].s = 0; |
| apd->last[1].s = 0; |
| } else { |
| apd->last[0].b = (BYTE)0x80; |
| apd->last[1].b = (BYTE)0x80; |
| } |
| } |
| |
| /*********************************************************************** |
| * PCM_StreamOpen |
| * |
| */ |
| static LRESULT PCM_StreamOpen(PACMDRVSTREAMINSTANCE adsi) |
| { |
| AcmPcmData* apd; |
| int idx = 0; |
| |
| assert(!(adsi->fdwOpen & ACM_STREAMOPENF_ASYNC)); |
| |
| if (PCM_GetFormatIndex(adsi->pwfxSrc) == 0xFFFFFFFF || |
| PCM_GetFormatIndex(adsi->pwfxDst) == 0xFFFFFFFF) |
| return ACMERR_NOTPOSSIBLE; |
| |
| apd = HeapAlloc(GetProcessHeap(), 0, sizeof(AcmPcmData)); |
| if (apd == 0) return MMSYSERR_NOMEM; |
| |
| adsi->dwDriver = (DWORD)apd; |
| adsi->fdwDriver = 0; |
| |
| if (adsi->pwfxSrc->wBitsPerSample == 16) idx += 8; |
| if (adsi->pwfxDst->wBitsPerSample == 16) idx += 4; |
| if (adsi->pwfxSrc->nChannels == 1) idx += 2; |
| if (adsi->pwfxDst->nChannels == 1) idx += 1; |
| |
| if (adsi->pwfxSrc->nSamplesPerSec == adsi->pwfxDst->nSamplesPerSec) { |
| apd->cvt.cvtKeepRate = PCM_ConvertKeepRate[idx]; |
| } else { |
| adsi->fdwDriver |= PCM_RESAMPLE; |
| apd->dstIncr = (double)(adsi->pwfxSrc->nSamplesPerSec) / |
| (double)(adsi->pwfxDst->nSamplesPerSec); |
| PCM_Reset(apd, adsi->pwfxSrc->wBitsPerSample); |
| apd->cvt.cvtChangeRate = PCM_ConvertChangeRate[idx]; |
| } |
| |
| return MMSYSERR_NOERROR; |
| } |
| |
| /*********************************************************************** |
| * PCM_StreamClose |
| * |
| */ |
| static LRESULT PCM_StreamClose(PACMDRVSTREAMINSTANCE adsi) |
| { |
| HeapFree(GetProcessHeap(), 0, (void*)adsi->dwDriver); |
| return MMSYSERR_NOERROR; |
| } |
| |
| /*********************************************************************** |
| * PCM_round |
| * |
| */ |
| static inline DWORD PCM_round(DWORD a, DWORD b, DWORD c) |
| { |
| assert(a && b && c); |
| /* to be sure, always return an entire number of c... */ |
| return ((double)a * (double)b + (double)c - 1) / (double)c; |
| } |
| |
| /*********************************************************************** |
| * PCM_StreamSize |
| * |
| */ |
| static LRESULT PCM_StreamSize(PACMDRVSTREAMINSTANCE adsi, PACMDRVSTREAMSIZE adss) |
| { |
| switch (adss->fdwSize) { |
| case ACM_STREAMSIZEF_DESTINATION: |
| /* cbDstLength => cbSrcLength */ |
| adss->cbSrcLength = PCM_round(adss->cbDstLength, |
| adsi->pwfxSrc->nAvgBytesPerSec, |
| adsi->pwfxDst->nAvgBytesPerSec); |
| break; |
| case ACM_STREAMSIZEF_SOURCE: |
| /* cbSrcLength => cbDstLength */ |
| adss->cbDstLength = PCM_round(adss->cbSrcLength, |
| adsi->pwfxDst->nAvgBytesPerSec, |
| adsi->pwfxSrc->nAvgBytesPerSec); |
| break; |
| default: |
| WARN("Unsupported query %08lx\n", adss->fdwSize); |
| return MMSYSERR_NOTSUPPORTED; |
| } |
| return MMSYSERR_NOERROR; |
| } |
| |
| /*********************************************************************** |
| * PCM_StreamConvert |
| * |
| */ |
| static LRESULT PCM_StreamConvert(PACMDRVSTREAMINSTANCE adsi, PACMDRVSTREAMHEADER adsh) |
| { |
| AcmPcmData* apd = (AcmPcmData*)adsi->dwDriver; |
| DWORD nsrc = NUM_OF(adsh->cbSrcLength, adsi->pwfxSrc->nBlockAlign); |
| DWORD ndst = NUM_OF(adsh->cbDstLength, adsi->pwfxDst->nBlockAlign); |
| |
| if (adsh->fdwConvert & |
| ~(ACM_STREAMCONVERTF_BLOCKALIGN| |
| ACM_STREAMCONVERTF_END| |
| ACM_STREAMCONVERTF_START)) { |
| FIXME("Unsupported fdwConvert (%08lx), ignoring it\n", adsh->fdwConvert); |
| } |
| /* ACM_STREAMCONVERTF_BLOCKALIGN |
| * currently all conversions are block aligned, so do nothing for this flag |
| * ACM_STREAMCONVERTF_END |
| * no pending data, so do nothing for this flag |
| */ |
| if ((adsh->fdwConvert & ACM_STREAMCONVERTF_START) && |
| (adsi->fdwDriver & PCM_RESAMPLE)) { |
| PCM_Reset(apd, adsi->pwfxSrc->wBitsPerSample); |
| } |
| |
| /* do the job */ |
| if (adsi->fdwDriver & PCM_RESAMPLE) { |
| DWORD nsrc2 = nsrc; |
| DWORD ndst2 = ndst; |
| |
| apd->cvt.cvtChangeRate(apd, adsh->pbSrc, &nsrc2, adsh->pbDst, &ndst2); |
| nsrc -= nsrc2; |
| ndst -= ndst2; |
| } else { |
| if (nsrc < ndst) ndst = nsrc; else nsrc = ndst; |
| |
| /* nsrc is now equal to ndst */ |
| apd->cvt.cvtKeepRate(adsh->pbSrc, nsrc, adsh->pbDst); |
| } |
| |
| adsh->cbSrcLengthUsed = nsrc * adsi->pwfxSrc->nBlockAlign; |
| adsh->cbDstLengthUsed = ndst * adsi->pwfxDst->nBlockAlign; |
| |
| return MMSYSERR_NOERROR; |
| } |
| |
| /************************************************************************** |
| * PCM_DriverProc [exported] |
| */ |
| LRESULT CALLBACK PCM_DriverProc(DWORD dwDevID, HDRVR hDriv, UINT wMsg, |
| LPARAM dwParam1, LPARAM dwParam2) |
| { |
| TRACE("(%08lx %08lx %u %08lx %08lx);\n", |
| dwDevID, (DWORD)hDriv, wMsg, dwParam1, dwParam2); |
| |
| switch (wMsg) { |
| case DRV_LOAD: return 1; |
| case DRV_FREE: return 1; |
| case DRV_OPEN: return PCM_drvOpen((LPSTR)dwParam1); |
| case DRV_CLOSE: return PCM_drvClose(dwDevID); |
| case DRV_ENABLE: return 1; |
| case DRV_DISABLE: return 1; |
| case DRV_QUERYCONFIGURE: return 1; |
| case DRV_CONFIGURE: MessageBoxA(0, "MSACM PCM filter !", "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 PCM_DriverDetails((PACMDRIVERDETAILSW)dwParam1); |
| |
| case ACMDM_FORMATTAG_DETAILS: |
| return PCM_FormatTagDetails((PACMFORMATTAGDETAILSW)dwParam1, dwParam2); |
| |
| case ACMDM_FORMAT_DETAILS: |
| return PCM_FormatDetails((PACMFORMATDETAILSW)dwParam1, dwParam2); |
| |
| case ACMDM_FORMAT_SUGGEST: |
| return PCM_FormatSuggest((PACMDRVFORMATSUGGEST)dwParam1); |
| |
| case ACMDM_STREAM_OPEN: |
| return PCM_StreamOpen((PACMDRVSTREAMINSTANCE)dwParam1); |
| |
| case ACMDM_STREAM_CLOSE: |
| return PCM_StreamClose((PACMDRVSTREAMINSTANCE)dwParam1); |
| |
| case ACMDM_STREAM_SIZE: |
| return PCM_StreamSize((PACMDRVSTREAMINSTANCE)dwParam1, (PACMDRVSTREAMSIZE)dwParam2); |
| |
| case ACMDM_STREAM_CONVERT: |
| return PCM_StreamConvert((PACMDRVSTREAMINSTANCE)dwParam1, (PACMDRVSTREAMHEADER)dwParam2); |
| |
| 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 */ |
| case ACMDM_STREAM_PREPARE: |
| case ACMDM_STREAM_UNPREPARE: |
| /* nothing special to do here... so don't do anything */ |
| return MMSYSERR_NOTSUPPORTED; |
| |
| default: |
| return DefDriverProc(dwDevID, hDriv, wMsg, dwParam1, dwParam2); |
| } |
| return 0; |
| } |