1 ////////////////////////////////////////////////////////////////////////////
2 //                           **** WAVPACK ****                            //
3 //                  Hybrid Lossless Wavefile Compressor                   //
4 //                Copyright (c) 1998 - 2019 David Bryant.                 //
5 //                          All Rights Reserved.                          //
6 //      Distributed under the BSD Software License (see license.txt)      //
7 ////////////////////////////////////////////////////////////////////////////
8 
9 // caff_write.c
10 
11 // This module is a helper to the WavPack command-line programs to support CAF files.
12 
13 #include <string.h>
14 #include <stdlib.h>
15 #include <stdio.h>
16 
17 #include "wavpack.h"
18 #include "utils.h"
19 
20 extern int debug_logging_mode;
21 
22 typedef struct
23 {
24     char mFileType [4];
25     uint16_t mFileVersion;
26     uint16_t mFileFlags;
27 } CAFFileHeader;
28 
29 #define CAFFileHeaderFormat "4SS"
30 
31 #pragma pack(push,4)
32 typedef struct
33 {
34     char mChunkType [4];
35     int64_t mChunkSize;
36 } CAFChunkHeader;
37 #pragma pack(pop)
38 
39 #define CAFChunkHeaderFormat "4D"
40 
41 typedef struct
42 {
43     double mSampleRate;
44     char mFormatID [4];
45     uint32_t mFormatFlags;
46     uint32_t mBytesPerPacket;
47     uint32_t mFramesPerPacket;
48     uint32_t mChannelsPerFrame;
49     uint32_t mBitsPerChannel;
50 } CAFAudioFormat;
51 
52 #define CAFAudioFormatFormat "D4LLLLL"
53 #define CAF_FORMAT_FLOAT            0x1
54 #define CAF_FORMAT_LITTLE_ENDIAN    0x2
55 
56 typedef struct
57 {
58     uint32_t mChannelLayoutTag;
59     uint32_t mChannelBitmap;
60     uint32_t mNumberChannelDescriptions;
61 } CAFChannelLayout;
62 
63 #define CAFChannelLayoutFormat "LLL"
64 
65 enum {
66     kCAFChannelLayoutTag_UseChannelDescriptions = (0<<16) | 0,  // use the array of AudioChannelDescriptions to define the mapping.
67     kCAFChannelLayoutTag_UseChannelBitmap = (1<<16) | 0,        // use the bitmap to define the mapping.
68 };
69 
70 typedef struct
71 {
72     uint32_t mChannelLabel;
73     uint32_t mChannelFlags;
74     float mCoordinates [3];
75 } CAFChannelDescription;
76 
77 #define CAFChannelDescriptionFormat "LLLLL"
78 
WriteCaffHeader(FILE * outfile,WavpackContext * wpc,int64_t total_samples,int qmode)79 int WriteCaffHeader (FILE *outfile, WavpackContext *wpc, int64_t total_samples, int qmode)
80 {
81     CAFChunkHeader caf_desc_chunk_header, caf_chan_chunk_header, caf_data_chunk_header;
82     CAFChannelLayout caf_channel_layout;
83     CAFAudioFormat caf_audio_format;
84     CAFFileHeader caf_file_header;
85     uint32_t mEditCount, bcount;
86 
87     int num_channels = WavpackGetNumChannels (wpc);
88     int32_t channel_mask = WavpackGetChannelMask (wpc);
89     int32_t sample_rate = WavpackGetSampleRate (wpc);
90     int bytes_per_sample = WavpackGetBytesPerSample (wpc);
91     int bits_per_sample = WavpackGetBitsPerSample (wpc);
92     int float_norm_exp = WavpackGetFloatNormExp (wpc);
93     uint32_t channel_layout_tag = WavpackGetChannelLayout (wpc, NULL);
94     unsigned char *channel_identities = malloc (num_channels + 1);
95     int num_identified_chans, i;
96 
97     if (float_norm_exp && float_norm_exp != 127) {
98         error_line ("invalid float data for CAFF, use --normalize-floats and omit MD5 check!");
99         free (channel_identities);
100         return FALSE;
101     }
102 
103     // get the channel identities (including Microsoft) and count up the defined ones
104 
105     WavpackGetChannelIdentities (wpc, channel_identities);
106 
107     for (num_identified_chans = i = 0; i < num_channels; ++i)
108         if (channel_identities [i] != 0xff)
109             num_identified_chans++;
110 
111     // format and write the CAF File Header
112 
113     memcpy (caf_file_header.mFileType, "caff", sizeof (caf_file_header.mFileType));
114     caf_file_header.mFileVersion = 1;
115     caf_file_header.mFileFlags = 0;
116     WavpackNativeToBigEndian (&caf_file_header, CAFFileHeaderFormat);
117 
118     if (!DoWriteFile (outfile, &caf_file_header, sizeof (caf_file_header), &bcount) ||
119         bcount != sizeof (caf_file_header))
120             return FALSE;
121 
122     // format and write the Audio Description Chunk
123 
124     memcpy (caf_desc_chunk_header.mChunkType, "desc", sizeof (caf_desc_chunk_header.mChunkType));
125     caf_desc_chunk_header.mChunkSize = sizeof (caf_audio_format);
126     WavpackNativeToBigEndian (&caf_desc_chunk_header, CAFChunkHeaderFormat);
127 
128     if (!DoWriteFile (outfile, &caf_desc_chunk_header, sizeof (caf_desc_chunk_header), &bcount) ||
129         bcount != sizeof (caf_desc_chunk_header))
130             return FALSE;
131 
132     caf_audio_format.mSampleRate = (double) sample_rate;
133     memcpy (caf_audio_format.mFormatID, "lpcm", sizeof (caf_audio_format.mFormatID));
134     caf_audio_format.mFormatFlags = float_norm_exp ? CAF_FORMAT_FLOAT : 0;
135 
136     if (!(qmode & QMODE_BIG_ENDIAN))
137         caf_audio_format.mFormatFlags |= CAF_FORMAT_LITTLE_ENDIAN;
138 
139     caf_audio_format.mBytesPerPacket = bytes_per_sample * num_channels;
140     caf_audio_format.mFramesPerPacket = 1;
141     caf_audio_format.mChannelsPerFrame = num_channels;
142     caf_audio_format.mBitsPerChannel = bits_per_sample;
143     WavpackNativeToBigEndian (&caf_audio_format, CAFAudioFormatFormat);
144 
145     if (!DoWriteFile (outfile, &caf_audio_format, sizeof (caf_audio_format), &bcount) ||
146         bcount != sizeof (caf_audio_format))
147             return FALSE;
148 
149     // we write the Channel Layout Chunk if any of these are true:
150     // 1. a specific CAF layout was specified (100 - 147)
151     // 2. there are more than 2 channels and ANY are defined
152     // 3. there are 1 or 2 channels and NOT regular mono/stereo
153 
154     if (channel_layout_tag || (num_channels > 2 ? num_identified_chans : channel_mask != 5 - num_channels)) {
155         unsigned int bits = 0, bmask;
156 
157         for (bmask = 1; bmask; bmask <<= 1)     // count the set bits in the channel mask
158             if (bmask & channel_mask)
159                 ++bits;
160 
161         // we use a layout tag if there is a specific CAF layout (100 - 147) or
162         // all the channels are MS defined and in MS order...otherwise we have to
163         // write a full channel description array
164 
165         if ((channel_layout_tag & 0xff0000) || (bits == num_channels && !(qmode & QMODE_REORDERED_CHANS))) {
166 
167             memcpy (caf_chan_chunk_header.mChunkType, "chan", sizeof (caf_chan_chunk_header.mChunkType));
168             caf_chan_chunk_header.mChunkSize = sizeof (caf_channel_layout);
169             WavpackNativeToBigEndian (&caf_chan_chunk_header, CAFChunkHeaderFormat);
170 
171             if (!DoWriteFile (outfile, &caf_chan_chunk_header, sizeof (caf_chan_chunk_header), &bcount) ||
172                 bcount != sizeof (caf_chan_chunk_header))
173                     return FALSE;
174 
175             if (channel_layout_tag) {
176                 if (debug_logging_mode)
177                     error_line ("writing \"chan\" chunk with layout tag 0x%08x", channel_layout_tag);
178 
179                 caf_channel_layout.mChannelLayoutTag = channel_layout_tag;
180                 caf_channel_layout.mChannelBitmap = 0;
181             }
182             else {
183                 if (debug_logging_mode)
184                     error_line ("writing \"chan\" chunk with UseChannelBitmap tag, bitmap = 0x%08x", channel_mask);
185 
186                 caf_channel_layout.mChannelLayoutTag = kCAFChannelLayoutTag_UseChannelBitmap;
187                 caf_channel_layout.mChannelBitmap = channel_mask;
188             }
189 
190             caf_channel_layout.mNumberChannelDescriptions = 0;
191             WavpackNativeToBigEndian (&caf_channel_layout, CAFChannelLayoutFormat);
192 
193             if (!DoWriteFile (outfile, &caf_channel_layout, sizeof (caf_channel_layout), &bcount) ||
194                 bcount != sizeof (caf_channel_layout))
195                     return FALSE;
196         }
197         else {  // write a channel description array because a single layout or bitmap won't do it...
198             CAFChannelDescription caf_channel_description;
199             unsigned char *new_channel_order = NULL;
200             int i;
201 
202             if (debug_logging_mode)
203                 error_line ("writing \"chan\" chunk with UseChannelDescriptions tag, bitmap = 0x%08x, reordered = %s",
204                     channel_mask, (qmode & QMODE_REORDERED_CHANS) ? "yes" : "no");
205 
206             if (qmode & QMODE_REORDERED_CHANS) {
207                 if ((int)(channel_layout_tag & 0xff) <= num_channels) {
208                     new_channel_order = malloc (num_channels);
209 
210                     for (i = 0; i < num_channels; ++i)
211                         new_channel_order [i] = i;
212 
213                     WavpackGetChannelLayout (wpc, new_channel_order);
214                 }
215             }
216 
217             memcpy (caf_chan_chunk_header.mChunkType, "chan", sizeof (caf_chan_chunk_header.mChunkType));
218             caf_chan_chunk_header.mChunkSize = sizeof (caf_channel_layout) + sizeof (caf_channel_description) * num_channels;
219             WavpackNativeToBigEndian (&caf_chan_chunk_header, CAFChunkHeaderFormat);
220 
221             if (!DoWriteFile (outfile, &caf_chan_chunk_header, sizeof (caf_chan_chunk_header), &bcount) ||
222                 bcount != sizeof (caf_chan_chunk_header))
223                     return FALSE;
224 
225             caf_channel_layout.mChannelLayoutTag = kCAFChannelLayoutTag_UseChannelDescriptions;
226             caf_channel_layout.mChannelBitmap = 0;
227             caf_channel_layout.mNumberChannelDescriptions = num_channels;
228             WavpackNativeToBigEndian (&caf_channel_layout, CAFChannelLayoutFormat);
229 
230             if (!DoWriteFile (outfile, &caf_channel_layout, sizeof (caf_channel_layout), &bcount) ||
231                 bcount != sizeof (caf_channel_layout))
232                     return FALSE;
233 
234             for (i = 0; i < num_channels; ++i) {
235                 unsigned char chan_id = new_channel_order ? channel_identities [new_channel_order [i]] : channel_identities [i];
236                 CLEAR (caf_channel_description);
237 
238                 if ((chan_id >= 1 && chan_id <= 18) || (chan_id >= 33 && chan_id <= 44) || (chan_id >= 200 && chan_id <= 207))
239                     caf_channel_description.mChannelLabel = chan_id;
240                 else if (chan_id >= 221 && chan_id <= 225)
241                     caf_channel_description.mChannelLabel = chan_id + 80;
242 
243                 if (debug_logging_mode)
244                     error_line ("chan %d --> %d", i + 1, caf_channel_description.mChannelLabel);
245 
246                 WavpackNativeToBigEndian (&caf_channel_description, CAFChannelDescriptionFormat);
247 
248                 if (!DoWriteFile (outfile, &caf_channel_description, sizeof (caf_channel_description), &bcount) ||
249                     bcount != sizeof (caf_channel_description))
250                         return FALSE;
251             }
252 
253             if (new_channel_order)
254                 free (new_channel_order);
255         }
256     }
257 
258     // format and write the Audio Data Chunk
259 
260     memcpy (caf_data_chunk_header.mChunkType, "data", sizeof (caf_data_chunk_header.mChunkType));
261 
262     if (total_samples == -1)
263         caf_data_chunk_header.mChunkSize = -1;
264     else
265         caf_data_chunk_header.mChunkSize = (total_samples * bytes_per_sample * num_channels) + sizeof (mEditCount);
266 
267     WavpackNativeToBigEndian (&caf_data_chunk_header, CAFChunkHeaderFormat);
268 
269     if (!DoWriteFile (outfile, &caf_data_chunk_header, sizeof (caf_data_chunk_header), &bcount) ||
270         bcount != sizeof (caf_data_chunk_header))
271             return FALSE;
272 
273     mEditCount = 0;
274     WavpackNativeToBigEndian (&mEditCount, "L");
275 
276     if (!DoWriteFile (outfile, &mEditCount, sizeof (mEditCount), &bcount) ||
277         bcount != sizeof (mEditCount))
278             return FALSE;
279 
280     free (channel_identities);
281 
282     return TRUE;
283 }
284