Skip to content

Commit 83904a2

Browse files
eoleinikmeta-codesync[bot]
authored andcommitted
{Feature} Core - Read raw PCM/S16 audio streams in AudioPlayer
Summary: Explanation: `AudioPlayer::onAudioRead` previously handled only OPUS/`S16` and PCM/`S32` audio and threw `Unsupported audio sample format` for raw PCM/`S16`. Because the data provider reads one data record per audio stream when opening a file, a single PCM/`S16` audio stream caused the entire file (video included) to fail to open. This adds a PCM/`S16` branch to `AudioPlayer::onAudioRead`: it reads the `int16` samples, widens them to `int32`, and reports `maxAmplitude` as `INT16_MAX`. This mirrors the existing OPUS path, which also originates from `S16` samples, so consumers that normalize by `maxAmplitude` produce identical results whether the stream is stored as `S16` or `S32`. Reproducibility: Open a VRS file that contains a PCM/`S16` audio stream with `create_vrs_data_provider` and read the audio stream. Before this change it threw `Unsupported audio sample format: int16le`; now it succeeds and returns correctly scaled samples. Reviewed By: SeaOtocinclus Differential Revision: D109741204 fbshipit-source-id: 8f07c9da72116d6cdab720ff9741ba1d9409b120
1 parent 5763e6d commit 83904a2

2 files changed

Lines changed: 165 additions & 0 deletions

File tree

core/data_provider/players/AudioPlayer.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,20 @@ bool AudioPlayer::onAudioRead(
8686
data_.maxAmplitude = static_cast<double>(std::numeric_limits<int32_t>::max());
8787
callback_(data_, dataRecord_, configRecord_, verbose_);
8888
}
89+
}
90+
// Raw PCM S16: widen to int32 and report INT16_MAX as the amplitude scale, so
91+
// amplitude-normalizing consumers stay correct regardless of on-disk width.
92+
else if (
93+
audioSpec.getAudioFormat() == vrs::AudioFormat::PCM &&
94+
audioSpec.getSampleFormat() == vrs::AudioSampleFormat::S16_LE) {
95+
data_.data.clear();
96+
std::vector<int16_t> rawVec(
97+
audioSpec.getSampleCount() * static_cast<size_t>(audioSpec.getChannelCount()));
98+
if (r.reader->read(rawVec) == 0) {
99+
data_.data.assign(rawVec.begin(), rawVec.end());
100+
data_.maxAmplitude = static_cast<double>(std::numeric_limits<int16_t>::max());
101+
callback_(data_, dataRecord_, configRecord_, verbose_);
102+
}
89103
} else {
90104
throw std::runtime_error(
91105
fmt::format("Unsupported audio sample format: {}", audioSpec.getSampleFormatAsString()));
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include <data_provider/data_layout/AudioMetadata.h>
18+
#include <data_provider/players/AudioPlayer.h>
19+
20+
#include <gtest/gtest.h>
21+
22+
#include <vrs/DataSource.h>
23+
#include <vrs/RecordFileReader.h>
24+
#include <vrs/RecordFileWriter.h>
25+
#include <vrs/RecordFormat.h>
26+
#include <vrs/Recordable.h>
27+
#include <vrs/os/Utils.h>
28+
29+
#include <cstdint>
30+
#include <limits>
31+
#include <vector>
32+
33+
using namespace projectaria::tools::data_provider;
34+
35+
namespace {
36+
37+
constexpr uint8_t kNumChannels = 2;
38+
constexpr uint32_t kSampleRate = 16000;
39+
40+
// Writes a single PCM/S16 stereo audio stream using the same datalayout the Aria
41+
// recorder produces. The audio content block is the trailing block, so VRS
42+
// derives the sample count from the remaining payload size (the recorder omits
43+
// an explicit sample-count field).
44+
class PcmS16AudioRecordable : public vrs::Recordable {
45+
public:
46+
explicit PcmS16AudioRecordable(std::vector<int16_t> interleavedSamples)
47+
: vrs::Recordable(vrs::RecordableTypeId::StereoAudioRecordableClass, "test/microphone"),
48+
samples_(std::move(interleavedSamples)) {
49+
addRecordFormat(vrs::Record::Type::CONFIGURATION, 1, config_.getContentBlock(), {&config_});
50+
addRecordFormat(
51+
vrs::Record::Type::DATA,
52+
1,
53+
data_.getContentBlock() + vrs::ContentBlock(vrs::AudioFormat::PCM, kNumChannels),
54+
{&data_});
55+
}
56+
57+
const vrs::Record* createConfigurationRecord() override {
58+
config_.streamId.set(0);
59+
config_.numChannels.set(kNumChannels);
60+
config_.sampleRate.set(kSampleRate);
61+
config_.sampleFormat.set(static_cast<uint8_t>(vrs::AudioSampleFormat::S16_LE));
62+
return createRecord(0.0, vrs::Record::Type::CONFIGURATION, 1, vrs::DataSource(config_));
63+
}
64+
65+
const vrs::Record* createStateRecord() override {
66+
return createRecord(0.0, vrs::Record::Type::STATE, 1);
67+
}
68+
69+
void writeDataRecord() {
70+
const auto numFrames = static_cast<uint32_t>(samples_.size() / kNumChannels);
71+
std::vector<int64_t> captureTimestampsNs(numFrames);
72+
for (uint32_t i = 0; i < numFrames; ++i) {
73+
captureTimestampsNs[i] = static_cast<int64_t>(i);
74+
}
75+
data_.captureTimestampsNs.stage(captureTimestampsNs);
76+
data_.audioMuted.set(0);
77+
createRecord(
78+
1.0,
79+
vrs::Record::Type::DATA,
80+
1,
81+
vrs::DataSource(
82+
data_, vrs::DataSourceChunk(samples_.data(), samples_.size() * sizeof(int16_t))));
83+
}
84+
85+
private:
86+
datalayout::AudioConfigRecordMetadata config_;
87+
datalayout::AudioDataRecordMetadata data_;
88+
std::vector<int16_t> samples_;
89+
};
90+
91+
// Removes the temp file on scope exit so an early ASSERT failure cannot leak it.
92+
struct TempFileGuard {
93+
std::string path;
94+
~TempFileGuard() {
95+
if (!path.empty()) {
96+
vrs::os::remove(path);
97+
}
98+
}
99+
};
100+
101+
void writeTempPcmS16Vrs(const std::string& path, const std::vector<int16_t>& samples) {
102+
vrs::RecordFileWriter writer;
103+
PcmS16AudioRecordable recordable(samples);
104+
writer.addRecordable(&recordable);
105+
recordable.createConfigurationRecord();
106+
recordable.createStateRecord();
107+
recordable.writeDataRecord();
108+
ASSERT_EQ(writer.writeToFile(path), 0);
109+
}
110+
111+
} // namespace
112+
113+
// AudioPlayer must read a raw PCM/S16 stream (it used to throw "Unsupported audio
114+
// sample format: int16le"), widening the int16 samples verbatim to int32 and
115+
// reporting INT16_MAX as the amplitude scale so amplitude-normalizing consumers
116+
// stay correct regardless of the on-disk sample width.
117+
TEST(AudioPlayerTest, ReadsPcmS16WidenedToInt32) {
118+
// Independently chosen interleaved [L, R] frames spanning the int16 range.
119+
const std::vector<int16_t> samples = {0, -1, 100, -100, 32767, -32768, 1234, -4321};
120+
const std::vector<int32_t> expected(samples.begin(), samples.end());
121+
122+
const std::string path =
123+
vrs::os::getUniquePath(vrs::os::getTempFolder() + "projectaria_pcm_s16_audio_test");
124+
TempFileGuard guard{path};
125+
ASSERT_NO_FATAL_FAILURE(writeTempPcmS16Vrs(path, samples));
126+
127+
vrs::RecordFileReader reader;
128+
ASSERT_EQ(reader.openFile(path), 0);
129+
130+
vrs::StreamId audioStreamId;
131+
for (const auto& id : reader.getStreams()) {
132+
if (id.getTypeId() == vrs::RecordableTypeId::StereoAudioRecordableClass) {
133+
audioStreamId = id;
134+
break;
135+
}
136+
}
137+
ASSERT_TRUE(audioStreamId.isValid());
138+
139+
AudioPlayer player(audioStreamId);
140+
reader.setStreamPlayer(audioStreamId, &player);
141+
ASSERT_EQ(reader.readAllRecords(), 0);
142+
reader.closeFile();
143+
144+
EXPECT_EQ(player.getConfigRecord().numChannels, kNumChannels);
145+
EXPECT_EQ(player.getConfigRecord().sampleRate, kSampleRate);
146+
EXPECT_EQ(player.getDetectedAudioFormat(), vrs::AudioFormat::PCM);
147+
148+
const AudioData& audioData = player.getData();
149+
EXPECT_DOUBLE_EQ(audioData.maxAmplitude, std::numeric_limits<int16_t>::max());
150+
EXPECT_EQ(audioData.data, expected);
151+
}

0 commit comments

Comments
 (0)