Skip to content

Commit e62f600

Browse files
Artem TitovCommit Bot
Artem Titov
authored and
Commit Bot
committed
Extend WavReader and WavWriter API.
Add ability to read and write wav files using rtc::PlatformFile instead of file name. Bug: webrtc:8946 Change-Id: If18d9465f2155a33547f800edbdac45971a0e878 Reviewed-on: https://webrtc-review.googlesource.com/61424 Commit-Queue: Artem Titov <[email protected]> Reviewed-by: Karl Wiberg <[email protected]> Cr-Commit-Position: refs/heads/master@{#22497}
1 parent 451dfdf commit e62f600

File tree

11 files changed

+249
-16
lines changed

11 files changed

+249
-16
lines changed

Diff for: common_audio/wav_file.cc

+40-8
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "common_audio/include/audio_util.h"
1919
#include "common_audio/wav_header.h"
2020
#include "rtc_base/checks.h"
21+
#include "rtc_base/logging.h"
2122
#include "rtc_base/numerics/safe_conversions.h"
2223

2324
namespace webrtc {
@@ -47,8 +48,21 @@ std::string WavFile::FormatAsString() const {
4748
}
4849

4950
WavReader::WavReader(const std::string& filename)
50-
: file_handle_(fopen(filename.c_str(), "rb")) {
51-
RTC_CHECK(file_handle_) << "Could not open wav file for reading.";
51+
: WavReader(rtc::OpenPlatformFileReadOnly(filename)) {}
52+
53+
WavReader::WavReader(rtc::PlatformFile file) {
54+
RTC_CHECK_NE(file, rtc::kInvalidPlatformFileValue)
55+
<< "Invalid file. Could not create file handle for wav file.";
56+
file_handle_ = rtc::FdopenPlatformFile(file, "rb");
57+
if (!file_handle_) {
58+
RTC_LOG(LS_ERROR) << "Could not open wav file for reading: " << errno;
59+
// Even though we failed to open a FILE*, the file is still open
60+
// and needs to be closed.
61+
if (!rtc::ClosePlatformFile(file)) {
62+
RTC_LOG(LS_ERROR) << "Can't close file.";
63+
}
64+
FATAL() << "Could not open wav file for reading.";
65+
}
5266

5367
ReadableWavFile readable(file_handle_);
5468
WavFormat format;
@@ -110,13 +124,31 @@ void WavReader::Close() {
110124
file_handle_ = nullptr;
111125
}
112126

113-
WavWriter::WavWriter(const std::string& filename, int sample_rate,
127+
WavWriter::WavWriter(const std::string& filename,
128+
int sample_rate,
129+
size_t num_channels)
130+
// Unlike plain fopen, CreatePlatformFile takes care of filename utf8 ->
131+
// wchar conversion on windows.
132+
: WavWriter(rtc::CreatePlatformFile(filename), sample_rate, num_channels) {}
133+
134+
WavWriter::WavWriter(rtc::PlatformFile file,
135+
int sample_rate,
114136
size_t num_channels)
115-
: sample_rate_(sample_rate),
116-
num_channels_(num_channels),
117-
num_samples_(0),
118-
file_handle_(fopen(filename.c_str(), "wb")) {
119-
RTC_CHECK(file_handle_) << "Could not open wav file for writing.";
137+
: sample_rate_(sample_rate), num_channels_(num_channels), num_samples_(0) {
138+
// Handle errors from the CreatePlatformFile call in above constructor.
139+
RTC_CHECK_NE(file, rtc::kInvalidPlatformFileValue)
140+
<< "Invalid file. Could not create wav file.";
141+
file_handle_ = rtc::FdopenPlatformFile(file, "wb");
142+
if (!file_handle_) {
143+
RTC_LOG(LS_ERROR) << "Could not open wav file for writing.";
144+
// Even though we failed to open a FILE*, the file is still open
145+
// and needs to be closed.
146+
if (!rtc::ClosePlatformFile(file)) {
147+
RTC_LOG(LS_ERROR) << "Can't close file.";
148+
}
149+
FATAL() << "Could not open wav file for writing.";
150+
}
151+
120152
RTC_CHECK(CheckWavParameters(num_channels_, sample_rate_, kWavFormat,
121153
kBytesPerSample, num_samples_));
122154

Diff for: common_audio/wav_file.h

+7
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include <string>
1919

2020
#include "rtc_base/constructormagic.h"
21+
#include "rtc_base/platform_file.h"
2122

2223
namespace webrtc {
2324

@@ -41,6 +42,9 @@ class WavWriter final : public WavFile {
4142
// Open a new WAV file for writing.
4243
WavWriter(const std::string& filename, int sample_rate, size_t num_channels);
4344

45+
// Open a new WAV file for writing.
46+
WavWriter(rtc::PlatformFile file, int sample_rate, size_t num_channels);
47+
4448
// Close the WAV file, after writing its header.
4549
~WavWriter() override;
4650

@@ -70,6 +74,9 @@ class WavReader final : public WavFile {
7074
// Opens an existing WAV file for reading.
7175
explicit WavReader(const std::string& filename);
7276

77+
// Opens an existing WAV file for reading.
78+
explicit WavReader(rtc::PlatformFile file);
79+
7380
// Close the WAV file.
7481
~WavReader() override;
7582

Diff for: common_audio/wav_file_unittest.cc

+68
Original file line numberDiff line numberDiff line change
@@ -174,4 +174,72 @@ TEST(WavWriterTest, LargeFile) {
174174
}
175175
}
176176

177+
// Write a tiny WAV file with the the std::FILE interface and verify the
178+
// result.
179+
TEST(WavWriterTest, CPPFileDescriptor) {
180+
const std::string outfile = test::OutputPath() + "wavtest1.wav";
181+
static constexpr size_t kNumSamples = 3;
182+
{
183+
WavWriter w(rtc::CreatePlatformFile(outfile), 14099, 1);
184+
EXPECT_EQ(14099, w.sample_rate());
185+
EXPECT_EQ(1u, w.num_channels());
186+
EXPECT_EQ(0u, w.num_samples());
187+
w.WriteSamples(kSamples, kNumSamples);
188+
EXPECT_EQ(kNumSamples, w.num_samples());
189+
}
190+
// Write some extra "metadata" to the file that should be silently ignored
191+
// by WavReader. We don't use WavWriter directly for this because it doesn't
192+
// support metadata.
193+
static constexpr uint8_t kMetadata[] = {101, 202};
194+
{
195+
FILE* f = fopen(outfile.c_str(), "ab");
196+
ASSERT_TRUE(f);
197+
ASSERT_EQ(1u, fwrite(kMetadata, sizeof(kMetadata), 1, f));
198+
fclose(f);
199+
}
200+
static const uint8_t kExpectedContents[] = {
201+
// clang-format off
202+
'R', 'I', 'F', 'F',
203+
42, 0, 0, 0, // size of whole file - 8: 6 + 44 - 8
204+
'W', 'A', 'V', 'E',
205+
'f', 'm', 't', ' ',
206+
16, 0, 0, 0, // size of fmt block - 8: 24 - 8
207+
1, 0, // format: PCM (1)
208+
1, 0, // channels: 1
209+
0x13, 0x37, 0, 0, // sample rate: 14099
210+
0x26, 0x6e, 0, 0, // byte rate: 2 * 14099
211+
2, 0, // block align: NumChannels * BytesPerSample
212+
16, 0, // bits per sample: 2 * 8
213+
'd', 'a', 't', 'a',
214+
6, 0, 0, 0, // size of payload: 6
215+
0, 0, // first sample: 0.0
216+
10, 0, // second sample: 10.0
217+
0xff, 0x7f, // third sample: 4e4 (saturated)
218+
kMetadata[0], kMetadata[1],
219+
// clang-format on
220+
};
221+
static constexpr size_t kContentSize =
222+
kWavHeaderSize + kNumSamples * sizeof(int16_t) + sizeof(kMetadata);
223+
static_assert(sizeof(kExpectedContents) == kContentSize, "");
224+
EXPECT_EQ(kContentSize, test::GetFileSize(outfile));
225+
FILE* f = fopen(outfile.c_str(), "rb");
226+
ASSERT_TRUE(f);
227+
uint8_t contents[kContentSize];
228+
ASSERT_EQ(1u, fread(contents, kContentSize, 1, f));
229+
EXPECT_EQ(0, fclose(f));
230+
EXPECT_EQ(0, memcmp(kExpectedContents, contents, kContentSize));
231+
232+
{
233+
WavReader r(rtc::OpenPlatformFileReadOnly(outfile));
234+
EXPECT_EQ(14099, r.sample_rate());
235+
EXPECT_EQ(1u, r.num_channels());
236+
EXPECT_EQ(kNumSamples, r.num_samples());
237+
static constexpr float kTruncatedSamples[] = {0.0, 10.0, 32767.0};
238+
float samples[kNumSamples];
239+
EXPECT_EQ(kNumSamples, r.ReadSamples(kNumSamples, samples));
240+
EXPECT_EQ(0, memcmp(kTruncatedSamples, samples, sizeof(samples)));
241+
EXPECT_EQ(0u, r.ReadSamples(kNumSamples, samples));
242+
}
243+
}
244+
177245
} // namespace webrtc

Diff for: modules/audio_processing/aec/echo_cancellation.cc

+4-4
Original file line numberDiff line numberDiff line change
@@ -677,7 +677,7 @@ static int ProcessNormal(Aec* aecInst,
677677
}
678678

679679
static void ProcessExtended(Aec* self,
680-
const float* const* near,
680+
const float* const* nearend,
681681
size_t num_bands,
682682
float* const* out,
683683
size_t num_samples,
@@ -709,8 +709,8 @@ static void ProcessExtended(Aec* self,
709709
if (!self->farend_started) {
710710
for (i = 0; i < num_bands; ++i) {
711711
// Only needed if they don't already point to the same place.
712-
if (near[i] != out[i]) {
713-
memcpy(out[i], near[i], sizeof(near[i][0]) * num_samples);
712+
if (nearend[i] != out[i]) {
713+
memcpy(out[i], nearend[i], sizeof(nearend[i][0]) * num_samples);
714714
}
715715
}
716716
return;
@@ -746,7 +746,7 @@ static void ProcessExtended(Aec* self,
746746
const int adjusted_known_delay =
747747
WEBRTC_SPL_MAX(0, self->knownDelay + delay_diff_offset);
748748

749-
WebRtcAec_ProcessFrames(self->aec, near, num_bands, num_samples,
749+
WebRtcAec_ProcessFrames(self->aec, nearend, num_bands, num_samples,
750750
adjusted_known_delay, out);
751751
}
752752
}

Diff for: rtc_base/BUILD.gn

+1
Original file line numberDiff line numberDiff line change
@@ -1194,6 +1194,7 @@ if (rtc_include_tests) {
11941194
"numerics/safe_minmax_unittest.cc",
11951195
"onetimeevent_unittest.cc",
11961196
"pathutils_unittest.cc",
1197+
"platform_file_unittest.cc",
11971198
"platform_thread_unittest.cc",
11981199
"random_unittest.cc",
11991200
"rate_limiter_unittest.cc",

Diff for: rtc_base/platform_file.cc

+17-4
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,21 @@
2323

2424
namespace rtc {
2525

26+
FILE* FdopenPlatformFileForWriting(PlatformFile file) {
27+
return FdopenPlatformFile(file, "w");
28+
}
29+
2630
#if defined(WEBRTC_WIN)
2731
const PlatformFile kInvalidPlatformFileValue = INVALID_HANDLE_VALUE;
2832

29-
FILE* FdopenPlatformFileForWriting(PlatformFile file) {
33+
FILE* FdopenPlatformFile(PlatformFile file, const char* modes) {
3034
if (file == kInvalidPlatformFileValue)
3135
return nullptr;
3236
int fd = _open_osfhandle(reinterpret_cast<intptr_t>(file), 0);
3337
if (fd < 0)
3438
return nullptr;
3539

36-
return _fdopen(fd, "w");
40+
return _fdopen(fd, modes);
3741
}
3842

3943
bool ClosePlatformFile(PlatformFile file) {
@@ -49,6 +53,11 @@ PlatformFile OpenPlatformFile(const std::string& path) {
4953
nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
5054
}
5155

56+
PlatformFile OpenPlatformFileReadOnly(const std::string& path) {
57+
return ::CreateFile(ToUtf16(path).c_str(), GENERIC_READ, FILE_SHARE_READ,
58+
nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
59+
}
60+
5261
PlatformFile CreatePlatformFile(const std::string& path) {
5362
return ::CreateFile(ToUtf16(path).c_str(), GENERIC_READ | GENERIC_WRITE, 0,
5463
nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
@@ -58,8 +67,8 @@ PlatformFile CreatePlatformFile(const std::string& path) {
5867

5968
const PlatformFile kInvalidPlatformFileValue = -1;
6069

61-
FILE* FdopenPlatformFileForWriting(PlatformFile file) {
62-
return fdopen(file, "w");
70+
FILE* FdopenPlatformFile(PlatformFile file, const char* modes) {
71+
return fdopen(file, modes);
6372
}
6473

6574
bool ClosePlatformFile(PlatformFile file) {
@@ -74,6 +83,10 @@ PlatformFile OpenPlatformFile(const std::string& path) {
7483
return ::open(path.c_str(), O_RDWR);
7584
}
7685

86+
PlatformFile OpenPlatformFileReadOnly(const std::string& path) {
87+
return ::open(path.c_str(), O_RDONLY);
88+
}
89+
7790
PlatformFile CreatePlatformFile(const std::string& path) {
7891
return ::open(path.c_str(), O_CREAT | O_TRUNC | O_RDWR, S_IRUSR | S_IWUSR);
7992
}

Diff for: rtc_base/platform_file.h

+9
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ extern const PlatformFile kInvalidPlatformFileValue;
3535
// the PlatformFile should no longer be used.
3636
FILE* FdopenPlatformFileForWriting(PlatformFile file);
3737

38+
// Associates a standard FILE stream with an existing PlatformFile.
39+
// Note that after this function has returned a valid FILE stream,
40+
// the PlatformFile should no longer be used.
41+
FILE* FdopenPlatformFile(PlatformFile file, const char* modes);
42+
3843
// Closes a PlatformFile. Returns true on success, false on failure.
3944
// Don't use ClosePlatformFile to close a file opened with FdopenPlatformFile.
4045
// Use fclose instead.
@@ -47,6 +52,10 @@ bool RemoveFile(const std::string& path);
4752
// instead.
4853
PlatformFile OpenPlatformFile(const std::string& path);
4954

55+
// Opens a file for reading only. You might want to use base/file.h
56+
// instead.
57+
PlatformFile OpenPlatformFileReadOnly(const std::string& path);
58+
5059
// Creates a new file for reading and writing. If the file already exists it
5160
// will be overwritten. You might want to use base/file.h instead.
5261
PlatformFile CreatePlatformFile(const std::string& path);

Diff for: rtc_base/platform_file_unittest.cc

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
3+
*
4+
* Use of this source code is governed by a BSD-style license
5+
* that can be found in the LICENSE file in the root of the source
6+
* tree. An additional intellectual property rights grant can be found
7+
* in the file PATENTS. All contributing project authors may
8+
* be found in the AUTHORS file in the root of the source tree.
9+
*/
10+
11+
#include "rtc_base/platform_file.h"
12+
#include "test/gtest.h"
13+
#include "test/testsupport/fileutils.h"
14+
15+
namespace rtc {
16+
17+
void FillWithDummyDataAndClose(FILE* const file, const std::string& filename) {
18+
EXPECT_GT(fprintf(file, "%s", "Dummy data"), 0)
19+
<< "Failed to write to file: " << filename;
20+
fclose(file);
21+
}
22+
23+
TEST(PlatformFileTest, CreateWriteAndDelete) {
24+
const std::string filename = webrtc::test::GenerateTempFilename(
25+
webrtc::test::OutputPath(), ".testfile");
26+
const PlatformFile fd = rtc::CreatePlatformFile(filename);
27+
ASSERT_NE(fd, rtc::kInvalidPlatformFileValue)
28+
<< "Failed to create file descriptor for file: " << filename;
29+
FILE* const file = rtc::FdopenPlatformFile(fd, "w");
30+
ASSERT_TRUE(file != nullptr) << "Failed to open file: " << filename;
31+
FillWithDummyDataAndClose(file, filename);
32+
webrtc::test::RemoveFile(filename);
33+
}
34+
35+
TEST(PlatformFileTest, OpenExistingWriteAndDelete) {
36+
const std::string filename = webrtc::test::GenerateTempFilename(
37+
webrtc::test::OutputPath(), ".testfile");
38+
39+
// Create file with dummy data.
40+
FILE* file = fopen(filename.c_str(), "wb");
41+
ASSERT_TRUE(file != nullptr) << "Failed to open file: " << filename;
42+
FillWithDummyDataAndClose(file, filename);
43+
44+
// Open it for write, write and delete.
45+
const PlatformFile fd = rtc::OpenPlatformFile(filename);
46+
ASSERT_NE(fd, rtc::kInvalidPlatformFileValue)
47+
<< "Failed to open file descriptor for file: " << filename;
48+
file = rtc::FdopenPlatformFile(fd, "w");
49+
ASSERT_TRUE(file != nullptr) << "Failed to open file: " << filename;
50+
FillWithDummyDataAndClose(file, filename);
51+
webrtc::test::RemoveFile(filename);
52+
}
53+
54+
TEST(PlatformFileTest, OpenExistingReadOnlyAndDelete) {
55+
const std::string filename = webrtc::test::GenerateTempFilename(
56+
webrtc::test::OutputPath(), ".testfile");
57+
58+
// Create file with dummy data.
59+
FILE* file = fopen(filename.c_str(), "wb");
60+
ASSERT_TRUE(file != nullptr) << "Failed to open file: " << filename;
61+
FillWithDummyDataAndClose(file, filename);
62+
63+
// Open it for read, read and delete.
64+
const PlatformFile fd = rtc::OpenPlatformFileReadOnly(filename);
65+
ASSERT_NE(fd, rtc::kInvalidPlatformFileValue)
66+
<< "Failed to open file descriptor for file: " << filename;
67+
file = rtc::FdopenPlatformFile(fd, "r");
68+
ASSERT_TRUE(file != nullptr) << "Failed to open file: " << filename;
69+
70+
int buf[]{0};
71+
ASSERT_GT(fread(&buf, 1, 1, file), 0u)
72+
<< "Failed to read from file: " << filename;
73+
fclose(file);
74+
webrtc::test::RemoveFile(filename);
75+
}
76+
77+
} // namespace rtc

Diff for: test/testsupport/fileutils.cc

+7
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,13 @@ std::string TempFilename(const std::string &dir, const std::string &prefix) {
215215
#endif
216216
}
217217

218+
std::string GenerateTempFilename(const std::string& dir,
219+
const std::string& prefix) {
220+
std::string filename = TempFilename(dir, prefix);
221+
RemoveFile(filename);
222+
return filename;
223+
}
224+
218225
rtc::Optional<std::vector<std::string>> ReadDirectory(std::string path) {
219226
if (path.length() == 0)
220227
return rtc::Optional<std::vector<std::string>>();

Diff for: test/testsupport/fileutils.h

+6
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,14 @@ std::string OutputPath();
4040

4141
// Generates an empty file with a unique name in the specified directory and
4242
// returns the file name and path.
43+
// TODO(titovartem) rename to TempFile and next method to TempFilename
4344
std::string TempFilename(const std::string &dir, const std::string &prefix);
4445

46+
// Generates a unique file name that can be used for file creation. Doesn't
47+
// create any files.
48+
std::string GenerateTempFilename(const std::string& dir,
49+
const std::string& prefix);
50+
4551
// Returns a path to a resource file for the currently executing platform.
4652
// Adapts to what filenames are currently present in the
4753
// [project-root]/resources/ dir.

0 commit comments

Comments
 (0)