1/*****************************************************************************************
2Monkey's Audio MACLib.h (include for using MACLib.lib in your projects)
3Copyright (C) 2000-2003 by Matthew T. Ashland   All Rights Reserved.
4
5Overview:
6
7There are two main interfaces... create one (using CreateIAPExxx) and go to town:
8
9    IAPECompress - for creating APE files
10    IAPEDecompress - for decompressing and analyzing APE files
11
12Note(s):
13
14Unless otherwise specified, functions return ERROR_SUCCESS (0) on success and an
15error code on failure.
16
17The terminology "Sample" refers to a single sample value, and "Block" refers
18to a collection    of "Channel" samples.  For simplicity, MAC typically uses blocks
19everywhere so that channel mis-alignment cannot happen. (i.e. on a CD, a sample is
202 bytes and a block is 4 bytes ([2 bytes per sample] * [2 channels] = 4 bytes))
21
22Questions / Suggestions:
23
24Please direct questions or comments to the Monkey's Audio developers board:
25http://www.monkeysaudio.com/cgi-bin/YaBB/YaBB.cgi -> Developers
26or, if necessary, matt @ monkeysaudio.com
27*****************************************************************************************/
28
29#ifndef APE_MACLIB_H
30#define APE_MACLIB_H
31
32/*************************************************************************************************
33APE File Format Overview: (pieces in order -- only valid for the latest version APE files)
34
35    JUNK - any amount of "junk" before the APE_DESCRIPTOR (so people that put ID3v2 tags on the files aren't hosed)
36    APE_DESCRIPTOR - defines the sizes (and offsets) of all the pieces, as well as the MD5 checksum
37    APE_HEADER - describes all of the necessary information about the APE file
38    SEEK TABLE - the table that represents seek offsets [optional]
39    HEADER DATA - the pre-audio data from the original file [optional]
40    APE FRAMES - the actual compressed audio (broken into frames for seekability)
41    TERMINATING DATA - the post-audio data from the original file [optional]
42    TAG - describes all the properties of the file [optional]
43
44Notes:
45
46    Junk:
47
48    This block may not be supported in the future, so don't write any software that adds meta data
49    before the APE_DESCRIPTOR.  Please use the APE Tag for any meta data.
50
51    Seek Table:
52
53    A 32-bit unsigned integer array of offsets from the header to the frame data.  May become "delta"
54    values someday to better suit huge files.
55
56    MD5 Hash:
57
58    Since the header is the last part written to an APE file, you must calculate the MD5 checksum out of order.
59    So, you first calculate from the tail of the seek table to the end of the terminating data.
60    Then, go back and do from the end of the descriptor to the tail of the seek table.
61    You may wish to just cache the header data when starting and run it last, so you don't
62    need to seek back in the I/O.
63*************************************************************************************************/
64
65#include "NoWindows.h"
66#include "All.h"
67
68/*****************************************************************************************
69Defines
70*****************************************************************************************/
71#define COMPRESSION_LEVEL_FAST          1000
72#define COMPRESSION_LEVEL_NORMAL        2000
73#define COMPRESSION_LEVEL_HIGH          3000
74#define COMPRESSION_LEVEL_EXTRA_HIGH    4000
75#define COMPRESSION_LEVEL_INSANE        5000
76
77#define MAC_FORMAT_FLAG_8_BIT                 1    // is 8-bit [OBSOLETE]
78#define MAC_FORMAT_FLAG_CRC                   2    // uses the new CRC32 error detection [OBSOLETE]
79#define MAC_FORMAT_FLAG_HAS_PEAK_LEVEL        4    // uint32 nPeakLevel after the header [OBSOLETE]
80#define MAC_FORMAT_FLAG_24_BIT                8    // is 24-bit [OBSOLETE]
81#define MAC_FORMAT_FLAG_HAS_SEEK_ELEMENTS    16    // has the number of seek elements after the peak level
82#define MAC_FORMAT_FLAG_CREATE_WAV_HEADER    32    // create the wave header on decompression (not stored)
83
84#define CREATE_WAV_HEADER_ON_DECOMPRESSION    -1
85#define MAX_AUDIO_BYTES_UNKNOWN -1
86
87typedef void (__stdcall * APE_PROGRESS_CALLBACK) (int);
88
89/*****************************************************************************************
90WAV header structure
91*****************************************************************************************/
92struct WAVE_HEADER
93{
94    // RIFF header
95    char cRIFFHeader[4];
96    unsigned int nRIFFBytes;
97
98    // data type
99    char cDataTypeID[4];
100
101    // wave format
102    char cFormatHeader[4];
103    unsigned int nFormatBytes;
104
105    unsigned short nFormatTag;
106    unsigned short nChannels;
107    unsigned int nSamplesPerSec;
108    unsigned int nAvgBytesPerSec;
109    unsigned short nBlockAlign;
110    unsigned short nBitsPerSample;
111
112    // data chunk header
113    char cDataHeader[4];
114    unsigned int nDataBytes;
115};
116
117/*****************************************************************************************
118APE_DESCRIPTOR structure (file header that describes lengths, offsets, etc.)
119*****************************************************************************************/
120struct APE_DESCRIPTOR
121{
122    char    cID[4];                             // should equal 'MAC '
123    uint16  nVersion;                           // version number * 1000 (3.81 = 3810)
124
125    uint32  nDescriptorBytes;                   // the number of descriptor bytes (allows later expansion of this header)
126    uint32  nHeaderBytes;                       // the number of header APE_HEADER bytes
127    uint32  nSeekTableBytes;                    // the number of bytes of the seek table
128    uint32  nHeaderDataBytes;                   // the number of header data bytes (from original file)
129    uint32  nAPEFrameDataBytes;                 // the number of bytes of APE frame data
130    uint32  nAPEFrameDataBytesHigh;             // the high order number of APE frame data bytes
131    uint32  nTerminatingDataBytes;              // the terminating data of the file (not including tag data)
132
133    uint8   cFileMD5[16];                       // the MD5 hash of the file (see notes for usage... it's a littly tricky)
134};
135
136/*****************************************************************************************
137APE_HEADER structure (describes the format, duration, etc. of the APE file)
138*****************************************************************************************/
139struct APE_HEADER
140{
141    uint16    nCompressionLevel;                 // the compression level (see defines I.E. COMPRESSION_LEVEL_FAST)
142    uint16    nFormatFlags;                      // any format flags (for future use)
143
144    uint32    nBlocksPerFrame;                   // the number of audio blocks in one frame
145    uint32    nFinalFrameBlocks;                 // the number of audio blocks in the final frame
146    uint32    nTotalFrames;                      // the total number of frames
147
148    uint16    nBitsPerSample;                    // the bits per sample (typically 16)
149    uint16    nChannels;                         // the number of channels (1 or 2)
150    uint32    nSampleRate;                       // the sample rate (typically 44100)
151};
152
153/*************************************************************************************************
154Classes (fully defined elsewhere)
155*************************************************************************************************/
156class CIO;
157class CInputSource;
158class CAPEInfo;
159
160/*************************************************************************************************
161IAPEDecompress fields - used when querying for information
162
163Note(s):
164-the distinction between APE_INFO_XXXX and APE_DECOMPRESS_XXXX is that the first is querying the APE
165information engine, and the other is querying the decompressor, and since the decompressor can be
166a range of an APE file (for APL), differences will arise.  Typically, use the APE_DECOMPRESS_XXXX
167fields when querying for info about the length, etc. so APL will work properly.
168(i.e. (APE_INFO_TOTAL_BLOCKS != APE_DECOMPRESS_TOTAL_BLOCKS) for APL files)
169*************************************************************************************************/
170enum APE_DECOMPRESS_FIELDS
171{
172    APE_INFO_FILE_VERSION = 1000,               // version of the APE file * 1000 (3.93 = 3930) [ignored, ignored]
173    APE_INFO_COMPRESSION_LEVEL = 1001,          // compression level of the APE file [ignored, ignored]
174    APE_INFO_FORMAT_FLAGS = 1002,               // format flags of the APE file [ignored, ignored]
175    APE_INFO_SAMPLE_RATE = 1003,                // sample rate (Hz) [ignored, ignored]
176    APE_INFO_BITS_PER_SAMPLE = 1004,            // bits per sample [ignored, ignored]
177    APE_INFO_BYTES_PER_SAMPLE = 1005,           // number of bytes per sample [ignored, ignored]
178    APE_INFO_CHANNELS = 1006,                   // channels [ignored, ignored]
179    APE_INFO_BLOCK_ALIGN = 1007,                // block alignment [ignored, ignored]
180    APE_INFO_BLOCKS_PER_FRAME = 1008,           // number of blocks in a frame (frames are used internally)  [ignored, ignored]
181    APE_INFO_FINAL_FRAME_BLOCKS = 1009,         // blocks in the final frame (frames are used internally) [ignored, ignored]
182    APE_INFO_TOTAL_FRAMES = 1010,               // total number frames (frames are used internally) [ignored, ignored]
183    APE_INFO_WAV_HEADER_BYTES = 1011,           // header bytes of the decompressed WAV [ignored, ignored]
184    APE_INFO_WAV_TERMINATING_BYTES = 1012,      // terminating bytes of the decompressed WAV [ignored, ignored]
185    APE_INFO_WAV_DATA_BYTES = 1013,             // data bytes of the decompressed WAV [ignored, ignored]
186    APE_INFO_WAV_TOTAL_BYTES = 1014,            // total bytes of the decompressed WAV [ignored, ignored]
187    APE_INFO_APE_TOTAL_BYTES = 1015,            // total bytes of the APE file [ignored, ignored]
188    APE_INFO_TOTAL_BLOCKS = 1016,               // total blocks of audio data [ignored, ignored]
189    APE_INFO_LENGTH_MS = 1017,                  // length in ms (1 sec = 1000 ms) [ignored, ignored]
190    APE_INFO_AVERAGE_BITRATE = 1018,            // average bitrate of the APE [ignored, ignored]
191    APE_INFO_FRAME_BITRATE = 1019,              // bitrate of specified APE frame [frame index, ignored]
192    APE_INFO_DECOMPRESSED_BITRATE = 1020,       // bitrate of the decompressed WAV [ignored, ignored]
193    APE_INFO_PEAK_LEVEL = 1021,                 // peak audio level (obsolete) (-1 is unknown) [ignored, ignored]
194    APE_INFO_SEEK_BIT = 1022,                   // bit offset [frame index, ignored]
195    APE_INFO_SEEK_BYTE = 1023,                  // byte offset [frame index, ignored]
196    APE_INFO_WAV_HEADER_DATA = 1024,            // error code [buffer *, max bytes]
197    APE_INFO_WAV_TERMINATING_DATA = 1025,       // error code [buffer *, max bytes]
198    APE_INFO_WAVEFORMATEX = 1026,               // error code [waveformatex *, ignored]
199    APE_INFO_IO_SOURCE = 1027,                  // I/O source (CIO *) [ignored, ignored]
200    APE_INFO_FRAME_BYTES = 1028,                // bytes (compressed) of the frame [frame index, ignored]
201    APE_INFO_FRAME_BLOCKS = 1029,               // blocks in a given frame [frame index, ignored]
202    APE_INFO_TAG = 1030,                        // point to tag (CAPETag *) [ignored, ignored]
203
204    APE_DECOMPRESS_CURRENT_BLOCK = 2000,        // current block location [ignored, ignored]
205    APE_DECOMPRESS_CURRENT_MS = 2001,           // current millisecond location [ignored, ignored]
206    APE_DECOMPRESS_TOTAL_BLOCKS = 2002,         // total blocks in the decompressors range [ignored, ignored]
207    APE_DECOMPRESS_LENGTH_MS = 2003,            // total blocks in the decompressors range [ignored, ignored]
208    APE_DECOMPRESS_CURRENT_BITRATE = 2004,      // current bitrate [ignored, ignored]
209    APE_DECOMPRESS_AVERAGE_BITRATE = 2005,      // average bitrate (works with ranges) [ignored, ignored]
210
211    APE_INTERNAL_INFO = 3000,                   // for internal use -- don't use (returns APE_FILE_INFO *) [ignored, ignored]
212};
213
214/*************************************************************************************************
215IAPEDecompress - interface for working with existing APE files (decoding, seeking, analyzing, etc.)
216*************************************************************************************************/
217class IAPEDecompress
218{
219public:
220
221    // destructor (needed so implementation's destructor will be called)
222    virtual ~IAPEDecompress() {}
223
224    /*********************************************************************************************
225    * Decompress / Seek
226    *********************************************************************************************/
227
228    //////////////////////////////////////////////////////////////////////////////////////////////
229    // GetData(...) - gets raw decompressed audio
230    //
231    // Parameters:
232    //    char * pBuffer
233    //        a pointer to a buffer to put the data into
234    //    int nBlocks
235    //        the number of audio blocks desired (see note at intro about blocks vs. samples)
236    //    int * pBlocksRetrieved
237    //        the number of blocks actually retrieved (could be less at end of file or on critical failure)
238    //////////////////////////////////////////////////////////////////////////////////////////////
239    virtual int GetData(char * pBuffer, int nBlocks, int * pBlocksRetrieved) = 0;
240
241    //////////////////////////////////////////////////////////////////////////////////////////////
242    // Seek(...) - seeks
243    //
244    // Parameters:
245    //    int nBlockOffset
246    //        the block to seek to (see note at intro about blocks vs. samples)
247    //////////////////////////////////////////////////////////////////////////////////////////////
248    virtual int Seek(int nBlockOffset) = 0;
249
250    /*********************************************************************************************
251    * Get Information
252    *********************************************************************************************/
253
254    //////////////////////////////////////////////////////////////////////////////////////////////
255    // GetInfo(...) - get information about the APE file or the state of the decompressor
256    //
257    // Parameters:
258    //    APE_DECOMPRESS_FIELDS Field
259    //        the field we're querying (see APE_DECOMPRESS_FIELDS above for more info)
260    //    int nParam1
261    //        generic parameter... usage is listed in APE_DECOMPRESS_FIELDS
262    //    int nParam2
263    //        generic parameter... usage is listed in APE_DECOMPRESS_FIELDS
264    //////////////////////////////////////////////////////////////////////////////////////////////
265    virtual int GetInfo(APE_DECOMPRESS_FIELDS Field, int nParam1 = 0, int nParam2 = 0) = 0;
266};
267
268/*************************************************************************************************
269IAPECompress - interface for creating APE files
270
271Usage:
272
273    To create an APE file, you Start(...), then add data (in a variety of ways), then Finish(...)
274*************************************************************************************************/
275class IAPECompress
276{
277public:
278
279    // destructor (needed so implementation's destructor will be called)
280    virtual ~IAPECompress() {}
281
282    /*********************************************************************************************
283    * Start
284    *********************************************************************************************/
285
286    //////////////////////////////////////////////////////////////////////////////////////////////
287    // Start(...) / StartEx(...) - starts encoding
288    //
289    // Parameters:
290    //    CIO * pioOutput / const str_utf16 * pFilename
291    //        the output... either a filename or an I/O source
292    //    WAVEFORMATEX * pwfeInput
293    //        format of the audio to encode (use FillWaveFormatEx() if necessary)
294    //    int nMaxAudioBytes
295    //        the absolute maximum audio bytes that will be encoded... encoding fails with a
296    //        ERROR_APE_COMPRESS_TOO_MUCH_DATA if you attempt to encode more than specified here
297    //        (if unknown, use MAX_AUDIO_BYTES_UNKNOWN to allocate as much storage in the seek table as
298    //        possible... limit is then 2 GB of data (~4 hours of CD music)... this wastes around
299    //        30kb, so only do it if completely necessary)
300    //    int nCompressionLevel
301    //        the compression level for the APE file (fast - extra high)
302    //        (note: extra-high is much slower for little gain)
303    //    const void * pHeaderData
304    //        a pointer to a buffer containing the WAV header (data before the data block in the WAV)
305    //        (note: use NULL for on-the-fly encoding... see next parameter)
306    //    int nHeaderBytes
307    //        number of bytes in the header data buffer (use CREATE_WAV_HEADER_ON_DECOMPRESSION and
308    //        NULL for the pHeaderData and MAC will automatically create the appropriate WAV header
309    //        on decompression)
310    //////////////////////////////////////////////////////////////////////////////////////////////
311
312    virtual int Start(const str_utf16 * pOutputFilename, const WAVEFORMATEX * pwfeInput,
313        int nMaxAudioBytes = MAX_AUDIO_BYTES_UNKNOWN, int nCompressionLevel = COMPRESSION_LEVEL_NORMAL,
314        const void * pHeaderData = NULL, int nHeaderBytes = CREATE_WAV_HEADER_ON_DECOMPRESSION) = 0;
315
316    virtual int StartEx(CIO * pioOutput, const WAVEFORMATEX * pwfeInput,
317        int nMaxAudioBytes = MAX_AUDIO_BYTES_UNKNOWN, int nCompressionLevel = COMPRESSION_LEVEL_NORMAL,
318        const void * pHeaderData = NULL, int nHeaderBytes = CREATE_WAV_HEADER_ON_DECOMPRESSION) = 0;
319
320    /*********************************************************************************************
321    * Add / Compress Data
322    *    - there are 3 ways to add data:
323    *        1) simple call AddData(...)
324    *        2) lock MAC's buffer, copy into it, and unlock (LockBuffer(...) / UnlockBuffer(...))
325    *        3) from an I/O source (AddDataFromInputSource(...))
326    *********************************************************************************************/
327
328    //////////////////////////////////////////////////////////////////////////////////////////////
329    // AddData(...) - adds data to the encoder
330    //
331    // Parameters:
332    //    unsigned char * pData
333    //        a pointer to a buffer containing the raw audio data
334    //    int nBytes
335    //        the number of bytes in the buffer
336    //////////////////////////////////////////////////////////////////////////////////////////////
337    virtual int AddData(unsigned char * pData, int nBytes) = 0;
338
339    //////////////////////////////////////////////////////////////////////////////////////////////
340    // GetBufferBytesAvailable(...) - returns the number of bytes available in the buffer
341    //    (helpful when locking)
342    //////////////////////////////////////////////////////////////////////////////////////////////
343    virtual int GetBufferBytesAvailable() = 0;
344
345    //////////////////////////////////////////////////////////////////////////////////////////////
346    // LockBuffer(...) - locks MAC's buffer so we can copy into it
347    //
348    // Parameters:
349    //    int * pBytesAvailable
350    //        returns the number of bytes available in the buffer (DO NOT COPY MORE THAN THIS IN)
351    //
352    // Return:
353    //    pointer to the buffer (add at that location)
354    //////////////////////////////////////////////////////////////////////////////////////////////
355    virtual unsigned char * LockBuffer(int * pBytesAvailable) = 0;
356
357    //////////////////////////////////////////////////////////////////////////////////////////////
358    // UnlockBuffer(...) - releases the buffer
359    //
360    // Parameters:
361    //    int nBytesAdded
362    //        the number of bytes copied into the buffer
363    //    BOOL bProcess
364    //        whether MAC should process as much as possible of the buffer
365    //////////////////////////////////////////////////////////////////////////////////////////////
366    virtual int UnlockBuffer(int nBytesAdded, BOOL bProcess = TRUE) = 0;
367
368
369    //////////////////////////////////////////////////////////////////////////////////////////////
370    // AddDataFromInputSource(...) - use a CInputSource (input source) to add data
371    //
372    // Parameters:
373    //    CInputSource * pInputSource
374    //        a pointer to the input source
375    //    int nMaxBytes
376    //        the maximum number of bytes to let MAC add (-1 if MAC can add any amount)
377    //    int * pBytesAdded
378    //        returns the number of bytes added from the I/O source
379    //////////////////////////////////////////////////////////////////////////////////////////////
380    virtual int AddDataFromInputSource(CInputSource * pInputSource, int nMaxBytes = -1, int * pBytesAdded = NULL) = 0;
381
382    /*********************************************************************************************
383    * Finish / Kill
384    *********************************************************************************************/
385
386    //////////////////////////////////////////////////////////////////////////////////////////////
387    // Finish(...) - ends encoding and finalizes the file
388    //
389    // Parameters:
390    //    unsigned char * pTerminatingData
391    //        a pointer to a buffer containing the information to place at the end of the APE file
392    //        (comprised of the WAV terminating data (data after the data block in the WAV) followed
393    //        by any tag information)
394    //    int nTerminatingBytes
395    //        number of bytes in the terminating data buffer
396    //    int nWAVTerminatingBytes
397    //        the number of bytes of the terminating data buffer that should be appended to a decoded
398    //        WAV file (it's basically nTerminatingBytes - the bytes that make up the tag)
399    //////////////////////////////////////////////////////////////////////////////////////////////
400    virtual int Finish(unsigned char * pTerminatingData, int nTerminatingBytes, int nWAVTerminatingBytes) = 0;
401
402    //////////////////////////////////////////////////////////////////////////////////////////////
403    // Kill(...) - stops encoding and deletes the output file
404    // --- NOT CURRENTLY IMPLEMENTED ---
405    //////////////////////////////////////////////////////////////////////////////////////////////
406    virtual int Kill() = 0;
407};
408
409/*************************************************************************************************
410Functions to create the interfaces
411
412Usage:
413    Interface creation returns a NULL pointer on failure (and fills error code if it was passed in)
414
415Usage example:
416    int nErrorCode;
417    IAPEDecompress * pAPEDecompress = CreateIAPEDecompress("c:\\1.ape", &nErrorCode);
418    if (pAPEDecompress == NULL)
419    {
420        // failure... nErrorCode will have specific code
421    }
422
423*************************************************************************************************/
424extern "C"
425{
426    IAPEDecompress * __stdcall CreateIAPEDecompress(const str_utf16 * pFilename, int * pErrorCode = NULL);
427    IAPEDecompress * __stdcall CreateIAPEDecompressEx(CIO * pIO, int * pErrorCode = NULL);
428    IAPEDecompress * __stdcall CreateIAPEDecompressEx2(CAPEInfo * pAPEInfo, int nStartBlock = -1, int nFinishBlock = -1, int * pErrorCode = NULL);
429    IAPECompress * __stdcall CreateIAPECompress(int * pErrorCode = NULL);
430}
431
432/*************************************************************************************************
433Simple functions - see the SDK sample projects for usage examples
434*************************************************************************************************/
435extern "C"
436{
437    // process whole files
438    DLLEXPORT int __stdcall CompressFile(const str_ansi * pInputFilename, const str_ansi * pOutputFilename, int nCompressionLevel = COMPRESSION_LEVEL_NORMAL, int * pPercentageDone = NULL, APE_PROGRESS_CALLBACK ProgressCallback = 0, int * pKillFlag = NULL);
439    DLLEXPORT int __stdcall DecompressFile(const str_ansi * pInputFilename, const str_ansi * pOutputFilename, int * pPercentageDone, APE_PROGRESS_CALLBACK ProgressCallback, int * pKillFlag);
440    DLLEXPORT int __stdcall ConvertFile(const str_ansi * pInputFilename, const str_ansi * pOutputFilename, int nCompressionLevel, int * pPercentageDone, APE_PROGRESS_CALLBACK ProgressCallback, int * pKillFlag);
441    DLLEXPORT int __stdcall VerifyFile(const str_ansi * pInputFilename, int * pPercentageDone, APE_PROGRESS_CALLBACK ProgressCallback, int * pKillFlag, BOOL bQuickVerifyIfPossible);
442
443    DLLEXPORT int __stdcall CompressFileW(const str_utf16 * pInputFilename, const str_utf16 * pOutputFilename, int nCompressionLevel = COMPRESSION_LEVEL_NORMAL, int * pPercentageDone = NULL, APE_PROGRESS_CALLBACK ProgressCallback = 0, int * pKillFlag = NULL);
444    DLLEXPORT int __stdcall DecompressFileW(const str_utf16 * pInputFilename, const str_utf16 * pOutputFilename, int * pPercentageDone, APE_PROGRESS_CALLBACK ProgressCallback, int * pKillFlag);
445    DLLEXPORT int __stdcall ConvertFileW(const str_utf16 * pInputFilename, const str_utf16 * pOutputFilename, int nCompressionLevel, int * pPercentageDone, APE_PROGRESS_CALLBACK ProgressCallback, int * pKillFlag);
446    DLLEXPORT int __stdcall VerifyFileW(const str_utf16 * pInputFilename, int * pPercentageDone, APE_PROGRESS_CALLBACK ProgressCallback, int * pKillFlag, BOOL bQuickVerifyIfPossible = FALSE);
447
448    // helper functions
449    DLLEXPORT int __stdcall FillWaveFormatEx(WAVEFORMATEX * pWaveFormatEx, int nSampleRate = 44100, int nBitsPerSample = 16, int nChannels = 2);
450    DLLEXPORT int __stdcall FillWaveHeader(WAVE_HEADER * pWAVHeader, int nAudioBytes, WAVEFORMATEX * pWaveFormatEx, int nTerminatingBytes = 0);
451}
452
453#endif // #ifndef APE_MACLIB_H
454