mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 10:18:41 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			276 lines
		
	
	
	
		
			9.8 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			276 lines
		
	
	
	
		
			9.8 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | 
						|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | 
						|
/* 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/. */
 | 
						|
 | 
						|
#if !defined(AudioConverter_h)
 | 
						|
#  define AudioConverter_h
 | 
						|
 | 
						|
#  include "MediaInfo.h"
 | 
						|
 | 
						|
// Forward declaration
 | 
						|
typedef struct SpeexResamplerState_ SpeexResamplerState;
 | 
						|
 | 
						|
namespace mozilla {
 | 
						|
 | 
						|
template <AudioConfig::SampleFormat T>
 | 
						|
struct AudioDataBufferTypeChooser;
 | 
						|
template <>
 | 
						|
struct AudioDataBufferTypeChooser<AudioConfig::FORMAT_U8> {
 | 
						|
  typedef uint8_t Type;
 | 
						|
};
 | 
						|
template <>
 | 
						|
struct AudioDataBufferTypeChooser<AudioConfig::FORMAT_S16> {
 | 
						|
  typedef int16_t Type;
 | 
						|
};
 | 
						|
template <>
 | 
						|
struct AudioDataBufferTypeChooser<AudioConfig::FORMAT_S24LSB> {
 | 
						|
  typedef int32_t Type;
 | 
						|
};
 | 
						|
template <>
 | 
						|
struct AudioDataBufferTypeChooser<AudioConfig::FORMAT_S24> {
 | 
						|
  typedef int32_t Type;
 | 
						|
};
 | 
						|
template <>
 | 
						|
struct AudioDataBufferTypeChooser<AudioConfig::FORMAT_S32> {
 | 
						|
  typedef int32_t Type;
 | 
						|
};
 | 
						|
template <>
 | 
						|
struct AudioDataBufferTypeChooser<AudioConfig::FORMAT_FLT> {
 | 
						|
  typedef float Type;
 | 
						|
};
 | 
						|
 | 
						|
// 'Value' is the type used externally to deal with stored value.
 | 
						|
// AudioDataBuffer can perform conversion between different SampleFormat
 | 
						|
// content.
 | 
						|
template <AudioConfig::SampleFormat Format,
 | 
						|
          typename Value = typename AudioDataBufferTypeChooser<Format>::Type>
 | 
						|
class AudioDataBuffer {
 | 
						|
 public:
 | 
						|
  AudioDataBuffer() = default;
 | 
						|
  AudioDataBuffer(Value* aBuffer, size_t aLength) : mBuffer(aBuffer, aLength) {}
 | 
						|
  explicit AudioDataBuffer(const AudioDataBuffer& aOther)
 | 
						|
      : mBuffer(aOther.mBuffer) {}
 | 
						|
  AudioDataBuffer(AudioDataBuffer&& aOther)
 | 
						|
      : mBuffer(std::move(aOther.mBuffer)) {}
 | 
						|
  template <AudioConfig::SampleFormat OtherFormat, typename OtherValue>
 | 
						|
  explicit AudioDataBuffer(
 | 
						|
      const AudioDataBuffer<OtherFormat, OtherValue>& other) {
 | 
						|
    // TODO: Convert from different type, may use asm routines.
 | 
						|
    MOZ_CRASH("Conversion not implemented yet");
 | 
						|
  }
 | 
						|
 | 
						|
  // A u8, s16 and float aligned buffer can only be treated as
 | 
						|
  // FORMAT_U8, FORMAT_S16 and FORMAT_FLT respectively.
 | 
						|
  // So allow them as copy and move constructors.
 | 
						|
  explicit AudioDataBuffer(const AlignedByteBuffer& aBuffer)
 | 
						|
      : mBuffer(aBuffer) {
 | 
						|
    static_assert(Format == AudioConfig::FORMAT_U8,
 | 
						|
                  "Conversion not implemented yet");
 | 
						|
  }
 | 
						|
  explicit AudioDataBuffer(const AlignedShortBuffer& aBuffer)
 | 
						|
      : mBuffer(aBuffer) {
 | 
						|
    static_assert(Format == AudioConfig::FORMAT_S16,
 | 
						|
                  "Conversion not implemented yet");
 | 
						|
  }
 | 
						|
  explicit AudioDataBuffer(const AlignedFloatBuffer& aBuffer)
 | 
						|
      : mBuffer(aBuffer) {
 | 
						|
    static_assert(Format == AudioConfig::FORMAT_FLT,
 | 
						|
                  "Conversion not implemented yet");
 | 
						|
  }
 | 
						|
  explicit AudioDataBuffer(AlignedByteBuffer&& aBuffer)
 | 
						|
      : mBuffer(std::move(aBuffer)) {
 | 
						|
    static_assert(Format == AudioConfig::FORMAT_U8,
 | 
						|
                  "Conversion not implemented yet");
 | 
						|
  }
 | 
						|
  explicit AudioDataBuffer(AlignedShortBuffer&& aBuffer)
 | 
						|
      : mBuffer(std::move(aBuffer)) {
 | 
						|
    static_assert(Format == AudioConfig::FORMAT_S16,
 | 
						|
                  "Conversion not implemented yet");
 | 
						|
  }
 | 
						|
  explicit AudioDataBuffer(AlignedFloatBuffer&& aBuffer)
 | 
						|
      : mBuffer(std::move(aBuffer)) {
 | 
						|
    static_assert(Format == AudioConfig::FORMAT_FLT,
 | 
						|
                  "Conversion not implemented yet");
 | 
						|
  }
 | 
						|
  AudioDataBuffer& operator=(AudioDataBuffer&& aOther) {
 | 
						|
    mBuffer = std::move(aOther.mBuffer);
 | 
						|
    return *this;
 | 
						|
  }
 | 
						|
  AudioDataBuffer& operator=(const AudioDataBuffer& aOther) {
 | 
						|
    mBuffer = aOther.mBuffer;
 | 
						|
    return *this;
 | 
						|
  }
 | 
						|
 | 
						|
  Value* Data() const { return mBuffer.Data(); }
 | 
						|
  size_t Length() const { return mBuffer.Length(); }
 | 
						|
  size_t Size() const { return mBuffer.Size(); }
 | 
						|
  AlignedBuffer<Value> Forget() {
 | 
						|
    // Correct type -> Just give values as-is.
 | 
						|
    return std::move(mBuffer);
 | 
						|
  }
 | 
						|
 | 
						|
 private:
 | 
						|
  AlignedBuffer<Value> mBuffer;
 | 
						|
};
 | 
						|
 | 
						|
typedef AudioDataBuffer<AudioConfig::FORMAT_DEFAULT> AudioSampleBuffer;
 | 
						|
 | 
						|
class AudioConverter {
 | 
						|
 public:
 | 
						|
  AudioConverter(const AudioConfig& aIn, const AudioConfig& aOut);
 | 
						|
  ~AudioConverter();
 | 
						|
 | 
						|
  // Convert the AudioDataBuffer.
 | 
						|
  // Conversion will be done in place if possible. Otherwise a new buffer will
 | 
						|
  // be returned.
 | 
						|
  // Providing an empty buffer and resampling is expected, the resampler
 | 
						|
  // will be drained.
 | 
						|
  template <AudioConfig::SampleFormat Format, typename Value>
 | 
						|
  AudioDataBuffer<Format, Value> Process(
 | 
						|
      AudioDataBuffer<Format, Value>&& aBuffer) {
 | 
						|
    MOZ_DIAGNOSTIC_ASSERT(mIn.Format() == mOut.Format() &&
 | 
						|
                          mIn.Format() == Format);
 | 
						|
    AudioDataBuffer<Format, Value> buffer = std::move(aBuffer);
 | 
						|
    if (CanWorkInPlace()) {
 | 
						|
      AlignedBuffer<Value> temp = buffer.Forget();
 | 
						|
      Process(temp, temp.Data(), SamplesInToFrames(temp.Length()));
 | 
						|
      return AudioDataBuffer<Format, Value>(std::move(temp));
 | 
						|
    }
 | 
						|
    return Process(buffer);
 | 
						|
  }
 | 
						|
 | 
						|
  template <AudioConfig::SampleFormat Format, typename Value>
 | 
						|
  AudioDataBuffer<Format, Value> Process(
 | 
						|
      const AudioDataBuffer<Format, Value>& aBuffer) {
 | 
						|
    MOZ_DIAGNOSTIC_ASSERT(mIn.Format() == mOut.Format() &&
 | 
						|
                          mIn.Format() == Format);
 | 
						|
    // Perform the downmixing / reordering in temporary buffer.
 | 
						|
    size_t frames = SamplesInToFrames(aBuffer.Length());
 | 
						|
    AlignedBuffer<Value> temp1;
 | 
						|
    if (!temp1.SetLength(FramesOutToSamples(frames))) {
 | 
						|
      return AudioDataBuffer<Format, Value>(std::move(temp1));
 | 
						|
    }
 | 
						|
    frames = ProcessInternal(temp1.Data(), aBuffer.Data(), frames);
 | 
						|
    if (mIn.Rate() == mOut.Rate()) {
 | 
						|
      MOZ_ALWAYS_TRUE(temp1.SetLength(FramesOutToSamples(frames)));
 | 
						|
      return AudioDataBuffer<Format, Value>(std::move(temp1));
 | 
						|
    }
 | 
						|
 | 
						|
    // At this point, temp1 contains the buffer reordered and downmixed.
 | 
						|
    // If we are downsampling we can re-use it.
 | 
						|
    AlignedBuffer<Value>* outputBuffer = &temp1;
 | 
						|
    AlignedBuffer<Value> temp2;
 | 
						|
    if (!frames || mOut.Rate() > mIn.Rate()) {
 | 
						|
      // We are upsampling or about to drain, we can't work in place.
 | 
						|
      // Allocate another temporary buffer where the upsampling will occur.
 | 
						|
      if (!temp2.SetLength(
 | 
						|
              FramesOutToSamples(ResampleRecipientFrames(frames)))) {
 | 
						|
        return AudioDataBuffer<Format, Value>(std::move(temp2));
 | 
						|
      }
 | 
						|
      outputBuffer = &temp2;
 | 
						|
    }
 | 
						|
    if (!frames) {
 | 
						|
      frames = DrainResampler(outputBuffer->Data());
 | 
						|
    } else {
 | 
						|
      frames = ResampleAudio(outputBuffer->Data(), temp1.Data(), frames);
 | 
						|
    }
 | 
						|
    MOZ_ALWAYS_TRUE(outputBuffer->SetLength(FramesOutToSamples(frames)));
 | 
						|
    return AudioDataBuffer<Format, Value>(std::move(*outputBuffer));
 | 
						|
  }
 | 
						|
 | 
						|
  // Attempt to convert the AudioDataBuffer in place.
 | 
						|
  // Will return 0 if the conversion wasn't possible.
 | 
						|
  template <typename Value>
 | 
						|
  size_t Process(Value* aBuffer, size_t aFrames) {
 | 
						|
    MOZ_DIAGNOSTIC_ASSERT(mIn.Format() == mOut.Format());
 | 
						|
    if (!CanWorkInPlace()) {
 | 
						|
      return 0;
 | 
						|
    }
 | 
						|
    size_t frames = ProcessInternal(aBuffer, aBuffer, aFrames);
 | 
						|
    if (frames && mIn.Rate() != mOut.Rate()) {
 | 
						|
      frames = ResampleAudio(aBuffer, aBuffer, aFrames);
 | 
						|
    }
 | 
						|
    return frames;
 | 
						|
  }
 | 
						|
 | 
						|
  template <typename Value>
 | 
						|
  size_t Process(AlignedBuffer<Value>& aOutBuffer, const Value* aInBuffer,
 | 
						|
                 size_t aFrames) {
 | 
						|
    MOZ_DIAGNOSTIC_ASSERT(mIn.Format() == mOut.Format());
 | 
						|
    MOZ_ASSERT((aFrames && aInBuffer) || !aFrames);
 | 
						|
    // Up/down mixing first
 | 
						|
    if (!aOutBuffer.SetLength(FramesOutToSamples(aFrames))) {
 | 
						|
      MOZ_ALWAYS_TRUE(aOutBuffer.SetLength(0));
 | 
						|
      return 0;
 | 
						|
    }
 | 
						|
    size_t frames = ProcessInternal(aOutBuffer.Data(), aInBuffer, aFrames);
 | 
						|
    MOZ_ASSERT(frames == aFrames);
 | 
						|
    // Check if resampling is needed
 | 
						|
    if (mIn.Rate() == mOut.Rate()) {
 | 
						|
      return frames;
 | 
						|
    }
 | 
						|
    // Prepare output in cases of drain or up-sampling
 | 
						|
    if ((!frames || mOut.Rate() > mIn.Rate()) &&
 | 
						|
        !aOutBuffer.SetLength(
 | 
						|
            FramesOutToSamples(ResampleRecipientFrames(frames)))) {
 | 
						|
      MOZ_ALWAYS_TRUE(aOutBuffer.SetLength(0));
 | 
						|
      return 0;
 | 
						|
    }
 | 
						|
    if (!frames) {
 | 
						|
      frames = DrainResampler(aOutBuffer.Data());
 | 
						|
    } else {
 | 
						|
      frames = ResampleAudio(aOutBuffer.Data(), aInBuffer, frames);
 | 
						|
    }
 | 
						|
    // Update with the actual buffer length
 | 
						|
    MOZ_ALWAYS_TRUE(aOutBuffer.SetLength(FramesOutToSamples(frames)));
 | 
						|
    return frames;
 | 
						|
  }
 | 
						|
 | 
						|
  bool CanWorkInPlace() const;
 | 
						|
  bool CanReorderAudio() const {
 | 
						|
    return mIn.Layout().MappingTable(mOut.Layout());
 | 
						|
  }
 | 
						|
  static bool CanConvert(const AudioConfig& aIn, const AudioConfig& aOut);
 | 
						|
 | 
						|
  const AudioConfig& InputConfig() const { return mIn; }
 | 
						|
  const AudioConfig& OutputConfig() const { return mOut; }
 | 
						|
 | 
						|
 private:
 | 
						|
  const AudioConfig mIn;
 | 
						|
  const AudioConfig mOut;
 | 
						|
  // mChannelOrderMap will be empty if we do not know how to proceed with this
 | 
						|
  // channel layout.
 | 
						|
  AutoTArray<uint8_t, AudioConfig::ChannelLayout::MAX_CHANNELS>
 | 
						|
      mChannelOrderMap;
 | 
						|
  /**
 | 
						|
   * ProcessInternal
 | 
						|
   * Parameters:
 | 
						|
   * aOut  : destination buffer where converted samples will be copied
 | 
						|
   * aIn   : source buffer
 | 
						|
   * aSamples: number of frames in source buffer
 | 
						|
   *
 | 
						|
   * Return Value: number of frames converted or 0 if error
 | 
						|
   */
 | 
						|
  size_t ProcessInternal(void* aOut, const void* aIn, size_t aFrames);
 | 
						|
  void ReOrderInterleavedChannels(void* aOut, const void* aIn,
 | 
						|
                                  size_t aFrames) const;
 | 
						|
  size_t DownmixAudio(void* aOut, const void* aIn, size_t aFrames) const;
 | 
						|
  size_t UpmixAudio(void* aOut, const void* aIn, size_t aFrames) const;
 | 
						|
 | 
						|
  size_t FramesOutToSamples(size_t aFrames) const;
 | 
						|
  size_t SamplesInToFrames(size_t aSamples) const;
 | 
						|
  size_t FramesOutToBytes(size_t aFrames) const;
 | 
						|
 | 
						|
  // Resampler context.
 | 
						|
  SpeexResamplerState* mResampler;
 | 
						|
  size_t ResampleAudio(void* aOut, const void* aIn, size_t aFrames);
 | 
						|
  size_t ResampleRecipientFrames(size_t aFrames) const;
 | 
						|
  void RecreateResampler();
 | 
						|
  size_t DrainResampler(void* aOut);
 | 
						|
};
 | 
						|
 | 
						|
}  // namespace mozilla
 | 
						|
 | 
						|
#endif /* AudioConverter_h */
 |