-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathreferenceReader_main.cpp
More file actions
193 lines (165 loc) · 6.7 KB
/
Copy pathreferenceReader_main.cpp
File metadata and controls
193 lines (165 loc) · 6.7 KB
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
/******************************************************************************
* *
* Copyright (C) 2024 Acoustic Echo Cancellation Component *
* All Rights Reserved. *
* *
******************************************************************************/
#include "referenceReader.hpp"
#include <atomic>
#include <chrono>
#include <csignal>
#include <cstdint>
#include <ctime>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <mutex>
#include <sstream>
#include <yarp/os/LogStream.h>
#include <yarp/os/Network.h>
#include <yarp/os/Time.h>
namespace
{
std::atomic<bool> g_shouldExit{false};
void signalHandler(int)
{
// Convert SIGINT/SIGTERM into a cooperative shutdown request.
g_shouldExit = true;
}
// Write a 16-bit integer in little-endian order for WAV headers.
void writeLittleEndian16(std::ostream &stream, std::uint16_t value)
{
stream.put(static_cast<char>(value & 0xff));
stream.put(static_cast<char>((value >> 8) & 0xff));
}
// Write a 32-bit integer in little-endian order for WAV headers.
void writeLittleEndian32(std::ostream &stream, std::uint32_t value)
{
stream.put(static_cast<char>(value & 0xff));
stream.put(static_cast<char>((value >> 8) & 0xff));
stream.put(static_cast<char>((value >> 16) & 0xff));
stream.put(static_cast<char>((value >> 24) & 0xff));
}
// Format a timestamp that can be embedded into filenames.
std::string makeTimestampString(const std::chrono::system_clock::time_point &timePoint)
{
const auto time = std::chrono::system_clock::to_time_t(timePoint);
const auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(timePoint.time_since_epoch()) % std::chrono::seconds(1);
std::tm localTime{};
#if defined(_WIN32)
localtime_s(&localTime, &time);
#else
localtime_r(&time, &localTime);
#endif
std::ostringstream stream;
stream << std::put_time(&localTime, "%Y%m%d_%H%M%S")
<< '_' << std::setw(3) << std::setfill('0') << milliseconds.count();
return stream.str();
}
bool saveWavFile(const std::filesystem::path &outputPath,
const std::vector<short> &samples,
int sampleRate,
int channelCount)
{
// Serialize a mono or multi-channel PCM WAV file to disk.
std::ofstream outputFile(outputPath, std::ios::binary);
if (!outputFile)
{
yError() << "[referenceReader] Unable to open output file:" << outputPath.string();
return false;
}
const std::uint32_t outputSampleRate = static_cast<std::uint32_t>(sampleRate > 0 ? sampleRate : 48000);
const std::uint16_t channels = static_cast<std::uint16_t>(channelCount > 0 ? channelCount : 1);
const std::uint16_t bitsPerSample = 16;
const std::uint16_t blockAlign = static_cast<std::uint16_t>(channels * (bitsPerSample / 8));
const std::uint32_t byteRate = outputSampleRate * blockAlign;
const std::uint32_t dataSize = static_cast<std::uint32_t>(samples.size() * sizeof(std::int16_t));
const std::uint32_t riffChunkSize = 36 + dataSize;
outputFile.write("RIFF", 4);
writeLittleEndian32(outputFile, riffChunkSize);
outputFile.write("WAVE", 4);
outputFile.write("fmt ", 4);
writeLittleEndian32(outputFile, 16);
writeLittleEndian16(outputFile, 1);
writeLittleEndian16(outputFile, channels);
writeLittleEndian32(outputFile, outputSampleRate);
writeLittleEndian32(outputFile, byteRate);
writeLittleEndian16(outputFile, blockAlign);
writeLittleEndian16(outputFile, bitsPerSample);
outputFile.write("data", 4);
writeLittleEndian32(outputFile, dataSize);
for (short sample : samples)
{
writeLittleEndian16(outputFile, static_cast<std::uint16_t>(static_cast<std::int16_t>(sample)));
}
return static_cast<bool>(outputFile);
}
} // namespace
int main(int argc, char *argv[])
{
(void)argc;
(void)argv;
// Install signal handlers before any blocking work starts.
std::signal(SIGINT, signalHandler);
std::signal(SIGTERM, signalHandler);
// Make sure YARP is reachable before opening the reader port.
yarp::os::Network yarp;
if (!yarp.checkNetwork())
{
yError() << "[referenceReader] YARP network is not available";
return EXIT_FAILURE;
}
ReferenceReader reader;
if (!reader.open())
{
yError() << "[referenceReader] Failed to open reference reader";
return EXIT_FAILURE;
}
// Run until the user presses ctrl+c, then drain and save everything collected.
yInfo() << "[referenceReader] Listening on" << reader.portName();
yInfo() << "[referenceReader] Press ctrl+c to save queued reference audio and exit";
while (!g_shouldExit.load())
{
yarp::os::Time::delay(0.1);
}
const auto saveStart = std::chrono::steady_clock::now();
// Stop incoming reads before draining the queue to disk.
reader.close();
// Concatenate every queued reference block into one PCM buffer.
std::vector<short> queuedSamples;
int sampleRate = 0;
std::vector<short> blockSamples;
int blockSampleRate = 0;
while (reader.tryPopBlock(blockSamples, blockSampleRate))
{
if (sampleRate <= 0 && blockSampleRate > 0)
{
sampleRate = blockSampleRate;
}
queuedSamples.insert(queuedSamples.end(), blockSamples.begin(), blockSamples.end());
}
const auto outputDirectory = std::filesystem::path("./reference-reader-recordings");
std::error_code errorCode;
std::filesystem::create_directories(outputDirectory, errorCode);
if (errorCode)
{
yError() << "[referenceReader] Unable to create output directory:" << outputDirectory.string() << errorCode.message();
return EXIT_FAILURE;
}
// Use the current time in the filename so each capture is unique.
const auto captureTime = std::chrono::system_clock::now();
const auto outputPath = outputDirectory / ("reference_queue_" + makeTimestampString(captureTime) + ".wav");
if (!saveWavFile(outputPath, queuedSamples, sampleRate, 1))
{
yError() << "[referenceReader] Failed to save queued reference audio";
return EXIT_FAILURE;
}
// Report how long the flush-to-disk step took.
const auto saveDurationMs = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - saveStart)
.count();
yInfo() << "[referenceReader] Saved" << queuedSamples.size() << "samples to" << outputPath.string()
<< "in" << saveDurationMs << "ms";
return EXIT_SUCCESS;
}