summaryrefslogtreecommitdiff
path: root/src/input_aiff.cpp
blob: ed6fbe58751a7957188dfc3f80cea459cdd55a03 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
/**
 * This file is roughly based on the WAVInputStream implementation,
 * since AIFF and WAV files are so alike.
 */

#include <string.h>
#include "debug.h"
#include "input_aiff.h"
#include "utility.h"


namespace audiere {

  static inline bool isValidSampleSize(u32 size) {
    return (size == 8 || size == 16);
  }


  AIFFInputStream::AIFFInputStream() {
    m_file = 0;

    m_channel_count = 0;
    m_sample_rate   = 0;
    m_sample_format = SF_U8;  // reasonable default?

    m_data_chunk_location = 0;
    m_data_chunk_length   = 0;

    m_frames_left_in_chunk = 0;
  }


  /// @todo  this really should be replaced with a factory function
  bool
  AIFFInputStream::initialize(FilePtr file) {
    ADR_GUARD("AIFFInputStream::initialize");

    m_file = file;

    u8 header[12];
    if (file->read(header, 12) != 12) {
      ADR_LOG("Failed to read AIFF header");
      m_file = 0;
      return false;
    }

    if (memcmp(header, "FORM", 4) != 0 ||
        read32_be(header + 4) == 0 ||
        memcmp(header + 8, "AIFF", 4) != 0)
    {
      ADR_LOG("Invalid AIFF header");
      m_file = 0;
      return false;
    }

    if (findCommonChunk() && findSoundChunk()) {
      return true;
    } else {
      m_file = 0;
      return false;
    }
  }


  void
  AIFFInputStream::getFormat(
    int& channel_count,
    int& sample_rate,
    SampleFormat& sample_format)
  {
    channel_count = m_channel_count;
    sample_rate   = m_sample_rate;
    sample_format = m_sample_format;
  }


  int
  AIFFInputStream::doRead(int frame_count, void* buffer) {
    if (m_frames_left_in_chunk == 0) {
      return 0;
    }

    const int frames_to_read = std::min(frame_count, m_frames_left_in_chunk);
    const int frame_size = m_channel_count * GetSampleSize(m_sample_format);
    const int bytes_to_read = frames_to_read * frame_size;
  
    const int read = m_file->read(buffer, bytes_to_read);
    const int frames_read = read / frame_size;

#ifndef WORDS_BIGENDIAN
    if (m_sample_format == SF_S16) {
      // make little endian into host endian
      u8* out = (u8*)buffer;
      for (int i = 0; i < frames_read * m_channel_count; ++i) {
        std::swap(out[0], out[1]);
        out += 2;
      }
    }
#endif

    // assume that if we didn't get a full read, we're done
    if (read != bytes_to_read) {
      m_frames_left_in_chunk = 0;
      return frames_read;
    }

    m_frames_left_in_chunk -= frames_read;
    return frames_read;
  }


  void
  AIFFInputStream::reset() {
    // seek to the beginning of the data chunk
    m_frames_left_in_chunk = m_data_chunk_length;
    if (!m_file->seek(m_data_chunk_location, File::BEGIN)) {
      ADR_LOG("Seek in AIFFInputStream::reset");
    }
  }


  bool
  AIFFInputStream::isSeekable() {
    return true;
  }


  int
  AIFFInputStream::getLength() {
    return m_data_chunk_length;
  }


  void
  AIFFInputStream::setPosition(int position) {
    int frame_size = m_channel_count * GetSampleSize(m_sample_format);
    m_frames_left_in_chunk = m_data_chunk_length - position;
    m_file->seek(m_data_chunk_location + position * frame_size, File::BEGIN);
  }


  int
  AIFFInputStream::getPosition() {
    return m_data_chunk_length - m_frames_left_in_chunk;
  }


  bool
  AIFFInputStream::findCommonChunk() {
    ADR_GUARD("AIFFInputStream::findCommonChunk");

    // seek to just after the IFF header
    m_file->seek(12, File::BEGIN);

    // search for a common chunk
    for (;;) {
      u8 chunk_header[8];
      if (m_file->read(chunk_header, 8) != 8) {
        return false;
      }
      u32 chunk_length = read32_be(chunk_header + 4);

      // if we found a format chunk, excellent!
      if (memcmp(chunk_header, "COMM", 4) == 0 && chunk_length >= 18) {
        ADR_LOG("Found common chunk");

        // read common chunk
        u8 chunk[18];
        if (m_file->read(chunk, 18) != 18) {
          return false;
        }

        chunk_length -= 18;

        // parse the memory into useful information
        u16 channel_count   = read16_be(chunk + 0);
        //u32 frame_count     = read32_be(chunk + 2);
        u16 bits_per_sample = read16_be(chunk + 6);
        u32 sample_rate     = readLD_be(chunk + 8);

        // we only support mono and stereo, 8-bit or 16-bit
        if (channel_count > 2 ||
            !isValidSampleSize(bits_per_sample)) {
          ADR_LOG("Invalid AIFF");
          return false;
        }

        // skip the rest of the chunk
        if (!skipBytes(chunk_length)) {
          ADR_LOG("failed skipping rest of common chunk");
          return false;
        }

        // figure out the sample format
        if (bits_per_sample == 8) {
          m_sample_format = SF_U8;
        } else if (bits_per_sample == 16) {
          m_sample_format = SF_S16;
        } else {
          return false;
        }

        // store the other important attributes
        m_channel_count = channel_count;
        m_sample_rate   = sample_rate;
        return true;

      } else {

        // skip the rest of the chunk
        if (!skipBytes(chunk_length)) {
          // oops, end of stream
          return false;
        }

      }
    }
  }


  bool
  AIFFInputStream::findSoundChunk() {
    ADR_GUARD("AIFFInputStream::findSoundChunk");

    // seek to just after the IFF header
    m_file->seek(12, File::BEGIN);

    // search for a sound chunk
    while (true) {
      u8 chunk_header[8];
      if (m_file->read(chunk_header, 8) != 8) {
        ADR_LOG("Couldn't read SSND chunk header");
        return false;
      }
      u32 chunk_length = read32_be(chunk_header + 4);

      // if we found a data chunk, excellent!
      if (memcmp(chunk_header, "SSND", 4) == 0) {
        ADR_LOG("Found sound chunk");

        u8 chunk_contents[8];
        if (m_file->read(chunk_contents, 8) != 8) {
          ADR_LOG("Couldn't read SSND chunk contents");
          return false;
        }
        if (read32_be(chunk_contents + 0) != 0 ||
            read32_be(chunk_contents + 4) != 0)
        {
          ADR_LOG("Block-aligned AIFF files not supported!");
          return false;
        }

        // calculate the frame size so we can truncate the data chunk
        int frame_size = m_channel_count * GetSampleSize(m_sample_format);

        m_data_chunk_location  = m_file->tell();
        m_data_chunk_length    = (chunk_length - 8) / frame_size;
        m_frames_left_in_chunk = m_data_chunk_length;
        return true;

      } else {

        ADR_IF_DEBUG {
          const u8* ci = chunk_header;
          char str[80];
          sprintf(str, "Skipping: %d bytes in chunk '%c%c%c%c'",
                  (int)chunk_length, ci[0], ci[1], ci[2], ci[3]);
          ADR_LOG(str);
        }

        // skip the rest of the chunk
        if (!skipBytes(chunk_length)) {
          // oops, end of stream
          return false;
        }

      }
    }
  }


  bool
  AIFFInputStream::skipBytes(int size) {
    return m_file->seek(size, File::CURRENT);
  }

}