| // Copyright 2020 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef SRC_MEDIA_AUDIO_LIB_ANALYSIS_GENERATORS_H_ |
| #define SRC_MEDIA_AUDIO_LIB_ANALYSIS_GENERATORS_H_ |
| |
| #include <cmath> |
| #include <memory> |
| |
| #include "src/media/audio/lib/format/audio_buffer.h" |
| #include "src/media/audio/lib/wav/wav_reader.h" |
| |
| namespace media::audio { |
| |
| // Construct a stream of silent audio data. |
| template <fuchsia::media::AudioSampleFormat SampleFormat> |
| AudioBuffer<SampleFormat> GenerateSilentAudio(TypedFormat<SampleFormat> format, |
| int64_t num_frames) { |
| AudioBuffer buf(format, num_frames); |
| std::fill(buf.samples().begin(), buf.samples().end(), |
| SampleFormatTraits<SampleFormat>::kSilentValue); |
| return buf; |
| } |
| |
| // Construct a stream of synthetic audio data that is uses a fixed constant value. |
| // |
| // As this does not create a meaningful sound, this is intended to be used in test scenarios that |
| // perform bit-for-bit comparisons on the output of an audio pipeline. |
| template <fuchsia::media::AudioSampleFormat SampleFormat> |
| AudioBuffer<SampleFormat> GenerateConstantAudio(TypedFormat<SampleFormat> format, |
| int64_t num_frames, |
| typename AudioBuffer<SampleFormat>::SampleT val) { |
| AudioBuffer out(format, num_frames); |
| std::fill(out.samples().begin(), out.samples().end(), val); |
| return out; |
| } |
| |
| // Construct a stream of synthetic audio data that is sequentially incremented. For integer types, |
| // payload data values increase by 1. For FLOAT, data increases by 2^-16, which is about 10^-5. |
| // |
| // As this does not create a meaningful sound, this is intended to be used in test scenarios that |
| // perform bit-for-bit comparisons on the output of an audio pipeline. |
| template <fuchsia::media::AudioSampleFormat SampleFormat> |
| AudioBuffer<SampleFormat> GenerateSequentialAudio( |
| TypedFormat<SampleFormat> format, int64_t num_frames, |
| typename AudioBuffer<SampleFormat>::SampleT first_val = 0) { |
| typename AudioBuffer<SampleFormat>::SampleT increment = 1; |
| if constexpr (SampleFormat == fuchsia::media::AudioSampleFormat::FLOAT) { |
| first_val = std::clamp<float>(first_val, -1.0f, 1.0f); |
| increment = powf(2.0f, -16); |
| } else if constexpr (SampleFormat == fuchsia::media::AudioSampleFormat::SIGNED_24_IN_32) { |
| first_val = lround(static_cast<double>(first_val) / 256.0) * 256; |
| increment *= 256; |
| } |
| AudioBuffer out(format, num_frames); |
| for (size_t sample = 0; sample < out.samples().size(); ++sample) { |
| out.samples()[sample] = first_val; |
| first_val += increment; |
| if (SampleFormat == fuchsia::media::AudioSampleFormat::FLOAT && first_val > 1) { |
| first_val = -1; |
| } |
| } |
| return out; |
| } |
| |
| // Construct a stream of sinusoidal values of the given number of frames, determined by equation |
| // "buffer[idx] = magn * cosine(idx*freq/num_frames*2*M_PI + phase)". If the format has >1 channels, |
| // each channel is assigned a duplicate value. |
| // |
| // Restated: |freq| is the number of **complete sinusoidal periods** that should perfectly fit into |
| // the buffer; |magn| is a multiplier applied to the output (default value is the largest that fits |
| // into the int container, or 1.0 for float); |phase| is an offset (default value 0.0) which shifts |
| // the signal along the x-axis (value expressed in radians, so runs from -M_PI to +M_PI). |
| template <fuchsia::media::AudioSampleFormat SampleFormat> |
| AudioBuffer<SampleFormat> GenerateCosineAudio( |
| TypedFormat<SampleFormat> format, int64_t num_frames, double freq, |
| double magn = SampleFormatTraits<SampleFormat>::kUnityValue - |
| SampleFormatTraits<SampleFormat>::kSilentValue, |
| double phase = 0.0) { |
| // If frequency is 0 (constant val), phase offset causes reduced amplitude |
| FX_CHECK(freq > 0.0 || (freq == 0.0 && phase == 0.0)); |
| |
| // Freqs above num_frames/2 (Nyquist limit) will alias into lower frequencies. |
| FX_CHECK(freq * 2.0 <= static_cast<double>(num_frames)) |
| << "Buffer too short--requested frequency will be aliased"; |
| |
| AudioBuffer out(format, num_frames); |
| for (int64_t frame = 0; frame < num_frames; ++frame) { |
| // This is 2*PI * freq * (frame/num_frames), reordered for _slightly_ better precision |
| double angle = static_cast<double>(frame * 2) * freq / static_cast<double>(num_frames) * M_PI; |
| double val = magn * std::cos(angle + phase); |
| |
| switch (SampleFormat) { |
| case fuchsia::media::AudioSampleFormat::UNSIGNED_8: |
| val = round(val) + 0x80; |
| break; |
| case fuchsia::media::AudioSampleFormat::SIGNED_16: |
| val = round(val); |
| break; |
| case fuchsia::media::AudioSampleFormat::SIGNED_24_IN_32: |
| val = round(val / 256.0) * 256; |
| break; |
| case fuchsia::media::AudioSampleFormat::FLOAT: |
| break; |
| } |
| for (int32_t chan = 0; chan < format.channels(); chan++) { |
| using SampleT = typename SampleFormatTraits<SampleFormat>::SampleT; |
| out.samples()[out.SampleIndex(frame, chan)] = static_cast<SampleT>(val); |
| } |
| } |
| return out; |
| } |
| |
| // Load audio from a WAV file. |
| template <fuchsia::media::AudioSampleFormat SampleFormat> |
| AudioBuffer<SampleFormat> LoadWavFile(const std::string& file_name) { |
| auto open_result = WavReader::Open(file_name); |
| FX_CHECK(open_result.is_ok()) << "Open(" << file_name << ") failed with status " |
| << open_result.error(); |
| |
| auto r = open_result.take_value(); |
| FX_CHECK(r->sample_format() == SampleFormat) |
| << "Read(" << file_name << ") failed, expected format " << static_cast<int>(SampleFormat) |
| << ", got " << static_cast<int>(r->sample_format()); |
| |
| auto format = Format::Create<SampleFormat>(r->channel_count(), r->frame_rate()).take_value(); |
| AudioBuffer out(format, r->length_in_frames()); |
| auto size = r->length_in_bytes(); |
| auto read_result = r->Read(&out.samples()[0], size); |
| |
| FX_CHECK(read_result.is_ok()) << "Read(" << file_name |
| << ") failed, error: " << read_result.error(); |
| FX_CHECK(size == read_result.value()) << "Read(" << file_name << ") failed, expected " << size |
| << " bytes, got " << read_result.value(); |
| |
| return out; |
| } |
| |
| // Copy the given slice to a buffer that is padded with silence up to the nearest power-of-2. |
| template <fuchsia::media::AudioSampleFormat SampleFormat> |
| AudioBuffer<SampleFormat> PadToNearestPower2(AudioBufferSlice<SampleFormat> in) { |
| int64_t pow2 = 1; |
| while (pow2 < in.NumFrames()) { |
| pow2 <<= 1; |
| } |
| AudioBuffer<SampleFormat> buf(in.format(), pow2); |
| std::copy(in.buf()->samples().begin(), in.buf()->samples().end(), buf.samples().begin()); |
| std::fill(buf.samples().begin() + in.NumSamples(), buf.samples().end(), |
| SampleFormatTraits<SampleFormat>::kSilentValue); |
| return buf; |
| } |
| |
| } // namespace media::audio |
| |
| #endif // SRC_MEDIA_AUDIO_LIB_ANALYSIS_GENERATORS_H_ |