Copyright 2007 by Thomas Becker, D-49080 Osnabrück.
All rights reserved.
This example shows how the content of a complete TAK file is sequentially decoded and written to a new Wave file.
The example is intended primarily to illustrate the use of the relevant SDK functions. Accordingly the code is stripped down to a minimum. It includes neither proper dealing with errors nor appropriate resource protection. Furthermore, some help functions (writing to a Wave file, for example) are only placeholders without any implementation.
Partially placeholder functions without implementation.
void Proto (const char * ALine)
Writes the text line ALine to the screen or a file.
void Error (const char * AWhat) { Proto (AWhat); abort (); /* Stop program execution. */ }
Shows the error message AWhat and stops program execution.
void DecoError (TtakSeekableStreamDecoder ADecoder) { char ErrorMsg[tak_ErrorStringSizeMax]; tak_SSD_GetErrorString (tak_SSD_State (ADecoder), ErrorMsg, tak_ErrorStringSizeMax); Error (ErrorMsg); }
This procedure is called at any error, supplied the affected decoder object as a parameter. It does the following:
void WriteToWave (void * ABuf, TtakInt32 AByteNum)
Writes AByteNum bytes out of ABuf to the Wave file.
void CreateWaveHeader (const Ttak_str_StreamInfo * AStreamInfo)
Creates a Wave header according to audio format and number of samples specified by AStreamInfo and writes it to the Wave file.
Note: This procedure calls a few functions explained in detail in the subsequent paragraphs.
void DecodeFile () { TtakSSDOptions Options; TtakSeekableStreamDecoder Decoder; Ttak_str_StreamInfo StreamInfo; Ttak_str_SimpleWaveDataHeader WaveMetaDesc; char * WaveMetaBuf; Options.Cpu = tak_Cpu_Any; Options.Flags = tak_ssd_opt_SequentialRead; Decoder = tak_SSD_Create_FromFile ("d:\YourFilePath.tak", &Options, NULL, NULL); if (Decoder != NULL) { if (tak_SSD_Valid (Decoder) == tak_True) { if (tak_SSD_GetStreamInfo (ADecoder, &StreamInfo) != tak_res_Ok) DecoError (Decoder); /* Can virtually never happen if the decoder was valid after its construction... */ ReadWaveMetaData (Decoder, &WaveMetaDesc, &WaveMetaBuf); if (WaveMetaBuf != NULL) WriteWaveMetaDataHead (&WaveMetaDesc, WaveMetaBuf); /* If WaveMetaBuf != NULL, the Wave metadata was read successfully. */ else CreateWaveHeader (&StreamInfo); /* No Wave metadata available, we have to create an appropriate Wave header. */ DecodeAudioData (&StreamInfo); if (WaveMetaBuf != NULL) { WriteWaveMetaDataTail (&WaveMetaDesc, WaveMetaBuf); DestroyWaveMetaData (&WaveMetaDesc, &WaveMetaBuf); } } else DecoError (Decoder); tak_SSD_Destroy (Decoder); } else Proto ("System error: Unable to create Decoder!"); }
First the decoder options are defined. The Cpu field should always be set to tak_Cpu_Any for the decoder to use all available CPU features for optimizations. Features not available (such as SSE, for example) are deactivated automatically.
The Flags field contains a combination of option constants. tak_ssd_opt_SequentialRead gives maximum performance if sequential file reading is used exclusively.
By default, audio data of damaged frames is replaced with silence. If you want this data to be skipped, the tak_ssd_opt_SkipDamagedFrames flag is additionally required:
Options.Flags = tak_ssd_opt_SequentialRead | tak_ssd_opt_SkipDamagedFrames;
It is recommended to offer an appropriate option to the end user of the software.
Important notes:
The result of the decoder constructor tak_SSD_Create_FromFile is only NULL if a serious system error occurred (such as OutOfMemory). Because of this, after a successful construction it is additionally required to use tak_SSD_Valid to test for decoder errors such as an error while opening the source file.
tak_SSD_GetStreamInfo reads the StreamInfo containing some useful information that is required later on.
After decoding is finished, tak_SSD_Destroy destroys the decoder construct.
By default, the metadata block of a TAK file contains the original Wave file header and - where applicable - metadata attached to the audio data. Both will be read now.
void DestroyWaveMetaData (const Ttak_str_SimpleWaveDataHeader * ADesc, char ** ABuf) /* If ABuf != NULL, the memory allocated by it is deallocated according to the size information in ADesc. ABuf is then set to NULL. */ { if (*ABuf != NULL) { free (*ABuf); *ABuf = NULL; } } void ReadWaveMetaData (TtakSeekableStreamDecoder ADecoder, Ttak_str_SimpleWaveDataHeader * ADesc, char ** ABuf) /* Reads the Wave metadata description in ADesc and the metadata itself in ABuf. Success results in ABuf != NULL after return. Possibly ABuf has to be deallocated later, using DestroyWaveMetaData. */ { *ABuf = NULL; if (tak_SSD_GetSimpleWaveDataDesc (ADecoder, ADesc) == tak_res_Ok) { /* The Wave metadata description was read successfully. */ *ABuf = malloc (ADesc->HeadSize + ADesc->TailSize); /* Allocate memory for metadata. */ if (tak_SSD_ReadSimpleWaveData (ADecoder, *ABuf, ADesc->HeadSize + ADesc->TailSize) != tak_res_Ok) { /* Reading metadata failed. */ if (tak_SSD_Valid (ADecoder) != tak_True) DecoError (ADecoder); /* Fatal error! */ else DestroyWaveMetaData (ADesc, ABuf); /* We can continue, but we have to create our own header. */ } } else { /* The Wave metadata description could not be read. */ if (tak_SSD_Valid (ADecoder) != tak_True) DecoError (ADecoder); /* The cause was a fatal error. */ } }
tak_SSD_GetSimpleWaveDataDesc reads the Wave metadata description from the stream. As this is optional, a failure not necessarily implies an error. If required, the function result sheds light on the reason for the failure.
The metadata description ADesc contains the sizes for leading (header) and trailing metadata. The sum of these is the required size of the buffer ABuf which the metadata is written to, using tak_SSD_ReadSimpleWaveData.
The following two procedures write the Wave header and, if required, trailing metadata to the destination file:
void WriteWaveMetaDataHead (Ttak_str_SimpleWaveDataHeader * ADesc, char * ABuf) /* Writes the Wave header (if there is any) from ABuf to the Wave file. */ { if (ABuf != NULL) WriteToWave (ABuf, ADesc->HeadSize); } void WriteWaveMetaDataTail (Ttak_str_SimpleWaveDataHeader * ADesc, char * ABuf); /* Writes trailing metadata (if there is any) from ABuf to the Wave file. */ { if ((ABuf != NULL) && (ADesc->TailSize != 0)) WriteToWave (&ABuf[ADesc->HeadSize], ADesc->TailSize); }
The audio data is sequentially decoded and written to the Wave file.
void DecodeAudioData (Ttak_str_StreamInfo * AStreamInfo) { TtakInt32 SamplesPerBuf; TtakInt32 SampleSize; TtakInt32 BufSize; char * Audio; TtakInt32 ReadNum; TtakResult OpResult; SamplesPerBuf = AStreamInfo->Sizes.FrameSizeInSamples; SampleSize = AStreamInfo->Audio.BlockSize; /* Frame / Sample size. */ BufSize = SamplesPerBuf * SampleSize; /* Enough space to hold a decoded frame. */ Audio = malloc (BufSize); /* For optimum performance, the memory should be aligned to an integer multiple of 4 or - even better - 8. */
AStreamInfo contains some useful information which is copied to local variables for easier handling:
If the stream is read sequentially, the highest speed can be achieved by reading the audio data frame by frame. The product of the frame size SamplesPerBuf and the sample size SampleSize is the appropriate size BufSize of the buffer memory Audio for read operations.
On every iteration of the following loop a frame is decoded and the resulting audio data is written to the Wave file:
ReadNum = 1; while (ReadNum > 0) { OpResult = tak_SSD_ReadAudio (Decoder, Audio, SamplesPerBuf, &ReadNum); if ( (OpResult != tak_res_Ok) && (OpResult != tak_res_ssd_FrameDamaged)) DecoError (Decoder); if (ReadNum > 0) WriteToWave (Audio, ReadNum * SampleSize); } free (Audio); }
tak_SSD_ReadAudio reads a maximum of SamplesPerBuf samples to the buffer Audio. ReadNum contains the number of samples actually read and can be less than SamplesPerBuf only when reading the last frame of the file.
The result tak_res_ssd_FrameDamaged indicates that damaged audio data was skipped or replaced with silence. The decoder can proceed without problems nonetheless; it is up to the programmer whether decoding is cancelled in case of damaged frames. The example ignores them.
It is not necessary to record messages regarding damaged frames. A tak_SSD_GetStateInfo call provides detailed status information at any time, including damages.