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