1 /*
2 * Copyright (c) 2023, Alliance for Open Media. All rights reserved
3 *
4 * This source code is subject to the terms of the BSD 3-Clause Clear License
5 * and the Alliance for Open Media Patent License 1.0. If the BSD 3-Clause Clear
6 * License was not distributed with this source code in the LICENSE file, you
7 * can obtain it at www.aomedia.org/license/software-license/bsd-3-c-c. If the
8 * Alliance for Open Media Patent License 1.0 was not distributed with this
9 * source code in the PATENTS file, you can obtain it at
10 * www.aomedia.org/license/patent.
11 */
12
13 #include "iamf/cli/wav_writer.h"
14
15 #include <algorithm>
16 #include <cstddef>
17 #include <cstdint>
18 #include <cstdio>
19 #include <memory>
20 #include <string>
21 #include <utility>
22 #include <vector>
23
24 #include "absl/base/nullability.h"
25 #include "absl/functional/any_invocable.h"
26 #include "absl/log/check.h"
27 #include "absl/log/log.h"
28 #include "absl/memory/memory.h"
29 #include "absl/status/status.h"
30 #include "absl/strings/str_cat.h"
31 #include "absl/types/span.h"
32 #include "iamf/cli/sample_processor_base.h"
33 #include "iamf/common/utils/macros.h"
34 #include "iamf/common/utils/sample_processing_utils.h"
35 #include "src/dsp/write_wav_file.h"
36
37 namespace iamf_tools {
38
39 namespace {
40 // Some audio to tactile functions return 0 on success and 1 on failure.
41 constexpr int kAudioToTactileResultFailure = 0;
42 constexpr int kAudioToTactileResultSuccess = 1;
43
44 // This class is implemented to consume all samples without producing output
45 // samples.
46 constexpr size_t kMaxOutputSamplesPerFrame = 0;
47
48 // Write samples for all channels.
WriteSamplesInternal(absl::Nullable<FILE * > file,size_t num_channels,int bit_depth,size_t max_num_samples_per_frame,const std::vector<uint8_t> & buffer,size_t & total_samples_accumulator)49 absl::Status WriteSamplesInternal(absl::Nullable<FILE*> file,
50 size_t num_channels, int bit_depth,
51 size_t max_num_samples_per_frame,
52 const std::vector<uint8_t>& buffer,
53 size_t& total_samples_accumulator) {
54 if (file == nullptr) {
55 // Wav writer may have been aborted.
56 return absl::FailedPreconditionError(
57 "Wav writer is not accepting samples.");
58 }
59
60 const auto buffer_size = buffer.size();
61
62 if (buffer_size == 0) {
63 // Nothing to write.
64 return absl::OkStatus();
65 }
66
67 if (buffer_size % (bit_depth * num_channels / 8) != 0) {
68 return absl::InvalidArgumentError(
69 "Must write an integer number of samples.");
70 }
71
72 // Calculate how many samples there are.
73 const int bytes_per_sample = bit_depth / 8;
74 const size_t num_total_samples = (buffer_size) / bytes_per_sample;
75 const size_t num_samples_per_channel = num_total_samples / num_channels;
76 if (num_samples_per_channel > max_num_samples_per_frame) {
77 return absl::InvalidArgumentError(
78 absl::StrCat("Too many samples per frame. The `WavWriter` is "
79 "configured with a maximum number of "
80 "samples per frame of: ",
81 max_num_samples_per_frame,
82 ". The number of samples per frame received is: ",
83 num_samples_per_channel));
84 }
85
86 int write_sample_result = kAudioToTactileResultFailure;
87 if (bit_depth == 16) {
88 // Arrange the input samples into an int16_t to match the expected input of
89 // `WriteWavSamples`.
90 std::vector<int16_t> samples(num_total_samples, 0);
91 for (int i = 0; i < num_total_samples * bytes_per_sample;
92 i += bytes_per_sample) {
93 samples[i / bytes_per_sample] = (buffer[i + 1] << 8) | buffer[i];
94 }
95
96 write_sample_result = WriteWavSamples(file, samples.data(), samples.size());
97 } else if (bit_depth == 24) {
98 // Arrange the input samples into an int32_t to match the expected input of
99 // `WriteWavSamples24Bit` with the lowest byte unused.
100 std::vector<int32_t> samples(num_total_samples, 0);
101 for (int i = 0; i < num_total_samples * bytes_per_sample;
102 i += bytes_per_sample) {
103 samples[i / bytes_per_sample] =
104 (buffer[i + 2] << 24) | buffer[i + 1] << 16 | buffer[i] << 8;
105 }
106 write_sample_result =
107 WriteWavSamples24Bit(file, samples.data(), samples.size());
108 } else if (bit_depth == 32) {
109 // Arrange the input samples into an int32_t to match the expected input of
110 // `WriteWavSamples32Bit`.
111 std::vector<int32_t> samples(num_total_samples, 0);
112 for (int i = 0; i < num_total_samples * bytes_per_sample;
113 i += bytes_per_sample) {
114 samples[i / bytes_per_sample] = buffer[i + 3] << 24 |
115 buffer[i + 2] << 16 | buffer[i + 1] << 8 |
116 buffer[i];
117 }
118 write_sample_result =
119 WriteWavSamples32Bit(file, samples.data(), samples.size());
120 } else {
121 // This should never happen because the factory method would never create
122 // an object with disallowed `bit_depth_` values.
123 LOG(FATAL) << "WavWriter only supports 16, 24, and 32-bit samples; got "
124 << bit_depth;
125 }
126
127 if (write_sample_result == kAudioToTactileResultSuccess) {
128 total_samples_accumulator += num_total_samples;
129 return absl::OkStatus();
130 }
131
132 // It's not clear why this would happen.
133 return absl::UnknownError(
134 absl::StrCat("Error writing samples to wav file. write_sample_result= ",
135 write_sample_result));
136 }
137
MaybeFinalizeFile(size_t sample_rate_hz,size_t num_channels,auto & wav_header_writer,FILE * & file,size_t & total_samples_written)138 void MaybeFinalizeFile(size_t sample_rate_hz, size_t num_channels,
139 auto& wav_header_writer, FILE*& file,
140 size_t& total_samples_written) {
141 if (file == nullptr) {
142 return;
143 }
144
145 // Finalize the temporary header based on the total number of samples written
146 // and close the file.
147 if (wav_header_writer) {
148 std::fseek(file, 0, SEEK_SET);
149 wav_header_writer(file, total_samples_written, sample_rate_hz,
150 num_channels);
151 }
152 std::fclose(file);
153 file = nullptr;
154 }
155
156 } // namespace
157
Create(const std::string & wav_filename,int num_channels,int sample_rate_hz,int bit_depth,size_t num_samples_per_frame,bool write_header)158 std::unique_ptr<WavWriter> WavWriter::Create(const std::string& wav_filename,
159 int num_channels,
160 int sample_rate_hz, int bit_depth,
161 size_t num_samples_per_frame,
162 bool write_header) {
163 // Open the file to write to.
164 LOG(INFO) << "Writer \"" << wav_filename << "\"";
165 auto* file = std::fopen(wav_filename.c_str(), "wb");
166 if (file == nullptr) {
167 LOG(ERROR).WithPerror() << "Error opening file \"" << wav_filename << "\"";
168 return nullptr;
169 }
170
171 // Write a dummy header. This will be overwritten in the destructor.
172 WavHeaderWriter wav_header_writer;
173 switch (bit_depth) {
174 case 16:
175 wav_header_writer = WriteWavHeader;
176 break;
177 case 24:
178 wav_header_writer = WriteWavHeader24Bit;
179 break;
180 case 32:
181 wav_header_writer = WriteWavHeader32Bit;
182 break;
183 default:
184 LOG(WARNING) << "This implementation does not support writing "
185 << bit_depth << "-bit wav files.";
186 std::fclose(file);
187 std::remove(wav_filename.c_str());
188 return nullptr;
189 }
190
191 // Set to an empty writer. The emptiness can be checked to skip writing the
192 // header.
193 if (!write_header) {
194 wav_header_writer = WavHeaderWriter();
195 } else if (wav_header_writer(file, 0, sample_rate_hz, num_channels) ==
196 kAudioToTactileResultFailure) {
197 LOG(ERROR) << "Error writing header of file \"" << wav_filename << "\"";
198 return nullptr;
199 }
200
201 return absl::WrapUnique(
202 new WavWriter(wav_filename, num_channels, sample_rate_hz, bit_depth,
203 num_samples_per_frame, file, std::move(wav_header_writer)));
204 }
205
~WavWriter()206 WavWriter::~WavWriter() {
207 // Finalize the header, in case the user did not call `Flush()`.
208 MaybeFinalizeFile(sample_rate_hz_, num_channels_, wav_header_writer_, file_,
209 total_samples_written_);
210 }
211
PushFrameDerived(absl::Span<const std::vector<int32_t>> time_channel_samples)212 absl::Status WavWriter::PushFrameDerived(
213 absl::Span<const std::vector<int32_t>> time_channel_samples) {
214 // Flatten down the serialized PCM for compatibility with the internal
215 // `WriteSamplesInternal` function.
216 const size_t num_ticks = time_channel_samples.size();
217 const size_t num_channels =
218 time_channel_samples.empty() ? 0 : time_channel_samples[0].size();
219 if (!std::all_of(
220 time_channel_samples.begin(), time_channel_samples.end(),
221 [&](const auto& tick) { return tick.size() == num_channels; })) {
222 return absl::InvalidArgumentError(
223 "All ticks must have the same number of channels.");
224 }
225
226 std::vector<uint8_t> samples_as_pcm(num_channels * num_ticks * bit_depth_ / 8,
227 0);
228 size_t write_position = 0;
229 for (const auto& tick : time_channel_samples) {
230 for (const auto& channel_sample : tick) {
231 RETURN_IF_NOT_OK(WritePcmSample(channel_sample, bit_depth_,
232 /*big_endian=*/false,
233 samples_as_pcm.data(), write_position));
234 }
235 }
236
237 return WriteSamplesInternal(file_, num_channels_, bit_depth_,
238 max_input_samples_per_frame_, samples_as_pcm,
239 total_samples_written_);
240 }
241
FlushDerived()242 absl::Status WavWriter::FlushDerived() {
243 // No more samples are coming, finalize the header and close the file.
244 MaybeFinalizeFile(sample_rate_hz_, num_channels_, wav_header_writer_, file_,
245 total_samples_written_);
246 return absl::OkStatus();
247 }
248
WritePcmSamples(const std::vector<uint8_t> & buffer)249 absl::Status WavWriter::WritePcmSamples(const std::vector<uint8_t>& buffer) {
250 return WriteSamplesInternal(file_, num_channels_, bit_depth_,
251 max_input_samples_per_frame_, buffer,
252 total_samples_written_);
253 }
254
Abort()255 void WavWriter::Abort() {
256 std::fclose(file_);
257 std::remove(filename_to_remove_.c_str());
258 file_ = nullptr;
259 }
260
WavWriter(const std::string & filename_to_remove,int num_channels,int sample_rate_hz,int bit_depth,size_t num_samples_per_frame,FILE * file,WavHeaderWriter wav_header_writer)261 WavWriter::WavWriter(const std::string& filename_to_remove, int num_channels,
262 int sample_rate_hz, int bit_depth,
263 size_t num_samples_per_frame, FILE* file,
264 WavHeaderWriter wav_header_writer)
265 : SampleProcessorBase(num_samples_per_frame, num_channels,
266 kMaxOutputSamplesPerFrame),
267 sample_rate_hz_(sample_rate_hz),
268 bit_depth_(bit_depth),
269 total_samples_written_(0),
270 file_(file),
271 filename_to_remove_(filename_to_remove),
272 wav_header_writer_(std::move(wav_header_writer)) {}
273
274 } // namespace iamf_tools
275