Cog Audio: Implement HDCD decoding

CQTexperiment
Christopher Snowhill 2022-01-19 02:08:57 -08:00
parent f2feb3bcd7
commit 6f0a737123
11 changed files with 1538 additions and 10 deletions

View File

@ -75,4 +75,6 @@
- (double)secondsBuffered; - (double)secondsBuffered;
- (void)sustainHDCD;
@end @end

View File

@ -231,7 +231,7 @@
- (double)secondsBuffered - (double)secondsBuffered
{ {
double duration = 0.0; double duration = 0.0;
OutputNode * outputNode = [controller output]; OutputNode * outputNode = (OutputNode *) [controller output];
duration += [outputNode secondsBuffered]; duration += [outputNode secondsBuffered];
Node * node = [self finalNode]; Node * node = [self finalNode];
@ -242,4 +242,10 @@
return duration; return duration;
} }
- (void)sustainHDCD
{
OutputNode * outputNode = (OutputNode *) [controller output];
[outputNode sustainHDCD];
}
@end @end

View File

@ -71,6 +71,8 @@
id __weak originalPreviousNode; id __weak originalPreviousNode;
NSString *outputResampling; NSString *outputResampling;
void *hdcd_decoder;
} }
@property AudioStreamBasicDescription inputFormat; @property AudioStreamBasicDescription inputFormat;

View File

@ -19,6 +19,8 @@
#import "lpc.h" #import "lpc.h"
#import "util.h" #import "util.h"
#import "hdcd_decode2.h"
#import <TargetConditionals.h> #import <TargetConditionals.h>
#if TARGET_CPU_X86 || TARGET_CPU_X86_64 #if TARGET_CPU_X86 || TARGET_CPU_X86_64
@ -81,6 +83,8 @@ void PrintStreamDesc (AudioStreamBasicDescription *inDesc)
outputResampling = @""; outputResampling = @"";
hdcd_decoder = NULL;
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.volumeScaling" options:0 context:nil]; [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.volumeScaling" options:0 context:nil];
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.outputResampling" options:0 context:nil]; [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.outputResampling" options:0 context:nil];
} }
@ -542,7 +546,7 @@ static void dsd2pcm_process(void * _state, uint8_t * src, size_t sofs, size_t si
state->fpos = fpos; state->fpos = fpos;
} }
static void convert_dsd_to_f32(float *output, uint8_t *input, size_t count, size_t channels, void ** dsd2pcm) static void convert_dsd_to_f32(float *output, const uint8_t *input, size_t count, size_t channels, void ** dsd2pcm)
{ {
for (size_t channel = 0; channel < channels; ++channel) for (size_t channel = 0; channel < channels; ++channel)
{ {
@ -550,7 +554,7 @@ static void convert_dsd_to_f32(float *output, uint8_t *input, size_t count, size
} }
} }
static void convert_u8_to_s16(int16_t *output, uint8_t *input, size_t count) static void convert_u8_to_s16(int16_t *output, const uint8_t *input, size_t count)
{ {
for (size_t i = 0; i < count; ++i) for (size_t i = 0; i < count; ++i)
{ {
@ -560,7 +564,7 @@ static void convert_u8_to_s16(int16_t *output, uint8_t *input, size_t count)
} }
} }
static void convert_s8_to_s16(int16_t *output, uint8_t *input, size_t count) static void convert_s8_to_s16(int16_t *output, const uint8_t *input, size_t count)
{ {
for (size_t i = 0; i < count; ++i) for (size_t i = 0; i < count; ++i)
{ {
@ -577,7 +581,15 @@ static void convert_u16_to_s16(int16_t *buffer, size_t count)
} }
} }
static void convert_s24_to_s32(int32_t *output, uint8_t *input, size_t count) static void convert_s16_to_hdcd_input(int32_t *output, const int16_t *input, size_t count)
{
for (size_t i = 0; i < count; ++i)
{
output[i] = input[i];
}
}
static void convert_s24_to_s32(int32_t *output, const uint8_t *input, size_t count)
{ {
for (size_t i = 0; i < count; ++i) for (size_t i = 0; i < count; ++i)
{ {
@ -586,7 +598,7 @@ static void convert_s24_to_s32(int32_t *output, uint8_t *input, size_t count)
} }
} }
static void convert_u24_to_s32(int32_t *output, uint8_t *input, size_t count) static void convert_u24_to_s32(int32_t *output, const uint8_t *input, size_t count)
{ {
for (size_t i = 0; i < count; ++i) for (size_t i = 0; i < count; ++i)
{ {
@ -603,7 +615,7 @@ static void convert_u32_to_s32(int32_t *buffer, size_t count)
} }
} }
static void convert_f64_to_f32(float *output, double *input, size_t count) static void convert_f64_to_f32(float *output, const double *input, size_t count)
{ {
for (size_t i = 0; i < count; ++i) for (size_t i = 0; i < count; ++i)
{ {
@ -822,6 +834,7 @@ tryagain:
if (bytesReadFromInput && !isFloat) if (bytesReadFromInput && !isFloat)
{ {
float gain = 1.0;
size_t bitsPerSample = inputFormat.mBitsPerChannel; size_t bitsPerSample = inputFormat.mBitsPerChannel;
if (bitsPerSample == 1) { if (bitsPerSample == 1) {
samplesRead = bytesReadFromInput / inputFormat.mBytesPerPacket; samplesRead = bytesReadFromInput / inputFormat.mBytesPerPacket;
@ -842,7 +855,23 @@ tryagain:
bytesReadFromInput = samplesRead * 2; bytesReadFromInput = samplesRead * 2;
isUnsigned = NO; isUnsigned = NO;
} }
if (bitsPerSample <= 16) { if (hdcd_decoder) { // implied bits per sample is 16, produces 32 bit int scale
samplesRead = bytesReadFromInput / 2;
if (isUnsigned)
convert_u16_to_s16(inputBuffer, samplesRead);
convert_s16_to_hdcd_input(inputBuffer + bytesReadFromInput, inputBuffer, samplesRead);
memmove(inputBuffer, inputBuffer + bytesReadFromInput, samplesRead * 4);
hdcd_process_stereo((hdcd_state_stereo_t *)hdcd_decoder, inputBuffer, (int)(samplesRead / 2));
if (((hdcd_state_stereo_t*)hdcd_decoder)->channel[0].sustain &&
((hdcd_state_stereo_t*)hdcd_decoder)->channel[1].sustain) {
[controller sustainHDCD];
}
gain = 2.0;
bitsPerSample = 32;
bytesReadFromInput = samplesRead * 4;
isUnsigned = NO;
}
else if (bitsPerSample <= 16) {
samplesRead = bytesReadFromInput / 2; samplesRead = bytesReadFromInput / 2;
if (isUnsigned) if (isUnsigned)
convert_u16_to_s16(inputBuffer, samplesRead); convert_u16_to_s16(inputBuffer, samplesRead);
@ -868,7 +897,7 @@ tryagain:
samplesRead = bytesReadFromInput / 4; samplesRead = bytesReadFromInput / 4;
if (isUnsigned) if (isUnsigned)
convert_u32_to_s32(inputBuffer, samplesRead); convert_u32_to_s32(inputBuffer, samplesRead);
convert_s32_to_float(inputBuffer + bytesReadFromInput, inputBuffer, samplesRead, 1.0); convert_s32_to_float(inputBuffer + bytesReadFromInput, inputBuffer, samplesRead, gain);
memmove(inputBuffer, inputBuffer + bytesReadFromInput, samplesRead * sizeof(float)); memmove(inputBuffer, inputBuffer + bytesReadFromInput, samplesRead * sizeof(float));
bitsPerSample = 32; bitsPerSample = 32;
bytesReadFromInput = samplesRead * sizeof(float); bytesReadFromInput = samplesRead * sizeof(float);
@ -1137,6 +1166,13 @@ static float db_to_scale(float db)
return NO; return NO;
// These are really placeholders, as we're doing everything internally now // These are really placeholders, as we're doing everything internally now
if (inputFormat.mBitsPerChannel == 16 &&
inputFormat.mChannelsPerFrame == 2 &&
inputFormat.mSampleRate == 44100) {
// possibly HDCD, run through decoder
hdcd_decoder = calloc(1, sizeof(hdcd_state_stereo_t));
hdcd_reset_stereo((hdcd_state_stereo_t *)hdcd_decoder, 44100);
}
floatFormat = inputFormat; floatFormat = inputFormat;
floatFormat.mFormatFlags = kAudioFormatFlagsNativeFloatPacked; floatFormat.mFormatFlags = kAudioFormatFlagsNativeFloatPacked;
@ -1301,6 +1337,11 @@ static float db_to_scale(float db)
{ {
usleep(500); usleep(500);
} }
if (hdcd_decoder)
{
free(hdcd_decoder);
hdcd_decoder = NULL;
}
if (resampler && resampler_data) if (resampler && resampler_data)
{ {
resampler->free(resampler, resampler_data); resampler->free(resampler, resampler_data);

View File

@ -60,4 +60,6 @@
- (BOOL)isPaused; - (BOOL)isPaused;
- (void)sustainHDCD;
@end @end

View File

@ -171,4 +171,9 @@
{ {
[controller endEqualizer:eq]; [controller endEqualizer:eq];
} }
- (void)sustainHDCD
{
[output sustainHDCD];
}
@end @end

View File

@ -51,6 +51,8 @@
835C88AA2797D4D400E28EAE /* lpc.c in Sources */ = {isa = PBXBuildFile; fileRef = 835C88A62797D4D400E28EAE /* lpc.c */; }; 835C88AA2797D4D400E28EAE /* lpc.c in Sources */ = {isa = PBXBuildFile; fileRef = 835C88A62797D4D400E28EAE /* lpc.c */; };
835C88AB2797D4D400E28EAE /* lpc.h in Headers */ = {isa = PBXBuildFile; fileRef = 835C88A72797D4D400E28EAE /* lpc.h */; }; 835C88AB2797D4D400E28EAE /* lpc.h in Headers */ = {isa = PBXBuildFile; fileRef = 835C88A72797D4D400E28EAE /* lpc.h */; };
835C88AD2797DA5800E28EAE /* util.h in Headers */ = {isa = PBXBuildFile; fileRef = 835C88AC2797DA5800E28EAE /* util.h */; }; 835C88AD2797DA5800E28EAE /* util.h in Headers */ = {isa = PBXBuildFile; fileRef = 835C88AC2797DA5800E28EAE /* util.h */; };
835C88B1279811A500E28EAE /* hdcd_decode2.h in Headers */ = {isa = PBXBuildFile; fileRef = 835C88AF279811A500E28EAE /* hdcd_decode2.h */; };
835C88B2279811A500E28EAE /* hdcd_decode2.c in Sources */ = {isa = PBXBuildFile; fileRef = 835C88B0279811A500E28EAE /* hdcd_decode2.c */; };
8384912718080FF100E7332D /* Logging.h in Headers */ = {isa = PBXBuildFile; fileRef = 8384912618080FF100E7332D /* Logging.h */; }; 8384912718080FF100E7332D /* Logging.h in Headers */ = {isa = PBXBuildFile; fileRef = 8384912618080FF100E7332D /* Logging.h */; };
8389F270278E64590074164C /* config.h in Headers */ = {isa = PBXBuildFile; fileRef = 8389F225278E64590074164C /* config.h */; }; 8389F270278E64590074164C /* config.h in Headers */ = {isa = PBXBuildFile; fileRef = 8389F225278E64590074164C /* config.h */; };
8389F279278E64590074164C /* utf.h in Headers */ = {isa = PBXBuildFile; fileRef = 8389F236278E64590074164C /* utf.h */; }; 8389F279278E64590074164C /* utf.h in Headers */ = {isa = PBXBuildFile; fileRef = 8389F236278E64590074164C /* utf.h */; };
@ -164,6 +166,8 @@
835C88A62797D4D400E28EAE /* lpc.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = lpc.c; sourceTree = "<group>"; }; 835C88A62797D4D400E28EAE /* lpc.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = lpc.c; sourceTree = "<group>"; };
835C88A72797D4D400E28EAE /* lpc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = lpc.h; sourceTree = "<group>"; }; 835C88A72797D4D400E28EAE /* lpc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = lpc.h; sourceTree = "<group>"; };
835C88AC2797DA5800E28EAE /* util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = util.h; path = ThirdParty/lvqcl/util.h; sourceTree = SOURCE_ROOT; }; 835C88AC2797DA5800E28EAE /* util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = util.h; path = ThirdParty/lvqcl/util.h; sourceTree = SOURCE_ROOT; };
835C88AF279811A500E28EAE /* hdcd_decode2.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = hdcd_decode2.h; sourceTree = "<group>"; };
835C88B0279811A500E28EAE /* hdcd_decode2.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = hdcd_decode2.c; sourceTree = "<group>"; };
8384912618080FF100E7332D /* Logging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Logging.h; path = ../../Utils/Logging.h; sourceTree = "<group>"; }; 8384912618080FF100E7332D /* Logging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Logging.h; path = ../../Utils/Logging.h; sourceTree = "<group>"; };
8389F225278E64590074164C /* config.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = config.h; sourceTree = "<group>"; }; 8389F225278E64590074164C /* config.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = config.h; sourceTree = "<group>"; };
8389F228278E64590074164C /* encoding_utf.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = encoding_utf.c; sourceTree = "<group>"; }; 8389F228278E64590074164C /* encoding_utf.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = encoding_utf.c; sourceTree = "<group>"; };
@ -369,6 +373,7 @@
17D21CD80B8BE5B400D1EBDE /* ThirdParty */ = { 17D21CD80B8BE5B400D1EBDE /* ThirdParty */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
835C88AE279811A500E28EAE /* hdcd */,
835C88A22797D4D400E28EAE /* lvqcl */, 835C88A22797D4D400E28EAE /* lvqcl */,
8389F224278E64590074164C /* RetroArch */, 8389F224278E64590074164C /* RetroArch */,
17D21DC40B8BE79700D1EBDE /* CoreAudioUtils */, 17D21DC40B8BE79700D1EBDE /* CoreAudioUtils */,
@ -443,6 +448,15 @@
path = License; path = License;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
835C88AE279811A500E28EAE /* hdcd */ = {
isa = PBXGroup;
children = (
835C88AF279811A500E28EAE /* hdcd_decode2.h */,
835C88B0279811A500E28EAE /* hdcd_decode2.c */,
);
path = hdcd;
sourceTree = "<group>";
};
8389F224278E64590074164C /* RetroArch */ = { 8389F224278E64590074164C /* RetroArch */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -766,6 +780,7 @@
839366671815923C006DD712 /* CogPluginMulti.h in Headers */, 839366671815923C006DD712 /* CogPluginMulti.h in Headers */,
17ADB13C0B97926D00257CA2 /* AudioSource.h in Headers */, 17ADB13C0B97926D00257CA2 /* AudioSource.h in Headers */,
8389F292278E64590074164C /* s16_to_float.h in Headers */, 8389F292278E64590074164C /* s16_to_float.h in Headers */,
835C88B1279811A500E28EAE /* hdcd_decode2.h in Headers */,
8389F279278E64590074164C /* utf.h in Headers */, 8389F279278E64590074164C /* utf.h in Headers */,
8389F288278E64590074164C /* retro_environment.h in Headers */, 8389F288278E64590074164C /* retro_environment.h in Headers */,
83A44A02279119B50049B6E2 /* RefillNode.h in Headers */, 83A44A02279119B50049B6E2 /* RefillNode.h in Headers */,
@ -864,6 +879,7 @@
17D21CE00B8BE5B400D1EBDE /* VirtualRingBuffer.m in Sources */, 17D21CE00B8BE5B400D1EBDE /* VirtualRingBuffer.m in Sources */,
8389F29D278E64590074164C /* s16_to_float.c in Sources */, 8389F29D278E64590074164C /* s16_to_float.c in Sources */,
8389F29B278E64590074164C /* sinc_resampler.c in Sources */, 8389F29B278E64590074164C /* sinc_resampler.c in Sources */,
835C88B2279811A500E28EAE /* hdcd_decode2.c in Sources */,
8389F299278E64590074164C /* memalign.c in Sources */, 8389F299278E64590074164C /* memalign.c in Sources */,
17D21CF40B8BE5EF00D1EBDE /* Semaphore.m in Sources */, 17D21CF40B8BE5EF00D1EBDE /* Semaphore.m in Sources */,
8347C7422796C58800FA8A7D /* NSFileHandle+CreateFile.m in Sources */, 8347C7422796C58800FA8A7D /* NSFileHandle+CreateFile.m in Sources */,

View File

@ -42,6 +42,7 @@
BOOL eqEnabled; BOOL eqEnabled;
atomic_long bytesRendered; atomic_long bytesRendered;
atomic_long bytesHdcdSustained;
BOOL listenerapplied; BOOL listenerapplied;
@ -76,4 +77,6 @@
- (void)setEqualizerEnabled:(BOOL)enabled; - (void)setEqualizerEnabled:(BOOL)enabled;
- (void)sustainHDCD;
@end @end

View File

@ -121,7 +121,19 @@ static OSStatus renderCallback( void *inRefCon, AudioUnitRenderActionFlags *ioAc
} }
} }
scaleBuffersByVolume(ioData, _self->volume); float volumeScale = 1.0;
long sustained = atomic_load_explicit(&_self->bytesHdcdSustained, memory_order_relaxed);
if (sustained) {
if (sustained < amountRead) {
atomic_store(&_self->bytesHdcdSustained, 0);
}
else {
atomic_fetch_sub(&_self->bytesHdcdSustained, amountRead);
}
volumeScale = 0.5;
}
scaleBuffersByVolume(ioData, _self->volume * volumeScale);
if (amountRead < amountToRead) if (amountRead < amountToRead)
{ {
@ -150,6 +162,7 @@ static OSStatus renderCallback( void *inRefCon, AudioUnitRenderActionFlags *ioAc
started = NO; started = NO;
atomic_init(&bytesRendered, 0); atomic_init(&bytesRendered, 0);
atomic_init(&bytesHdcdSustained, 0);
writeSemaphore = [[Semaphore alloc] init]; writeSemaphore = [[Semaphore alloc] init];
readSemaphore = [[Semaphore alloc] init]; readSemaphore = [[Semaphore alloc] init];
@ -767,4 +780,9 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const
paused = NO; paused = NO;
} }
- (void)sustainHDCD
{
atomic_store(&bytesHdcdSustained, deviceFormat.mSampleRate * 10 * sizeof(float) * 2);
}
@end @end

1308
Audio/ThirdParty/hdcd/hdcd_decode2.c vendored Normal file

File diff suppressed because it is too large Load Diff

125
Audio/ThirdParty/hdcd/hdcd_decode2.h vendored Normal file
View File

@ -0,0 +1,125 @@
/*
Copyright (C) 2010-2017, Christopher Snowhill,
All rights reserved.
Optimizations by Gumboot
Additional work by Burt P.
Original code reverse engineered from HDCD decoder library by Christopher Key,
which was likely reverse engineered from Windows Media Player.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. The names of its contributors may not be used to endorse or promote
products derived from this software without specific prior written
permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
HDCD is High Definition Compatible Digital
http://wiki.hydrogenaud.io/index.php?title=High_Definition_Compatible_Digital
More information about HDCD-encoded audio CDs:
http://www.audiomisc.co.uk/HFN/HDCD/Enigma.html
http://www.audiomisc.co.uk/HFN/HDCD/Examined.html
*/
#ifndef _HDCD_DECODE2_H_
#define _HDCD_DECODE2_H_
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
uint64_t window;
unsigned char readahead;
/* arg is set when a packet prefix is found.
* control is the active control code, where
* bit 0-3: target_gain, 4-bit (3.1) fixed-point value
* bit 4 : peak_extend
* bit 5 : transient_filter
* bit 6,7: always zero */
unsigned char arg, control;
unsigned sustain, sustain_reset; /* code detect timer */
int running_gain; /* 11-bit (3.8) fixed point, extended from target_gain */
/* counters */
int code_counterA; /* 8-bit format packet */
int code_counterA_almost; /* looks like an A code, but a bit expected to be 0 is 1 */
int code_counterB; /* 16-bit format packet, 8-bit code, 8-bit XOR of code */
int code_counterB_checkfails; /* looks like a B code, but doesn't pass the XOR check */
int code_counterC; /* packet prefix was found, expect a code */
int code_counterC_unmatched; /* told to look for a code, but didn't find one */
int count_peak_extend; /* valid packets where peak_extend was enabled */
int count_transient_filter; /* valid packets where filter was detected */
/* target_gain is a 4-bit (3.1) fixed-point value, always
* negative, but stored positive.
* The 16 possible values range from -7.5 to 0.0 dB in
* steps of 0.5, but no value below -6.0 dB should appear. */
int gain_counts[16]; /* for cursiosity, mostly */
int max_gain;
/* occurences of code detect timer expiring without detecting
* a code. -1 for timer never set. */
int count_sustain_expired;
} hdcd_state_t;
typedef struct {
hdcd_state_t channel[2];
int val_target_gain;
int count_tg_mismatch;
} hdcd_state_stereo_t;
typedef enum {
HDCD_PE_NEVER = 0,
HDCD_PE_INTERMITTENT = 1,
HDCD_PE_PERMANENT = 2,
} hdcd_pe_t;
typedef struct {
int hdcd_detected;
int errors; /* detectable errors */
hdcd_pe_t peak_extend;
int uses_transient_filter;
float max_gain_adjustment; /* in dB, expected in the range -7.5 to 0.0 */
} hdcd_detection_data_t;
void hdcd_reset(hdcd_state_t *state, unsigned rate);
void hdcd_process(hdcd_state_t *state, int *samples, int count, int stride);
void hdcd_reset_stereo(hdcd_state_stereo_t *state, unsigned rate);
void hdcd_process_stereo(hdcd_state_stereo_t *state, int *samples, int count);
void hdcd_detect_reset(hdcd_detection_data_t *detect);
void hdcd_detect_str(hdcd_detection_data_t *detect, char *str); /* char str[256] should be enough */
/* there isn't a non-stereo version */
void hdcd_detect_stereo(hdcd_state_stereo_t *state, hdcd_detection_data_t *detect);
#ifdef __cplusplus
}
#endif
#endif