summaryrefslogtreecommitdiffstats
path: root/dom/media/AudioPacketizer.h
blob: 17579618ea96cd6f3024a9b4ca810abe5f61f9a3 (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
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef AudioPacketizer_h_
#define AudioPacketizer_h_

#include <mozilla/PodOperations.h>
#include <mozilla/Assertions.h>
#include <mozilla/UniquePtr.h>
#include <AudioSampleFormat.h>

// Enable this to warn when `Output` has been called but not enough data was
// buffered.
// #define LOG_PACKETIZER_UNDERRUN

namespace mozilla {
/**
 * This class takes arbitrary input data, and returns packets of a specific
 * size. In the process, it can convert audio samples from 16bit integers to
 * float (or vice-versa).
 *
 * Input and output, as well as length units in the public interface are
 * interleaved frames.
 *
 * Allocations of output buffer can be performed by this class.  Buffers can
 * simply be delete-d.  This is because packets are intended to be sent off to
 * non-gecko code using normal pointers/length pairs
 *
 * Alternatively, consumers can pass in a buffer in which the output is copied.
 * The buffer needs to be large enough to store a packet worth of audio.
 *
 * The implementation uses a circular buffer using absolute virtual indices.
 */
template <typename InputType, typename OutputType>
class AudioPacketizer {
 public:
  AudioPacketizer(uint32_t aPacketSize, uint32_t aChannels)
      : mPacketSize(aPacketSize),
        mChannels(aChannels),
        mReadIndex(0),
        mWriteIndex(0),
        // Start off with a single packet
        mStorage(new InputType[aPacketSize * aChannels]),
        mLength(aPacketSize * aChannels) {
    MOZ_ASSERT(aPacketSize > 0 && aChannels > 0,
               "The packet size and the number of channel should be strictly "
               "positive");
  }

  void Input(const InputType* aFrames, uint32_t aFrameCount) {
    uint32_t inputSamples = aFrameCount * mChannels;
    // Need to grow the storage. This should rarely happen, if at all, once the
    // array has the right size.
    if (inputSamples > EmptySlots()) {
      // Calls to Input and Output are roughtly interleaved
      // (Input,Output,Input,Output, etc.), or balanced
      // (Input,Input,Input,Output,Output,Output), so we update the buffer to
      // the exact right size in order to not waste space.
      uint32_t newLength = AvailableSamples() + inputSamples;
      uint32_t toCopy = AvailableSamples();
      UniquePtr<InputType[]> oldStorage = std::move(mStorage);
      mStorage = mozilla::MakeUnique<InputType[]>(newLength);
      // Copy the old data at the beginning of the new storage.
      if (WriteIndex() >= ReadIndex()) {
        PodCopy(mStorage.get(), oldStorage.get() + ReadIndex(),
                AvailableSamples());
      } else {
        uint32_t firstPartLength = mLength - ReadIndex();
        uint32_t secondPartLength = AvailableSamples() - firstPartLength;
        PodCopy(mStorage.get(), oldStorage.get() + ReadIndex(),
                firstPartLength);
        PodCopy(mStorage.get() + firstPartLength, oldStorage.get(),
                secondPartLength);
      }
      mWriteIndex = toCopy;
      mReadIndex = 0;
      mLength = newLength;
    }

    if (WriteIndex() + inputSamples <= mLength) {
      PodCopy(mStorage.get() + WriteIndex(), aFrames, aFrameCount * mChannels);
    } else {
      uint32_t firstPartLength = mLength - WriteIndex();
      uint32_t secondPartLength = inputSamples - firstPartLength;
      PodCopy(mStorage.get() + WriteIndex(), aFrames, firstPartLength);
      PodCopy(mStorage.get(), aFrames + firstPartLength, secondPartLength);
    }

    mWriteIndex += inputSamples;
  }

  OutputType* Output() {
    uint32_t samplesNeeded = mPacketSize * mChannels;
    OutputType* out = new OutputType[samplesNeeded];

    Output(out);

    return out;
  }

  // Return the number of actual frames dequeued -- this can be lower than the
  // packet size when underruning or draining.
  size_t Output(OutputType* aOutputBuffer) {
    uint32_t samplesNeeded = mPacketSize * mChannels;
    size_t rv = 0;

    // Under-run. Pad the end of the buffer with silence.
    if (AvailableSamples() < samplesNeeded) {
      rv = AvailableSamples() / mChannels;
#ifdef LOG_PACKETIZER_UNDERRUN
      char buf[256];
      snprintf(buf, 256,
               "AudioPacketizer %p underrun: available: %u, needed: %u\n", this,
               AvailableSamples(), samplesNeeded);
      NS_WARNING(buf);
#endif
      uint32_t zeros = samplesNeeded - AvailableSamples();
      PodZero(aOutputBuffer + AvailableSamples(), zeros);
      samplesNeeded -= zeros;
    } else {
      rv = mPacketSize;
    }
    if (ReadIndex() + samplesNeeded <= mLength) {
      ConvertAudioSamples<InputType, OutputType>(mStorage.get() + ReadIndex(),
                                                 aOutputBuffer, samplesNeeded);
    } else {
      uint32_t firstPartLength = mLength - ReadIndex();
      uint32_t secondPartLength = samplesNeeded - firstPartLength;
      ConvertAudioSamples<InputType, OutputType>(
          mStorage.get() + ReadIndex(), aOutputBuffer, firstPartLength);
      ConvertAudioSamples<InputType, OutputType>(
          mStorage.get(), aOutputBuffer + firstPartLength, secondPartLength);
    }
    mReadIndex += samplesNeeded;
    return rv;
  }

  void Clear() {
    mReadIndex = 0;
    mWriteIndex = 0;
  }

  uint32_t PacketsAvailable() const {
    return AvailableSamples() / mChannels / mPacketSize;
  }

  uint32_t FramesAvailable() const { return AvailableSamples() / mChannels; }

  bool Empty() const { return mWriteIndex == mReadIndex; }

  bool Full() const { return mWriteIndex - mReadIndex == mLength; }

  // Size of one packet of audio, in frames
  const uint32_t mPacketSize;
  // Number of channels of the stream flowing through this packetizer
  const uint32_t mChannels;

 private:
  uint32_t ReadIndex() const { return mReadIndex % mLength; }

  uint32_t WriteIndex() const { return mWriteIndex % mLength; }

  uint32_t AvailableSamples() const { return mWriteIndex - mReadIndex; }

  uint32_t EmptySlots() const { return mLength - AvailableSamples(); }

  // Two virtual index into the buffer: the read position and the write
  // position.
  uint64_t mReadIndex;
  uint64_t mWriteIndex;
  // Storage for the samples
  mozilla::UniquePtr<InputType[]> mStorage;
  // Length of the buffer, in samples
  uint32_t mLength;
};

}  // namespace mozilla

#endif  // AudioPacketizer_h_